@buoy-gg/time-machine 7.0.36 → 7.0.38
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/README.md +40 -35
- package/lib/commonjs/components/HeaderActions.js +91 -0
- package/lib/commonjs/components/HeaderReloadButton.js +22 -25
- package/lib/commonjs/components/TimeMachineBody.js +68 -13
- package/lib/commonjs/components/floating/NamePromptModal.js +193 -0
- package/lib/commonjs/components/floating/TimeMachineFloatingTool.js +715 -0
- package/lib/commonjs/components/floating/armedPoint.js +92 -0
- package/lib/commonjs/components/floating/barSettings.js +66 -0
- package/lib/commonjs/components/list/CaptureBar.js +2 -0
- package/lib/commonjs/components/list/RestoreOutcomeCard.js +2 -0
- package/lib/commonjs/components/list/SnapshotRow.js +37 -13
- package/lib/commonjs/components/list/SourcesStrip.js +127 -83
- package/lib/commonjs/components/list/UndoDeleteBar.js +3 -1
- package/lib/commonjs/components/preview/RestorePreviewScreen.js +7 -2
- package/lib/commonjs/components/sources.js +19 -8
- package/lib/commonjs/index.js +42 -0
- package/lib/commonjs/preset.js +6 -4
- package/lib/commonjs/store/sourceIds.js +9 -0
- package/lib/commonjs/store/sourceSettings.js +79 -0
- package/lib/commonjs/store/timeMachineStore.js +75 -10
- package/lib/commonjs/sync/timeMachineSyncAdapter.js +5 -1
- package/lib/commonjs/ui/SwipeActionsRow.js +14 -3
- package/lib/commonjs/ui/primitives.js +2 -0
- package/lib/commonjs/ui/tmTheme.js +18 -5
- package/lib/module/components/HeaderActions.js +85 -0
- package/lib/module/components/HeaderReloadButton.js +24 -27
- package/lib/module/components/TimeMachineBody.js +71 -16
- package/lib/module/components/floating/NamePromptModal.js +188 -0
- package/lib/module/components/floating/TimeMachineFloatingTool.js +708 -0
- package/lib/module/components/floating/armedPoint.js +82 -0
- package/lib/module/components/floating/barSettings.js +57 -0
- package/lib/module/components/list/CaptureBar.js +2 -0
- package/lib/module/components/list/RestoreOutcomeCard.js +2 -0
- package/lib/module/components/list/SnapshotRow.js +37 -13
- package/lib/module/components/list/SourcesStrip.js +130 -84
- package/lib/module/components/list/UndoDeleteBar.js +3 -1
- package/lib/module/components/preview/RestorePreviewScreen.js +7 -2
- package/lib/module/components/sources.js +13 -6
- package/lib/module/index.js +7 -1
- package/lib/module/preset.js +6 -4
- package/lib/module/store/sourceIds.js +5 -0
- package/lib/module/store/sourceSettings.js +69 -0
- package/lib/module/store/timeMachineStore.js +74 -9
- package/lib/module/sync/timeMachineSyncAdapter.js +5 -1
- package/lib/module/ui/SwipeActionsRow.js +14 -3
- package/lib/module/ui/primitives.js +2 -0
- package/lib/module/ui/tmTheme.js +18 -5
- package/lib/typescript/components/HeaderActions.d.ts +7 -0
- package/lib/typescript/components/floating/NamePromptModal.d.ts +9 -0
- package/lib/typescript/components/floating/TimeMachineFloatingTool.d.ts +6 -0
- package/lib/typescript/components/floating/armedPoint.d.ts +14 -0
- package/lib/typescript/components/floating/barSettings.d.ts +7 -0
- package/lib/typescript/components/list/SnapshotRow.d.ts +5 -1
- package/lib/typescript/components/list/SourcesStrip.d.ts +3 -1
- package/lib/typescript/components/sources.d.ts +7 -2
- package/lib/typescript/index.d.ts +3 -1
- package/lib/typescript/preset.d.ts +4 -2
- package/lib/typescript/store/snapshotVault.d.ts +6 -0
- package/lib/typescript/store/sourceIds.d.ts +3 -0
- package/lib/typescript/store/sourceSettings.d.ts +9 -0
- package/lib/typescript/store/timeMachineStore.d.ts +16 -1
- package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +4 -0
- package/lib/typescript/ui/tmTheme.d.ts +17 -4
- package/package.json +10 -7
- package/lib/commonjs/components/HeaderReloadButton.js.map +0 -1
- package/lib/commonjs/components/TimeMachineBody.js.map +0 -1
- package/lib/commonjs/components/TimeMachineModal.js.map +0 -1
- package/lib/commonjs/components/confirm.js.map +0 -1
- package/lib/commonjs/components/format.js.map +0 -1
- package/lib/commonjs/components/list/CaptureBar.js.map +0 -1
- package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +0 -1
- package/lib/commonjs/components/list/SnapshotRow.js.map +0 -1
- package/lib/commonjs/components/list/SourcesStrip.js.map +0 -1
- package/lib/commonjs/components/list/UndoDeleteBar.js.map +0 -1
- package/lib/commonjs/components/list/WipeAllRow.js.map +0 -1
- package/lib/commonjs/components/preview/ItemDiffPanel.js.map +0 -1
- package/lib/commonjs/components/preview/PreviewFlow.js.map +0 -1
- package/lib/commonjs/components/preview/RestorePointNameRow.js.map +0 -1
- package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +0 -1
- package/lib/commonjs/components/preview/verdictMeta.js.map +0 -1
- package/lib/commonjs/components/sources.js.map +0 -1
- package/lib/commonjs/hooks/useTimeMachine.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/commonjs/store/routeRestore.js.map +0 -1
- package/lib/commonjs/store/snapshotVault.js.map +0 -1
- package/lib/commonjs/store/timeMachineStore.js.map +0 -1
- package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +0 -1
- package/lib/commonjs/ui/SwipeActionsRow.js.map +0 -1
- package/lib/commonjs/ui/primitives.js.map +0 -1
- package/lib/commonjs/ui/tmTheme.js.map +0 -1
- package/lib/module/components/HeaderReloadButton.js.map +0 -1
- package/lib/module/components/TimeMachineBody.js.map +0 -1
- package/lib/module/components/TimeMachineModal.js.map +0 -1
- package/lib/module/components/confirm.js.map +0 -1
- package/lib/module/components/format.js.map +0 -1
- package/lib/module/components/list/CaptureBar.js.map +0 -1
- package/lib/module/components/list/RestoreOutcomeCard.js.map +0 -1
- package/lib/module/components/list/SnapshotRow.js.map +0 -1
- package/lib/module/components/list/SourcesStrip.js.map +0 -1
- package/lib/module/components/list/UndoDeleteBar.js.map +0 -1
- package/lib/module/components/list/WipeAllRow.js.map +0 -1
- package/lib/module/components/preview/ItemDiffPanel.js.map +0 -1
- package/lib/module/components/preview/PreviewFlow.js.map +0 -1
- package/lib/module/components/preview/RestorePointNameRow.js.map +0 -1
- package/lib/module/components/preview/RestorePreviewScreen.js.map +0 -1
- package/lib/module/components/preview/verdictMeta.js.map +0 -1
- package/lib/module/components/sources.js.map +0 -1
- package/lib/module/hooks/useTimeMachine.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/module/store/routeRestore.js.map +0 -1
- package/lib/module/store/snapshotVault.js.map +0 -1
- package/lib/module/store/timeMachineStore.js.map +0 -1
- package/lib/module/sync/timeMachineSyncAdapter.js.map +0 -1
- package/lib/module/ui/SwipeActionsRow.js.map +0 -1
- package/lib/module/ui/primitives.js.map +0 -1
- package/lib/module/ui/tmTheme.js.map +0 -1
- package/lib/typescript/components/HeaderReloadButton.d.ts.map +0 -1
- package/lib/typescript/components/TimeMachineBody.d.ts.map +0 -1
- package/lib/typescript/components/TimeMachineModal.d.ts.map +0 -1
- package/lib/typescript/components/confirm.d.ts.map +0 -1
- package/lib/typescript/components/format.d.ts.map +0 -1
- package/lib/typescript/components/list/CaptureBar.d.ts.map +0 -1
- package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +0 -1
- package/lib/typescript/components/list/SnapshotRow.d.ts.map +0 -1
- package/lib/typescript/components/list/SourcesStrip.d.ts.map +0 -1
- package/lib/typescript/components/list/UndoDeleteBar.d.ts.map +0 -1
- package/lib/typescript/components/list/WipeAllRow.d.ts.map +0 -1
- package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +0 -1
- package/lib/typescript/components/preview/PreviewFlow.d.ts.map +0 -1
- package/lib/typescript/components/preview/RestorePointNameRow.d.ts.map +0 -1
- package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +0 -1
- package/lib/typescript/components/preview/verdictMeta.d.ts.map +0 -1
- package/lib/typescript/components/sources.d.ts.map +0 -1
- package/lib/typescript/hooks/useTimeMachine.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/store/routeRestore.d.ts.map +0 -1
- package/lib/typescript/store/snapshotVault.d.ts.map +0 -1
- package/lib/typescript/store/timeMachineStore.d.ts.map +0 -1
- package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/ui/SwipeActionsRow.d.ts.map +0 -1
- package/lib/typescript/ui/primitives.d.ts.map +0 -1
- package/lib/typescript/ui/tmTheme.d.ts.map +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which restore point the floating restore bar restores on tap.
|
|
5
|
+
*
|
|
6
|
+
* The bar needs an answer before the user picks anything, or its first tap is
|
|
7
|
+
* a picker instead of a restore. So the armed point is resolved, not just
|
|
8
|
+
* stored: an explicit pick wins, then the point restored most recently, then
|
|
9
|
+
* the newest capture. Baselines never resolve; restoring one wipes storage
|
|
10
|
+
* and reloads behind a confirm dialog, which is not a one-tap action. The
|
|
11
|
+
* safety slot never resolves either: Undo is its way in.
|
|
12
|
+
*
|
|
13
|
+
* The explicit pick is persisted so it survives the reloads the loop itself
|
|
14
|
+
* causes (RELOAD APP, reload-mode restores).
|
|
15
|
+
*/
|
|
16
|
+
import { devToolsStorageKeys, persistentStorage } from "@buoy-gg/shared-ui";
|
|
17
|
+
const KEY = devToolsStorageKeys.timeMachine.armedPoint();
|
|
18
|
+
let pickedId = null;
|
|
19
|
+
let loaded = false;
|
|
20
|
+
let loadPromise = null;
|
|
21
|
+
const listeners = new Set();
|
|
22
|
+
const notify = () => listeners.forEach(listener => listener());
|
|
23
|
+
export function getPickedPointId() {
|
|
24
|
+
return pickedId;
|
|
25
|
+
}
|
|
26
|
+
export function subscribePickedPoint(listener) {
|
|
27
|
+
listeners.add(listener);
|
|
28
|
+
return () => {
|
|
29
|
+
listeners.delete(listener);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Read the saved pick once. Later calls return the same promise. */
|
|
34
|
+
export function ensurePickedPointLoaded() {
|
|
35
|
+
if (loaded) return Promise.resolve();
|
|
36
|
+
if (!loadPromise) {
|
|
37
|
+
loadPromise = persistentStorage.getItem(KEY).then(saved => {
|
|
38
|
+
// A pick made while the read was in flight wins over the disk copy.
|
|
39
|
+
if (pickedId === null && saved) {
|
|
40
|
+
pickedId = saved;
|
|
41
|
+
notify();
|
|
42
|
+
}
|
|
43
|
+
}).catch(() => {}).finally(() => {
|
|
44
|
+
loaded = true;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return loadPromise;
|
|
48
|
+
}
|
|
49
|
+
export function setPickedPointId(id) {
|
|
50
|
+
if (pickedId === id) return;
|
|
51
|
+
pickedId = id;
|
|
52
|
+
notify();
|
|
53
|
+
void (id === null ? persistentStorage.removeItem(KEY) : persistentStorage.setItem(KEY, id)).catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Test seam: forget the pick and the load, without touching storage. */
|
|
57
|
+
export function resetPickedPointForTests() {
|
|
58
|
+
pickedId = null;
|
|
59
|
+
loaded = false;
|
|
60
|
+
loadPromise = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The point the bar restores: the saved pick if it still exists, else the
|
|
65
|
+
* most recently restored point, else the newest capture. Never a baseline.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveArmedPoint(snapshots, pickedId) {
|
|
68
|
+
const candidates = snapshots.filter(meta => !meta.baseline && !meta.safety);
|
|
69
|
+
if (candidates.length === 0) return null;
|
|
70
|
+
if (pickedId) {
|
|
71
|
+
const picked = candidates.find(meta => meta.id === pickedId);
|
|
72
|
+
if (picked) return picked;
|
|
73
|
+
}
|
|
74
|
+
let best = null;
|
|
75
|
+
for (const meta of candidates) {
|
|
76
|
+
if (meta.lastRestoredAt === undefined) continue;
|
|
77
|
+
if (!best || meta.lastRestoredAt > (best.lastRestoredAt ?? 0)) best = meta;
|
|
78
|
+
}
|
|
79
|
+
if (best) return best;
|
|
80
|
+
return candidates.reduce((newest, meta) => meta.createdAt > newest.createdAt ? meta : newest);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=armedPoint.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The action bar's one setting: restore live, or restore and reload.
|
|
5
|
+
*
|
|
6
|
+
* Live restore replaces stores in place and is right for state that lives in
|
|
7
|
+
* Redux, Zustand, Jotai or the query cache. It cannot reach a React context
|
|
8
|
+
* or `useState` that hydrates itself from storage once at mount: the
|
|
9
|
+
* storage key comes back, the screen keeps showing what it already had, and
|
|
10
|
+
* "restore did nothing" is the report. A restore that reloads the app makes
|
|
11
|
+
* those screens rebuild from the restored storage. Persisted, because the
|
|
12
|
+
* reload itself is what brings the bar back.
|
|
13
|
+
*/
|
|
14
|
+
import { devToolsStorageKeys, persistentStorage } from "@buoy-gg/shared-ui";
|
|
15
|
+
const KEY = devToolsStorageKeys.timeMachine.barReloadMode();
|
|
16
|
+
let reloadMode = false;
|
|
17
|
+
let loaded = false;
|
|
18
|
+
let loadPromise = null;
|
|
19
|
+
const listeners = new Set();
|
|
20
|
+
const notify = () => listeners.forEach(listener => listener());
|
|
21
|
+
export function getBarReloadMode() {
|
|
22
|
+
return reloadMode;
|
|
23
|
+
}
|
|
24
|
+
export function subscribeBarSettings(listener) {
|
|
25
|
+
listeners.add(listener);
|
|
26
|
+
return () => {
|
|
27
|
+
listeners.delete(listener);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function ensureBarSettingsLoaded() {
|
|
31
|
+
if (loaded) return Promise.resolve();
|
|
32
|
+
if (!loadPromise) {
|
|
33
|
+
loadPromise = persistentStorage.getItem(KEY).then(saved => {
|
|
34
|
+
if (!reloadMode && saved === "1") {
|
|
35
|
+
reloadMode = true;
|
|
36
|
+
notify();
|
|
37
|
+
}
|
|
38
|
+
}).catch(() => {}).finally(() => {
|
|
39
|
+
loaded = true;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return loadPromise;
|
|
43
|
+
}
|
|
44
|
+
export function setBarReloadMode(on) {
|
|
45
|
+
if (on === reloadMode) return;
|
|
46
|
+
reloadMode = on;
|
|
47
|
+
notify();
|
|
48
|
+
void persistentStorage.setItem(KEY, on ? "1" : "0").catch(() => {});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Test seam. */
|
|
52
|
+
export function resetBarSettingsForTests() {
|
|
53
|
+
reloadMode = false;
|
|
54
|
+
loaded = false;
|
|
55
|
+
loadPromise = null;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=barSettings.js.map
|
|
@@ -29,20 +29,29 @@ function describe(meta) {
|
|
|
29
29
|
// A saved selection is worth stating on the row — this restore point no
|
|
30
30
|
// longer does the obvious thing, and one tap on RESTORE honors it.
|
|
31
31
|
if (meta.excludedKeys?.length) parts.push(`skips ${meta.excludedKeys.length}`);else if (meta.scope?.length) parts.push(`scoped (${meta.scope.length})`);
|
|
32
|
+
// The bar's own undo slot: kept for one restore, not a point you chose.
|
|
33
|
+
if (meta.safety) parts.unshift("safety copy");
|
|
32
34
|
return parts.join(" · ");
|
|
33
35
|
}
|
|
34
36
|
export function SnapshotRow({
|
|
35
37
|
meta,
|
|
36
38
|
busy,
|
|
39
|
+
armed = false,
|
|
37
40
|
onOpen,
|
|
38
41
|
onRestore,
|
|
39
42
|
onDelete,
|
|
40
|
-
onDuplicate
|
|
43
|
+
onDuplicate,
|
|
44
|
+
onArm
|
|
41
45
|
}) {
|
|
42
46
|
const testID = `time-machine-snap-${meta.id}`;
|
|
43
47
|
return /*#__PURE__*/_jsx(SwipeActionsRow, {
|
|
44
48
|
testID: testID,
|
|
45
49
|
actions: [
|
|
50
|
+
// "Bar": put this point on the floating action bar.
|
|
51
|
+
{
|
|
52
|
+
label: "Bar",
|
|
53
|
+
onPress: onArm
|
|
54
|
+
},
|
|
46
55
|
// "Dup" over "Copy": on a row full of text, "Copy" reads as
|
|
47
56
|
// copy-to-clipboard.
|
|
48
57
|
{
|
|
@@ -54,21 +63,26 @@ export function SnapshotRow({
|
|
|
54
63
|
background: tm.color.danger,
|
|
55
64
|
color: tm.color.onDanger
|
|
56
65
|
}],
|
|
57
|
-
children: /*#__PURE__*/_jsxs(
|
|
66
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
58
67
|
style: styles.row,
|
|
59
|
-
|
|
60
|
-
activeOpacity: 0.7,
|
|
61
|
-
accessibilityRole: "button",
|
|
62
|
-
accessibilityLabel: `${meta.name}. ${describe(meta)}. Open restore preview`,
|
|
63
|
-
testID: testID,
|
|
64
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
68
|
+
children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
65
69
|
style: styles.text,
|
|
70
|
+
onPress: onOpen,
|
|
71
|
+
activeOpacity: 0.7,
|
|
72
|
+
accessibilityRole: "button",
|
|
73
|
+
accessibilityLabel: `${meta.name}. ${describe(meta)}. Open restore preview`,
|
|
74
|
+
testID: testID,
|
|
66
75
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
67
76
|
style: styles.name,
|
|
68
77
|
numberOfLines: 1,
|
|
69
78
|
testID: `${testID}-name`,
|
|
70
79
|
children: meta.name
|
|
71
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
80
|
+
}), armed ? /*#__PURE__*/_jsx(Text, {
|
|
81
|
+
style: styles.armed,
|
|
82
|
+
numberOfLines: 1,
|
|
83
|
+
testID: `${testID}-armed`,
|
|
84
|
+
children: "On the action bar"
|
|
85
|
+
}) : null, /*#__PURE__*/_jsx(Text, {
|
|
72
86
|
style: styles.meta,
|
|
73
87
|
numberOfLines: 1,
|
|
74
88
|
testID: `${testID}-meta`,
|
|
@@ -87,7 +101,7 @@ export function SnapshotRow({
|
|
|
87
101
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
88
102
|
onPress: onRestore,
|
|
89
103
|
disabled: busy,
|
|
90
|
-
style: busy ? styles.actionDisabled : null,
|
|
104
|
+
style: [styles.restore, busy ? styles.actionDisabled : null],
|
|
91
105
|
accessibilityRole: "button",
|
|
92
106
|
accessibilityLabel: meta.baseline ? "Wipe and reload" : "Restore",
|
|
93
107
|
testID: `${testID}-restore`,
|
|
@@ -106,12 +120,17 @@ const styles = StyleSheet.create({
|
|
|
106
120
|
flexDirection: "row",
|
|
107
121
|
alignItems: "center",
|
|
108
122
|
gap: 10,
|
|
109
|
-
paddingHorizontal: tm.textInset
|
|
110
|
-
paddingVertical: 11
|
|
123
|
+
paddingHorizontal: tm.textInset
|
|
111
124
|
},
|
|
125
|
+
// The text block is the tallest child, so its padding sets the row height
|
|
126
|
+
// and the open target spans the full row.
|
|
112
127
|
text: {
|
|
113
128
|
flex: 1,
|
|
114
|
-
minWidth: 0
|
|
129
|
+
minWidth: 0,
|
|
130
|
+
paddingVertical: 11
|
|
131
|
+
},
|
|
132
|
+
restore: {
|
|
133
|
+
justifyContent: "center"
|
|
115
134
|
},
|
|
116
135
|
name: {
|
|
117
136
|
color: tm.color.label,
|
|
@@ -130,6 +149,11 @@ const styles = StyleSheet.create({
|
|
|
130
149
|
fontSize: tm.font.footnote,
|
|
131
150
|
marginTop: 2
|
|
132
151
|
},
|
|
152
|
+
armed: {
|
|
153
|
+
color: tm.color.accent,
|
|
154
|
+
fontSize: tm.font.footnote,
|
|
155
|
+
marginTop: 2
|
|
156
|
+
},
|
|
133
157
|
route: {
|
|
134
158
|
color: tm.color.labelTertiary,
|
|
135
159
|
fontFamily: TM_MONO,
|
|
@@ -1,123 +1,169 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Sources — one
|
|
4
|
+
* Sources — one settings row per source, each with a switch.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* The iOS Settings shape the rest of the tool uses, because that is what a
|
|
7
|
+
* list of things you can turn on and off looks like: icon, name, a line of
|
|
8
|
+
* status, and a switch at the trailing edge. Off means every capture and
|
|
9
|
+
* restore leaves that source alone, for the store whose full replace the
|
|
10
|
+
* app cannot survive.
|
|
11
|
+
*
|
|
12
|
+
* Only sources the app has wired are listed; a library the app does not use
|
|
13
|
+
* is not a choice to make. Icons keep full brightness in every state. Under
|
|
14
|
+
* each name is what the source holds, in plain words; a short state line
|
|
15
|
+
* appears only when something is off or capture-only.
|
|
10
16
|
*/
|
|
11
|
-
import {
|
|
17
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
18
|
+
import { NightSwitch } from "@buoy-gg/shared-ui";
|
|
19
|
+
import { Group } from "../../ui/primitives";
|
|
12
20
|
import { tm } from "../../ui/tmTheme";
|
|
13
21
|
import { sourceIcon } from "../sources";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* What each source holds, in words for someone who has never heard of the
|
|
25
|
+
* library. The library name is the row title; this line is the meaning.
|
|
26
|
+
*/
|
|
14
27
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
const DESCRIPTIONS = {
|
|
29
|
+
storage: "Settings, sign-in and other things saved on the device",
|
|
30
|
+
redux: "Data the whole app shares between screens",
|
|
31
|
+
zustand: "Data the whole app shares between screens",
|
|
32
|
+
jotai: "Small pieces of data screens share",
|
|
33
|
+
query: "Data the app loaded from the server",
|
|
34
|
+
route: "Which screen you were on"
|
|
35
|
+
};
|
|
36
|
+
function describe(source) {
|
|
37
|
+
return DESCRIPTIONS[source.id] ?? "App data";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The short state under the description, when there is one worth saying.
|
|
42
|
+
* "Working" is the default and needs no line.
|
|
43
|
+
*/
|
|
44
|
+
function stateLine(source) {
|
|
45
|
+
if (!source.enabled) return {
|
|
46
|
+
text: "Off · left as it is",
|
|
47
|
+
tone: "off"
|
|
48
|
+
};
|
|
49
|
+
if (!source.canRestore) return {
|
|
50
|
+
text: "Can be saved but not put back",
|
|
51
|
+
tone: "warn"
|
|
52
|
+
};
|
|
53
|
+
return null;
|
|
19
54
|
}
|
|
20
|
-
function
|
|
21
|
-
source
|
|
55
|
+
function SourceRow({
|
|
56
|
+
source,
|
|
57
|
+
onToggle
|
|
22
58
|
}) {
|
|
23
59
|
const Icon = sourceIcon(source.id);
|
|
24
|
-
const
|
|
60
|
+
const description = describe(source);
|
|
61
|
+
const state = stateLine(source);
|
|
25
62
|
return /*#__PURE__*/_jsxs(View, {
|
|
26
|
-
style: styles.
|
|
63
|
+
style: styles.row,
|
|
27
64
|
testID: `time-machine-source-${source.id}`,
|
|
28
|
-
accessibilityLabel: `${source.label}: ${
|
|
29
|
-
children: [/*#__PURE__*/
|
|
30
|
-
style:
|
|
31
|
-
children:
|
|
32
|
-
size:
|
|
33
|
-
bgColor: tm.color.
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
accessibilityLabel: `${source.label}: ${description}${state ? `. ${state.text}` : ""}`,
|
|
66
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
67
|
+
style: styles.disc,
|
|
68
|
+
children: /*#__PURE__*/_jsx(Icon, {
|
|
69
|
+
size: 22,
|
|
70
|
+
bgColor: tm.color.disc
|
|
71
|
+
})
|
|
72
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
73
|
+
style: styles.text,
|
|
74
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
75
|
+
style: styles.label,
|
|
76
|
+
numberOfLines: 1,
|
|
77
|
+
testID: `time-machine-source-${source.id}-label`,
|
|
78
|
+
children: source.label
|
|
79
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
80
|
+
style: styles.description,
|
|
81
|
+
numberOfLines: 2,
|
|
82
|
+
testID: `time-machine-source-${source.id}-description`,
|
|
83
|
+
children: description
|
|
84
|
+
}), state ? /*#__PURE__*/_jsx(Text, {
|
|
85
|
+
style: [styles.state, styles[`state_${state.tone}`]],
|
|
86
|
+
numberOfLines: 1,
|
|
87
|
+
testID: state.tone === "off" ? `time-machine-source-${source.id}-off` : `time-machine-source-${source.id}-detail`,
|
|
88
|
+
children: state.text
|
|
38
89
|
}) : null]
|
|
39
|
-
}), /*#__PURE__*/_jsx(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
90
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
91
|
+
testID: `time-machine-source-${source.id}-switch`,
|
|
92
|
+
children: /*#__PURE__*/_jsx(NightSwitch, {
|
|
93
|
+
value: source.enabled
|
|
94
|
+
// The desktop mirror shows the device's setting and cannot flip it.
|
|
95
|
+
,
|
|
96
|
+
disabled: !onToggle,
|
|
97
|
+
onValueChange: next => onToggle?.(source.id, next)
|
|
98
|
+
})
|
|
44
99
|
})]
|
|
45
100
|
});
|
|
46
101
|
}
|
|
47
102
|
export function SourcesStrip({
|
|
48
|
-
sources
|
|
103
|
+
sources,
|
|
104
|
+
onToggle
|
|
49
105
|
}) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
children: "No state tools
|
|
63
|
-
})
|
|
64
|
-
})
|
|
106
|
+
// A source the app has not wired (no Redux Provider, no Zustand stores)
|
|
107
|
+
// is not listed: there is nothing to capture and nothing to switch off.
|
|
108
|
+
const wired = sources.filter(source => source.canCapture);
|
|
109
|
+
return /*#__PURE__*/_jsxs(Group, {
|
|
110
|
+
children: [wired.map(source => /*#__PURE__*/_jsx(SourceRow, {
|
|
111
|
+
source: source,
|
|
112
|
+
onToggle: onToggle
|
|
113
|
+
}, source.id)), wired.length === 0 ? /*#__PURE__*/_jsx(View, {
|
|
114
|
+
style: styles.row,
|
|
115
|
+
testID: "time-machine-sources-empty",
|
|
116
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
117
|
+
style: styles.description,
|
|
118
|
+
children: "No state tools are set up in this app."
|
|
119
|
+
})
|
|
120
|
+
}) : null]
|
|
65
121
|
});
|
|
66
122
|
}
|
|
67
123
|
const styles = StyleSheet.create({
|
|
68
|
-
|
|
69
|
-
marginHorizontal: tm.gutter,
|
|
70
|
-
backgroundColor: tm.color.group,
|
|
71
|
-
borderRadius: tm.radius.group,
|
|
72
|
-
paddingVertical: 12
|
|
73
|
-
},
|
|
74
|
-
strip: {
|
|
124
|
+
row: {
|
|
75
125
|
flexDirection: "row",
|
|
76
|
-
alignItems: "flex-start",
|
|
77
|
-
paddingHorizontal: tm.textInset,
|
|
78
|
-
gap: 18
|
|
79
|
-
},
|
|
80
|
-
tile: {
|
|
81
126
|
alignItems: "center",
|
|
82
|
-
gap:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
width: 78
|
|
127
|
+
gap: 12,
|
|
128
|
+
minHeight: tm.rowMinHeight,
|
|
129
|
+
paddingHorizontal: tm.textInset,
|
|
130
|
+
paddingVertical: 10
|
|
87
131
|
},
|
|
88
132
|
disc: {
|
|
89
|
-
width:
|
|
90
|
-
height:
|
|
133
|
+
width: 36,
|
|
134
|
+
height: 36,
|
|
91
135
|
borderRadius: tm.radius.pill,
|
|
92
|
-
backgroundColor: tm.color.
|
|
136
|
+
backgroundColor: tm.color.disc,
|
|
93
137
|
alignItems: "center",
|
|
94
138
|
justifyContent: "center"
|
|
95
139
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
position: "absolute",
|
|
101
|
-
top: 2,
|
|
102
|
-
right: 2,
|
|
103
|
-
width: 10,
|
|
104
|
-
height: 10,
|
|
105
|
-
borderRadius: 5,
|
|
106
|
-
// Ring it in the card color so it reads as a badge, not a smudge.
|
|
107
|
-
borderWidth: 2,
|
|
108
|
-
borderColor: tm.color.group
|
|
140
|
+
text: {
|
|
141
|
+
flex: 1,
|
|
142
|
+
minWidth: 0,
|
|
143
|
+
gap: 2
|
|
109
144
|
},
|
|
110
145
|
label: {
|
|
111
|
-
color: tm.color.
|
|
146
|
+
color: tm.color.label,
|
|
147
|
+
fontSize: tm.font.row,
|
|
148
|
+
fontWeight: "500",
|
|
149
|
+
letterSpacing: tm.tracking.row
|
|
150
|
+
},
|
|
151
|
+
description: {
|
|
152
|
+
fontSize: tm.font.footnote,
|
|
153
|
+
lineHeight: tm.font.footnote * 1.3,
|
|
154
|
+
color: tm.color.labelSecondary
|
|
155
|
+
},
|
|
156
|
+
state: {
|
|
112
157
|
fontSize: tm.font.mono,
|
|
113
|
-
|
|
158
|
+
lineHeight: tm.font.mono * 1.3,
|
|
159
|
+
fontWeight: "600",
|
|
160
|
+
marginTop: 1
|
|
114
161
|
},
|
|
115
|
-
|
|
162
|
+
state_off: {
|
|
116
163
|
color: tm.color.labelTertiary
|
|
117
164
|
},
|
|
118
|
-
|
|
119
|
-
color: tm.color.
|
|
120
|
-
fontSize: tm.font.footnote
|
|
165
|
+
state_warn: {
|
|
166
|
+
color: tm.color.warning
|
|
121
167
|
}
|
|
122
168
|
});
|
|
123
169
|
//# sourceMappingURL=SourcesStrip.js.map
|
|
@@ -62,7 +62,9 @@ const styles = StyleSheet.create({
|
|
|
62
62
|
minHeight: 46,
|
|
63
63
|
paddingHorizontal: tm.textInset,
|
|
64
64
|
backgroundColor: tm.color.group,
|
|
65
|
-
borderRadius: tm.radius.group
|
|
65
|
+
borderRadius: tm.radius.group,
|
|
66
|
+
borderWidth: 1,
|
|
67
|
+
borderColor: tm.color.groupBorder
|
|
66
68
|
},
|
|
67
69
|
text: {
|
|
68
70
|
flex: 1,
|
|
@@ -20,6 +20,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
20
20
|
import { ActivityIndicator, FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
21
21
|
import { ChevronUp, AlertTriangle, RefreshCw, useSafeAreaInsets } from "@buoy-gg/shared-ui";
|
|
22
22
|
import { useToolHeader, useToolHost } from "@buoy-gg/core";
|
|
23
|
+
import { TimeMachineHeaderActions } from "../HeaderActions";
|
|
23
24
|
import { DisclosureChevron, Group, SectionLabel } from "../../ui/primitives";
|
|
24
25
|
import { TM_MONO, tm } from "../../ui/tmTheme";
|
|
25
26
|
import { timeMachineStore, willRestoreRoute } from "../../store/timeMachineStore";
|
|
@@ -379,10 +380,14 @@ export function RestorePreviewScreen({
|
|
|
379
380
|
useToolHeader(hosted ? {
|
|
380
381
|
title: "Restore Preview",
|
|
381
382
|
subtitle: rightMeta?.name ?? "preview",
|
|
382
|
-
onBack
|
|
383
|
+
onBack,
|
|
384
|
+
// BAR from here arms the point being previewed.
|
|
385
|
+
actions: /*#__PURE__*/_jsx(TimeMachineHeaderActions, {
|
|
386
|
+
armId: rightMeta?.id
|
|
387
|
+
})
|
|
383
388
|
} : null,
|
|
384
389
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
385
|
-
[hosted, rightMeta?.name]);
|
|
390
|
+
[hosted, rightMeta?.name, rightMeta?.id]);
|
|
386
391
|
const {
|
|
387
392
|
rows,
|
|
388
393
|
blockedSources
|
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { JotaiIcon, QueryIcon, ReduxIcon, RoutesIcon, StorageIcon, TimeMachineIcon, ZustandIcon } from "@buoy-gg/floating-tools-core";
|
|
14
|
+
import { useSyncExternalStore } from "react";
|
|
14
15
|
import { useSnapshotProviders, useTimeMachineState } from "../hooks/useTimeMachine";
|
|
15
16
|
import { canNavigate } from "../store/routeRestore";
|
|
17
|
+
import { ROUTE_SOURCE_ID } from "../store/sourceIds";
|
|
18
|
+
import { getDisabledSources, subscribeSourceSettings } from "../store/sourceSettings";
|
|
19
|
+
export { ROUTE_SOURCE_ID };
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Buoy icons paint their knock-out parts with `fill: "bg"`, defaulting to a
|
|
@@ -32,19 +36,21 @@ const ICONS = {
|
|
|
32
36
|
export function sourceIcon(id) {
|
|
33
37
|
return ICONS[id] ?? TimeMachineIcon;
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
/** Id of the route tile — a device capability, not a snapshot provider. */
|
|
37
|
-
export const ROUTE_SOURCE_ID = "route";
|
|
38
39
|
export function useSourceStatuses() {
|
|
39
40
|
const {
|
|
40
41
|
remoteProviders,
|
|
41
42
|
remoteRoute
|
|
42
43
|
} = useTimeMachineState();
|
|
43
44
|
const localProviders = useSnapshotProviders();
|
|
44
|
-
const
|
|
45
|
+
const disabled = useSyncExternalStore(subscribeSourceSettings, getDisabledSources, getDisabledSources);
|
|
46
|
+
const providers = remoteProviders ? remoteProviders.map(p => ({
|
|
47
|
+
...p,
|
|
48
|
+
enabled: p.enabled !== false
|
|
49
|
+
})) : localProviders.map(p => ({
|
|
45
50
|
id: p.id,
|
|
46
51
|
label: p.label,
|
|
47
|
-
...p.status()
|
|
52
|
+
...p.status(),
|
|
53
|
+
enabled: !disabled.has(p.id)
|
|
48
54
|
}));
|
|
49
55
|
const routeAvailable = remoteProviders ? remoteRoute?.available === true : canNavigate();
|
|
50
56
|
return [...providers, {
|
|
@@ -52,6 +58,7 @@ export function useSourceStatuses() {
|
|
|
52
58
|
label: "Route",
|
|
53
59
|
canCapture: routeAvailable,
|
|
54
60
|
canRestore: routeAvailable,
|
|
61
|
+
enabled: remoteProviders ? remoteRoute?.enabled !== false : !disabled.has(ROUTE_SOURCE_ID),
|
|
55
62
|
// No live pathname here: these screens only re-render on store changes,
|
|
56
63
|
// so a route printed as status goes stale the moment you navigate with
|
|
57
64
|
// the tool still open. Each restore point shows the route it holds.
|
|
@@ -61,6 +68,6 @@ export function useSourceStatuses() {
|
|
|
61
68
|
|
|
62
69
|
/** A source is "attached" when a snapshot would actually pick it up. */
|
|
63
70
|
export function isAttached(source) {
|
|
64
|
-
return source.canCapture;
|
|
71
|
+
return source.canCapture && source.enabled;
|
|
65
72
|
}
|
|
66
73
|
//# sourceMappingURL=sources.js.map
|
package/lib/module/index.js
CHANGED
|
@@ -20,15 +20,21 @@ export { timeMachineToolPreset, createTimeMachineTool } from "./preset";
|
|
|
20
20
|
// Components
|
|
21
21
|
export { TimeMachineModal, TimeMachineToolView } from "./components/TimeMachineModal";
|
|
22
22
|
|
|
23
|
+
// The floating restore bar (one armed point, one tap to restore)
|
|
24
|
+
export { openTimeMachineFloatingTool, TIME_MACHINE_FLOATING_TOOL_ID } from "./components/floating/TimeMachineFloatingTool";
|
|
25
|
+
|
|
23
26
|
// External-sync adapter (auto-registered by @buoy-gg/core)
|
|
24
27
|
export { timeMachineSyncAdapter } from "./sync/timeMachineSyncAdapter";
|
|
25
28
|
|
|
26
29
|
// Programmatic snapshot control (also used by tests / power users)
|
|
27
|
-
export { timeMachineStore, RESTORE_ORDER } from "./store/timeMachineStore";
|
|
30
|
+
export { timeMachineStore, RESTORE_ORDER, SAFETY_SNAPSHOT_NAME } from "./store/timeMachineStore";
|
|
28
31
|
|
|
29
32
|
// Route capture / restore ("also take me back to the screen I was on")
|
|
30
33
|
export { armPendingRouteRestore, captureCurrentRoute, navigateToRoute, canNavigate as canRestoreRoute } from "./store/routeRestore";
|
|
31
34
|
|
|
35
|
+
// Per-source on/off switches (the Sources strip); off = never captured or restored
|
|
36
|
+
export { isSourceEnabled, setSourceEnabled, subscribeSourceSettings } from "./store/sourceSettings";
|
|
37
|
+
|
|
32
38
|
// Buoy Desktop remote-mirror seam
|
|
33
39
|
export { setRemoteTimeMachineHandler, replaceTimeMachineFromSnapshot } from "./store/timeMachineStore";
|
|
34
40
|
// A reload-mode restore that also restores the route can't navigate itself —
|
package/lib/module/preset.js
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { Platform } from "react-native";
|
|
14
15
|
import { TimeMachineIcon, TIME_MACHINE_ICON_COLOR } from "@buoy-gg/shared-ui";
|
|
15
16
|
import { TimeMachineBody } from "./components/TimeMachineBody";
|
|
16
|
-
import {
|
|
17
|
+
import { TimeMachineHeaderActions } from "./components/HeaderActions";
|
|
17
18
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
19
|
export const timeMachineToolPreset = {
|
|
19
20
|
id: "time-machine",
|
|
@@ -22,9 +23,10 @@ export const timeMachineToolPreset = {
|
|
|
22
23
|
slot: "both",
|
|
23
24
|
icon: ({
|
|
24
25
|
size,
|
|
25
|
-
color
|
|
26
|
+
color,
|
|
27
|
+
slot
|
|
26
28
|
}) => /*#__PURE__*/_jsx(TimeMachineIcon, {
|
|
27
|
-
size: size,
|
|
29
|
+
size: slot === "dial" && Platform.OS !== "web" ? size * 1.5 : size,
|
|
28
30
|
color: color || TIME_MACHINE_ICON_COLOR
|
|
29
31
|
}),
|
|
30
32
|
component: TimeMachineBody,
|
|
@@ -34,7 +36,7 @@ export const timeMachineToolPreset = {
|
|
|
34
36
|
subtitle: "snapshot & restore app state",
|
|
35
37
|
searchable: true,
|
|
36
38
|
searchPlaceholder: "Filter…",
|
|
37
|
-
headerActions: /*#__PURE__*/_jsx(
|
|
39
|
+
headerActions: /*#__PURE__*/_jsx(TimeMachineHeaderActions, {})
|
|
38
40
|
}
|
|
39
41
|
};
|
|
40
42
|
|