@buoy-gg/shared-ui 7.0.22 → 7.0.24
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/lib/commonjs/JsModal.js +39 -16
- package/lib/commonjs/dataViewer/JsonStringNotice.js +59 -0
- package/lib/commonjs/dataViewer/ThemedSplitView.js +20 -1
- package/lib/commonjs/dataViewer/TimestampDeltaBar.js +96 -0
- package/lib/commonjs/dataViewer/index.js +52 -0
- package/lib/commonjs/dataViewer/jsonStringValue.js +94 -0
- package/lib/commonjs/dataViewer/timestampDiff.js +146 -0
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/index.js +50 -1
- package/lib/commonjs/license/FeatureGate.js +43 -10
- package/lib/commonjs/license/FreeAccountPrompt.js +181 -0
- package/lib/commonjs/license/index.js +57 -42
- package/lib/commonjs/license/licenseModule.js +99 -0
- package/lib/commonjs/license/tierLimits.js +165 -0
- package/lib/commonjs/ui/backgrounds/BackgroundSwitcher.js +180 -0
- package/lib/commonjs/ui/backgrounds/README.md +115 -0
- package/lib/commonjs/ui/backgrounds/ToolBackground.js +74 -0
- package/lib/commonjs/ui/backgrounds/backgroundStore.js +116 -0
- package/lib/commonjs/ui/backgrounds/index.js +70 -0
- package/lib/commonjs/ui/backgrounds/primitives.js +333 -0
- package/lib/commonjs/ui/backgrounds/registry.js +112 -0
- package/lib/commonjs/ui/backgrounds/rng.js +72 -0
- package/lib/commonjs/ui/backgrounds/types.js +1 -0
- package/lib/commonjs/ui/backgrounds/variants/deepField.js +351 -0
- package/lib/commonjs/ui/backgrounds/variants/lattice.js +579 -0
- package/lib/commonjs/ui/backgrounds/variants/starfield.js +210 -0
- package/lib/commonjs/ui/index.js +15 -3
- package/lib/module/JsModal.js +39 -16
- package/lib/module/dataViewer/JsonStringNotice.js +54 -0
- package/lib/module/dataViewer/ThemedSplitView.js +20 -1
- package/lib/module/dataViewer/TimestampDeltaBar.js +91 -0
- package/lib/module/dataViewer/index.js +4 -0
- package/lib/module/dataViewer/jsonStringValue.js +89 -0
- package/lib/module/dataViewer/timestampDiff.js +139 -0
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/license/FeatureGate.js +43 -10
- package/lib/module/license/FreeAccountPrompt.js +176 -0
- package/lib/module/license/index.js +5 -40
- package/lib/module/license/licenseModule.js +92 -0
- package/lib/module/license/tierLimits.js +158 -0
- package/lib/module/ui/backgrounds/BackgroundSwitcher.js +176 -0
- package/lib/module/ui/backgrounds/README.md +115 -0
- package/lib/module/ui/backgrounds/ToolBackground.js +70 -0
- package/lib/module/ui/backgrounds/backgroundStore.js +111 -0
- package/lib/module/ui/backgrounds/index.js +9 -0
- package/lib/module/ui/backgrounds/primitives.js +328 -0
- package/lib/module/ui/backgrounds/registry.js +105 -0
- package/lib/module/ui/backgrounds/rng.js +65 -0
- package/lib/module/ui/backgrounds/types.js +1 -0
- package/lib/module/ui/backgrounds/variants/deepField.js +347 -0
- package/lib/module/ui/backgrounds/variants/lattice.js +575 -0
- package/lib/module/ui/backgrounds/variants/starfield.js +206 -0
- package/lib/module/ui/index.js +3 -0
- package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/JsonStringNotice.d.ts +6 -0
- package/lib/typescript/commonjs/dataViewer/JsonStringNotice.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/ThemedSplitView.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/TimestampDeltaBar.d.ts +7 -0
- package/lib/typescript/commonjs/dataViewer/TimestampDeltaBar.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/index.d.ts +6 -0
- package/lib/typescript/commonjs/dataViewer/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/jsonStringValue.d.ts +25 -0
- package/lib/typescript/commonjs/dataViewer/jsonStringValue.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/timestampDiff.d.ts +55 -0
- package/lib/typescript/commonjs/dataViewer/timestampDiff.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/FeatureGate.d.ts +19 -3
- package/lib/typescript/commonjs/license/FeatureGate.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts +23 -0
- package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts.map +1 -0
- package/lib/typescript/commonjs/license/index.d.ts +6 -15
- package/lib/typescript/commonjs/license/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/licenseModule.d.ts +55 -0
- package/lib/typescript/commonjs/license/licenseModule.d.ts.map +1 -0
- package/lib/typescript/commonjs/license/tierLimits.d.ts +143 -0
- package/lib/typescript/commonjs/license/tierLimits.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/BackgroundSwitcher.d.ts +13 -0
- package/lib/typescript/commonjs/ui/backgrounds/BackgroundSwitcher.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts +30 -0
- package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/backgroundStore.d.ts +43 -0
- package/lib/typescript/commonjs/ui/backgrounds/backgroundStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/index.d.ts +7 -0
- package/lib/typescript/commonjs/ui/backgrounds/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/primitives.d.ts +106 -0
- package/lib/typescript/commonjs/ui/backgrounds/primitives.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts +28 -0
- package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/rng.d.ts +27 -0
- package/lib/typescript/commonjs/ui/backgrounds/rng.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/types.d.ts +14 -0
- package/lib/typescript/commonjs/ui/backgrounds/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/deepField.d.ts +65 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/deepField.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/lattice.d.ts +33 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/lattice.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/starfield.d.ts +12 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/starfield.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/index.d.ts +1 -0
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/JsModal.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/JsonStringNotice.d.ts +6 -0
- package/lib/typescript/module/dataViewer/JsonStringNotice.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/ThemedSplitView.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/TimestampDeltaBar.d.ts +7 -0
- package/lib/typescript/module/dataViewer/TimestampDeltaBar.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/index.d.ts +6 -0
- package/lib/typescript/module/dataViewer/index.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/jsonStringValue.d.ts +25 -0
- package/lib/typescript/module/dataViewer/jsonStringValue.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/timestampDiff.d.ts +55 -0
- package/lib/typescript/module/dataViewer/timestampDiff.d.ts.map +1 -0
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/index.d.ts +2 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/license/FeatureGate.d.ts +19 -3
- package/lib/typescript/module/license/FeatureGate.d.ts.map +1 -1
- package/lib/typescript/module/license/FreeAccountPrompt.d.ts +23 -0
- package/lib/typescript/module/license/FreeAccountPrompt.d.ts.map +1 -0
- package/lib/typescript/module/license/index.d.ts +6 -15
- package/lib/typescript/module/license/index.d.ts.map +1 -1
- package/lib/typescript/module/license/licenseModule.d.ts +55 -0
- package/lib/typescript/module/license/licenseModule.d.ts.map +1 -0
- package/lib/typescript/module/license/tierLimits.d.ts +143 -0
- package/lib/typescript/module/license/tierLimits.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/BackgroundSwitcher.d.ts +13 -0
- package/lib/typescript/module/ui/backgrounds/BackgroundSwitcher.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts +30 -0
- package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/backgroundStore.d.ts +43 -0
- package/lib/typescript/module/ui/backgrounds/backgroundStore.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/index.d.ts +7 -0
- package/lib/typescript/module/ui/backgrounds/index.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/primitives.d.ts +106 -0
- package/lib/typescript/module/ui/backgrounds/primitives.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/registry.d.ts +28 -0
- package/lib/typescript/module/ui/backgrounds/registry.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/rng.d.ts +27 -0
- package/lib/typescript/module/ui/backgrounds/rng.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/types.d.ts +14 -0
- package/lib/typescript/module/ui/backgrounds/types.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/variants/deepField.d.ts +65 -0
- package/lib/typescript/module/ui/backgrounds/variants/deepField.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/variants/lattice.d.ts +33 -0
- package/lib/typescript/module/ui/backgrounds/variants/lattice.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/variants/starfield.d.ts +12 -0
- package/lib/typescript/module/ui/backgrounds/variants/starfield.d.ts.map +1 -0
- package/lib/typescript/module/ui/index.d.ts +1 -0
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/scripts/detect-safe-area.js +0 -20
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.TIER_LIMITS = void 0;
|
|
7
|
+
exports.freeKeyRaises = freeKeyRaises;
|
|
8
|
+
exports.resolveEffectiveTier = resolveEffectiveTier;
|
|
9
|
+
exports.useLimit = useLimit;
|
|
10
|
+
var _licenseModule = require("./licenseModule.js");
|
|
11
|
+
/**
|
|
12
|
+
* Per-tier numeric limits — the ONLY thing that separates anonymous from free.
|
|
13
|
+
*
|
|
14
|
+
* Every binary Pro gate (copy-to-clipboard, state editing, storage mutations,
|
|
15
|
+
* navigation) is identical for both: locked. That keeps the tier split to one
|
|
16
|
+
* table instead of scattered conditionals.
|
|
17
|
+
*
|
|
18
|
+
* The governing rule: **the free column never moves.** Anonymous is carved out
|
|
19
|
+
* BELOW today's free tier — nothing is ever added to free in exchange for an
|
|
20
|
+
* account, because that would come straight out of the Pro upgrade.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const LIMITS = {
|
|
24
|
+
// ── network ───────────────────────────────────────────────────────────────
|
|
25
|
+
"network.requests": {
|
|
26
|
+
anonymous: 5,
|
|
27
|
+
free: 25,
|
|
28
|
+
pro: Infinity
|
|
29
|
+
},
|
|
30
|
+
"network.saved": {
|
|
31
|
+
anonymous: 1,
|
|
32
|
+
free: 5,
|
|
33
|
+
pro: 50
|
|
34
|
+
},
|
|
35
|
+
// Already 1 for free, so 25% would be 0 — which deletes the feature rather
|
|
36
|
+
// than trialling it. Raising the FREE value to make room is not an option.
|
|
37
|
+
"network.overrideRules": {
|
|
38
|
+
anonymous: 1,
|
|
39
|
+
free: 1,
|
|
40
|
+
pro: 50
|
|
41
|
+
},
|
|
42
|
+
// ── storage ───────────────────────────────────────────────────────────────
|
|
43
|
+
"storage.events": {
|
|
44
|
+
anonymous: 5,
|
|
45
|
+
free: 25,
|
|
46
|
+
pro: Infinity
|
|
47
|
+
},
|
|
48
|
+
"storage.keys": {
|
|
49
|
+
anonymous: 5,
|
|
50
|
+
free: 25,
|
|
51
|
+
pro: Infinity
|
|
52
|
+
},
|
|
53
|
+
// Same floor-at-1 reasoning as network.overrideRules.
|
|
54
|
+
"storage.mmkvInstances": {
|
|
55
|
+
anonymous: 1,
|
|
56
|
+
free: 1,
|
|
57
|
+
pro: Infinity
|
|
58
|
+
},
|
|
59
|
+
// ── state tools ───────────────────────────────────────────────────────────
|
|
60
|
+
"redux.actions": {
|
|
61
|
+
anonymous: 5,
|
|
62
|
+
free: 25,
|
|
63
|
+
pro: Infinity
|
|
64
|
+
},
|
|
65
|
+
"zustand.changes": {
|
|
66
|
+
anonymous: 5,
|
|
67
|
+
free: 25,
|
|
68
|
+
pro: Infinity
|
|
69
|
+
},
|
|
70
|
+
"jotai.changes": {
|
|
71
|
+
anonymous: 5,
|
|
72
|
+
free: 25,
|
|
73
|
+
pro: Infinity
|
|
74
|
+
},
|
|
75
|
+
"events.entries": {
|
|
76
|
+
anonymous: 5,
|
|
77
|
+
free: 25,
|
|
78
|
+
pro: Infinity
|
|
79
|
+
},
|
|
80
|
+
// ── render / navigation ───────────────────────────────────────────────────
|
|
81
|
+
"highlightUpdates.components": {
|
|
82
|
+
anonymous: 5,
|
|
83
|
+
free: 25,
|
|
84
|
+
pro: Infinity
|
|
85
|
+
},
|
|
86
|
+
"highlightUpdates.historyEvents": {
|
|
87
|
+
anonymous: 1,
|
|
88
|
+
free: 3,
|
|
89
|
+
pro: Infinity
|
|
90
|
+
},
|
|
91
|
+
"highlightUpdates.detailEvents": {
|
|
92
|
+
anonymous: 2,
|
|
93
|
+
free: 5,
|
|
94
|
+
pro: Infinity
|
|
95
|
+
},
|
|
96
|
+
"routeEvents.entries": {
|
|
97
|
+
anonymous: 1,
|
|
98
|
+
free: 3,
|
|
99
|
+
pro: Infinity
|
|
100
|
+
},
|
|
101
|
+
"timeMachine.snapshots": {
|
|
102
|
+
anonymous: 1,
|
|
103
|
+
free: 2,
|
|
104
|
+
pro: Infinity
|
|
105
|
+
},
|
|
106
|
+
"perfMonitor.automationCases": {
|
|
107
|
+
anonymous: 1,
|
|
108
|
+
free: 3,
|
|
109
|
+
pro: Infinity
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const TIER_LIMITS = exports.TIER_LIMITS = LIMITS;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The tier whose limits actually apply.
|
|
116
|
+
*
|
|
117
|
+
* `hasProAccess` is `useIsPro()`, which is true for a real Pro license OR the
|
|
118
|
+
* free Weekend Pass. Routing the Weekend Pass through here is what keeps
|
|
119
|
+
* today's behavior intact: weekend users get Pro limits, exactly as they do
|
|
120
|
+
* now. When the Weekend Pass is later gated to key-holders, that change lands
|
|
121
|
+
* here for free — no limit call site has to know about it.
|
|
122
|
+
*/
|
|
123
|
+
function resolveEffectiveTier(tier, hasProAccess) {
|
|
124
|
+
if (hasProAccess) return "pro";
|
|
125
|
+
// The switch lives in @buoy-gg/license so that this and the Weekend Pass
|
|
126
|
+
// change flip together — see isAnonymousTierEnabled().
|
|
127
|
+
if (!(0, _licenseModule.isAnonymousTierEnabled)() && tier === "anonymous") return "free";
|
|
128
|
+
return tier;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The limit for the current tier.
|
|
133
|
+
*
|
|
134
|
+
* Replaces the per-package `const FREE_TIER_X = 25` + `isPro ? Infinity : X`
|
|
135
|
+
* pattern. `Infinity` is returned for uncapped tiers and is safe to use
|
|
136
|
+
* directly in `slice(0, limit)` and `length >= limit` comparisons.
|
|
137
|
+
*/
|
|
138
|
+
function useLimit(key) {
|
|
139
|
+
const hasProAccess = (0, _licenseModule.getUseIsPro)()();
|
|
140
|
+
const tier = (0, _licenseModule.getUseTier)()();
|
|
141
|
+
return TIER_LIMITS[key][resolveEffectiveTier(tier, hasProAccess)];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Whether claiming a FREE key would actually raise this limit right now.
|
|
146
|
+
*
|
|
147
|
+
* The anonymous upsell must never promise something a free key won't deliver.
|
|
148
|
+
* Two ways it could lie, and this closes both:
|
|
149
|
+
*
|
|
150
|
+
* - **Pro-exclusive features.** Copy-to-clipboard, state editing, storage
|
|
151
|
+
* mutations and navigation are locked for anonymous AND free alike. Telling
|
|
152
|
+
* an anonymous user a free key opens them would be false.
|
|
153
|
+
* - **Limits that are already equal.** Override rules and MMKV instances sit
|
|
154
|
+
* at 1 for both tiers, so a key changes nothing there either.
|
|
155
|
+
*
|
|
156
|
+
* It also respects `ANONYMOUS_TIER_ENABLED`: while the anonymous tier is off,
|
|
157
|
+
* an anonymous user is *already* getting the free limits, so there is nothing
|
|
158
|
+
* to claim and this correctly returns false everywhere. That falls out of
|
|
159
|
+
* asking `resolveEffectiveTier` rather than reading the table directly, so the
|
|
160
|
+
* switch and the copy can never disagree.
|
|
161
|
+
*/
|
|
162
|
+
function freeKeyRaises(key) {
|
|
163
|
+
const limits = TIER_LIMITS[key];
|
|
164
|
+
return limits[resolveEffectiveTier("anonymous", false)] < limits[resolveEffectiveTier("free", false)];
|
|
165
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.BackgroundSwitcher = void 0;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _lucideIcons = require("../../icons/lucide-icons.js");
|
|
10
|
+
var _macOSDesignSystemColors = require("../gameUI/constants/macOSDesignSystemColors.js");
|
|
11
|
+
var _backgroundStore = require("./backgroundStore.js");
|
|
12
|
+
var _registry = require("./registry.js");
|
|
13
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
|
+
/**
|
|
15
|
+
* The ‹ prev / next › strip for auditioning backgrounds.
|
|
16
|
+
*
|
|
17
|
+
* This is a REVIEW control, not shipped chrome: it exists so the whole
|
|
18
|
+
* catalogue can be walked without a rebuild, with the cost of each preset shown
|
|
19
|
+
* next to its name. Mount it at the top of a tool's body while evaluating, and
|
|
20
|
+
* pull it once a winner is picked (or keep it behind a setting).
|
|
21
|
+
*
|
|
22
|
+
* It writes to the shared `backgroundStore`, so stepping here changes the
|
|
23
|
+
* background in every tool at once.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const BackgroundSwitcher = exports.BackgroundSwitcher = /*#__PURE__*/(0, _react.memo)(function BackgroundSwitcher() {
|
|
27
|
+
const {
|
|
28
|
+
id,
|
|
29
|
+
index,
|
|
30
|
+
count,
|
|
31
|
+
motion,
|
|
32
|
+
next,
|
|
33
|
+
previous,
|
|
34
|
+
setMotion
|
|
35
|
+
} = (0, _backgroundStore.useToolBackground)();
|
|
36
|
+
const preset = (0, _registry.getPreset)(id);
|
|
37
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
38
|
+
style: styles.root,
|
|
39
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
40
|
+
onPress: previous,
|
|
41
|
+
style: styles.step,
|
|
42
|
+
hitSlop: HIT_SLOP,
|
|
43
|
+
accessibilityLabel: "Previous background",
|
|
44
|
+
"sentry-label": "ignore background previous",
|
|
45
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.ChevronLeft, {
|
|
46
|
+
size: 16,
|
|
47
|
+
color: _macOSDesignSystemColors.macOSColors.text.secondary
|
|
48
|
+
})
|
|
49
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
50
|
+
style: styles.center,
|
|
51
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
52
|
+
style: styles.titleRow,
|
|
53
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.Palette, {
|
|
54
|
+
size: 11,
|
|
55
|
+
color: _macOSDesignSystemColors.macOSColors.semantic.info
|
|
56
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
57
|
+
style: styles.title,
|
|
58
|
+
numberOfLines: 1,
|
|
59
|
+
children: preset.label
|
|
60
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
61
|
+
style: styles.counter,
|
|
62
|
+
children: [index + 1, "/", count]
|
|
63
|
+
})]
|
|
64
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
65
|
+
style: styles.blurb,
|
|
66
|
+
numberOfLines: 1,
|
|
67
|
+
children: preset.blurb
|
|
68
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
69
|
+
style: styles.cost,
|
|
70
|
+
numberOfLines: 1,
|
|
71
|
+
children: [preset.nodes === 0 ? "0 animated nodes" : `${preset.nodes} native node${preset.nodes === 1 ? "" : "s"}`, " · ", "~", preset.views, " static views"]
|
|
72
|
+
})]
|
|
73
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
74
|
+
onPress: () => setMotion(!motion),
|
|
75
|
+
style: [styles.motion, motion ? styles.motionOn : null],
|
|
76
|
+
hitSlop: HIT_SLOP,
|
|
77
|
+
accessibilityLabel: motion ? "Turn motion off" : "Turn motion on",
|
|
78
|
+
"sentry-label": "ignore background motion",
|
|
79
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
80
|
+
style: [styles.motionText, motion ? styles.motionTextOn : null],
|
|
81
|
+
children: motion ? "MOTION" : "STATIC"
|
|
82
|
+
})
|
|
83
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
84
|
+
onPress: next,
|
|
85
|
+
style: styles.step,
|
|
86
|
+
hitSlop: HIT_SLOP,
|
|
87
|
+
accessibilityLabel: "Next background",
|
|
88
|
+
"sentry-label": "ignore background next",
|
|
89
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.ChevronRight, {
|
|
90
|
+
size: 16,
|
|
91
|
+
color: _macOSDesignSystemColors.macOSColors.text.secondary
|
|
92
|
+
})
|
|
93
|
+
})]
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
const HIT_SLOP = {
|
|
97
|
+
top: 8,
|
|
98
|
+
bottom: 8,
|
|
99
|
+
left: 8,
|
|
100
|
+
right: 8
|
|
101
|
+
};
|
|
102
|
+
const styles = _reactNative.StyleSheet.create({
|
|
103
|
+
root: {
|
|
104
|
+
flexDirection: "row",
|
|
105
|
+
alignItems: "center",
|
|
106
|
+
gap: 8,
|
|
107
|
+
paddingHorizontal: 8,
|
|
108
|
+
paddingVertical: 6,
|
|
109
|
+
marginHorizontal: 8,
|
|
110
|
+
marginTop: 6,
|
|
111
|
+
marginBottom: 2,
|
|
112
|
+
borderRadius: 10,
|
|
113
|
+
borderWidth: 1,
|
|
114
|
+
borderColor: _macOSDesignSystemColors.macOSColors.border.default,
|
|
115
|
+
// Deliberately translucent: the strip sits ON the background it is
|
|
116
|
+
// previewing, so you can see the field through it.
|
|
117
|
+
backgroundColor: "rgba(26,26,28,0.72)"
|
|
118
|
+
},
|
|
119
|
+
step: {
|
|
120
|
+
width: 28,
|
|
121
|
+
height: 28,
|
|
122
|
+
borderRadius: 8,
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
justifyContent: "center",
|
|
125
|
+
backgroundColor: "rgba(38,38,42,0.8)",
|
|
126
|
+
borderWidth: 1,
|
|
127
|
+
borderColor: _macOSDesignSystemColors.macOSColors.border.default
|
|
128
|
+
},
|
|
129
|
+
center: {
|
|
130
|
+
flex: 1,
|
|
131
|
+
minWidth: 0
|
|
132
|
+
},
|
|
133
|
+
titleRow: {
|
|
134
|
+
flexDirection: "row",
|
|
135
|
+
alignItems: "center",
|
|
136
|
+
gap: 5
|
|
137
|
+
},
|
|
138
|
+
title: {
|
|
139
|
+
color: _macOSDesignSystemColors.macOSColors.text.primary,
|
|
140
|
+
fontSize: 12,
|
|
141
|
+
fontWeight: "600"
|
|
142
|
+
},
|
|
143
|
+
counter: {
|
|
144
|
+
color: _macOSDesignSystemColors.macOSColors.text.muted,
|
|
145
|
+
fontSize: 10,
|
|
146
|
+
fontVariant: ["tabular-nums"]
|
|
147
|
+
},
|
|
148
|
+
blurb: {
|
|
149
|
+
color: _macOSDesignSystemColors.macOSColors.text.secondary,
|
|
150
|
+
fontSize: 10,
|
|
151
|
+
marginTop: 1
|
|
152
|
+
},
|
|
153
|
+
cost: {
|
|
154
|
+
color: _macOSDesignSystemColors.macOSColors.text.icon,
|
|
155
|
+
fontSize: 9,
|
|
156
|
+
marginTop: 1,
|
|
157
|
+
fontVariant: ["tabular-nums"]
|
|
158
|
+
},
|
|
159
|
+
motion: {
|
|
160
|
+
paddingHorizontal: 7,
|
|
161
|
+
paddingVertical: 4,
|
|
162
|
+
borderRadius: 6,
|
|
163
|
+
borderWidth: 1,
|
|
164
|
+
borderColor: _macOSDesignSystemColors.macOSColors.border.default,
|
|
165
|
+
backgroundColor: "rgba(38,38,42,0.8)"
|
|
166
|
+
},
|
|
167
|
+
motionOn: {
|
|
168
|
+
borderColor: _macOSDesignSystemColors.macOSColors.semantic.info + "55",
|
|
169
|
+
backgroundColor: _macOSDesignSystemColors.macOSColors.semantic.infoBackground
|
|
170
|
+
},
|
|
171
|
+
motionText: {
|
|
172
|
+
fontSize: 9,
|
|
173
|
+
fontWeight: "700",
|
|
174
|
+
letterSpacing: 0.6,
|
|
175
|
+
color: _macOSDesignSystemColors.macOSColors.text.muted
|
|
176
|
+
},
|
|
177
|
+
motionTextOn: {
|
|
178
|
+
color: _macOSDesignSystemColors.macOSColors.semantic.info
|
|
179
|
+
}
|
|
180
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Tool backgrounds
|
|
2
|
+
|
|
3
|
+
Animated and static backdrops for Buoy tool surfaces. Drop `<ToolBackground />`
|
|
4
|
+
in as the first child of a tool's body container; everything after it draws on
|
|
5
|
+
top.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<View style={styles.container}>
|
|
9
|
+
<ToolBackground />
|
|
10
|
+
{/* screens */}
|
|
11
|
+
</View>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The preset is a single shared, persisted choice (`backgroundStore`), so
|
|
15
|
+
switching it in one tool switches it everywhere. `<BackgroundSwitcher />` is the
|
|
16
|
+
‹ prev / next › review strip used to audition the catalogue.
|
|
17
|
+
|
|
18
|
+
## The two families
|
|
19
|
+
|
|
20
|
+
- **Starfield** (`variants/starfield.tsx`) — the cheap scenic preset: 99 stars
|
|
21
|
+
quantised into five depth bands.
|
|
22
|
+
- **Deep Field** (`variants/deepField.tsx`) — the Everlights
|
|
23
|
+
`ParallaxDriftBackground`, ported 1:1: same seed and generator call order,
|
|
24
|
+
same 160 stars, same far-plane bias, sizes, palette and drift. Speed is
|
|
25
|
+
quantised into 18 bands (a distinct speed costs a distinct animated node);
|
|
26
|
+
everything else stays per-star. Roughly twice Starfield's cost — the file
|
|
27
|
+
documents exactly what could and couldn't come across from Skia.
|
|
28
|
+
- **The lattice family** (`variants/lattice.tsx`) — Hex Mesh, Iso Grid,
|
|
29
|
+
Crosshatch, Perf Board, Halftone, Waffle, Instrument, Radar. Quiet technical
|
|
30
|
+
geometry: near-black base, one soft off-centre glow, a rasterized hairline
|
|
31
|
+
pattern at low alpha, a slow breath, a vignette. They read as instrument
|
|
32
|
+
panels and drafting paper rather than scenery, which is what keeps them
|
|
33
|
+
legible under dense tool UI. `LatticeShell` owns everything but the geometry,
|
|
34
|
+
so the family stays a family.
|
|
35
|
+
|
|
36
|
+
## The constraint
|
|
37
|
+
|
|
38
|
+
Buoy's packages peer-depend on nothing but `react` and `react-native`. No Skia,
|
|
39
|
+
no Reanimated, no SVG, no gradient library — a devtool cannot force a native
|
|
40
|
+
dependency into someone's app. So every preset is plain `View`s plus `Animated`,
|
|
41
|
+
and the gradients and glows in `primitives.tsx` are faked by stacking
|
|
42
|
+
translucent layers. For the same reason nothing here uses `transformOrigin`
|
|
43
|
+
(RN 0.74+); end-pivot rotation is built out of layout instead — see Radar.
|
|
44
|
+
|
|
45
|
+
## The performance contract
|
|
46
|
+
|
|
47
|
+
1. **Every animation is native-driven.** `useNativeDriver: true` on a looping
|
|
48
|
+
`Animated.timing` hands the whole loop to the UI thread once and never
|
|
49
|
+
touches JS again — no per-frame callback, no re-render, no bridge traffic. A
|
|
50
|
+
preset animates a handful of *layers*, never per-particle.
|
|
51
|
+
2. **Every static layer is rasterized.** `shouldRasterizeIOS` /
|
|
52
|
+
`renderToHardwareTextureAndroid` (see `RasterLayer`) flatten a layer of 40
|
|
53
|
+
dots into one cached GPU texture, so sliding it is a single composite.
|
|
54
|
+
3. **Nothing recomputes.** Scatter tables are generated once at module load from
|
|
55
|
+
a fixed seed (`rng.ts`); presets only read them. `ToolBackground` buckets its
|
|
56
|
+
measured size to 8px so a resize drag rebuilds a handful of times, not
|
|
57
|
+
hundreds.
|
|
58
|
+
|
|
59
|
+
Loops stop when the app backgrounds, and unmount with the tool.
|
|
60
|
+
|
|
61
|
+
**Known trap:** `Animated.sequence([timing, Animated.loop(...)])` silently fails
|
|
62
|
+
to drive the value on the native driver — no warning, the field renders and
|
|
63
|
+
never moves. It's the obvious way to phase-offset siblings; don't. Stagger via
|
|
64
|
+
different periods, or bake the offset into the `interpolate`.
|
|
65
|
+
|
|
66
|
+
### Measured
|
|
67
|
+
|
|
68
|
+
Network tool open on an iPhone 16 simulator, via perf-monitor and
|
|
69
|
+
`measure_renders`. Starfield is the heaviest preset (6 animated nodes, ~222
|
|
70
|
+
static views); the lattice presets are 1–2 nodes and 40–176 views.
|
|
71
|
+
|
|
72
|
+
**Steady state** — 12–13s idle recordings:
|
|
73
|
+
|
|
74
|
+
| | nodes | views | JS FPS | UI FPS | CPU avg | jank | render commits | render ms |
|
|
75
|
+
|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
76
|
+
| Off | 0 | 1 | 60 | 60 | 23.65% | 0 | 3 | 0 |
|
|
77
|
+
| Starfield | 6 | 222 | 60 | 60 | 23.79% | 0 | 4 | 0 |
|
|
78
|
+
| Deep Field | 22 | 462 | 60 | 60 | 27.19% | 0 | 4 | 0 |
|
|
79
|
+
|
|
80
|
+
Starfield's 0.14pt is sample noise. **Deep Field's +3.5pt is not** — 22 nodes
|
|
81
|
+
driving 462 transformed views per frame is real UI-thread work, even native. It
|
|
82
|
+
still holds 60/60 with zero dropped frames, but it is the one preset here with a
|
|
83
|
+
measurable running cost, and that is the trade for matching the original.
|
|
84
|
+
|
|
85
|
+
The commits in every column are the network tool's own once-a-minute
|
|
86
|
+
`TickProvider`, not the background. A separate `measure_renders` idle sample
|
|
87
|
+
reported one component render in every configuration — the animation contributes
|
|
88
|
+
zero React work while it runs, which is rule 1 holding regardless of node count.
|
|
89
|
+
|
|
90
|
+
**Mount** — the cost that is real for all of them:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Off → Starfield 348 renders / 3 commits · 13.3ms
|
|
94
|
+
StarBand 4.0ms ×5 mount ← 222 star views ≈ 4.5ms for the preset
|
|
95
|
+
|
|
96
|
+
Starfield → Deep Field 668 renders / 3 commits · 25.2ms
|
|
97
|
+
DeepBand 8.5ms ×18 mount ← 462 star views ≈ 9.6ms for the preset
|
|
98
|
+
TwinkleGroup 0.5ms ×4
|
|
99
|
+
RasterLayer 0.4ms ×18
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The balance in each is the switcher and its icons re-rendering. So: ~4.5ms and
|
|
103
|
+
~9.6ms of React work respectively, once, when the tool opens — scaling with view
|
|
104
|
+
count, as expected. `registry.tsx` lists each preset's count so the trade is
|
|
105
|
+
visible while comparing.
|
|
106
|
+
|
|
107
|
+
## Adding a preset
|
|
108
|
+
|
|
109
|
+
Add the component to `variants/`, then one entry in `registry.tsx` with an
|
|
110
|
+
honest `nodes` (animated nodes) and `views` (static views at ~380×620). The
|
|
111
|
+
switcher shows both, which is the point — it's a review tool, not decoration.
|
|
112
|
+
|
|
113
|
+
Alpha calibration: Hex Mesh is the reference. Anything fainter than its lattice
|
|
114
|
+
disappears under the vignette — hairline strokes in particular need ~0.2 alpha
|
|
115
|
+
or a second pixel of height to register at all.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ToolBackground = void 0;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _absoluteFill = require("../../utils/absoluteFill.js");
|
|
10
|
+
var _backgroundStore = require("./backgroundStore.js");
|
|
11
|
+
var _registry = require("./registry.js");
|
|
12
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
+
/**
|
|
14
|
+
* Drop-in animated backdrop for a Buoy tool surface.
|
|
15
|
+
*
|
|
16
|
+
* Mount it as the FIRST child of a tool's body container and everything after
|
|
17
|
+
* it draws on top:
|
|
18
|
+
*
|
|
19
|
+
* <View style={styles.container}>
|
|
20
|
+
* <ToolBackground />
|
|
21
|
+
* ...screens...
|
|
22
|
+
* </View>
|
|
23
|
+
*
|
|
24
|
+
* It is `position: absolute` + `pointerEvents: "none"`, so it never affects
|
|
25
|
+
* layout and never eats a touch. The preset comes from the shared persisted
|
|
26
|
+
* selection (`backgroundStore`), so switching it in one tool switches it in all
|
|
27
|
+
* of them.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Round the measured size into 8px buckets. Presets lay out from width/height,
|
|
32
|
+
* so an unbucketed measurement would rebuild the whole field on every frame of
|
|
33
|
+
* a modal resize drag; at this granularity nobody can see the difference and a
|
|
34
|
+
* drag costs a handful of rebuilds instead of hundreds.
|
|
35
|
+
*/
|
|
36
|
+
const bucket = n => Math.max(0, Math.ceil(n / 8) * 8);
|
|
37
|
+
const ToolBackground = exports.ToolBackground = /*#__PURE__*/(0, _react.memo)(function ToolBackground({
|
|
38
|
+
id,
|
|
39
|
+
motion,
|
|
40
|
+
opacity = 1
|
|
41
|
+
}) {
|
|
42
|
+
const shared = (0, _backgroundStore.useToolBackground)();
|
|
43
|
+
const [size, setSize] = (0, _react.useState)({
|
|
44
|
+
width: 0,
|
|
45
|
+
height: 0
|
|
46
|
+
});
|
|
47
|
+
const onLayout = (0, _react.useCallback)(event => {
|
|
48
|
+
const {
|
|
49
|
+
width,
|
|
50
|
+
height
|
|
51
|
+
} = event.nativeEvent.layout;
|
|
52
|
+
const next = {
|
|
53
|
+
width: bucket(width),
|
|
54
|
+
height: bucket(height)
|
|
55
|
+
};
|
|
56
|
+
setSize(prev => prev.width === next.width && prev.height === next.height ? prev : next);
|
|
57
|
+
}, []);
|
|
58
|
+
const preset = (0, _registry.getPreset)(id ?? shared.id);
|
|
59
|
+
const animated = motion ?? shared.motion;
|
|
60
|
+
const Variant = preset.Component;
|
|
61
|
+
const ready = size.width > 0 && size.height > 0;
|
|
62
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
63
|
+
style: [_absoluteFill.absoluteFill, opacity === 1 ? null : {
|
|
64
|
+
opacity
|
|
65
|
+
}],
|
|
66
|
+
pointerEvents: "none",
|
|
67
|
+
onLayout: onLayout,
|
|
68
|
+
children: Variant && ready ? /*#__PURE__*/(0, _jsxRuntime.jsx)(Variant, {
|
|
69
|
+
width: size.width,
|
|
70
|
+
height: size.height,
|
|
71
|
+
animated: animated
|
|
72
|
+
}) : null
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.backgroundStore = void 0;
|
|
7
|
+
exports.useToolBackground = useToolBackground;
|
|
8
|
+
var _react = require("react");
|
|
9
|
+
var _devToolsStorageKeys = require("../../storage/devToolsStorageKeys.js");
|
|
10
|
+
var _persistentStorage = require("../../utils/persistentStorage.js");
|
|
11
|
+
var _registry = require("./registry.js");
|
|
12
|
+
/**
|
|
13
|
+
* The persisted background selection, shared by every tool.
|
|
14
|
+
*
|
|
15
|
+
* A singleton rather than context so a tool anywhere in the tree — modal,
|
|
16
|
+
* inline desktop panel, bottom sheet — reads the same choice without a provider
|
|
17
|
+
* having to wrap it. Same shape as `ignoredPatternsStore`: subscribe/notify,
|
|
18
|
+
* best-effort persistence, a dirty flag so a slow initial read can't clobber a
|
|
19
|
+
* choice the user already made.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const KEY = `${_devToolsStorageKeys.devToolsStorageKeys.base}_tool_background`;
|
|
23
|
+
function isValidId(value) {
|
|
24
|
+
return typeof value === "string" && _registry.BACKGROUND_PRESETS.some(p => p.id === value);
|
|
25
|
+
}
|
|
26
|
+
class BackgroundStore {
|
|
27
|
+
id = _registry.DEFAULT_BACKGROUND;
|
|
28
|
+
motion = true;
|
|
29
|
+
listeners = new Set();
|
|
30
|
+
loadPromise = null;
|
|
31
|
+
dirty = false;
|
|
32
|
+
getId() {
|
|
33
|
+
return this.id;
|
|
34
|
+
}
|
|
35
|
+
getMotion() {
|
|
36
|
+
return this.motion;
|
|
37
|
+
}
|
|
38
|
+
subscribe(listener) {
|
|
39
|
+
this.listeners.add(listener);
|
|
40
|
+
void this.load();
|
|
41
|
+
return () => {
|
|
42
|
+
this.listeners.delete(listener);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
notify() {
|
|
46
|
+
for (const listener of this.listeners) listener();
|
|
47
|
+
}
|
|
48
|
+
load() {
|
|
49
|
+
if (this.loadPromise) return this.loadPromise;
|
|
50
|
+
this.loadPromise = (async () => {
|
|
51
|
+
try {
|
|
52
|
+
const raw = await _persistentStorage.persistentStorage.getItem(KEY);
|
|
53
|
+
if (!raw || this.dirty) return;
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
if (isValidId(parsed.id)) this.id = parsed.id;
|
|
56
|
+
if (typeof parsed.motion === "boolean") this.motion = parsed.motion;
|
|
57
|
+
this.notify();
|
|
58
|
+
} catch {
|
|
59
|
+
// A missing or corrupt entry just means "use the default".
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
return this.loadPromise;
|
|
63
|
+
}
|
|
64
|
+
persist() {
|
|
65
|
+
this.dirty = true;
|
|
66
|
+
const payload = {
|
|
67
|
+
id: this.id,
|
|
68
|
+
motion: this.motion
|
|
69
|
+
};
|
|
70
|
+
void _persistentStorage.persistentStorage.setItem(KEY, JSON.stringify(payload)).catch(() => {});
|
|
71
|
+
}
|
|
72
|
+
setId(id) {
|
|
73
|
+
if (this.id === id) return;
|
|
74
|
+
this.id = id;
|
|
75
|
+
this.persist();
|
|
76
|
+
this.notify();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Step through the catalogue; `delta` is +1 (next) or -1 (previous). */
|
|
80
|
+
step(delta) {
|
|
81
|
+
this.setId((0, _registry.presetAt)((0, _registry.presetIndex)(this.id) + delta).id);
|
|
82
|
+
}
|
|
83
|
+
setMotion(motion) {
|
|
84
|
+
if (this.motion === motion) return;
|
|
85
|
+
this.motion = motion;
|
|
86
|
+
this.persist();
|
|
87
|
+
this.notify();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const backgroundStore = exports.backgroundStore = new BackgroundStore();
|
|
91
|
+
/** Subscribe a component to the current background selection. */
|
|
92
|
+
function useToolBackground() {
|
|
93
|
+
const [id, setLocalId] = (0, _react.useState)(() => backgroundStore.getId());
|
|
94
|
+
const [motion, setLocalMotion] = (0, _react.useState)(() => backgroundStore.getMotion());
|
|
95
|
+
(0, _react.useEffect)(() => backgroundStore.subscribe(() => {
|
|
96
|
+
setLocalId(backgroundStore.getId());
|
|
97
|
+
setLocalMotion(backgroundStore.getMotion());
|
|
98
|
+
}), []);
|
|
99
|
+
return {
|
|
100
|
+
id,
|
|
101
|
+
motion,
|
|
102
|
+
index: (0, _registry.presetIndex)(id),
|
|
103
|
+
count: _registry.BACKGROUND_PRESETS.length,
|
|
104
|
+
setId: setId,
|
|
105
|
+
next: next,
|
|
106
|
+
previous: previous,
|
|
107
|
+
setMotion: setMotion
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Module-scope identities so the returned object's callbacks never change and
|
|
112
|
+
// consumers can hand them straight to a memoized button.
|
|
113
|
+
const setId = id => backgroundStore.setId(id);
|
|
114
|
+
const next = () => backgroundStore.step(1);
|
|
115
|
+
const previous = () => backgroundStore.step(-1);
|
|
116
|
+
const setMotion = motion => backgroundStore.setMotion(motion);
|