@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.
Files changed (2) hide show
  1. package/client.js +111 -2
  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
- null,
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.strokeStyle = "rgba(99, 149, 255, 0.07)";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@philomelcc/dsh-workbench",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Workbench shell for DeepSeek Harness: a view/page module API, shared UI primitives and a top-bar switcher.",
6
6
  "license": "MIT",