@buoy-gg/time-machine 7.0.35 → 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 +11 -8
- 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
package/README.md
CHANGED
|
@@ -1,55 +1,60 @@
|
|
|
1
1
|
# @buoy-gg/time-machine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Save and restore supported client state while testing a React Native app. A restore point can include registered storage, Redux, Zustand, Jotai, and the React Query cache. Review the restore preview before applying changes.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @buoy-gg/core @buoy-gg/time-machine
|
|
9
|
+
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Account setup
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
import { TimeMachineComponent } from '@buoy-gg/time-machine';
|
|
13
|
+
Start in a development build with a Free or Pro Buoy account key. From the app directory:
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npx --package=@buoy-gg/core buoy login
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
For Expo, initialize Buoy with the key written to `.env.local`:
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
```tsx
|
|
22
|
+
import { Buoy } from "@buoy-gg/core";
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
pnpm build
|
|
24
|
+
Buoy.init({ licenseKey: process.env.EXPO_PUBLIC_BUOY_KEY });
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
For React Native CLI, pass the key through your app's environment configuration; `.env.local` is not loaded automatically. Keep the menu inside the providers used by your app. Restart the development server after installing packages. See the [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) for the complete root component.
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
pnpm typecheck
|
|
32
|
-
```
|
|
29
|
+
## Use a restore point
|
|
33
30
|
|
|
34
|
-
|
|
31
|
+
Mount `FloatingDevTools` in your app. Buoy discovers this package. Install and configure the source tools for the state you want to capture: Storage, Redux, Zustand, Jotai, or React Query.
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
1. Put the app into a recognizable test state and capture a named restore point.
|
|
34
|
+
2. Change a disposable value through the app.
|
|
35
|
+
3. Open the restore preview. Check additions, removals, overwrites, skipped items, and selected sources.
|
|
36
|
+
4. Restore, then read the value from the app and inspect the per-source results.
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
To repeat one restore many times, tap **Action bar** in the tool header or swipe a restore point and choose **Bar**. The tool minimizes and a floating strip stays over the app with one restore point armed: tap to restore it (long press for state only, without changing screens), `+` to name, capture and arm a new point, the chevron beside the name picks another. Each restore from the strip saves a "Before last restore" copy first and offers Undo for ten seconds. The strip's picker has a "Reload the app after restoring" switch for screens that read their state from storage only on mount (a cart kept in a React context, for example): a live restore cannot reach `useState`, a reload can. The strip returns after a reload and can be dragged or hidden at the screen edge.
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
Live restore applies supported state in place. Restore + reload writes persisted storage and reloads JavaScript so the app can initialize from that storage. Optional Expo Router URL restoration does not recreate the entire navigation back stack.
|
|
41
|
+
|
|
42
|
+
Each tile in the Sources strip is a switch. A source switched off is left out of every capture and restore, so a store the app cannot survive having replaced (a Zustand store holding live handles, for example) can stay untouched while the rest of the state round-trips.
|
|
43
|
+
|
|
44
|
+
## Restore limits
|
|
45
|
+
|
|
46
|
+
Restoring can remove keys absent from the snapshot. Exclude values you need to preserve in the preview. Read-only or unsupported values are skipped and reported. SecureStore and MMKV require the same registered keys and instances as the Storage tool.
|
|
47
|
+
|
|
48
|
+
Backend changes, component-local useState, and in-flight requests are not restored. A restored cart or session remains subject to backend validation. Redux needs a supported full-state restore integration; writable Jotai atoms can be restored, while read-only atoms recompute. React Query mutation history is not replayed.
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
For explicit registration, use the exported `createTimeMachineTool` or `timeMachineToolPreset`. Advanced integrations can use `timeMachineStore`, `RestoreOptions`, and `RestoreOutcome`.
|
|
53
|
+
|
|
54
|
+
See the [Time Machine guide](https://buoy.gg/buoy/latest/docs/tools/time-machine) for source-specific behavior, persistent exclusions, and restore results.
|
|
55
|
+
|
|
56
|
+
## Desktop and MCP
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
React Native connections require `@buoy-gg/external-sync`. Follow the [Desktop connection guide](https://buoy.gg/buoy/latest/docs/desktop), sign in to Desktop separately, and confirm the selected device. [MCP](https://buoy.gg/buoy/latest/docs/mcp) also needs a process account and Pro access.
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
- React and React Native as peer dependencies
|
|
60
|
+
Device data can travel over your LAN to the configured broker. Account validation makes network requests. See [Telemetry](https://buoy.gg/buoy/latest/docs/telemetry) for data-flow details.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.HeaderBarButton = HeaderBarButton;
|
|
7
|
+
exports.TimeMachineHeaderActions = TimeMachineHeaderActions;
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
+
var _core = require("@buoy-gg/core");
|
|
11
|
+
var _HeaderReloadButton = require("./HeaderReloadButton");
|
|
12
|
+
var _TimeMachineFloatingTool = require("./floating/TimeMachineFloatingTool");
|
|
13
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
|
+
/**
|
|
15
|
+
* The tool's header actions, in Network's header language: labeled
|
|
16
|
+
* dark-glass chips. "Action bar" is named for the thing it opens, the
|
|
17
|
+
* floating action bar over the app, and takes the accent ink as the one
|
|
18
|
+
* action a newcomer has to find. Reload is the plain JS reload.
|
|
19
|
+
*
|
|
20
|
+
* Action bar puts a restore point on the floating bar and gets the modal out
|
|
21
|
+
* of the way. The preview screen passes the point it is showing as `armId`,
|
|
22
|
+
* so opening it from there arms that point; the list leaves it to the
|
|
23
|
+
* default rule.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
function HeaderBarButton({
|
|
27
|
+
armId
|
|
28
|
+
}) {
|
|
29
|
+
const host = (0, _core.useToolHost)();
|
|
30
|
+
const onPress = () => {
|
|
31
|
+
(0, _TimeMachineFloatingTool.openTimeMachineFloatingTool)(armId);
|
|
32
|
+
// Same move as Network: the bar is for using the app, so the modal
|
|
33
|
+
// minimizes to the dock instead of covering the screen the bar controls.
|
|
34
|
+
host.minimize();
|
|
35
|
+
};
|
|
36
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
37
|
+
style: styles.labeledChip,
|
|
38
|
+
onPress: onPress,
|
|
39
|
+
accessibilityLabel: "Open the floating action bar over the app",
|
|
40
|
+
accessibilityRole: "button",
|
|
41
|
+
testID: "time-machine-header-bar",
|
|
42
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
43
|
+
style: styles.glyph
|
|
44
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
45
|
+
style: styles.label,
|
|
46
|
+
children: "Action bar"
|
|
47
|
+
})]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function TimeMachineHeaderActions({
|
|
51
|
+
armId
|
|
52
|
+
}) {
|
|
53
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
54
|
+
style: styles.row,
|
|
55
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(HeaderBarButton, {
|
|
56
|
+
armId: armId
|
|
57
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_HeaderReloadButton.HeaderReloadButton, {})]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const styles = _reactNative.StyleSheet.create({
|
|
61
|
+
row: {
|
|
62
|
+
flexDirection: "row",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
gap: 8
|
|
65
|
+
},
|
|
66
|
+
labeledChip: {
|
|
67
|
+
height: 32,
|
|
68
|
+
flexDirection: "row",
|
|
69
|
+
alignItems: "center",
|
|
70
|
+
gap: 6,
|
|
71
|
+
paddingHorizontal: 10,
|
|
72
|
+
borderRadius: 8,
|
|
73
|
+
borderWidth: 1,
|
|
74
|
+
borderColor: _sharedUi.night.color.border,
|
|
75
|
+
backgroundColor: _sharedUi.night.color.buttonSurface
|
|
76
|
+
},
|
|
77
|
+
// A small strip outline: the thing the button opens.
|
|
78
|
+
glyph: {
|
|
79
|
+
width: 14,
|
|
80
|
+
height: 8,
|
|
81
|
+
borderRadius: 3,
|
|
82
|
+
borderWidth: 1.5,
|
|
83
|
+
borderColor: _sharedUi.night.color.accent
|
|
84
|
+
},
|
|
85
|
+
label: {
|
|
86
|
+
fontSize: 11,
|
|
87
|
+
fontWeight: "700",
|
|
88
|
+
color: _sharedUi.night.color.accent
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=HeaderActions.js.map
|
|
@@ -8,10 +8,11 @@ var _reactNative = require("react-native");
|
|
|
8
8
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
9
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* The reload header action: a plain JS-bundle reload (no restore), so
|
|
12
|
+
* in-memory state rebuilds from whatever is persisted.
|
|
13
|
+
*
|
|
14
|
+
* A labeled dark-glass chip, the same shape as the Pin chip beside it and
|
|
15
|
+
* Network's "Override": the words are the whole explanation.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
function HeaderReloadButton() {
|
|
@@ -24,38 +25,34 @@ function HeaderReloadButton() {
|
|
|
24
25
|
// No reload mechanism available (bare production build) — nothing to do.
|
|
25
26
|
}
|
|
26
27
|
};
|
|
27
|
-
return /*#__PURE__*/(0, _jsxRuntime.
|
|
28
|
-
style: styles.
|
|
28
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
29
|
+
style: styles.chip,
|
|
29
30
|
onPress: onPress,
|
|
30
|
-
hitSlop: {
|
|
31
|
-
top: 8,
|
|
32
|
-
bottom: 8,
|
|
33
|
-
left: 8,
|
|
34
|
-
right: 8
|
|
35
|
-
},
|
|
36
31
|
accessibilityLabel: "Reload app",
|
|
32
|
+
accessibilityHint: "Reloads the JavaScript bundle without restoring anything",
|
|
37
33
|
accessibilityRole: "button",
|
|
38
34
|
testID: "time-machine-reload-app",
|
|
39
|
-
children:
|
|
40
|
-
size: 12,
|
|
41
|
-
color: _sharedUi.night.color.textSecondary
|
|
42
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
35
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
43
36
|
style: styles.label,
|
|
44
|
-
children: "
|
|
45
|
-
})
|
|
37
|
+
children: "Reload app"
|
|
38
|
+
})
|
|
46
39
|
});
|
|
47
40
|
}
|
|
48
41
|
const styles = _reactNative.StyleSheet.create({
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
chip: {
|
|
43
|
+
height: 32,
|
|
44
|
+
paddingHorizontal: 10,
|
|
45
|
+
borderRadius: 8,
|
|
46
|
+
borderWidth: 1,
|
|
47
|
+
borderColor: _sharedUi.night.color.border,
|
|
48
|
+
backgroundColor: _sharedUi.night.color.buttonSurface,
|
|
51
49
|
alignItems: "center",
|
|
52
|
-
|
|
53
|
-
padding: 4
|
|
50
|
+
justifyContent: "center"
|
|
54
51
|
},
|
|
55
52
|
label: {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
fontSize: 11,
|
|
54
|
+
fontWeight: "700",
|
|
55
|
+
color: _sharedUi.night.color.textSecondary
|
|
59
56
|
}
|
|
60
57
|
});
|
|
61
58
|
//# sourceMappingURL=HeaderReloadButton.js.map
|
|
@@ -8,7 +8,6 @@ var _react = require("react");
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
10
|
var _core = require("@buoy-gg/core");
|
|
11
|
-
var _license = require("@buoy-gg/license");
|
|
12
11
|
var _useTimeMachine = require("../hooks/useTimeMachine");
|
|
13
12
|
var _timeMachineStore = require("../store/timeMachineStore");
|
|
14
13
|
var _primitives = require("../ui/primitives");
|
|
@@ -22,6 +21,9 @@ var _UndoDeleteBar = require("./list/UndoDeleteBar");
|
|
|
22
21
|
var _SourcesStrip = require("./list/SourcesStrip");
|
|
23
22
|
var _WipeAllRow = require("./list/WipeAllRow");
|
|
24
23
|
var _PreviewFlow = require("./preview/PreviewFlow");
|
|
24
|
+
var _TimeMachineFloatingTool = require("./floating/TimeMachineFloatingTool");
|
|
25
|
+
var _armedPoint = require("./floating/armedPoint");
|
|
26
|
+
var _sourceSettings = require("../store/sourceSettings");
|
|
25
27
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
26
28
|
/**
|
|
27
29
|
* Time Machine body — the screen router plus the restore-point list.
|
|
@@ -37,6 +39,7 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
37
39
|
* app-wide wipe kept last and understated.
|
|
38
40
|
*/
|
|
39
41
|
|
|
42
|
+
const isBarOpen = () => (0, _sharedUi.getFloatingTools)().some(tool => tool.id === _TimeMachineFloatingTool.TIME_MACHINE_FLOATING_TOOL_ID);
|
|
40
43
|
function TimeMachineBody() {
|
|
41
44
|
const [screen, setScreen] = (0, _react.useState)({
|
|
42
45
|
kind: "list"
|
|
@@ -83,21 +86,35 @@ function RestorePointList({
|
|
|
83
86
|
hasLoaded,
|
|
84
87
|
busy,
|
|
85
88
|
lastOutcome,
|
|
86
|
-
recentlyDeleted
|
|
89
|
+
recentlyDeleted,
|
|
90
|
+
remoteProviders
|
|
87
91
|
} = (0, _useTimeMachine.useTimeMachineState)();
|
|
88
|
-
const isPro = (0, _license.useIsPro)();
|
|
89
92
|
const snapshotLimit = (0, _sharedUi.useLimit)("timeMachine.snapshots");
|
|
90
93
|
const host = (0, _core.useToolHost)();
|
|
91
94
|
const insets = (0, _sharedUi.useSafeAreaInsets)({
|
|
92
95
|
minBottom: 16
|
|
93
96
|
});
|
|
97
|
+
// Which point the floating restore bar has armed, while the bar is open.
|
|
98
|
+
const barOpen = (0, _react.useSyncExternalStore)(_sharedUi.subscribeFloatingTools, isBarOpen, isBarOpen);
|
|
99
|
+
const pickedId = (0, _react.useSyncExternalStore)(_armedPoint.subscribePickedPoint, _armedPoint.getPickedPointId, _armedPoint.getPickedPointId);
|
|
100
|
+
const armedId = barOpen ? (0, _armedPoint.resolveArmedPoint)(snapshots, pickedId)?.id : undefined;
|
|
101
|
+
// Restoring the same point from this list again and again is what the bar
|
|
102
|
+
// is for; say so once, at the moment it is true.
|
|
103
|
+
const listRestores = (0, _react.useRef)({});
|
|
104
|
+
const [barHintFor, setBarHintFor] = (0, _react.useState)(null);
|
|
105
|
+
const putOnBar = id => {
|
|
106
|
+
setBarHintFor(null);
|
|
107
|
+
(0, _TimeMachineFloatingTool.openTimeMachineFloatingTool)(id);
|
|
108
|
+
host.minimize();
|
|
109
|
+
};
|
|
94
110
|
|
|
95
111
|
// Header search (when hosted by the core shell) filters the list.
|
|
96
112
|
const query = host.search.trim().toLowerCase();
|
|
97
113
|
const visible = query ? snapshots.filter(s => s.name.toLowerCase().includes(query)) : snapshots;
|
|
98
114
|
|
|
99
|
-
// Infinity for Pro, so this is simply false for them.
|
|
100
|
-
|
|
115
|
+
// Infinity for Pro, so this is simply false for them. The bar's safety
|
|
116
|
+
// slot is not a point you chose to keep, so it does not count.
|
|
117
|
+
const atCaptureLimit = snapshots.filter(s => !s.safety).length >= snapshotLimit;
|
|
101
118
|
const captureDisabled = busy !== null || atCaptureLimit;
|
|
102
119
|
|
|
103
120
|
/**
|
|
@@ -117,12 +134,16 @@ function RestorePointList({
|
|
|
117
134
|
(0, _confirm.confirmDestructive)("Restore fresh install?", "This wipes all app storage and reloads the app.", "Wipe & reload", () => void _timeMachineStore.timeMachineStore.restore(meta.id, "reload"));
|
|
118
135
|
return;
|
|
119
136
|
}
|
|
137
|
+
const count = (listRestores.current[meta.id] ?? 0) + 1;
|
|
138
|
+
listRestores.current[meta.id] = count;
|
|
139
|
+
if (count >= 2 && !barOpen) setBarHintFor(meta.id);
|
|
120
140
|
void _timeMachineStore.timeMachineStore.restore(meta.id, "live").then(outcome => {
|
|
121
141
|
const clean = Object.values(outcome.results).every(r => r.ok);
|
|
122
142
|
if (!clean || outcome.willReload) return;
|
|
123
143
|
host.minimize();
|
|
124
144
|
});
|
|
125
145
|
};
|
|
146
|
+
const barHint = barHintFor && !barOpen ? snapshots.find(s => s.id === barHintFor) ?? null : null;
|
|
126
147
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
127
148
|
style: styles.fill,
|
|
128
149
|
contentContainerStyle: [styles.content, {
|
|
@@ -152,24 +173,47 @@ function RestorePointList({
|
|
|
152
173
|
}), atCaptureLimit ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Footnote, {
|
|
153
174
|
tone: "warning",
|
|
154
175
|
testID: "time-machine-limit",
|
|
155
|
-
children: ["Free plan keeps ", snapshotLimit, " restore
|
|
176
|
+
children: ["Free plan keeps ", snapshotLimit, " restore point", snapshotLimit === 1 ? "" : "s", ". Upgrade to Pro for unlimited snapshots."]
|
|
156
177
|
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.SectionLabel, {
|
|
157
178
|
children: "Sources"
|
|
158
179
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_SourcesStrip.SourcesStrip, {
|
|
159
|
-
sources: sources
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
sources: sources,
|
|
181
|
+
onToggle: remoteProviders ? undefined : _sourceSettings.setSourceEnabled
|
|
182
|
+
}), remoteProviders ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Footnote, {
|
|
183
|
+
testID: "time-machine-sources-hint",
|
|
184
|
+
children: "Switch a source off to leave it out of every capture and restore."
|
|
185
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.SectionLabel, {
|
|
164
186
|
children: "Restore Points"
|
|
165
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(
|
|
187
|
+
}), barHint ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
188
|
+
style: styles.barHint,
|
|
189
|
+
testID: "time-machine-bar-hint",
|
|
190
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Footnote, {
|
|
191
|
+
children: ["Restoring \"", barHint.name, "\" a lot? Put it on the floating action bar and restore it with one tap."]
|
|
192
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
193
|
+
onPress: () => putOnBar(barHint.id),
|
|
194
|
+
accessibilityRole: "button",
|
|
195
|
+
testID: "time-machine-bar-hint-pin",
|
|
196
|
+
hitSlop: {
|
|
197
|
+
top: 6,
|
|
198
|
+
bottom: 6,
|
|
199
|
+
left: 6,
|
|
200
|
+
right: 6
|
|
201
|
+
},
|
|
202
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
203
|
+
style: styles.barHintAction,
|
|
204
|
+
children: "Open the action bar"
|
|
205
|
+
})
|
|
206
|
+
})]
|
|
207
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Group, {
|
|
166
208
|
children: [visible.map(meta => /*#__PURE__*/(0, _jsxRuntime.jsx)(_SnapshotRow.SnapshotRow, {
|
|
167
209
|
meta: meta,
|
|
168
210
|
busy: busy !== null,
|
|
211
|
+
armed: meta.id === armedId,
|
|
169
212
|
onOpen: () => onOpenPreview(meta),
|
|
170
213
|
onRestore: () => restore(meta),
|
|
171
214
|
onDelete: () => void _timeMachineStore.timeMachineStore.delete(meta.id),
|
|
172
|
-
onDuplicate: () => void _timeMachineStore.timeMachineStore.duplicate(meta.id)
|
|
215
|
+
onDuplicate: () => void _timeMachineStore.timeMachineStore.duplicate(meta.id),
|
|
216
|
+
onArm: () => putOnBar(meta.id)
|
|
173
217
|
}, meta.id)), visible.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
|
|
174
218
|
testID: "time-machine-empty",
|
|
175
219
|
...(!hasLoaded ? {
|
|
@@ -205,6 +249,17 @@ function RestorePointList({
|
|
|
205
249
|
});
|
|
206
250
|
}
|
|
207
251
|
const styles = _reactNative.StyleSheet.create({
|
|
252
|
+
barHint: {
|
|
253
|
+
marginHorizontal: _tmTheme.tm.gutter,
|
|
254
|
+
marginBottom: 8
|
|
255
|
+
},
|
|
256
|
+
barHintAction: {
|
|
257
|
+
color: _tmTheme.tm.color.accent,
|
|
258
|
+
fontSize: _tmTheme.tm.font.footnote,
|
|
259
|
+
fontWeight: "600",
|
|
260
|
+
paddingHorizontal: _tmTheme.tm.textInset,
|
|
261
|
+
paddingTop: 2
|
|
262
|
+
},
|
|
208
263
|
fill: {
|
|
209
264
|
flex: 1,
|
|
210
265
|
// Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NamePromptModal = NamePromptModal;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
+
/**
|
|
12
|
+
* "Name this restore point": the dialog `+` on the restore bar opens.
|
|
13
|
+
*
|
|
14
|
+
* A real Modal rather than a panel under the strip, so it sits centered over
|
|
15
|
+
* the app, above the keyboard, wherever the strip happens to be. An empty
|
|
16
|
+
* name is allowed: the store then names the point after the screen and the
|
|
17
|
+
* time, the same default the tool's capture bar uses.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
function NamePromptModal({
|
|
21
|
+
visible,
|
|
22
|
+
summary,
|
|
23
|
+
onCapture,
|
|
24
|
+
onCancel
|
|
25
|
+
}) {
|
|
26
|
+
const [name, setName] = (0, _react.useState)("");
|
|
27
|
+
// Every opening starts blank, so a cancelled name never comes back.
|
|
28
|
+
(0, _react.useEffect)(() => {
|
|
29
|
+
if (visible) setName("");
|
|
30
|
+
}, [visible]);
|
|
31
|
+
const submit = () => onCapture(name.trim());
|
|
32
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
|
|
33
|
+
visible: visible,
|
|
34
|
+
transparent: true,
|
|
35
|
+
animationType: "fade",
|
|
36
|
+
onRequestClose: onCancel,
|
|
37
|
+
statusBarTranslucent: true,
|
|
38
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.KeyboardAvoidingView, {
|
|
39
|
+
behavior: _reactNative.Platform.OS === "ios" ? "padding" : undefined,
|
|
40
|
+
style: styles.fill,
|
|
41
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
42
|
+
style: styles.backdrop,
|
|
43
|
+
onPress: onCancel,
|
|
44
|
+
accessibilityLabel: "Cancel",
|
|
45
|
+
testID: "time-machine-bar-name-backdrop",
|
|
46
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
47
|
+
style: styles.card,
|
|
48
|
+
onPress: event => event.stopPropagation(),
|
|
49
|
+
testID: "time-machine-bar-name-panel"
|
|
50
|
+
// A Pressable is one accessibility element by default, which
|
|
51
|
+
// hid the field and both buttons from VoiceOver and from tests.
|
|
52
|
+
,
|
|
53
|
+
accessible: false,
|
|
54
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
55
|
+
style: styles.title,
|
|
56
|
+
children: "Name this restore point"
|
|
57
|
+
}), summary ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
58
|
+
style: styles.summary,
|
|
59
|
+
testID: "time-machine-bar-name-summary",
|
|
60
|
+
children: summary
|
|
61
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
|
|
62
|
+
testID: "time-machine-bar-name-input",
|
|
63
|
+
accessibilityLabel: "Restore point name",
|
|
64
|
+
value: name,
|
|
65
|
+
onChangeText: setName,
|
|
66
|
+
onSubmitEditing: submit,
|
|
67
|
+
placeholder: "Screen and time, if left empty",
|
|
68
|
+
placeholderTextColor: _sharedUi.night.color.placeholder,
|
|
69
|
+
autoFocus: true,
|
|
70
|
+
autoCorrect: false
|
|
71
|
+
// Names are labels, not sentences: "bag" should stay "bag".
|
|
72
|
+
,
|
|
73
|
+
autoCapitalize: "none",
|
|
74
|
+
returnKeyType: "done",
|
|
75
|
+
style: styles.input
|
|
76
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
77
|
+
style: styles.hint,
|
|
78
|
+
children: "Captures the state the app is in right now and arms it on the bar."
|
|
79
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
80
|
+
style: styles.actions,
|
|
81
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
82
|
+
testID: "time-machine-bar-name-cancel",
|
|
83
|
+
accessibilityRole: "button",
|
|
84
|
+
onPress: onCancel,
|
|
85
|
+
style: styles.secondary,
|
|
86
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
87
|
+
style: styles.secondaryText,
|
|
88
|
+
children: "Cancel"
|
|
89
|
+
})
|
|
90
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
91
|
+
testID: "time-machine-bar-name-capture",
|
|
92
|
+
accessibilityRole: "button",
|
|
93
|
+
accessibilityLabel: "Capture and arm this restore point",
|
|
94
|
+
onPress: submit,
|
|
95
|
+
style: styles.primary,
|
|
96
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
97
|
+
style: styles.primaryText,
|
|
98
|
+
children: "Capture"
|
|
99
|
+
})
|
|
100
|
+
})]
|
|
101
|
+
})]
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const styles = _reactNative.StyleSheet.create({
|
|
108
|
+
fill: {
|
|
109
|
+
flex: 1
|
|
110
|
+
},
|
|
111
|
+
backdrop: {
|
|
112
|
+
flex: 1,
|
|
113
|
+
backgroundColor: "rgba(0, 0, 0, 0.55)",
|
|
114
|
+
alignItems: "center",
|
|
115
|
+
justifyContent: "center",
|
|
116
|
+
padding: 24
|
|
117
|
+
},
|
|
118
|
+
card: {
|
|
119
|
+
width: "100%",
|
|
120
|
+
maxWidth: 360,
|
|
121
|
+
borderRadius: _sharedUi.night.radius.sheet,
|
|
122
|
+
borderWidth: 1,
|
|
123
|
+
borderColor: _sharedUi.night.color.separator,
|
|
124
|
+
backgroundColor: _sharedUi.night.color.surface,
|
|
125
|
+
padding: 18,
|
|
126
|
+
gap: 12,
|
|
127
|
+
shadowColor: "#000",
|
|
128
|
+
shadowOffset: {
|
|
129
|
+
width: 0,
|
|
130
|
+
height: 12
|
|
131
|
+
},
|
|
132
|
+
shadowOpacity: 0.4,
|
|
133
|
+
shadowRadius: 24,
|
|
134
|
+
elevation: 12
|
|
135
|
+
},
|
|
136
|
+
title: {
|
|
137
|
+
color: _sharedUi.night.color.text,
|
|
138
|
+
fontSize: _sharedUi.night.font.title,
|
|
139
|
+
fontWeight: "600"
|
|
140
|
+
},
|
|
141
|
+
input: {
|
|
142
|
+
minHeight: 44,
|
|
143
|
+
borderRadius: _sharedUi.night.radius.input,
|
|
144
|
+
borderWidth: 1,
|
|
145
|
+
borderColor: _sharedUi.night.color.accentBorder,
|
|
146
|
+
backgroundColor: _sharedUi.night.color.surfaceElevated,
|
|
147
|
+
color: _sharedUi.night.color.text,
|
|
148
|
+
fontSize: _sharedUi.night.font.row,
|
|
149
|
+
paddingHorizontal: 12
|
|
150
|
+
},
|
|
151
|
+
summary: {
|
|
152
|
+
color: _sharedUi.night.color.textTertiary,
|
|
153
|
+
fontSize: _sharedUi.night.font.caption,
|
|
154
|
+
marginTop: -6
|
|
155
|
+
},
|
|
156
|
+
hint: {
|
|
157
|
+
color: _sharedUi.night.color.textSecondary,
|
|
158
|
+
fontSize: _sharedUi.night.font.caption,
|
|
159
|
+
lineHeight: 18
|
|
160
|
+
},
|
|
161
|
+
actions: {
|
|
162
|
+
flexDirection: "row",
|
|
163
|
+
justifyContent: "flex-end",
|
|
164
|
+
gap: 10,
|
|
165
|
+
marginTop: 4
|
|
166
|
+
},
|
|
167
|
+
secondary: {
|
|
168
|
+
minHeight: 40,
|
|
169
|
+
paddingHorizontal: 14,
|
|
170
|
+
borderRadius: _sharedUi.night.radius.input,
|
|
171
|
+
alignItems: "center",
|
|
172
|
+
justifyContent: "center"
|
|
173
|
+
},
|
|
174
|
+
secondaryText: {
|
|
175
|
+
color: _sharedUi.night.color.textSecondary,
|
|
176
|
+
fontSize: 14,
|
|
177
|
+
fontWeight: "600"
|
|
178
|
+
},
|
|
179
|
+
primary: {
|
|
180
|
+
minHeight: 40,
|
|
181
|
+
paddingHorizontal: 18,
|
|
182
|
+
borderRadius: _sharedUi.night.radius.input,
|
|
183
|
+
backgroundColor: _sharedUi.night.color.accent,
|
|
184
|
+
alignItems: "center",
|
|
185
|
+
justifyContent: "center"
|
|
186
|
+
},
|
|
187
|
+
primaryText: {
|
|
188
|
+
color: _sharedUi.night.color.onAccent,
|
|
189
|
+
fontSize: 14,
|
|
190
|
+
fontWeight: "700"
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
//# sourceMappingURL=NamePromptModal.js.map
|