@switchbot/homebridge-switchbot 5.0.0-beta.107 → 5.0.0-beta.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/TODO.md +564 -0
  2. package/config.schema.json +100 -25
  3. package/dist/device-types.d.ts +15 -0
  4. package/dist/device-types.d.ts.map +1 -0
  5. package/dist/device-types.js +145 -0
  6. package/dist/device-types.js.map +1 -0
  7. package/dist/deviceFactory.d.ts +1 -1
  8. package/dist/deviceFactory.d.ts.map +1 -1
  9. package/dist/deviceFactory.js +55 -17
  10. package/dist/deviceFactory.js.map +1 -1
  11. package/dist/devices/genericDevice.d.ts +1 -1
  12. package/dist/devices/genericDevice.d.ts.map +1 -1
  13. package/dist/devices/genericDevice.js +4 -2
  14. package/dist/devices/genericDevice.js.map +1 -1
  15. package/dist/homebridge-ui/endpoints/config.d.ts +6 -0
  16. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
  17. package/dist/homebridge-ui/endpoints/config.js +73 -0
  18. package/dist/homebridge-ui/endpoints/config.js.map +1 -0
  19. package/dist/homebridge-ui/endpoints/credentials.d.ts +6 -0
  20. package/dist/homebridge-ui/endpoints/credentials.d.ts.map +1 -0
  21. package/dist/homebridge-ui/endpoints/credentials.js +65 -0
  22. package/dist/homebridge-ui/endpoints/credentials.js.map +1 -0
  23. package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
  24. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
  25. package/dist/homebridge-ui/endpoints/devices.js +285 -0
  26. package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
  27. package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
  28. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
  29. package/dist/homebridge-ui/endpoints/discovery.js +171 -0
  30. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
  31. package/dist/homebridge-ui/public/index.html +144 -588
  32. package/dist/homebridge-ui/public/js/api.d.ts +15 -0
  33. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
  34. package/dist/homebridge-ui/public/js/api.js +164 -0
  35. package/dist/homebridge-ui/public/js/api.js.map +1 -0
  36. package/dist/homebridge-ui/public/js/api.ts +189 -0
  37. package/dist/homebridge-ui/public/js/app.d.ts +2 -0
  38. package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
  39. package/dist/homebridge-ui/public/js/app.js +1069 -0
  40. package/dist/homebridge-ui/public/js/app.js.map +7 -0
  41. package/dist/homebridge-ui/public/js/app.ts +21 -0
  42. package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
  43. package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
  44. package/dist/homebridge-ui/public/js/constants.js +2 -0
  45. package/dist/homebridge-ui/public/js/constants.js.map +1 -0
  46. package/dist/homebridge-ui/public/js/constants.ts +1 -0
  47. package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
  48. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
  49. package/dist/homebridge-ui/public/js/credentials.js +96 -0
  50. package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
  51. package/dist/homebridge-ui/public/js/credentials.ts +100 -0
  52. package/dist/homebridge-ui/public/js/devices-delete.d.ts +2 -0
  53. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
  54. package/dist/homebridge-ui/public/js/devices-delete.js +99 -0
  55. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
  56. package/dist/homebridge-ui/public/js/devices-delete.ts +113 -0
  57. package/dist/homebridge-ui/public/js/devices.d.ts +8 -0
  58. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
  59. package/dist/homebridge-ui/public/js/devices.js +71 -0
  60. package/dist/homebridge-ui/public/js/devices.js.map +1 -0
  61. package/dist/homebridge-ui/public/js/devices.ts +78 -0
  62. package/dist/homebridge-ui/public/js/discovery.d.ts +3 -0
  63. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
  64. package/dist/homebridge-ui/public/js/discovery.js +107 -0
  65. package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
  66. package/dist/homebridge-ui/public/js/discovery.ts +123 -0
  67. package/dist/homebridge-ui/public/js/logger.d.ts +6 -0
  68. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
  69. package/dist/homebridge-ui/public/js/logger.js +12 -0
  70. package/dist/homebridge-ui/public/js/logger.js.map +1 -0
  71. package/dist/homebridge-ui/public/js/logger.ts +11 -0
  72. package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
  73. package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
  74. package/dist/homebridge-ui/public/js/modal.js +26 -0
  75. package/dist/homebridge-ui/public/js/modal.js.map +1 -0
  76. package/dist/homebridge-ui/public/js/modal.ts +28 -0
  77. package/dist/homebridge-ui/public/js/modals.d.ts +2 -0
  78. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
  79. package/dist/homebridge-ui/public/js/modals.js +132 -0
  80. package/dist/homebridge-ui/public/js/modals.js.map +1 -0
  81. package/dist/homebridge-ui/public/js/modals.ts +153 -0
  82. package/dist/homebridge-ui/public/js/render.d.ts +6 -0
  83. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
  84. package/dist/homebridge-ui/public/js/render.js +166 -0
  85. package/dist/homebridge-ui/public/js/render.js.map +1 -0
  86. package/dist/homebridge-ui/public/js/render.ts +190 -0
  87. package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
  88. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
  89. package/dist/homebridge-ui/public/js/toast.js +29 -0
  90. package/dist/homebridge-ui/public/js/toast.js.map +1 -0
  91. package/dist/homebridge-ui/public/js/toast.ts +37 -0
  92. package/dist/homebridge-ui/public/js/types.d.ts +23 -0
  93. package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
  94. package/dist/homebridge-ui/public/js/types.js +2 -0
  95. package/dist/homebridge-ui/public/js/types.js.map +1 -0
  96. package/dist/homebridge-ui/public/js/types.ts +26 -0
  97. package/dist/homebridge-ui/server.d.ts +1 -3
  98. package/dist/homebridge-ui/server.d.ts.map +1 -1
  99. package/dist/homebridge-ui/server.js +10 -561
  100. package/dist/homebridge-ui/server.js.map +1 -1
  101. package/dist/homebridge-ui/utils/config-parser.d.ts +35 -0
  102. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
  103. package/dist/homebridge-ui/utils/config-parser.js +87 -0
  104. package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
  105. package/dist/homebridge-ui/utils/logger.d.ts +7 -0
  106. package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
  107. package/dist/homebridge-ui/utils/logger.js +17 -0
  108. package/dist/homebridge-ui/utils/logger.js.map +1 -0
  109. package/dist/platform.d.ts.map +1 -1
  110. package/dist/platform.js +6 -2
  111. package/dist/platform.js.map +1 -1
  112. package/dist/switchbotClient.d.ts.map +1 -1
  113. package/dist/switchbotClient.js +2 -0
  114. package/dist/switchbotClient.js.map +1 -1
  115. package/docs/variables/default.html +1 -1
  116. package/eslint.config.js +5 -0
  117. package/nodemon.json +2 -2
  118. package/package.json +20 -10
  119. package/scripts/build-ui.js +36 -0
  120. package/scripts/free-dev-ports.mjs +105 -0
  121. package/scripts/sync-device-types.mjs +28 -0
  122. package/src/device-types.ts +145 -0
  123. package/src/deviceFactory.ts +71 -31
  124. package/src/devices/genericDevice.ts +5 -2
  125. package/src/homebridge-ui/endpoints/config.ts +79 -0
  126. package/src/homebridge-ui/endpoints/credentials.ts +74 -0
  127. package/src/homebridge-ui/endpoints/devices.ts +321 -0
  128. package/src/homebridge-ui/endpoints/discovery.ts +191 -0
  129. package/src/homebridge-ui/public/index.html +144 -588
  130. package/src/homebridge-ui/public/js/api.ts +189 -0
  131. package/src/homebridge-ui/public/js/app.ts +21 -0
  132. package/src/homebridge-ui/public/js/constants.ts +1 -0
  133. package/src/homebridge-ui/public/js/credentials.ts +100 -0
  134. package/src/homebridge-ui/public/js/devices-delete.ts +113 -0
  135. package/src/homebridge-ui/public/js/devices.ts +78 -0
  136. package/src/homebridge-ui/public/js/discovery.ts +123 -0
  137. package/src/homebridge-ui/public/js/logger.ts +11 -0
  138. package/src/homebridge-ui/public/js/modal.ts +28 -0
  139. package/src/homebridge-ui/public/js/modals.ts +153 -0
  140. package/src/homebridge-ui/public/js/render.ts +190 -0
  141. package/src/homebridge-ui/public/js/toast.ts +37 -0
  142. package/src/homebridge-ui/public/js/types.ts +26 -0
  143. package/src/homebridge-ui/server.ts +11 -647
  144. package/src/homebridge-ui/utils/config-parser.ts +108 -0
  145. package/src/homebridge-ui/utils/logger.ts +17 -0
  146. package/src/platform.ts +6 -2
  147. package/src/switchbotClient.ts +2 -0
  148. package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
@@ -0,0 +1,153 @@
1
+ import { DEVICE_TYPES } from './constants.js'
2
+ import { uiLog } from './logger.js'
3
+
4
+ export async function editDevice(device: any): Promise<void> {
5
+ // Create modal dialog for editing
6
+ const div = document.createElement('div')
7
+ div.style.position = 'fixed'
8
+ div.style.top = '0'
9
+ div.style.left = '0'
10
+ div.style.width = '100%'
11
+ div.style.height = '100%'
12
+ div.style.background = 'rgba(0,0,0,0.7)'
13
+ div.style.display = 'flex'
14
+ div.style.alignItems = 'center'
15
+ div.style.justifyContent = 'center'
16
+ div.style.zIndex = '9999'
17
+
18
+ const modal = document.createElement('div')
19
+ modal.style.background = getComputedStyle(document.body).backgroundColor
20
+ modal.style.color = getComputedStyle(document.body).color
21
+ modal.style.padding = '24px'
22
+ modal.style.borderRadius = '8px'
23
+ modal.style.minWidth = '400px'
24
+ modal.style.boxShadow = '0 10px 40px rgba(0,0,0,0.3)'
25
+
26
+ const title = document.createElement('h3')
27
+ title.textContent = 'Edit Device'
28
+ title.style.marginTop = '0'
29
+
30
+ const nameLabel = document.createElement('label')
31
+ nameLabel.textContent = 'Device Name'
32
+ nameLabel.style.display = 'block'
33
+ nameLabel.style.marginBottom = '8px'
34
+ nameLabel.style.fontWeight = '500'
35
+
36
+ const nameInput = document.createElement('input')
37
+ nameInput.type = 'text'
38
+ nameInput.value = device.name || device.id
39
+ nameInput.style.width = '100%'
40
+ nameInput.style.marginBottom = '16px'
41
+
42
+ // Read-only API device type field
43
+ const apiTypeLabel = document.createElement('label')
44
+ apiTypeLabel.textContent = 'Device Type (API - Read Only)'
45
+ apiTypeLabel.style.display = 'block'
46
+ apiTypeLabel.style.marginBottom = '8px'
47
+ apiTypeLabel.style.fontWeight = '500'
48
+
49
+ const apiTypeInput = document.createElement('input')
50
+ apiTypeInput.type = 'text'
51
+ apiTypeInput.value = device.deviceType || device.type || 'Unknown'
52
+ apiTypeInput.readOnly = true
53
+ apiTypeInput.style.width = '100%'
54
+ apiTypeInput.style.marginBottom = '16px'
55
+ apiTypeInput.style.opacity = '0.6'
56
+ apiTypeInput.style.cursor = 'not-allowed'
57
+
58
+ // Editable config device type dropdown
59
+ const typeLabel = document.createElement('label')
60
+ typeLabel.textContent = 'Config Device Type (Override)'
61
+ typeLabel.style.display = 'block'
62
+ typeLabel.style.marginBottom = '8px'
63
+ typeLabel.style.fontWeight = '500'
64
+
65
+ const typeSelect = document.createElement('select')
66
+ typeSelect.style.width = '100%'
67
+ typeSelect.style.padding = '8px'
68
+ typeSelect.style.marginBottom = '16px'
69
+ typeSelect.style.borderRadius = '4px'
70
+ typeSelect.style.background = getComputedStyle(nameInput).background
71
+ typeSelect.style.color = getComputedStyle(nameInput).color
72
+ typeSelect.style.border = getComputedStyle(nameInput).border
73
+
74
+ // Add option groups with all API device types
75
+ Object.keys(DEVICE_TYPES).forEach((categoryName) => {
76
+ const optgroup = document.createElement('optgroup')
77
+ optgroup.label = categoryName
78
+
79
+ DEVICE_TYPES[categoryName as keyof typeof DEVICE_TYPES].forEach((deviceType) => {
80
+ const opt = document.createElement('option')
81
+ opt.value = deviceType
82
+ opt.text = deviceType
83
+
84
+ // Select current configDeviceType if set, otherwise match API deviceType
85
+ const currentType = device.configDeviceType || device.deviceType || device.type || ''
86
+ opt.selected = currentType === deviceType
87
+ optgroup.appendChild(opt)
88
+ })
89
+
90
+ typeSelect.appendChild(optgroup)
91
+ })
92
+
93
+ const buttons = document.createElement('div')
94
+ buttons.style.display = 'flex'
95
+ buttons.style.gap = '8px'
96
+ buttons.style.justifyContent = 'flex-end'
97
+
98
+ const errorMessage = document.createElement('div')
99
+ errorMessage.style.color = '#ef4444'
100
+ errorMessage.style.marginBottom = '12px'
101
+ errorMessage.style.fontSize = '12px'
102
+ errorMessage.style.display = 'none'
103
+
104
+ const cancelBtn = document.createElement('button')
105
+ cancelBtn.textContent = 'Cancel'
106
+ cancelBtn.style.background = '#666'
107
+ cancelBtn.onclick = () => div.remove()
108
+
109
+ const saveBtn = document.createElement('button')
110
+ saveBtn.textContent = 'Save'
111
+ saveBtn.onclick = async () => {
112
+ try {
113
+ const { updateDevice, syncParentPluginConfigFromDisk, fetchDevices } = await import('./api.js')
114
+ const { renderDeviceList } = await import('./render.js')
115
+
116
+ const params = {
117
+ deviceId: device.id,
118
+ configDeviceName: nameInput.value || undefined,
119
+ configDeviceType: typeSelect.value,
120
+ }
121
+
122
+ await updateDevice(params.deviceId, params.configDeviceName, params.configDeviceType)
123
+ await syncParentPluginConfigFromDisk()
124
+
125
+ // Refresh device list
126
+ uiLog.info('[Edit Device] Refreshing device list after update')
127
+ const list = await fetchDevices()
128
+ renderDeviceList(list)
129
+ div.remove()
130
+ } catch (e) {
131
+ uiLog.error('Update error:', e)
132
+ errorMessage.textContent = `Error: ${e instanceof Error ? e.message : 'Failed to update device'}`
133
+ errorMessage.style.display = 'block'
134
+ }
135
+ }
136
+
137
+ buttons.appendChild(cancelBtn)
138
+ buttons.appendChild(saveBtn)
139
+
140
+ modal.appendChild(title)
141
+ modal.appendChild(nameLabel)
142
+ modal.appendChild(nameInput)
143
+ modal.appendChild(apiTypeLabel)
144
+ modal.appendChild(apiTypeInput)
145
+ modal.appendChild(typeLabel)
146
+ modal.appendChild(typeSelect)
147
+ modal.appendChild(errorMessage)
148
+ modal.appendChild(buttons)
149
+
150
+ div.appendChild(modal)
151
+ document.body.appendChild(div)
152
+ nameInput.focus()
153
+ }
@@ -0,0 +1,190 @@
1
+ import type { DEVICE_TYPES } from './constants.js'
2
+
3
+ export function renderBadge(text: string, style: string): HTMLElement {
4
+ const badge = document.createElement('span')
5
+ badge.textContent = text
6
+ badge.style.cssText = style
7
+ return badge
8
+ }
9
+
10
+ export function renderConnectionBadge(connectionType: string): HTMLElement | null {
11
+ if (!connectionType) { return null }
12
+
13
+ const badge = renderBadge(connectionType, '')
14
+
15
+ if (connectionType === 'BLE') {
16
+ badge.style.cssText
17
+ = 'background: #4285f4; color: white; padding: 2px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 8px;'
18
+ } else if (connectionType === 'Both') {
19
+ badge.style.cssText
20
+ = 'background: #34a853; color: white; padding: 2px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 8px;'
21
+ } else {
22
+ badge.style.cssText
23
+ = 'background: #9e9e9e; color: white; padding: 2px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 8px;'
24
+ }
25
+
26
+ return badge
27
+ }
28
+
29
+ export function renderIRBadge(): HTMLElement {
30
+ return renderBadge(
31
+ 'IR',
32
+ 'background: #ff6b35; color: white; padding: 2px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-left: 8px;',
33
+ )
34
+ }
35
+
36
+ export async function renderDiscoveredDevices(devices: any[]): Promise<HTMLElement> {
37
+ const ul = document.createElement('ul')
38
+ ul.style.maxHeight = '300px'
39
+ ul.style.overflowY = 'auto'
40
+
41
+ const { addDeviceToConfig } = await import('./discovery.js')
42
+
43
+ for (const d of devices) {
44
+ const li = document.createElement('li')
45
+ li.className = 'device-item'
46
+
47
+ const info = document.createElement('div')
48
+ info.style.flex = '1'
49
+
50
+ const name = document.createElement('div')
51
+ name.style.fontWeight = '500'
52
+ name.textContent = d.name || d.id
53
+
54
+ // Add connection type badge
55
+ if (d.connectionType) {
56
+ name.appendChild(document.createTextNode(' '))
57
+ const badge = renderConnectionBadge(d.connectionType)
58
+ if (badge) { name.appendChild(badge) }
59
+ }
60
+
61
+ // Add IR badge if it's an IR device
62
+ if (d.isIR) {
63
+ name.appendChild(document.createTextNode(' '))
64
+ name.appendChild(renderIRBadge())
65
+ }
66
+
67
+ const details = document.createElement('div')
68
+ details.style.fontSize = '12px'
69
+ details.style.opacity = '0.75'
70
+
71
+ let detailsText = `ID: ${d.id} | Type: ${d.type} | Model: ${d.model || 'N/A'}`
72
+ if (d.hubDeviceId) { detailsText += ` | Hub: ${d.hubDeviceId}` }
73
+ if (d.rssi) { detailsText += ` | Signal: ${d.rssi} dBm` }
74
+ if (d.address) { detailsText += ` | MAC: ${d.address}` }
75
+ details.textContent = detailsText
76
+
77
+ info.appendChild(name)
78
+ info.appendChild(details)
79
+
80
+ const addBtn = document.createElement('button')
81
+ addBtn.textContent = '+ Add'
82
+ addBtn.style.marginLeft = '8px'
83
+ addBtn.onclick = async () => {
84
+ await addDeviceToConfig(d)
85
+ }
86
+
87
+ li.appendChild(info)
88
+ li.appendChild(addBtn)
89
+ ul.appendChild(li)
90
+ }
91
+
92
+ return ul
93
+ }
94
+
95
+ export function renderDeviceList(list: any[]): void {
96
+ const ul = document.getElementById('devices')
97
+ const status = document.getElementById('status')
98
+
99
+ if (!ul || !status) { return }
100
+
101
+ if (!list.length) {
102
+ status.textContent = 'No devices found in config.'
103
+ ul.innerHTML = ''
104
+ return
105
+ }
106
+
107
+ status.textContent = `Found ${list.length} device(s)`
108
+ ul.innerHTML = ''
109
+
110
+ for (const d of list) {
111
+ const li = document.createElement('li')
112
+ li.className = 'device-item'
113
+
114
+ const info = document.createElement('div')
115
+ info.style.flex = '1'
116
+
117
+ const name = document.createElement('div')
118
+ name.style.fontWeight = '500'
119
+ name.textContent = d.name || d.id
120
+
121
+ const code = document.createElement('code')
122
+ code.textContent = d.id
123
+ code.style.fontSize = '12px'
124
+ code.style.opacity = '0.75'
125
+ code.style.marginLeft = '8px'
126
+
127
+ const meta = document.createElement('div')
128
+ meta.style.opacity = '0.75'
129
+ meta.style.marginTop = '4px'
130
+ meta.style.fontSize = '12px'
131
+
132
+ const typeText = d.type ? `type: ${d.type}` : ''
133
+ const connText = d.connectionPreference ? `conn: ${d.connectionPreference}` : ''
134
+ const roomText = d.room ? `room: ${d.room}` : ''
135
+ meta.textContent = [typeText, connText, roomText].filter(Boolean).join(' | ')
136
+
137
+ info.appendChild(name)
138
+ info.appendChild(code)
139
+ info.appendChild(meta)
140
+
141
+ const buttons = document.createElement('div')
142
+ buttons.style.display = 'flex'
143
+ buttons.style.gap = '8px'
144
+
145
+ const editBtn = document.createElement('button')
146
+ editBtn.textContent = '✏️ Edit'
147
+ editBtn.style.padding = '6px 12px'
148
+ editBtn.style.fontSize = '12px'
149
+ editBtn.onclick = async () => {
150
+ const { editDevice } = await import('./modals.js')
151
+ await editDevice(d)
152
+ }
153
+
154
+ const copyBtn = document.createElement('button')
155
+ copyBtn.textContent = 'Copy ID'
156
+ copyBtn.style.padding = '6px 12px'
157
+ copyBtn.style.fontSize = '12px'
158
+ copyBtn.addEventListener('click', async () => {
159
+ try {
160
+ await navigator.clipboard.writeText(d.id)
161
+ copyBtn.textContent = 'Copied'
162
+ copyBtn.classList.add('success')
163
+ setTimeout(() => {
164
+ copyBtn.textContent = 'Copy ID'
165
+ copyBtn.classList.remove('success')
166
+ }, 1200)
167
+ } catch (e) {
168
+ alert('Failed to copy')
169
+ }
170
+ })
171
+
172
+ const deleteBtn = document.createElement('button')
173
+ deleteBtn.textContent = '🗑️ Delete'
174
+ deleteBtn.style.padding = '6px 12px'
175
+ deleteBtn.style.fontSize = '12px'
176
+ deleteBtn.style.background = '#ef4444'
177
+ deleteBtn.onclick = async () => {
178
+ const { deleteDeviceFromConfig } = await import('./devices-delete.js')
179
+ await deleteDeviceFromConfig(d.id, d.name || d.id)
180
+ }
181
+
182
+ buttons.appendChild(editBtn)
183
+ buttons.appendChild(copyBtn)
184
+ buttons.appendChild(deleteBtn)
185
+
186
+ li.appendChild(info)
187
+ li.appendChild(buttons)
188
+ ul.appendChild(li)
189
+ }
190
+ }
@@ -0,0 +1,37 @@
1
+ import './types.js'
2
+ import { uiLog } from './logger.js'
3
+
4
+ function showToast(
5
+ method: 'success' | 'error' | 'warning' | 'info',
6
+ message: string,
7
+ title = 'SwitchBot',
8
+ ): void {
9
+ try {
10
+ const toast = homebridge?.toast
11
+ const fn = toast?.[method]
12
+ if (typeof fn === 'function') {
13
+ fn(message, title)
14
+ return
15
+ }
16
+
17
+ uiLog.info(`[Toast:${method}] ${title} - ${message}`)
18
+ } catch (e) {
19
+ uiLog.warn(`Toast ${method} failed:`, e)
20
+ }
21
+ }
22
+
23
+ export function toastSuccess(message: string, title?: string): void {
24
+ showToast('success', message, title)
25
+ }
26
+
27
+ export function toastError(message: string, title?: string): void {
28
+ showToast('error', message, title)
29
+ }
30
+
31
+ export function toastWarning(message: string, title?: string): void {
32
+ showToast('warning', message, title)
33
+ }
34
+
35
+ export function toastInfo(message: string, title?: string): void {
36
+ showToast('info', message, title)
37
+ }
@@ -0,0 +1,26 @@
1
+ // Global homebridge API type declarations
2
+ declare global {
3
+ interface HomebridgeToastApi {
4
+ success?: (message: string, title?: string) => void
5
+ error?: (message: string, title?: string) => void
6
+ warning?: (message: string, title?: string) => void
7
+ info?: (message: string, title?: string) => void
8
+ }
9
+
10
+ interface HomebridgePluginUiAPI {
11
+ request: (endpoint: string, params?: any) => Promise<any>
12
+ getPluginConfig?: () => Promise<any[]>
13
+ updatePluginConfig?: (config: any[]) => Promise<void>
14
+ savePluginConfig?: () => Promise<void>
15
+ closeSettings?: () => void
16
+ showSpinner?: () => void
17
+ hideSpinner?: () => void
18
+ disableSaveButton?: () => void
19
+ enableSaveButton?: () => void
20
+ toast?: HomebridgeToastApi
21
+ }
22
+
23
+ let homebridge: HomebridgePluginUiAPI
24
+ }
25
+
26
+ export {}