@philomelcc/dsh-workbench 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.js +111 -2
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -434,7 +434,96 @@ window.__ModuleLoader__.load({
|
|
|
434
434
|
h('path', { d: 'M4 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-4 3.5V6z', stroke: 'currentColor', strokeWidth: 1.6, strokeLinejoin: 'round' }));
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
// ---------- theme switcher ----------
|
|
438
|
+
// `paper` builds on the light palette and overrides the alias layer only.
|
|
439
|
+
const PAPER_THEME = {
|
|
440
|
+
id: 'paper',
|
|
441
|
+
colorScheme: 'light',
|
|
442
|
+
tokens: {
|
|
443
|
+
'--dsw-alias-bg-base': '#f4efe3',
|
|
444
|
+
'--dsw-alias-bg-layer-1': '#efe9db',
|
|
445
|
+
'--dsw-alias-bg-layer-2': '#e7dfcd',
|
|
446
|
+
'--dsw-alias-bg-overlay': '#fbf7ee',
|
|
447
|
+
'--dsw-alias-border-l1': '#d8cfba',
|
|
448
|
+
'--dsw-alias-border-l2': '#c6bba2',
|
|
449
|
+
'--dsw-alias-brand-primary': '#8a6a45',
|
|
450
|
+
'--dsw-alias-label-primary': '#3b342a',
|
|
451
|
+
'--dsw-alias-label-secondary': '#6f6557',
|
|
452
|
+
'--dsw-alias-state-error-primary': '#a34a3c',
|
|
453
|
+
'--dsw-alias-state-warn-primary': '#a8792c',
|
|
454
|
+
'--dsw-alias-state-success-primary': '#4f7a5a',
|
|
455
|
+
'--dsw-alias-state-idle-primary': '#a49a89',
|
|
456
|
+
'--dsw-specific-sidebar-fill': '#efe9db',
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// Swatches double as both the current-state indicator and the toggle.
|
|
461
|
+
const THEME_SWATCHES = [
|
|
462
|
+
{ id: 'dark', label: '深色', color: '#15171a' },
|
|
463
|
+
{ id: 'light', label: '浅色', color: '#f7f8fa' },
|
|
464
|
+
{ id: 'paper', label: '纸质', color: '#f4efe3' },
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
function createThemeSwitcher(ctx) {
|
|
468
|
+
function ThemeSwitcher() {
|
|
469
|
+
const theme = typeof ctx.get === 'function' ? ctx.get('theme') : ctx.theme;
|
|
470
|
+
const read = () => {
|
|
471
|
+
try {
|
|
472
|
+
const snapshot = theme && typeof theme.getTheme === 'function' ? theme.getTheme() : null;
|
|
473
|
+
return snapshot ? snapshot.preference : null;
|
|
474
|
+
} catch (_error) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const [preference, setPreference] = React.useState(read);
|
|
479
|
+
|
|
480
|
+
React.useEffect(() => {
|
|
481
|
+
if (!theme || typeof theme.getTheme !== 'function' || typeof ctx.on !== 'function') return undefined;
|
|
482
|
+
let off = null;
|
|
483
|
+
try {
|
|
484
|
+
off = ctx.on('theme/change', () => setPreference(read()));
|
|
485
|
+
} catch (_error) {
|
|
486
|
+
off = null;
|
|
487
|
+
}
|
|
488
|
+
return () => { if (typeof off === 'function') off(); };
|
|
489
|
+
}, []);
|
|
490
|
+
|
|
491
|
+
return h('div', {
|
|
492
|
+
style: { display: 'inline-flex', alignItems: 'center', gap: 7, flex: '0 0 auto' },
|
|
493
|
+
title: '主题',
|
|
494
|
+
},
|
|
495
|
+
THEME_SWATCHES.map((swatch) => {
|
|
496
|
+
const active = preference === swatch.id;
|
|
497
|
+
return h('button', {
|
|
498
|
+
key: swatch.id,
|
|
499
|
+
type: 'button',
|
|
500
|
+
title: swatch.label,
|
|
501
|
+
'aria-label': swatch.label,
|
|
502
|
+
'aria-pressed': active ? 'true' : 'false',
|
|
503
|
+
onClick: () => {
|
|
504
|
+
if (!theme || typeof theme.setTheme !== 'function') return;
|
|
505
|
+
try { theme.setTheme(swatch.id); } catch (error) { console.warn('[workbench] setTheme failed', error); }
|
|
506
|
+
},
|
|
507
|
+
style: {
|
|
508
|
+
width: 15, height: 15, padding: 0, borderRadius: '50%', flex: '0 0 auto',
|
|
509
|
+
background: swatch.color,
|
|
510
|
+
border: '1px solid ' + (active ? 'var(--dsw-alias-brand-primary)' : 'var(--dsw-alias-border-l2)'),
|
|
511
|
+
boxShadow: active
|
|
512
|
+
? '0 0 0 2px var(--dsw-alias-bg-base), 0 0 0 3px var(--dsw-alias-brand-primary)'
|
|
513
|
+
: 'none',
|
|
514
|
+
cursor: 'pointer',
|
|
515
|
+
transition: 'box-shadow .12s, border-color .12s',
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
}),
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
return ThemeSwitcher;
|
|
522
|
+
}
|
|
523
|
+
|
|
437
524
|
function createTopBar(ctx, balance) {
|
|
525
|
+
const ThemeSwitcher = createThemeSwitcher(ctx);
|
|
526
|
+
|
|
438
527
|
function BalanceChip() {
|
|
439
528
|
const snap = React.useSyncExternalStore(balance.subscribe, balance.getSnapshot);
|
|
440
529
|
const [hover, setHover] = React.useState(false);
|
|
@@ -637,7 +726,7 @@ window.__ModuleLoader__.load({
|
|
|
637
726
|
iconBtn('plugins', '插件', IconPlugins),
|
|
638
727
|
iconBtn('schedules', '自动化任务', IconClock),
|
|
639
728
|
),
|
|
640
|
-
|
|
729
|
+
h('div', { style: { flex: '0 0 auto', marginRight: 150 } }, h(ThemeSwitcher)),
|
|
641
730
|
),
|
|
642
731
|
h('div', { style: { height: 45, display: 'flex', alignItems: 'center', gap: 8, padding: '0 14px', borderTop: '1px solid var(--dsw-alias-border-l1)' } },
|
|
643
732
|
h('div', { ref: tabsRef, style: { position: 'relative', display: 'flex', alignItems: 'center', gap: 4, flex: 1, overflowX: 'auto', overflowY: 'hidden' } },
|
|
@@ -1116,8 +1205,17 @@ div[data-row-key^="session:"]:has(svg[data-state="ongoing"]) svg[data-state="ong
|
|
|
1116
1205
|
var r = radiusOf(width, height);
|
|
1117
1206
|
var total = perimeter(width, height);
|
|
1118
1207
|
var t = (now % ORBIT_MS) / ORBIT_MS;
|
|
1208
|
+
// 呼吸边框:2.8s 一个周期,和原来 CSS 动画同节奏;在 canvas 内画,
|
|
1209
|
+
// 所以只会有一条边(不会再和 CSS box-shadow 叠成双线)。
|
|
1210
|
+
var breath = 0.5 + 0.5 * Math.sin((now / 2800) * Math.PI * 2);
|
|
1211
|
+
var dim = entry.lights === 1 ? 0.35 : 1;
|
|
1212
|
+
var edgeAlpha = (0.12 + breath * 0.33) * dim;
|
|
1119
1213
|
|
|
1120
|
-
context.
|
|
1214
|
+
context.save();
|
|
1215
|
+
context.lineWidth = 1;
|
|
1216
|
+
context.strokeStyle = "rgba(99, 149, 255, " + edgeAlpha.toFixed(3) + ")";
|
|
1217
|
+
context.shadowBlur = (4 + breath * 10) * dim;
|
|
1218
|
+
context.shadowColor = "rgba(99, 149, 255, " + ((0.2 + breath * 0.35) * dim).toFixed(3) + ")";
|
|
1121
1219
|
context.beginPath();
|
|
1122
1220
|
if (typeof context.roundRect === "function") {
|
|
1123
1221
|
context.roundRect(0.5, 0.5, width - 1, height - 1, r);
|
|
@@ -1125,6 +1223,7 @@ div[data-row-key^="session:"]:has(svg[data-state="ongoing"]) svg[data-state="ong
|
|
|
1125
1223
|
context.rect(0.5, 0.5, width - 1, height - 1);
|
|
1126
1224
|
}
|
|
1127
1225
|
context.stroke();
|
|
1226
|
+
context.restore();
|
|
1128
1227
|
|
|
1129
1228
|
var count = Math.max(24, Math.ceil(total / STEP_PX));
|
|
1130
1229
|
for (var i = 0; i < count; i += 1) {
|
|
@@ -1230,6 +1329,16 @@ div[data-row-key^="session:"]:has(svg[data-state="ongoing"]) svg[data-state="ong
|
|
|
1230
1329
|
layoutService = ctx.get('layout') || null;
|
|
1231
1330
|
layoutGetter = () => ctx.get('layout') || null;
|
|
1232
1331
|
|
|
1332
|
+
// Extra palette on top of the built-in light/dark pair.
|
|
1333
|
+
try {
|
|
1334
|
+
const themeService = typeof ctx.get === 'function' ? ctx.get('theme') : null;
|
|
1335
|
+
if (themeService && typeof themeService.register === 'function') {
|
|
1336
|
+
ctx.effect(() => themeService.register(PAPER_THEME), 'workbench: paper theme');
|
|
1337
|
+
}
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
console.warn('[workbench] paper theme registration failed', error);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1233
1342
|
// The shell's own overview lives above the tab row: it is reachable from
|
|
1234
1343
|
// the top-bar "工作台" icon, so it registers a page without a tab.
|
|
1235
1344
|
ctx.effect(() => api.registerView({
|
package/package.json
CHANGED