@musnows/scriverse 0.6.4 → 0.6.6
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/dist/ai.js +22 -15
- package/dist/ai.js.map +1 -1
- package/dist/app.js +4 -0
- package/dist/app.js.map +1 -1
- package/dist/cli-contract.js +6 -5
- package/dist/cli-contract.js.map +1 -1
- package/dist/database.js +45 -1
- package/dist/database.js.map +1 -1
- package/dist/public/ai-mentions.js +5 -1
- package/dist/public/app.js +102 -52
- package/dist/public/character-version.js +1 -0
- package/dist/public/index.html +6 -10
- package/dist/public/styles.css +82 -49
- package/dist/public/timeline-view.d.ts +50 -0
- package/dist/public/timeline-view.js +119 -0
- package/dist/store.js +40 -25
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/styles.css
CHANGED
|
@@ -38,6 +38,14 @@
|
|
|
38
38
|
--relationship-edge-selected: rgba(38,69,118,.9);
|
|
39
39
|
--relationship-label-ink: rgba(40,52,73,.9);
|
|
40
40
|
--relationship-label-stroke: #eef2f7;
|
|
41
|
+
--timeline-track-palette-0: #8b3d2c;
|
|
42
|
+
--timeline-track-palette-1: #356f94;
|
|
43
|
+
--timeline-track-palette-2: #5e765c;
|
|
44
|
+
--timeline-track-palette-3: #9a6b2f;
|
|
45
|
+
--timeline-track-palette-4: #6b5b95;
|
|
46
|
+
--timeline-track-palette-5: #2f7a78;
|
|
47
|
+
--timeline-track-palette-6: #8a4f63;
|
|
48
|
+
--timeline-track-palette-7: #746f65;
|
|
41
49
|
--relationship-node-ink: rgba(36,51,74,.9);
|
|
42
50
|
--relationship-node-label: rgba(48,62,86,.88);
|
|
43
51
|
--relationship-node-label-active: rgba(24,44,71,.98);
|
|
@@ -55,7 +63,7 @@
|
|
|
55
63
|
--relationship-detail-muted: #67758c;
|
|
56
64
|
--font-latin: "SFMono-Regular", "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono";
|
|
57
65
|
--font-cjk: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "Heiti SC";
|
|
58
|
-
--ui-font-size:
|
|
66
|
+
--ui-font-size: 14px;
|
|
59
67
|
--ui-font-scale: 1;
|
|
60
68
|
--ai-font-size: 14px;
|
|
61
69
|
--ai-font-scale: 1;
|
|
@@ -272,6 +280,14 @@
|
|
|
272
280
|
--relationship-edge-selected: rgba(220,220,235,.88);
|
|
273
281
|
--relationship-label-ink: rgba(230,230,240,.88);
|
|
274
282
|
--relationship-label-stroke: #12121a;
|
|
283
|
+
--timeline-track-palette-0: #bd6550;
|
|
284
|
+
--timeline-track-palette-1: #7fb8d8;
|
|
285
|
+
--timeline-track-palette-2: #89a985;
|
|
286
|
+
--timeline-track-palette-3: #d4a35c;
|
|
287
|
+
--timeline-track-palette-4: #a794c8;
|
|
288
|
+
--timeline-track-palette-5: #6cb5b2;
|
|
289
|
+
--timeline-track-palette-6: #c4879a;
|
|
290
|
+
--timeline-track-palette-7: #aaa398;
|
|
275
291
|
--relationship-node-ink: rgba(245,245,250,.88);
|
|
276
292
|
--relationship-node-label: rgba(235,235,245,.78);
|
|
277
293
|
--relationship-node-label-active: rgba(255,255,255,.95);
|
|
@@ -515,7 +531,7 @@ input[type="checkbox"][hidden] { display: none; }
|
|
|
515
531
|
|
|
516
532
|
.left-panel, .ai-panel { position: relative; min-height: 0; background: var(--panel); }
|
|
517
533
|
.left-panel { display: flex; flex-direction: column; border-right: 1px solid var(--line); padding: 0; overflow: hidden; }
|
|
518
|
-
.left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px
|
|
534
|
+
.left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px 14px 16px; overflow-y: auto; }
|
|
519
535
|
.ai-panel { border-left: 1px solid var(--line); display: flex; flex-direction: column; padding: 14px 16px 18px; min-width: 0; }
|
|
520
536
|
.panel-collapse-button { z-index: 8; flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--muted); font-size: 18px; line-height: 1; }
|
|
521
537
|
.ai-heading #ai-panel-toggle { flex-basis: 30px; width: 30px; height: 30px; }
|
|
@@ -1295,6 +1311,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1295
1311
|
.record-card.has-card-edit .character-card-heading { padding-right: 34px; }
|
|
1296
1312
|
.record-card.has-card-edit .character-card-heading h3 { padding-right: 0; }
|
|
1297
1313
|
.character-lock-badge { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 3px; min-height: 20px; padding: 2px 6px; border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line)); border-radius: 10px; background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-dark); font-size: 9px; font-variant-numeric: tabular-nums; line-height: 1; }
|
|
1314
|
+
.entity-lifecycle-badge { display: inline-flex; align-items: center; min-height: 20px; margin-left: 7px; padding: 2px 7px; border: 1px solid color-mix(in srgb, var(--accent) 58%, var(--line)); border-radius: 10px; background: color-mix(in srgb, var(--accent) 13%, var(--surface)); color: var(--accent-dark); font-size: 9px; font-weight: 650; line-height: 1; vertical-align: middle; white-space: nowrap; }
|
|
1298
1315
|
.character-lock-badge svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; }
|
|
1299
1316
|
.character-filter-toolbar { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)) auto; gap: 12px; align-items: start; width: 100%; min-width: 0; box-sizing: border-box; margin-bottom: 18px; padding: 12px 14px; border: 1px solid var(--line); background: var(--surface-soft); }
|
|
1300
1317
|
.character-filter-dropdown { position: relative; min-width: 0; }
|
|
@@ -1732,30 +1749,65 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1732
1749
|
.default-model-select { min-width: 260px; padding: 7px 9px; font-size: 11px; }
|
|
1733
1750
|
.empty-state { padding: 70px 30px; text-align: center; color: var(--muted); border: 1px dashed var(--line); }
|
|
1734
1751
|
.empty-state b { display: block; color: var(--ink); margin-bottom: 8px; }
|
|
1752
|
+
.timeline-axis { display: grid; gap: 14px; }
|
|
1735
1753
|
.timeline-list { position: relative; margin-left: 15px; border-left: 1px solid var(--line); }
|
|
1736
|
-
.timeline-item { position: relative; padding: 0 0 28px 25px; }
|
|
1737
|
-
.timeline-item::before { content: ""; position: absolute; left: -5px; top: 5px; width: 9px; height: 9px; border-radius: 50%; background: var(--
|
|
1738
|
-
.timeline-item h3 { margin: 4px 0 7px; }
|
|
1754
|
+
.timeline-item { position: relative; padding: 0 0 28px 25px; --timeline-track-color: var(--accent); }
|
|
1755
|
+
.timeline-item::before { content: ""; position: absolute; left: -5px; top: 5px; width: 9px; height: 9px; border-radius: 50%; background: var(--timeline-track-color); box-shadow: 0 0 0 5px var(--paper); }
|
|
1756
|
+
.timeline-item h3 { margin: 4px 0 7px; font-size: 15px; }
|
|
1739
1757
|
.timeline-item p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.55; }
|
|
1758
|
+
.timeline-item-location { display: block; margin-top: 7px; color: var(--muted); font-size: 10px; }
|
|
1759
|
+
.timeline-item .card-actions { margin-top: 10px; }
|
|
1740
1760
|
.table-list { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
1741
1761
|
.table-list th, .table-list td { padding: 12px 10px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
|
|
1742
1762
|
.table-list th { color: var(--muted); font-weight: 400; font-size: 10px; text-transform: uppercase; letter-spacing: .1em; }
|
|
1743
1763
|
.danger-text { color: var(--accent); }
|
|
1744
1764
|
.timeline-tools { display: flex; justify-content: flex-end; gap: 8px; }
|
|
1745
1765
|
.timeline-select { position: absolute; left: -33px; top: 0; }
|
|
1746
|
-
.timeline-
|
|
1747
|
-
.timeline-
|
|
1748
|
-
.timeline-
|
|
1749
|
-
.timeline-
|
|
1750
|
-
.timeline-track-
|
|
1751
|
-
.timeline-track-
|
|
1752
|
-
.timeline-track-
|
|
1753
|
-
.timeline-
|
|
1754
|
-
.timeline-
|
|
1755
|
-
.timeline-
|
|
1756
|
-
.timeline-
|
|
1757
|
-
.timeline-
|
|
1758
|
-
.timeline-
|
|
1766
|
+
.timeline-card-meta { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
|
|
1767
|
+
.timeline-card-meta small { color: var(--timeline-track-color, var(--accent)); font-size: 9px; }
|
|
1768
|
+
.timeline-track-badge { display: inline-flex; align-items: center; padding: 2px 7px; border: 1px solid color-mix(in srgb, var(--timeline-track-color, var(--accent)) 45%, var(--line)); border-radius: 999px; color: var(--timeline-track-color, var(--accent)); font-size: 9px; line-height: 1.3; background: color-mix(in srgb, var(--timeline-track-color, var(--accent)) 10%, transparent); }
|
|
1769
|
+
.timeline-track-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
|
|
1770
|
+
.timeline-track-tab { display: inline-flex; align-items: center; gap: 7px; min-height: 34px; padding: 0 12px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); font-size: 11px; }
|
|
1771
|
+
.timeline-track-tab:hover, .timeline-track-tab:focus-visible { color: var(--ink); background: var(--paper-deep); }
|
|
1772
|
+
.timeline-track-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
1773
|
+
.timeline-track-tab[aria-selected="true"] { color: var(--ink); border-color: color-mix(in srgb, var(--timeline-track-color, var(--accent)) 55%, var(--line)); background: color-mix(in srgb, var(--timeline-track-color, var(--accent)) 14%, var(--surface)); box-shadow: inset 0 -2px 0 var(--timeline-track-color, var(--accent)); font-weight: 600; }
|
|
1774
|
+
.timeline-track-tab .timeline-track-swatch { margin-top: 0; }
|
|
1775
|
+
.timeline-track-panel { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-start; justify-content: space-between; margin-bottom: 16px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 5px; background: var(--panel); }
|
|
1776
|
+
.timeline-track-panel-copy { display: grid; gap: 4px; min-width: min(280px, 100%); flex: 1; }
|
|
1777
|
+
.timeline-track-panel-copy strong { font-size: 13px; }
|
|
1778
|
+
.timeline-track-panel-copy p { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.45; }
|
|
1779
|
+
.timeline-track-panel-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1780
|
+
.timeline-track-swatch { width: 10px; height: 10px; margin-top: 3px; flex: 0 0 auto; border-radius: 50%; background: var(--timeline-track-color, var(--accent)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--timeline-track-color, var(--accent)) 16%, transparent); }
|
|
1781
|
+
.timeline-track-menu { border: 1px solid var(--line); border-radius: 3px; padding: 5px 7px; background: transparent; color: var(--muted); font-size: 9px; line-height: 1.2; }
|
|
1782
|
+
.timeline-track-menu:hover, .timeline-track-menu:focus-visible { color: var(--ink); background: var(--paper-deep); }
|
|
1783
|
+
.timeline-track-menu:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
1784
|
+
.timeline-track-menu[aria-pressed="true"] { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); background: color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
1785
|
+
.timeline-track-actions { display: flex; flex-wrap: wrap; gap: 5px; flex: 0 0 auto; align-items: center; }
|
|
1786
|
+
.timeline-add-event { padding: 5px 7px; border: 1px solid var(--line); border-radius: 3px; background: transparent; color: var(--muted); font-size: 9px; line-height: 1.2; }
|
|
1787
|
+
.timeline-item[data-track-color-index="0"],
|
|
1788
|
+
.timeline-track-tab[data-track-color-index="0"],
|
|
1789
|
+
.timeline-track-panel[data-track-color-index="0"] { --timeline-track-color: var(--timeline-track-palette-0); }
|
|
1790
|
+
.timeline-item[data-track-color-index="1"],
|
|
1791
|
+
.timeline-track-tab[data-track-color-index="1"],
|
|
1792
|
+
.timeline-track-panel[data-track-color-index="1"] { --timeline-track-color: var(--timeline-track-palette-1); }
|
|
1793
|
+
.timeline-item[data-track-color-index="2"],
|
|
1794
|
+
.timeline-track-tab[data-track-color-index="2"],
|
|
1795
|
+
.timeline-track-panel[data-track-color-index="2"] { --timeline-track-color: var(--timeline-track-palette-2); }
|
|
1796
|
+
.timeline-item[data-track-color-index="3"],
|
|
1797
|
+
.timeline-track-tab[data-track-color-index="3"],
|
|
1798
|
+
.timeline-track-panel[data-track-color-index="3"] { --timeline-track-color: var(--timeline-track-palette-3); }
|
|
1799
|
+
.timeline-item[data-track-color-index="4"],
|
|
1800
|
+
.timeline-track-tab[data-track-color-index="4"],
|
|
1801
|
+
.timeline-track-panel[data-track-color-index="4"] { --timeline-track-color: var(--timeline-track-palette-4); }
|
|
1802
|
+
.timeline-item[data-track-color-index="5"],
|
|
1803
|
+
.timeline-track-tab[data-track-color-index="5"],
|
|
1804
|
+
.timeline-track-panel[data-track-color-index="5"] { --timeline-track-color: var(--timeline-track-palette-5); }
|
|
1805
|
+
.timeline-item[data-track-color-index="6"],
|
|
1806
|
+
.timeline-track-tab[data-track-color-index="6"],
|
|
1807
|
+
.timeline-track-panel[data-track-color-index="6"] { --timeline-track-color: var(--timeline-track-palette-6); }
|
|
1808
|
+
.timeline-item[data-track-color-index="7"],
|
|
1809
|
+
.timeline-track-tab[data-track-color-index="7"],
|
|
1810
|
+
.timeline-track-panel[data-track-color-index="7"] { --timeline-track-color: var(--timeline-track-palette-7); }
|
|
1759
1811
|
.relationship-map-card { margin-bottom: 28px; overflow: hidden; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
|
|
1760
1812
|
.relationship-map-toolbar { display: flex; align-items: center; gap: 18px; padding: 13px 16px; border-bottom: 1px solid var(--line); }
|
|
1761
1813
|
.relationship-map-toolbar > div:first-child { display: grid; gap: 3px; }
|
|
@@ -2212,27 +2264,6 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2212
2264
|
.prompt-options select { padding: 6px; color: var(--muted); font-size: 10px; }
|
|
2213
2265
|
.prompt-options .ai-roleplay-character { min-width: 0; }
|
|
2214
2266
|
.ai-panel.is-roleplaying .ai-roleplay-character { border-color: color-mix(in srgb, var(--accent) 52%, var(--line)); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); color: var(--accent-dark); }
|
|
2215
|
-
.prompt-options .ai-include-setting-info {
|
|
2216
|
-
grid-column: 1 / -1;
|
|
2217
|
-
display: inline-flex;
|
|
2218
|
-
align-items: center;
|
|
2219
|
-
gap: 6px;
|
|
2220
|
-
min-height: 28px;
|
|
2221
|
-
color: var(--muted);
|
|
2222
|
-
font-size: 10px;
|
|
2223
|
-
cursor: pointer;
|
|
2224
|
-
user-select: none;
|
|
2225
|
-
}
|
|
2226
|
-
.prompt-options .ai-include-setting-info input {
|
|
2227
|
-
width: 14px;
|
|
2228
|
-
height: 14px;
|
|
2229
|
-
margin: 0;
|
|
2230
|
-
accent-color: var(--accent);
|
|
2231
|
-
}
|
|
2232
|
-
.prompt-options .ai-include-setting-info:has(input:disabled) {
|
|
2233
|
-
opacity: 0.55;
|
|
2234
|
-
cursor: not-allowed;
|
|
2235
|
-
}
|
|
2236
2267
|
.prompt-model-field { margin: 0 0 7px; }
|
|
2237
2268
|
.prompt-model-field select { padding: 6px; color: var(--muted); font-size: 10px; }
|
|
2238
2269
|
.prompt-composer { position: relative; }
|
|
@@ -2557,9 +2588,11 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2557
2588
|
.character-aliases b { margin-right: 2px; color: var(--muted); font-size: 9px; font-weight: 500; }
|
|
2558
2589
|
.character-aliases .pill { margin: 0; border: 1px solid var(--line); background: var(--surface); }
|
|
2559
2590
|
.character-code { display: flex; align-items: center; gap: 7px; margin-top: 10px; color: var(--muted); font-size: 10px; }
|
|
2591
|
+
.character-code b { font-weight: 500; }
|
|
2592
|
+
.character-code .pill { display: inline-flex; align-items: center; justify-content: center; margin: 0; min-height: 18px; padding: 2px 8px; line-height: 1; font-family: var(--font-latin), var(--font-cjk), monospace; }
|
|
2560
2593
|
.character-species { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; gap: 6px; margin-top: 8px; color: var(--muted); font-size: 10px; }
|
|
2561
2594
|
.character-species b { font-weight: 500; }
|
|
2562
|
-
.character-species .pill { justify-self: start; min-width: 0; max-width: 100%; margin: 0; overflow-wrap: anywhere; }
|
|
2595
|
+
.character-species .pill { display: inline-flex; align-items: center; justify-content: center; justify-self: start; min-width: 0; max-width: 100%; margin: 0; min-height: 18px; padding: 2px 8px; line-height: 1.25; overflow-wrap: anywhere; }
|
|
2563
2596
|
.character-detail-list { display: grid; gap: 5px; margin: 12px 0; }
|
|
2564
2597
|
.character-detail-list div { display: grid; grid-template-columns: minmax(70px, .42fr) minmax(0, 1fr); gap: 8px; font-size: 10px; }
|
|
2565
2598
|
.character-detail-list dt { color: var(--muted); }
|
|
@@ -2922,7 +2955,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2922
2955
|
}
|
|
2923
2956
|
|
|
2924
2957
|
@media (max-width: 1100px) {
|
|
2925
|
-
.left-panel-body { padding-inline: 10px
|
|
2958
|
+
.left-panel-body { padding-inline: 10px; }
|
|
2926
2959
|
.ai-panel { padding-inline: 14px; }
|
|
2927
2960
|
.editor-toolbar { grid-template-areas: "path" "title" "actions"; grid-template-columns: minmax(0, 1fr); gap: 12px; padding-inline: 6%; }
|
|
2928
2961
|
.editor-actions { justify-content: flex-end; flex-wrap: wrap; }
|
|
@@ -3264,15 +3297,15 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3264
3297
|
.dialog-submit-progress > span { width: 100%; transform: none; }
|
|
3265
3298
|
}
|
|
3266
3299
|
|
|
3267
|
-
/*
|
|
3300
|
+
/* 显示设置中的界面字号按原有视觉层级缩放;正文与 Agent 对话分别独立调节。 */
|
|
3268
3301
|
body { font-size: var(--ui-font-size); }
|
|
3269
|
-
:is(.module-nav button, .ghost-button, .primary-button
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
}
|
|
3302
|
+
:is(.module-nav button, .ghost-button.ghost-button, .primary-button.primary-button) { font-size: calc(12px * var(--ui-font-scale)); }
|
|
3303
|
+
.module-header p { font-size: calc(13px * var(--ui-font-scale)); }
|
|
3304
|
+
.eyebrow, .field-label, .dialog-header-meta, .settings-hub-card small, .book-card small, .book-info > span { font-size: calc(10px * var(--ui-font-scale)); }
|
|
3305
|
+
.panel-heading, .dialog-fields label, .dialog-fields .form-field-note, .appearance-grid label,
|
|
3306
|
+
.config-section-header p, .config-section .config-save-button { font-size: calc(11px * var(--ui-font-scale)); }
|
|
3307
|
+
.settings-hub-card strong, .book-info strong { font-size: calc(16px * var(--ui-font-scale)); }
|
|
3308
|
+
.shelf-header p, .topbar { font-size: var(--ui-font-size); }
|
|
3276
3309
|
.appearance-grid select, .dialog-fields input, .dialog-fields textarea, .dialog-fields select { font-size: calc(13px * var(--ui-font-scale)); }
|
|
3277
3310
|
|
|
3278
3311
|
/* Agent 面板原有的细小字号统一以可读的对话字号为基准缩放。 */
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type TimelineSortDirection = "asc" | "desc";
|
|
2
|
+
|
|
3
|
+
export type TimelineViewEvent = {
|
|
4
|
+
id?: string | null;
|
|
5
|
+
trackId?: string | null;
|
|
6
|
+
timeSort?: number | null;
|
|
7
|
+
updatedAt?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type TimelineViewTrack = {
|
|
11
|
+
id?: string | null;
|
|
12
|
+
name?: string | null;
|
|
13
|
+
sortOrder?: number | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export declare const TIMELINE_TRACK_PALETTE_SIZE: number;
|
|
17
|
+
export declare const TIMELINE_UNGROUPED_COLOR_INDEX: number;
|
|
18
|
+
|
|
19
|
+
export declare function normalizeTimelineSortDirection(direction?: unknown): TimelineSortDirection;
|
|
20
|
+
|
|
21
|
+
export declare function sortTimelineEvents<T extends TimelineViewEvent>(
|
|
22
|
+
events?: T[],
|
|
23
|
+
options?: { direction?: TimelineSortDirection }
|
|
24
|
+
): T[];
|
|
25
|
+
|
|
26
|
+
export declare function filterTimelineEvents<T extends TimelineViewEvent>(
|
|
27
|
+
events?: T[],
|
|
28
|
+
filters?: { trackIds?: Array<string | null | undefined> }
|
|
29
|
+
): T[];
|
|
30
|
+
|
|
31
|
+
export declare function timelineTrackColorIndex(
|
|
32
|
+
trackId: string | null | undefined,
|
|
33
|
+
tracks?: TimelineViewTrack[]
|
|
34
|
+
): number;
|
|
35
|
+
|
|
36
|
+
export declare function prepareTimelineEvents<T extends TimelineViewEvent>(
|
|
37
|
+
events?: T[],
|
|
38
|
+
filters?: { trackIds?: Array<string | null | undefined> },
|
|
39
|
+
options?: { direction?: TimelineSortDirection }
|
|
40
|
+
): T[];
|
|
41
|
+
|
|
42
|
+
export declare function resolveTimelineActiveTrackId(
|
|
43
|
+
activeTrackId: string | null | undefined,
|
|
44
|
+
tracks?: TimelineViewTrack[]
|
|
45
|
+
): string;
|
|
46
|
+
|
|
47
|
+
export declare function timelineTrackDisplayName(
|
|
48
|
+
trackId: string | null | undefined,
|
|
49
|
+
tracks?: TimelineViewTrack[]
|
|
50
|
+
): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** 时间轴轨道色板(与 CSS `--timeline-track-palette-*` 索引对应) */
|
|
2
|
+
export const TIMELINE_TRACK_PALETTE_SIZE = 8;
|
|
3
|
+
|
|
4
|
+
/** 未分组轨道使用的色板索引 */
|
|
5
|
+
export const TIMELINE_UNGROUPED_COLOR_INDEX = TIMELINE_TRACK_PALETTE_SIZE - 1;
|
|
6
|
+
|
|
7
|
+
function timelineEventTrackKey(event) {
|
|
8
|
+
return String(event?.trackId ?? "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function compareNullableTimeSort(left, right, direction = "asc") {
|
|
12
|
+
const leftSort = left?.timeSort;
|
|
13
|
+
const rightSort = right?.timeSort;
|
|
14
|
+
const leftMissing = leftSort === null || leftSort === undefined || Number.isNaN(Number(leftSort));
|
|
15
|
+
const rightMissing = rightSort === null || rightSort === undefined || Number.isNaN(Number(rightSort));
|
|
16
|
+
if (leftMissing && rightMissing) return 0;
|
|
17
|
+
if (leftMissing) return 1;
|
|
18
|
+
if (rightMissing) return -1;
|
|
19
|
+
const delta = Number(leftSort) - Number(rightSort);
|
|
20
|
+
if (delta === 0) return 0;
|
|
21
|
+
return direction === "desc" ? -delta : delta;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function compareStableTieBreak(left, right, direction = "asc") {
|
|
25
|
+
const leftUpdated = String(left?.updatedAt ?? "");
|
|
26
|
+
const rightUpdated = String(right?.updatedAt ?? "");
|
|
27
|
+
if (leftUpdated !== rightUpdated) {
|
|
28
|
+
const delta = leftUpdated < rightUpdated ? -1 : 1;
|
|
29
|
+
return direction === "desc" ? -delta : delta;
|
|
30
|
+
}
|
|
31
|
+
const leftId = String(left?.id ?? "");
|
|
32
|
+
const rightId = String(right?.id ?? "");
|
|
33
|
+
if (leftId === rightId) return 0;
|
|
34
|
+
const delta = leftId < rightId ? -1 : 1;
|
|
35
|
+
return direction === "desc" ? -delta : delta;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeTimelineSortDirection(direction) {
|
|
39
|
+
return direction === "desc" ? "desc" : "asc";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 按 timeSort 排序;缺失或无效的排序值始终置底;同值按 updatedAt、id 稳定排序。
|
|
44
|
+
*/
|
|
45
|
+
export function sortTimelineEvents(events = [], { direction = "asc" } = {}) {
|
|
46
|
+
const sortDirection = normalizeTimelineSortDirection(direction);
|
|
47
|
+
return [...events].sort((left, right) => {
|
|
48
|
+
const bySort = compareNullableTimeSort(left, right, sortDirection);
|
|
49
|
+
if (bySort !== 0) return bySort;
|
|
50
|
+
return compareStableTieBreak(left, right, sortDirection);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 按轨道筛选。trackIds 为空表示不过滤;未分组轨道用空字符串 ""。
|
|
56
|
+
*/
|
|
57
|
+
export function filterTimelineEvents(events = [], { trackIds = [] } = {}) {
|
|
58
|
+
const selected = new Set(trackIds.map((id) => String(id)));
|
|
59
|
+
if (selected.size === 0) return events;
|
|
60
|
+
return events.filter((event) => selected.has(timelineEventTrackKey(event)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 将轨道映射到固定色板索引。按 sortOrder、id 排序后取模;未分组固定为末位索引。
|
|
65
|
+
*/
|
|
66
|
+
export function timelineTrackColorIndex(trackId, tracks = []) {
|
|
67
|
+
const key = String(trackId ?? "");
|
|
68
|
+
if (!key) return TIMELINE_UNGROUPED_COLOR_INDEX;
|
|
69
|
+
const ordered = [...tracks]
|
|
70
|
+
.filter((track) => String(track?.id ?? ""))
|
|
71
|
+
.sort((left, right) => {
|
|
72
|
+
const orderDelta = Number(left?.sortOrder ?? 0) - Number(right?.sortOrder ?? 0);
|
|
73
|
+
if (orderDelta !== 0) return orderDelta;
|
|
74
|
+
const leftId = String(left?.id ?? "");
|
|
75
|
+
const rightId = String(right?.id ?? "");
|
|
76
|
+
if (leftId === rightId) return 0;
|
|
77
|
+
return leftId < rightId ? -1 : 1;
|
|
78
|
+
});
|
|
79
|
+
const index = ordered.findIndex((track) => String(track.id) === key);
|
|
80
|
+
if (index < 0) return TIMELINE_UNGROUPED_COLOR_INDEX;
|
|
81
|
+
return index % (TIMELINE_TRACK_PALETTE_SIZE - 1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 先筛选再按时间排序,供列表渲染使用。
|
|
86
|
+
*/
|
|
87
|
+
export function prepareTimelineEvents(events = [], filters = {}, { direction = "asc" } = {}) {
|
|
88
|
+
return sortTimelineEvents(filterTimelineEvents(events, filters), { direction });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 解析当前激活的轨道 Tab。优先保留已选轨道;失效时回退到第一条真实轨道,再回退未分组。
|
|
93
|
+
* null/undefined 表示尚未选择,回退到第一条真实轨道。
|
|
94
|
+
*/
|
|
95
|
+
export function resolveTimelineActiveTrackId(activeTrackId, tracks = []) {
|
|
96
|
+
const orderedIds = [...tracks]
|
|
97
|
+
.filter((track) => String(track?.id ?? ""))
|
|
98
|
+
.sort((left, right) => {
|
|
99
|
+
const orderDelta = Number(left?.sortOrder ?? 0) - Number(right?.sortOrder ?? 0);
|
|
100
|
+
if (orderDelta !== 0) return orderDelta;
|
|
101
|
+
const leftId = String(left?.id ?? "");
|
|
102
|
+
const rightId = String(right?.id ?? "");
|
|
103
|
+
if (leftId === rightId) return 0;
|
|
104
|
+
return leftId < rightId ? -1 : 1;
|
|
105
|
+
})
|
|
106
|
+
.map((track) => String(track.id));
|
|
107
|
+
const fallback = orderedIds[0] ?? "";
|
|
108
|
+
if (activeTrackId === null || activeTrackId === undefined) return fallback;
|
|
109
|
+
const key = String(activeTrackId);
|
|
110
|
+
if (orderedIds.includes(key) || key === "") return key;
|
|
111
|
+
return fallback;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function timelineTrackDisplayName(trackId, tracks = []) {
|
|
115
|
+
const key = String(trackId ?? "");
|
|
116
|
+
if (!key) return "未分组";
|
|
117
|
+
const track = tracks.find((item) => String(item?.id ?? "") === key);
|
|
118
|
+
return track?.name || "未知轨道";
|
|
119
|
+
}
|