pinokiod 8.0.61 → 8.0.62
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/package.json +1 -1
- package/server/public/layout.js +277 -253
- package/server/public/vault.js +0 -14
- package/server/views/app.ejs +0 -22
- package/server/views/layout.ejs +77 -79
- package/test/vault-automatic-scan-ui.test.js +145 -21
package/package.json
CHANGED
package/server/public/layout.js
CHANGED
|
@@ -241,12 +241,7 @@
|
|
|
241
241
|
iframe.addEventListener('focus', markActive);
|
|
242
242
|
iframe.addEventListener('pointerdown', markActive);
|
|
243
243
|
|
|
244
|
-
entry = {
|
|
245
|
-
container,
|
|
246
|
-
iframe,
|
|
247
|
-
updateNodeLocation,
|
|
248
|
-
vaultAutoSettingsReady: false,
|
|
249
|
-
};
|
|
244
|
+
entry = { container, iframe, updateNodeLocation };
|
|
250
245
|
leafElements.set(node.id, entry);
|
|
251
246
|
return entry;
|
|
252
247
|
}
|
|
@@ -641,7 +636,6 @@
|
|
|
641
636
|
leaf.src = state.defaultPath;
|
|
642
637
|
const entry = leafElements.get(leaf.id);
|
|
643
638
|
if (entry) {
|
|
644
|
-
entry.vaultAutoSettingsReady = false;
|
|
645
639
|
entry.iframe.src = leaf.src;
|
|
646
640
|
}
|
|
647
641
|
cleanupSessionIfSingleLeaf();
|
|
@@ -688,7 +682,6 @@
|
|
|
688
682
|
return false;
|
|
689
683
|
}
|
|
690
684
|
node.src = normalized;
|
|
691
|
-
entry.vaultAutoSettingsReady = false;
|
|
692
685
|
entry.iframe.src = normalized;
|
|
693
686
|
state.activeLeafId = frameId;
|
|
694
687
|
saveStateToStorage();
|
|
@@ -699,16 +692,6 @@
|
|
|
699
692
|
if (!event || !event.data || typeof event.data !== 'object') {
|
|
700
693
|
return;
|
|
701
694
|
}
|
|
702
|
-
if (event.data.e === 'vault-auto-settings-ready' &&
|
|
703
|
-
event.origin === window.location.origin) {
|
|
704
|
-
for (const entry of leafElements.values()) {
|
|
705
|
-
if (entry.iframe && entry.iframe.contentWindow === event.source) {
|
|
706
|
-
entry.vaultAutoSettingsReady = true;
|
|
707
|
-
break;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
695
|
if (event.data.e === 'layout-state-request') {
|
|
713
696
|
let frameEntry = null;
|
|
714
697
|
let frameId = null;
|
|
@@ -788,7 +771,6 @@
|
|
|
788
771
|
if (node.type === 'leaf') {
|
|
789
772
|
const entry = ensureLeafElement(node);
|
|
790
773
|
if (entry && entry.iframe.src !== node.src) {
|
|
791
|
-
entry.vaultAutoSettingsReady = false;
|
|
792
774
|
entry.iframe.src = node.src;
|
|
793
775
|
}
|
|
794
776
|
}
|
|
@@ -874,21 +856,23 @@
|
|
|
874
856
|
const COMPLETION_VISIBLE_MS = 4000;
|
|
875
857
|
|
|
876
858
|
let eventSource = null;
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if (
|
|
882
|
-
|
|
859
|
+
const cards = new Map();
|
|
860
|
+
|
|
861
|
+
function statusText(state) {
|
|
862
|
+
if (state === 'paused') return 'Automatic checks are paused';
|
|
863
|
+
if (state === 'result') return 'Possible duplicate files found';
|
|
864
|
+
if (state === 'complete') return 'No possible duplicate files found';
|
|
865
|
+
if (state === 'checking-again') {
|
|
866
|
+
return 'Checking again for possible duplicate files...';
|
|
883
867
|
}
|
|
884
|
-
return 'Checking for possible duplicate files
|
|
868
|
+
return 'Checking for possible duplicate files...';
|
|
885
869
|
}
|
|
886
870
|
|
|
887
|
-
function actionFor(
|
|
888
|
-
if (
|
|
871
|
+
function actionFor(state) {
|
|
872
|
+
if (state === 'paused') {
|
|
889
873
|
return { label: 'Resume', action: 'automatic_resume' };
|
|
890
874
|
}
|
|
891
|
-
if (
|
|
875
|
+
if (state === 'result') {
|
|
892
876
|
return { label: 'Review', action: 'automatic_review' };
|
|
893
877
|
}
|
|
894
878
|
return { label: 'Pause', action: 'automatic_pause' };
|
|
@@ -915,21 +899,27 @@
|
|
|
915
899
|
return result || {};
|
|
916
900
|
}
|
|
917
901
|
|
|
918
|
-
function
|
|
919
|
-
|
|
920
|
-
tray.hidden = tray.childElementCount === 0;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
function removeCompletion(app) {
|
|
924
|
-
const completion = completions.get(app);
|
|
902
|
+
function stopCompletion(card) {
|
|
903
|
+
const completion = card && card.completion;
|
|
925
904
|
if (!completion) return;
|
|
926
905
|
if (completion.timer) window.clearTimeout(completion.timer);
|
|
927
|
-
|
|
928
|
-
removeRow(completion.item);
|
|
906
|
+
card.completion = null;
|
|
929
907
|
}
|
|
930
908
|
|
|
931
|
-
function
|
|
932
|
-
|
|
909
|
+
function removeCard(app) {
|
|
910
|
+
const card = cards.get(app);
|
|
911
|
+
if (card) {
|
|
912
|
+
stopCompletion(card);
|
|
913
|
+
card.item.remove();
|
|
914
|
+
}
|
|
915
|
+
cards.delete(app);
|
|
916
|
+
tray.hidden = cards.size === 0;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function resetCards() {
|
|
920
|
+
[...cards.keys()].forEach(removeCard);
|
|
921
|
+
tray.replaceChildren();
|
|
922
|
+
tray.hidden = true;
|
|
933
923
|
}
|
|
934
924
|
|
|
935
925
|
function scheduleCompletion(completion) {
|
|
@@ -937,11 +927,12 @@
|
|
|
937
927
|
completion.startedAt = Date.now();
|
|
938
928
|
completion.timer = window.setTimeout(() => {
|
|
939
929
|
completion.timer = null;
|
|
940
|
-
|
|
930
|
+
removeCard(completion.app);
|
|
941
931
|
}, completion.remaining);
|
|
942
932
|
}
|
|
943
933
|
|
|
944
934
|
function setCompletionPaused(completion, reason, paused) {
|
|
935
|
+
if (!completion) return;
|
|
945
936
|
if (paused) {
|
|
946
937
|
if (completion.paused.has(reason)) return;
|
|
947
938
|
if (!completion.paused.size && completion.timer) {
|
|
@@ -957,60 +948,6 @@
|
|
|
957
948
|
if (!completion.paused.size) scheduleCompletion(completion);
|
|
958
949
|
}
|
|
959
950
|
|
|
960
|
-
function startCompletion(app) {
|
|
961
|
-
if (completions.has(app)) return;
|
|
962
|
-
const item = document.createElement('div');
|
|
963
|
-
item.className = 'vault-auto-scan-row';
|
|
964
|
-
item.dataset.state = 'complete';
|
|
965
|
-
|
|
966
|
-
const close = document.createElement('button');
|
|
967
|
-
close.type = 'button';
|
|
968
|
-
close.className = 'vault-auto-scan-close';
|
|
969
|
-
close.setAttribute('aria-label',
|
|
970
|
-
`Dismiss Disk Saver completion for ${app}`);
|
|
971
|
-
close.title = 'Dismiss';
|
|
972
|
-
close.textContent = '×';
|
|
973
|
-
|
|
974
|
-
const icon = document.createElement('span');
|
|
975
|
-
icon.className = 'vault-auto-scan-icon';
|
|
976
|
-
icon.innerHTML = COMPLETE_ICON;
|
|
977
|
-
|
|
978
|
-
const copy = document.createElement('span');
|
|
979
|
-
copy.className = 'vault-auto-scan-copy';
|
|
980
|
-
const appName = document.createElement('span');
|
|
981
|
-
appName.className = 'vault-auto-scan-app';
|
|
982
|
-
appName.textContent = app;
|
|
983
|
-
appName.title = app;
|
|
984
|
-
const status = document.createElement('span');
|
|
985
|
-
status.className = 'vault-auto-scan-status';
|
|
986
|
-
status.textContent = 'No possible duplicate files found';
|
|
987
|
-
copy.append(appName, status);
|
|
988
|
-
item.append(close, icon, copy);
|
|
989
|
-
|
|
990
|
-
const completion = {
|
|
991
|
-
app,
|
|
992
|
-
item,
|
|
993
|
-
timer: null,
|
|
994
|
-
remaining: COMPLETION_VISIBLE_MS,
|
|
995
|
-
startedAt: 0,
|
|
996
|
-
paused: new Set()
|
|
997
|
-
};
|
|
998
|
-
close.addEventListener('click', () => removeCompletion(app));
|
|
999
|
-
item.addEventListener('mouseenter', () =>
|
|
1000
|
-
setCompletionPaused(completion, 'pointer', true));
|
|
1001
|
-
item.addEventListener('mouseleave', () =>
|
|
1002
|
-
setCompletionPaused(completion, 'pointer', false));
|
|
1003
|
-
item.addEventListener('focusin', () =>
|
|
1004
|
-
setCompletionPaused(completion, 'focus', true));
|
|
1005
|
-
item.addEventListener('focusout', (event) => {
|
|
1006
|
-
if (!item.contains(event.relatedTarget)) {
|
|
1007
|
-
setCompletionPaused(completion, 'focus', false);
|
|
1008
|
-
}
|
|
1009
|
-
});
|
|
1010
|
-
completions.set(app, completion);
|
|
1011
|
-
scheduleCompletion(completion);
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
951
|
function automaticSettingsKey(app) {
|
|
1015
952
|
return `pinokio:vault:auto-settings:${encodeURIComponent(app)}`;
|
|
1016
953
|
}
|
|
@@ -1024,24 +961,6 @@
|
|
|
1024
961
|
try {
|
|
1025
962
|
sessionStorage.setItem(storageKey, '1');
|
|
1026
963
|
} catch (_) {}
|
|
1027
|
-
let frameId = state.activeLeafId;
|
|
1028
|
-
if (!frameId || !leafElements.has(frameId)) {
|
|
1029
|
-
frameId = leafElements.keys().next().value || null;
|
|
1030
|
-
}
|
|
1031
|
-
const node = frameId ? nodeById.get(frameId) : null;
|
|
1032
|
-
const entry = frameId ? leafElements.get(frameId) : null;
|
|
1033
|
-
let currentPath = '';
|
|
1034
|
-
let targetPath = '';
|
|
1035
|
-
try {
|
|
1036
|
-
currentPath = new URL(node?.src || '', window.location.href).pathname;
|
|
1037
|
-
targetPath = new URL(href, window.location.href).pathname;
|
|
1038
|
-
} catch (_) {}
|
|
1039
|
-
if (entry && entry.vaultAutoSettingsReady &&
|
|
1040
|
-
currentPath && currentPath === targetPath) {
|
|
1041
|
-
entry.iframe?.contentWindow?.postMessage(
|
|
1042
|
-
{ e: 'vault-auto-settings' }, window.location.origin);
|
|
1043
|
-
return true;
|
|
1044
|
-
}
|
|
1045
964
|
const opened = navigateActiveLeaf(href);
|
|
1046
965
|
if (!opened) {
|
|
1047
966
|
try { sessionStorage.removeItem(storageKey); } catch (_) {}
|
|
@@ -1067,166 +986,271 @@
|
|
|
1067
986
|
return opened;
|
|
1068
987
|
}
|
|
1069
988
|
|
|
989
|
+
function createCard(app) {
|
|
990
|
+
const item = document.createElement('section');
|
|
991
|
+
item.className = 'vault-auto-scan-row';
|
|
992
|
+
|
|
993
|
+
const close = document.createElement('button');
|
|
994
|
+
close.type = 'button';
|
|
995
|
+
close.className = 'vault-auto-scan-close';
|
|
996
|
+
close.setAttribute('aria-label',
|
|
997
|
+
`Dismiss Disk Saver notification for ${app}`);
|
|
998
|
+
close.title = 'Dismiss';
|
|
999
|
+
close.textContent = '×';
|
|
1000
|
+
|
|
1001
|
+
const header = document.createElement('header');
|
|
1002
|
+
header.className = 'vault-auto-scan-header';
|
|
1003
|
+
const product = document.createElement('span');
|
|
1004
|
+
product.className = 'vault-auto-scan-product';
|
|
1005
|
+
product.textContent = 'Disk Saver';
|
|
1006
|
+
const separator = document.createElement('span');
|
|
1007
|
+
separator.className = 'vault-auto-scan-separator';
|
|
1008
|
+
separator.setAttribute('aria-hidden', 'true');
|
|
1009
|
+
separator.textContent = '·';
|
|
1010
|
+
const appName = document.createElement('span');
|
|
1011
|
+
appName.className = 'vault-auto-scan-app';
|
|
1012
|
+
appName.textContent = app;
|
|
1013
|
+
appName.title = app;
|
|
1014
|
+
header.append(product, separator, appName);
|
|
1015
|
+
|
|
1016
|
+
const messages = document.createElement('div');
|
|
1017
|
+
messages.className = 'vault-auto-scan-messages';
|
|
1018
|
+
const controls = document.createElement('div');
|
|
1019
|
+
controls.className = 'vault-auto-scan-controls';
|
|
1020
|
+
item.append(close, header, messages, controls);
|
|
1021
|
+
|
|
1022
|
+
const card = {
|
|
1023
|
+
app,
|
|
1024
|
+
item,
|
|
1025
|
+
messages,
|
|
1026
|
+
controls,
|
|
1027
|
+
currentState: null,
|
|
1028
|
+
currentNoticeId: null,
|
|
1029
|
+
dismissPending: false,
|
|
1030
|
+
pointerInside: false,
|
|
1031
|
+
focusInside: false,
|
|
1032
|
+
completion: null
|
|
1033
|
+
};
|
|
1034
|
+
close.addEventListener('click', async () => {
|
|
1035
|
+
if (card.currentState === 'complete') {
|
|
1036
|
+
removeCard(app);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
card.dismissPending = card.currentState === 'checking';
|
|
1040
|
+
close.disabled = true;
|
|
1041
|
+
try {
|
|
1042
|
+
const result = await requestAction(
|
|
1043
|
+
'automatic_dismiss', app, card.currentNoticeId);
|
|
1044
|
+
if (result.stale) {
|
|
1045
|
+
card.dismissPending = false;
|
|
1046
|
+
await loadState();
|
|
1047
|
+
if (close.isConnected) close.disabled = false;
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
removeCard(app);
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
console.warn('[Disk Saver] Automatic check dismissal failed', error);
|
|
1053
|
+
card.dismissPending = false;
|
|
1054
|
+
close.disabled = false;
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
item.addEventListener('mouseenter', () => {
|
|
1058
|
+
card.pointerInside = true;
|
|
1059
|
+
setCompletionPaused(card.completion, 'pointer', true);
|
|
1060
|
+
});
|
|
1061
|
+
item.addEventListener('mouseleave', () => {
|
|
1062
|
+
card.pointerInside = false;
|
|
1063
|
+
setCompletionPaused(card.completion, 'pointer', false);
|
|
1064
|
+
});
|
|
1065
|
+
item.addEventListener('focusin', () => {
|
|
1066
|
+
card.focusInside = true;
|
|
1067
|
+
setCompletionPaused(card.completion, 'focus', true);
|
|
1068
|
+
});
|
|
1069
|
+
item.addEventListener('focusout', (event) => {
|
|
1070
|
+
if (!item.contains(event.relatedTarget)) {
|
|
1071
|
+
card.focusInside = false;
|
|
1072
|
+
setCompletionPaused(card.completion, 'focus', false);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
cards.set(app, card);
|
|
1076
|
+
return card;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function messageIcon(state) {
|
|
1080
|
+
if (state === 'paused') return PAUSE_ICON;
|
|
1081
|
+
if (state === 'result') return DRIVE_ICON;
|
|
1082
|
+
if (state === 'complete') return COMPLETE_ICON;
|
|
1083
|
+
return '';
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function appendMessage(card, state) {
|
|
1087
|
+
const previous = card.messages.querySelector('[data-current="true"]');
|
|
1088
|
+
if (previous) {
|
|
1089
|
+
previous.dataset.current = 'false';
|
|
1090
|
+
previous.removeAttribute('aria-current');
|
|
1091
|
+
}
|
|
1092
|
+
const message = document.createElement('div');
|
|
1093
|
+
message.className = 'vault-auto-scan-message';
|
|
1094
|
+
message.dataset.state = state === 'checking-again' ? 'checking' : state;
|
|
1095
|
+
message.dataset.current = 'true';
|
|
1096
|
+
message.setAttribute('aria-current', 'true');
|
|
1097
|
+
const icon = document.createElement('span');
|
|
1098
|
+
icon.className = 'vault-auto-scan-icon';
|
|
1099
|
+
icon.innerHTML = messageIcon(state);
|
|
1100
|
+
const status = document.createElement('span');
|
|
1101
|
+
status.className = 'vault-auto-scan-status';
|
|
1102
|
+
status.textContent = statusText(state);
|
|
1103
|
+
message.append(icon, status);
|
|
1104
|
+
card.messages.appendChild(message);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function addSettingsButton(card) {
|
|
1108
|
+
const settings = document.createElement('button');
|
|
1109
|
+
settings.type = 'button';
|
|
1110
|
+
settings.className = 'vault-auto-scan-settings';
|
|
1111
|
+
settings.textContent = 'Automatic check settings';
|
|
1112
|
+
settings.addEventListener('click', async () => {
|
|
1113
|
+
settings.disabled = true;
|
|
1114
|
+
try {
|
|
1115
|
+
const result = await requestAction(
|
|
1116
|
+
'automatic_settings', card.app);
|
|
1117
|
+
if (!result.href || !openAutomaticSettings(card.app, result.href)) {
|
|
1118
|
+
throw new Error('The app page is unavailable.');
|
|
1119
|
+
}
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
console.warn('[Disk Saver] Automatic check settings failed', error);
|
|
1122
|
+
} finally {
|
|
1123
|
+
if (settings.isConnected) settings.disabled = false;
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
card.controls.appendChild(settings);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function addStateAction(card, row) {
|
|
1130
|
+
const action = actionFor(row.state);
|
|
1131
|
+
const button = document.createElement('button');
|
|
1132
|
+
button.type = 'button';
|
|
1133
|
+
button.className = 'vault-auto-scan-action';
|
|
1134
|
+
button.textContent = action.label;
|
|
1135
|
+
button.addEventListener('click', async () => {
|
|
1136
|
+
button.disabled = true;
|
|
1137
|
+
try {
|
|
1138
|
+
const result = await requestAction(
|
|
1139
|
+
action.action, card.app, row.notice_id);
|
|
1140
|
+
if (result.stale) {
|
|
1141
|
+
await loadState();
|
|
1142
|
+
if (button.isConnected) button.disabled = false;
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (action.action === 'automatic_review' && result.href) {
|
|
1146
|
+
if (!openAutomaticReview(card.app, result.href)) {
|
|
1147
|
+
throw new Error('The app page could not be opened.');
|
|
1148
|
+
}
|
|
1149
|
+
removeCard(card.app);
|
|
1150
|
+
} else {
|
|
1151
|
+
await loadState();
|
|
1152
|
+
}
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
console.warn('[Disk Saver] Automatic check action failed', error);
|
|
1155
|
+
if (button.isConnected) button.disabled = false;
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
card.controls.appendChild(button);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function renderControls(card, row) {
|
|
1162
|
+
card.controls.replaceChildren();
|
|
1163
|
+
if (row.state === 'complete') return;
|
|
1164
|
+
if (row.state !== 'result') addSettingsButton(card);
|
|
1165
|
+
addStateAction(card, row);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function updateCard(row) {
|
|
1169
|
+
let card = cards.get(row.app);
|
|
1170
|
+
if (!card) card = createCard(row.app);
|
|
1171
|
+
const unchanged = card.currentState === row.state &&
|
|
1172
|
+
card.currentNoticeId === (row.notice_id || null);
|
|
1173
|
+
if (unchanged) return card;
|
|
1174
|
+
stopCompletion(card);
|
|
1175
|
+
const displayState = row.state === 'checking' &&
|
|
1176
|
+
card.currentState === 'paused' ? 'checking-again' : row.state;
|
|
1177
|
+
appendMessage(card, displayState);
|
|
1178
|
+
card.currentState = row.state;
|
|
1179
|
+
card.currentNoticeId = row.notice_id || null;
|
|
1180
|
+
card.dismissPending = false;
|
|
1181
|
+
card.item.dataset.state = row.state;
|
|
1182
|
+
renderControls(card, row);
|
|
1183
|
+
return card;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function startCompletion(app) {
|
|
1187
|
+
const card = cards.get(app);
|
|
1188
|
+
if (!card || card.currentState !== 'checking' ||
|
|
1189
|
+
card.dismissPending || card.completion) return false;
|
|
1190
|
+
appendMessage(card, 'complete');
|
|
1191
|
+
card.currentState = 'complete';
|
|
1192
|
+
card.currentNoticeId = null;
|
|
1193
|
+
card.item.dataset.state = 'complete';
|
|
1194
|
+
renderControls(card, { state: 'complete' });
|
|
1195
|
+
const completion = {
|
|
1196
|
+
app,
|
|
1197
|
+
timer: null,
|
|
1198
|
+
remaining: COMPLETION_VISIBLE_MS,
|
|
1199
|
+
startedAt: 0,
|
|
1200
|
+
paused: new Set()
|
|
1201
|
+
};
|
|
1202
|
+
if (card.pointerInside) completion.paused.add('pointer');
|
|
1203
|
+
if (card.focusInside) completion.paused.add('focus');
|
|
1204
|
+
card.completion = completion;
|
|
1205
|
+
scheduleCompletion(completion);
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1070
1209
|
function render(snapshot, options = {}) {
|
|
1071
1210
|
if (!snapshot || snapshot.global_scan_ready !== true) {
|
|
1072
|
-
|
|
1073
|
-
visibleCheckingApps = new Set();
|
|
1074
|
-
tray.replaceChildren();
|
|
1075
|
-
tray.hidden = true;
|
|
1211
|
+
resetCards();
|
|
1076
1212
|
return;
|
|
1077
1213
|
}
|
|
1078
|
-
const rows =
|
|
1214
|
+
const rows = Array.isArray(snapshot.rows)
|
|
1079
1215
|
? snapshot.rows
|
|
1080
1216
|
: [];
|
|
1081
1217
|
const validRows = rows.filter((row) =>
|
|
1082
1218
|
row && typeof row.app === 'string' && row.app);
|
|
1083
1219
|
const liveApps = new Set(validRows.map((row) => row.app));
|
|
1084
|
-
|
|
1085
|
-
if (liveApps.has(app)) removeCompletion(app);
|
|
1086
|
-
});
|
|
1087
|
-
const manualApps = new Set(snapshot && Array.isArray(snapshot.settings)
|
|
1220
|
+
const manualApps = new Set(Array.isArray(snapshot.settings)
|
|
1088
1221
|
? snapshot.settings.filter((setting) =>
|
|
1089
1222
|
setting && setting.mode === 'manual').map((setting) => setting.app)
|
|
1090
1223
|
: []);
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1224
|
+
const retained = new Set();
|
|
1225
|
+
validRows.forEach((row) => {
|
|
1226
|
+
updateCard(row);
|
|
1227
|
+
retained.add(row.app);
|
|
1228
|
+
});
|
|
1229
|
+
const completion = snapshot.completion;
|
|
1094
1230
|
if (options.acceptCompletion && completion &&
|
|
1095
1231
|
completion.outcome === 'no_possible_duplicates' &&
|
|
1096
1232
|
typeof completion.app === 'string' && completion.app &&
|
|
1097
|
-
visibleCheckingApps.has(completion.app) &&
|
|
1098
1233
|
!liveApps.has(completion.app) &&
|
|
1099
|
-
!manualApps.has(completion.app)
|
|
1100
|
-
|
|
1234
|
+
!manualApps.has(completion.app) &&
|
|
1235
|
+
startCompletion(completion.app)) {
|
|
1236
|
+
retained.add(completion.app);
|
|
1101
1237
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1238
|
+
cards.forEach((card, app) => {
|
|
1239
|
+
if (card.completion && !manualApps.has(app)) retained.add(app);
|
|
1240
|
+
});
|
|
1241
|
+
[...cards.keys()].forEach((app) => {
|
|
1242
|
+
if (!retained.has(app)) removeCard(app);
|
|
1243
|
+
});
|
|
1104
1244
|
validRows.forEach((row) => {
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
item.dataset.state = row.state || 'checking';
|
|
1108
|
-
|
|
1109
|
-
const close = document.createElement('button');
|
|
1110
|
-
close.type = 'button';
|
|
1111
|
-
close.className = 'vault-auto-scan-close';
|
|
1112
|
-
close.setAttribute('aria-label', `Dismiss Disk Saver notification for ${row.app}`);
|
|
1113
|
-
close.title = 'Dismiss';
|
|
1114
|
-
close.textContent = '×';
|
|
1115
|
-
close.addEventListener('click', async () => {
|
|
1116
|
-
if (row.state === 'checking') visibleCheckingApps.delete(row.app);
|
|
1117
|
-
close.disabled = true;
|
|
1118
|
-
try {
|
|
1119
|
-
const result = await requestAction(
|
|
1120
|
-
'automatic_dismiss', row.app, row.notice_id);
|
|
1121
|
-
if (result.stale) {
|
|
1122
|
-
await loadState();
|
|
1123
|
-
if (close.isConnected) close.disabled = false;
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
removeRow(item);
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
console.warn('[Disk Saver] Automatic check dismissal failed', error);
|
|
1129
|
-
if (row.state === 'checking' && item.isConnected) {
|
|
1130
|
-
visibleCheckingApps.add(row.app);
|
|
1131
|
-
}
|
|
1132
|
-
close.disabled = false;
|
|
1133
|
-
}
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
|
-
const icon = document.createElement('span');
|
|
1137
|
-
icon.className = 'vault-auto-scan-icon';
|
|
1138
|
-
if (row.state === 'paused') {
|
|
1139
|
-
icon.innerHTML = PAUSE_ICON;
|
|
1140
|
-
} else if (row.state === 'result') {
|
|
1141
|
-
icon.innerHTML = DRIVE_ICON;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
const copy = document.createElement('span');
|
|
1145
|
-
copy.className = 'vault-auto-scan-copy';
|
|
1146
|
-
const appName = document.createElement('span');
|
|
1147
|
-
appName.className = 'vault-auto-scan-app';
|
|
1148
|
-
appName.textContent = row.app;
|
|
1149
|
-
appName.title = row.app;
|
|
1150
|
-
const status = document.createElement('span');
|
|
1151
|
-
status.className = 'vault-auto-scan-status';
|
|
1152
|
-
if (row.state === 'result') {
|
|
1153
|
-
const detail = document.createElement('span');
|
|
1154
|
-
detail.className = 'vault-auto-scan-detail';
|
|
1155
|
-
detail.textContent = 'may have duplicate files';
|
|
1156
|
-
status.append(detail);
|
|
1157
|
-
} else {
|
|
1158
|
-
status.textContent = statusText(row);
|
|
1159
|
-
}
|
|
1160
|
-
copy.append(appName, status);
|
|
1161
|
-
|
|
1162
|
-
const controls = document.createElement('span');
|
|
1163
|
-
controls.className = 'vault-auto-scan-controls';
|
|
1164
|
-
if (row.state !== 'result') {
|
|
1165
|
-
const settings = document.createElement('button');
|
|
1166
|
-
settings.type = 'button';
|
|
1167
|
-
settings.className = 'vault-auto-scan-settings';
|
|
1168
|
-
settings.textContent = 'Automatic check settings';
|
|
1169
|
-
settings.addEventListener('click', async () => {
|
|
1170
|
-
settings.disabled = true;
|
|
1171
|
-
try {
|
|
1172
|
-
const result = await requestAction(
|
|
1173
|
-
'automatic_settings', row.app);
|
|
1174
|
-
if (!result.href || !openAutomaticSettings(row.app, result.href)) {
|
|
1175
|
-
throw new Error('The app page is unavailable.');
|
|
1176
|
-
}
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
console.warn('[Disk Saver] Automatic check settings failed', error);
|
|
1179
|
-
} finally {
|
|
1180
|
-
if (settings.isConnected) settings.disabled = false;
|
|
1181
|
-
}
|
|
1182
|
-
});
|
|
1183
|
-
copy.appendChild(settings);
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
const action = actionFor(row);
|
|
1187
|
-
const button = document.createElement('button');
|
|
1188
|
-
button.type = 'button';
|
|
1189
|
-
button.className = 'vault-auto-scan-action';
|
|
1190
|
-
button.textContent = action.label;
|
|
1191
|
-
button.addEventListener('click', async () => {
|
|
1192
|
-
button.disabled = true;
|
|
1193
|
-
try {
|
|
1194
|
-
const result = await requestAction(
|
|
1195
|
-
action.action, row.app, row.notice_id);
|
|
1196
|
-
if (result.stale) {
|
|
1197
|
-
await loadState();
|
|
1198
|
-
if (button.isConnected) button.disabled = false;
|
|
1199
|
-
return;
|
|
1200
|
-
}
|
|
1201
|
-
if (action.action === 'automatic_review' && result.href) {
|
|
1202
|
-
if (!openAutomaticReview(row.app, result.href)) {
|
|
1203
|
-
throw new Error('The app page could not be opened.');
|
|
1204
|
-
}
|
|
1205
|
-
removeRow(item);
|
|
1206
|
-
} else {
|
|
1207
|
-
await loadState();
|
|
1208
|
-
}
|
|
1209
|
-
} catch (error) {
|
|
1210
|
-
console.warn('[Disk Saver] Automatic check action failed', error);
|
|
1211
|
-
button.disabled = false;
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
|
|
1215
|
-
controls.appendChild(button);
|
|
1216
|
-
|
|
1217
|
-
item.append(close, icon, copy, controls);
|
|
1218
|
-
fragment.appendChild(item);
|
|
1245
|
+
const card = cards.get(row.app);
|
|
1246
|
+
if (card) tray.appendChild(card.item);
|
|
1219
1247
|
});
|
|
1220
|
-
|
|
1221
|
-
if (!liveApps.has(
|
|
1222
|
-
|
|
1248
|
+
cards.forEach((card, app) => {
|
|
1249
|
+
if (card.completion && !liveApps.has(app)) {
|
|
1250
|
+
tray.appendChild(card.item);
|
|
1223
1251
|
}
|
|
1224
1252
|
});
|
|
1225
|
-
tray.
|
|
1226
|
-
tray.hidden = tray.childElementCount === 0;
|
|
1227
|
-
visibleCheckingApps = new Set(validRows
|
|
1228
|
-
.filter((row) => row.state === 'checking')
|
|
1229
|
-
.map((row) => row.app));
|
|
1253
|
+
tray.hidden = cards.size === 0;
|
|
1230
1254
|
}
|
|
1231
1255
|
|
|
1232
1256
|
async function loadState() {
|
|
@@ -1262,7 +1286,7 @@
|
|
|
1262
1286
|
}
|
|
1263
1287
|
};
|
|
1264
1288
|
eventSource.onerror = () => {
|
|
1265
|
-
|
|
1289
|
+
resetCards();
|
|
1266
1290
|
loadState();
|
|
1267
1291
|
};
|
|
1268
1292
|
}
|
package/server/public/vault.js
CHANGED
|
@@ -582,14 +582,6 @@ const connectAutomaticMode = () => {
|
|
|
582
582
|
} catch (error) {}
|
|
583
583
|
}
|
|
584
584
|
}
|
|
585
|
-
const requestAutomaticModeMenu = () => {
|
|
586
|
-
if (!IS_APP_MODE) return
|
|
587
|
-
if (automaticSettingsKey) {
|
|
588
|
-
try { sessionStorage.removeItem(automaticSettingsKey) } catch (error) {}
|
|
589
|
-
}
|
|
590
|
-
state.automaticModeMenuRequested = true
|
|
591
|
-
if (state.data) renderOverview()
|
|
592
|
-
}
|
|
593
585
|
const sourceById = (id) => (state.data.sources || []).find((source) => source.id === id)
|
|
594
586
|
const sourceChildren = (id) => (state.data.sources || []).filter((source) => source.parent_id === id)
|
|
595
587
|
const sourceIsWithinScope = (sourceId) => {
|
|
@@ -4186,12 +4178,6 @@ if (IS_APP_MODE) {
|
|
|
4186
4178
|
const trigger = menu.querySelector("summary")
|
|
4187
4179
|
if (trigger) trigger.focus()
|
|
4188
4180
|
}, true)
|
|
4189
|
-
window.addEventListener("message", (event) => {
|
|
4190
|
-
if (event.source !== window.parent ||
|
|
4191
|
-
event.origin !== window.location.origin ||
|
|
4192
|
-
!event.data || event.data.e !== "vault-auto-settings") return
|
|
4193
|
-
requestAutomaticModeMenu()
|
|
4194
|
-
})
|
|
4195
4181
|
loadAutomaticMode().finally(connectAutomaticMode)
|
|
4196
4182
|
window.addEventListener("beforeunload", () => {
|
|
4197
4183
|
if (automaticModeEventSource) automaticModeEventSource.close()
|
package/server/views/app.ejs
CHANGED
|
@@ -12889,28 +12889,6 @@ const rerenderMenuSection = (container, html) => {
|
|
|
12889
12889
|
|
|
12890
12890
|
|
|
12891
12891
|
});
|
|
12892
|
-
const openVaultAutomaticSettings = () => {
|
|
12893
|
-
const frame = document.querySelector("iframe[name='app-vault']")
|
|
12894
|
-
if (!frame || !frame.contentWindow) return false
|
|
12895
|
-
frame.contentWindow.postMessage(
|
|
12896
|
-
{ e: "vault-auto-settings" }, window.location.origin)
|
|
12897
|
-
return true
|
|
12898
|
-
}
|
|
12899
|
-
window.addEventListener("message", async (event) => {
|
|
12900
|
-
if (event.source !== window.parent ||
|
|
12901
|
-
event.origin !== window.location.origin ||
|
|
12902
|
-
!event.data || event.data.e !== "vault-auto-settings") return
|
|
12903
|
-
const diskSaverTab = document.querySelector("#save-space-tab")
|
|
12904
|
-
if (!diskSaverTab) return
|
|
12905
|
-
if (diskSaverTab.classList.contains("selected") &&
|
|
12906
|
-
openVaultAutomaticSettings()) return
|
|
12907
|
-
await renderSelection({ target: diskSaverTab, force: true })
|
|
12908
|
-
openVaultAutomaticSettings()
|
|
12909
|
-
})
|
|
12910
|
-
if (window.parent !== window) {
|
|
12911
|
-
window.parent.postMessage(
|
|
12912
|
-
{ e: "vault-auto-settings-ready" }, window.location.origin)
|
|
12913
|
-
}
|
|
12914
12892
|
renderSelection({ force: true })
|
|
12915
12893
|
<% if (type === "browse" || type === "files") { %>
|
|
12916
12894
|
const repoStatusCache = new Map()
|
package/server/views/layout.ejs
CHANGED
|
@@ -161,15 +161,12 @@
|
|
|
161
161
|
|
|
162
162
|
.vault-auto-scan-row {
|
|
163
163
|
position: relative;
|
|
164
|
-
display:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
align-items: center;
|
|
168
|
-
column-gap: 10px;
|
|
169
|
-
min-height: 86px;
|
|
170
|
-
padding: 9px 34px 9px 12px;
|
|
164
|
+
display: flex;
|
|
165
|
+
min-width: 0;
|
|
166
|
+
padding: 10px 12px;
|
|
171
167
|
box-sizing: border-box;
|
|
172
|
-
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
gap: 8px;
|
|
173
170
|
color: var(--vault-notice-text);
|
|
174
171
|
background: var(--vault-notice-surface);
|
|
175
172
|
border: 1px solid var(--vault-notice-border);
|
|
@@ -179,10 +176,56 @@
|
|
|
179
176
|
animation: vault-notice-enter 180ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
180
177
|
}
|
|
181
178
|
|
|
179
|
+
.vault-auto-scan-header {
|
|
180
|
+
display: flex;
|
|
181
|
+
min-width: 0;
|
|
182
|
+
min-height: 18px;
|
|
183
|
+
padding-right: 24px;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 6px;
|
|
186
|
+
font: 500 11px/18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.vault-auto-scan-product {
|
|
190
|
+
color: var(--vault-notice-text);
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
white-space: nowrap;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.vault-auto-scan-separator {
|
|
196
|
+
color: var(--vault-notice-muted);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.vault-auto-scan-app {
|
|
200
|
+
min-width: 0;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
color: var(--vault-notice-muted);
|
|
203
|
+
text-overflow: ellipsis;
|
|
204
|
+
white-space: nowrap;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.vault-auto-scan-messages {
|
|
208
|
+
display: grid;
|
|
209
|
+
min-width: 0;
|
|
210
|
+
gap: 4px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.vault-auto-scan-message {
|
|
214
|
+
display: grid;
|
|
215
|
+
min-width: 0;
|
|
216
|
+
min-height: 22px;
|
|
217
|
+
grid-template-columns: 20px minmax(0, 1fr);
|
|
218
|
+
align-items: center;
|
|
219
|
+
column-gap: 8px;
|
|
220
|
+
transition: opacity 120ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.vault-auto-scan-message[data-current="false"] {
|
|
224
|
+
opacity: 0.58;
|
|
225
|
+
}
|
|
226
|
+
|
|
182
227
|
.vault-auto-scan-icon {
|
|
183
228
|
display: grid;
|
|
184
|
-
grid-column: 1;
|
|
185
|
-
grid-row: 1 / 4;
|
|
186
229
|
place-items: center;
|
|
187
230
|
width: 20px;
|
|
188
231
|
height: 20px;
|
|
@@ -199,57 +242,43 @@
|
|
|
199
242
|
stroke-width: 1.8;
|
|
200
243
|
}
|
|
201
244
|
|
|
202
|
-
.vault-auto-scan-
|
|
245
|
+
.vault-auto-scan-message[data-state="checking"] .vault-auto-scan-icon {
|
|
203
246
|
border: 1.5px solid var(--vault-notice-border);
|
|
204
|
-
border-top-color: var(--vault-notice-text);
|
|
205
247
|
border-radius: 50%;
|
|
206
248
|
box-sizing: border-box;
|
|
207
|
-
animation: vault-notice-spin 900ms linear infinite;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.vault-auto-scan-copy {
|
|
211
|
-
display: grid;
|
|
212
|
-
grid-column: 2;
|
|
213
|
-
grid-row: 1 / 4;
|
|
214
|
-
grid-template-rows: 16px 34px 18px;
|
|
215
|
-
align-items: center;
|
|
216
|
-
min-width: 0;
|
|
217
249
|
}
|
|
218
250
|
|
|
219
|
-
.vault-auto-scan-
|
|
220
|
-
|
|
221
|
-
color: var(--vault-notice-
|
|
222
|
-
|
|
223
|
-
font-weight: 500;
|
|
224
|
-
line-height: 16px;
|
|
225
|
-
text-overflow: ellipsis;
|
|
226
|
-
white-space: nowrap;
|
|
251
|
+
.vault-auto-scan-message[data-current="true"][data-state="checking"]
|
|
252
|
+
.vault-auto-scan-icon {
|
|
253
|
+
border-top-color: var(--vault-notice-text);
|
|
254
|
+
animation: vault-notice-spin 900ms linear infinite;
|
|
227
255
|
}
|
|
228
256
|
|
|
229
257
|
.vault-auto-scan-status {
|
|
230
|
-
display: flex;
|
|
231
258
|
min-width: 0;
|
|
232
|
-
align-items: baseline;
|
|
233
259
|
overflow: hidden;
|
|
234
260
|
font-size: 13px;
|
|
235
261
|
font-weight: 600;
|
|
236
|
-
line-height:
|
|
262
|
+
line-height: 20px;
|
|
237
263
|
text-overflow: ellipsis;
|
|
238
264
|
white-space: nowrap;
|
|
239
265
|
}
|
|
240
266
|
|
|
241
|
-
.vault-auto-scan-
|
|
242
|
-
|
|
243
|
-
color: var(--vault-notice-muted);
|
|
244
|
-
font-weight: 400;
|
|
245
|
-
text-overflow: ellipsis;
|
|
267
|
+
.vault-auto-scan-message[data-current="false"] .vault-auto-scan-status {
|
|
268
|
+
font-weight: 500;
|
|
246
269
|
}
|
|
247
270
|
|
|
248
271
|
.vault-auto-scan-controls {
|
|
249
|
-
display:
|
|
250
|
-
|
|
251
|
-
|
|
272
|
+
display: flex;
|
|
273
|
+
min-height: 32px;
|
|
274
|
+
padding-left: 28px;
|
|
252
275
|
align-items: center;
|
|
276
|
+
justify-content: flex-end;
|
|
277
|
+
gap: 8px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.vault-auto-scan-controls:empty {
|
|
281
|
+
display: none;
|
|
253
282
|
}
|
|
254
283
|
|
|
255
284
|
.vault-auto-scan-settings,
|
|
@@ -261,7 +290,7 @@
|
|
|
261
290
|
}
|
|
262
291
|
|
|
263
292
|
.vault-auto-scan-settings {
|
|
264
|
-
|
|
293
|
+
margin-right: auto;
|
|
265
294
|
min-height: 18px;
|
|
266
295
|
padding: 0;
|
|
267
296
|
font: 500 10.5px/18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -293,8 +322,7 @@
|
|
|
293
322
|
}
|
|
294
323
|
|
|
295
324
|
.vault-auto-scan-action {
|
|
296
|
-
|
|
297
|
-
width: 76px;
|
|
325
|
+
min-width: 76px;
|
|
298
326
|
min-height: 32px;
|
|
299
327
|
padding: 6px 10px;
|
|
300
328
|
color: var(--vault-notice-text);
|
|
@@ -333,32 +361,7 @@
|
|
|
333
361
|
opacity: 0.55;
|
|
334
362
|
}
|
|
335
363
|
|
|
336
|
-
.vault-auto-scan-
|
|
337
|
-
.vault-auto-scan-row[data-state="complete"] {
|
|
338
|
-
grid-template-rows: 16px 30px;
|
|
339
|
-
min-height: 66px;
|
|
340
|
-
padding-top: 10px;
|
|
341
|
-
padding-bottom: 10px;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.vault-auto-scan-row[data-state="result"] .vault-auto-scan-icon,
|
|
345
|
-
.vault-auto-scan-row[data-state="result"] .vault-auto-scan-copy,
|
|
346
|
-
.vault-auto-scan-row[data-state="result"] .vault-auto-scan-controls,
|
|
347
|
-
.vault-auto-scan-row[data-state="complete"] .vault-auto-scan-icon,
|
|
348
|
-
.vault-auto-scan-row[data-state="complete"] .vault-auto-scan-copy {
|
|
349
|
-
grid-row: 1 / 3;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.vault-auto-scan-row[data-state="result"] .vault-auto-scan-copy,
|
|
353
|
-
.vault-auto-scan-row[data-state="complete"] .vault-auto-scan-copy {
|
|
354
|
-
grid-template-rows: 16px 30px;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
.vault-auto-scan-row[data-state="complete"] .vault-auto-scan-copy {
|
|
358
|
-
grid-column: 2 / 4;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
.vault-auto-scan-row[data-state="complete"] .vault-auto-scan-icon {
|
|
364
|
+
.vault-auto-scan-message[data-state="complete"] .vault-auto-scan-icon {
|
|
362
365
|
color: var(--vault-notice-accent);
|
|
363
366
|
}
|
|
364
367
|
|
|
@@ -372,24 +375,19 @@
|
|
|
372
375
|
}
|
|
373
376
|
|
|
374
377
|
@media (pointer: coarse) {
|
|
375
|
-
.vault-auto-scan-
|
|
376
|
-
.vault-auto-scan-copy {
|
|
377
|
-
grid-template-rows: 16px 44px 28px;
|
|
378
|
-
}
|
|
379
|
-
.vault-auto-scan-row {
|
|
380
|
-
min-height: 106px;
|
|
381
|
-
}
|
|
378
|
+
.vault-auto-scan-controls { min-height: 44px; }
|
|
382
379
|
.vault-auto-scan-action {
|
|
383
380
|
min-height: 44px;
|
|
384
381
|
}
|
|
385
|
-
.vault-auto-scan-settings { min-height:
|
|
382
|
+
.vault-auto-scan-settings { min-height: 44px; }
|
|
386
383
|
}
|
|
387
384
|
|
|
388
385
|
@media (prefers-reduced-motion: reduce) {
|
|
389
386
|
.vault-auto-scan-row {
|
|
390
387
|
animation: none;
|
|
391
388
|
}
|
|
392
|
-
.vault-auto-scan-
|
|
389
|
+
.vault-auto-scan-message[data-current="true"][data-state="checking"]
|
|
390
|
+
.vault-auto-scan-icon {
|
|
393
391
|
animation-duration: 1800ms;
|
|
394
392
|
}
|
|
395
393
|
}
|
|
@@ -101,10 +101,15 @@ test("the shared layout renders and reviews automatic possible-match notices", a
|
|
|
101
101
|
|
|
102
102
|
const tray = dom.window.document.getElementById("vault-auto-scan-tray")
|
|
103
103
|
assert.equal(tray.hidden, false)
|
|
104
|
+
assert.equal(tray.querySelector(
|
|
105
|
+
".vault-auto-scan-product").textContent, "Disk Saver")
|
|
104
106
|
assert.equal(tray.querySelector(
|
|
105
107
|
".vault-auto-scan-app").textContent, "ComfyUI")
|
|
106
108
|
assert.equal(tray.querySelector(
|
|
107
|
-
".vault-auto-scan-
|
|
109
|
+
".vault-auto-scan-status").textContent,
|
|
110
|
+
"Possible duplicate files found")
|
|
111
|
+
assert.equal(tray.querySelectorAll(
|
|
112
|
+
".vault-auto-scan-message").length, 1)
|
|
108
113
|
assert.equal(tray.querySelector(".vault-auto-scan-value"), null)
|
|
109
114
|
assert.equal(tray.querySelector(
|
|
110
115
|
".vault-auto-scan-action").textContent, "Review")
|
|
@@ -131,9 +136,119 @@ test("the shared layout renders and reviews automatic possible-match notices", a
|
|
|
131
136
|
assert.equal(requests.some((request) =>
|
|
132
137
|
request && request.url === "/info/vault/automatic-scans"), false)
|
|
133
138
|
assert.match(script, /window\.location\.assign\(/)
|
|
134
|
-
assert.ok(script.indexOf("if (!openAutomaticReview(
|
|
135
|
-
script.indexOf("
|
|
136
|
-
"if (!openAutomaticReview(
|
|
139
|
+
assert.ok(script.indexOf("if (!openAutomaticReview(card.app, result.href))") <
|
|
140
|
+
script.indexOf("removeCard(card.app);", script.indexOf(
|
|
141
|
+
"if (!openAutomaticReview(card.app, result.href))")))
|
|
142
|
+
|
|
143
|
+
dom.window.close()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("automatic check states append within one card", async () => {
|
|
147
|
+
const template = await fs.promises.readFile(
|
|
148
|
+
path.join(root, "server", "views", "layout.ejs"), "utf8")
|
|
149
|
+
const script = await fs.promises.readFile(
|
|
150
|
+
path.join(root, "server", "public", "layout.js"), "utf8")
|
|
151
|
+
const html = ejs.render(template, {
|
|
152
|
+
theme: "light",
|
|
153
|
+
agent: "web",
|
|
154
|
+
initialPath: "/home",
|
|
155
|
+
defaultPath: "/home",
|
|
156
|
+
sessionId: null,
|
|
157
|
+
vaultEnabled: true
|
|
158
|
+
})
|
|
159
|
+
const dom = new JSDOM(html, {
|
|
160
|
+
runScripts: "outside-only",
|
|
161
|
+
pretendToBeVisual: true,
|
|
162
|
+
url: "http://localhost/"
|
|
163
|
+
})
|
|
164
|
+
const eventSources = []
|
|
165
|
+
dom.window.EventSource = class EventSource {
|
|
166
|
+
constructor(url) {
|
|
167
|
+
this.url = url
|
|
168
|
+
eventSources.push(this)
|
|
169
|
+
}
|
|
170
|
+
close() {}
|
|
171
|
+
}
|
|
172
|
+
dom.window.fetch = async (url) => {
|
|
173
|
+
throw new Error(`Unexpected request: ${url}`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
dom.window.eval(script)
|
|
177
|
+
await waitFor(() => eventSources.length === 1)
|
|
178
|
+
const send = (rows, settings = []) => eventSources[0].onmessage({
|
|
179
|
+
data: JSON.stringify({
|
|
180
|
+
enabled: true,
|
|
181
|
+
global_scan_ready: true,
|
|
182
|
+
rows,
|
|
183
|
+
settings
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
const row = (state, noticeId) => ({
|
|
187
|
+
app: "ComfyUI",
|
|
188
|
+
state,
|
|
189
|
+
notice_id: noticeId
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
send([row("checking", "checking:1:")])
|
|
193
|
+
const tray = dom.window.document.getElementById("vault-auto-scan-tray")
|
|
194
|
+
const card = tray.querySelector(".vault-auto-scan-row")
|
|
195
|
+
assert.ok(card)
|
|
196
|
+
|
|
197
|
+
send([row("paused", "paused:2:")], [
|
|
198
|
+
{ app: "ComfyUI", mode: "manual" }
|
|
199
|
+
])
|
|
200
|
+
assert.equal(tray.querySelector(".vault-auto-scan-row"), card)
|
|
201
|
+
assert.deepEqual([...card.querySelectorAll(".vault-auto-scan-status")]
|
|
202
|
+
.map((status) => status.textContent), [
|
|
203
|
+
"Checking for possible duplicate files...",
|
|
204
|
+
"Automatic checks are paused"
|
|
205
|
+
])
|
|
206
|
+
assert.equal(card.querySelectorAll(
|
|
207
|
+
'.vault-auto-scan-message[data-current="true"]').length, 1)
|
|
208
|
+
assert.equal(card.querySelector(
|
|
209
|
+
'.vault-auto-scan-message[data-current="false"] button'), null)
|
|
210
|
+
assert.equal(card.querySelector(
|
|
211
|
+
".vault-auto-scan-action").textContent, "Resume")
|
|
212
|
+
|
|
213
|
+
send([row("checking", "checking:3:")], [
|
|
214
|
+
{ app: "ComfyUI", mode: "automatic" }
|
|
215
|
+
])
|
|
216
|
+
assert.equal(tray.querySelector(".vault-auto-scan-row"), card)
|
|
217
|
+
assert.equal(card.querySelectorAll(".vault-auto-scan-message").length, 3)
|
|
218
|
+
assert.equal(card.querySelector(
|
|
219
|
+
'.vault-auto-scan-message[data-current="true"] .vault-auto-scan-status')
|
|
220
|
+
.textContent, "Checking again for possible duplicate files...")
|
|
221
|
+
assert.equal(card.querySelector(
|
|
222
|
+
".vault-auto-scan-action").textContent, "Pause")
|
|
223
|
+
|
|
224
|
+
send([row("result", "result:4:result-a")], [
|
|
225
|
+
{ app: "ComfyUI", mode: "automatic" }
|
|
226
|
+
])
|
|
227
|
+
assert.equal(tray.querySelector(".vault-auto-scan-row"), card)
|
|
228
|
+
assert.equal(card.querySelectorAll(".vault-auto-scan-message").length, 4)
|
|
229
|
+
assert.equal(card.querySelector(
|
|
230
|
+
'.vault-auto-scan-message[data-current="true"] .vault-auto-scan-status')
|
|
231
|
+
.textContent, "Possible duplicate files found")
|
|
232
|
+
assert.equal(card.querySelector(".vault-auto-scan-settings"), null)
|
|
233
|
+
assert.equal(card.querySelector(
|
|
234
|
+
".vault-auto-scan-action").textContent, "Review")
|
|
235
|
+
|
|
236
|
+
send([row("result", "result:4:result-a")], [
|
|
237
|
+
{ app: "ComfyUI", mode: "automatic" }
|
|
238
|
+
])
|
|
239
|
+
assert.equal(card.querySelectorAll(".vault-auto-scan-message").length, 4,
|
|
240
|
+
"repeated snapshots do not duplicate the current message")
|
|
241
|
+
|
|
242
|
+
send([
|
|
243
|
+
row("result", "result:4:result-a"),
|
|
244
|
+
{ app: "OtherApp", state: "checking", notice_id: "checking:5:" }
|
|
245
|
+
], [
|
|
246
|
+
{ app: "ComfyUI", mode: "automatic" },
|
|
247
|
+
{ app: "OtherApp", mode: "automatic" }
|
|
248
|
+
])
|
|
249
|
+
assert.equal(tray.querySelectorAll(".vault-auto-scan-row").length, 2)
|
|
250
|
+
assert.deepEqual([...tray.querySelectorAll(".vault-auto-scan-app")]
|
|
251
|
+
.map((app) => app.textContent), ["ComfyUI", "OtherApp"])
|
|
137
252
|
|
|
138
253
|
dom.window.close()
|
|
139
254
|
})
|
|
@@ -205,7 +320,7 @@ test("checking notices expose settings, Pause, and dismissal", async () => {
|
|
|
205
320
|
".vault-auto-scan-app").textContent, "ComfyUI")
|
|
206
321
|
assert.equal(tray.querySelector(
|
|
207
322
|
".vault-auto-scan-status").textContent,
|
|
208
|
-
"Checking for possible duplicate files
|
|
323
|
+
"Checking for possible duplicate files...")
|
|
209
324
|
assert.equal(tray.querySelector(
|
|
210
325
|
".vault-auto-scan-settings").textContent,
|
|
211
326
|
"Automatic check settings")
|
|
@@ -223,19 +338,9 @@ test("checking notices expose settings, Pause, and dismissal", async () => {
|
|
|
223
338
|
assert.equal(tray.querySelector(
|
|
224
339
|
".vault-auto-scan-settings").disabled, false)
|
|
225
340
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
dom.window.dispatchEvent(new dom.window.MessageEvent("message", {
|
|
231
|
-
data: { e: "vault-auto-settings-ready" },
|
|
232
|
-
origin: dom.window.location.origin,
|
|
233
|
-
source: iframe.contentWindow
|
|
234
|
-
}))
|
|
235
|
-
tray.querySelector(".vault-auto-scan-settings").click()
|
|
236
|
-
await waitFor(() => relayed.length === 1)
|
|
237
|
-
assert.equal(relayed[0].message.e, "vault-auto-settings")
|
|
238
|
-
assert.equal(relayed[0].targetOrigin, dom.window.location.origin)
|
|
341
|
+
assert.doesNotMatch(script, /vaultAutoSettingsReady|vault-auto-settings-ready/)
|
|
342
|
+
assert.doesNotMatch(script,
|
|
343
|
+
/postMessage\(\s*\{ e: ['"]vault-auto-settings['"]/)
|
|
239
344
|
|
|
240
345
|
tray.querySelector(".vault-auto-scan-close").click()
|
|
241
346
|
await waitFor(() => tray.hidden)
|
|
@@ -380,10 +485,18 @@ test("an empty automatic check briefly confirms completion", async () => {
|
|
|
380
485
|
assert.ok(completed)
|
|
381
486
|
assert.equal(completed.querySelector(
|
|
382
487
|
".vault-auto-scan-app").textContent, "ComfyUI")
|
|
488
|
+
assert.deepEqual([...completed.querySelectorAll(
|
|
489
|
+
".vault-auto-scan-status")].map((status) => status.textContent), [
|
|
490
|
+
"Checking for possible duplicate files...",
|
|
491
|
+
"No possible duplicate files found"
|
|
492
|
+
])
|
|
493
|
+
assert.equal(completed.querySelectorAll(
|
|
494
|
+
".vault-auto-scan-message").length, 2)
|
|
383
495
|
assert.equal(completed.querySelector(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
assert.ok(completed.querySelector(
|
|
496
|
+
'.vault-auto-scan-message[data-current="false"]')
|
|
497
|
+
.dataset.state, "checking")
|
|
498
|
+
assert.ok(completed.querySelector(
|
|
499
|
+
'.vault-auto-scan-message[data-current="true"] .vault-auto-scan-icon svg'))
|
|
387
500
|
assert.equal(completed.querySelector(".vault-auto-scan-settings"), null)
|
|
388
501
|
assert.equal(completed.querySelector(".vault-auto-scan-action"), null)
|
|
389
502
|
assert.equal(completionTimers[0].delay, 4000)
|
|
@@ -426,6 +539,9 @@ test("an empty automatic check briefly confirms completion", async () => {
|
|
|
426
539
|
}],
|
|
427
540
|
settings: [{ app: "ComfyUI", mode: "automatic" }]
|
|
428
541
|
})
|
|
542
|
+
const focusedClose = tray.querySelector(".vault-auto-scan-close")
|
|
543
|
+
focusedClose.focus()
|
|
544
|
+
const timersBeforeFocusedCompletion = completionTimers.length
|
|
429
545
|
send({
|
|
430
546
|
enabled: true,
|
|
431
547
|
rows: [],
|
|
@@ -436,6 +552,13 @@ test("an empty automatic check briefly confirms completion", async () => {
|
|
|
436
552
|
}
|
|
437
553
|
})
|
|
438
554
|
assert.equal(tray.hidden, false)
|
|
555
|
+
assert.equal(completionTimers.length, timersBeforeFocusedCompletion,
|
|
556
|
+
"completion does not start its timer while focus is already in the card")
|
|
557
|
+
focusedClose.dispatchEvent(new dom.window.FocusEvent("focusout", {
|
|
558
|
+
bubbles: true,
|
|
559
|
+
relatedTarget: null
|
|
560
|
+
}))
|
|
561
|
+
assert.equal(completionTimers.length, timersBeforeFocusedCompletion + 1)
|
|
439
562
|
eventSources[0].onerror()
|
|
440
563
|
await new Promise((resolve) => nativeSetTimeout(resolve, 0))
|
|
441
564
|
assert.equal(tray.hidden, true,
|
|
@@ -614,6 +737,7 @@ test("the app sidebar mirrors Automatic and Manual Disk Saver modes", async () =
|
|
|
614
737
|
assert.equal(label.textContent, "Auto")
|
|
615
738
|
assert.match(template, /data-app-vault-mode/)
|
|
616
739
|
assert.match(template, /app-vault-mode\.js/)
|
|
740
|
+
assert.doesNotMatch(template, /vault-auto-settings/)
|
|
617
741
|
assert.match(template,
|
|
618
742
|
/#save-space-tab\s*\{[^}]*width:\s*100%;[^}]*max-width:\s*none;/s)
|
|
619
743
|
assert.match(template,
|