pinokiod 8.0.64 → 8.0.66

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.
@@ -1494,7 +1494,7 @@ class Vault {
1494
1494
  }
1495
1495
  }
1496
1496
 
1497
- async startFolderDiscovery(selectedRoot) {
1497
+ async startFolderDiscovery(selectedRoot, selectedThreshold) {
1498
1498
  if (!this.enabled || !this.folderFinder) {
1499
1499
  return { started: false, disabled: true }
1500
1500
  }
@@ -1509,6 +1509,9 @@ class Vault {
1509
1509
  !path.isAbsolute(selectedRoot.trim())) {
1510
1510
  return { error: "Choose a valid folder or drive." }
1511
1511
  }
1512
+ if (!CANDIDATE_SIZE_OPTIONS.includes(selectedThreshold)) {
1513
+ return { error: "Choose a valid minimum file size." }
1514
+ }
1512
1515
  if (this.scanPromise) {
1513
1516
  return { error: "Wait for the current scan to finish." }
1514
1517
  }
@@ -1520,10 +1523,15 @@ class Vault {
1520
1523
  return { error: "Run a global scan before finding folders." }
1521
1524
  }
1522
1525
  const publishedThreshold = Number(globalScan.candidate_min_bytes)
1523
- const threshold = Number.isFinite(publishedThreshold) &&
1524
- publishedThreshold >= 0
1525
- ? publishedThreshold
1526
- : this.sizeThreshold
1526
+ if (!Number.isFinite(publishedThreshold) || publishedThreshold < 0) {
1527
+ return { error: "Run a new global scan before finding folders." }
1528
+ }
1529
+ if (selectedThreshold < publishedThreshold) {
1530
+ return {
1531
+ error: "Run a global scan with this minimum file size before searching folders."
1532
+ }
1533
+ }
1534
+ const threshold = selectedThreshold
1527
1535
  const partial = !!globalScan.partial || (
1528
1536
  Array.isArray(globalScan.exclusions) &&
1529
1537
  globalScan.exclusions.length > 0
@@ -1832,7 +1840,10 @@ class Vault {
1832
1840
  case "cancel_scan":
1833
1841
  return this.cancelScan()
1834
1842
  case "find_folders":
1835
- return this.startFolderDiscovery(payload.path)
1843
+ return this.startFolderDiscovery(
1844
+ payload.path,
1845
+ payload.candidate_size
1846
+ )
1836
1847
  case "cancel_find_folders":
1837
1848
  return this.cancelFolderDiscovery()
1838
1849
  case "clear_find_folders":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "8.0.64",
3
+ "version": "8.0.66",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,6 +5,25 @@
5
5
  const app = status.dataset.app || ""
6
6
  const tab = status.closest("#save-space-tab")
7
7
  const label = status.querySelector("[data-app-vault-mode-label]")
8
+ const origin = window.location.origin
9
+ let parentStateVersion = 0
10
+ let fallbackTimer = null
11
+
12
+ const normalizeSnapshot = (snapshot) => ({
13
+ global_scan_ready: !!(snapshot && snapshot.global_scan_ready === true),
14
+ settings: snapshot && Array.isArray(snapshot.settings)
15
+ ? snapshot.settings.filter((setting) =>
16
+ setting && typeof setting.app === "string" && setting.app).map((setting) => ({
17
+ app: setting.app,
18
+ mode: setting.mode === "manual" ? "manual" : "automatic"
19
+ }))
20
+ : []
21
+ })
22
+ let latestSnapshot = normalizeSnapshot({
23
+ global_scan_ready: status.dataset.ready === "true",
24
+ settings: [{ app, mode: status.dataset.mode }]
25
+ })
26
+
8
27
  const setState = (value, ready) => {
9
28
  const mode = value === "manual" ? "manual" : "automatic"
10
29
  const globalScanReady = ready === true
@@ -24,23 +43,104 @@
24
43
  }
25
44
  }
26
45
  const applySnapshot = (snapshot) => {
27
- const settings = snapshot && Array.isArray(snapshot.settings)
28
- ? snapshot.settings
29
- : []
30
- const setting = settings.find((item) => item && item.app === app)
31
- setState(setting && setting.mode,
32
- snapshot && snapshot.global_scan_ready === true)
46
+ latestSnapshot = normalizeSnapshot(snapshot)
47
+ const setting = latestSnapshot.settings.find((item) =>
48
+ item.app === app)
49
+ setState(setting && setting.mode, latestSnapshot.global_scan_ready)
50
+ }
51
+ const vaultFrame = () =>
52
+ document.querySelector('iframe[name="app-vault"]')
53
+ const sendSnapshot = (targetWindow) => {
54
+ if (!targetWindow) return
55
+ try {
56
+ targetWindow.postMessage({
57
+ e: "vault-automatic-scan-state",
58
+ snapshot: latestSnapshot
59
+ }, origin)
60
+ } catch (_) {}
61
+ }
62
+ const relaySnapshot = () => {
63
+ const frame = vaultFrame()
64
+ if (frame) sendSnapshot(frame.contentWindow)
65
+ }
66
+ const mergeMode = (mode) => {
67
+ const settings = latestSnapshot.settings.filter((setting) =>
68
+ setting.app !== app)
69
+ settings.push({
70
+ app,
71
+ mode: mode === "manual" ? "manual" : "automatic"
72
+ })
73
+ applySnapshot({
74
+ global_scan_ready: latestSnapshot.global_scan_ready,
75
+ settings
76
+ })
77
+ }
78
+ const loadState = async () => {
79
+ if (!app) return
80
+ const requestedAtParentVersion = parentStateVersion
81
+ try {
82
+ const response = await fetch("/info/vault/automatic-scans", {
83
+ credentials: "same-origin",
84
+ cache: "no-store"
85
+ })
86
+ if (!response.ok) return
87
+ const snapshot = await response.json()
88
+ if (window.parent !== window &&
89
+ parentStateVersion !== requestedAtParentVersion) return
90
+ applySnapshot(snapshot)
91
+ relaySnapshot()
92
+ } catch (_) {}
33
93
  }
34
94
 
35
95
  setState(status.dataset.mode, status.dataset.ready === "true")
36
- if (!app || typeof window.EventSource !== "function") return
96
+ if (!app) return
97
+
98
+ window.addEventListener("message", (event) => {
99
+ if (!event || event.origin !== origin ||
100
+ !event.data || typeof event.data !== "object") return
101
+ if (event.data.e === "vault-automatic-scan-state" &&
102
+ window.parent !== window && event.source === window.parent) {
103
+ parentStateVersion += 1
104
+ if (fallbackTimer !== null) {
105
+ window.clearTimeout(fallbackTimer)
106
+ fallbackTimer = null
107
+ }
108
+ applySnapshot(event.data.snapshot)
109
+ relaySnapshot()
110
+ return
111
+ }
112
+ const frame = vaultFrame()
113
+ if (!frame || frame.contentWindow !== event.source) return
114
+ if (event.data.e === "vault-automatic-scan-state-request") {
115
+ sendSnapshot(event.source)
116
+ return
117
+ }
118
+ if (event.data.e === "vault-automatic-mode-changed" &&
119
+ event.data.app === app) {
120
+ mergeMode(event.data.mode)
121
+ if (window.parent !== window) {
122
+ try {
123
+ window.parent.postMessage({
124
+ e: "vault-automatic-mode-changed",
125
+ app,
126
+ mode: status.dataset.mode
127
+ }, origin)
128
+ } catch (_) {}
129
+ }
130
+ }
131
+ })
37
132
 
38
- const source = new window.EventSource(
39
- "/info/vault/automatic-scans/events")
40
- source.onmessage = (event) => {
133
+ if (window.parent !== window) {
41
134
  try {
42
- applySnapshot(JSON.parse(event.data))
135
+ window.parent.postMessage({
136
+ e: "vault-automatic-scan-state-request"
137
+ }, origin)
43
138
  } catch (_) {}
139
+ fallbackTimer = window.setTimeout(() => {
140
+ fallbackTimer = null
141
+ loadState()
142
+ }, 500)
143
+ } else {
144
+ loadState()
44
145
  }
45
- window.addEventListener("beforeunload", () => source.close(), { once: true })
46
146
  })()
@@ -485,6 +485,70 @@
485
485
  });
486
486
  }
487
487
 
488
+ let latestAutomaticScanState = null;
489
+
490
+ function automaticScanState(snapshot) {
491
+ const settings = snapshot && Array.isArray(snapshot.settings)
492
+ ? snapshot.settings.filter((setting) =>
493
+ setting && typeof setting.app === 'string' && setting.app).map((setting) => ({
494
+ app: setting.app,
495
+ mode: setting.mode === 'manual' ? 'manual' : 'automatic',
496
+ }))
497
+ : [];
498
+ return {
499
+ global_scan_ready: !!(snapshot && snapshot.global_scan_ready === true),
500
+ settings,
501
+ };
502
+ }
503
+
504
+ function sendAutomaticScanState(targetWindow) {
505
+ if (!targetWindow || !latestAutomaticScanState) {
506
+ return;
507
+ }
508
+ try {
509
+ targetWindow.postMessage({
510
+ e: 'vault-automatic-scan-state',
511
+ snapshot: latestAutomaticScanState,
512
+ }, window.location.origin);
513
+ } catch (_) {}
514
+ }
515
+
516
+ function broadcastAutomaticScanState(snapshot) {
517
+ latestAutomaticScanState = automaticScanState(snapshot);
518
+ leafElements.forEach((entry) => {
519
+ sendAutomaticScanState(entry.iframe?.contentWindow || null);
520
+ });
521
+ }
522
+
523
+ function isLeafWindow(sourceWindow) {
524
+ if (!sourceWindow) return false;
525
+ for (const entry of leafElements.values()) {
526
+ if (entry.iframe && entry.iframe.contentWindow === sourceWindow) {
527
+ return true;
528
+ }
529
+ }
530
+ return false;
531
+ }
532
+
533
+ function mergeAutomaticMode(app, mode) {
534
+ if (!latestAutomaticScanState || typeof app !== 'string' || !app) {
535
+ return;
536
+ }
537
+ const settings = latestAutomaticScanState.settings.filter((setting) =>
538
+ setting.app !== app);
539
+ settings.push({
540
+ app,
541
+ mode: mode === 'manual' ? 'manual' : 'automatic',
542
+ });
543
+ latestAutomaticScanState = {
544
+ global_scan_ready: latestAutomaticScanState.global_scan_ready,
545
+ settings,
546
+ };
547
+ leafElements.forEach((entry) => {
548
+ sendAutomaticScanState(entry.iframe?.contentWindow || null);
549
+ });
550
+ }
551
+
488
552
  let activeResize = null;
489
553
 
490
554
  function beginResize(splitId, pointerEvent) {
@@ -692,6 +756,20 @@
692
756
  if (!event || !event.data || typeof event.data !== 'object') {
693
757
  return;
694
758
  }
759
+ if (event.data.e === 'vault-automatic-scan-state-request') {
760
+ if (event.origin === window.location.origin &&
761
+ isLeafWindow(event.source)) {
762
+ sendAutomaticScanState(event.source);
763
+ }
764
+ return;
765
+ }
766
+ if (event.data.e === 'vault-automatic-mode-changed') {
767
+ if (event.origin === window.location.origin &&
768
+ isLeafWindow(event.source)) {
769
+ mergeAutomaticMode(event.data.app, event.data.mode);
770
+ }
771
+ return;
772
+ }
695
773
  if (event.data.e === 'layout-state-request') {
696
774
  let frameEntry = null;
697
775
  let frameId = null;
@@ -855,9 +933,12 @@
855
933
  </svg>`;
856
934
  const MIN_CHECKING_VISIBLE_MS = 500;
857
935
  const COMPLETION_VISIBLE_MS = 4000;
936
+ const NOTICE_EXIT_MS = 150;
937
+ const NOTICE_REFLOW_MS = 180;
858
938
 
859
939
  let eventSource = null;
860
940
  const cards = new Map();
941
+ let desiredCardOrder = [];
861
942
 
862
943
  function statusText(state) {
863
944
  if (state === 'paused') return 'Automatic checks are paused';
@@ -907,20 +988,92 @@
907
988
  card.completion = null;
908
989
  }
909
990
 
991
+ function reducedMotionRequested() {
992
+ return typeof window.matchMedia === 'function' &&
993
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches;
994
+ }
995
+
996
+ function syncTrayVisibility() {
997
+ tray.hidden = !tray.querySelector('.vault-auto-scan-row');
998
+ }
999
+
1000
+ function reorderCards() {
1001
+ const exiting = tray.querySelector('.vault-auto-scan-row.is-exiting');
1002
+ desiredCardOrder.forEach((app) => {
1003
+ const card = cards.get(app);
1004
+ if (!card || !card.item) return;
1005
+ if (!card.item.isConnected || !exiting) {
1006
+ tray.appendChild(card.item);
1007
+ }
1008
+ });
1009
+ }
1010
+
1011
+ function cardPositions() {
1012
+ const positions = new Map();
1013
+ tray.querySelectorAll(
1014
+ '.vault-auto-scan-row:not(.is-exiting)').forEach((item) => {
1015
+ positions.set(item, item.getBoundingClientRect());
1016
+ });
1017
+ return positions;
1018
+ }
1019
+
1020
+ function animateCardReflow(before) {
1021
+ if (reducedMotionRequested()) return;
1022
+ before.forEach((bounds, item) => {
1023
+ if (!item.isConnected || typeof item.animate !== 'function') return;
1024
+ const next = item.getBoundingClientRect();
1025
+ const x = bounds.left - next.left;
1026
+ const y = bounds.top - next.top;
1027
+ if (Math.abs(x) < 0.5 && Math.abs(y) < 0.5) return;
1028
+ item.animate([
1029
+ { transform: `translate3d(${x}px, ${y}px, 0)` },
1030
+ { transform: 'translate3d(0, 0, 0)' }
1031
+ ], {
1032
+ duration: NOTICE_REFLOW_MS,
1033
+ easing: 'cubic-bezier(0.25, 1, 0.5, 1)'
1034
+ });
1035
+ });
1036
+ }
1037
+
1038
+ function finishCardRemoval(card) {
1039
+ const before = cardPositions();
1040
+ card.item.remove();
1041
+ reorderCards();
1042
+ syncTrayVisibility();
1043
+ animateCardReflow(before);
1044
+ }
1045
+
910
1046
  function removeCard(app) {
911
1047
  const card = cards.get(app);
912
- if (card) {
913
- stopCompletion(card);
914
- card.item.remove();
915
- }
1048
+ if (!card) return;
1049
+ stopCompletion(card);
916
1050
  cards.delete(app);
917
- tray.hidden = cards.size === 0;
1051
+ if (!card.item.isConnected || reducedMotionRequested()) {
1052
+ finishCardRemoval(card);
1053
+ return;
1054
+ }
1055
+ card.item.classList.add('is-exiting');
1056
+ let finished = false;
1057
+ let fallbackTimer = null;
1058
+ const onAnimationEnd = (event) => {
1059
+ if (event.target === card.item &&
1060
+ event.animationName === 'vault-notice-exit') finish();
1061
+ };
1062
+ const finish = () => {
1063
+ if (finished) return;
1064
+ finished = true;
1065
+ if (fallbackTimer !== null) window.clearTimeout(fallbackTimer);
1066
+ card.item.removeEventListener('animationend', onAnimationEnd);
1067
+ finishCardRemoval(card);
1068
+ };
1069
+ card.item.addEventListener('animationend', onAnimationEnd);
1070
+ fallbackTimer = window.setTimeout(finish, NOTICE_EXIT_MS + 50);
918
1071
  }
919
1072
 
920
1073
  function resetCards() {
1074
+ desiredCardOrder = [];
921
1075
  [...cards.keys()].forEach(removeCard);
922
- tray.replaceChildren();
923
- tray.hidden = true;
1076
+ syncTrayVisibility();
924
1077
  }
925
1078
 
926
1079
  function scheduleCompletion(completion) {
@@ -1033,6 +1186,7 @@
1033
1186
  dismissPending: false,
1034
1187
  pointerInside: false,
1035
1188
  focusInside: false,
1189
+ isNew: true,
1036
1190
  completion: null
1037
1191
  };
1038
1192
  close.addEventListener('click', async () => {
@@ -1229,6 +1383,7 @@
1229
1383
  }
1230
1384
 
1231
1385
  function render(snapshot, options = {}) {
1386
+ broadcastAutomaticScanState(snapshot);
1232
1387
  if (!snapshot || snapshot.global_scan_ready !== true) {
1233
1388
  resetCards();
1234
1389
  return;
@@ -1244,8 +1399,15 @@
1244
1399
  setting && setting.mode === 'manual').map((setting) => setting.app)
1245
1400
  : []);
1246
1401
  const retained = new Set();
1402
+ let enteringCardIndex = 0;
1247
1403
  validRows.forEach((row) => {
1248
- updateCard(row);
1404
+ const card = updateCard(row);
1405
+ if (card.isNew) {
1406
+ card.item.style.setProperty('--vault-notice-enter-delay',
1407
+ `${Math.min(enteringCardIndex, 2) * 30}ms`);
1408
+ card.isNew = false;
1409
+ enteringCardIndex += 1;
1410
+ }
1249
1411
  retained.add(row.app);
1250
1412
  });
1251
1413
  const completion = snapshot.completion;
@@ -1260,19 +1422,21 @@
1260
1422
  cards.forEach((card, app) => {
1261
1423
  if (card.completion && !manualApps.has(app)) retained.add(app);
1262
1424
  });
1263
- [...cards.keys()].forEach((app) => {
1264
- if (!retained.has(app)) removeCard(app);
1265
- });
1425
+ const order = [];
1266
1426
  validRows.forEach((row) => {
1267
- const card = cards.get(row.app);
1268
- if (card) tray.appendChild(card.item);
1427
+ if (!order.includes(row.app)) order.push(row.app);
1269
1428
  });
1270
1429
  cards.forEach((card, app) => {
1271
- if (card.completion && !liveApps.has(app)) {
1272
- tray.appendChild(card.item);
1430
+ if (card.completion && !liveApps.has(app) && !order.includes(app)) {
1431
+ order.push(app);
1273
1432
  }
1274
1433
  });
1275
- tray.hidden = cards.size === 0;
1434
+ desiredCardOrder = order;
1435
+ [...cards.keys()].forEach((app) => {
1436
+ if (!retained.has(app)) removeCard(app);
1437
+ });
1438
+ reorderCards();
1439
+ syncTrayVisibility();
1276
1440
  }
1277
1441
 
1278
1442
  async function loadState() {
@@ -1308,7 +1472,6 @@
1308
1472
  }
1309
1473
  };
1310
1474
  eventSource.onerror = () => {
1311
- resetCards();
1312
1475
  loadState();
1313
1476
  };
1314
1477
  }
@@ -910,6 +910,39 @@ body[data-vault-mode="global"] .vault-explorer {
910
910
  display: grid;
911
911
  gap: 0;
912
912
  }
913
+ .vault-find-size-setting {
914
+ display: grid;
915
+ grid-template-columns: minmax(0, 1fr) auto;
916
+ align-items: center;
917
+ gap: 16px;
918
+ min-height: 68px;
919
+ padding: 12px 18px;
920
+ border-bottom: 1px solid var(--task-border);
921
+ background: var(--task-panel);
922
+ }
923
+ .vault-find-size-copy {
924
+ display: grid;
925
+ min-width: 0;
926
+ gap: 3px;
927
+ color: var(--task-text);
928
+ cursor: pointer;
929
+ }
930
+ .vault-find-size-copy strong {
931
+ font-size: 12.5px;
932
+ font-weight: 650;
933
+ line-height: 1.2;
934
+ }
935
+ .vault-find-size-copy span {
936
+ color: var(--task-muted);
937
+ font-size: 10.5px;
938
+ font-weight: 450;
939
+ line-height: 1.2;
940
+ }
941
+ .vault-select.vault-find-size-select {
942
+ min-width: 118px;
943
+ height: 34px;
944
+ cursor: pointer;
945
+ }
913
946
  .vault-find-choice {
914
947
  display: grid;
915
948
  width: 100%;
@@ -2037,6 +2070,11 @@ body.dark .vault-row-action .vault-text-button:hover:not(:disabled) {
2037
2070
  }
2038
2071
  @media (max-width: 520px) {
2039
2072
  .vault-storage-legend { column-gap: 14px; row-gap: 4px; }
2073
+ .vault-find-size-setting {
2074
+ grid-template-columns: minmax(0, 1fr);
2075
+ gap: 10px;
2076
+ }
2077
+ .vault-select.vault-find-size-select { width: 100%; }
2040
2078
  }
2041
2079
  @media (pointer: coarse) {
2042
2080
  .vault-page {
@@ -2048,6 +2086,7 @@ body.dark .vault-row-action .vault-text-button:hover:not(:disabled) {
2048
2086
  .vault-result .vault-button,
2049
2087
  .vault-group-action .vault-button { min-height: 44px; }
2050
2088
  .vault-scan-size-option { min-height: 44px; }
2089
+ .vault-select.vault-find-size-select { height: 44px; }
2051
2090
  .vault-icon-button { width: 44px; height: 44px; }
2052
2091
  .vault-find-tree-row {
2053
2092
  grid-template-columns: 44px 44px 17px minmax(0, 1fr);