hyper-scheduler 1.0.0 → 1.0.1

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 (80) hide show
  1. package/dist/devtools-4xVHTaUz.js +2161 -0
  2. package/dist/devtools-DdQ1I25H.cjs +1 -0
  3. package/dist/index.cjs +1 -0
  4. package/dist/index.js +995 -0
  5. package/dist/index.umd.cjs +1 -0
  6. package/package.json +7 -1
  7. package/.editorconfig +0 -21
  8. package/.eslintrc.cjs +0 -26
  9. package/GEMINI.md +0 -1
  10. package/docs/.vitepress/config.ts +0 -52
  11. package/docs/README.md +0 -120
  12. package/docs/api/devtools.md +0 -232
  13. package/docs/api/index.md +0 -178
  14. package/docs/api/scheduler.md +0 -322
  15. package/docs/api/task.md +0 -439
  16. package/docs/api/types.md +0 -365
  17. package/docs/examples/index.md +0 -295
  18. package/docs/guide/best-practices.md +0 -436
  19. package/docs/guide/core-concepts.md +0 -363
  20. package/docs/guide/getting-started.md +0 -138
  21. package/docs/index.md +0 -33
  22. package/docs/public/logo.svg +0 -54
  23. package/examples/browser/index.html +0 -354
  24. package/examples/node/simple.js +0 -36
  25. package/examples/react-demo/index.html +0 -12
  26. package/examples/react-demo/package.json +0 -23
  27. package/examples/react-demo/src/App.css +0 -212
  28. package/examples/react-demo/src/App.jsx +0 -160
  29. package/examples/react-demo/src/main.jsx +0 -9
  30. package/examples/react-demo/vite.config.ts +0 -12
  31. package/examples/react-demo/yarn.lock +0 -752
  32. package/examples/vue-demo/index.html +0 -12
  33. package/examples/vue-demo/package.json +0 -21
  34. package/examples/vue-demo/src/App.vue +0 -373
  35. package/examples/vue-demo/src/main.ts +0 -4
  36. package/examples/vue-demo/vite.config.ts +0 -13
  37. package/examples/vue-demo/yarn.lock +0 -375
  38. package/src/constants.ts +0 -18
  39. package/src/core/retry-strategy.ts +0 -28
  40. package/src/core/scheduler.ts +0 -601
  41. package/src/core/task-registry.ts +0 -58
  42. package/src/index.ts +0 -74
  43. package/src/platform/browser/browser-timer.ts +0 -66
  44. package/src/platform/browser/main-thread-timer.ts +0 -16
  45. package/src/platform/browser/worker.ts +0 -31
  46. package/src/platform/node/debug-cli.ts +0 -19
  47. package/src/platform/node/node-timer.ts +0 -15
  48. package/src/platform/timer-strategy.ts +0 -19
  49. package/src/plugins/dev-tools.ts +0 -101
  50. package/src/types.ts +0 -115
  51. package/src/ui/components/devtools.ts +0 -525
  52. package/src/ui/components/floating-trigger.ts +0 -102
  53. package/src/ui/components/icons.ts +0 -16
  54. package/src/ui/components/resizer.ts +0 -129
  55. package/src/ui/components/task-detail.ts +0 -228
  56. package/src/ui/components/task-header.ts +0 -319
  57. package/src/ui/components/task-list.ts +0 -416
  58. package/src/ui/components/timeline.ts +0 -364
  59. package/src/ui/debug-panel.ts +0 -56
  60. package/src/ui/i18n/en.ts +0 -76
  61. package/src/ui/i18n/index.ts +0 -42
  62. package/src/ui/i18n/zh.ts +0 -76
  63. package/src/ui/store/dev-tools-store.ts +0 -191
  64. package/src/ui/styles/theme.css.ts +0 -56
  65. package/src/ui/styles.ts +0 -43
  66. package/src/utils/cron-lite.ts +0 -221
  67. package/src/utils/cron.ts +0 -20
  68. package/src/utils/id.ts +0 -10
  69. package/src/utils/schedule.ts +0 -93
  70. package/src/vite-env.d.ts +0 -1
  71. package/stats.html +0 -4949
  72. package/tests/integration/Debug.test.ts +0 -58
  73. package/tests/unit/Plugin.test.ts +0 -16
  74. package/tests/unit/RetryStrategy.test.ts +0 -21
  75. package/tests/unit/Scheduler.test.ts +0 -38
  76. package/tests/unit/schedule.test.ts +0 -70
  77. package/tests/unit/ui/DevToolsStore.test.ts +0 -67
  78. package/tsconfig.json +0 -28
  79. package/vite.config.ts +0 -51
  80. package/vitest.config.ts +0 -24
@@ -0,0 +1,2161 @@
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
+ /* Light Theme (Default) */
331
+ --hs-bg: #ffffff;
332
+ --hs-bg-secondary: #f3f4f6;
333
+ --hs-text: #1f2937;
334
+ --hs-text-secondary: #6b7280;
335
+ --hs-border: #e5e7eb;
336
+ --hs-primary: #3b82f6;
337
+ --hs-primary-hover: #2563eb;
338
+ --hs-danger: #ef4444;
339
+ --hs-danger-hover: #dc2626;
340
+ --hs-success: #10b981;
341
+ --hs-warning: #f59e0b;
342
+
343
+ --hs-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
344
+ --hs-radius: 6px;
345
+ --hs-header-height: 40px;
346
+ --hs-z-index: 9999;
347
+
348
+ /* Default display styles for the host itself */
349
+ background: var(--hs-bg);
350
+ color: var(--hs-text);
351
+ font-family: var(--hs-font-family);
352
+ font-size: var(--hs-font-size);
353
+ line-height: var(--hs-line-height);
354
+ }
355
+
356
+ :host([theme="dark"]) {
357
+ --hs-bg: #111827;
358
+ --hs-bg-secondary: #1f2937;
359
+ --hs-text: #f9fafb;
360
+ --hs-text-secondary: #9ca3af;
361
+ --hs-border: #374151;
362
+ --hs-primary: #60a5fa;
363
+ --hs-primary-hover: #3b82f6;
364
+ --hs-danger: #f87171;
365
+ --hs-danger-hover: #ef4444;
366
+ --hs-success: #34d399;
367
+ --hs-warning: #fbbf24;
368
+
369
+ --hs-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
370
+ }
371
+
372
+ :host {
373
+ background: var(--hs-bg);
374
+ color: var(--hs-text);
375
+ }
376
+ `;
377
+ const ICONS = {
378
+ 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>`,
379
+ 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>`,
380
+ 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>`,
381
+ 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>`,
382
+ 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>`,
383
+ 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>`,
384
+ 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>`,
385
+ 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>`,
386
+ 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>`,
387
+ 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>`,
388
+ 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>`
389
+ };
390
+ class FloatingTrigger extends HTMLElement {
391
+ constructor() {
392
+ super();
393
+ __publicField(this, "_shadow");
394
+ // 配置属性
395
+ __publicField(this, "_position", "bottom-right");
396
+ __publicField(this, "_bgColor", "");
397
+ __publicField(this, "_textColor", "");
398
+ this._shadow = this.attachShadow({ mode: "open" });
399
+ }
400
+ static get observedAttributes() {
401
+ return ["position", "bg-color", "text-color"];
402
+ }
403
+ connectedCallback() {
404
+ this.render();
405
+ this.addEventListeners();
406
+ }
407
+ attributeChangedCallback(name, _oldVal, newVal) {
408
+ console.log("[FloatingTrigger] attributeChangedCallback:", name, newVal);
409
+ if (name === "position") {
410
+ this._position = newVal || "bottom-right";
411
+ } else if (name === "bg-color") {
412
+ this._bgColor = newVal || "";
413
+ } else if (name === "text-color") {
414
+ this._textColor = newVal || "";
415
+ }
416
+ if (this._shadow.querySelector("button")) {
417
+ console.log("[FloatingTrigger] re-rendering with:", this._position, this._bgColor, this._textColor);
418
+ this.render();
419
+ this.addEventListeners();
420
+ }
421
+ }
422
+ addEventListeners() {
423
+ const btn = this._shadow.querySelector("button");
424
+ btn == null ? void 0 : btn.addEventListener("click", () => {
425
+ this.dispatchEvent(new CustomEvent("toggle", { bubbles: true, composed: true }));
426
+ });
427
+ }
428
+ render() {
429
+ const pos = this._position;
430
+ const posStyles = `
431
+ top: ${pos.includes("top") ? "20px" : "auto"};
432
+ bottom: ${pos.includes("bottom") ? "20px" : "auto"};
433
+ left: ${pos.includes("left") ? "20px" : "auto"};
434
+ right: ${pos.includes("right") ? "20px" : "auto"};
435
+ `;
436
+ const bgStyle = this._bgColor ? `background: ${this._bgColor};` : "";
437
+ const colorStyle = this._textColor ? `color: ${this._textColor};` : "";
438
+ this._shadow.innerHTML = `
439
+ <style>
440
+ ${themeStyles}
441
+ button {
442
+ position: fixed;
443
+ ${posStyles}
444
+ width: 48px;
445
+ height: 48px;
446
+ border-radius: 50%;
447
+ background: var(--hs-primary);
448
+ color: white;
449
+ ${bgStyle}
450
+ ${colorStyle}
451
+ border: none;
452
+ box-shadow: var(--hs-shadow);
453
+ cursor: pointer;
454
+ display: flex;
455
+ align-items: center;
456
+ justify-content: center;
457
+ font-family: var(--hs-font-family);
458
+ z-index: var(--hs-z-index);
459
+ transition: transform 0.2s;
460
+ }
461
+ button:hover {
462
+ ${this._bgColor ? `background: ${this._bgColor}; filter: brightness(1.1);` : "background: var(--hs-primary-hover);"}
463
+ transform: scale(1.1);
464
+ }
465
+ button:active {
466
+ transform: scale(0.95);
467
+ }
468
+ </style>
469
+ <button title="Toggle Hyper Scheduler DevTools">
470
+ ${ICONS.chart}
471
+ </button>
472
+ `;
473
+ }
474
+ }
475
+ customElements.define("hs-floating-trigger", FloatingTrigger);
476
+ class TaskHeader extends HTMLElement {
477
+ constructor() {
478
+ super();
479
+ __publicField(this, "_shadow");
480
+ __publicField(this, "_fps", 0);
481
+ __publicField(this, "_stats", { active: 0, total: 0 });
482
+ __publicField(this, "_theme", "auto");
483
+ __publicField(this, "_activeTab", "tasks");
484
+ __publicField(this, "_language", "en");
485
+ __publicField(this, "_schedulerRunning", false);
486
+ __publicField(this, "$fps");
487
+ __publicField(this, "$stats");
488
+ __publicField(this, "$schedulerStatus");
489
+ __publicField(this, "$themeIcon");
490
+ __publicField(this, "$dockIcon");
491
+ __publicField(this, "$tabs");
492
+ __publicField(this, "$searchInput");
493
+ __publicField(this, "$title");
494
+ __publicField(this, "$langBtn");
495
+ this._shadow = this.attachShadow({ mode: "open" });
496
+ }
497
+ connectedCallback() {
498
+ this.render();
499
+ this.cacheDom();
500
+ this.addEventListeners();
501
+ this.updateView();
502
+ }
503
+ set fps(val) {
504
+ this._fps = Math.round(val);
505
+ if (this.$fps) {
506
+ const color = this._fps < 30 ? "var(--hs-danger)" : this._fps < 50 ? "var(--hs-warning)" : "var(--hs-success)";
507
+ this.$fps.innerHTML = `⚡ ${t("stats.fps")}: <span style="color:${color}">${this._fps}</span> (${t("stats.mainThread")})`;
508
+ }
509
+ }
510
+ set stats(val) {
511
+ this._stats = val;
512
+ if (this.$stats) {
513
+ 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>`;
514
+ }
515
+ }
516
+ set schedulerRunning(val) {
517
+ this._schedulerRunning = val;
518
+ if (this.$schedulerStatus) {
519
+ const statusText = val ? t("stats.running") : t("stats.stopped");
520
+ const statusColor = val ? "var(--hs-success)" : "var(--hs-danger)";
521
+ const statusIcon = val ? "▶️" : "⏹️";
522
+ this.$schedulerStatus.innerHTML = `${statusIcon} ${t("stats.scheduler")}: <span style="color:${statusColor}">${statusText}</span>`;
523
+ }
524
+ }
525
+ set theme(val) {
526
+ this._theme = val;
527
+ this.setAttribute("theme", val);
528
+ this.updateThemeIcon();
529
+ }
530
+ set language(val) {
531
+ this._language = val;
532
+ this.updateTexts();
533
+ }
534
+ set dockPosition(val) {
535
+ var _a;
536
+ if (this.$dockIcon) {
537
+ this.$dockIcon.innerHTML = val === "right" ? ICONS.dock : ICONS.dockRight;
538
+ (_a = this.$dockIcon.parentElement) == null ? void 0 : _a.setAttribute("title", t("header.toggleDock"));
539
+ }
540
+ }
541
+ set activeTab(val) {
542
+ this._activeTab = val;
543
+ this.updateTabs();
544
+ }
545
+ cacheDom() {
546
+ this.$fps = this._shadow.querySelector(".fps");
547
+ this.$stats = this._shadow.querySelector(".stats");
548
+ this.$schedulerStatus = this._shadow.querySelector(".scheduler-status");
549
+ this.$themeIcon = this._shadow.querySelector(".theme-btn span");
550
+ this.$dockIcon = this._shadow.querySelector(".dock-btn");
551
+ this.$tabs = this._shadow.querySelectorAll(".tab");
552
+ this.$searchInput = this._shadow.querySelector(".search-input");
553
+ this.$title = this._shadow.querySelector(".title");
554
+ this.$langBtn = this._shadow.querySelector(".lang-btn");
555
+ }
556
+ addEventListeners() {
557
+ var _a, _b, _c, _d, _e;
558
+ (_a = this._shadow.querySelector(".dock-btn")) == null ? void 0 : _a.addEventListener("click", () => {
559
+ this.dispatchEvent(new CustomEvent("dock-toggle"));
560
+ });
561
+ (_b = this._shadow.querySelector(".theme-btn")) == null ? void 0 : _b.addEventListener("click", () => {
562
+ const newTheme = this._theme === "dark" ? "light" : "dark";
563
+ this.dispatchEvent(new CustomEvent("theme-toggle", { detail: newTheme }));
564
+ });
565
+ (_c = this._shadow.querySelector(".lang-btn")) == null ? void 0 : _c.addEventListener("click", () => {
566
+ const newLang = this._language === "en" ? "zh" : "en";
567
+ this.dispatchEvent(new CustomEvent("lang-toggle", { detail: newLang }));
568
+ });
569
+ (_d = this._shadow.querySelector(".close-btn")) == null ? void 0 : _d.addEventListener("click", () => {
570
+ this.dispatchEvent(new CustomEvent("close"));
571
+ });
572
+ this.$tabs.forEach((tab) => {
573
+ tab.addEventListener("click", (e) => {
574
+ const target = e.currentTarget.dataset.tab;
575
+ this.dispatchEvent(new CustomEvent("tab-change", { detail: target }));
576
+ });
577
+ });
578
+ (_e = this.$searchInput) == null ? void 0 : _e.addEventListener("input", (e) => {
579
+ const val = e.target.value;
580
+ this.dispatchEvent(new CustomEvent("search", { detail: val }));
581
+ });
582
+ }
583
+ updateThemeIcon() {
584
+ if (this.$themeIcon) {
585
+ this.$themeIcon.innerHTML = this._theme === "dark" ? ICONS.moon : ICONS.sun;
586
+ }
587
+ }
588
+ updateTabs() {
589
+ this.$tabs.forEach((tab) => {
590
+ if (tab.dataset.tab === this._activeTab) {
591
+ tab.classList.add("active");
592
+ } else {
593
+ tab.classList.remove("active");
594
+ }
595
+ });
596
+ }
597
+ updateTexts() {
598
+ if (this.$title) this.$title.innerHTML = `🕒 ${t("header.title")}`;
599
+ if (this.$searchInput) this.$searchInput.placeholder = t("header.searchPlaceholder");
600
+ if (this.$langBtn) this.$langBtn.textContent = this._language === "en" ? "中" : "EN";
601
+ this.$tabs.forEach((tab) => {
602
+ const key = tab.dataset.tab;
603
+ if (key === "tasks") tab.innerHTML = `📌 ${t("tabs.tasks")}`;
604
+ if (key === "timeline") tab.innerHTML = `📈 ${t("tabs.timeline")}`;
605
+ });
606
+ this.stats = this._stats;
607
+ this.fps = this._fps;
608
+ this.schedulerRunning = this._schedulerRunning;
609
+ }
610
+ updateView() {
611
+ this.updateThemeIcon();
612
+ this.updateTabs();
613
+ this.updateTexts();
614
+ }
615
+ render() {
616
+ this._shadow.innerHTML = `
617
+ <style>
618
+ ${themeStyles}
619
+ :host {
620
+ display: block;
621
+ background: var(--hs-bg);
622
+ border-bottom: 1px solid var(--hs-border);
623
+ padding: 0 16px;
624
+ height: var(--hs-header-height);
625
+ height: auto;
626
+ }
627
+ .top-bar {
628
+ display: flex;
629
+ justify-content: space-between;
630
+ align-items: center;
631
+ height: 40px;
632
+ border-bottom: 1px solid var(--hs-border);
633
+ }
634
+ .title {
635
+ font-weight: 600;
636
+ font-size: 13px;
637
+ color: var(--hs-text);
638
+ display: flex;
639
+ align-items: center;
640
+ gap: 6px;
641
+ }
642
+ .search-box {
643
+ flex: 1;
644
+ max-width: 300px;
645
+ margin: 0px 32px 0 16px;
646
+ }
647
+ .search-input {
648
+ width: 100%;
649
+ background: var(--hs-bg-secondary);
650
+ border: 1px solid var(--hs-border);
651
+ color: var(--hs-text);
652
+ padding: 6px 12px;
653
+ border-radius: 4px;
654
+ font-size: 12px;
655
+ }
656
+ .search-input::placeholder {
657
+ color: var(--hs-text-secondary);
658
+ }
659
+ .controls {
660
+ display: flex;
661
+ align-items: center;
662
+ gap: 4px;
663
+ }
664
+ button {
665
+ background: transparent;
666
+ border: none;
667
+ color: var(--hs-text-secondary);
668
+ cursor: pointer;
669
+ padding: 4px;
670
+ border-radius: 4px;
671
+ display: flex;
672
+ align-items: center;
673
+ justify-content: center;
674
+ font-size: 12px;
675
+ width: 28px;
676
+ height: 28px;
677
+ }
678
+ .theme-btn span {
679
+ margin-top: 4px;
680
+ }
681
+ button:hover {
682
+ background: var(--hs-bg-secondary);
683
+ color: var(--hs-text);
684
+ }
685
+ button svg {
686
+ width: 16px;
687
+ height: 16px;
688
+ }
689
+ .lang-btn {
690
+ font-weight: 600;
691
+ }
692
+ .stats-bar {
693
+ display: flex;
694
+ justify-content: space-between;
695
+ align-items: center;
696
+ height: 30px;
697
+ font-size: 11px;
698
+ color: var(--hs-text-secondary);
699
+ border-bottom: 1px solid var(--hs-border);
700
+ gap: 16px;
701
+ }
702
+ .stats-left {
703
+ display: flex;
704
+ gap: 16px;
705
+ }
706
+ .tabs-bar {
707
+ display: flex;
708
+ height: 36px;
709
+ gap: 0;
710
+ }
711
+ .tab {
712
+ display: flex;
713
+ align-items: center;
714
+ gap: 6px;
715
+ font-size: 12px;
716
+ color: var(--hs-text-secondary);
717
+ cursor: pointer;
718
+ border-bottom: 3px solid transparent;
719
+ padding: 0 16px;
720
+ transition: all 0.2s;
721
+ }
722
+ .tab:hover {
723
+ color: var(--hs-text);
724
+ background: var(--hs-bg-secondary);
725
+ }
726
+ .tab.active {
727
+ color: var(--hs-text);
728
+ font-weight: 600;
729
+ border-bottom-color: var(--hs-primary);
730
+ background: var(--hs-bg-secondary);
731
+ }
732
+ </style>
733
+
734
+ <div class="top-bar">
735
+ <div class="title"></div>
736
+ <div class="search-box">
737
+ <input type="text" class="search-input">
738
+ </div>
739
+ <div class="controls">
740
+ <button class="lang-btn" title="Switch Language">EN</button>
741
+ <button class="dock-btn" title="Toggle Dock">${ICONS.dock}</button>
742
+ <button class="theme-btn" title="Toggle Theme"><span>${ICONS.sun}</span></button>
743
+ <button class="close-btn" title="Close">${ICONS.close}</button>
744
+ </div>
745
+ </div>
746
+
747
+ <div class="stats-bar">
748
+ <div class="stats-left">
749
+ <div class="scheduler-status"></div>
750
+ <div class="stats"></div>
751
+ </div>
752
+ <div class="fps"></div>
753
+ </div>
754
+
755
+ <div class="tabs-bar">
756
+ <div class="tab active" data-tab="tasks"></div>
757
+ <div class="tab" data-tab="timeline"></div>
758
+ </div>
759
+ `;
760
+ }
761
+ }
762
+ customElements.define("hs-task-header", TaskHeader);
763
+ class TaskList extends HTMLElement {
764
+ constructor() {
765
+ super();
766
+ __publicField(this, "_shadow");
767
+ __publicField(this, "_tasks", []);
768
+ __publicField(this, "_lastExecutionTimes", /* @__PURE__ */ new Map());
769
+ this._shadow = this.attachShadow({ mode: "open" });
770
+ }
771
+ connectedCallback() {
772
+ this.render();
773
+ }
774
+ set tasks(map) {
775
+ const newTasks = Array.from(map.values());
776
+ newTasks.forEach((task) => {
777
+ const oldTask = this._tasks.find((t2) => t2.id === task.id);
778
+ if (oldTask && task.executionCount > oldTask.executionCount) {
779
+ this._lastExecutionTimes.set(task.id, Date.now());
780
+ }
781
+ });
782
+ this._tasks = newTasks;
783
+ this.renderRows();
784
+ }
785
+ filter(text, map) {
786
+ const all = Array.from(map.values());
787
+ if (!text) {
788
+ this._tasks = all;
789
+ } else {
790
+ const lower = text.toLowerCase();
791
+ this._tasks = all.filter(
792
+ (t2) => t2.id.toLowerCase().includes(lower) || t2.tags.some((tag) => tag.toLowerCase().includes(lower))
793
+ );
794
+ }
795
+ this.renderRows();
796
+ }
797
+ // Method to update table headers when language changes
798
+ updateHeaders() {
799
+ const thead = this._shadow.querySelector("thead");
800
+ if (thead) {
801
+ thead.innerHTML = `
802
+ <tr>
803
+ <th style="width:40px">#</th>
804
+ <th style="min-width:150px">${t("list.idTags")}</th>
805
+ <th style="width:150px">${t("list.status")}</th>
806
+ <th style="width:70px">${t("list.driver")}</th>
807
+ <th style="width:100px">${t("list.schedule")}</th>
808
+ <th style="width:60px">${t("list.count")}</th>
809
+ <th style="width:100px">${t("list.lastRun")}</th>
810
+ <th style="width:100px">${t("list.actions")}</th>
811
+ </tr>
812
+ `;
813
+ }
814
+ const tip = this._shadow.querySelector(".tip");
815
+ if (tip) {
816
+ tip.textContent = t("list.tip");
817
+ }
818
+ this.renderRows();
819
+ }
820
+ getStatusIcon(status, taskId) {
821
+ const lastExec = this._lastExecutionTimes.get(taskId);
822
+ const isRecentlyExecuted = lastExec && Date.now() - lastExec < 1e3;
823
+ switch (status) {
824
+ case TaskStatus.RUNNING:
825
+ return `<span style="color:var(--hs-primary)">🔵</span> <strong>${t("status.running")}</strong>`;
826
+ case TaskStatus.STOPPED:
827
+ return `<span style="color:var(--hs-text-secondary)">⚪</span> ${t("status.stopped")}`;
828
+ case TaskStatus.IDLE:
829
+ if (isRecentlyExecuted) {
830
+ return `<span class="status-flash" style="color:var(--hs-success)">🟢</span> ${t("status.idle")}`;
831
+ }
832
+ return `<span style="color:var(--hs-success)">🟢</span> ${t("status.idle")}`;
833
+ case TaskStatus.ERROR:
834
+ return `<span style="color:var(--hs-warning)">🟠</span> ${t("status.error")}`;
835
+ default:
836
+ return status;
837
+ }
838
+ }
839
+ formatSchedule(schedule) {
840
+ if (typeof schedule === "number") {
841
+ return `${schedule}ms`;
842
+ }
843
+ if (schedule && (schedule.includes("*") || schedule.includes(" "))) {
844
+ return schedule.length > 15 ? schedule.substring(0, 12) + "..." : schedule;
845
+ }
846
+ return schedule || "-";
847
+ }
848
+ formatTime(timestamp) {
849
+ if (!timestamp) return "-";
850
+ const date = new Date(timestamp);
851
+ return date.toLocaleTimeString("en-US", {
852
+ hour12: false,
853
+ hour: "2-digit",
854
+ minute: "2-digit",
855
+ second: "2-digit"
856
+ }) + "." + date.getMilliseconds().toString().padStart(3, "0");
857
+ }
858
+ getDriverBadge(driver) {
859
+ const d = driver || "worker";
860
+ if (d === "worker") {
861
+ return `<span class="driver-badge worker" title="${t("list.driverWorker")}">W</span>`;
862
+ }
863
+ return `<span class="driver-badge main" title="${t("list.driverMain")}">M</span>`;
864
+ }
865
+ renderRows() {
866
+ const tbody = this._shadow.querySelector("tbody");
867
+ if (!tbody) return;
868
+ tbody.innerHTML = this._tasks.map((task, index) => {
869
+ const lastExec = this._lastExecutionTimes.get(task.id);
870
+ const isRecentlyExecuted = lastExec && Date.now() - lastExec < 1e3;
871
+ const rowClass = isRecentlyExecuted ? "recently-executed" : "";
872
+ return `
873
+ <tr data-id="${task.id}" class="${rowClass}">
874
+ <td class="col-num">${index + 1}</td>
875
+ <td class="col-id">
876
+ <div class="task-id">${task.id}</div>
877
+ <div class="tags">
878
+ ${task.tags && task.tags.length > 0 ? task.tags.map((t2) => `<span class="tag">${t2}</span>`).join("") : `<span class="no-tags">${t("list.noTags")}</span>`}
879
+ </div>
880
+ </td>
881
+ <td>${this.getStatusIcon(task.status, task.id)}</td>
882
+ <td>${this.getDriverBadge(task.driver)}</td>
883
+ <td>${this.formatSchedule(task.schedule)}</td>
884
+ <td>${task.executionCount || 0}</td>
885
+ <td>${this.formatTime(task.lastRun)}</td>
886
+ <td class="col-actions">
887
+ <div class="action-group">
888
+ <button class="btn-icon" data-action="trigger" title="${t("actions.trigger")}" ${task.status === TaskStatus.RUNNING ? "disabled" : ""}>${ICONS.trigger}</button>
889
+ ${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>`}
890
+ <button class="btn-icon danger" data-action="remove" title="${t("actions.remove")}">${ICONS.remove}</button>
891
+ </div>
892
+ </td>
893
+ </tr>
894
+ `;
895
+ }).join("");
896
+ }
897
+ render() {
898
+ this._shadow.innerHTML = `
899
+ <style>
900
+ ${themeStyles}
901
+ :host {
902
+ display: flex;
903
+ flex-direction: column;
904
+ height: 100%;
905
+ background: var(--hs-bg);
906
+ }
907
+ .table-container {
908
+ flex: 1;
909
+ min-height: 0;
910
+ overflow-y: auto;
911
+ overflow-x: auto;
912
+ position: relative;
913
+ }
914
+ table {
915
+ width: 100%;
916
+ border-collapse: collapse;
917
+ font-size: var(--hs-font-size);
918
+ color: var(--hs-text);
919
+ }
920
+ thead {
921
+ position: sticky;
922
+ top: 0;
923
+ z-index: 2;
924
+ background: var(--hs-bg);
925
+ }
926
+ th {
927
+ text-align: left;
928
+ padding: 8px 12px;
929
+ border-bottom: 2px solid var(--hs-border);
930
+ color: var(--hs-text-secondary);
931
+ font-weight: 600;
932
+ background: var(--hs-bg);
933
+ font-size: 11px;
934
+ text-transform: uppercase;
935
+ white-space: nowrap;
936
+ }
937
+ th:last-child {
938
+ position: sticky;
939
+ right: 0;
940
+ background: var(--hs-bg);
941
+ box-shadow: -2px 0 4px rgba(0,0,0,0.1);
942
+ }
943
+ td {
944
+ padding: 8px 12px;
945
+ border-bottom: 1px solid var(--hs-border);
946
+ vertical-align: middle;
947
+ }
948
+ tr:hover {
949
+ background: var(--hs-bg-secondary);
950
+ cursor: pointer;
951
+ }
952
+ tr.recently-executed {
953
+ animation: flash-row 1s ease-out;
954
+ }
955
+ @keyframes flash-row {
956
+ 0% { background: rgba(34, 197, 94, 0.2); }
957
+ 100% { background: transparent; }
958
+ }
959
+ .status-flash {
960
+ animation: flash-icon 1s ease-out;
961
+ }
962
+ @keyframes flash-icon {
963
+ 0%, 50% { opacity: 1; }
964
+ 25%, 75% { opacity: 0.3; }
965
+ }
966
+ .tip {
967
+ padding: 12px;
968
+ text-align: center;
969
+ font-size: 11px;
970
+ color: var(--hs-text-secondary);
971
+ border-top: 1px solid var(--hs-border);
972
+ background: var(--hs-bg);
973
+ }
974
+ .task-id {
975
+ font-weight: 600;
976
+ }
977
+ .tags {
978
+ display: flex;
979
+ gap: 4px;
980
+ margin-top: 4px;
981
+ }
982
+ .tag {
983
+ background: var(--hs-bg-secondary);
984
+ border: 1px solid var(--hs-border);
985
+ border-radius: 10px;
986
+ padding: 2px 8px;
987
+ font-size: 10px;
988
+ color: var(--hs-text-secondary);
989
+ }
990
+ .no-tags {
991
+ font-size: 10px;
992
+ color: var(--hs-text-secondary);
993
+ font-style: italic;
994
+ }
995
+ .driver-badge {
996
+ display: inline-flex;
997
+ align-items: center;
998
+ justify-content: center;
999
+ width: 22px;
1000
+ height: 22px;
1001
+ border-radius: 4px;
1002
+ font-size: 11px;
1003
+ font-weight: 600;
1004
+ font-family: monospace;
1005
+ }
1006
+ .driver-badge.worker {
1007
+ background: rgba(34, 197, 94, 0.15);
1008
+ color: var(--hs-success);
1009
+ border: 1px solid var(--hs-success);
1010
+ }
1011
+ .driver-badge.main {
1012
+ background: rgba(245, 158, 11, 0.15);
1013
+ color: var(--hs-warning);
1014
+ border: 1px solid var(--hs-warning);
1015
+ }
1016
+ .col-num {
1017
+ width: 40px;
1018
+ color: var(--hs-text-secondary);
1019
+ font-size: 11px;
1020
+ }
1021
+ .col-actions {
1022
+ width: 100px;
1023
+ position: sticky;
1024
+ right: 0;
1025
+ background: var(--hs-bg);
1026
+ box-shadow: -2px 0 4px rgba(0,0,0,0.1);
1027
+ }
1028
+ .action-group {
1029
+ display: flex;
1030
+ gap: 4px;
1031
+ }
1032
+ .btn-icon {
1033
+ background: transparent;
1034
+ border: 1px solid var(--hs-border);
1035
+ color: var(--hs-text-secondary);
1036
+ border-radius: 4px;
1037
+ width: 24px;
1038
+ height: 24px;
1039
+ display: flex;
1040
+ align-items: center;
1041
+ justify-content: center;
1042
+ cursor: pointer;
1043
+ padding: 0;
1044
+ flex-shrink: 0;
1045
+ }
1046
+ .btn-icon svg {
1047
+ width: 14px;
1048
+ height: 14px;
1049
+ display: block;
1050
+ }
1051
+ .btn-icon:hover {
1052
+ background: var(--hs-primary);
1053
+ color: white;
1054
+ border-color: var(--hs-primary);
1055
+ }
1056
+ .btn-trigger:hover {
1057
+ background: var(--hs-success);
1058
+ border-color: var(--hs-success);
1059
+ }
1060
+ .btn-pause:hover {
1061
+ background: var(--hs-warning);
1062
+ border-color: var(--hs-warning);
1063
+ }
1064
+ .btn-resume:hover {
1065
+ background: var(--hs-success);
1066
+ border-color: var(--hs-success);
1067
+ }
1068
+ .btn-remove:hover {
1069
+ color: white;
1070
+ background: var(--hs-danger);
1071
+ border-color: var(--hs-danger);
1072
+ }
1073
+ .btn-icon:disabled {
1074
+ opacity: 0.4;
1075
+ cursor: not-allowed;
1076
+ }
1077
+ .btn-icon:disabled:hover {
1078
+ background: transparent;
1079
+ color: var(--hs-text-secondary);
1080
+ border-color: var(--hs-border);
1081
+ }
1082
+ </style>
1083
+ <div class="table-container">
1084
+ <table>
1085
+ <thead>
1086
+ <tr>
1087
+ <th style="width:40px">#</th>
1088
+ <th style="min-width:150px">${t("list.idTags")}</th>
1089
+ <th style="width:150px">${t("list.status")}</th>
1090
+ <th style="width:70px">${t("list.driver")}</th>
1091
+ <th style="width:100px">${t("list.schedule")}</th>
1092
+ <th style="width:60px">${t("list.count")}</th>
1093
+ <th style="width:100px">${t("list.lastRun")}</th>
1094
+ <th style="width:100px">${t("list.actions")}</th>
1095
+ </tr>
1096
+ </thead>
1097
+ <tbody>
1098
+ <!-- Rows -->
1099
+ </tbody>
1100
+ </table>
1101
+ </div>
1102
+ <div class="tip">
1103
+ ${t("list.tip")}
1104
+ </div>
1105
+ `;
1106
+ this._shadow.addEventListener("click", (e) => {
1107
+ const target = e.target;
1108
+ const btn = target.closest("button");
1109
+ if (btn) {
1110
+ const action = btn.dataset.action;
1111
+ const tr2 = btn.closest("tr");
1112
+ if (!action || !tr2) return;
1113
+ const id = tr2.dataset.id;
1114
+ this.dispatchEvent(new CustomEvent("task-action", {
1115
+ detail: { action, id },
1116
+ bubbles: true,
1117
+ composed: true
1118
+ }));
1119
+ return;
1120
+ }
1121
+ const tr = target.closest("tr");
1122
+ if (tr && !target.closest(".col-actions")) {
1123
+ const id = tr.dataset.id;
1124
+ this.dispatchEvent(new CustomEvent("task-select", {
1125
+ detail: id,
1126
+ bubbles: true,
1127
+ composed: true
1128
+ }));
1129
+ }
1130
+ });
1131
+ }
1132
+ }
1133
+ customElements.define("hs-task-list", TaskList);
1134
+ class TaskDetail extends HTMLElement {
1135
+ constructor() {
1136
+ super();
1137
+ __publicField(this, "_shadow");
1138
+ __publicField(this, "_task", null);
1139
+ __publicField(this, "_history", []);
1140
+ this._shadow = this.attachShadow({ mode: "open" });
1141
+ }
1142
+ connectedCallback() {
1143
+ this.render();
1144
+ this._shadow.addEventListener("click", (e) => {
1145
+ if (e.target.closest(".back-btn")) {
1146
+ this.dispatchEvent(new CustomEvent("back"));
1147
+ }
1148
+ });
1149
+ }
1150
+ set task(task) {
1151
+ this._task = task;
1152
+ this.renderContent();
1153
+ }
1154
+ set history(h) {
1155
+ this._history = h || [];
1156
+ this.renderContent();
1157
+ }
1158
+ // Method to update texts when language changes
1159
+ updateTexts() {
1160
+ this.renderContent();
1161
+ }
1162
+ renderContent() {
1163
+ const container = this._shadow.querySelector(".content");
1164
+ if (!container) return;
1165
+ if (!this._task) {
1166
+ container.innerHTML = `<div class="empty">${t("detail.noTask")}</div>`;
1167
+ return;
1168
+ }
1169
+ const task = this._task;
1170
+ const config = {
1171
+ id: task.id,
1172
+ schedule: task.schedule,
1173
+ tags: task.tags
1174
+ };
1175
+ const avgDuration = this._history.length > 0 ? Math.round(this._history.reduce((sum, r) => sum + r.duration, 0) / this._history.length) : 0;
1176
+ container.innerHTML = `
1177
+ <div class="header">
1178
+ <button class="back-btn" title="${t("detail.back")}">${ICONS.back}</button>
1179
+ <h2>📂 ${task.id}</h2>
1180
+ </div>
1181
+
1182
+ <div class="section">
1183
+ <div class="config-label">${t("detail.config")}:</div>
1184
+ <pre>${JSON.stringify(config, null, 2)}</pre>
1185
+ </div>
1186
+
1187
+ <div class="section">
1188
+ <h3>📜 ${t("detail.history")} (${t("detail.lastRuns", { n: this._history.length })}) ${avgDuration > 0 ? `- ${t("detail.avgDuration")}: ${avgDuration}ms` : ""}</h3>
1189
+ <table class="history-table">
1190
+ <thead>
1191
+ <tr>
1192
+ <th>#</th>
1193
+ <th>${t("detail.startTime")}</th>
1194
+ <th>${t("detail.duration")}</th>
1195
+ <th>${t("detail.drift")}</th>
1196
+ <th>${t("detail.status")}</th>
1197
+ </tr>
1198
+ </thead>
1199
+ <tbody>
1200
+ ${this._history.length === 0 ? `<tr><td colspan="5" class="no-data">${t("detail.noHistory")}</td></tr>` : ""}
1201
+ ${this._history.slice().reverse().map((run, idx) => {
1202
+ const driftStr = "0ms";
1203
+ const statusIcon = run.success ? `✅ ${t("detail.success")}` : run.error ? `❌ ${t("detail.error")}: ${run.error}` : `⚠️ ${t("detail.failed")}`;
1204
+ const durationClass = run.duration > 100 ? "slow" : "";
1205
+ return `
1206
+ <tr class="${run.success ? "success" : "error"}">
1207
+ <td class="col-num">${this._history.length - idx}</td>
1208
+ <td>${new Date(run.timestamp).toLocaleTimeString("en-US", { hour12: false })}</td>
1209
+ <td class="${durationClass}">${run.duration}ms</td>
1210
+ <td>${driftStr}</td>
1211
+ <td class="status-cell">${statusIcon}</td>
1212
+ </tr>
1213
+ `;
1214
+ }).join("")}
1215
+ </tbody>
1216
+ </table>
1217
+ </div>
1218
+ `;
1219
+ }
1220
+ render() {
1221
+ this._shadow.innerHTML = `
1222
+ <style>
1223
+ ${themeStyles}
1224
+ :host {
1225
+ display: block;
1226
+ height: 100%;
1227
+ background: var(--hs-bg);
1228
+ overflow-y: auto;
1229
+ }
1230
+ .content {
1231
+ padding: 16px;
1232
+ }
1233
+ .header {
1234
+ display: flex;
1235
+ align-items: center;
1236
+ gap: 16px;
1237
+ margin-bottom: 24px;
1238
+ padding-bottom: 16px;
1239
+ border-bottom: 2px solid var(--hs-border);
1240
+ }
1241
+ .back-btn {
1242
+ background: var(--hs-bg-secondary);
1243
+ border: 1px solid var(--hs-border);
1244
+ color: var(--hs-text);
1245
+ padding: 6px 12px;
1246
+ border-radius: 4px;
1247
+ cursor: pointer;
1248
+ display: flex;
1249
+ align-items: center;
1250
+ gap: 4px;
1251
+ font-size: 12px;
1252
+ }
1253
+ .back-btn:hover {
1254
+ background: var(--hs-primary);
1255
+ color: white;
1256
+ border-color: var(--hs-primary);
1257
+ }
1258
+ h2 {
1259
+ margin: 0;
1260
+ font-size: 16px;
1261
+ font-weight: 600;
1262
+ }
1263
+ h3 {
1264
+ font-size: 13px;
1265
+ color: var(--hs-text);
1266
+ margin-bottom: 12px;
1267
+ font-weight: 600;
1268
+ }
1269
+ .section {
1270
+ margin-bottom: 32px;
1271
+ }
1272
+ .config-label {
1273
+ font-size: 12px;
1274
+ color: var(--hs-text-secondary);
1275
+ margin-bottom: 8px;
1276
+ }
1277
+ pre {
1278
+ background: var(--hs-bg-secondary);
1279
+ padding: 12px;
1280
+ border-radius: 6px;
1281
+ border: 1px solid var(--hs-border);
1282
+ font-family: 'Monaco', 'Menlo', monospace;
1283
+ font-size: 11px;
1284
+ overflow-x: auto;
1285
+ line-height: 1.5;
1286
+ }
1287
+ .history-table {
1288
+ width: 100%;
1289
+ border-collapse: collapse;
1290
+ font-size: 11px;
1291
+ }
1292
+ .history-table th {
1293
+ text-align: left;
1294
+ padding: 8px;
1295
+ border-bottom: 2px solid var(--hs-border);
1296
+ color: var(--hs-text-secondary);
1297
+ font-weight: 600;
1298
+ background: var(--hs-bg-secondary);
1299
+ }
1300
+ .history-table td {
1301
+ padding: 8px;
1302
+ border-bottom: 1px solid var(--hs-border);
1303
+ }
1304
+ .history-table tr.success {
1305
+ background: rgba(34, 197, 94, 0.05);
1306
+ }
1307
+ .history-table tr.error {
1308
+ background: rgba(239, 68, 68, 0.05);
1309
+ }
1310
+ .history-table tr:hover {
1311
+ background: var(--hs-bg-secondary);
1312
+ }
1313
+ .col-num {
1314
+ width: 40px;
1315
+ color: var(--hs-text-secondary);
1316
+ }
1317
+ .slow {
1318
+ color: var(--hs-warning);
1319
+ font-weight: 600;
1320
+ }
1321
+ .status-cell {
1322
+ font-size: 11px;
1323
+ }
1324
+ .no-data {
1325
+ color: var(--hs-text-secondary);
1326
+ font-style: italic;
1327
+ text-align: center;
1328
+ padding: 24px;
1329
+ }
1330
+ </style>
1331
+ <div class="content"></div>
1332
+ `;
1333
+ }
1334
+ }
1335
+ customElements.define("hs-task-detail", TaskDetail);
1336
+ class Timeline extends HTMLElement {
1337
+ constructor() {
1338
+ super();
1339
+ __publicField(this, "_shadow");
1340
+ __publicField(this, "_tasks", /* @__PURE__ */ new Map());
1341
+ __publicField(this, "_history", /* @__PURE__ */ new Map());
1342
+ __publicField(this, "$canvas");
1343
+ __publicField(this, "ctx");
1344
+ __publicField(this, "timeRange", 60 * 1e3);
1345
+ // 1 minute window
1346
+ __publicField(this, "zoom", 1);
1347
+ this._shadow = this.attachShadow({ mode: "open" });
1348
+ }
1349
+ connectedCallback() {
1350
+ this.render();
1351
+ this.$canvas = this._shadow.querySelector("canvas");
1352
+ if (!this.$canvas) {
1353
+ console.error("[Timeline] Canvas not found");
1354
+ return;
1355
+ }
1356
+ this.ctx = this.$canvas.getContext("2d");
1357
+ if (!this.ctx) {
1358
+ console.error("[Timeline] Canvas context not available");
1359
+ return;
1360
+ }
1361
+ this.setupZoom();
1362
+ this.startLoop();
1363
+ const container = this._shadow.querySelector(".canvas-container");
1364
+ if (container) {
1365
+ const resizeObserver = new ResizeObserver(() => {
1366
+ requestAnimationFrame(() => this.draw());
1367
+ });
1368
+ resizeObserver.observe(container);
1369
+ }
1370
+ }
1371
+ set data(val) {
1372
+ this._tasks = val.tasks;
1373
+ this._history = val.history;
1374
+ }
1375
+ set defaultZoom(val) {
1376
+ if (val >= 0.5 && val <= 5) {
1377
+ this.zoom = val;
1378
+ this.timeRange = 60 * 1e3 / this.zoom;
1379
+ const zoomSlider = this._shadow.querySelector(".zoom-slider");
1380
+ if (zoomSlider) zoomSlider.value = val.toString();
1381
+ this.updateZoomLabel();
1382
+ }
1383
+ }
1384
+ // Method to update texts when language changes
1385
+ updateTexts() {
1386
+ this.updateZoomLabel();
1387
+ }
1388
+ updateZoomLabel() {
1389
+ const zoomLabel = this._shadow.querySelector(".zoom-label");
1390
+ if (zoomLabel) {
1391
+ zoomLabel.textContent = `${t("timeline.zoom")}: ${this.zoom}x (${Math.round(this.timeRange / 1e3)}s)`;
1392
+ }
1393
+ }
1394
+ setupZoom() {
1395
+ const zoomSlider = this._shadow.querySelector(".zoom-slider");
1396
+ const zoomOut = this._shadow.querySelector(".zoom-out");
1397
+ const zoomIn = this._shadow.querySelector(".zoom-in");
1398
+ const updateZoom = (newZoom) => {
1399
+ this.zoom = newZoom;
1400
+ this.timeRange = 60 * 1e3 / this.zoom;
1401
+ this.updateZoomLabel();
1402
+ };
1403
+ zoomSlider == null ? void 0 : zoomSlider.addEventListener("input", (e) => {
1404
+ const newZoom = parseFloat(e.target.value);
1405
+ updateZoom(newZoom);
1406
+ });
1407
+ zoomOut == null ? void 0 : zoomOut.addEventListener("click", () => {
1408
+ const newZoom = Math.max(0.5, this.zoom - 0.5);
1409
+ if (zoomSlider) zoomSlider.value = newZoom.toString();
1410
+ updateZoom(newZoom);
1411
+ });
1412
+ zoomIn == null ? void 0 : zoomIn.addEventListener("click", () => {
1413
+ const newZoom = Math.min(5, this.zoom + 0.5);
1414
+ if (zoomSlider) zoomSlider.value = newZoom.toString();
1415
+ updateZoom(newZoom);
1416
+ });
1417
+ this.updateZoomLabel();
1418
+ }
1419
+ startLoop() {
1420
+ const loop = () => {
1421
+ if (this.isConnected) {
1422
+ this.draw();
1423
+ requestAnimationFrame(loop);
1424
+ }
1425
+ };
1426
+ requestAnimationFrame(loop);
1427
+ }
1428
+ draw() {
1429
+ if (!this.ctx || !this.$canvas) return;
1430
+ const container = this._shadow.querySelector(".canvas-container");
1431
+ if (!container) return;
1432
+ const dpr = window.devicePixelRatio || 1;
1433
+ const width = container.clientWidth;
1434
+ if (width === 0) return;
1435
+ const rowHeight = 40;
1436
+ const taskIds = Array.from(this._tasks.keys());
1437
+ const headerHeight = 60;
1438
+ const footerHeight = 40;
1439
+ const minHeight = container.clientHeight || 300;
1440
+ const contentHeight = taskIds.length * rowHeight + headerHeight + footerHeight;
1441
+ const height = Math.max(minHeight, contentHeight);
1442
+ this.$canvas.width = width * dpr;
1443
+ this.$canvas.height = height * dpr;
1444
+ this.$canvas.style.width = `${width}px`;
1445
+ this.$canvas.style.height = `${height}px`;
1446
+ this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1447
+ const now = Date.now();
1448
+ const startTime = now - this.timeRange;
1449
+ const labelWidth = 150;
1450
+ const hostStyles = getComputedStyle(this);
1451
+ const bgColor = hostStyles.getPropertyValue("--hs-bg").trim() || "#1e1e1e";
1452
+ const textColor = hostStyles.getPropertyValue("--hs-text").trim() || "#fff";
1453
+ const textSecondary = hostStyles.getPropertyValue("--hs-text-secondary").trim() || "#888";
1454
+ const borderColor = hostStyles.getPropertyValue("--hs-border").trim() || "#333";
1455
+ const successColor = hostStyles.getPropertyValue("--hs-success").trim() || "#22c55e";
1456
+ const dangerColor = hostStyles.getPropertyValue("--hs-danger").trim() || "#ef4444";
1457
+ this.ctx.fillStyle = bgColor;
1458
+ this.ctx.fillRect(0, 0, width, height);
1459
+ this.drawTimeAxis(width, labelWidth, startTime, textSecondary, borderColor);
1460
+ taskIds.forEach((taskId, index) => {
1461
+ const y = index * rowHeight + 60;
1462
+ this.drawTaskRow(taskId, y, width, labelWidth, startTime, now, textColor, textSecondary, borderColor, successColor, dangerColor);
1463
+ });
1464
+ this.drawLegend(width, height, textSecondary, successColor);
1465
+ }
1466
+ drawTimeAxis(width, labelWidth, startTime, textSecondary, borderColor) {
1467
+ const ctx = this.ctx;
1468
+ const timelineWidth = width - labelWidth - 20;
1469
+ const segments = 4;
1470
+ ctx.fillStyle = textSecondary;
1471
+ ctx.font = "10px monospace";
1472
+ ctx.textAlign = "center";
1473
+ for (let i = 0; i <= segments; i++) {
1474
+ const x = labelWidth + timelineWidth / segments * i;
1475
+ const time = startTime + this.timeRange / segments * i;
1476
+ const timeStr = new Date(time).toLocaleTimeString("en-US", {
1477
+ hour: "2-digit",
1478
+ minute: "2-digit",
1479
+ second: "2-digit",
1480
+ hour12: false
1481
+ });
1482
+ ctx.fillText(timeStr, x, 30);
1483
+ ctx.strokeStyle = borderColor;
1484
+ ctx.beginPath();
1485
+ ctx.moveTo(x, 40);
1486
+ ctx.lineTo(x, this.$canvas.clientHeight - 40);
1487
+ ctx.stroke();
1488
+ }
1489
+ ctx.textAlign = "left";
1490
+ ctx.fillText(t("timeline.timeRange", { n: Math.round(this.timeRange / 1e3) }), 10, 15);
1491
+ }
1492
+ drawTaskRow(taskId, y, width, labelWidth, startTime, endTime, textColor, _textSecondary, borderColor, successColor, dangerColor) {
1493
+ const ctx = this.ctx;
1494
+ const timelineWidth = width - labelWidth - 20;
1495
+ ctx.fillStyle = textColor;
1496
+ ctx.font = "11px sans-serif";
1497
+ ctx.textAlign = "left";
1498
+ ctx.fillText(taskId, 10, y + 15);
1499
+ const task = this._tasks.get(taskId);
1500
+ const driver = (task == null ? void 0 : task.driver) === "main" ? "M" : "W";
1501
+ const driverColor = driver === "W" ? "#22c55e" : "#f59e0b";
1502
+ ctx.fillStyle = driverColor;
1503
+ ctx.font = "9px monospace";
1504
+ ctx.fillText(`[${driver}]`, 10, y + 28);
1505
+ ctx.strokeStyle = borderColor;
1506
+ ctx.beginPath();
1507
+ ctx.moveTo(0, y + 35);
1508
+ ctx.lineTo(width, y + 35);
1509
+ ctx.stroke();
1510
+ const logs = this._history.get(taskId) || [];
1511
+ logs.forEach((log) => {
1512
+ if (log.timestamp < startTime || log.timestamp > endTime) return;
1513
+ const x = labelWidth + (log.timestamp - startTime) / this.timeRange * timelineWidth;
1514
+ const duration = log.duration;
1515
+ if (duration < 10) {
1516
+ ctx.fillStyle = log.success ? successColor : dangerColor;
1517
+ ctx.beginPath();
1518
+ ctx.arc(x, y + 15, 3, 0, Math.PI * 2);
1519
+ ctx.fill();
1520
+ } else {
1521
+ const barWidth = Math.max(2, duration / this.timeRange * timelineWidth);
1522
+ ctx.fillStyle = log.success ? successColor : dangerColor;
1523
+ ctx.globalAlpha = 0.7;
1524
+ ctx.fillRect(x, y + 5, barWidth, 20);
1525
+ ctx.globalAlpha = 1;
1526
+ }
1527
+ });
1528
+ }
1529
+ drawLegend(_width, height, textSecondary, successColor) {
1530
+ const ctx = this.ctx;
1531
+ const legendY = height - 25;
1532
+ ctx.font = "10px sans-serif";
1533
+ ctx.textAlign = "left";
1534
+ ctx.fillStyle = textSecondary;
1535
+ let x = 10;
1536
+ ctx.fillText(`${t("timeline.legend")}:`, x, legendY);
1537
+ x += 50;
1538
+ ctx.fillStyle = successColor;
1539
+ ctx.beginPath();
1540
+ ctx.arc(x, legendY - 4, 3, 0, Math.PI * 2);
1541
+ ctx.fill();
1542
+ ctx.fillStyle = textSecondary;
1543
+ ctx.fillText(t("timeline.instant"), x + 10, legendY);
1544
+ x += 60;
1545
+ ctx.fillStyle = successColor;
1546
+ ctx.globalAlpha = 0.7;
1547
+ ctx.fillRect(x, legendY - 8, 15, 10);
1548
+ ctx.globalAlpha = 1;
1549
+ ctx.fillStyle = textSecondary;
1550
+ ctx.fillText(t("timeline.duration"), x + 20, legendY);
1551
+ x += 80;
1552
+ ctx.fillText(`[W] ${t("timeline.workerDriver")}`, x, legendY);
1553
+ x += 110;
1554
+ ctx.fillText(`[M] ${t("timeline.mainDriver")}`, x, legendY);
1555
+ }
1556
+ render() {
1557
+ this._shadow.innerHTML = `
1558
+ <style>
1559
+ ${themeStyles}
1560
+ :host {
1561
+ display: flex;
1562
+ flex-direction: column;
1563
+ height: 100%;
1564
+ background: var(--hs-bg);
1565
+ overflow: hidden;
1566
+ }
1567
+ .controls {
1568
+ display: flex;
1569
+ justify-content: flex-end;
1570
+ align-items: center;
1571
+ padding: 12px 16px;
1572
+ gap: 8px;
1573
+ border-bottom: 1px solid var(--hs-border);
1574
+ flex-shrink: 0;
1575
+ }
1576
+ .zoom-label {
1577
+ font-size: 11px;
1578
+ color: var(--hs-text-secondary);
1579
+ }
1580
+ .zoom-btn {
1581
+ background: var(--hs-bg-secondary);
1582
+ border: 1px solid var(--hs-border);
1583
+ color: var(--hs-text);
1584
+ width: 24px;
1585
+ height: 24px;
1586
+ border-radius: 4px;
1587
+ cursor: pointer;
1588
+ display: flex;
1589
+ align-items: center;
1590
+ justify-content: center;
1591
+ }
1592
+ .zoom-btn:hover {
1593
+ background: var(--hs-primary);
1594
+ color: white;
1595
+ }
1596
+ .zoom-slider {
1597
+ width: 100px;
1598
+ }
1599
+ .canvas-container {
1600
+ flex: 1;
1601
+ overflow: auto;
1602
+ position: relative;
1603
+ }
1604
+ canvas {
1605
+ display: block;
1606
+ }
1607
+ </style>
1608
+ <div class="controls">
1609
+ <span class="zoom-label">Zoom:</span>
1610
+ <button class="zoom-btn zoom-out">-</button>
1611
+ <input type="range" class="zoom-slider" min="0.5" max="5" step="0.5" value="1">
1612
+ <button class="zoom-btn zoom-in">+</button>
1613
+ </div>
1614
+ <div class="canvas-container">
1615
+ <canvas></canvas>
1616
+ </div>
1617
+ `;
1618
+ }
1619
+ }
1620
+ customElements.define("hs-timeline", Timeline);
1621
+ class Resizer extends HTMLElement {
1622
+ constructor() {
1623
+ super();
1624
+ __publicField(this, "_shadow");
1625
+ __publicField(this, "startX", 0);
1626
+ __publicField(this, "startY", 0);
1627
+ __publicField(this, "startWidth", 0);
1628
+ __publicField(this, "startHeight", 0);
1629
+ __publicField(this, "panel", null);
1630
+ __publicField(this, "mode", "right");
1631
+ this._shadow = this.attachShadow({ mode: "open" });
1632
+ }
1633
+ // 检测是否为移动端
1634
+ isMobile() {
1635
+ return window.innerWidth <= 480;
1636
+ }
1637
+ connectedCallback() {
1638
+ this.render();
1639
+ this.addEventListeners();
1640
+ this.panel = this.closest(".panel");
1641
+ if (this.panel && this.panel.classList.contains("dock-bottom")) {
1642
+ this.mode = "bottom";
1643
+ }
1644
+ }
1645
+ addEventListeners() {
1646
+ const handle = this._shadow.querySelector(".handle");
1647
+ const onMouseMove = (e) => {
1648
+ if (!this.panel || this.isMobile()) return;
1649
+ if (this.mode === "right") {
1650
+ const dx = this.startX - e.clientX;
1651
+ const newWidth = Math.max(300, Math.min(window.innerWidth - 50, this.startWidth + dx));
1652
+ this.panel.style.width = `${newWidth}px`;
1653
+ this.dispatchEvent(new CustomEvent("resize", { detail: { width: newWidth }, bubbles: true, composed: true }));
1654
+ } else {
1655
+ const newHeight = Math.max(200, Math.min(window.innerHeight - 50, this.startHeight + (this.startY - e.clientY)));
1656
+ this.panel.style.height = `${newHeight}px`;
1657
+ this.dispatchEvent(new CustomEvent("resize", { detail: { height: newHeight }, bubbles: true, composed: true }));
1658
+ }
1659
+ };
1660
+ const onMouseUp = () => {
1661
+ document.removeEventListener("mousemove", onMouseMove);
1662
+ document.removeEventListener("mouseup", onMouseUp);
1663
+ document.body.style.userSelect = "";
1664
+ document.body.style.cursor = "";
1665
+ };
1666
+ handle.addEventListener("mousedown", (e) => {
1667
+ if (this.isMobile()) return;
1668
+ this.startX = e.clientX;
1669
+ this.startY = e.clientY;
1670
+ if (this.panel) {
1671
+ if (this.panel.classList.contains("dock-bottom")) {
1672
+ this.mode = "bottom";
1673
+ this.startHeight = this.panel.offsetHeight;
1674
+ } else {
1675
+ this.mode = "right";
1676
+ this.startWidth = this.panel.offsetWidth;
1677
+ }
1678
+ }
1679
+ document.addEventListener("mousemove", onMouseMove);
1680
+ document.addEventListener("mouseup", onMouseUp);
1681
+ document.body.style.userSelect = "none";
1682
+ document.body.style.cursor = this.mode === "right" ? "col-resize" : "row-resize";
1683
+ });
1684
+ }
1685
+ render() {
1686
+ this._shadow.innerHTML = `
1687
+ <style>
1688
+ ${themeStyles}
1689
+ :host {
1690
+ display: block;
1691
+ z-index: 100;
1692
+ }
1693
+ .handle {
1694
+ background: transparent;
1695
+ transition: background 0.2s;
1696
+ }
1697
+ .handle:hover {
1698
+ background: var(--hs-primary);
1699
+ }
1700
+
1701
+ /* Right Dock Mode (Vertical Handle on Left) */
1702
+ :host-context(.dock-right) .handle {
1703
+ width: 4px;
1704
+ height: 100%;
1705
+ cursor: col-resize;
1706
+ position: absolute;
1707
+ left: 0;
1708
+ top: 0;
1709
+ }
1710
+
1711
+ /* Bottom Dock Mode (Horizontal Handle on Top) */
1712
+ :host-context(.dock-bottom) .handle {
1713
+ width: 100%;
1714
+ height: 4px;
1715
+ cursor: row-resize;
1716
+ position: absolute;
1717
+ top: 0;
1718
+ left: 0;
1719
+ }
1720
+
1721
+ /* 移动端隐藏拖拽手柄 */
1722
+ @media (max-width: 480px) {
1723
+ .handle {
1724
+ display: none;
1725
+ }
1726
+ }
1727
+ </style>
1728
+ <div class="handle"></div>
1729
+ `;
1730
+ }
1731
+ }
1732
+ customElements.define("hs-resizer", Resizer);
1733
+ class DevTools extends HTMLElement {
1734
+ constructor() {
1735
+ super();
1736
+ __publicField(this, "_shadow");
1737
+ __publicField(this, "store");
1738
+ __publicField(this, "scheduler");
1739
+ __publicField(this, "rAFId");
1740
+ __publicField(this, "lastTime", 0);
1741
+ __publicField(this, "$panel");
1742
+ __publicField(this, "$header");
1743
+ __publicField(this, "$taskList");
1744
+ __publicField(this, "$taskDetail");
1745
+ __publicField(this, "$timeline");
1746
+ __publicField(this, "$trigger");
1747
+ this._shadow = this.attachShadow({ mode: "open" });
1748
+ this.store = new DevToolsStore();
1749
+ }
1750
+ connectedCallback() {
1751
+ const languageAttr = this.getAttribute("language");
1752
+ if (languageAttr === "en" || languageAttr === "zh") {
1753
+ this.store.setLanguageSync(languageAttr);
1754
+ }
1755
+ this.render();
1756
+ this.cacheDom();
1757
+ this.bindStore();
1758
+ const dockAttr = this.getAttribute("dock");
1759
+ console.log("[DevTools] dock attribute:", dockAttr);
1760
+ if (dockAttr === "bottom") {
1761
+ this.store.setDockPosition("bottom");
1762
+ }
1763
+ const themeAttr = this.getAttribute("theme");
1764
+ if (themeAttr === "light" || themeAttr === "dark" || themeAttr === "auto") {
1765
+ this.store.setTheme(themeAttr);
1766
+ }
1767
+ if (languageAttr === "en" || languageAttr === "zh") {
1768
+ this.store.setLanguage(languageAttr);
1769
+ }
1770
+ const triggerBg = this.getAttribute("trigger-bg");
1771
+ const triggerColor = this.getAttribute("trigger-color");
1772
+ const triggerPosition = this.getAttribute("trigger-position");
1773
+ console.log("[DevTools] trigger attrs:", { triggerBg, triggerColor, triggerPosition });
1774
+ if (triggerBg) this.$trigger.setAttribute("bg-color", triggerBg);
1775
+ if (triggerColor) this.$trigger.setAttribute("text-color", triggerColor);
1776
+ if (triggerPosition) this.$trigger.setAttribute("position", triggerPosition);
1777
+ const defaultZoom = this.getAttribute("default-zoom");
1778
+ if (defaultZoom) {
1779
+ const zoom = parseFloat(defaultZoom);
1780
+ if (!isNaN(zoom) && zoom >= 0.5 && zoom <= 5) {
1781
+ this.$timeline.defaultZoom = zoom;
1782
+ }
1783
+ }
1784
+ this.addEventListeners();
1785
+ this.startLoop();
1786
+ }
1787
+ disconnectedCallback() {
1788
+ if (this.rAFId) cancelAnimationFrame(this.rAFId);
1789
+ }
1790
+ setScheduler(api) {
1791
+ this.scheduler = api;
1792
+ this.store.setScheduler(api);
1793
+ const tasks = this.scheduler.getTasks();
1794
+ tasks.forEach((t2) => this.store.updateTask(t2));
1795
+ const isRunning = this.scheduler.isRunning();
1796
+ this.store.setSchedulerRunning(isRunning);
1797
+ const refreshTasks = () => {
1798
+ var _a;
1799
+ const allTasks = ((_a = this.scheduler) == null ? void 0 : _a.getTasks()) || [];
1800
+ allTasks.forEach((t2) => this.store.updateTask(t2));
1801
+ };
1802
+ this.scheduler.on(SchedulerEvents.TASK_REGISTERED, refreshTasks);
1803
+ this.scheduler.on(SchedulerEvents.TASK_UPDATED, (payload) => {
1804
+ console.log("[DevTools] task_updated event:", payload);
1805
+ refreshTasks();
1806
+ });
1807
+ this.scheduler.on(SchedulerEvents.TASK_STARTED, refreshTasks);
1808
+ this.scheduler.on(SchedulerEvents.TASK_REMOVED, refreshTasks);
1809
+ this.scheduler.on(SchedulerEvents.TASK_STOPPED, (payload) => {
1810
+ console.log("[DevTools] task_stopped event:", payload);
1811
+ refreshTasks();
1812
+ });
1813
+ this.scheduler.on(SchedulerEvents.SCHEDULER_STARTED, () => {
1814
+ console.log("[DevTools] scheduler_started event");
1815
+ this.store.setSchedulerRunning(true);
1816
+ refreshTasks();
1817
+ });
1818
+ this.scheduler.on(SchedulerEvents.SCHEDULER_STOPPED, () => {
1819
+ console.log("[DevTools] scheduler_stopped event");
1820
+ this.store.setSchedulerRunning(false);
1821
+ refreshTasks();
1822
+ });
1823
+ this.scheduler.on(SchedulerEvents.TASK_COMPLETED, (payload) => {
1824
+ var _a;
1825
+ refreshTasks();
1826
+ if (payload && payload.taskId) {
1827
+ this.store.addHistory(payload.taskId, {
1828
+ timestamp: ((_a = payload.task) == null ? void 0 : _a.lastRun) || Date.now(),
1829
+ duration: payload.duration || 0,
1830
+ success: true,
1831
+ error: null
1832
+ });
1833
+ }
1834
+ });
1835
+ this.scheduler.on(SchedulerEvents.TASK_FAILED, (payload) => {
1836
+ var _a;
1837
+ refreshTasks();
1838
+ if (payload && payload.taskId) {
1839
+ this.store.addHistory(payload.taskId, {
1840
+ timestamp: ((_a = payload.task) == null ? void 0 : _a.lastRun) || Date.now(),
1841
+ duration: payload.duration || 0,
1842
+ success: false,
1843
+ error: payload.error || "Unknown error"
1844
+ });
1845
+ }
1846
+ });
1847
+ setInterval(() => {
1848
+ if (this.store.getState().isOpen) {
1849
+ refreshTasks();
1850
+ }
1851
+ }, 500);
1852
+ }
1853
+ cacheDom() {
1854
+ this.$panel = this._shadow.querySelector(".panel");
1855
+ this.$header = this._shadow.querySelector("hs-task-header");
1856
+ this.$taskList = this._shadow.querySelector("hs-task-list");
1857
+ this.$taskDetail = this._shadow.querySelector("hs-task-detail");
1858
+ this.$timeline = this._shadow.querySelector("hs-timeline");
1859
+ this.$trigger = this._shadow.querySelector("hs-floating-trigger");
1860
+ }
1861
+ bindStore() {
1862
+ try {
1863
+ const saved = localStorage.getItem("hs-panel-size");
1864
+ if (saved) {
1865
+ this.store.setPanelSize(JSON.parse(saved));
1866
+ }
1867
+ } catch (e) {
1868
+ }
1869
+ this.store.subscribe("isOpen", (isOpen) => {
1870
+ const pos = this.store.getState().dockPosition;
1871
+ const size = this.store.getState().panelSize;
1872
+ if (isOpen) {
1873
+ this.$panel.classList.add("open");
1874
+ this.$trigger.style.display = "none";
1875
+ if (pos === "right") {
1876
+ this.$panel.style.right = "0";
1877
+ } else {
1878
+ this.$panel.style.bottom = "0";
1879
+ }
1880
+ } else {
1881
+ this.$panel.classList.remove("open");
1882
+ this.$trigger.style.display = "block";
1883
+ if (pos === "right") {
1884
+ this.$panel.style.right = `-${size.width}px`;
1885
+ } else {
1886
+ this.$panel.style.bottom = `-${size.height}px`;
1887
+ }
1888
+ }
1889
+ });
1890
+ this.store.subscribe("theme", (theme) => {
1891
+ const actualTheme = theme === "auto" ? "light" : theme;
1892
+ this.setAttribute("theme", actualTheme);
1893
+ this.$header.setAttribute("theme", actualTheme);
1894
+ this.$taskList.setAttribute("theme", actualTheme);
1895
+ this.$taskDetail.setAttribute("theme", actualTheme);
1896
+ this.$timeline.setAttribute("theme", actualTheme);
1897
+ this.$header.theme = theme;
1898
+ });
1899
+ this.store.subscribe("tasks", (tasks) => {
1900
+ this.$taskList.tasks = tasks;
1901
+ this.$timeline.data = { tasks, history: this.store.getState().history };
1902
+ let active = 0;
1903
+ tasks.forEach((t2) => {
1904
+ if (t2.status === "idle" || t2.status === "running") active++;
1905
+ });
1906
+ this.$header.stats = { active, total: tasks.size };
1907
+ const selectedId = this.store.getState().selectedTaskId;
1908
+ if (selectedId && tasks.has(selectedId)) {
1909
+ this.$taskDetail.task = tasks.get(selectedId) || null;
1910
+ }
1911
+ });
1912
+ this.store.subscribe("history", (map) => {
1913
+ this.$timeline.data = { tasks: this.store.getState().tasks, history: map };
1914
+ const id = this.store.getState().selectedTaskId;
1915
+ if (id) {
1916
+ this.$taskDetail.history = map.get(id) || [];
1917
+ }
1918
+ });
1919
+ this.store.subscribe("selectedTaskId", (id) => {
1920
+ if (this.store.getState().activeTab !== "tasks") return;
1921
+ if (id) {
1922
+ this.$taskList.style.display = "none";
1923
+ this.$taskDetail.style.display = "block";
1924
+ const task = this.store.getState().tasks.get(id);
1925
+ const history = this.store.getState().history.get(id);
1926
+ this.$taskDetail.task = task || null;
1927
+ this.$taskDetail.history = history || [];
1928
+ } else {
1929
+ this.$taskList.style.display = "block";
1930
+ this.$taskDetail.style.display = "none";
1931
+ }
1932
+ });
1933
+ this.store.subscribe("activeTab", (tab) => {
1934
+ this.$header.activeTab = tab;
1935
+ if (tab === "tasks") {
1936
+ this.$taskList.style.display = "block";
1937
+ this.$timeline.style.display = "none";
1938
+ if (this.store.getState().selectedTaskId) {
1939
+ this.$taskList.style.display = "none";
1940
+ this.$taskDetail.style.display = "block";
1941
+ } else {
1942
+ this.$taskList.style.display = "block";
1943
+ this.$taskDetail.style.display = "none";
1944
+ }
1945
+ } else {
1946
+ this.$taskList.style.display = "none";
1947
+ this.$taskDetail.style.display = "none";
1948
+ this.$timeline.style.display = "block";
1949
+ }
1950
+ });
1951
+ this.store.subscribe("dockPosition", (pos) => {
1952
+ this.$header.dockPosition = pos;
1953
+ const size = this.store.getState().panelSize;
1954
+ const isOpen = this.store.getState().isOpen;
1955
+ const isMobile = window.innerWidth <= 480;
1956
+ if (pos === "right") {
1957
+ this.$panel.classList.add("dock-right");
1958
+ this.$panel.classList.remove("dock-bottom");
1959
+ const width = isMobile ? window.innerWidth : size.width;
1960
+ this.$panel.style.width = isMobile ? "100vw" : `${width}px`;
1961
+ this.$panel.style.height = "100vh";
1962
+ this.$panel.style.bottom = "";
1963
+ this.$panel.style.right = isOpen ? "0" : `-${width}px`;
1964
+ } else {
1965
+ this.$panel.classList.add("dock-bottom");
1966
+ this.$panel.classList.remove("dock-right");
1967
+ this.$panel.style.width = "100%";
1968
+ const height = isMobile ? "50vh" : `${size.height}px`;
1969
+ this.$panel.style.height = height;
1970
+ this.$panel.style.right = "";
1971
+ this.$panel.style.bottom = isOpen ? "0" : isMobile ? "-50vh" : `-${size.height}px`;
1972
+ }
1973
+ });
1974
+ this.store.subscribe("panelSize", (size) => {
1975
+ const pos = this.store.getState().dockPosition;
1976
+ if (pos === "right" && size.width) {
1977
+ this.$panel.style.width = `${size.width}px`;
1978
+ this.$panel.style.right = this.store.getState().isOpen ? "0" : `-${size.width}px`;
1979
+ } else if (pos === "bottom" && size.height) {
1980
+ this.$panel.style.height = `${size.height}px`;
1981
+ this.$panel.style.bottom = this.store.getState().isOpen ? "0" : `-${size.height}px`;
1982
+ }
1983
+ });
1984
+ this.store.subscribe("language", (lang) => {
1985
+ var _a, _b, _c, _d;
1986
+ this.$header.language = lang;
1987
+ this.$taskList.updateHeaders();
1988
+ (_b = (_a = this.$taskDetail).updateTexts) == null ? void 0 : _b.call(_a);
1989
+ (_d = (_c = this.$timeline).updateTexts) == null ? void 0 : _d.call(_c);
1990
+ });
1991
+ this.store.subscribe("filterText", (text) => {
1992
+ const tasks = this.store.getState().tasks;
1993
+ this.$taskList.filter(text, tasks);
1994
+ });
1995
+ this.store.subscribe("schedulerRunning", (running) => {
1996
+ this.$header.schedulerRunning = running;
1997
+ });
1998
+ }
1999
+ addEventListeners() {
2000
+ this.$trigger.addEventListener("toggle", () => {
2001
+ this.store.toggle();
2002
+ });
2003
+ this.$header.addEventListener("close", (e) => {
2004
+ e.stopPropagation();
2005
+ this.store.toggle();
2006
+ });
2007
+ this.$header.addEventListener("dock-toggle", () => {
2008
+ const current = this.store.getState().dockPosition;
2009
+ this.store.setDockPosition(current === "right" ? "bottom" : "right");
2010
+ });
2011
+ this.$header.addEventListener("theme-toggle", (e) => {
2012
+ const theme = e.detail;
2013
+ this.store.setTheme(theme);
2014
+ });
2015
+ this.$header.addEventListener("lang-toggle", (e) => {
2016
+ const lang = e.detail;
2017
+ this.store.setLanguage(lang);
2018
+ });
2019
+ this.$header.addEventListener("tab-change", (e) => {
2020
+ const tab = e.detail;
2021
+ this.store.setTab(tab);
2022
+ });
2023
+ this.$header.addEventListener("search", (e) => {
2024
+ const text = e.detail;
2025
+ this.store.setFilterText(text);
2026
+ });
2027
+ this.addEventListener("resize", (e) => {
2028
+ const size = e.detail;
2029
+ this.store.setPanelSize(size);
2030
+ });
2031
+ this.$taskList.addEventListener("task-select", (e) => {
2032
+ const id = e.detail;
2033
+ this.store.selectTask(id);
2034
+ });
2035
+ this.$taskDetail.addEventListener("back", () => {
2036
+ this.store.selectTask(null);
2037
+ });
2038
+ this.$taskList.addEventListener("task-action", (e) => {
2039
+ const { action, id } = e.detail;
2040
+ console.log("[DevTools] task-action:", action, id);
2041
+ switch (action) {
2042
+ case "trigger":
2043
+ this.store.triggerTask(id);
2044
+ break;
2045
+ case "stop":
2046
+ this.store.stopTask(id);
2047
+ break;
2048
+ case "start":
2049
+ this.store.startTask(id);
2050
+ break;
2051
+ case "remove":
2052
+ if (confirm(`Remove task "${id}"?`)) {
2053
+ this.store.removeTask(id);
2054
+ }
2055
+ break;
2056
+ }
2057
+ });
2058
+ }
2059
+ startLoop() {
2060
+ const loop = (time) => {
2061
+ const delta = time - this.lastTime;
2062
+ this.lastTime = time;
2063
+ const fps = 1e3 / delta;
2064
+ if (this.$header) {
2065
+ this.$header.fps = fps;
2066
+ }
2067
+ this.rAFId = requestAnimationFrame(loop);
2068
+ };
2069
+ this.rAFId = requestAnimationFrame(loop);
2070
+ }
2071
+ render() {
2072
+ this._shadow.innerHTML = `
2073
+ <style>
2074
+ ${themeStyles}
2075
+ :host {
2076
+ font-family: var(--hs-font-family);
2077
+ font-size: var(--hs-font-size);
2078
+ color: var(--hs-text);
2079
+ line-height: var(--hs-line-height);
2080
+ }
2081
+ .panel {
2082
+ position: fixed;
2083
+ background: var(--hs-bg);
2084
+ box-shadow: var(--hs-shadow);
2085
+ z-index: var(--hs-z-index);
2086
+ transition: all 0.3s ease;
2087
+ display: flex;
2088
+ flex-direction: column;
2089
+ border-left: 1px solid var(--hs-border);
2090
+ }
2091
+ /* Default Right Dock */
2092
+ .panel.dock-right {
2093
+ top: 0;
2094
+ right: -500px;
2095
+ width: 500px;
2096
+ height: 100vh;
2097
+ border-left: 1px solid var(--hs-border);
2098
+ border-top: none;
2099
+ }
2100
+
2101
+ /* Bottom Dock */
2102
+ .panel.dock-bottom {
2103
+ bottom: -50vh;
2104
+ left: 0;
2105
+ width: 100%;
2106
+ height: 50vh;
2107
+ max-height: 50vh;
2108
+ border-top: 1px solid var(--hs-border);
2109
+ border-left: none;
2110
+ }
2111
+
2112
+ .content {
2113
+ flex: 1;
2114
+ overflow: hidden;
2115
+ position: relative;
2116
+ display: flex;
2117
+ flex-direction: column;
2118
+ }
2119
+ .content > * {
2120
+ flex: 1;
2121
+ min-height: 0;
2122
+ }
2123
+
2124
+ /* Mobile - 固定尺寸,禁用拖拽 */
2125
+ @media (max-width: 480px) {
2126
+ .panel.dock-right {
2127
+ width: 100vw !important;
2128
+ right: -100vw;
2129
+ }
2130
+ .panel.dock-right.open {
2131
+ right: 0;
2132
+ }
2133
+ .panel.dock-bottom {
2134
+ height: 50vh !important;
2135
+ max-height: 50vh !important;
2136
+ bottom: -50vh;
2137
+ }
2138
+ .panel.dock-bottom.open {
2139
+ bottom: 0;
2140
+ }
2141
+ }
2142
+ </style>
2143
+
2144
+ <hs-floating-trigger></hs-floating-trigger>
2145
+
2146
+ <div class="panel dock-right">
2147
+ <hs-resizer></hs-resizer>
2148
+ <hs-task-header></hs-task-header>
2149
+ <div class="content">
2150
+ <hs-task-list></hs-task-list>
2151
+ <hs-task-detail style="display:none"></hs-task-detail>
2152
+ <hs-timeline style="display:none"></hs-timeline>
2153
+ </div>
2154
+ </div>
2155
+ `;
2156
+ }
2157
+ }
2158
+ customElements.define("hs-devtools", DevTools);
2159
+ export {
2160
+ DevTools
2161
+ };