@nsnanocat/preference-panes 1.2.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { ActionMenu } from "./ActionMenu.mjs";
2
2
  import { validValue } from "./boxjs.mjs";
3
3
  import { PreferencesClient } from "./client.mjs";
4
- import { fieldControl, element as node, requestConfirmation, requestURLNavigation, resourceURL, settingRow, statusView } from "./components.mjs";
4
+ import { fieldControl, element as node, requestConfirmation, requestURLNavigation, settingRow, statusView } from "./components.mjs";
5
5
  import { Navigation } from "./Navigation.mjs";
6
6
 
7
7
  /**
@@ -112,17 +112,17 @@ export class PreferencesPanel {
112
112
  const notify = event => {
113
113
  if (destroyed) return;
114
114
  let message;
115
- switch (true) {
116
- case event.kind === "error":
115
+ switch (event.kind === "error" ? "error" : event.operation) {
116
+ case "error":
117
117
  message = `操作失败:${event.message}`;
118
118
  break;
119
- case event.operation === "delete":
119
+ case "delete":
120
120
  message = "删除成功";
121
121
  break;
122
- case event.operation === "clearCaches":
122
+ case "clearCaches":
123
123
  message = "Caches 已清空";
124
124
  break;
125
- case event.operation === "reset":
125
+ case "reset":
126
126
  message = "设置已重置";
127
127
  break;
128
128
  default:
@@ -245,21 +245,6 @@ export class PreferencesPanel {
245
245
  });
246
246
  return queue;
247
247
  }
248
- const metadata = definition.metadata;
249
- if (metadata) {
250
- const info = node("div", "pp-module-info");
251
- const details = node("div", "pp-module-details");
252
- for (const description of [metadata.author, metadata.desc ?? metadata.description, ...(metadata.descs ?? [])]) if (description) details.append(node("p", "pp-description", description));
253
- if (metadata.repo) {
254
- const link = node("a", "pp-module-source", "项目主页");
255
- link.href = resourceURL(metadata.repo);
256
- link.target = "_blank";
257
- link.rel = "noopener noreferrer";
258
- details.append(link);
259
- }
260
- info.append(details);
261
- view.append(info);
262
- }
263
248
  for (const field of definition.fields) {
264
249
  const match = /^\[([^\]]+)\]\s*(.*)$/.exec(field.name);
265
250
  const group = match?.[1] ?? "通用";
@@ -300,8 +285,8 @@ export class PreferencesPanel {
300
285
  let write;
301
286
  let inputContainer = row;
302
287
  let eventName = "change";
303
- switch (true) {
304
- case field.control === "url": {
288
+ switch (field.control === "url" ? "url" : field.options ? (field.type === "array" ? "checkboxes" : "selects") : field.type) {
289
+ case "url": {
305
290
  const link = node("a", "pp-choice-link", "打开");
306
291
  link.setAttribute("aria-label", field.name);
307
292
  link.rel = "noopener noreferrer";
@@ -317,7 +302,7 @@ export class PreferencesPanel {
317
302
  });
318
303
  break;
319
304
  }
320
- case Boolean(field.options) && field.type !== "array": {
305
+ case "selects": {
321
306
  const select = node("select", "");
322
307
  select.setAttribute("aria-label", field.name);
323
308
  field.options.forEach((option, index) => {
@@ -332,7 +317,7 @@ export class PreferencesPanel {
332
317
  read = () => field.options[select.selectedIndex]?.key;
333
318
  break;
334
319
  }
335
- case field.type === "array" && Boolean(field.options): {
320
+ case "checkboxes": {
336
321
  const page = node("section", "pp-choice-page");
337
322
  if (field.description) page.append(node("p", "pp-description", field.description));
338
323
  const choices = node("div", "pp-rows");
@@ -376,7 +361,7 @@ export class PreferencesPanel {
376
361
  };
377
362
  break;
378
363
  }
379
- case field.type === "boolean": {
364
+ case "boolean": {
380
365
  const toggle = node("input", "pp-switch");
381
366
  toggle.type = "checkbox";
382
367
  toggle.setAttribute("switch", "");
@@ -414,7 +399,12 @@ export class PreferencesPanel {
414
399
  growingInputs.push(grow);
415
400
  }
416
401
  eventName = "input";
417
- if (!multiline) input.type = field.type === "number" ? "number" : "text";
402
+ if (!multiline) {
403
+ input.type = field.type === "number" ? "number" : "text";
404
+ input.autocapitalize = "off";
405
+ input.setAttribute("autocorrect", "off");
406
+ input.spellcheck = false;
407
+ }
418
408
  write = value => {
419
409
  input.value = field.type === "array" ? JSON.stringify(value ?? []) : (value ?? "");
420
410
  grow();
@@ -517,7 +507,9 @@ export class PreferencesPanel {
517
507
  });
518
508
  handlers.set("clearCaches", async () => {
519
509
  if (saving) return;
520
- if (!(await requestConfirmation(window, `清空 ${active} 的全部 Caches?`)) || destroyed || saving) return;
510
+ const shared = definition.cachePath && definition.cachePath !== `@${definition.storageKey}.${active}.Caches`;
511
+ const message = shared ? `清空共享缓存 ${definition.cachePath}?使用该缓存的其它模块也会受影响。` : `清空 ${active} 的全部 Caches?`;
512
+ if (!(await requestConfirmation(window, message)) || destroyed || saving) return;
521
513
  return perform(
522
514
  () => client.clearCaches(),
523
515
  () => {
@@ -527,7 +519,8 @@ export class PreferencesPanel {
527
519
  });
528
520
  handlers.set("reset", async () => {
529
521
  if (saving) return;
530
- if (!(await requestConfirmation(window, `重置 ${active} 的设置?这将删除该模块的 Settings、Caches 和其它持久化数据。`)) || destroyed || saving) return;
522
+ const message = definition.resetPaths ? `重置 ${definition.metadata?.name ?? active} 的设置?只删除本页面的设置,保留共享存储中的其它设置和缓存。` : `重置 ${active} 的设置?这将删除该模块的 Settings、Caches 和其它持久化数据。`;
523
+ if (!(await requestConfirmation(window, message)) || destroyed || saving) return;
531
524
  return perform(() => client.reset(), controls);
532
525
  });
533
526
  navigation?.destroy();
@@ -1,6 +1,6 @@
1
1
  /* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。
2
2
  * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */
3
- .pp-panel {
3
+ :root {
4
4
  --pp-text: #18191c;
5
5
  --pp-background: #f6f7f8;
6
6
  --pp-surface: #fff;
@@ -9,6 +9,10 @@
9
9
  --pp-muted: #797f87;
10
10
  --pp-accent: #1677ff;
11
11
  --pp-warning: #9a6700;
12
+ --pp-host-height: 0px;
13
+ --pp-keyboard-height: 0px;
14
+ }
15
+ .pp-panel {
12
16
  font:
13
17
  15px / 1.5 -apple-system,
14
18
  BlinkMacSystemFont,
@@ -26,7 +30,7 @@
26
30
  overflow: hidden;
27
31
  }
28
32
 
29
- :root[data-theme="dark"] .pp-panel {
33
+ :root[data-theme="dark"] {
30
34
  --pp-text: #f1f2f3;
31
35
  --pp-background: #0d0e0f;
32
36
  --pp-surface: #18191c;
@@ -145,6 +149,7 @@
145
149
  min-height: 36px;
146
150
  padding: 8px 10px;
147
151
  font: inherit;
152
+ font-size: 16px;
148
153
  color: var(--pp-text);
149
154
  background: var(--pp-field);
150
155
  border: 0;
@@ -262,7 +267,7 @@
262
267
  box-sizing: border-box;
263
268
  width: 28px;
264
269
  height: 28px;
265
- border: 3px solid color-mix(in srgb, currentColor 25%, transparent);
270
+ border: 3px solid var(--pp-border, #8884);
266
271
  border-top-color: var(--pp-accent, AccentColor);
267
272
  border-radius: 50%;
268
273
  animation: pp-status-spin 0.8s linear infinite;
@@ -313,3 +318,240 @@
313
318
  outline: 2px solid var(--pp-accent);
314
319
  outline-offset: -2px;
315
320
  }
321
+
322
+ /* 首页沿用表单的分组与说明样式,入口按屏幕宽度自动换行。
323
+ * The landing page reuses form groups and descriptions, with entries wrapping by viewport width. */
324
+ :root.pp-page {
325
+ color-scheme: light dark;
326
+ }
327
+ .pp-page body {
328
+ margin: 0;
329
+ font:
330
+ 16px / 1.5 -apple-system,
331
+ BlinkMacSystemFont,
332
+ "Segoe UI",
333
+ sans-serif;
334
+ background: var(--pp-background, Canvas);
335
+ color: var(--pp-text, CanvasText);
336
+ }
337
+ [data-preference-panes-pages] {
338
+ position: relative;
339
+ min-height: calc(100vh - var(--pp-host-height));
340
+ background: inherit;
341
+ }
342
+ [data-preference-panes-module-page],
343
+ [data-preference-panes-link-page] {
344
+ position: fixed;
345
+ inset: var(--pp-host-height) 0 0;
346
+ background: inherit;
347
+ z-index: 1;
348
+ box-shadow: -8px 0 24px #00000014;
349
+ }
350
+ [data-preference-panes-module-page] iframe,
351
+ [data-preference-panes-link-page] iframe {
352
+ display: block;
353
+ border: 0;
354
+ width: 100%;
355
+ height: 100%;
356
+ background: inherit;
357
+ }
358
+ [data-module-message] {
359
+ position: absolute;
360
+ inset: 0;
361
+ margin: 0;
362
+ padding: 20px;
363
+ background: inherit;
364
+ }
365
+ [data-module-message][hidden] {
366
+ display: none;
367
+ }
368
+ .pp-host-header {
369
+ position: sticky;
370
+ top: 0;
371
+ z-index: 2;
372
+ display: grid;
373
+ grid-template-columns: 44px minmax(0, 1fr) 44px;
374
+ align-items: center;
375
+ min-height: 44px;
376
+ padding: env(safe-area-inset-top) max(12px, env(safe-area-inset-right)) 0 max(12px, env(safe-area-inset-left));
377
+ border-bottom: 1px solid var(--pp-border, #8884);
378
+ background: var(--pp-surface, Canvas);
379
+ color: var(--pp-accent, Highlight);
380
+ font-size: 17px;
381
+ }
382
+ .pp-host-title {
383
+ text-align: center;
384
+ color: var(--pp-text, CanvasText);
385
+ font-weight: 600;
386
+ overflow: hidden;
387
+ text-overflow: ellipsis;
388
+ white-space: nowrap;
389
+ }
390
+ .pp-host-back {
391
+ border: 0;
392
+ background: none;
393
+ color: inherit;
394
+ width: 44px;
395
+ height: 44px;
396
+ padding: 8px;
397
+ font:
398
+ 32px / 1 -apple-system,
399
+ sans-serif;
400
+ cursor: pointer;
401
+ }
402
+ .pp-host-back[hidden] {
403
+ visibility: hidden;
404
+ display: block;
405
+ }
406
+ .pp-host-toast {
407
+ position: fixed;
408
+ z-index: 3;
409
+ bottom: calc(24px + env(safe-area-inset-bottom) + var(--pp-keyboard-height));
410
+ left: 50%;
411
+ transform: translateX(-50%);
412
+ max-width: calc(100% - 48px);
413
+ padding: 10px 18px;
414
+ border-radius: 12px;
415
+ background: #222e;
416
+ color: #fff;
417
+ font-size: 14px;
418
+ text-align: center;
419
+ }
420
+ .pp-host-toast[hidden] {
421
+ display: none;
422
+ }
423
+ .pp-host-error {
424
+ margin: 24px;
425
+ padding: 16px;
426
+ border: 1px solid var(--pp-danger, #e45656);
427
+ border-radius: 12px;
428
+ font-size: 16px;
429
+ overflow-wrap: anywhere;
430
+ }
431
+
432
+ .pp-home {
433
+ max-width: 1000px;
434
+ margin: auto;
435
+ padding: 24px max(16px, env(safe-area-inset-right)) calc(24px + env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
436
+ }
437
+ .pp-home-logo {
438
+ display: block;
439
+ width: 64px;
440
+ height: 64px;
441
+ margin: 0 auto 24px;
442
+ object-fit: contain;
443
+ }
444
+ .pp-home-entries {
445
+ display: flex;
446
+ flex-wrap: wrap;
447
+ justify-content: space-around;
448
+ row-gap: 16px;
449
+ padding: 16px 0;
450
+ background: var(--pp-surface);
451
+ }
452
+ .pp-home-item {
453
+ box-sizing: border-box;
454
+ display: flex;
455
+ flex-direction: column;
456
+ align-items: center;
457
+ width: 25%;
458
+ min-width: 0;
459
+ padding: 0 4px;
460
+ border: 0;
461
+ background: none;
462
+ color: var(--pp-text);
463
+ font:
464
+ 12px / 1.5 -apple-system,
465
+ sans-serif;
466
+ cursor: pointer;
467
+ text-decoration: none;
468
+ }
469
+ @media (min-width: 500px) {
470
+ .pp-home-item {
471
+ width: calc(100% / 6);
472
+ }
473
+ }
474
+ @media (min-width: 700px) {
475
+ .pp-home-item {
476
+ width: 12.5%;
477
+ }
478
+ }
479
+ .pp-home-icon {
480
+ box-sizing: border-box;
481
+ width: 56px;
482
+ height: 56px;
483
+ padding: 8px;
484
+ border-radius: 50%;
485
+ background: var(--pp-field);
486
+ font-size: 32px;
487
+ line-height: 40px;
488
+ }
489
+ .pp-home-icon img {
490
+ display: block;
491
+ width: 100%;
492
+ height: 100%;
493
+ object-fit: contain;
494
+ }
495
+ .pp-home-name {
496
+ margin-top: 8px;
497
+ }
498
+ .pp-home-content {
499
+ min-width: 0;
500
+ width: 100%;
501
+ }
502
+ .pp-home-content > span {
503
+ display: block;
504
+ }
505
+ .pp-home-name,
506
+ .pp-home-item [data-module-status] {
507
+ width: 100%;
508
+ overflow: hidden;
509
+ text-align: center;
510
+ text-overflow: ellipsis;
511
+ white-space: nowrap;
512
+ }
513
+ .pp-home-item [data-module-status] {
514
+ min-height: 20px;
515
+ }
516
+ .pp-home-item:disabled {
517
+ opacity: 0.45;
518
+ cursor: default;
519
+ }
520
+ .pp-home-entries[data-layout="list"] {
521
+ display: block;
522
+ padding: 0;
523
+ }
524
+ .pp-home-entries[data-layout="list"] .pp-home-item {
525
+ flex-direction: row;
526
+ gap: 12px;
527
+ width: 100%;
528
+ padding: 12px 16px;
529
+ font-size: 15px;
530
+ }
531
+ .pp-home-entries[data-layout="list"] .pp-home-item + .pp-home-item {
532
+ border-top: 1px solid var(--pp-border);
533
+ }
534
+ .pp-home-entries[data-layout="list"] .pp-home-icon {
535
+ flex: none;
536
+ width: 40px;
537
+ height: 40px;
538
+ padding: 4px;
539
+ font-size: 24px;
540
+ line-height: 32px;
541
+ }
542
+ .pp-home-entries[data-layout="list"] .pp-home-name {
543
+ margin-top: 0;
544
+ text-align: left;
545
+ }
546
+ .pp-home-arrow {
547
+ color: var(--pp-muted);
548
+ font-size: 24px;
549
+ }
550
+ .pp-home-footer {
551
+ margin-top: 16px;
552
+ text-align: center;
553
+ white-space: normal;
554
+ }
555
+ .pp-home-footer p {
556
+ margin: 8px 0 0;
557
+ }
package/src/index.d.ts CHANGED
@@ -123,6 +123,10 @@ export interface SettingsResponse {
123
123
  * Fields, storage root and optional display metadata belonging to one module.
124
124
  */
125
125
  export interface ModuleDefinition {
126
+ /** 页面重置的完整设置路径,默认删除当前模块数据 / Complete setting paths reset by the page; defaults to deleting this module's data. */
127
+ resetPaths?: string[];
128
+ /** 同一存储根中的完整缓存路径,默认当前模块 Caches / Complete cache path in the same storage root, defaulting to this module's Caches. */
129
+ cachePath?: string;
126
130
  /**
127
131
  * 字段 ID 的模块段,不能由 app 名称推断
128
132
  * Module segment from field IDs, never inferred from app names.
@@ -246,6 +250,10 @@ export interface BoxJSSetting {
246
250
  * A BoxJS application.
247
251
  */
248
252
  export interface BoxJSApp extends BoxJSMetadata {
253
+ /** 共享模块存储时,限定本页面可重置的设置路径 / Limit reset paths when multiple pages share module storage. */
254
+ resetPaths?: string[];
255
+ /** 可选共享缓存路径,例如 @Root.Composite.Caches / Optional shared cache path, such as @Root.Composite.Caches. */
256
+ cachePath?: string;
249
257
  /**
250
258
  * 应用设置
251
259
  * Application settings.