@linxin666/dsh-client-ui-task-board 0.2.0 → 0.2.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/README.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +349 -73
- package/lib/client.js.map +1 -1
- package/lib/index.js +161 -31
- package/lib/types/client/PluginSettingsCard.d.ts +22 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +2 -2
- package/lib/types/client/host-api.d.ts +3 -3
- package/lib/types/client/sidebar-entry-core.d.ts +60 -0
- package/lib/types/client/sidebar-entry.d.ts +8 -10
- package/lib/types/core/controller.d.ts +9 -2
- package/lib/types/core/schedule.d.ts +7 -0
- package/lib/types/host-ledger.d.ts +10 -0
- package/lib/types/host-routes.d.ts +3 -1
- package/lib/types/host-runner.d.ts +12 -1
- package/lib/types/host-service.d.ts +5 -1
- package/lib/types/protocol.d.ts +6 -0
- package/package.json +1 -2
- package/src/client/PluginSettingsCard.tsx +219 -18
- package/src/client/TaskBoardSettingsCard.tsx +10 -10
- package/src/client/board/TaskBoard.tsx +5 -5
- package/src/client/board/TaskCard.tsx +1 -0
- package/src/client/board/TaskDetail.tsx +1 -1
- package/src/client/board-mount.tsx +1 -0
- package/src/client/host-api.ts +12 -3
- package/src/client/settings-card.module.css +90 -1
- package/src/client/sidebar-entry-core.ts +208 -0
- package/src/client/sidebar-entry.ts +24 -144
- package/src/core/controller.ts +20 -3
- package/src/core/execution.ts +1 -2
- package/src/core/schedule.ts +57 -10
- package/src/host-ledger.ts +67 -6
- package/src/host-routes.ts +11 -3
- package/src/host-runner.ts +24 -6
- package/src/host-service.ts +24 -7
- package/src/protocol.ts +7 -0
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: e9bcccc740d89fdd20a5142efa96dfe79ec842ab
|
|
6
|
+
README.zh.md: bd1b15821bda1577565044c8f9a8e34c1aadfb82
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ A hot-pluggable DeepSeek Harness (DSH) Web GUI plugin with a Host-authoritative
|
|
|
34
34
|
Install the aggregate package or this package alone, then restart `dsh web`:
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
dsh plugin --profile web add @linxin666/dsh-client-ui-task-board
|
|
37
|
+
dsh plugin --profile web add @linxin666/dsh-client-ui-task-board@latest
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
For local development:
|
package/README.zh.md
CHANGED
package/lib/client.js
CHANGED
|
@@ -260,19 +260,65 @@ window.__ModuleLoader__.load({
|
|
|
260
260
|
* can never match (for example `0 0 30 2 *`). The five-year horizon includes
|
|
261
261
|
* a full leap cycle, so a valid February 29 schedule remains reachable from
|
|
262
262
|
* every non-leap year.
|
|
263
|
+
*
|
|
264
|
+
* Walks candidate year/month/day/hour/minute values straight from the parsed
|
|
265
|
+
* field sets instead of scanning every minute: a sparse expression such as
|
|
266
|
+
* `0 0 29 2 *` used to iterate ~1.5M wall-clock minutes before reaching the
|
|
267
|
+
* next leap day. Wall-clock field construction + the final `matches` re-check
|
|
268
|
+
* preserve the old minute scan's DST semantics exactly (nonexistent spring
|
|
269
|
+
* minutes normalize forward and the repeated fall-back hour is never visited).
|
|
263
270
|
*/
|
|
264
271
|
function nextRunAtMs(expr, fromMs) {
|
|
265
272
|
const schedule = parseCron(expr);
|
|
266
273
|
if (schedule === null) return void 0;
|
|
267
274
|
if (!hasPossibleCalendarDay(schedule)) return void 0;
|
|
268
275
|
const from = new Date(fromMs);
|
|
269
|
-
const scan = new Date(from.getFullYear(), from.getMonth(), from.getDate(), from.getHours(), from.getMinutes() + 1, 0, 0);
|
|
270
276
|
const limitMs = fromMs + 5 * 366 * 24 * 60 * 60 * 1e3;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
277
|
+
const sortedMinutes = [...schedule.minutes].sort((a, b) => a - b);
|
|
278
|
+
const sortedHours = [...schedule.hours].sort((a, b) => a - b);
|
|
279
|
+
const sortedMonths = [...schedule.months].sort((a, b) => a - b);
|
|
280
|
+
let year = from.getFullYear();
|
|
281
|
+
let month = from.getMonth() + 1;
|
|
282
|
+
let day = from.getDate();
|
|
283
|
+
let hour = from.getHours();
|
|
284
|
+
let minute = from.getMinutes() + 1;
|
|
285
|
+
while (new Date(year, month - 1, 1, 0, 0, 0, 0).getTime() <= limitMs) {
|
|
286
|
+
for (const candidateMonth of sortedMonths) {
|
|
287
|
+
if (candidateMonth < month) continue;
|
|
288
|
+
const daysInMonth = new Date(year, candidateMonth, 0).getDate();
|
|
289
|
+
const dayStart = candidateMonth === month ? day : 1;
|
|
290
|
+
for (let candidateDay = dayStart; candidateDay <= daysInMonth; candidateDay += 1) {
|
|
291
|
+
if (!dayCandidate(schedule, new Date(year, candidateMonth - 1, candidateDay, 0, 0, 0, 0))) continue;
|
|
292
|
+
const hourStart = candidateMonth === month && candidateDay === day ? hour : 0;
|
|
293
|
+
for (const candidateHour of sortedHours) {
|
|
294
|
+
if (candidateHour < hourStart) continue;
|
|
295
|
+
const minuteStart = candidateMonth === month && candidateDay === day && candidateHour === hour ? minute : 0;
|
|
296
|
+
for (const candidateMinute of sortedMinutes) {
|
|
297
|
+
if (candidateMinute < minuteStart) continue;
|
|
298
|
+
const candidate = new Date(year, candidateMonth - 1, candidateDay, candidateHour, candidateMinute, 0, 0);
|
|
299
|
+
const time = candidate.getTime();
|
|
300
|
+
if (time <= fromMs) continue;
|
|
301
|
+
if (time > limitMs) return void 0;
|
|
302
|
+
if (matches(schedule, candidate)) return time;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
year += 1;
|
|
308
|
+
month = 1;
|
|
309
|
+
day = 1;
|
|
310
|
+
hour = 0;
|
|
311
|
+
minute = 0;
|
|
274
312
|
}
|
|
275
313
|
}
|
|
314
|
+
/** Day/weekday OR gate shared by {@link matches} and the candidate scan. */
|
|
315
|
+
function dayCandidate(schedule, date) {
|
|
316
|
+
const dayMatches = schedule.days.has(date.getDate());
|
|
317
|
+
const weekdayMatches = schedule.weekdays.has(date.getDay());
|
|
318
|
+
if (schedule.dayWildcard) return weekdayMatches;
|
|
319
|
+
if (schedule.weekdayWildcard) return dayMatches;
|
|
320
|
+
return dayMatches || weekdayMatches;
|
|
321
|
+
}
|
|
276
322
|
/** Reject impossible month/day pairs without spending the multi-year scan. */
|
|
277
323
|
function hasPossibleCalendarDay(schedule) {
|
|
278
324
|
if (schedule.dayWildcard || !schedule.weekdayWildcard) return true;
|
|
@@ -331,11 +377,7 @@ window.__ModuleLoader__.load({
|
|
|
331
377
|
if (!schedule.minutes.has(date.getMinutes())) return false;
|
|
332
378
|
if (!schedule.hours.has(date.getHours())) return false;
|
|
333
379
|
if (!schedule.months.has(date.getMonth() + 1)) return false;
|
|
334
|
-
|
|
335
|
-
const weekdayMatches = schedule.weekdays.has(date.getDay());
|
|
336
|
-
if (schedule.dayWildcard) return weekdayMatches;
|
|
337
|
-
if (schedule.weekdayWildcard) return dayMatches;
|
|
338
|
-
return dayMatches || weekdayMatches;
|
|
380
|
+
return dayCandidate(schedule, date);
|
|
339
381
|
}
|
|
340
382
|
function isDigits(value) {
|
|
341
383
|
return /^\d+$/.test(value);
|
|
@@ -945,8 +987,8 @@ window.__ModuleLoader__.load({
|
|
|
945
987
|
this.acceptRemote(await transport.bootstrap(this.tasks));
|
|
946
988
|
if (!this.remoteSubscribed) {
|
|
947
989
|
this.remoteSubscribed = true;
|
|
948
|
-
this.disposers.push(transport.subscribe(() => {
|
|
949
|
-
this.
|
|
990
|
+
this.disposers.push(transport.subscribe((event) => {
|
|
991
|
+
this.onRemoteEvent(event);
|
|
950
992
|
}));
|
|
951
993
|
}
|
|
952
994
|
return true;
|
|
@@ -956,6 +998,24 @@ window.__ModuleLoader__.load({
|
|
|
956
998
|
return false;
|
|
957
999
|
}
|
|
958
1000
|
}
|
|
1001
|
+
/**
|
|
1002
|
+
* SSE frames carry revision/scheduler/power. When the revision matches the
|
|
1003
|
+
* one already applied, apply the frame's scheduler/power in place and skip
|
|
1004
|
+
* the full /state fetch; otherwise the 5 s heartbeat would re-clone and
|
|
1005
|
+
* re-serialize the whole ledger per tab even while nothing changes.
|
|
1006
|
+
*/
|
|
1007
|
+
onRemoteEvent(event) {
|
|
1008
|
+
if (event !== void 0 && this.hostState !== void 0 && event.revision === this.hostState.revision && typeof event.scheduler === "object" && event.scheduler !== null && typeof event.power === "object" && event.power !== null) {
|
|
1009
|
+
this.hostState = {
|
|
1010
|
+
revision: event.revision,
|
|
1011
|
+
scheduler: event.scheduler,
|
|
1012
|
+
power: event.power
|
|
1013
|
+
};
|
|
1014
|
+
this.notify();
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
this.refreshRemote();
|
|
1018
|
+
}
|
|
959
1019
|
async refreshRemote(preserveError) {
|
|
960
1020
|
const transport = this.deps.transport;
|
|
961
1021
|
if (transport === void 0) return true;
|
|
@@ -1850,6 +1910,7 @@ window.__ModuleLoader__.load({
|
|
|
1850
1910
|
type: "button",
|
|
1851
1911
|
className: board_module_css_default.card,
|
|
1852
1912
|
"data-status": archived ? "archived" : task.status,
|
|
1913
|
+
"data-dsh-part": "card",
|
|
1853
1914
|
"data-pending": pending || void 0,
|
|
1854
1915
|
onClick,
|
|
1855
1916
|
title: task.description !== "" ? task.description : task.title,
|
|
@@ -2467,6 +2528,7 @@ window.__ModuleLoader__.load({
|
|
|
2467
2528
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2468
2529
|
className: board_module_css_default.board,
|
|
2469
2530
|
"data-dsh-taskboard-board": "",
|
|
2531
|
+
"data-dsh-plugin": "task-board",
|
|
2470
2532
|
children: [
|
|
2471
2533
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
2472
2534
|
className: board_module_css_default.boardHeader,
|
|
@@ -2542,6 +2604,7 @@ window.__ModuleLoader__.load({
|
|
|
2542
2604
|
children: archiveView ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2543
2605
|
className: board_module_css_default.column,
|
|
2544
2606
|
"data-status": "archived",
|
|
2607
|
+
"data-dsh-part": "column",
|
|
2545
2608
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
2546
2609
|
className: board_module_css_default.columnHeader,
|
|
2547
2610
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
@@ -2568,6 +2631,7 @@ window.__ModuleLoader__.load({
|
|
|
2568
2631
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2569
2632
|
className: board_module_css_default.column,
|
|
2570
2633
|
"data-status": column.status,
|
|
2634
|
+
"data-dsh-part": "column",
|
|
2571
2635
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
2572
2636
|
className: board_module_css_default.columnHeader,
|
|
2573
2637
|
children: [
|
|
@@ -2655,6 +2719,7 @@ window.__ModuleLoader__.load({
|
|
|
2655
2719
|
if (column === void 0) return;
|
|
2656
2720
|
container = document.createElement("div");
|
|
2657
2721
|
container.dataset.dshTaskboardView = "";
|
|
2722
|
+
container.dataset.dshPlugin = "task-board";
|
|
2658
2723
|
container.className = board_module_css_default.boardView;
|
|
2659
2724
|
column.appendChild(container);
|
|
2660
2725
|
root = (0, react_dom_client.createRoot)(container);
|
|
@@ -2702,9 +2767,7 @@ window.__ModuleLoader__.load({
|
|
|
2702
2767
|
};
|
|
2703
2768
|
}
|
|
2704
2769
|
//#endregion
|
|
2705
|
-
//#region src/client/sidebar-entry.ts
|
|
2706
|
-
/** Inline icon (matches the shell's 16px nav-icon look). */
|
|
2707
|
-
const ICON = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="2.5" width="12" height="11" rx="1.5"/><path d="M2 6.5h12M6.5 6.5v7"/></svg>`;
|
|
2770
|
+
//#region src/client/sidebar-entry-core.ts
|
|
2708
2771
|
/** Find the sidebar shell root element, or undefined while not yet mounted. */
|
|
2709
2772
|
function sidebarRoot() {
|
|
2710
2773
|
const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
|
|
@@ -2718,27 +2781,30 @@ window.__ModuleLoader__.load({
|
|
|
2718
2781
|
for (const child of root.children) if (child.tagName === "BUTTON") return child;
|
|
2719
2782
|
}
|
|
2720
2783
|
/** Build the entry row (a detached button; insert once the shell is up). */
|
|
2721
|
-
function createEntry(
|
|
2784
|
+
function createEntry(options) {
|
|
2722
2785
|
const entry = document.createElement("button");
|
|
2723
2786
|
entry.type = "button";
|
|
2724
|
-
entry.
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2787
|
+
entry.setAttribute(options.rowAttribute, "");
|
|
2788
|
+
if (options.plugin !== void 0) {
|
|
2789
|
+
entry.setAttribute("data-dsh-plugin", options.plugin);
|
|
2790
|
+
entry.setAttribute("data-dsh-part", "sidebar-entry");
|
|
2791
|
+
}
|
|
2792
|
+
entry.className = options.css["entry"] ?? "";
|
|
2793
|
+
entry.setAttribute("aria-label", options.label());
|
|
2794
|
+
if (options.tooltip !== void 0) entry.setAttribute("title", options.tooltip());
|
|
2795
|
+
entry.innerHTML = "<span class=\"" + (options.css["entryIcon"] ?? "") + "\">" + options.icon + "</span><span class=\"" + (options.css["entryLabel"] ?? "") + "\">" + options.label() + "</span>";
|
|
2796
|
+
entry.addEventListener("click", options.onToggle);
|
|
2731
2797
|
return entry;
|
|
2732
2798
|
}
|
|
2733
2799
|
/** Re-insert the entry after the New Session row (before the browser region). */
|
|
2734
|
-
function placeEntry(root, entry) {
|
|
2800
|
+
function placeEntry(root, entry, options) {
|
|
2735
2801
|
const button = newSessionButton(root);
|
|
2736
2802
|
if (button === void 0) return false;
|
|
2737
2803
|
if (entry.parentElement !== root) {
|
|
2738
2804
|
const row = button.closest("[class*=\"logoRow\"]");
|
|
2739
2805
|
const base = row !== null && row.parentElement === root ? row : button;
|
|
2740
|
-
const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches("
|
|
2741
|
-
const anchor = family.length > 0 ? family[0] : base.nextElementSibling;
|
|
2806
|
+
const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(options.familySelectors.join(", ")));
|
|
2807
|
+
const anchor = options.position === "before" ? family.length > 0 ? family[0] : base.nextElementSibling : family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
2742
2808
|
root.insertBefore(entry, anchor);
|
|
2743
2809
|
}
|
|
2744
2810
|
return true;
|
|
@@ -2746,12 +2812,12 @@ window.__ModuleLoader__.load({
|
|
|
2746
2812
|
/**
|
|
2747
2813
|
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
2748
2814
|
* on later React re-renders.
|
|
2749
|
-
* @param
|
|
2815
|
+
* @param options - the row's attribute/icon/copy/action/ordering configuration.
|
|
2750
2816
|
* @returns disposer removing the entry and its observers.
|
|
2751
2817
|
*/
|
|
2752
|
-
function mountSidebarEntry(
|
|
2753
|
-
if (typeof document !== "undefined" && document.querySelector(
|
|
2754
|
-
const entry = createEntry(
|
|
2818
|
+
function mountSidebarEntry$1(options) {
|
|
2819
|
+
if (typeof document !== "undefined" && document.querySelector(options.rowSelector) !== null) return () => {};
|
|
2820
|
+
const entry = createEntry(options);
|
|
2755
2821
|
let root;
|
|
2756
2822
|
let placed = false;
|
|
2757
2823
|
const tryPlace = () => {
|
|
@@ -2768,7 +2834,7 @@ window.__ModuleLoader__.load({
|
|
|
2768
2834
|
}
|
|
2769
2835
|
root ??= sidebarRoot();
|
|
2770
2836
|
if (root === void 0) return;
|
|
2771
|
-
placed = placeEntry(root, entry);
|
|
2837
|
+
placed = placeEntry(root, entry, options);
|
|
2772
2838
|
if (placed) rootObserver.observe(root, {
|
|
2773
2839
|
childList: true,
|
|
2774
2840
|
subtree: true
|
|
@@ -2787,25 +2853,59 @@ window.__ModuleLoader__.load({
|
|
|
2787
2853
|
tryPlace();
|
|
2788
2854
|
return;
|
|
2789
2855
|
}
|
|
2790
|
-
if (!root.contains(entry)) placed = placeEntry(root, entry);
|
|
2856
|
+
if (!root.contains(entry)) placed = placeEntry(root, entry, options);
|
|
2791
2857
|
});
|
|
2792
|
-
const
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2858
|
+
const unsubscribeActive = options.active === void 0 ? void 0 : (() => {
|
|
2859
|
+
const syncActive = () => {
|
|
2860
|
+
if (options.active.isOpen()) entry.dataset.active = "true";
|
|
2861
|
+
else delete entry.dataset.active;
|
|
2862
|
+
};
|
|
2863
|
+
const unsubscribe = options.active.subscribe(syncActive);
|
|
2864
|
+
syncActive();
|
|
2865
|
+
return unsubscribe;
|
|
2866
|
+
})();
|
|
2798
2867
|
tryPlace();
|
|
2799
2868
|
return () => {
|
|
2800
2869
|
waitObserver.disconnect();
|
|
2801
2870
|
rootObserver.disconnect();
|
|
2802
|
-
|
|
2871
|
+
unsubscribeActive?.();
|
|
2803
2872
|
entry.remove();
|
|
2804
2873
|
};
|
|
2805
2874
|
}
|
|
2806
2875
|
//#endregion
|
|
2876
|
+
//#region src/client/sidebar-entry.ts
|
|
2877
|
+
/** Stable data attribute identifying the injected entry row. */
|
|
2878
|
+
const ENTRY_SELECTOR = "[data-dsh-taskboard-entry]";
|
|
2879
|
+
/** Inline icon (matches the shell's 16px nav-icon look). */
|
|
2880
|
+
const ICON = "<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2\" y=\"2.5\" width=\"12\" height=\"11\" rx=\"1.5\"/><path d=\"M2 6.5h12M6.5 6.5v7\"/></svg>";
|
|
2881
|
+
/**
|
|
2882
|
+
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
2883
|
+
* on later React re-renders.
|
|
2884
|
+
* @param controller - the board controller the entry toggles.
|
|
2885
|
+
* @returns disposer removing the entry and its observers.
|
|
2886
|
+
*/
|
|
2887
|
+
function mountSidebarEntry(controller) {
|
|
2888
|
+
return mountSidebarEntry$1({
|
|
2889
|
+
rowAttribute: "data-dsh-taskboard-entry",
|
|
2890
|
+
rowSelector: ENTRY_SELECTOR,
|
|
2891
|
+
plugin: "task-board",
|
|
2892
|
+
icon: ICON,
|
|
2893
|
+
css: board_module_css_default,
|
|
2894
|
+
label: () => t("entry.label"),
|
|
2895
|
+
onToggle: () => {
|
|
2896
|
+
controller.toggleBoard();
|
|
2897
|
+
},
|
|
2898
|
+
position: "before",
|
|
2899
|
+
familySelectors: ["[data-dsh-taskboard-entry]", "[data-dsh-ssh-entry]"],
|
|
2900
|
+
active: {
|
|
2901
|
+
subscribe: (listener) => controller.subscribe(listener),
|
|
2902
|
+
isOpen: () => controller.getSnapshot().boardOpen
|
|
2903
|
+
}
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
//#endregion
|
|
2807
2907
|
//#region \0dsh-css:packages/dsh-task-board/src/client/settings-card.module.css.mjs
|
|
2808
|
-
const css = ".Jh0q7G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Jh0q7G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Jh0q7G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Jh0q7G_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.Jh0q7G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Jh0q7G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Jh0q7G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Jh0q7G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_chevronOpen{transform:rotate(180deg)}.Jh0q7G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Jh0q7G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Jh0q7G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Jh0q7G_discard,.Jh0q7G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Jh0q7G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Jh0q7G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Jh0q7G_discard:disabled,.Jh0q7G_save:disabled{opacity:.4;cursor:default}.Jh0q7G_discard:focus-visible,.Jh0q7G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Jh0q7G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Jh0q7G_field+.Jh0q7G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Jh0q7G_head{align-items:center;gap:8px;display:flex}.Jh0q7G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Jh0q7G_badges{align-items:center;gap:8px;display:inline-flex}.Jh0q7G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Jh0q7G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Jh0q7G_reset:disabled{cursor:default}.Jh0q7G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Jh0q7G_input,.Jh0q7G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_input:focus-visible,.Jh0q7G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Jh0q7G_input:disabled,.Jh0q7G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Jh0q7G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Jh0q7G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Jh0q7G_card,.Jh0q7G_header,.Jh0q7G_chevron,.Jh0q7G_chevronOpen,.Jh0q7G_discard,.Jh0q7G_save{transition:none}}";
|
|
2908
|
+
const css = ".Jh0q7G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Jh0q7G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Jh0q7G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Jh0q7G_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.Jh0q7G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Jh0q7G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Jh0q7G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Jh0q7G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_chevronOpen{transform:rotate(180deg)}.Jh0q7G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Jh0q7G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Jh0q7G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Jh0q7G_discard,.Jh0q7G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Jh0q7G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Jh0q7G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Jh0q7G_discard:disabled,.Jh0q7G_save:disabled{opacity:.4;cursor:default}.Jh0q7G_discard:focus-visible,.Jh0q7G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Jh0q7G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Jh0q7G_field+.Jh0q7G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Jh0q7G_head{align-items:center;gap:8px;display:flex}.Jh0q7G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Jh0q7G_badges{align-items:center;gap:8px;display:inline-flex}.Jh0q7G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Jh0q7G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Jh0q7G_reset:disabled{cursor:default}.Jh0q7G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Jh0q7G_input,.Jh0q7G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_input:focus-visible,.Jh0q7G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Jh0q7G_input:disabled,.Jh0q7G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Jh0q7G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Jh0q7G_selectWrap{position:relative}.Jh0q7G_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.Jh0q7G_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Jh0q7G_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_selectChevronOpen{transform:rotate(180deg)}.Jh0q7G_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.Jh0q7G_selectPopupOpen{opacity:1;transform:none}.Jh0q7G_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.Jh0q7G_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.Jh0q7G_selectOption:hover,.Jh0q7G_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.Jh0q7G_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.Jh0q7G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Jh0q7G_card,.Jh0q7G_header,.Jh0q7G_chevron,.Jh0q7G_chevronOpen,.Jh0q7G_discard,.Jh0q7G_save,.Jh0q7G_selectChevron,.Jh0q7G_selectChevronOpen,.Jh0q7G_selectPopup{transition:none}}";
|
|
2809
2909
|
const tagId = "@linxin666/dsh-client-ui-task-board/settings-card.module.css";
|
|
2810
2910
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2811
2911
|
const tag = document.createElement("style");
|
|
@@ -2842,7 +2942,18 @@ window.__ModuleLoader__.load({
|
|
|
2842
2942
|
"readOnly": "Jh0q7G_readOnly",
|
|
2843
2943
|
"reset": "Jh0q7G_reset",
|
|
2844
2944
|
"save": "Jh0q7G_save",
|
|
2845
|
-
"select": "Jh0q7G_select"
|
|
2945
|
+
"select": "Jh0q7G_select",
|
|
2946
|
+
"selectButton": "Jh0q7G_selectButton",
|
|
2947
|
+
"selectChevron": "Jh0q7G_selectChevron",
|
|
2948
|
+
"selectChevronOpen": "Jh0q7G_selectChevronOpen",
|
|
2949
|
+
"selectLabel": "Jh0q7G_selectLabel",
|
|
2950
|
+
"selectOption": "Jh0q7G_selectOption",
|
|
2951
|
+
"selectOptionActive": "Jh0q7G_selectOptionActive",
|
|
2952
|
+
"selectOptionSelected": "Jh0q7G_selectOptionSelected",
|
|
2953
|
+
"selectPopup": "Jh0q7G_selectPopup",
|
|
2954
|
+
"selectPopupClose": "Jh0q7G_selectPopupClose",
|
|
2955
|
+
"selectPopupOpen": "Jh0q7G_selectPopupOpen",
|
|
2956
|
+
"selectWrap": "Jh0q7G_selectWrap"
|
|
2846
2957
|
};
|
|
2847
2958
|
//#endregion
|
|
2848
2959
|
//#region src/client/PluginSettingsCard.tsx
|
|
@@ -2976,6 +3087,175 @@ window.__ModuleLoader__.load({
|
|
|
2976
3087
|
}) : null]
|
|
2977
3088
|
});
|
|
2978
3089
|
}
|
|
3090
|
+
const NON_SKIN_BODY_MARKERS = /* @__PURE__ */ new Set(["dshSkinCenter", "dshSidebarCollapsed"]);
|
|
3091
|
+
function isSkinActive() {
|
|
3092
|
+
return Object.keys(document.body.dataset).some((key) => key.startsWith("dsh") && !NON_SKIN_BODY_MARKERS.has(key));
|
|
3093
|
+
}
|
|
3094
|
+
const SELECT_CLOSE_MS = 100;
|
|
3095
|
+
/**
|
|
3096
|
+
* The shared dual-mode select control. While an appearance skin is active it
|
|
3097
|
+
* renders the legacy native `<select>` untouched, so element-level skin
|
|
3098
|
+
* selectors keep working; under the default appearance it renders a
|
|
3099
|
+
* self-drawn `role="listbox"` popup whose open/close is transition-animated.
|
|
3100
|
+
* Staged cards reach it through BooleanField/ChoiceField; immediate-apply
|
|
3101
|
+
* editors (the side-card prefs) bind it directly through onEdit.
|
|
3102
|
+
* 双模式下拉框:皮肤激活时用原生 select,默认外观用自绘动画弹层。
|
|
3103
|
+
*/
|
|
3104
|
+
function SelectField(props) {
|
|
3105
|
+
const { id, options, value } = props;
|
|
3106
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
3107
|
+
const [closing, setClosing] = (0, react.useState)(false);
|
|
3108
|
+
const [phase, setPhase] = (0, react.useState)("initial");
|
|
3109
|
+
const [activeIndex, setActiveIndex] = (0, react.useState)(0);
|
|
3110
|
+
const closeTimer = (0, react.useRef)(void 0);
|
|
3111
|
+
const wrapRef = (0, react.useRef)(null);
|
|
3112
|
+
const popupRef = (0, react.useRef)(null);
|
|
3113
|
+
const currentIndex = () => {
|
|
3114
|
+
const index = options.findIndex((option) => option.value === value);
|
|
3115
|
+
return index >= 0 ? index : 0;
|
|
3116
|
+
};
|
|
3117
|
+
const close = (0, react.useCallback)(() => {
|
|
3118
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
3119
|
+
setClosing(true);
|
|
3120
|
+
closeTimer.current = setTimeout(() => {
|
|
3121
|
+
setClosing(false);
|
|
3122
|
+
setOpen(false);
|
|
3123
|
+
}, SELECT_CLOSE_MS);
|
|
3124
|
+
}, []);
|
|
3125
|
+
const openPopup = () => {
|
|
3126
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
3127
|
+
setActiveIndex(currentIndex());
|
|
3128
|
+
setPhase("initial");
|
|
3129
|
+
setClosing(false);
|
|
3130
|
+
setOpen(true);
|
|
3131
|
+
};
|
|
3132
|
+
const commit = (index) => {
|
|
3133
|
+
const option = options[index];
|
|
3134
|
+
if (option) props.onEdit(option.value);
|
|
3135
|
+
close();
|
|
3136
|
+
};
|
|
3137
|
+
const onTriggerClick = () => {
|
|
3138
|
+
if (props.disabled) return;
|
|
3139
|
+
if (open && !closing) close();
|
|
3140
|
+
else openPopup();
|
|
3141
|
+
};
|
|
3142
|
+
const onKeyDown = (event) => {
|
|
3143
|
+
if (props.disabled) return;
|
|
3144
|
+
const count = options.length;
|
|
3145
|
+
switch (event.key) {
|
|
3146
|
+
case "ArrowDown":
|
|
3147
|
+
case "ArrowUp":
|
|
3148
|
+
case "Enter":
|
|
3149
|
+
case " ":
|
|
3150
|
+
event.preventDefault();
|
|
3151
|
+
if (!open) openPopup();
|
|
3152
|
+
else if (!closing) if (event.key === "ArrowDown") setActiveIndex((index) => (index + 1) % count);
|
|
3153
|
+
else if (event.key === "ArrowUp") setActiveIndex((index) => (index - 1 + count) % count);
|
|
3154
|
+
else commit(activeIndex);
|
|
3155
|
+
break;
|
|
3156
|
+
case "Escape":
|
|
3157
|
+
if (open) {
|
|
3158
|
+
event.preventDefault();
|
|
3159
|
+
event.stopPropagation();
|
|
3160
|
+
close();
|
|
3161
|
+
}
|
|
3162
|
+
break;
|
|
3163
|
+
case "Tab":
|
|
3164
|
+
if (open) close();
|
|
3165
|
+
break;
|
|
3166
|
+
}
|
|
3167
|
+
};
|
|
3168
|
+
(0, react.useEffect)(() => () => {
|
|
3169
|
+
if (closeTimer.current !== void 0) clearTimeout(closeTimer.current);
|
|
3170
|
+
}, []);
|
|
3171
|
+
(0, react.useLayoutEffect)(() => {
|
|
3172
|
+
if (open && !closing && phase === "initial") {
|
|
3173
|
+
popupRef.current?.offsetHeight;
|
|
3174
|
+
setPhase("open");
|
|
3175
|
+
}
|
|
3176
|
+
}, [
|
|
3177
|
+
open,
|
|
3178
|
+
closing,
|
|
3179
|
+
phase
|
|
3180
|
+
]);
|
|
3181
|
+
(0, react.useEffect)(() => {
|
|
3182
|
+
if (!open) return;
|
|
3183
|
+
const onPointerDown = (event) => {
|
|
3184
|
+
const target = event.target;
|
|
3185
|
+
if (target instanceof Node && !wrapRef.current?.contains(target)) close();
|
|
3186
|
+
};
|
|
3187
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
3188
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
3189
|
+
}, [open, close]);
|
|
3190
|
+
(0, react.useEffect)(() => {
|
|
3191
|
+
if (props.disabled && open) close();
|
|
3192
|
+
}, [
|
|
3193
|
+
props.disabled,
|
|
3194
|
+
open,
|
|
3195
|
+
close
|
|
3196
|
+
]);
|
|
3197
|
+
if (isSkinActive()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
3198
|
+
id,
|
|
3199
|
+
className: settings_card_module_css_default.select,
|
|
3200
|
+
value,
|
|
3201
|
+
disabled: props.disabled,
|
|
3202
|
+
onChange: (event) => {
|
|
3203
|
+
props.onEdit(event.target.value);
|
|
3204
|
+
},
|
|
3205
|
+
children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3206
|
+
value: option.value,
|
|
3207
|
+
children: option.label
|
|
3208
|
+
}, option.value))
|
|
3209
|
+
});
|
|
3210
|
+
const label = options.find((option) => option.value === value)?.label ?? "";
|
|
3211
|
+
const popupClass = closing ? `${settings_card_module_css_default.selectPopup} ${settings_card_module_css_default.selectPopupClose}` : phase === "open" ? `${settings_card_module_css_default.selectPopup} ${settings_card_module_css_default.selectPopupOpen}` : settings_card_module_css_default.selectPopup;
|
|
3212
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3213
|
+
className: settings_card_module_css_default.selectWrap,
|
|
3214
|
+
ref: wrapRef,
|
|
3215
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3216
|
+
type: "button",
|
|
3217
|
+
id,
|
|
3218
|
+
className: `${settings_card_module_css_default.select} ${settings_card_module_css_default.selectButton}`,
|
|
3219
|
+
disabled: props.disabled,
|
|
3220
|
+
"aria-haspopup": "listbox",
|
|
3221
|
+
"aria-expanded": open,
|
|
3222
|
+
"aria-activedescendant": open ? `${id}-o${activeIndex}` : void 0,
|
|
3223
|
+
"aria-invalid": props.invalid || void 0,
|
|
3224
|
+
onClick: onTriggerClick,
|
|
3225
|
+
onKeyDown,
|
|
3226
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3227
|
+
className: settings_card_module_css_default.selectLabel,
|
|
3228
|
+
children: label
|
|
3229
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3230
|
+
width: "14",
|
|
3231
|
+
height: "14",
|
|
3232
|
+
viewBox: "0 0 14 14",
|
|
3233
|
+
fill: "none",
|
|
3234
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3235
|
+
className: open ? `${settings_card_module_css_default.selectChevron} ${settings_card_module_css_default.selectChevronOpen}` : settings_card_module_css_default.selectChevron,
|
|
3236
|
+
"aria-hidden": "true",
|
|
3237
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
3238
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
3239
|
+
fill: "currentColor"
|
|
3240
|
+
})
|
|
3241
|
+
})]
|
|
3242
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3243
|
+
className: popupClass,
|
|
3244
|
+
role: "listbox",
|
|
3245
|
+
ref: popupRef,
|
|
3246
|
+
children: options.map((option, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3247
|
+
id: `${id}-o${index}`,
|
|
3248
|
+
role: "option",
|
|
3249
|
+
"aria-selected": option.value === value,
|
|
3250
|
+
className: `${settings_card_module_css_default.selectOption}${option.value === value ? ` ${settings_card_module_css_default.selectOptionSelected}` : ""}${index === activeIndex && !closing ? ` ${settings_card_module_css_default.selectOptionActive}` : ""}`,
|
|
3251
|
+
onClick: () => {
|
|
3252
|
+
commit(index);
|
|
3253
|
+
},
|
|
3254
|
+
children: option.label
|
|
3255
|
+
}, option.value))
|
|
3256
|
+
}) : null]
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
2979
3259
|
/** A staged boolean field: 继承 / 开 / 关. */
|
|
2980
3260
|
function BooleanField(props) {
|
|
2981
3261
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -3001,28 +3281,26 @@ window.__ModuleLoader__.load({
|
|
|
3001
3281
|
})]
|
|
3002
3282
|
}) : null]
|
|
3003
3283
|
}),
|
|
3004
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
3284
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
|
|
3005
3285
|
id: props.id,
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
disabled: props.disabled,
|
|
3009
|
-
onChange: (event) => {
|
|
3010
|
-
props.onEdit(event.target.value);
|
|
3011
|
-
},
|
|
3012
|
-
children: [
|
|
3013
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3286
|
+
options: [
|
|
3287
|
+
{
|
|
3014
3288
|
value: "",
|
|
3015
|
-
|
|
3016
|
-
}
|
|
3017
|
-
|
|
3289
|
+
label: props.inheritLabel
|
|
3290
|
+
},
|
|
3291
|
+
{
|
|
3018
3292
|
value: "true",
|
|
3019
|
-
|
|
3020
|
-
}
|
|
3021
|
-
|
|
3293
|
+
label: props.onLabel
|
|
3294
|
+
},
|
|
3295
|
+
{
|
|
3022
3296
|
value: "false",
|
|
3023
|
-
|
|
3024
|
-
}
|
|
3025
|
-
]
|
|
3297
|
+
label: props.offLabel
|
|
3298
|
+
}
|
|
3299
|
+
],
|
|
3300
|
+
value: props.text,
|
|
3301
|
+
disabled: props.disabled,
|
|
3302
|
+
invalid: props.invalid,
|
|
3303
|
+
onEdit: props.onEdit
|
|
3026
3304
|
}),
|
|
3027
3305
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3028
3306
|
className: settings_card_module_css_default.hint,
|
|
@@ -3339,24 +3617,16 @@ window.__ModuleLoader__.load({
|
|
|
3339
3617
|
const [power, setPower] = (0, react.useState)();
|
|
3340
3618
|
(0, react.useEffect)(() => {
|
|
3341
3619
|
let live = true;
|
|
3342
|
-
const
|
|
3620
|
+
const events = new EventSource("/api/task-board/events");
|
|
3621
|
+
events.onmessage = (message) => {
|
|
3343
3622
|
try {
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
credentials: "same-origin"
|
|
3347
|
-
});
|
|
3348
|
-
if (!response.ok) return;
|
|
3349
|
-
const snapshot = await response.json();
|
|
3350
|
-
if (live) setPower(snapshot.power);
|
|
3623
|
+
const frame = JSON.parse(message.data);
|
|
3624
|
+
if (frame.power !== void 0 && live) setPower(frame.power);
|
|
3351
3625
|
} catch {}
|
|
3352
3626
|
};
|
|
3353
|
-
refresh();
|
|
3354
|
-
const timer = setInterval(() => {
|
|
3355
|
-
refresh();
|
|
3356
|
-
}, 5e3);
|
|
3357
3627
|
return () => {
|
|
3358
3628
|
live = false;
|
|
3359
|
-
|
|
3629
|
+
events.close();
|
|
3360
3630
|
};
|
|
3361
3631
|
}, []);
|
|
3362
3632
|
const fieldProps = {
|
|
@@ -3515,8 +3785,14 @@ window.__ModuleLoader__.load({
|
|
|
3515
3785
|
}
|
|
3516
3786
|
subscribe(listener) {
|
|
3517
3787
|
const events = new EventSource(`${TASK_BOARD_API_PREFIX}/events`);
|
|
3518
|
-
events.onmessage = () => {
|
|
3519
|
-
|
|
3788
|
+
events.onmessage = (message) => {
|
|
3789
|
+
try {
|
|
3790
|
+
const parsed = JSON.parse(message.data);
|
|
3791
|
+
if (parsed === null || typeof parsed !== "object" || typeof parsed.revision !== "number") throw new Error("invalid event frame");
|
|
3792
|
+
listener(parsed);
|
|
3793
|
+
} catch {
|
|
3794
|
+
listener();
|
|
3795
|
+
}
|
|
3520
3796
|
};
|
|
3521
3797
|
const onVisible = () => {
|
|
3522
3798
|
if (document.visibilityState === "visible") listener();
|