homebridge-roborock-vacuum 2.1.0 → 2.1.2
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.
- package/CHANGELOG.md +21 -0
- package/config.schema.json +6 -0
- package/homebridge-ui/public/index.html +183 -152
- package/homebridge-ui/public/index.js +25 -1
- package/homebridge-ui/public/styles.css +100 -132
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.2
|
|
4
|
+
|
|
5
|
+
Maintenance release — no functional changes.
|
|
6
|
+
|
|
7
|
+
- **Fix**: Added the standard `name` property to `config.schema.json`. The settings UI already wrote a platform name to the config; it was only missing from the schema (flagged by the Homebridge verification checks)
|
|
8
|
+
|
|
9
|
+
## 2.1.1
|
|
10
|
+
|
|
11
|
+
Maintenance release — no functional changes to device control.
|
|
12
|
+
|
|
13
|
+
- **Improvement**: The plugin settings page now inherits the Homebridge UI theme (theme color and light/dark mode) instead of using its own hard-coded dark palette, so it matches the rest of the Homebridge UI
|
|
14
|
+
- **Fix**: Corrected a malformed `repository` URL in `package.json` (`git+hhttps://…`) that broke the "Repository" link on the NPM package page
|
|
15
|
+
|
|
16
|
+
## 2.1.0
|
|
17
|
+
|
|
18
|
+
- **New Feature**: Dock mop-wash switch
|
|
19
|
+
- Vacuums with a washing dock get an extra stateful switch that starts/stops a dock mop wash and mirrors the device's washing state — including washes started from the Roborock app
|
|
20
|
+
- Available over both HomeKit (extra Switch service on the vacuum accessory) and Matter (bridged on/off accessory per washing-capable vacuum)
|
|
21
|
+
- **New Feature**: `language` config option (settings-page dropdown, default `en`, adds `zh-tw`) for localized device-provided switch names. Changing the language never affects accessory UUIDs or service subtypes, so no re-pairing or room re-selection is needed
|
|
22
|
+
- **Fix**: Dock type detection used the attribute name instead of the actual `dock_type` value
|
|
23
|
+
|
|
3
24
|
## 2.0.1
|
|
4
25
|
|
|
5
26
|
Stability and security hardening release based on a full plugin vulnerability scan. No new features, no configuration changes — updating is strongly recommended, especially on low-power hosts (NAS, Raspberry Pi).
|
package/config.schema.json
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
"schema": {
|
|
8
8
|
"type": "object",
|
|
9
9
|
"properties": {
|
|
10
|
+
"name": {
|
|
11
|
+
"title": "Name",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "Roborock Vacuum",
|
|
14
|
+
"description": "Platform name shown in the Homebridge log."
|
|
15
|
+
},
|
|
10
16
|
"email": {
|
|
11
17
|
"title": "Email",
|
|
12
18
|
"type": "string",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<link rel="stylesheet" href="styles.css?v=1.
|
|
1
|
+
<link rel="stylesheet" href="styles.css?v=2.1.1" />
|
|
2
2
|
|
|
3
|
-
<main class="container">
|
|
3
|
+
<main class="rr-container">
|
|
4
4
|
<header>
|
|
5
5
|
<h1>Roborock Vacuum</h1>
|
|
6
6
|
<p class="subtitle">
|
|
@@ -9,169 +9,200 @@
|
|
|
9
9
|
</header>
|
|
10
10
|
|
|
11
11
|
<section class="card">
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
12
|
+
<div class="card-body">
|
|
13
|
+
<h2>Account</h2>
|
|
14
|
+
<label>
|
|
15
|
+
Email
|
|
16
|
+
<input
|
|
17
|
+
id="email"
|
|
18
|
+
type="email"
|
|
19
|
+
class="form-control"
|
|
20
|
+
placeholder="name@example.com"
|
|
21
|
+
/>
|
|
22
|
+
</label>
|
|
23
|
+
<label id="password-row">
|
|
24
|
+
Password
|
|
25
|
+
<input
|
|
26
|
+
id="password"
|
|
27
|
+
type="password"
|
|
28
|
+
class="form-control"
|
|
29
|
+
placeholder="Password"
|
|
30
|
+
/>
|
|
31
|
+
</label>
|
|
32
|
+
<p class="help">
|
|
33
|
+
Use password login to test credentials and save a token.
|
|
34
|
+
</p>
|
|
35
|
+
<label>
|
|
36
|
+
Region
|
|
37
|
+
<select id="base-url" class="form-select">
|
|
38
|
+
<option value="https://usiot.roborock.com">
|
|
39
|
+
US (usiot.roborock.com)
|
|
40
|
+
</option>
|
|
41
|
+
<option value="https://euiot.roborock.com">
|
|
42
|
+
EU (euiot.roborock.com)
|
|
43
|
+
</option>
|
|
44
|
+
<option value="https://cniot.roborock.com">
|
|
45
|
+
CN (cniot.roborock.com)
|
|
46
|
+
</option>
|
|
47
|
+
<option value="https://api.roborock.com">
|
|
48
|
+
Asia (api.roborock.com)
|
|
49
|
+
</option>
|
|
50
|
+
</select>
|
|
51
|
+
</label>
|
|
52
|
+
<label>
|
|
53
|
+
Language
|
|
54
|
+
<select id="language" class="form-select">
|
|
55
|
+
<option value="en">English</option>
|
|
56
|
+
<option value="de">Deutsch</option>
|
|
57
|
+
<option value="es">Español</option>
|
|
58
|
+
<option value="fr">Français</option>
|
|
59
|
+
<option value="it">Italiano</option>
|
|
60
|
+
<option value="nl">Nederlands</option>
|
|
61
|
+
<option value="pl">Polski</option>
|
|
62
|
+
<option value="pt">Português</option>
|
|
63
|
+
<option value="ru">Русский</option>
|
|
64
|
+
<option value="uk">Українська</option>
|
|
65
|
+
<option value="zh-cn">简体中文</option>
|
|
66
|
+
<option value="zh-tw">繁體中文</option>
|
|
67
|
+
</select>
|
|
68
|
+
</label>
|
|
69
|
+
<p class="help">
|
|
70
|
+
Language for device-provided switch names (e.g. the dock mop-wash
|
|
71
|
+
switch). Changing it only renames accessories after a restart — no
|
|
72
|
+
re-pairing and no room re-selection is required.
|
|
73
|
+
</p>
|
|
74
|
+
<div class="field">
|
|
75
|
+
<div class="field-header">
|
|
76
|
+
<span>Devices</span>
|
|
77
|
+
<div class="field-actions">
|
|
78
|
+
<button
|
|
79
|
+
id="load-devices"
|
|
80
|
+
class="btn btn-secondary btn-sm"
|
|
81
|
+
type="button"
|
|
82
|
+
title="Load devices from your account"
|
|
83
|
+
>
|
|
84
|
+
Load devices
|
|
85
|
+
</button>
|
|
86
|
+
<button
|
|
87
|
+
id="add-skip-device"
|
|
88
|
+
class="icon-button"
|
|
89
|
+
type="button"
|
|
90
|
+
title="Add skipped device"
|
|
91
|
+
aria-label="Add skipped device"
|
|
92
|
+
>
|
|
93
|
+
+
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
82
96
|
</div>
|
|
97
|
+
<div id="discovered-devices" class="discovered-devices"></div>
|
|
98
|
+
<div id="skip-devices" class="skip-device-list"></div>
|
|
83
99
|
</div>
|
|
84
|
-
<div id="discovered-devices" class="discovered-devices"></div>
|
|
85
|
-
<div id="skip-devices" class="skip-device-list"></div>
|
|
86
|
-
</div>
|
|
87
|
-
<p class="help">
|
|
88
|
-
Click <strong>Load devices</strong> to choose how each vacuum is
|
|
89
|
-
published: <strong>HomeKit (HAP)</strong> (default),
|
|
90
|
-
<strong>Matter</strong> (requires Homebridge 2.x with Matter enabled), or
|
|
91
|
-
<strong>Skip</strong>. Manually entered IDs below are always skipped.
|
|
92
|
-
Changing a device's protocol re-creates it on restart, so it loses its
|
|
93
|
-
room and automation assignments in your home app.
|
|
94
|
-
</p>
|
|
95
|
-
<div id="matter-section" class="hidden">
|
|
96
|
-
<label class="checkbox">
|
|
97
|
-
<input id="matter-child-bridge" type="checkbox" />
|
|
98
|
-
Enable Matter on this plugin's child bridge
|
|
99
|
-
</label>
|
|
100
100
|
<p class="help">
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
re-
|
|
101
|
+
Click <strong>Load devices</strong> to choose how each vacuum is
|
|
102
|
+
published: <strong>HomeKit (HAP)</strong> (default),
|
|
103
|
+
<strong>Matter</strong> (requires Homebridge 2.x with Matter enabled),
|
|
104
|
+
or <strong>Skip</strong>. Manually entered IDs below are always skipped.
|
|
105
|
+
Changing a device's protocol re-creates it on restart, so it loses its
|
|
106
|
+
room and automation assignments in your home app.
|
|
106
107
|
</p>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
<div id="matter-section" class="hidden">
|
|
109
|
+
<label class="checkbox">
|
|
110
|
+
<input id="matter-child-bridge" type="checkbox" />
|
|
111
|
+
Enable Matter on this plugin's child bridge
|
|
112
|
+
</label>
|
|
113
|
+
<p class="help">
|
|
114
|
+
Requires a Homebridge restart to apply. After enabling, commission
|
|
115
|
+
this bridge's Matter side from your controller app (pairing code in
|
|
116
|
+
the Homebridge UI bridge settings) before selecting Matter for a
|
|
117
|
+
device. Disabling keeps the commissioning info so it can be re-enabled
|
|
118
|
+
without re-pairing.
|
|
119
|
+
</p>
|
|
120
|
+
</div>
|
|
121
|
+
<label class="checkbox">
|
|
122
|
+
<input id="debug-mode" type="checkbox" />
|
|
123
|
+
Enable debug logging
|
|
124
|
+
</label>
|
|
125
|
+
<div class="actions">
|
|
126
|
+
<button id="login" class="btn btn-primary">Password Login</button>
|
|
127
|
+
<button id="logout" class="btn btn-secondary hidden">Logout</button>
|
|
128
|
+
</div>
|
|
115
129
|
</div>
|
|
116
130
|
</section>
|
|
117
131
|
|
|
118
132
|
<section class="card">
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
133
|
+
<div class="card-body">
|
|
134
|
+
<h2>Current Room → MQTT</h2>
|
|
135
|
+
<p class="help">
|
|
136
|
+
Publish the room each vacuum is currently cleaning to a local MQTT
|
|
137
|
+
broker (telemetry only; not exposed to HomeKit). Off by default.
|
|
138
|
+
</p>
|
|
139
|
+
<label class="checkbox">
|
|
140
|
+
<input id="room-mqtt-enabled" type="checkbox" />
|
|
141
|
+
Publish current cleaning room to MQTT
|
|
142
|
+
</label>
|
|
143
|
+
<label>
|
|
144
|
+
Broker URL
|
|
145
|
+
<input
|
|
146
|
+
id="room-mqtt-broker"
|
|
147
|
+
type="text"
|
|
148
|
+
class="form-control"
|
|
149
|
+
placeholder="mqtt://127.0.0.1:1883"
|
|
150
|
+
/>
|
|
151
|
+
</label>
|
|
152
|
+
<label>
|
|
153
|
+
Topic Template
|
|
154
|
+
<input
|
|
155
|
+
id="room-mqtt-topic"
|
|
156
|
+
type="text"
|
|
157
|
+
class="form-control"
|
|
158
|
+
placeholder="homebridge/roborock/{duid}/current_room"
|
|
159
|
+
/>
|
|
160
|
+
</label>
|
|
161
|
+
<p class="help">
|
|
162
|
+
Supports <code>{duid}</code> and <code>{name}</code> tokens; if neither
|
|
163
|
+
is present, <code>/{duid}</code> is appended automatically so multiple
|
|
164
|
+
vacuums never collide.
|
|
165
|
+
</p>
|
|
166
|
+
<label>
|
|
167
|
+
Cleaning Poll Interval (s)
|
|
168
|
+
<input
|
|
169
|
+
id="room-mqtt-poll"
|
|
170
|
+
type="number"
|
|
171
|
+
class="form-control"
|
|
172
|
+
min="5"
|
|
173
|
+
placeholder="10"
|
|
174
|
+
/>
|
|
175
|
+
</label>
|
|
176
|
+
<p class="help">
|
|
177
|
+
How often status is polled while the vacuum is actively cleaning.
|
|
178
|
+
</p>
|
|
179
|
+
</div>
|
|
156
180
|
</section>
|
|
157
181
|
|
|
158
182
|
<section class="card" id="two-factor-section">
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
183
|
+
<div class="card-body">
|
|
184
|
+
<h2>Two-Factor Authentication</h2>
|
|
185
|
+
<p class="help">
|
|
186
|
+
If your account requires 2FA, send a verification email and enter the
|
|
187
|
+
code below.
|
|
188
|
+
</p>
|
|
189
|
+
<label id="code-row">
|
|
190
|
+
Verification Code
|
|
191
|
+
<input
|
|
192
|
+
id="two-factor-code"
|
|
193
|
+
type="text"
|
|
194
|
+
class="form-control"
|
|
195
|
+
placeholder="6-digit code"
|
|
196
|
+
/>
|
|
197
|
+
</label>
|
|
198
|
+
<div class="actions">
|
|
199
|
+
<button id="send-2fa" class="btn btn-primary">Send 2FA Email</button>
|
|
200
|
+
<button id="verify-2fa" class="btn btn-secondary">Verify Code</button>
|
|
201
|
+
</div>
|
|
171
202
|
</div>
|
|
172
203
|
</section>
|
|
173
204
|
</main>
|
|
174
205
|
|
|
175
|
-
<div id="toast-container" class="toast-container"></div>
|
|
206
|
+
<div id="toast-container" class="rr-toast-container"></div>
|
|
176
207
|
|
|
177
|
-
<script src="index.js?v=1.
|
|
208
|
+
<script src="index.js?v=2.1.1"></script>
|
|
@@ -91,7 +91,7 @@ function showToast(type, message) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
const toast = document.createElement("div");
|
|
94
|
-
toast.className = `toast ${type}`;
|
|
94
|
+
toast.className = `rr-toast ${type}`;
|
|
95
95
|
toast.textContent = message;
|
|
96
96
|
elements.toastContainer.appendChild(toast);
|
|
97
97
|
|
|
@@ -354,6 +354,7 @@ function addSkipDeviceRow(value = "", shouldFocus = true) {
|
|
|
354
354
|
|
|
355
355
|
const input = document.createElement("input");
|
|
356
356
|
input.type = "text";
|
|
357
|
+
input.className = "form-control";
|
|
357
358
|
input.placeholder = "Device ID";
|
|
358
359
|
input.setAttribute("aria-label", "Skipped device ID");
|
|
359
360
|
input.value = value;
|
|
@@ -607,6 +608,7 @@ function renderDiscoveredDevices(devices) {
|
|
|
607
608
|
label.textContent = `${device.name || device.duid}${model} — ${device.duid}${shared}`;
|
|
608
609
|
|
|
609
610
|
const select = document.createElement("select");
|
|
611
|
+
select.className = "form-select form-select-sm";
|
|
610
612
|
const current = currentProtocol(device.duid);
|
|
611
613
|
const options = [{ value: "hap", text: "HomeKit (HAP)" }];
|
|
612
614
|
if (matterSupported || current === "matter") {
|
|
@@ -905,7 +907,29 @@ async function updatePluginConfig(patch) {
|
|
|
905
907
|
await withTimeout(window.homebridge.savePluginConfig(), 3000, undefined);
|
|
906
908
|
}
|
|
907
909
|
|
|
910
|
+
// The Homebridge UI injects its themed stylesheet into this iframe shortly
|
|
911
|
+
// after "ready". Sample the theme's accent from a .primary-text probe once it
|
|
912
|
+
// lands, so native checkboxes follow the user's theme color (--rr-primary in
|
|
913
|
+
// styles.css). Retries because the injected stylesheet arrives asynchronously.
|
|
914
|
+
function adoptThemeAccent(attempt = 0) {
|
|
915
|
+
const probe = document.createElement("span");
|
|
916
|
+
probe.className = "primary-text";
|
|
917
|
+
probe.style.display = "none";
|
|
918
|
+
document.body.appendChild(probe);
|
|
919
|
+
const color = getComputedStyle(probe).color;
|
|
920
|
+
probe.remove();
|
|
921
|
+
|
|
922
|
+
if (color && color !== getComputedStyle(document.body).color) {
|
|
923
|
+
document.documentElement.style.setProperty("--rr-primary", color);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (attempt < 10) {
|
|
927
|
+
setTimeout(() => adoptThemeAccent(attempt + 1), 300);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
908
931
|
function init() {
|
|
932
|
+
adoptThemeAccent();
|
|
909
933
|
renderSkipDevices([]);
|
|
910
934
|
loadConfig().catch(() => {
|
|
911
935
|
showToast("error", "Failed to load current config.");
|
|
@@ -1,74 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
|
+
* Layout-only stylesheet. Color and typography come from the Homebridge UI:
|
|
3
|
+
* config-ui-x injects its themed stylesheet (Bootstrap 5 plus the user's
|
|
4
|
+
* config-ui-x-<theme> body class, plus `dark-mode` when active) into this
|
|
5
|
+
* iframe, so anything not overridden here follows whichever theme the user
|
|
6
|
+
* picked. The few greys below mirror the host's neutral palette.
|
|
7
|
+
*/
|
|
2
8
|
|
|
3
9
|
:root {
|
|
4
|
-
--
|
|
5
|
-
--
|
|
6
|
-
--
|
|
7
|
-
--muted: #94a3b8;
|
|
8
|
-
--primary: #38bdf8;
|
|
9
|
-
--secondary: #1e293b;
|
|
10
|
-
--border: #1f2a44;
|
|
11
|
-
--shadow: 0 20px 40px rgba(15, 23, 42, 0.35);
|
|
10
|
+
--rr-muted: #6c757d;
|
|
11
|
+
--rr-border: #dee2e6;
|
|
12
|
+
--rr-surface: #f8f9fa;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
/* Neutral dark surfaces matching config-ui-x's dark palette (#242424 body,
|
|
16
|
+
#2b2b2b cards, #333 inputs). The --bs-* variables drive the injected
|
|
17
|
+
Bootstrap form controls, including their focus state and select caret. */
|
|
18
|
+
body.dark-mode {
|
|
19
|
+
--rr-muted: #9e9e9e;
|
|
20
|
+
--rr-border: #444;
|
|
21
|
+
--rr-surface: #333;
|
|
22
|
+
--bs-body-bg: #333;
|
|
23
|
+
--bs-body-color: #eee;
|
|
24
|
+
--bs-border-color: #444;
|
|
25
|
+
--bs-secondary-bg: #2b2b2b;
|
|
26
|
+
--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
|
|
16
27
|
}
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
font-family: "Space Grotesk", "Segoe UI", sans-serif;
|
|
21
|
-
background: radial-gradient(circle at top, #1e3a8a, #0b1120 55%, #020617);
|
|
22
|
-
color: var(--text);
|
|
29
|
+
* {
|
|
30
|
+
box-sizing: border-box;
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
.container
|
|
33
|
+
/* rr- prefix: Bootstrap's own .container would impose its breakpoint widths. */
|
|
34
|
+
.rr-container {
|
|
26
35
|
max-width: 860px;
|
|
27
36
|
margin: 0 auto;
|
|
28
|
-
padding:
|
|
37
|
+
padding: 4px 2px 12px;
|
|
29
38
|
display: grid;
|
|
30
|
-
gap:
|
|
39
|
+
gap: 16px;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
header h1 {
|
|
34
|
-
font-size:
|
|
35
|
-
|
|
43
|
+
font-size: 1.3rem;
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
margin: 0 0 4px;
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
.subtitle {
|
|
39
49
|
margin: 0;
|
|
40
|
-
color: var(--muted);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.card {
|
|
44
|
-
background: var(--card);
|
|
45
|
-
border: 1px solid var(--border);
|
|
46
|
-
border-radius: 18px;
|
|
47
|
-
padding: 24px;
|
|
48
|
-
box-shadow: var(--shadow);
|
|
50
|
+
color: var(--rr-muted);
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
.card h2 {
|
|
52
|
-
|
|
53
|
-
font-
|
|
54
|
+
font-size: 1.05rem;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
margin: 0 0 4px;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
.help {
|
|
57
|
-
color: var(--muted);
|
|
58
|
-
|
|
60
|
+
color: var(--rr-muted);
|
|
61
|
+
font-size: 0.875rem;
|
|
62
|
+
margin: 8px 0 0;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
label {
|
|
62
66
|
display: block;
|
|
63
|
-
margin-top:
|
|
64
|
-
font-size: 0.
|
|
65
|
-
|
|
67
|
+
margin-top: 14px;
|
|
68
|
+
font-size: 0.9rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
label > input,
|
|
72
|
+
label > select {
|
|
73
|
+
margin-top: 6px;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
.field {
|
|
69
|
-
margin-top:
|
|
70
|
-
font-size: 0.
|
|
71
|
-
color: var(--muted);
|
|
77
|
+
margin-top: 14px;
|
|
78
|
+
font-size: 0.9rem;
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
.field-header {
|
|
@@ -78,33 +85,15 @@ label {
|
|
|
78
85
|
gap: 12px;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
select,
|
|
82
|
-
input,
|
|
83
|
-
textarea {
|
|
84
|
-
width: 100%;
|
|
85
|
-
margin-top: 8px;
|
|
86
|
-
padding: 12px 14px;
|
|
87
|
-
border-radius: 10px;
|
|
88
|
-
border: 1px solid var(--border);
|
|
89
|
-
background: #0b1328;
|
|
90
|
-
color: var(--text);
|
|
91
|
-
font-size: 1rem;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
textarea {
|
|
95
|
-
min-height: 110px;
|
|
96
|
-
resize: vertical;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
88
|
.field-actions {
|
|
100
89
|
display: flex;
|
|
101
90
|
align-items: center;
|
|
102
91
|
gap: 8px;
|
|
103
92
|
}
|
|
104
93
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
/* --rr-primary is sampled from the injected theme at runtime (index.js). */
|
|
95
|
+
input[type="checkbox"] {
|
|
96
|
+
accent-color: var(--rr-primary, auto);
|
|
108
97
|
}
|
|
109
98
|
|
|
110
99
|
.discovered-devices {
|
|
@@ -149,7 +138,7 @@ textarea {
|
|
|
149
138
|
gap: 6px;
|
|
150
139
|
margin: 0;
|
|
151
140
|
font-size: 0.85rem;
|
|
152
|
-
color: var(--muted);
|
|
141
|
+
color: var(--rr-muted);
|
|
153
142
|
}
|
|
154
143
|
|
|
155
144
|
.scene-toggle input[type="checkbox"] {
|
|
@@ -163,11 +152,12 @@ textarea {
|
|
|
163
152
|
align-items: center;
|
|
164
153
|
margin: 6px 0 10px;
|
|
165
154
|
padding: 12px;
|
|
166
|
-
border: 1px solid var(--border);
|
|
167
|
-
border-radius:
|
|
168
|
-
background: var(--
|
|
155
|
+
border: 1px solid var(--rr-border);
|
|
156
|
+
border-radius: 8px;
|
|
157
|
+
background: var(--rr-surface);
|
|
169
158
|
}
|
|
170
159
|
|
|
160
|
+
/* QR codes keep a white quiet zone so they stay scannable in dark mode. */
|
|
171
161
|
.pairing-qr svg {
|
|
172
162
|
display: block;
|
|
173
163
|
width: 132px;
|
|
@@ -203,16 +193,11 @@ textarea {
|
|
|
203
193
|
margin-top: 0;
|
|
204
194
|
}
|
|
205
195
|
|
|
206
|
-
input::placeholder,
|
|
207
|
-
textarea::placeholder {
|
|
208
|
-
color: #64748b;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
196
|
.checkbox {
|
|
212
197
|
display: flex;
|
|
213
198
|
align-items: center;
|
|
214
199
|
gap: 10px;
|
|
215
|
-
margin-top:
|
|
200
|
+
margin-top: 16px;
|
|
216
201
|
}
|
|
217
202
|
|
|
218
203
|
.checkbox input {
|
|
@@ -220,18 +205,16 @@ textarea::placeholder {
|
|
|
220
205
|
margin: 0;
|
|
221
206
|
}
|
|
222
207
|
|
|
208
|
+
/* !important: the injected sheet loads after this one, so Bootstrap's
|
|
209
|
+
.btn { display: inline-block } would otherwise win the tie. */
|
|
223
210
|
.hidden {
|
|
224
|
-
display: none;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.readonly input {
|
|
228
|
-
background: #111827;
|
|
229
|
-
opacity: 0.75;
|
|
211
|
+
display: none !important;
|
|
230
212
|
}
|
|
231
213
|
|
|
214
|
+
.readonly input,
|
|
232
215
|
.readonly select {
|
|
233
|
-
|
|
234
|
-
|
|
216
|
+
opacity: 0.7;
|
|
217
|
+
cursor: not-allowed;
|
|
235
218
|
}
|
|
236
219
|
|
|
237
220
|
.actions {
|
|
@@ -241,53 +224,35 @@ textarea::placeholder {
|
|
|
241
224
|
margin-top: 20px;
|
|
242
225
|
}
|
|
243
226
|
|
|
244
|
-
|
|
245
|
-
border: none;
|
|
246
|
-
border-radius: 999px;
|
|
247
|
-
padding: 12px 20px;
|
|
248
|
-
font-size: 0.95rem;
|
|
249
|
-
cursor: pointer;
|
|
250
|
-
transition:
|
|
251
|
-
transform 0.15s ease,
|
|
252
|
-
box-shadow 0.15s ease;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
button:hover {
|
|
256
|
-
transform: translateY(-1px);
|
|
257
|
-
box-shadow: 0 12px 24px rgba(56, 189, 248, 0.18);
|
|
258
|
-
}
|
|
259
|
-
|
|
227
|
+
/* Compact +/- buttons; the injected theme colors their hover state. */
|
|
260
228
|
.icon-button {
|
|
261
|
-
width:
|
|
262
|
-
min-width:
|
|
263
|
-
height:
|
|
229
|
+
width: 38px;
|
|
230
|
+
min-width: 38px;
|
|
231
|
+
height: 38px;
|
|
264
232
|
padding: 0;
|
|
265
233
|
display: inline-flex;
|
|
266
234
|
align-items: center;
|
|
267
235
|
justify-content: center;
|
|
268
|
-
font-size: 1.
|
|
236
|
+
font-size: 1.25rem;
|
|
269
237
|
line-height: 1;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
238
|
+
border: 1px solid var(--rr-border);
|
|
239
|
+
border-radius: 0.375rem;
|
|
240
|
+
background-color: transparent;
|
|
241
|
+
color: inherit;
|
|
242
|
+
cursor: pointer;
|
|
273
243
|
}
|
|
274
244
|
|
|
275
245
|
.icon-button.remove {
|
|
276
|
-
color: #
|
|
246
|
+
color: #dc3545;
|
|
277
247
|
}
|
|
278
248
|
|
|
279
|
-
.
|
|
280
|
-
|
|
281
|
-
color: #041826;
|
|
249
|
+
body.dark-mode .icon-button.remove {
|
|
250
|
+
color: #f28b94;
|
|
282
251
|
}
|
|
283
252
|
|
|
284
|
-
.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
border: 1px solid var(--border);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
.toast-container {
|
|
253
|
+
/* Fallback toasts, shown only when window.homebridge.toast is unavailable.
|
|
254
|
+
rr- prefix keeps them clear of Bootstrap's own .toast component. */
|
|
255
|
+
.rr-toast-container {
|
|
291
256
|
position: fixed;
|
|
292
257
|
right: 24px;
|
|
293
258
|
bottom: 24px;
|
|
@@ -297,29 +262,38 @@ button:hover {
|
|
|
297
262
|
z-index: 9999;
|
|
298
263
|
}
|
|
299
264
|
|
|
300
|
-
.toast {
|
|
301
|
-
background: #
|
|
302
|
-
border: 1px solid var(--border);
|
|
303
|
-
border-
|
|
265
|
+
.rr-toast {
|
|
266
|
+
background: #ffffff;
|
|
267
|
+
border: 1px solid var(--rr-border);
|
|
268
|
+
border-left-width: 4px;
|
|
269
|
+
border-radius: 8px;
|
|
304
270
|
padding: 12px 16px;
|
|
305
|
-
box-shadow:
|
|
271
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
306
272
|
min-width: 220px;
|
|
307
|
-
animation: slide-in 0.2s ease-out;
|
|
273
|
+
animation: rr-slide-in 0.2s ease-out;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
body.dark-mode .rr-toast {
|
|
277
|
+
background: #2b2b2b;
|
|
308
278
|
}
|
|
309
279
|
|
|
310
|
-
.toast.success {
|
|
311
|
-
border-color: #
|
|
280
|
+
.rr-toast.success {
|
|
281
|
+
border-left-color: #28a745;
|
|
312
282
|
}
|
|
313
283
|
|
|
314
|
-
.toast.error {
|
|
315
|
-
border-color: #
|
|
284
|
+
.rr-toast.error {
|
|
285
|
+
border-left-color: #dc3545;
|
|
316
286
|
}
|
|
317
287
|
|
|
318
|
-
.toast.warning {
|
|
319
|
-
border-color: #
|
|
288
|
+
.rr-toast.warning {
|
|
289
|
+
border-left-color: #ffc107;
|
|
320
290
|
}
|
|
321
291
|
|
|
322
|
-
|
|
292
|
+
.rr-toast.info {
|
|
293
|
+
border-left-color: #17a2b8;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@keyframes rr-slide-in {
|
|
323
297
|
from {
|
|
324
298
|
transform: translateY(10px);
|
|
325
299
|
opacity: 0;
|
|
@@ -329,9 +303,3 @@ button:hover {
|
|
|
329
303
|
opacity: 1;
|
|
330
304
|
}
|
|
331
305
|
}
|
|
332
|
-
|
|
333
|
-
@media (max-width: 640px) {
|
|
334
|
-
header h1 {
|
|
335
|
-
font-size: 2.1rem;
|
|
336
|
-
}
|
|
337
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-vacuum",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Roborock Vacuum Cleaner - plugin for Homebridge.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "git+
|
|
14
|
+
"url": "git+https://github.com/tasict/homebridge-roborock-vacuum.git"
|
|
15
15
|
},
|
|
16
16
|
"bugs": {
|
|
17
17
|
"url": "https://github.com/tasict/homebridge-roborock-vacuum/issues"
|