hyper-scheduler 1.0.0 → 1.1.0

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 (86) hide show
  1. package/README.md +17 -0
  2. package/dist/devtools-Bxtz0rO_.cjs +1 -0
  3. package/dist/devtools-ByJU-Gv1.js +2505 -0
  4. package/dist/index.cjs +1 -0
  5. package/dist/index.js +1048 -0
  6. package/dist/index.umd.cjs +1 -0
  7. package/docs/.vitepress/cache/deps/_metadata.json +31 -0
  8. package/docs/.vitepress/cache/deps/chunk-EKBJ2FPM.js +12798 -0
  9. package/docs/.vitepress/cache/deps/chunk-EKBJ2FPM.js.map +7 -0
  10. package/docs/.vitepress/cache/deps/package.json +3 -0
  11. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4505 -0
  12. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  13. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +9731 -0
  14. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  15. package/docs/.vitepress/cache/deps/vue.js +347 -0
  16. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  17. package/docs/.vitepress/config.ts +4 -0
  18. package/docs/.vitepress/theme/components/DemoFrame.vue +111 -0
  19. package/docs/.vitepress/theme/custom.css +6 -0
  20. package/docs/.vitepress/theme/index.ts +10 -0
  21. package/docs/api/devtools.md +13 -0
  22. package/docs/api/scheduler.md +28 -8
  23. package/docs/examples/index.md +55 -8
  24. package/docs/guide/getting-started.md +38 -0
  25. package/package.json +13 -4
  26. package/.editorconfig +0 -21
  27. package/.eslintrc.cjs +0 -26
  28. package/GEMINI.md +0 -1
  29. package/examples/browser/index.html +0 -354
  30. package/examples/node/simple.js +0 -36
  31. package/examples/react-demo/index.html +0 -12
  32. package/examples/react-demo/package.json +0 -23
  33. package/examples/react-demo/src/App.css +0 -212
  34. package/examples/react-demo/src/App.jsx +0 -160
  35. package/examples/react-demo/src/main.jsx +0 -9
  36. package/examples/react-demo/vite.config.ts +0 -12
  37. package/examples/react-demo/yarn.lock +0 -752
  38. package/examples/vue-demo/index.html +0 -12
  39. package/examples/vue-demo/package.json +0 -21
  40. package/examples/vue-demo/src/App.vue +0 -373
  41. package/examples/vue-demo/src/main.ts +0 -4
  42. package/examples/vue-demo/vite.config.ts +0 -13
  43. package/examples/vue-demo/yarn.lock +0 -375
  44. package/src/constants.ts +0 -18
  45. package/src/core/retry-strategy.ts +0 -28
  46. package/src/core/scheduler.ts +0 -601
  47. package/src/core/task-registry.ts +0 -58
  48. package/src/index.ts +0 -74
  49. package/src/platform/browser/browser-timer.ts +0 -66
  50. package/src/platform/browser/main-thread-timer.ts +0 -16
  51. package/src/platform/browser/worker.ts +0 -31
  52. package/src/platform/node/debug-cli.ts +0 -19
  53. package/src/platform/node/node-timer.ts +0 -15
  54. package/src/platform/timer-strategy.ts +0 -19
  55. package/src/plugins/dev-tools.ts +0 -101
  56. package/src/types.ts +0 -115
  57. package/src/ui/components/devtools.ts +0 -525
  58. package/src/ui/components/floating-trigger.ts +0 -102
  59. package/src/ui/components/icons.ts +0 -16
  60. package/src/ui/components/resizer.ts +0 -129
  61. package/src/ui/components/task-detail.ts +0 -228
  62. package/src/ui/components/task-header.ts +0 -319
  63. package/src/ui/components/task-list.ts +0 -416
  64. package/src/ui/components/timeline.ts +0 -364
  65. package/src/ui/debug-panel.ts +0 -56
  66. package/src/ui/i18n/en.ts +0 -76
  67. package/src/ui/i18n/index.ts +0 -42
  68. package/src/ui/i18n/zh.ts +0 -76
  69. package/src/ui/store/dev-tools-store.ts +0 -191
  70. package/src/ui/styles/theme.css.ts +0 -56
  71. package/src/ui/styles.ts +0 -43
  72. package/src/utils/cron-lite.ts +0 -221
  73. package/src/utils/cron.ts +0 -20
  74. package/src/utils/id.ts +0 -10
  75. package/src/utils/schedule.ts +0 -93
  76. package/src/vite-env.d.ts +0 -1
  77. package/stats.html +0 -4949
  78. package/tests/integration/Debug.test.ts +0 -58
  79. package/tests/unit/Plugin.test.ts +0 -16
  80. package/tests/unit/RetryStrategy.test.ts +0 -21
  81. package/tests/unit/Scheduler.test.ts +0 -38
  82. package/tests/unit/schedule.test.ts +0 -70
  83. package/tests/unit/ui/DevToolsStore.test.ts +0 -67
  84. package/tsconfig.json +0 -28
  85. package/vite.config.ts +0 -51
  86. package/vitest.config.ts +0 -24
@@ -0,0 +1,2505 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { TaskStatus, SchedulerEvents } from "./index.js";
5
+ const en = {
6
+ header: {
7
+ title: "DevTools",
8
+ searchPlaceholder: "Search IDs/Tags... 🔍",
9
+ toggleDock: "Toggle Dock Position",
10
+ toggleTheme: "Toggle Theme",
11
+ close: "Close"
12
+ },
13
+ stats: {
14
+ loading: "Loading...",
15
+ fps: "FPS",
16
+ status: "Status",
17
+ active: "Active",
18
+ total: "Total",
19
+ mainThread: "Main Thread",
20
+ scheduler: "Scheduler",
21
+ running: "Running",
22
+ stopped: "Stopped"
23
+ },
24
+ tabs: {
25
+ tasks: "Tasks List",
26
+ timeline: "Timeline"
27
+ },
28
+ list: {
29
+ idTags: "ID / Tags",
30
+ status: "Status",
31
+ driver: "Driver",
32
+ driverWorker: "Worker (Web Worker)",
33
+ driverMain: "Main (setTimeout)",
34
+ schedule: "Schedule",
35
+ count: "Count",
36
+ lastRun: "Last Run",
37
+ actions: "Actions",
38
+ noTags: "(No Tags)",
39
+ tip: "✨ Tip: Click a row for details & history."
40
+ },
41
+ detail: {
42
+ back: "Back",
43
+ details: "Task Details",
44
+ config: "Config",
45
+ history: "Execution History",
46
+ lastRuns: "Last {n} runs",
47
+ avgDuration: "Avg Duration",
48
+ startTime: "Start Time",
49
+ duration: "Duration",
50
+ drift: "Drift",
51
+ status: "Status",
52
+ noHistory: "No execution history",
53
+ noTask: "No task selected",
54
+ success: "Success",
55
+ failed: "Failed",
56
+ error: "Error"
57
+ },
58
+ timeline: {
59
+ zoom: "Zoom",
60
+ timeRange: "Time Range: Last {n}s",
61
+ legend: "Legend",
62
+ instant: "Instant",
63
+ duration: "Duration",
64
+ workerDriver: "Worker Driver",
65
+ mainDriver: "Main Driver"
66
+ },
67
+ status: {
68
+ running: "Running",
69
+ paused: "Paused",
70
+ stopped: "Stopped",
71
+ idle: "Scheduled",
72
+ error: "Error"
73
+ },
74
+ actions: {
75
+ trigger: "Trigger now",
76
+ start: "Start task",
77
+ stop: "Stop task",
78
+ remove: "Remove task"
79
+ }
80
+ };
81
+ const zh = {
82
+ header: {
83
+ title: "调试工具",
84
+ searchPlaceholder: "搜索 ID/标签... 🔍",
85
+ toggleDock: "切换停靠位置",
86
+ toggleTheme: "切换主题",
87
+ close: "关闭"
88
+ },
89
+ stats: {
90
+ loading: "加载中...",
91
+ fps: "帧率",
92
+ status: "状态",
93
+ active: "活跃",
94
+ total: "总数",
95
+ mainThread: "主线程",
96
+ scheduler: "调度器",
97
+ running: "运行中",
98
+ stopped: "已停止"
99
+ },
100
+ tabs: {
101
+ tasks: "任务列表",
102
+ timeline: "时间线"
103
+ },
104
+ list: {
105
+ idTags: "ID / 标签",
106
+ status: "状态",
107
+ driver: "驱动",
108
+ driverWorker: "Worker (Web Worker)",
109
+ driverMain: "主线程 (setTimeout)",
110
+ schedule: "调度规则",
111
+ count: "次数",
112
+ lastRun: "最后运行",
113
+ actions: "操作",
114
+ noTags: "(无标签)",
115
+ tip: "✨ 提示: 点击行查看详情和历史记录。"
116
+ },
117
+ detail: {
118
+ back: "返回",
119
+ details: "任务详情",
120
+ config: "配置",
121
+ history: "执行历史",
122
+ lastRuns: "最近 {n} 次运行",
123
+ avgDuration: "平均耗时",
124
+ startTime: "开始时间",
125
+ duration: "耗时",
126
+ drift: "偏差",
127
+ status: "状态",
128
+ noHistory: "暂无执行历史",
129
+ noTask: "未选择任务",
130
+ success: "成功",
131
+ failed: "失败",
132
+ error: "错误"
133
+ },
134
+ timeline: {
135
+ zoom: "缩放",
136
+ timeRange: "时间范围: 最近 {n}秒",
137
+ legend: "图例",
138
+ instant: "瞬间",
139
+ duration: "耗时",
140
+ workerDriver: "Worker 驱动",
141
+ mainDriver: "主线程 驱动"
142
+ },
143
+ status: {
144
+ running: "执行中",
145
+ paused: "已暂停",
146
+ stopped: "已停止",
147
+ idle: "调度中",
148
+ error: "错误"
149
+ },
150
+ actions: {
151
+ trigger: "立即触发",
152
+ start: "启动任务",
153
+ stop: "停止任务",
154
+ remove: "删除任务"
155
+ }
156
+ };
157
+ let translations = en;
158
+ function setLanguage(lang) {
159
+ translations = lang === "zh" ? zh : en;
160
+ }
161
+ function t(key, params) {
162
+ const keys = key.split(".");
163
+ let value = translations;
164
+ for (const k of keys) {
165
+ if (value && typeof value === "object" && k in value) {
166
+ value = value[k];
167
+ } else {
168
+ return key;
169
+ }
170
+ }
171
+ if (typeof value !== "string") {
172
+ return key;
173
+ }
174
+ if (params) {
175
+ return value.replace(/\{(\w+)\}/g, (_, k) => {
176
+ return params[k] !== void 0 ? String(params[k]) : `{${k}}`;
177
+ });
178
+ }
179
+ return value;
180
+ }
181
+ class DevToolsStore {
182
+ constructor() {
183
+ __publicField(this, "state");
184
+ __publicField(this, "listeners");
185
+ __publicField(this, "scheduler");
186
+ this.state = {
187
+ isOpen: false,
188
+ activeTab: "tasks",
189
+ theme: "auto",
190
+ dockPosition: "right",
191
+ panelSize: { width: 500, height: 500 },
192
+ language: "en",
193
+ filterText: "",
194
+ selectedTaskId: null,
195
+ tasks: /* @__PURE__ */ new Map(),
196
+ history: /* @__PURE__ */ new Map(),
197
+ fps: 0,
198
+ schedulerRunning: false
199
+ };
200
+ this.listeners = /* @__PURE__ */ new Map();
201
+ }
202
+ setScheduler(scheduler) {
203
+ this.scheduler = scheduler;
204
+ }
205
+ getState() {
206
+ return this.state;
207
+ }
208
+ subscribe(key, callback) {
209
+ if (!this.listeners.has(key)) {
210
+ this.listeners.set(key, /* @__PURE__ */ new Set());
211
+ }
212
+ this.listeners.get(key).add(callback);
213
+ return () => {
214
+ var _a;
215
+ (_a = this.listeners.get(key)) == null ? void 0 : _a.delete(callback);
216
+ };
217
+ }
218
+ notify(key, value) {
219
+ var _a;
220
+ (_a = this.listeners.get(key)) == null ? void 0 : _a.forEach((cb) => cb(value));
221
+ }
222
+ toggle() {
223
+ this.state.isOpen = !this.state.isOpen;
224
+ this.notify("isOpen", this.state.isOpen);
225
+ }
226
+ setTheme(theme) {
227
+ this.state.theme = theme;
228
+ this.notify("theme", this.state.theme);
229
+ }
230
+ /**
231
+ * Set language synchronously without notifying listeners.
232
+ * Used during initialization before child components render.
233
+ */
234
+ setLanguageSync(lang) {
235
+ setLanguage(lang);
236
+ this.state.language = lang;
237
+ }
238
+ setLanguage(lang) {
239
+ setLanguage(lang);
240
+ this.state.language = lang;
241
+ this.notify("language", this.state.language);
242
+ }
243
+ setPanelSize(size) {
244
+ this.state.panelSize = { ...this.state.panelSize, ...size };
245
+ try {
246
+ localStorage.setItem("hs-panel-size", JSON.stringify(this.state.panelSize));
247
+ } catch (e) {
248
+ }
249
+ this.notify("panelSize", this.state.panelSize);
250
+ }
251
+ setTab(tab) {
252
+ this.state.activeTab = tab;
253
+ this.notify("activeTab", this.state.activeTab);
254
+ }
255
+ setDockPosition(pos) {
256
+ this.state.dockPosition = pos;
257
+ this.notify("dockPosition", this.state.dockPosition);
258
+ }
259
+ setFilterText(text) {
260
+ this.state.filterText = text;
261
+ this.notify("filterText", this.state.filterText);
262
+ }
263
+ updateTask(task) {
264
+ const newTasks = new Map(this.state.tasks);
265
+ newTasks.set(task.id, task);
266
+ this.state.tasks = newTasks;
267
+ this.notify("tasks", this.state.tasks);
268
+ if ("history" in task && Array.isArray(task.history)) {
269
+ const newHistory = new Map(this.state.history);
270
+ newHistory.set(task.id, task.history);
271
+ this.state.history = newHistory;
272
+ this.notify("history", this.state.history);
273
+ }
274
+ }
275
+ selectTask(id) {
276
+ this.state.selectedTaskId = id;
277
+ this.notify("selectedTaskId", id);
278
+ }
279
+ addHistory(taskId, record) {
280
+ const list = [...this.state.history.get(taskId) || []];
281
+ list.push(record);
282
+ if (list.length > 50) {
283
+ list.splice(0, list.length - 50);
284
+ }
285
+ const newHistory = new Map(this.state.history);
286
+ newHistory.set(taskId, list);
287
+ this.state.history = newHistory;
288
+ this.notify("history", newHistory);
289
+ }
290
+ async triggerTask(id) {
291
+ if (this.scheduler) {
292
+ await this.scheduler.trigger(id);
293
+ }
294
+ }
295
+ stopTask(id) {
296
+ console.log("[DevToolsStore] stopTask:", id);
297
+ if (this.scheduler) {
298
+ this.scheduler.pause(id);
299
+ }
300
+ }
301
+ startTask(id) {
302
+ console.log("[DevToolsStore] startTask:", id);
303
+ if (this.scheduler) {
304
+ this.scheduler.resume(id);
305
+ }
306
+ }
307
+ removeTask(id) {
308
+ if (this.scheduler) {
309
+ this.scheduler.remove(id);
310
+ const newTasks = new Map(this.state.tasks);
311
+ newTasks.delete(id);
312
+ this.state.tasks = newTasks;
313
+ this.notify("tasks", this.state.tasks);
314
+ }
315
+ }
316
+ setSchedulerRunning(running) {
317
+ this.state.schedulerRunning = running;
318
+ this.notify("schedulerRunning", running);
319
+ }
320
+ }
321
+ const themeStyles = `
322
+ :host {
323
+ --hs-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
324
+ --hs-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
325
+ --hs-font-size: 12px;
326
+ --hs-line-height: 1.5;
327
+ --hs-panel-width: 400px;
328
+ --hs-panel-height: 300px;
329
+
330
+ /* 等宽数字字体 */
331
+ --hs-font-monospaced-num: var(--hs-font-mono);
332
+
333
+ /* Light Theme (Default) */
334
+ --hs-bg: #ffffff;
335
+ --hs-bg-secondary: #f3f4f6;
336
+ --hs-text: #1f2937;
337
+ --hs-text-secondary: #6b7280;
338
+ --hs-border: #e5e7eb;
339
+ --hs-primary: #3b82f6;
340
+ --hs-primary-hover: #2563eb;
341
+ --hs-danger: #ef4444;
342
+ --hs-danger-hover: #dc2626;
343
+ --hs-success: #10b981;
344
+ --hs-warning: #f59e0b;
345
+
346
+ --hs-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
347
+ --hs-radius: 6px;
348
+ --hs-header-height: 40px;
349
+ --hs-z-index: 9999;
350
+ --hs-z-index-overlay: 9998;
351
+
352
+ /* Default display styles for the host itself */
353
+ background: var(--hs-bg);
354
+ color: var(--hs-text);
355
+ font-family: var(--hs-font-family);
356
+ font-size: var(--hs-font-size);
357
+ line-height: var(--hs-line-height);
358
+ }
359
+
360
+ :host([theme="dark"]) {
361
+ --hs-bg: #111827;
362
+ --hs-bg-secondary: #1f2937;
363
+ --hs-text: #f9fafb;
364
+ --hs-text-secondary: #9ca3af;
365
+ --hs-border: #374151;
366
+ --hs-primary: #60a5fa;
367
+ --hs-primary-hover: #3b82f6;
368
+ --hs-danger: #f87171;
369
+ --hs-danger-hover: #ef4444;
370
+ --hs-success: #34d399;
371
+ --hs-warning: #fbbf24;
372
+
373
+ --hs-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
374
+ }
375
+
376
+ :host {
377
+ background: var(--hs-bg);
378
+ color: var(--hs-text);
379
+ }
380
+ `;
381
+ const ICONS = {
382
+ back: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>`,
383
+ trigger: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>`,
384
+ pause: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>`,
385
+ resume: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`,
386
+ remove: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>`,
387
+ close: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`,
388
+ sun: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>`,
389
+ moon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>`,
390
+ dock: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="18" rx="2" ry="2"></rect><line x1="2" y1="15" x2="22" y2="15"></line></svg>`,
391
+ dockRight: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="18" rx="2" ry="2"></rect><line x1="15" y1="3" x2="15" y2="21"></line></svg>`,
392
+ chart: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg>`
393
+ };
394
+ const STORAGE_KEY_POS = "hs-trigger-position";
395
+ const STORAGE_KEY_COLLAPSED = "hs-trigger-collapsed";
396
+ class FloatingTrigger extends HTMLElement {
397
+ constructor() {
398
+ super();
399
+ __publicField(this, "_shadow");
400
+ // 配置属性
401
+ __publicField(this, "_position", "bottom-right");
402
+ __publicField(this, "_bgColor", "");
403
+ __publicField(this, "_textColor", "");
404
+ __publicField(this, "_isDragging", false);
405
+ __publicField(this, "_wasDragging", false);
406
+ // 标记是否发生过拖拽
407
+ __publicField(this, "_offsetX", 0);
408
+ __publicField(this, "_offsetY", 0);
409
+ __publicField(this, "_isCollapsed", false);
410
+ this._shadow = this.attachShadow({ mode: "open" });
411
+ }
412
+ // 新增:收起状态
413
+ static get observedAttributes() {
414
+ return ["position", "bg-color", "text-color"];
415
+ }
416
+ connectedCallback() {
417
+ this.loadState();
418
+ this.render();
419
+ this.addEventListeners();
420
+ this.applyPosition();
421
+ window.addEventListener("resize", this.onResize.bind(this));
422
+ }
423
+ disconnectedCallback() {
424
+ window.removeEventListener("resize", this.onResize.bind(this));
425
+ }
426
+ onResize() {
427
+ this.applyPosition();
428
+ }
429
+ attributeChangedCallback(name, _oldVal, newVal) {
430
+ if (name === "position") {
431
+ this._position = newVal || "bottom-right";
432
+ this.applyPosition();
433
+ } else if (name === "bg-color") {
434
+ this._bgColor = newVal || "";
435
+ this.updateStyles();
436
+ } else if (name === "text-color") {
437
+ this._textColor = newVal || "";
438
+ this.updateStyles();
439
+ }
440
+ }
441
+ loadState() {
442
+ try {
443
+ const savedPos = localStorage.getItem(STORAGE_KEY_POS);
444
+ if (savedPos) {
445
+ const { x, y } = JSON.parse(savedPos);
446
+ this.style.setProperty("--hs-trigger-left", `${x}px`);
447
+ this.style.setProperty("--hs-trigger-top", `${y}px`);
448
+ this.style.setProperty("--hs-trigger-position-set", "true");
449
+ }
450
+ const savedCollapsed = localStorage.getItem(STORAGE_KEY_COLLAPSED);
451
+ if (savedCollapsed === "true") {
452
+ this._isCollapsed = true;
453
+ }
454
+ } catch (e) {
455
+ console.warn("[FloatingTrigger] Failed to load state:", e);
456
+ }
457
+ }
458
+ saveState() {
459
+ const button = this._shadow.querySelector("button");
460
+ if (!button) return;
461
+ const rect = button.getBoundingClientRect();
462
+ localStorage.setItem(STORAGE_KEY_POS, JSON.stringify({ x: rect.left, y: rect.top }));
463
+ localStorage.setItem(STORAGE_KEY_COLLAPSED, String(this._isCollapsed));
464
+ }
465
+ applyPosition() {
466
+ const button = this._shadow.querySelector("button");
467
+ if (!button) return;
468
+ const maxX = window.innerWidth - button.offsetWidth;
469
+ const maxY = window.innerHeight - button.offsetHeight;
470
+ if (!this.style.getPropertyValue("--hs-trigger-position-set")) {
471
+ const pos = this._position;
472
+ button.style.top = pos.includes("top") ? "20px" : "auto";
473
+ button.style.bottom = pos.includes("bottom") ? "20px" : "auto";
474
+ button.style.left = pos.includes("left") ? "20px" : "auto";
475
+ button.style.right = pos.includes("right") ? "20px" : "auto";
476
+ } else {
477
+ let currentX = parseFloat(this.style.getPropertyValue("--hs-trigger-left") || "0");
478
+ let currentY = parseFloat(this.style.getPropertyValue("--hs-trigger-top") || "0");
479
+ currentX = Math.max(0, Math.min(currentX, maxX));
480
+ currentY = Math.max(0, Math.min(currentY, maxY));
481
+ this.style.setProperty("--hs-trigger-left", `${currentX}px`);
482
+ this.style.setProperty("--hs-trigger-top", `${currentY}px`);
483
+ button.style.left = `${currentX}px`;
484
+ button.style.top = `${currentY}px`;
485
+ button.style.right = "auto";
486
+ button.style.bottom = "auto";
487
+ }
488
+ this.updateCollapsedState();
489
+ }
490
+ updateStyles() {
491
+ const button = this._shadow.querySelector("button");
492
+ if (button) {
493
+ button.style.background = this._bgColor || "var(--hs-primary)";
494
+ button.style.color = this._textColor || "white";
495
+ if (this._bgColor) {
496
+ button.style.setProperty("--hs-trigger-bg-hover", `${this._bgColor}; filter: brightness(1.1);`);
497
+ } else {
498
+ button.style.removeProperty("--hs-trigger-bg-hover");
499
+ }
500
+ }
501
+ }
502
+ updateCollapsedState() {
503
+ const button = this._shadow.querySelector("button");
504
+ if (button) {
505
+ if (this._isCollapsed) {
506
+ button.classList.add("collapsed");
507
+ } else {
508
+ button.classList.remove("collapsed");
509
+ }
510
+ }
511
+ }
512
+ addEventListeners() {
513
+ const btn = this._shadow.querySelector("button");
514
+ if (!btn) return;
515
+ btn.addEventListener("click", (e) => {
516
+ if (this._wasDragging) {
517
+ e.preventDefault();
518
+ e.stopPropagation();
519
+ this._wasDragging = false;
520
+ return;
521
+ }
522
+ this.dispatchEvent(new CustomEvent("toggle", { bubbles: true, composed: true }));
523
+ });
524
+ const collapseBtn = this._shadow.querySelector(".collapse-btn");
525
+ collapseBtn == null ? void 0 : collapseBtn.addEventListener("click", (e) => {
526
+ e.stopPropagation();
527
+ e.preventDefault();
528
+ this._isCollapsed = !this._isCollapsed;
529
+ this.updateCollapsedState();
530
+ this.saveState();
531
+ });
532
+ btn.addEventListener("dblclick", (e) => {
533
+ if (this._isCollapsed) {
534
+ e.stopPropagation();
535
+ this._isCollapsed = false;
536
+ this.updateCollapsedState();
537
+ this.saveState();
538
+ }
539
+ });
540
+ btn.addEventListener("mousedown", (e) => {
541
+ if (e.button !== 0) return;
542
+ if (e.target.closest(".collapse-btn")) return;
543
+ this._isDragging = true;
544
+ this._wasDragging = false;
545
+ this._offsetX = e.clientX - btn.getBoundingClientRect().left;
546
+ this._offsetY = e.clientY - btn.getBoundingClientRect().top;
547
+ const startX = e.clientX;
548
+ const startY = e.clientY;
549
+ let hasMoved = false;
550
+ const onMouseMove = (moveEvent) => {
551
+ if (!this._isDragging) return;
552
+ if (!hasMoved && (Math.abs(moveEvent.clientX - startX) > 2 || Math.abs(moveEvent.clientY - startY) > 2)) {
553
+ hasMoved = true;
554
+ this._wasDragging = true;
555
+ btn.style.cursor = "grabbing";
556
+ btn.style.transition = "none";
557
+ }
558
+ if (hasMoved) {
559
+ let newX = moveEvent.clientX - this._offsetX;
560
+ let newY = moveEvent.clientY - this._offsetY;
561
+ const maxX = window.innerWidth - btn.offsetWidth;
562
+ const maxY = window.innerHeight - btn.offsetHeight;
563
+ newX = Math.max(0, Math.min(newX, maxX));
564
+ newY = Math.max(0, Math.min(newY, maxY));
565
+ btn.style.left = `${newX}px`;
566
+ btn.style.top = `${newY}px`;
567
+ btn.style.right = "auto";
568
+ btn.style.bottom = "auto";
569
+ this.style.setProperty("--hs-trigger-position-set", "true");
570
+ this.style.setProperty("--hs-trigger-left", `${newX}px`);
571
+ this.style.setProperty("--hs-trigger-top", `${newY}px`);
572
+ }
573
+ };
574
+ const onMouseUp = () => {
575
+ if (!this._isDragging) return;
576
+ this._isDragging = false;
577
+ btn.style.cursor = "pointer";
578
+ btn.style.transition = "";
579
+ if (hasMoved) {
580
+ this.saveState();
581
+ const preventClick = (e2) => {
582
+ e2.stopPropagation();
583
+ e2.preventDefault();
584
+ };
585
+ window.addEventListener("click", preventClick, { capture: true, once: true });
586
+ }
587
+ window.removeEventListener("mousemove", onMouseMove);
588
+ window.removeEventListener("mouseup", onMouseUp);
589
+ };
590
+ window.addEventListener("mousemove", onMouseMove);
591
+ window.addEventListener("mouseup", onMouseUp);
592
+ });
593
+ }
594
+ render() {
595
+ const posStyles = `
596
+ top: var(--hs-trigger-top, ${this._position.includes("top") ? "20px" : "auto"});
597
+ bottom: var(--hs-trigger-bottom, ${this._position.includes("bottom") ? "20px" : "auto"});
598
+ left: var(--hs-trigger-left, ${this._position.includes("left") ? "20px" : "auto"});
599
+ right: var(--hs-trigger-right, ${this._position.includes("right") ? "20px" : "auto"});
600
+ transform: ${this._isCollapsed ? "translateX(calc(100% - 12px))" : "translateX(0)"};
601
+ `;
602
+ const bgStyle = this._bgColor ? `background: ${this._bgColor};` : "";
603
+ const colorStyle = this._textColor ? `color: ${this._textColor};` : "";
604
+ this._shadow.innerHTML = `
605
+ <style>
606
+ ${themeStyles}
607
+ button {
608
+ position: fixed;
609
+ ${posStyles}
610
+ width: 48px;
611
+ height: 48px;
612
+ border-radius: 12px; /* 更现代的圆角 */
613
+ background: var(--hs-primary);
614
+ color: white;
615
+ ${bgStyle}
616
+ ${colorStyle}
617
+ border: none;
618
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15); /* 更柔和的阴影 */
619
+ cursor: pointer;
620
+ display: flex;
621
+ align-items: center;
622
+ justify-content: center;
623
+ font-family: var(--hs-font-family);
624
+ z-index: var(--hs-z-index);
625
+ transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), background 0.2s, box-shadow 0.2s;
626
+ overflow: visible; /* 允许子元素(收起按钮)溢出或显示 */
627
+ }
628
+ button:hover {
629
+ ${this._bgColor ? `background: ${this._bgColor}; filter: brightness(1.1);` : "background: var(--hs-primary-hover);"}
630
+ transform: scale(1.05);
631
+ box-shadow: 0 6px 16px rgba(0,0,0,0.2);
632
+ }
633
+ button:active {
634
+ transform: scale(0.95);
635
+ }
636
+
637
+ /* 显式收起按钮 */
638
+ .collapse-btn {
639
+ position: absolute;
640
+ top: 0;
641
+ right: 0;
642
+ width: 20px;
643
+ height: 20px;
644
+ background: rgba(0,0,0,0.1);
645
+ border-bottom-left-radius: 8px;
646
+ display: flex;
647
+ align-items: center;
648
+ justify-content: center;
649
+ opacity: 1; /* 默认可见 */
650
+ transition: opacity 0.2s, background 0.2s;
651
+ color: white;
652
+ font-weight: bold;
653
+ }
654
+ .collapse-btn::before {
655
+ content: '—'; /* 最小化图标 */
656
+ font-size: 12px;
657
+ color: currentColor;
658
+ }
659
+ button:hover .collapse-btn {
660
+ opacity: 1;
661
+ }
662
+ .collapse-btn:hover {
663
+ background: rgba(0,0,0,0.3);
664
+ }
665
+
666
+ /* 收起状态样式 */
667
+ button.collapsed {
668
+ width: 36px;
669
+ height: 36px;
670
+ border-radius: 18px 0 0 18px;
671
+ transform: translateX(calc(100% - 12px));
672
+ opacity: 0.8;
673
+ }
674
+ button.collapsed:hover {
675
+ transform: translateX(calc(100% - 24px)); /* hover 时稍微伸出一点 */
676
+ opacity: 1;
677
+ }
678
+ button.collapsed .icon {
679
+ display: none;
680
+ }
681
+ button.collapsed .collapse-btn {
682
+ right: auto;
683
+ left: 0;
684
+ border-radius: 0 12px 12px 0;
685
+ }
686
+ button.collapsed .collapse-btn::before {
687
+ content: '›'; /* 展开图标 */
688
+ }
689
+ </style>
690
+ <button title="Toggle Hyper Scheduler DevTools">
691
+ <div class="collapse-btn" title="Minimize"></div>
692
+ <span class="icon">${ICONS.chart}</span>
693
+ </button>
694
+ `;
695
+ this.applyPosition();
696
+ this.updateCollapsedState();
697
+ this.updateStyles();
698
+ }
699
+ }
700
+ customElements.define("hs-floating-trigger", FloatingTrigger);
701
+ class TaskHeader extends HTMLElement {
702
+ constructor() {
703
+ super();
704
+ __publicField(this, "_shadow");
705
+ __publicField(this, "_fps", 0);
706
+ __publicField(this, "_stats", { active: 0, total: 0 });
707
+ __publicField(this, "_theme", "auto");
708
+ __publicField(this, "_activeTab", "tasks");
709
+ __publicField(this, "_language", "en");
710
+ __publicField(this, "_schedulerRunning", false);
711
+ __publicField(this, "$fps");
712
+ __publicField(this, "$stats");
713
+ __publicField(this, "$schedulerStatus");
714
+ __publicField(this, "$themeIcon");
715
+ __publicField(this, "$dockIcon");
716
+ __publicField(this, "$tabs");
717
+ __publicField(this, "$searchInput");
718
+ __publicField(this, "$title");
719
+ __publicField(this, "$langBtn");
720
+ this._shadow = this.attachShadow({ mode: "open" });
721
+ }
722
+ connectedCallback() {
723
+ this.render();
724
+ this.cacheDom();
725
+ this.addEventListeners();
726
+ this.updateView();
727
+ }
728
+ set fps(val) {
729
+ this._fps = Math.round(val);
730
+ if (this.$fps) {
731
+ const color = this._fps < 30 ? "var(--hs-danger)" : this._fps < 50 ? "var(--hs-warning)" : "var(--hs-success)";
732
+ this.$fps.innerHTML = `⚡ ${t("stats.fps")}: <span style="color:${color}; font-family:var(--hs-font-monospaced-num);">${this._fps}</span> (${t("stats.mainThread")})`;
733
+ }
734
+ }
735
+ set stats(val) {
736
+ this._stats = val;
737
+ if (this.$stats) {
738
+ this.$stats.innerHTML = `📊 ${t("stats.status")}: <span style="color:var(--hs-success)">🟢 ${t("stats.active")}: ${val.active}</span> <span style="margin-left:12px;color:var(--hs-text-secondary)">⚪ ${t("stats.total")}: ${val.total}</span>`;
739
+ }
740
+ }
741
+ set schedulerRunning(val) {
742
+ this._schedulerRunning = val;
743
+ if (this.$schedulerStatus) {
744
+ const statusText = val ? t("stats.running") : t("stats.stopped");
745
+ const statusColor = val ? "var(--hs-success)" : "var(--hs-danger)";
746
+ const statusIcon = val ? "▶️" : "⏹️";
747
+ this.$schedulerStatus.innerHTML = `${statusIcon} ${t("stats.scheduler")}: <span style="color:${statusColor}">${statusText}</span>`;
748
+ }
749
+ }
750
+ set theme(val) {
751
+ this._theme = val;
752
+ this.setAttribute("theme", val);
753
+ this.updateThemeIcon();
754
+ }
755
+ set language(val) {
756
+ this._language = val;
757
+ this.updateTexts();
758
+ }
759
+ set dockPosition(val) {
760
+ var _a;
761
+ if (this.$dockIcon) {
762
+ this.$dockIcon.innerHTML = val === "right" ? ICONS.dock : ICONS.dockRight;
763
+ (_a = this.$dockIcon.parentElement) == null ? void 0 : _a.setAttribute("title", t("header.toggleDock"));
764
+ }
765
+ }
766
+ set activeTab(val) {
767
+ this._activeTab = val;
768
+ this.updateTabs();
769
+ }
770
+ cacheDom() {
771
+ this.$fps = this._shadow.querySelector(".fps");
772
+ this.$stats = this._shadow.querySelector(".stats");
773
+ this.$schedulerStatus = this._shadow.querySelector(".scheduler-status");
774
+ this.$themeIcon = this._shadow.querySelector(".theme-btn span");
775
+ this.$dockIcon = this._shadow.querySelector(".dock-btn");
776
+ this.$tabs = this._shadow.querySelectorAll(".tab");
777
+ this.$searchInput = this._shadow.querySelector(".search-input");
778
+ this.$title = this._shadow.querySelector(".title");
779
+ this.$langBtn = this._shadow.querySelector(".lang-btn");
780
+ }
781
+ addEventListeners() {
782
+ var _a, _b, _c, _d, _e;
783
+ (_a = this._shadow.querySelector(".dock-btn")) == null ? void 0 : _a.addEventListener("click", () => {
784
+ this.dispatchEvent(new CustomEvent("dock-toggle"));
785
+ });
786
+ (_b = this._shadow.querySelector(".theme-btn")) == null ? void 0 : _b.addEventListener("click", () => {
787
+ const newTheme = this._theme === "dark" ? "light" : "dark";
788
+ this.dispatchEvent(new CustomEvent("theme-toggle", { detail: newTheme }));
789
+ });
790
+ (_c = this._shadow.querySelector(".lang-btn")) == null ? void 0 : _c.addEventListener("click", () => {
791
+ const newLang = this._language === "en" ? "zh" : "en";
792
+ this.dispatchEvent(new CustomEvent("lang-toggle", { detail: newLang }));
793
+ });
794
+ (_d = this._shadow.querySelector(".close-btn")) == null ? void 0 : _d.addEventListener("click", () => {
795
+ this.dispatchEvent(new CustomEvent("close"));
796
+ });
797
+ this.$tabs.forEach((tab) => {
798
+ tab.addEventListener("click", (e) => {
799
+ const target = e.currentTarget.dataset.tab;
800
+ this.dispatchEvent(new CustomEvent("tab-change", { detail: target }));
801
+ });
802
+ });
803
+ (_e = this.$searchInput) == null ? void 0 : _e.addEventListener("input", (e) => {
804
+ const val = e.target.value;
805
+ this.dispatchEvent(new CustomEvent("search", { detail: val }));
806
+ });
807
+ }
808
+ updateThemeIcon() {
809
+ if (this.$themeIcon) {
810
+ this.$themeIcon.innerHTML = this._theme === "dark" ? ICONS.moon : ICONS.sun;
811
+ }
812
+ }
813
+ updateTabs() {
814
+ this.$tabs.forEach((tab) => {
815
+ if (tab.dataset.tab === this._activeTab) {
816
+ tab.classList.add("active");
817
+ } else {
818
+ tab.classList.remove("active");
819
+ }
820
+ });
821
+ }
822
+ updateTexts() {
823
+ if (this.$title) this.$title.innerHTML = `🕒 ${t("header.title")}`;
824
+ if (this.$searchInput) this.$searchInput.placeholder = t("header.searchPlaceholder");
825
+ if (this.$langBtn) this.$langBtn.textContent = this._language === "en" ? "中" : "EN";
826
+ this.$tabs.forEach((tab) => {
827
+ const key = tab.dataset.tab;
828
+ if (key === "tasks") tab.innerHTML = `📌 ${t("tabs.tasks")}`;
829
+ if (key === "timeline") tab.innerHTML = `📈 ${t("tabs.timeline")}`;
830
+ });
831
+ this.stats = this._stats;
832
+ this.fps = this._fps;
833
+ this.schedulerRunning = this._schedulerRunning;
834
+ }
835
+ updateView() {
836
+ this.updateThemeIcon();
837
+ this.updateTabs();
838
+ this.updateTexts();
839
+ }
840
+ render() {
841
+ this._shadow.innerHTML = `
842
+ <style>
843
+ ${themeStyles}
844
+ :host {
845
+ display: block;
846
+ background: var(--hs-bg);
847
+ border-bottom: 1px solid var(--hs-border);
848
+ padding: 0 16px;
849
+ height: var(--hs-header-height);
850
+ height: auto;
851
+ }
852
+ .top-bar {
853
+ display: flex;
854
+ justify-content: space-between;
855
+ align-items: center;
856
+ height: 40px;
857
+ border-bottom: 1px solid var(--hs-border);
858
+ }
859
+ .title {
860
+ font-weight: 600;
861
+ font-size: 13px;
862
+ color: var(--hs-text);
863
+ display: flex;
864
+ align-items: center;
865
+ gap: 6px;
866
+ }
867
+ .search-box {
868
+ flex: 1;
869
+ max-width: 300px;
870
+ margin: 0px 32px 0 16px;
871
+ }
872
+ .search-input {
873
+ width: 100%;
874
+ background: var(--hs-bg-secondary);
875
+ border: 1px solid var(--hs-border);
876
+ color: var(--hs-text);
877
+ padding: 6px 12px;
878
+ border-radius: 4px;
879
+ font-size: 12px;
880
+ }
881
+ .search-input::placeholder {
882
+ color: var(--hs-text-secondary);
883
+ }
884
+ .controls {
885
+ display: flex;
886
+ align-items: center;
887
+ gap: 4px;
888
+ }
889
+ button {
890
+ background: transparent;
891
+ border: none;
892
+ color: var(--hs-text-secondary);
893
+ cursor: pointer;
894
+ padding: 4px;
895
+ border-radius: 4px;
896
+ display: flex;
897
+ align-items: center;
898
+ justify-content: center;
899
+ font-size: 12px;
900
+ width: 28px;
901
+ height: 28px;
902
+ }
903
+ .theme-btn span {
904
+ margin-top: 4px;
905
+ }
906
+ button:hover {
907
+ background: var(--hs-bg-secondary);
908
+ color: var(--hs-text);
909
+ }
910
+ button svg {
911
+ width: 16px;
912
+ height: 16px;
913
+ }
914
+ .lang-btn {
915
+ font-weight: 600;
916
+ }
917
+ .stats-bar {
918
+ display: flex;
919
+ justify-content: space-between;
920
+ align-items: center;
921
+ height: 30px;
922
+ font-size: 11px;
923
+ color: var(--hs-text-secondary);
924
+ border-bottom: 1px solid var(--hs-border);
925
+ gap: 16px;
926
+ }
927
+ .stats-left {
928
+ display: flex;
929
+ gap: 16px;
930
+ }
931
+ .tabs-bar {
932
+ display: flex;
933
+ height: 36px;
934
+ gap: 0;
935
+ }
936
+ .tab {
937
+ display: flex;
938
+ align-items: center;
939
+ gap: 6px;
940
+ font-size: 12px;
941
+ color: var(--hs-text-secondary);
942
+ cursor: pointer;
943
+ border-bottom: 3px solid transparent;
944
+ padding: 0 16px;
945
+ transition: all 0.2s;
946
+ }
947
+ .tab:hover {
948
+ color: var(--hs-text);
949
+ background: var(--hs-bg-secondary);
950
+ }
951
+ .tab.active {
952
+ color: var(--hs-text);
953
+ font-weight: 600;
954
+ border-bottom-color: var(--hs-primary);
955
+ background: var(--hs-bg-secondary);
956
+ }
957
+ </style>
958
+
959
+ <div class="top-bar">
960
+ <div class="title"></div>
961
+ <div class="search-box">
962
+ <input type="text" class="search-input">
963
+ </div>
964
+ <div class="controls">
965
+ <button class="lang-btn" title="Switch Language">EN</button>
966
+ <button class="dock-btn" title="Toggle Dock">${ICONS.dock}</button>
967
+ <button class="theme-btn" title="Toggle Theme"><span>${ICONS.sun}</span></button>
968
+ <button class="close-btn" title="Close">${ICONS.close}</button>
969
+ </div>
970
+ </div>
971
+
972
+ <div class="stats-bar">
973
+ <div class="stats-left">
974
+ <div class="scheduler-status"></div>
975
+ <div class="stats"></div>
976
+ </div>
977
+ <div class="fps"></div>
978
+ </div>
979
+
980
+ <div class="tabs-bar">
981
+ <div class="tab active" data-tab="tasks"></div>
982
+ <div class="tab" data-tab="timeline"></div>
983
+ </div>
984
+ `;
985
+ }
986
+ }
987
+ customElements.define("hs-task-header", TaskHeader);
988
+ class TaskList extends HTMLElement {
989
+ constructor() {
990
+ super();
991
+ __publicField(this, "_shadow");
992
+ __publicField(this, "_tasks", []);
993
+ __publicField(this, "_lastExecutionTimes", /* @__PURE__ */ new Map());
994
+ __publicField(this, "_expandedNamespaces", /* @__PURE__ */ new Set(["default"]));
995
+ this._shadow = this.attachShadow({ mode: "open" });
996
+ }
997
+ connectedCallback() {
998
+ this.render();
999
+ }
1000
+ set tasks(map) {
1001
+ const newTasks = Array.from(map.values());
1002
+ newTasks.forEach((task) => {
1003
+ const oldTask = this._tasks.find((t2) => t2.id === task.id);
1004
+ if (oldTask && task.executionCount > oldTask.executionCount) {
1005
+ this._lastExecutionTimes.set(task.id, Date.now());
1006
+ }
1007
+ });
1008
+ this._tasks = newTasks;
1009
+ this.renderRows();
1010
+ }
1011
+ groupTasksByNamespace(tasks) {
1012
+ const groups = /* @__PURE__ */ new Map();
1013
+ tasks.forEach((task) => {
1014
+ const ns = task.namespace || "default";
1015
+ if (!groups.has(ns)) {
1016
+ groups.set(ns, []);
1017
+ }
1018
+ groups.get(ns).push(task);
1019
+ });
1020
+ return groups;
1021
+ }
1022
+ filter(text, map) {
1023
+ const all = Array.from(map.values());
1024
+ if (!text) {
1025
+ this._tasks = all;
1026
+ } else {
1027
+ const lower = text.toLowerCase();
1028
+ this._tasks = all.filter(
1029
+ (t2) => t2.id.toLowerCase().includes(lower) || t2.tags.some((tag) => tag.toLowerCase().includes(lower)) || t2.namespace && t2.namespace.toLowerCase().includes(lower)
1030
+ );
1031
+ }
1032
+ this.renderRows();
1033
+ }
1034
+ // Method to update table headers when language changes
1035
+ updateHeaders() {
1036
+ const thead = this._shadow.querySelector("thead");
1037
+ if (thead) {
1038
+ thead.innerHTML = `
1039
+ <tr>
1040
+ <th style="width:40px">#</th>
1041
+ <th style="min-width:150px">${t("list.idTags")}</th>
1042
+ <th style="width:150px">${t("list.status")}</th>
1043
+ <th style="width:70px">${t("list.driver")}</th>
1044
+ <th style="width:100px">${t("list.schedule")}</th>
1045
+ <th style="width:60px">${t("list.count")}</th>
1046
+ <th style="width:100px">${t("list.lastRun")}</th>
1047
+ <th style="width:100px">${t("list.actions")}</th>
1048
+ </tr>
1049
+ `;
1050
+ }
1051
+ const tip = this._shadow.querySelector(".tip");
1052
+ if (tip) {
1053
+ tip.textContent = t("list.tip");
1054
+ }
1055
+ this.renderRows();
1056
+ }
1057
+ getStatusIcon(status, taskId) {
1058
+ const lastExec = this._lastExecutionTimes.get(taskId);
1059
+ const isRecentlyExecuted = lastExec && Date.now() - lastExec < 1e3;
1060
+ switch (status) {
1061
+ case TaskStatus.RUNNING:
1062
+ return `<span style="color:var(--hs-primary)">🔵</span> <strong>${t("status.running")}</strong>`;
1063
+ case TaskStatus.STOPPED:
1064
+ return `<span style="color:var(--hs-text-secondary)">⚪</span> ${t("status.stopped")}`;
1065
+ case TaskStatus.IDLE:
1066
+ if (isRecentlyExecuted) {
1067
+ return `<span class="status-flash" style="color:var(--hs-success)">🟢</span> ${t("status.idle")}`;
1068
+ }
1069
+ return `<span style="color:var(--hs-success)">🟢</span> ${t("status.idle")}`;
1070
+ case TaskStatus.ERROR:
1071
+ return `<span style="color:var(--hs-warning)">🟠</span> ${t("status.error")}`;
1072
+ default:
1073
+ return status;
1074
+ }
1075
+ }
1076
+ formatSchedule(schedule) {
1077
+ if (typeof schedule === "number") {
1078
+ return `${schedule}ms`;
1079
+ }
1080
+ if (schedule && (schedule.includes("*") || schedule.includes(" "))) {
1081
+ return schedule.length > 15 ? schedule.substring(0, 12) + "..." : schedule;
1082
+ }
1083
+ return schedule || "-";
1084
+ }
1085
+ formatTime(timestamp) {
1086
+ if (!timestamp) return "-";
1087
+ const date = new Date(timestamp);
1088
+ return date.toLocaleTimeString("en-US", {
1089
+ hour12: false,
1090
+ hour: "2-digit",
1091
+ minute: "2-digit",
1092
+ second: "2-digit"
1093
+ }) + "." + date.getMilliseconds().toString().padStart(3, "0");
1094
+ }
1095
+ getDriverBadge(driver) {
1096
+ const d = driver || "worker";
1097
+ if (d === "worker") {
1098
+ return `<span class="driver-badge worker" title="${t("list.driverWorker")}">W</span>`;
1099
+ }
1100
+ return `<span class="driver-badge main" title="${t("list.driverMain")}">M</span>`;
1101
+ }
1102
+ renderTaskRow(task, index, isNested = false) {
1103
+ const lastExec = this._lastExecutionTimes.get(task.id);
1104
+ const isRecentlyExecuted = lastExec && Date.now() - lastExec < 1e3;
1105
+ const rowClass = isRecentlyExecuted ? "recently-executed" : "";
1106
+ const nestedClass = isNested ? "nested-task" : "";
1107
+ return `
1108
+ <tr data-id="${task.id}" class="${rowClass} ${nestedClass}">
1109
+ <td class="col-num">
1110
+ ${isNested ? "" : index + 1}
1111
+ </td>
1112
+ <td class="col-id">
1113
+ <div class="task-id">
1114
+ ${task.id}
1115
+ ${task.namespace && task.namespace !== "default" && !isNested ? `<span class="namespace-badge" title="Namespace">${task.namespace}</span>` : ""}
1116
+ </div>
1117
+ <div class="tags">
1118
+ ${task.tags && task.tags.length > 0 ? task.tags.map((t2) => `<span class="tag">${t2}</span>`).join("") : `<span class="no-tags">${t("list.noTags")}</span>`}
1119
+ </div>
1120
+ </td>
1121
+ <td>${this.getStatusIcon(task.status, task.id)}</td>
1122
+ <td>${this.getDriverBadge(task.driver)}</td>
1123
+ <td>${this.formatSchedule(task.schedule)}</td>
1124
+ <td>${task.executionCount || 0}</td>
1125
+ <td>${this.formatTime(task.lastRun)}</td>
1126
+ <td class="col-actions">
1127
+ <div class="action-group">
1128
+ <button class="btn-icon" data-action="trigger" title="${t("actions.trigger")}" ${task.status === TaskStatus.RUNNING ? "disabled" : ""}>${ICONS.trigger}</button>
1129
+ ${task.status === TaskStatus.STOPPED || task.status === TaskStatus.ERROR ? `<button class="btn-icon success" data-action="start" title="${t("actions.start")}">${ICONS.resume}</button>` : `<button class="btn-icon warning" data-action="stop" title="${t("actions.stop")}" ${task.status === TaskStatus.RUNNING ? "disabled" : ""}>${ICONS.pause}</button>`}
1130
+ <button class="btn-icon danger" data-action="remove" title="${t("actions.remove")}">${ICONS.remove}</button>
1131
+ </div>
1132
+ </td>
1133
+ </tr>
1134
+ `;
1135
+ }
1136
+ renderRows() {
1137
+ const tbody = this._shadow.querySelector("tbody");
1138
+ if (!tbody) return;
1139
+ const groups = this.groupTasksByNamespace(this._tasks);
1140
+ let html = "";
1141
+ let globalIndex = 0;
1142
+ const defaultTasks = groups.get("default");
1143
+ if (defaultTasks && defaultTasks.length > 0) {
1144
+ html += defaultTasks.map((task) => this.renderTaskRow(task, ++globalIndex, false)).join("");
1145
+ }
1146
+ groups.delete("default");
1147
+ const sortedNamespaces = Array.from(groups.keys()).sort((a, b) => a.localeCompare(b));
1148
+ sortedNamespaces.forEach((ns) => {
1149
+ const tasks = groups.get(ns);
1150
+ const isExpanded = this._expandedNamespaces.has(ns);
1151
+ const icon = isExpanded ? "▼" : "▶";
1152
+ html += `
1153
+ <tr class="namespace-row ${isExpanded ? "ns-expanded" : ""}" data-ns="${ns}">
1154
+ <td colspan="8">
1155
+ <span class="ns-toggle">${icon}</span>
1156
+ <span class="ns-icon">📂</span>
1157
+ <span class="ns-name">${ns}</span>
1158
+ <span class="ns-count">(${tasks.length})</span>
1159
+ </td>
1160
+ </tr>
1161
+ `;
1162
+ if (isExpanded) {
1163
+ html += tasks.map((task) => this.renderTaskRow(task, ++globalIndex, true)).join("");
1164
+ }
1165
+ });
1166
+ tbody.innerHTML = html;
1167
+ }
1168
+ render() {
1169
+ this._shadow.innerHTML = `
1170
+ <style>
1171
+ ${themeStyles}
1172
+ :host {
1173
+ display: flex;
1174
+ flex-direction: column;
1175
+ height: 100%;
1176
+ background: var(--hs-bg);
1177
+ }
1178
+ .table-container {
1179
+ flex: 1;
1180
+ min-height: 0;
1181
+ overflow-y: auto;
1182
+ overflow-x: auto;
1183
+ position: relative;
1184
+ }
1185
+ table {
1186
+ width: 100%;
1187
+ border-collapse: collapse;
1188
+ font-size: var(--hs-font-size);
1189
+ color: var(--hs-text);
1190
+ }
1191
+ thead {
1192
+ position: sticky;
1193
+ top: 0;
1194
+ z-index: 2;
1195
+ background: var(--hs-bg);
1196
+ }
1197
+ th {
1198
+ text-align: left;
1199
+ padding: 8px 12px;
1200
+ border-bottom: 2px solid var(--hs-border);
1201
+ color: var(--hs-text-secondary);
1202
+ font-weight: 600;
1203
+ background: var(--hs-bg);
1204
+ font-size: 11px;
1205
+ text-transform: uppercase;
1206
+ white-space: nowrap;
1207
+ }
1208
+ th:last-child {
1209
+ position: sticky;
1210
+ right: 0;
1211
+ background: var(--hs-bg);
1212
+ box-shadow: -2px 0 4px rgba(0,0,0,0.1);
1213
+ }
1214
+ td {
1215
+ padding: 8px 12px;
1216
+ border-bottom: 1px solid var(--hs-border);
1217
+ vertical-align: middle;
1218
+ }
1219
+ tr:hover {
1220
+ background: var(--hs-bg-secondary);
1221
+ cursor: pointer;
1222
+ }
1223
+ tr.recently-executed {
1224
+ animation: flash-row 1s ease-out;
1225
+ }
1226
+ @keyframes flash-row {
1227
+ 0% { background: rgba(34, 197, 94, 0.2); }
1228
+ 100% { background: transparent; }
1229
+ }
1230
+ .status-flash {
1231
+ animation: flash-icon 1s ease-out;
1232
+ }
1233
+ @keyframes flash-icon {
1234
+ 0%, 50% { opacity: 1; }
1235
+ 25%, 75% { opacity: 0.3; }
1236
+ }
1237
+ .tip {
1238
+ padding: 12px;
1239
+ text-align: center;
1240
+ font-size: 11px;
1241
+ color: var(--hs-text-secondary);
1242
+ border-top: 1px solid var(--hs-border);
1243
+ background: var(--hs-bg);
1244
+ }
1245
+ .task-id {
1246
+ font-weight: 600;
1247
+ }
1248
+ .namespace-badge {
1249
+ display: inline-block;
1250
+ background: var(--hs-bg-secondary);
1251
+ color: var(--hs-text-secondary);
1252
+ border: 1px solid var(--hs-border);
1253
+ border-radius: 4px;
1254
+ padding: 0 4px;
1255
+ font-size: 9px;
1256
+ margin-left: 6px;
1257
+ font-family: monospace;
1258
+ vertical-align: middle;
1259
+ }
1260
+ .tags {
1261
+ display: flex;
1262
+ gap: 4px;
1263
+ margin-top: 4px;
1264
+ }
1265
+ .tag {
1266
+ background: var(--hs-bg-secondary);
1267
+ border: 1px solid var(--hs-border);
1268
+ border-radius: 10px;
1269
+ padding: 2px 8px;
1270
+ font-size: 10px;
1271
+ color: var(--hs-text-secondary);
1272
+ }
1273
+ .no-tags {
1274
+ font-size: 10px;
1275
+ color: var(--hs-text-secondary);
1276
+ font-style: italic;
1277
+ }
1278
+ .driver-badge {
1279
+ display: inline-flex;
1280
+ align-items: center;
1281
+ justify-content: center;
1282
+ width: 22px;
1283
+ height: 22px;
1284
+ border-radius: 4px;
1285
+ font-size: 11px;
1286
+ font-weight: 600;
1287
+ font-family: monospace;
1288
+ }
1289
+ .driver-badge.worker {
1290
+ background: rgba(34, 197, 94, 0.15);
1291
+ color: var(--hs-success);
1292
+ border: 1px solid var(--hs-success);
1293
+ }
1294
+ .driver-badge.main {
1295
+ background: rgba(245, 158, 11, 0.15);
1296
+ color: var(--hs-warning);
1297
+ border: 1px solid var(--hs-warning);
1298
+ }
1299
+ .col-num {
1300
+ width: 40px;
1301
+ color: var(--hs-text-secondary);
1302
+ font-size: 11px;
1303
+ }
1304
+ .col-actions {
1305
+ width: 100px;
1306
+ position: sticky;
1307
+ right: 0;
1308
+ background: var(--hs-bg);
1309
+ box-shadow: -2px 0 4px rgba(0,0,0,0.1);
1310
+ }
1311
+ .action-group {
1312
+ display: flex;
1313
+ gap: 4px;
1314
+ }
1315
+ .btn-icon {
1316
+ background: transparent;
1317
+ border: 1px solid var(--hs-border);
1318
+ color: var(--hs-text-secondary);
1319
+ border-radius: 4px;
1320
+ width: 24px;
1321
+ height: 24px;
1322
+ display: flex;
1323
+ align-items: center;
1324
+ justify-content: center;
1325
+ cursor: pointer;
1326
+ padding: 0;
1327
+ flex-shrink: 0;
1328
+ }
1329
+ .btn-icon svg {
1330
+ width: 14px;
1331
+ height: 14px;
1332
+ display: block;
1333
+ }
1334
+ .btn-icon:hover {
1335
+ background: var(--hs-primary);
1336
+ color: white;
1337
+ border-color: var(--hs-primary);
1338
+ }
1339
+ .btn-trigger:hover {
1340
+ background: var(--hs-success);
1341
+ border-color: var(--hs-success);
1342
+ }
1343
+ .btn-pause:hover {
1344
+ background: var(--hs-warning);
1345
+ border-color: var(--hs-warning);
1346
+ }
1347
+ .btn-resume:hover {
1348
+ background: var(--hs-success);
1349
+ border-color: var(--hs-success);
1350
+ }
1351
+ .btn-remove:hover {
1352
+ color: white;
1353
+ background: var(--hs-danger);
1354
+ border-color: var(--hs-danger);
1355
+ }
1356
+ .btn-icon:disabled {
1357
+ opacity: 0.4;
1358
+ cursor: not-allowed;
1359
+ }
1360
+ .btn-icon:disabled:hover {
1361
+ background: transparent;
1362
+ color: var(--hs-text-secondary);
1363
+ border-color: var(--hs-border);
1364
+ }
1365
+ /* Namespace Styles */
1366
+ .namespace-row {
1367
+ background: var(--hs-bg-secondary);
1368
+ cursor: pointer;
1369
+ font-weight: 600;
1370
+ user-select: none;
1371
+ }
1372
+ .namespace-row:hover {
1373
+ background: var(--hs-border);
1374
+ }
1375
+ .namespace-row td {
1376
+ padding: 8px 12px;
1377
+ border-bottom: 1px solid var(--hs-border);
1378
+ }
1379
+ .ns-toggle {
1380
+ display: inline-block;
1381
+ width: 20px;
1382
+ text-align: center;
1383
+ font-size: 10px;
1384
+ color: var(--hs-text-secondary);
1385
+ transition: transform 0.2s;
1386
+ }
1387
+ .ns-icon {
1388
+ margin-right: 4px;
1389
+ }
1390
+ .ns-count {
1391
+ font-weight: normal;
1392
+ color: var(--hs-text-secondary);
1393
+ font-size: 11px;
1394
+ margin-left: 4px;
1395
+ }
1396
+ .nested-task .col-id {
1397
+ padding-left: 32px !important;
1398
+ }
1399
+ </style>
1400
+ <div class="table-container">
1401
+ <table>
1402
+ <thead>
1403
+ <tr>
1404
+ <th style="width:40px">#</th>
1405
+ <th style="min-width:150px">${t("list.idTags")}</th>
1406
+ <th style="width:150px">${t("list.status")}</th>
1407
+ <th style="width:70px">${t("list.driver")}</th>
1408
+ <th style="width:100px">${t("list.schedule")}</th>
1409
+ <th style="width:60px">${t("list.count")}</th>
1410
+ <th style="width:100px">${t("list.lastRun")}</th>
1411
+ <th style="width:100px">${t("list.actions")}</th>
1412
+ </tr>
1413
+ </thead>
1414
+ <tbody>
1415
+ <!-- Rows -->
1416
+ </tbody>
1417
+ </table>
1418
+ </div>
1419
+ <div class="tip">
1420
+ ${t("list.tip")}
1421
+ </div>
1422
+ `;
1423
+ this._shadow.addEventListener("click", (e) => {
1424
+ const target = e.target;
1425
+ const nsRow = target.closest(".namespace-row");
1426
+ if (nsRow) {
1427
+ const ns = nsRow.dataset.ns;
1428
+ if (ns) {
1429
+ if (this._expandedNamespaces.has(ns)) {
1430
+ this._expandedNamespaces.delete(ns);
1431
+ } else {
1432
+ this._expandedNamespaces.add(ns);
1433
+ }
1434
+ this.renderRows();
1435
+ }
1436
+ return;
1437
+ }
1438
+ const btn = target.closest("button");
1439
+ if (btn) {
1440
+ const action = btn.dataset.action;
1441
+ const tr2 = btn.closest("tr");
1442
+ if (!action || !tr2) return;
1443
+ const id = tr2.dataset.id;
1444
+ this.dispatchEvent(new CustomEvent("task-action", {
1445
+ detail: { action, id },
1446
+ bubbles: true,
1447
+ composed: true
1448
+ }));
1449
+ return;
1450
+ }
1451
+ const tr = target.closest("tr");
1452
+ if (tr && !target.closest(".col-actions") && !tr.classList.contains("namespace-row")) {
1453
+ const id = tr.dataset.id;
1454
+ this.dispatchEvent(new CustomEvent("task-select", {
1455
+ detail: id,
1456
+ bubbles: true,
1457
+ composed: true
1458
+ }));
1459
+ }
1460
+ });
1461
+ }
1462
+ }
1463
+ customElements.define("hs-task-list", TaskList);
1464
+ class TaskDetail extends HTMLElement {
1465
+ constructor() {
1466
+ super();
1467
+ __publicField(this, "_shadow");
1468
+ __publicField(this, "_task", null);
1469
+ __publicField(this, "_history", []);
1470
+ this._shadow = this.attachShadow({ mode: "open" });
1471
+ }
1472
+ connectedCallback() {
1473
+ this.render();
1474
+ this._shadow.addEventListener("click", (e) => {
1475
+ if (e.target.closest(".back-btn")) {
1476
+ this.dispatchEvent(new CustomEvent("back"));
1477
+ }
1478
+ });
1479
+ }
1480
+ set task(task) {
1481
+ this._task = task;
1482
+ this.renderContent();
1483
+ }
1484
+ set history(h) {
1485
+ this._history = h || [];
1486
+ this.renderContent();
1487
+ }
1488
+ // Method to update texts when language changes
1489
+ updateTexts() {
1490
+ this.renderContent();
1491
+ }
1492
+ renderContent() {
1493
+ const container = this._shadow.querySelector(".content");
1494
+ if (!container) return;
1495
+ if (!this._task) {
1496
+ container.innerHTML = `<div class="empty">${t("detail.noTask")}</div>`;
1497
+ return;
1498
+ }
1499
+ const task = this._task;
1500
+ const config = {
1501
+ id: task.id,
1502
+ schedule: task.schedule,
1503
+ tags: task.tags
1504
+ };
1505
+ const avgDuration = this._history.length > 0 ? Math.round(this._history.reduce((sum, r) => sum + r.duration, 0) / this._history.length) : 0;
1506
+ container.innerHTML = `
1507
+ <div class="header">
1508
+ <button class="back-btn" title="${t("detail.back")}">${ICONS.back}</button>
1509
+ <h2>📂 ${task.id}</h2>
1510
+ </div>
1511
+
1512
+ <div class="section">
1513
+ <div class="config-label">${t("detail.config")}:</div>
1514
+ <pre>${JSON.stringify(config, null, 2)}</pre>
1515
+ </div>
1516
+
1517
+ <div class="section">
1518
+ <h3>📜 ${t("detail.history")} (${t("detail.lastRuns", { n: this._history.length })}) ${avgDuration > 0 ? `- ${t("detail.avgDuration")}: ${avgDuration}ms` : ""}</h3>
1519
+ <table class="history-table">
1520
+ <thead>
1521
+ <tr>
1522
+ <th>#</th>
1523
+ <th>${t("detail.startTime")}</th>
1524
+ <th>${t("detail.duration")}</th>
1525
+ <th>${t("detail.drift")}</th>
1526
+ <th>${t("detail.status")}</th>
1527
+ </tr>
1528
+ </thead>
1529
+ <tbody>
1530
+ ${this._history.length === 0 ? `<tr><td colspan="5" class="no-data">${t("detail.noHistory")}</td></tr>` : ""}
1531
+ ${this._history.slice().reverse().map((run, idx) => {
1532
+ const driftStr = "0ms";
1533
+ const statusIcon = run.success ? `✅ ${t("detail.success")}` : run.error ? `❌ ${t("detail.error")}: ${run.error}` : `⚠️ ${t("detail.failed")}`;
1534
+ const durationClass = run.duration > 100 ? "slow" : "";
1535
+ return `
1536
+ <tr class="${run.success ? "success" : "error"}">
1537
+ <td class="col-num">${this._history.length - idx}</td>
1538
+ <td>${new Date(run.timestamp).toLocaleTimeString("en-US", { hour12: false })}</td>
1539
+ <td class="${durationClass}">${run.duration}ms</td>
1540
+ <td>${driftStr}</td>
1541
+ <td class="status-cell">${statusIcon}</td>
1542
+ </tr>
1543
+ `;
1544
+ }).join("")}
1545
+ </tbody>
1546
+ </table>
1547
+ </div>
1548
+ `;
1549
+ }
1550
+ render() {
1551
+ this._shadow.innerHTML = `
1552
+ <style>
1553
+ ${themeStyles}
1554
+ :host {
1555
+ display: block;
1556
+ height: 100%;
1557
+ background: var(--hs-bg);
1558
+ overflow-y: auto;
1559
+ }
1560
+ .content {
1561
+ padding: 16px;
1562
+ }
1563
+ .header {
1564
+ display: flex;
1565
+ align-items: center;
1566
+ gap: 16px;
1567
+ margin-bottom: 24px;
1568
+ padding-bottom: 16px;
1569
+ border-bottom: 2px solid var(--hs-border);
1570
+ }
1571
+ .back-btn {
1572
+ background: var(--hs-bg-secondary);
1573
+ border: 1px solid var(--hs-border);
1574
+ color: var(--hs-text);
1575
+ padding: 6px 12px;
1576
+ border-radius: 4px;
1577
+ cursor: pointer;
1578
+ display: flex;
1579
+ align-items: center;
1580
+ gap: 4px;
1581
+ font-size: 12px;
1582
+ }
1583
+ .back-btn:hover {
1584
+ background: var(--hs-primary);
1585
+ color: white;
1586
+ border-color: var(--hs-primary);
1587
+ }
1588
+ h2 {
1589
+ margin: 0;
1590
+ font-size: 16px;
1591
+ font-weight: 600;
1592
+ }
1593
+ h3 {
1594
+ font-size: 13px;
1595
+ color: var(--hs-text);
1596
+ margin-bottom: 12px;
1597
+ font-weight: 600;
1598
+ }
1599
+ .section {
1600
+ margin-bottom: 32px;
1601
+ }
1602
+ .config-label {
1603
+ font-size: 12px;
1604
+ color: var(--hs-text-secondary);
1605
+ margin-bottom: 8px;
1606
+ }
1607
+ pre {
1608
+ background: var(--hs-bg-secondary);
1609
+ padding: 12px;
1610
+ border-radius: 6px;
1611
+ border: 1px solid var(--hs-border);
1612
+ font-family: 'Monaco', 'Menlo', monospace;
1613
+ font-size: 11px;
1614
+ overflow-x: auto;
1615
+ line-height: 1.5;
1616
+ }
1617
+ .history-table {
1618
+ width: 100%;
1619
+ border-collapse: collapse;
1620
+ font-size: 11px;
1621
+ }
1622
+ .history-table th {
1623
+ text-align: left;
1624
+ padding: 8px;
1625
+ border-bottom: 2px solid var(--hs-border);
1626
+ color: var(--hs-text-secondary);
1627
+ font-weight: 600;
1628
+ background: var(--hs-bg-secondary);
1629
+ }
1630
+ .history-table td {
1631
+ padding: 8px;
1632
+ border-bottom: 1px solid var(--hs-border);
1633
+ }
1634
+ .history-table tr.success {
1635
+ background: rgba(34, 197, 94, 0.05);
1636
+ }
1637
+ .history-table tr.error {
1638
+ background: rgba(239, 68, 68, 0.05);
1639
+ }
1640
+ .history-table tr:hover {
1641
+ background: var(--hs-bg-secondary);
1642
+ }
1643
+ .col-num {
1644
+ width: 40px;
1645
+ color: var(--hs-text-secondary);
1646
+ }
1647
+ .slow {
1648
+ color: var(--hs-warning);
1649
+ font-weight: 600;
1650
+ }
1651
+ .status-cell {
1652
+ font-size: 11px;
1653
+ }
1654
+ .no-data {
1655
+ color: var(--hs-text-secondary);
1656
+ font-style: italic;
1657
+ text-align: center;
1658
+ padding: 24px;
1659
+ }
1660
+ </style>
1661
+ <div class="content"></div>
1662
+ `;
1663
+ }
1664
+ }
1665
+ customElements.define("hs-task-detail", TaskDetail);
1666
+ class Timeline extends HTMLElement {
1667
+ constructor() {
1668
+ super();
1669
+ __publicField(this, "_shadow");
1670
+ __publicField(this, "_tasks", /* @__PURE__ */ new Map());
1671
+ __publicField(this, "_history", /* @__PURE__ */ new Map());
1672
+ __publicField(this, "$canvas");
1673
+ __publicField(this, "ctx");
1674
+ __publicField(this, "timeRange", 60 * 1e3);
1675
+ // 1 minute window
1676
+ __publicField(this, "zoom", 1);
1677
+ this._shadow = this.attachShadow({ mode: "open" });
1678
+ }
1679
+ connectedCallback() {
1680
+ this.render();
1681
+ this.$canvas = this._shadow.querySelector("canvas");
1682
+ if (!this.$canvas) {
1683
+ console.error("[Timeline] Canvas not found");
1684
+ return;
1685
+ }
1686
+ this.ctx = this.$canvas.getContext("2d");
1687
+ if (!this.ctx) {
1688
+ console.error("[Timeline] Canvas context not available");
1689
+ return;
1690
+ }
1691
+ this.setupZoom();
1692
+ this.startLoop();
1693
+ const container = this._shadow.querySelector(".canvas-container");
1694
+ if (container) {
1695
+ const resizeObserver = new ResizeObserver(() => {
1696
+ requestAnimationFrame(() => this.draw());
1697
+ });
1698
+ resizeObserver.observe(container);
1699
+ }
1700
+ }
1701
+ set data(val) {
1702
+ this._tasks = val.tasks;
1703
+ this._history = val.history;
1704
+ }
1705
+ set defaultZoom(val) {
1706
+ if (val >= 0.5 && val <= 5) {
1707
+ this.zoom = val;
1708
+ this.timeRange = 60 * 1e3 / this.zoom;
1709
+ const zoomSlider = this._shadow.querySelector(".zoom-slider");
1710
+ if (zoomSlider) zoomSlider.value = val.toString();
1711
+ this.updateZoomLabel();
1712
+ }
1713
+ }
1714
+ // Method to update texts when language changes
1715
+ updateTexts() {
1716
+ this.updateZoomLabel();
1717
+ }
1718
+ updateZoomLabel() {
1719
+ const zoomLabel = this._shadow.querySelector(".zoom-label");
1720
+ if (zoomLabel) {
1721
+ zoomLabel.textContent = `${t("timeline.zoom")}: ${this.zoom}x (${Math.round(this.timeRange / 1e3)}s)`;
1722
+ }
1723
+ }
1724
+ setupZoom() {
1725
+ const zoomSlider = this._shadow.querySelector(".zoom-slider");
1726
+ const zoomOut = this._shadow.querySelector(".zoom-out");
1727
+ const zoomIn = this._shadow.querySelector(".zoom-in");
1728
+ const updateZoom = (newZoom) => {
1729
+ this.zoom = newZoom;
1730
+ this.timeRange = 60 * 1e3 / this.zoom;
1731
+ this.updateZoomLabel();
1732
+ };
1733
+ zoomSlider == null ? void 0 : zoomSlider.addEventListener("input", (e) => {
1734
+ const newZoom = parseFloat(e.target.value);
1735
+ updateZoom(newZoom);
1736
+ });
1737
+ zoomOut == null ? void 0 : zoomOut.addEventListener("click", () => {
1738
+ const newZoom = Math.max(0.5, this.zoom - 0.5);
1739
+ if (zoomSlider) zoomSlider.value = newZoom.toString();
1740
+ updateZoom(newZoom);
1741
+ });
1742
+ zoomIn == null ? void 0 : zoomIn.addEventListener("click", () => {
1743
+ const newZoom = Math.min(5, this.zoom + 0.5);
1744
+ if (zoomSlider) zoomSlider.value = newZoom.toString();
1745
+ updateZoom(newZoom);
1746
+ });
1747
+ this.updateZoomLabel();
1748
+ }
1749
+ startLoop() {
1750
+ const loop = () => {
1751
+ if (this.isConnected) {
1752
+ this.draw();
1753
+ requestAnimationFrame(loop);
1754
+ }
1755
+ };
1756
+ requestAnimationFrame(loop);
1757
+ }
1758
+ draw() {
1759
+ if (!this.ctx || !this.$canvas) return;
1760
+ const container = this._shadow.querySelector(".canvas-container");
1761
+ if (!container) return;
1762
+ const dpr = window.devicePixelRatio || 1;
1763
+ const width = container.clientWidth;
1764
+ if (width === 0) return;
1765
+ const rowHeight = 40;
1766
+ const taskIds = Array.from(this._tasks.keys());
1767
+ const headerHeight = 60;
1768
+ const footerHeight = 40;
1769
+ const minHeight = container.clientHeight || 300;
1770
+ const contentHeight = taskIds.length * rowHeight + headerHeight + footerHeight;
1771
+ const height = Math.max(minHeight, contentHeight);
1772
+ this.$canvas.width = width * dpr;
1773
+ this.$canvas.height = height * dpr;
1774
+ this.$canvas.style.width = `${width}px`;
1775
+ this.$canvas.style.height = `${height}px`;
1776
+ this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1777
+ const now = Date.now();
1778
+ const startTime = now - this.timeRange;
1779
+ const labelWidth = 150;
1780
+ const hostStyles = getComputedStyle(this);
1781
+ const bgColor = hostStyles.getPropertyValue("--hs-bg").trim() || "#1e1e1e";
1782
+ const textColor = hostStyles.getPropertyValue("--hs-text").trim() || "#fff";
1783
+ const textSecondary = hostStyles.getPropertyValue("--hs-text-secondary").trim() || "#888";
1784
+ const borderColor = hostStyles.getPropertyValue("--hs-border").trim() || "#333";
1785
+ const successColor = hostStyles.getPropertyValue("--hs-success").trim() || "#22c55e";
1786
+ const dangerColor = hostStyles.getPropertyValue("--hs-danger").trim() || "#ef4444";
1787
+ this.ctx.fillStyle = bgColor;
1788
+ this.ctx.fillRect(0, 0, width, height);
1789
+ this.drawTimeAxis(width, labelWidth, startTime, textSecondary, borderColor);
1790
+ taskIds.forEach((taskId, index) => {
1791
+ const y = index * rowHeight + 60;
1792
+ this.drawTaskRow(taskId, y, width, labelWidth, startTime, now, textColor, textSecondary, borderColor, successColor, dangerColor);
1793
+ });
1794
+ this.drawLegend(width, height, textSecondary, successColor);
1795
+ }
1796
+ drawTimeAxis(width, labelWidth, startTime, textSecondary, borderColor) {
1797
+ const ctx = this.ctx;
1798
+ const timelineWidth = width - labelWidth - 20;
1799
+ const segments = 4;
1800
+ ctx.fillStyle = textSecondary;
1801
+ ctx.font = "10px monospace";
1802
+ ctx.textAlign = "center";
1803
+ for (let i = 0; i <= segments; i++) {
1804
+ const x = labelWidth + timelineWidth / segments * i;
1805
+ const time = startTime + this.timeRange / segments * i;
1806
+ const timeStr = new Date(time).toLocaleTimeString("en-US", {
1807
+ hour: "2-digit",
1808
+ minute: "2-digit",
1809
+ second: "2-digit",
1810
+ hour12: false
1811
+ });
1812
+ ctx.fillText(timeStr, x, 30);
1813
+ ctx.strokeStyle = borderColor;
1814
+ ctx.beginPath();
1815
+ ctx.moveTo(x, 40);
1816
+ ctx.lineTo(x, this.$canvas.clientHeight - 40);
1817
+ ctx.stroke();
1818
+ }
1819
+ ctx.textAlign = "left";
1820
+ ctx.fillText(t("timeline.timeRange", { n: Math.round(this.timeRange / 1e3) }), 10, 15);
1821
+ }
1822
+ drawTaskRow(taskId, y, width, labelWidth, startTime, endTime, textColor, _textSecondary, borderColor, successColor, dangerColor) {
1823
+ const ctx = this.ctx;
1824
+ const timelineWidth = width - labelWidth - 20;
1825
+ ctx.fillStyle = textColor;
1826
+ ctx.font = "11px sans-serif";
1827
+ ctx.textAlign = "left";
1828
+ ctx.fillText(taskId, 10, y + 15);
1829
+ const task = this._tasks.get(taskId);
1830
+ const driver = (task == null ? void 0 : task.driver) === "main" ? "M" : "W";
1831
+ const driverColor = driver === "W" ? "#22c55e" : "#f59e0b";
1832
+ ctx.fillStyle = driverColor;
1833
+ ctx.font = "9px monospace";
1834
+ ctx.fillText(`[${driver}]`, 10, y + 28);
1835
+ ctx.strokeStyle = borderColor;
1836
+ ctx.beginPath();
1837
+ ctx.moveTo(0, y + 35);
1838
+ ctx.lineTo(width, y + 35);
1839
+ ctx.stroke();
1840
+ const logs = this._history.get(taskId) || [];
1841
+ logs.forEach((log) => {
1842
+ if (log.timestamp < startTime || log.timestamp > endTime) return;
1843
+ const x = labelWidth + (log.timestamp - startTime) / this.timeRange * timelineWidth;
1844
+ const duration = log.duration;
1845
+ if (duration < 10) {
1846
+ ctx.fillStyle = log.success ? successColor : dangerColor;
1847
+ ctx.beginPath();
1848
+ ctx.arc(x, y + 15, 3, 0, Math.PI * 2);
1849
+ ctx.fill();
1850
+ } else {
1851
+ const barWidth = Math.max(2, duration / this.timeRange * timelineWidth);
1852
+ ctx.fillStyle = log.success ? successColor : dangerColor;
1853
+ ctx.globalAlpha = 0.7;
1854
+ ctx.fillRect(x, y + 5, barWidth, 20);
1855
+ ctx.globalAlpha = 1;
1856
+ }
1857
+ });
1858
+ }
1859
+ drawLegend(_width, height, textSecondary, successColor) {
1860
+ const ctx = this.ctx;
1861
+ const legendY = height - 25;
1862
+ ctx.font = "10px sans-serif";
1863
+ ctx.textAlign = "left";
1864
+ ctx.fillStyle = textSecondary;
1865
+ let x = 10;
1866
+ ctx.fillText(`${t("timeline.legend")}:`, x, legendY);
1867
+ x += 50;
1868
+ ctx.fillStyle = successColor;
1869
+ ctx.beginPath();
1870
+ ctx.arc(x, legendY - 4, 3, 0, Math.PI * 2);
1871
+ ctx.fill();
1872
+ ctx.fillStyle = textSecondary;
1873
+ ctx.fillText(t("timeline.instant"), x + 10, legendY);
1874
+ x += 60;
1875
+ ctx.fillStyle = successColor;
1876
+ ctx.globalAlpha = 0.7;
1877
+ ctx.fillRect(x, legendY - 8, 15, 10);
1878
+ ctx.globalAlpha = 1;
1879
+ ctx.fillStyle = textSecondary;
1880
+ ctx.fillText(t("timeline.duration"), x + 20, legendY);
1881
+ x += 80;
1882
+ ctx.fillText(`[W] ${t("timeline.workerDriver")}`, x, legendY);
1883
+ x += 110;
1884
+ ctx.fillText(`[M] ${t("timeline.mainDriver")}`, x, legendY);
1885
+ }
1886
+ render() {
1887
+ this._shadow.innerHTML = `
1888
+ <style>
1889
+ ${themeStyles}
1890
+ :host {
1891
+ display: flex;
1892
+ flex-direction: column;
1893
+ height: 100%;
1894
+ background: var(--hs-bg);
1895
+ overflow: hidden;
1896
+ }
1897
+ .controls {
1898
+ display: flex;
1899
+ justify-content: flex-end;
1900
+ align-items: center;
1901
+ padding: 12px 16px;
1902
+ gap: 8px;
1903
+ border-bottom: 1px solid var(--hs-border);
1904
+ flex-shrink: 0;
1905
+ }
1906
+ .zoom-label {
1907
+ font-size: 11px;
1908
+ color: var(--hs-text-secondary);
1909
+ }
1910
+ .zoom-btn {
1911
+ background: var(--hs-bg-secondary);
1912
+ border: 1px solid var(--hs-border);
1913
+ color: var(--hs-text);
1914
+ width: 24px;
1915
+ height: 24px;
1916
+ border-radius: 4px;
1917
+ cursor: pointer;
1918
+ display: flex;
1919
+ align-items: center;
1920
+ justify-content: center;
1921
+ }
1922
+ .zoom-btn:hover {
1923
+ background: var(--hs-primary);
1924
+ color: white;
1925
+ }
1926
+ .zoom-slider {
1927
+ width: 100px;
1928
+ }
1929
+ .canvas-container {
1930
+ flex: 1;
1931
+ overflow: auto;
1932
+ position: relative;
1933
+ }
1934
+ canvas {
1935
+ display: block;
1936
+ }
1937
+ </style>
1938
+ <div class="controls">
1939
+ <span class="zoom-label">Zoom:</span>
1940
+ <button class="zoom-btn zoom-out">-</button>
1941
+ <input type="range" class="zoom-slider" min="0.5" max="5" step="0.5" value="1">
1942
+ <button class="zoom-btn zoom-in">+</button>
1943
+ </div>
1944
+ <div class="canvas-container">
1945
+ <canvas></canvas>
1946
+ </div>
1947
+ `;
1948
+ }
1949
+ }
1950
+ customElements.define("hs-timeline", Timeline);
1951
+ class Resizer extends HTMLElement {
1952
+ constructor() {
1953
+ super();
1954
+ __publicField(this, "_shadow");
1955
+ __publicField(this, "startX", 0);
1956
+ __publicField(this, "startY", 0);
1957
+ __publicField(this, "startWidth", 0);
1958
+ __publicField(this, "startHeight", 0);
1959
+ __publicField(this, "panel", null);
1960
+ __publicField(this, "mode", "right");
1961
+ this._shadow = this.attachShadow({ mode: "open" });
1962
+ }
1963
+ // 检测是否为移动端
1964
+ isMobile() {
1965
+ return window.innerWidth <= 480;
1966
+ }
1967
+ connectedCallback() {
1968
+ this.render();
1969
+ this.addEventListeners();
1970
+ this.panel = this.closest(".panel");
1971
+ if (this.panel && this.panel.classList.contains("dock-bottom")) {
1972
+ this.mode = "bottom";
1973
+ }
1974
+ }
1975
+ addEventListeners() {
1976
+ const handle = this._shadow.querySelector(".handle");
1977
+ const onMouseMove = (e) => {
1978
+ if (!this.panel || this.isMobile()) return;
1979
+ if (this.mode === "right") {
1980
+ const dx = this.startX - e.clientX;
1981
+ const newWidth = Math.max(300, Math.min(window.innerWidth - 50, this.startWidth + dx));
1982
+ this.panel.style.width = `${newWidth}px`;
1983
+ this.dispatchEvent(new CustomEvent("resize", { detail: { width: newWidth }, bubbles: true, composed: true }));
1984
+ } else {
1985
+ const newHeight = Math.max(200, Math.min(window.innerHeight - 50, this.startHeight + (this.startY - e.clientY)));
1986
+ this.panel.style.height = `${newHeight}px`;
1987
+ this.dispatchEvent(new CustomEvent("resize", { detail: { height: newHeight }, bubbles: true, composed: true }));
1988
+ }
1989
+ };
1990
+ const onMouseUp = () => {
1991
+ document.removeEventListener("mousemove", onMouseMove);
1992
+ document.removeEventListener("mouseup", onMouseUp);
1993
+ document.body.style.userSelect = "";
1994
+ document.body.style.cursor = "";
1995
+ };
1996
+ handle.addEventListener("mousedown", (e) => {
1997
+ if (this.isMobile()) return;
1998
+ this.startX = e.clientX;
1999
+ this.startY = e.clientY;
2000
+ if (this.panel) {
2001
+ if (this.panel.classList.contains("dock-bottom")) {
2002
+ this.mode = "bottom";
2003
+ this.startHeight = this.panel.offsetHeight;
2004
+ } else {
2005
+ this.mode = "right";
2006
+ this.startWidth = this.panel.offsetWidth;
2007
+ }
2008
+ }
2009
+ document.addEventListener("mousemove", onMouseMove);
2010
+ document.addEventListener("mouseup", onMouseUp);
2011
+ document.body.style.userSelect = "none";
2012
+ document.body.style.cursor = this.mode === "right" ? "col-resize" : "row-resize";
2013
+ });
2014
+ }
2015
+ render() {
2016
+ this._shadow.innerHTML = `
2017
+ <style>
2018
+ ${themeStyles}
2019
+ :host {
2020
+ display: block;
2021
+ z-index: 100;
2022
+ }
2023
+ .handle {
2024
+ background: transparent;
2025
+ transition: background 0.2s;
2026
+ }
2027
+ .handle:hover {
2028
+ background: var(--hs-primary);
2029
+ }
2030
+
2031
+ /* Right Dock Mode (Vertical Handle on Left) */
2032
+ :host-context(.dock-right) .handle {
2033
+ width: 4px;
2034
+ height: 100%;
2035
+ cursor: col-resize;
2036
+ position: absolute;
2037
+ left: 0;
2038
+ top: 0;
2039
+ }
2040
+
2041
+ /* Bottom Dock Mode (Horizontal Handle on Top) */
2042
+ :host-context(.dock-bottom) .handle {
2043
+ width: 100%;
2044
+ height: 4px;
2045
+ cursor: row-resize;
2046
+ position: absolute;
2047
+ top: 0;
2048
+ left: 0;
2049
+ }
2050
+
2051
+ /* 移动端隐藏拖拽手柄 */
2052
+ @media (max-width: 480px) {
2053
+ .handle {
2054
+ display: none;
2055
+ }
2056
+ }
2057
+ </style>
2058
+ <div class="handle"></div>
2059
+ `;
2060
+ }
2061
+ }
2062
+ customElements.define("hs-resizer", Resizer);
2063
+ class DevTools extends HTMLElement {
2064
+ constructor() {
2065
+ super();
2066
+ __publicField(this, "_shadow");
2067
+ __publicField(this, "store");
2068
+ __publicField(this, "scheduler");
2069
+ __publicField(this, "rAFId");
2070
+ __publicField(this, "lastTime", 0);
2071
+ __publicField(this, "$panel");
2072
+ __publicField(this, "$header");
2073
+ __publicField(this, "$taskList");
2074
+ __publicField(this, "$taskDetail");
2075
+ __publicField(this, "$timeline");
2076
+ __publicField(this, "$trigger");
2077
+ __publicField(this, "$overlay");
2078
+ this._shadow = this.attachShadow({ mode: "open" });
2079
+ this.store = new DevToolsStore();
2080
+ }
2081
+ connectedCallback() {
2082
+ const languageAttr = this.getAttribute("language");
2083
+ if (languageAttr === "en" || languageAttr === "zh") {
2084
+ this.store.setLanguageSync(languageAttr);
2085
+ }
2086
+ this.render();
2087
+ this.cacheDom();
2088
+ this.bindStore();
2089
+ const dockAttr = this.getAttribute("dock");
2090
+ console.log("[DevTools] dock attribute:", dockAttr);
2091
+ if (dockAttr === "bottom") {
2092
+ this.store.setDockPosition("bottom");
2093
+ }
2094
+ const themeAttr = this.getAttribute("theme");
2095
+ if (themeAttr === "light" || themeAttr === "dark" || themeAttr === "auto") {
2096
+ this.store.setTheme(themeAttr);
2097
+ }
2098
+ if (languageAttr === "en" || languageAttr === "zh") {
2099
+ this.store.setLanguage(languageAttr);
2100
+ }
2101
+ const triggerBg = this.getAttribute("trigger-bg");
2102
+ const triggerColor = this.getAttribute("trigger-color");
2103
+ const triggerPosition = this.getAttribute("trigger-position");
2104
+ console.log("[DevTools] trigger attrs:", { triggerBg, triggerColor, triggerPosition });
2105
+ if (triggerBg) this.$trigger.setAttribute("bg-color", triggerBg);
2106
+ if (triggerColor) this.$trigger.setAttribute("text-color", triggerColor);
2107
+ if (triggerPosition) this.$trigger.setAttribute("position", triggerPosition);
2108
+ const defaultZoom = this.getAttribute("default-zoom");
2109
+ if (defaultZoom) {
2110
+ const zoom = parseFloat(defaultZoom);
2111
+ if (!isNaN(zoom) && zoom >= 0.5 && zoom <= 5) {
2112
+ this.$timeline.defaultZoom = zoom;
2113
+ }
2114
+ }
2115
+ this.addEventListeners();
2116
+ this.startLoop();
2117
+ }
2118
+ disconnectedCallback() {
2119
+ if (this.rAFId) cancelAnimationFrame(this.rAFId);
2120
+ }
2121
+ setScheduler(api) {
2122
+ this.scheduler = api;
2123
+ this.store.setScheduler(api);
2124
+ const tasks = this.scheduler.getTasks();
2125
+ tasks.forEach((t2) => this.store.updateTask(t2));
2126
+ const isRunning = this.scheduler.isRunning();
2127
+ this.store.setSchedulerRunning(isRunning);
2128
+ const refreshTasks = () => {
2129
+ var _a;
2130
+ const allTasks = ((_a = this.scheduler) == null ? void 0 : _a.getTasks()) || [];
2131
+ allTasks.forEach((t2) => this.store.updateTask(t2));
2132
+ };
2133
+ this.scheduler.on(SchedulerEvents.TASK_REGISTERED, refreshTasks);
2134
+ this.scheduler.on(SchedulerEvents.TASK_UPDATED, (payload) => {
2135
+ console.log("[DevTools] task_updated event:", payload);
2136
+ refreshTasks();
2137
+ });
2138
+ this.scheduler.on(SchedulerEvents.TASK_STARTED, refreshTasks);
2139
+ this.scheduler.on(SchedulerEvents.TASK_REMOVED, refreshTasks);
2140
+ this.scheduler.on(SchedulerEvents.TASK_STOPPED, (payload) => {
2141
+ console.log("[DevTools] task_stopped event:", payload);
2142
+ refreshTasks();
2143
+ });
2144
+ this.scheduler.on(SchedulerEvents.SCHEDULER_STARTED, () => {
2145
+ console.log("[DevTools] scheduler_started event");
2146
+ this.store.setSchedulerRunning(true);
2147
+ refreshTasks();
2148
+ });
2149
+ this.scheduler.on(SchedulerEvents.SCHEDULER_STOPPED, () => {
2150
+ console.log("[DevTools] scheduler_stopped event");
2151
+ this.store.setSchedulerRunning(false);
2152
+ refreshTasks();
2153
+ });
2154
+ this.scheduler.on(SchedulerEvents.TASK_COMPLETED, (payload) => {
2155
+ var _a;
2156
+ refreshTasks();
2157
+ if (payload && payload.taskId) {
2158
+ this.store.addHistory(payload.taskId, {
2159
+ timestamp: ((_a = payload.task) == null ? void 0 : _a.lastRun) || Date.now(),
2160
+ duration: payload.duration || 0,
2161
+ success: true,
2162
+ error: null
2163
+ });
2164
+ }
2165
+ });
2166
+ this.scheduler.on(SchedulerEvents.TASK_FAILED, (payload) => {
2167
+ var _a;
2168
+ refreshTasks();
2169
+ if (payload && payload.taskId) {
2170
+ this.store.addHistory(payload.taskId, {
2171
+ timestamp: ((_a = payload.task) == null ? void 0 : _a.lastRun) || Date.now(),
2172
+ duration: payload.duration || 0,
2173
+ success: false,
2174
+ error: payload.error || "Unknown error"
2175
+ });
2176
+ }
2177
+ });
2178
+ setInterval(() => {
2179
+ if (this.store.getState().isOpen) {
2180
+ refreshTasks();
2181
+ }
2182
+ }, 500);
2183
+ }
2184
+ cacheDom() {
2185
+ this.$panel = this._shadow.querySelector(".panel");
2186
+ this.$header = this._shadow.querySelector("hs-task-header");
2187
+ this.$taskList = this._shadow.querySelector("hs-task-list");
2188
+ this.$taskDetail = this._shadow.querySelector("hs-task-detail");
2189
+ this.$timeline = this._shadow.querySelector("hs-timeline");
2190
+ this.$trigger = this._shadow.querySelector("hs-floating-trigger");
2191
+ this.$overlay = this._shadow.querySelector(".overlay");
2192
+ }
2193
+ bindStore() {
2194
+ try {
2195
+ const saved = localStorage.getItem("hs-panel-size");
2196
+ if (saved) {
2197
+ this.store.setPanelSize(JSON.parse(saved));
2198
+ }
2199
+ } catch (e) {
2200
+ }
2201
+ this.store.subscribe("isOpen", (isOpen) => {
2202
+ const pos = this.store.getState().dockPosition;
2203
+ const size = this.store.getState().panelSize;
2204
+ if (isOpen) {
2205
+ this.$panel.classList.add("open");
2206
+ this.$trigger.style.display = "none";
2207
+ this.$overlay.style.display = "block";
2208
+ if (pos === "right") {
2209
+ this.$panel.style.right = "0";
2210
+ } else {
2211
+ this.$panel.style.bottom = "0";
2212
+ }
2213
+ } else {
2214
+ this.$panel.classList.remove("open");
2215
+ this.$trigger.style.display = "block";
2216
+ this.$overlay.style.display = "none";
2217
+ if (pos === "right") {
2218
+ this.$panel.style.right = `-${size.width}px`;
2219
+ } else {
2220
+ this.$panel.style.bottom = `-${size.height}px`;
2221
+ }
2222
+ }
2223
+ });
2224
+ this.store.subscribe("theme", (theme) => {
2225
+ const actualTheme = theme === "auto" ? "light" : theme;
2226
+ this.setAttribute("theme", actualTheme);
2227
+ this.$header.setAttribute("theme", actualTheme);
2228
+ this.$taskList.setAttribute("theme", actualTheme);
2229
+ this.$taskDetail.setAttribute("theme", actualTheme);
2230
+ this.$timeline.setAttribute("theme", actualTheme);
2231
+ this.$header.theme = theme;
2232
+ });
2233
+ this.store.subscribe("tasks", (tasks) => {
2234
+ this.$taskList.tasks = tasks;
2235
+ this.$timeline.data = { tasks, history: this.store.getState().history };
2236
+ let active = 0;
2237
+ tasks.forEach((t2) => {
2238
+ if (t2.status === "idle" || t2.status === "running") active++;
2239
+ });
2240
+ this.$header.stats = { active, total: tasks.size };
2241
+ const selectedId = this.store.getState().selectedTaskId;
2242
+ if (selectedId && tasks.has(selectedId)) {
2243
+ this.$taskDetail.task = tasks.get(selectedId) || null;
2244
+ }
2245
+ });
2246
+ this.store.subscribe("history", (map) => {
2247
+ this.$timeline.data = { tasks: this.store.getState().tasks, history: map };
2248
+ const id = this.store.getState().selectedTaskId;
2249
+ if (id) {
2250
+ this.$taskDetail.history = map.get(id) || [];
2251
+ }
2252
+ });
2253
+ this.store.subscribe("selectedTaskId", (id) => {
2254
+ if (this.store.getState().activeTab !== "tasks") return;
2255
+ if (id) {
2256
+ this.$taskList.style.display = "none";
2257
+ this.$taskDetail.style.display = "block";
2258
+ const task = this.store.getState().tasks.get(id);
2259
+ const history = this.store.getState().history.get(id);
2260
+ this.$taskDetail.task = task || null;
2261
+ this.$taskDetail.history = history || [];
2262
+ } else {
2263
+ this.$taskList.style.display = "block";
2264
+ this.$taskDetail.style.display = "none";
2265
+ }
2266
+ });
2267
+ this.store.subscribe("activeTab", (tab) => {
2268
+ this.$header.activeTab = tab;
2269
+ if (tab === "tasks") {
2270
+ this.$taskList.style.display = "block";
2271
+ this.$timeline.style.display = "none";
2272
+ if (this.store.getState().selectedTaskId) {
2273
+ this.$taskList.style.display = "none";
2274
+ this.$taskDetail.style.display = "block";
2275
+ } else {
2276
+ this.$taskList.style.display = "block";
2277
+ this.$taskDetail.style.display = "none";
2278
+ }
2279
+ } else {
2280
+ this.$taskList.style.display = "none";
2281
+ this.$taskDetail.style.display = "none";
2282
+ this.$timeline.style.display = "block";
2283
+ }
2284
+ });
2285
+ this.store.subscribe("dockPosition", (pos) => {
2286
+ this.$header.dockPosition = pos;
2287
+ const size = this.store.getState().panelSize;
2288
+ const isOpen = this.store.getState().isOpen;
2289
+ const isMobile = window.innerWidth <= 480;
2290
+ if (pos === "right") {
2291
+ this.$panel.classList.add("dock-right");
2292
+ this.$panel.classList.remove("dock-bottom");
2293
+ const width = isMobile ? window.innerWidth : size.width;
2294
+ this.$panel.style.width = isMobile ? "100vw" : `${width}px`;
2295
+ this.$panel.style.height = "100vh";
2296
+ this.$panel.style.bottom = "";
2297
+ this.$panel.style.right = isOpen ? "0" : `-${width}px`;
2298
+ } else {
2299
+ this.$panel.classList.add("dock-bottom");
2300
+ this.$panel.classList.remove("dock-right");
2301
+ this.$panel.style.width = "100%";
2302
+ const height = isMobile ? "50vh" : `${size.height}px`;
2303
+ this.$panel.style.height = height;
2304
+ this.$panel.style.right = "";
2305
+ this.$panel.style.bottom = isOpen ? "0" : isMobile ? "-50vh" : `-${size.height}px`;
2306
+ }
2307
+ });
2308
+ this.store.subscribe("panelSize", (size) => {
2309
+ const pos = this.store.getState().dockPosition;
2310
+ if (pos === "right" && size.width) {
2311
+ this.$panel.style.width = `${size.width}px`;
2312
+ this.$panel.style.right = this.store.getState().isOpen ? "0" : `-${size.width}px`;
2313
+ } else if (pos === "bottom" && size.height) {
2314
+ this.$panel.style.height = `${size.height}px`;
2315
+ this.$panel.style.bottom = this.store.getState().isOpen ? "0" : `-${size.height}px`;
2316
+ }
2317
+ });
2318
+ this.store.subscribe("language", (lang) => {
2319
+ var _a, _b, _c, _d;
2320
+ this.$header.language = lang;
2321
+ this.$taskList.updateHeaders();
2322
+ (_b = (_a = this.$taskDetail).updateTexts) == null ? void 0 : _b.call(_a);
2323
+ (_d = (_c = this.$timeline).updateTexts) == null ? void 0 : _d.call(_c);
2324
+ });
2325
+ this.store.subscribe("filterText", (text) => {
2326
+ const tasks = this.store.getState().tasks;
2327
+ this.$taskList.filter(text, tasks);
2328
+ });
2329
+ this.store.subscribe("schedulerRunning", (running) => {
2330
+ this.$header.schedulerRunning = running;
2331
+ });
2332
+ }
2333
+ addEventListeners() {
2334
+ this.$trigger.addEventListener("toggle", () => {
2335
+ this.store.toggle();
2336
+ });
2337
+ this.$header.addEventListener("close", (e) => {
2338
+ e.stopPropagation();
2339
+ this.store.toggle();
2340
+ });
2341
+ this.$header.addEventListener("dock-toggle", () => {
2342
+ const current = this.store.getState().dockPosition;
2343
+ this.store.setDockPosition(current === "right" ? "bottom" : "right");
2344
+ });
2345
+ this.$header.addEventListener("theme-toggle", (e) => {
2346
+ const theme = e.detail;
2347
+ this.store.setTheme(theme);
2348
+ });
2349
+ this.$header.addEventListener("lang-toggle", (e) => {
2350
+ const lang = e.detail;
2351
+ this.store.setLanguage(lang);
2352
+ });
2353
+ this.$header.addEventListener("tab-change", (e) => {
2354
+ const tab = e.detail;
2355
+ this.store.setTab(tab);
2356
+ });
2357
+ this.$header.addEventListener("search", (e) => {
2358
+ const text = e.detail;
2359
+ this.store.setFilterText(text);
2360
+ });
2361
+ this.$overlay.addEventListener("click", () => {
2362
+ this.store.toggle();
2363
+ });
2364
+ this.$taskList.addEventListener("task-select", (e) => {
2365
+ const id = e.detail;
2366
+ this.store.selectTask(id);
2367
+ });
2368
+ this.$taskDetail.addEventListener("back", () => {
2369
+ this.store.selectTask(null);
2370
+ });
2371
+ this.$taskList.addEventListener("task-action", (e) => {
2372
+ const { action, id } = e.detail;
2373
+ console.log("[DevTools] task-action:", action, id);
2374
+ switch (action) {
2375
+ case "trigger":
2376
+ this.store.triggerTask(id);
2377
+ break;
2378
+ case "stop":
2379
+ this.store.stopTask(id);
2380
+ break;
2381
+ case "start":
2382
+ this.store.startTask(id);
2383
+ break;
2384
+ case "remove":
2385
+ if (confirm(`Remove task "${id}"?`)) {
2386
+ this.store.removeTask(id);
2387
+ }
2388
+ break;
2389
+ }
2390
+ });
2391
+ }
2392
+ startLoop() {
2393
+ const loop = (time) => {
2394
+ const delta = time - this.lastTime;
2395
+ this.lastTime = time;
2396
+ const fps = 1e3 / delta;
2397
+ if (this.$header) {
2398
+ this.$header.fps = fps;
2399
+ }
2400
+ this.rAFId = requestAnimationFrame(loop);
2401
+ };
2402
+ this.rAFId = requestAnimationFrame(loop);
2403
+ }
2404
+ render() {
2405
+ this._shadow.innerHTML = `
2406
+ <style>
2407
+ ${themeStyles}
2408
+ :host {
2409
+ font-family: var(--hs-font-family);
2410
+ font-size: var(--hs-font-size);
2411
+ color: var(--hs-text);
2412
+ line-height: var(--hs-line-height);
2413
+ }
2414
+ .overlay {
2415
+ position: fixed;
2416
+ top: 0;
2417
+ left: 0;
2418
+ width: 100vw;
2419
+ height: 100vh;
2420
+ background: rgba(0, 0, 0, 0.5);
2421
+ z-index: calc(var(--hs-z-index) - 1); /* 确保在面板之下,但在应用之上 */
2422
+ display: none; /* 默认隐藏 */
2423
+ }
2424
+ .panel {
2425
+ position: fixed;
2426
+ background: var(--hs-bg);
2427
+ box-shadow: var(--hs-shadow);
2428
+ z-index: var(--hs-z-index);
2429
+ transition: all 0.3s ease;
2430
+ display: flex;
2431
+ flex-direction: column;
2432
+ border-left: 1px solid var(--hs-border);
2433
+ }
2434
+ /* Default Right Dock */
2435
+ .panel.dock-right {
2436
+ top: 0;
2437
+ right: -500px;
2438
+ width: 500px;
2439
+ height: 100vh;
2440
+ border-left: 1px solid var(--hs-border);
2441
+ border-top: none;
2442
+ }
2443
+
2444
+ /* Bottom Dock */
2445
+ .panel.dock-bottom {
2446
+ bottom: -50vh;
2447
+ left: 0;
2448
+ width: 100%;
2449
+ height: 50vh;
2450
+ max-height: 50vh;
2451
+ border-top: 1px solid var(--hs-border);
2452
+ border-left: none;
2453
+ }
2454
+
2455
+ .content {
2456
+ flex: 1;
2457
+ overflow: hidden;
2458
+ position: relative;
2459
+ display: flex;
2460
+ flex-direction: column;
2461
+ }
2462
+ .content > * {
2463
+ flex: 1;
2464
+ min-height: 0;
2465
+ }
2466
+
2467
+ /* Mobile - 固定尺寸,禁用拖拽 */
2468
+ @media (max-width: 480px) {
2469
+ .panel.dock-right {
2470
+ width: 100vw !important;
2471
+ right: -100vw;
2472
+ }
2473
+ .panel.dock-right.open {
2474
+ right: 0;
2475
+ }
2476
+ .panel.dock-bottom {
2477
+ height: 50vh !important;
2478
+ max-height: 50vh !important;
2479
+ bottom: -50vh;
2480
+ }
2481
+ .panel.dock-bottom.open {
2482
+ bottom: 0;
2483
+ }
2484
+ }
2485
+ </style>
2486
+
2487
+ <div class="overlay"></div>
2488
+ <hs-floating-trigger></hs-floating-trigger>
2489
+
2490
+ <div class="panel dock-right">
2491
+ <hs-resizer></hs-resizer>
2492
+ <hs-task-header></hs-task-header>
2493
+ <div class="content">
2494
+ <hs-task-list></hs-task-list>
2495
+ <hs-task-detail style="display:none"></hs-task-detail>
2496
+ <hs-timeline style="display:none"></hs-timeline>
2497
+ </div>
2498
+ </div>
2499
+ `;
2500
+ }
2501
+ }
2502
+ customElements.define("hs-devtools", DevTools);
2503
+ export {
2504
+ DevTools
2505
+ };