@buoy-gg/core 2.1.14 → 2.2.0
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/floatingMenu/DevToolsSettingsModal.js +3 -33
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +3 -25
- package/lib/commonjs/floatingMenu/FloatingDevTools.js +14 -1
- package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +19 -9
- package/lib/commonjs/floatingMenu/FloatingMenu.js +6 -2
- package/lib/commonjs/floatingMenu/dial/DialDevTools.js +166 -196
- package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +82 -7
- package/lib/commonjs/floatingMenu/dial/DialIcon.js +66 -59
- package/lib/commonjs/floatingMenu/dial/DialPagination.js +170 -0
- package/lib/commonjs/floatingMenu/dial/dialUsageStore.js +97 -0
- package/lib/module/floatingMenu/DevToolsSettingsModal.js +3 -33
- package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +4 -28
- package/lib/module/floatingMenu/FloatingDevTools.js +14 -1
- package/lib/module/floatingMenu/FloatingDevTools.web.js +19 -9
- package/lib/module/floatingMenu/FloatingMenu.js +6 -2
- package/lib/module/floatingMenu/dial/DialDevTools.js +166 -196
- package/lib/module/floatingMenu/dial/DialDevTools.web.js +82 -7
- package/lib/module/floatingMenu/dial/DialIcon.js +67 -60
- package/lib/module/floatingMenu/dial/DialPagination.js +165 -0
- package/lib/module/floatingMenu/dial/dialUsageStore.js +89 -0
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +0 -2
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts +7 -2
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts +22 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts +34 -0
- package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +0 -2
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts +7 -2
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts +22 -0
- package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts +34 -0
- package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts.map +1 -0
- package/package.json +5 -5
|
@@ -7,6 +7,8 @@ exports.DialDevTools = void 0;
|
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _DialIcon = require("./DialIcon.js");
|
|
10
|
+
var _DialPagination = require("./DialPagination.js");
|
|
11
|
+
var _dialUsageStore = require("./dialUsageStore.js");
|
|
10
12
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
11
13
|
var _DevToolsSettingsModal = require("../DevToolsSettingsModal");
|
|
12
14
|
var _license = require("@buoy-gg/license");
|
|
@@ -17,7 +19,8 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
17
19
|
// Icons are provided by installedApps; no direct icon imports here.
|
|
18
20
|
|
|
19
21
|
const {
|
|
20
|
-
width: SCREEN_WIDTH
|
|
22
|
+
width: SCREEN_WIDTH,
|
|
23
|
+
height: SCREEN_HEIGHT
|
|
21
24
|
} = _reactNative.Dimensions.get("window");
|
|
22
25
|
|
|
23
26
|
// Use shared layout calculation from core
|
|
@@ -27,10 +30,17 @@ const layout = (0, _floatingToolsCore.getDialLayout)({
|
|
|
27
30
|
const CIRCLE_SIZE = layout.circleSize;
|
|
28
31
|
const BUTTON_SIZE = layout.buttonSize;
|
|
29
32
|
const ONBOARDING_STORAGE_KEY = "@react_buoy_settings_tooltip_shown";
|
|
33
|
+
/** A non-interactive placeholder used to fill out the last dial page. */
|
|
34
|
+
const createEmptySlot = slotIndex => ({
|
|
35
|
+
id: `empty-${slotIndex}`,
|
|
36
|
+
name: `empty-${slotIndex}`,
|
|
37
|
+
icon: null,
|
|
38
|
+
color: "transparent",
|
|
39
|
+
onPress: () => {}
|
|
40
|
+
});
|
|
30
41
|
const DialDevTools = ({
|
|
31
42
|
onClose,
|
|
32
43
|
onSettingsPress,
|
|
33
|
-
settings: externalSettings,
|
|
34
44
|
autoOpenSettings = false,
|
|
35
45
|
apps,
|
|
36
46
|
state,
|
|
@@ -43,18 +53,12 @@ const DialDevTools = ({
|
|
|
43
53
|
const onboardingDismissedRef = (0, _react.useRef)(false); // Track if onboarding was dismissed
|
|
44
54
|
const hintsDisabled = (0, _sharedUi.useHintsDisabled)();
|
|
45
55
|
const {
|
|
46
|
-
settings: hookSettings,
|
|
47
56
|
refreshSettings
|
|
48
57
|
} = (0, _DevToolsSettingsModal.useDevToolsSettings)();
|
|
49
58
|
const {
|
|
50
59
|
open
|
|
51
60
|
} = (0, _AppHost.useAppHost)();
|
|
52
61
|
const isPro = (0, _license.useIsPro)();
|
|
53
|
-
// Initialize with external settings if provided, otherwise use hook settings
|
|
54
|
-
const [localSettings, setLocalSettings] = (0, _react.useState)(externalSettings || hookSettings);
|
|
55
|
-
|
|
56
|
-
// Always use localSettings (which can be updated by the modal)
|
|
57
|
-
const settings = localSettings;
|
|
58
62
|
|
|
59
63
|
// Load persisted settings modal state on mount
|
|
60
64
|
(0, _react.useEffect)(() => {
|
|
@@ -83,20 +87,6 @@ const DialDevTools = ({
|
|
|
83
87
|
});
|
|
84
88
|
}, [isSettingsModalOpen, settingsModalStateLoaded]);
|
|
85
89
|
|
|
86
|
-
// Update local settings when external settings change
|
|
87
|
-
(0, _react.useEffect)(() => {
|
|
88
|
-
if (externalSettings) {
|
|
89
|
-
setLocalSettings(externalSettings);
|
|
90
|
-
}
|
|
91
|
-
}, [externalSettings]);
|
|
92
|
-
|
|
93
|
-
// Update local settings when hook settings change (if no external settings)
|
|
94
|
-
(0, _react.useEffect)(() => {
|
|
95
|
-
if (!externalSettings) {
|
|
96
|
-
setLocalSettings(hookSettings);
|
|
97
|
-
}
|
|
98
|
-
}, [hookSettings, externalSettings]);
|
|
99
|
-
|
|
100
90
|
// Auto-open settings modal when prop is true
|
|
101
91
|
(0, _react.useEffect)(() => {
|
|
102
92
|
if (autoOpenSettings && !isSettingsModalOpen) {
|
|
@@ -133,7 +123,6 @@ const DialDevTools = ({
|
|
|
133
123
|
const dialRotation = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
|
|
134
124
|
const centerButtonScale = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
|
|
135
125
|
const iconsProgress = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
|
|
136
|
-
const glitchOffset = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
|
|
137
126
|
const pulseScale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
138
127
|
const availableApps = (0, _react.useMemo)(() => apps.map(({
|
|
139
128
|
id,
|
|
@@ -147,88 +136,111 @@ const DialDevTools = ({
|
|
|
147
136
|
description
|
|
148
137
|
})), [apps]);
|
|
149
138
|
|
|
150
|
-
// Subtle animations
|
|
151
|
-
const floatingAnim = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
|
|
152
|
-
const breathingScale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
153
|
-
const circuitOpacity = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
|
|
154
|
-
|
|
155
139
|
// Animation tracking refs
|
|
156
|
-
const glitchIntervalRef = (0, _react.useRef)(null);
|
|
157
140
|
const pulseAnimationRef = (0, _react.useRef)(null);
|
|
158
141
|
|
|
159
|
-
//
|
|
160
|
-
|
|
142
|
+
// Dial-eligible apps: everything except row-only tools. All of them are
|
|
143
|
+
// shown — paginated across pages of MAX_DIAL_SLOTS — so there is no longer
|
|
144
|
+
// a per-tool show/hide setting.
|
|
145
|
+
const dialApps = (0, _react.useMemo)(() => apps.filter(a => (a.slot ?? "both") !== "row"), [apps]);
|
|
161
146
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
147
|
+
// Build a stable IconType for every dial-eligible app, keyed by id.
|
|
148
|
+
const iconsById = (0, _react.useMemo)(() => {
|
|
149
|
+
const map = new Map();
|
|
150
|
+
for (const app of dialApps) {
|
|
151
|
+
map.set(app.id, {
|
|
152
|
+
id: app.id,
|
|
153
|
+
name: app.name,
|
|
154
|
+
// Pass both the pre-rendered icon (for non-function icons) and the
|
|
155
|
+
// component (for dynamic rendering).
|
|
156
|
+
icon: typeof app.icon === "function" ? null : app.icon,
|
|
157
|
+
// Cast to the expected type - the signature is compatible at runtime.
|
|
158
|
+
iconComponent: typeof app.icon === "function" ? app.icon : undefined,
|
|
159
|
+
color: app.color ?? _sharedUi.buoyColors.primary,
|
|
160
|
+
onPress: () => {
|
|
161
|
+
// Record usage so frequently/recently used tools rank toward page 1.
|
|
162
|
+
void (0, _dialUsageStore.recordToolUsage)(app.id);
|
|
163
|
+
|
|
164
|
+
// Call the app's onPress callback if provided, passing actions for
|
|
165
|
+
// toggle tools.
|
|
166
|
+
app?.onPress?.(actions);
|
|
167
|
+
|
|
168
|
+
// Only open modal if not a toggle-only tool.
|
|
169
|
+
if (app.launchMode !== "toggle-only") {
|
|
170
|
+
const resolvedIcon = typeof app.icon === "function" ? app.icon({
|
|
171
|
+
slot: "dial",
|
|
172
|
+
size: 20
|
|
173
|
+
}) : app.icon;
|
|
174
|
+
open({
|
|
175
|
+
id: app.id,
|
|
176
|
+
title: app.name,
|
|
177
|
+
component: app.component,
|
|
178
|
+
props: app.props,
|
|
179
|
+
launchMode: app.launchMode ?? "self-modal",
|
|
180
|
+
singleton: app.singleton ?? true,
|
|
181
|
+
icon: resolvedIcon,
|
|
182
|
+
color: app.color
|
|
183
|
+
});
|
|
184
|
+
}
|
|
198
185
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const resolvedIcon = typeof app.icon === "function" ? app.icon({
|
|
202
|
-
slot: "dial",
|
|
203
|
-
size: 20
|
|
204
|
-
}) : app.icon;
|
|
205
|
-
open({
|
|
206
|
-
id: app.id,
|
|
207
|
-
title: app.name,
|
|
208
|
-
component: app.component,
|
|
209
|
-
props: app.props,
|
|
210
|
-
launchMode: app.launchMode ?? "self-modal",
|
|
211
|
-
singleton: app.singleton ?? true,
|
|
212
|
-
icon: resolvedIcon,
|
|
213
|
-
color: app.color
|
|
214
|
-
});
|
|
186
|
+
// Close the dial.
|
|
187
|
+
onClose?.();
|
|
215
188
|
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return map;
|
|
192
|
+
}, [dialApps, actions, open, onClose]);
|
|
216
193
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// More tools enabled than can be shown - they will be hidden
|
|
194
|
+
// Snapshot the usage-ranked order when the dial opens. It stays stable while
|
|
195
|
+
// open so icons don't jump positions mid-interaction.
|
|
196
|
+
const [rankedIds, setRankedIds] = (0, _react.useState)(() => (0, _dialUsageStore.getRankedToolIds)(dialApps.map(a => a.id)));
|
|
197
|
+
(0, _react.useEffect)(() => {
|
|
198
|
+
const ids = dialApps.map(a => a.id);
|
|
199
|
+
if ((0, _dialUsageStore.isDialUsageLoaded)()) {
|
|
200
|
+
setRankedIds((0, _dialUsageStore.getRankedToolIds)(ids));
|
|
201
|
+
return;
|
|
226
202
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
203
|
+
// Usage data not loaded yet — show default order, then re-rank once ready.
|
|
204
|
+
let cancelled = false;
|
|
205
|
+
(0, _dialUsageStore.loadDialUsage)().then(() => {
|
|
206
|
+
if (!cancelled) setRankedIds((0, _dialUsageStore.getRankedToolIds)(ids));
|
|
207
|
+
});
|
|
208
|
+
return () => {
|
|
209
|
+
cancelled = true;
|
|
210
|
+
};
|
|
211
|
+
}, [dialApps]);
|
|
212
|
+
const pageCount = Math.max(1, Math.ceil(rankedIds.length / _floatingToolsCore.MAX_DIAL_SLOTS));
|
|
213
|
+
const [currentPage, setCurrentPage] = (0, _react.useState)(0);
|
|
214
|
+
const safePage = Math.min(currentPage, pageCount - 1);
|
|
215
|
+
|
|
216
|
+
// Every dial-eligible icon, with the page/slot it occupies. The ranking is
|
|
217
|
+
// snapshotted on open, so each icon's page and slot are fixed for the
|
|
218
|
+
// session — which lets us mount all icons once and paginate purely by
|
|
219
|
+
// toggling visibility (no remounts on page change).
|
|
220
|
+
const allDialIcons = (0, _react.useMemo)(() => {
|
|
221
|
+
return rankedIds.map(id => iconsById.get(id)).filter(icon => Boolean(icon)).map((icon, i) => ({
|
|
222
|
+
icon,
|
|
223
|
+
page: Math.floor(i / _floatingToolsCore.MAX_DIAL_SLOTS),
|
|
224
|
+
slot: i % _floatingToolsCore.MAX_DIAL_SLOTS
|
|
225
|
+
}));
|
|
226
|
+
}, [rankedIds, iconsById]);
|
|
227
|
+
|
|
228
|
+
// Empty slot indices for the current page (only the last page can be
|
|
229
|
+
// partial). These are cheap placeholder dots.
|
|
230
|
+
const emptySlots = (0, _react.useMemo)(() => {
|
|
231
|
+
const onThisPage = allDialIcons.filter(d => d.page === safePage).length;
|
|
232
|
+
const slots = [];
|
|
233
|
+
for (let s = onThisPage; s < _floatingToolsCore.MAX_DIAL_SLOTS; s += 1) slots.push(s);
|
|
234
|
+
return slots;
|
|
235
|
+
}, [allDialIcons, safePage]);
|
|
236
|
+
|
|
237
|
+
// Swap to another page. Every icon is already mounted, so this only toggles
|
|
238
|
+
// which ones are visible — no remount, no re-animation — keeping page
|
|
239
|
+
// changes instant.
|
|
240
|
+
const handlePageChange = next => {
|
|
241
|
+
const clamped = Math.max(0, Math.min(next, pageCount - 1));
|
|
242
|
+
if (clamped !== safePage) setCurrentPage(clamped);
|
|
243
|
+
};
|
|
232
244
|
|
|
233
245
|
// Initialize animations on mount - using shared config from core
|
|
234
246
|
(0, _react.useEffect)(() => {
|
|
@@ -249,13 +261,13 @@ const DialDevTools = ({
|
|
|
249
261
|
}
|
|
250
262
|
},
|
|
251
263
|
centerButton: {
|
|
252
|
-
delay:
|
|
264
|
+
delay: 150,
|
|
253
265
|
damping: 10,
|
|
254
266
|
stiffness: 200
|
|
255
267
|
},
|
|
256
268
|
icons: {
|
|
257
|
-
delay:
|
|
258
|
-
duration:
|
|
269
|
+
delay: 200,
|
|
270
|
+
duration: 400
|
|
259
271
|
},
|
|
260
272
|
circuitTraces: {
|
|
261
273
|
delay: 600,
|
|
@@ -326,81 +338,21 @@ const DialDevTools = ({
|
|
|
326
338
|
useNativeDriver: true
|
|
327
339
|
})]).start();
|
|
328
340
|
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
duration: continuous.glitch.stepDuration,
|
|
334
|
-
useNativeDriver: true
|
|
335
|
-
}), _reactNative.Animated.timing(glitchOffset, {
|
|
336
|
-
toValue: -continuous.glitch.offset,
|
|
337
|
-
duration: continuous.glitch.stepDuration,
|
|
338
|
-
useNativeDriver: true
|
|
339
|
-
}), _reactNative.Animated.timing(glitchOffset, {
|
|
340
|
-
toValue: 0,
|
|
341
|
-
duration: continuous.glitch.stepDuration,
|
|
342
|
-
useNativeDriver: true
|
|
343
|
-
})]).start();
|
|
344
|
-
};
|
|
345
|
-
glitchIntervalRef.current = setInterval(glitchAnimation, continuous.glitch.interval);
|
|
346
|
-
|
|
347
|
-
// Pulse animation - using shared config
|
|
348
|
-
const startPulse = () => {
|
|
349
|
-
pulseAnimationRef.current = _reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(pulseScale, {
|
|
350
|
-
toValue: continuous.pulse.maxScale,
|
|
351
|
-
duration: continuous.pulse.duration,
|
|
352
|
-
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
353
|
-
useNativeDriver: true
|
|
354
|
-
}), _reactNative.Animated.timing(pulseScale, {
|
|
355
|
-
toValue: continuous.pulse.minScale,
|
|
356
|
-
duration: continuous.pulse.duration,
|
|
357
|
-
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
358
|
-
useNativeDriver: true
|
|
359
|
-
})]));
|
|
360
|
-
pulseAnimationRef.current.start();
|
|
361
|
-
};
|
|
362
|
-
startPulse();
|
|
363
|
-
|
|
364
|
-
// Subtle floating animation for the dial - using shared config
|
|
365
|
-
_reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(floatingAnim, {
|
|
366
|
-
toValue: continuous.floating.maxY,
|
|
367
|
-
duration: continuous.floating.duration,
|
|
341
|
+
// Pulse animation - only continuous effect kept for performance
|
|
342
|
+
pulseAnimationRef.current = _reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(pulseScale, {
|
|
343
|
+
toValue: continuous.pulse.maxScale,
|
|
344
|
+
duration: continuous.pulse.duration,
|
|
368
345
|
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
369
346
|
useNativeDriver: true
|
|
370
|
-
}), _reactNative.Animated.timing(
|
|
371
|
-
toValue: continuous.
|
|
372
|
-
duration: continuous.
|
|
347
|
+
}), _reactNative.Animated.timing(pulseScale, {
|
|
348
|
+
toValue: continuous.pulse.minScale,
|
|
349
|
+
duration: continuous.pulse.duration,
|
|
373
350
|
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
374
351
|
useNativeDriver: true
|
|
375
|
-
})]))
|
|
376
|
-
|
|
377
|
-
// Gentle breathing effect for center button - using shared config
|
|
378
|
-
_reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(breathingScale, {
|
|
379
|
-
toValue: continuous.breathing.maxScale,
|
|
380
|
-
duration: continuous.breathing.duration,
|
|
381
|
-
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
382
|
-
useNativeDriver: true
|
|
383
|
-
}), _reactNative.Animated.timing(breathingScale, {
|
|
384
|
-
toValue: continuous.breathing.minScale,
|
|
385
|
-
duration: continuous.breathing.duration,
|
|
386
|
-
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
387
|
-
useNativeDriver: true
|
|
388
|
-
})])).start();
|
|
389
|
-
|
|
390
|
-
// Circuit traces fade in - using shared config
|
|
391
|
-
_reactNative.Animated.timing(circuitOpacity, {
|
|
392
|
-
toValue: 1,
|
|
393
|
-
duration: entrance.circuitTraces.duration,
|
|
394
|
-
delay: entrance.circuitTraces.delay,
|
|
395
|
-
useNativeDriver: true
|
|
396
|
-
}).start();
|
|
352
|
+
})]));
|
|
353
|
+
pulseAnimationRef.current.start();
|
|
397
354
|
return () => {
|
|
398
|
-
|
|
399
|
-
clearInterval(glitchIntervalRef.current);
|
|
400
|
-
}
|
|
401
|
-
if (pulseAnimationRef.current) {
|
|
402
|
-
pulseAnimationRef.current.stop();
|
|
403
|
-
}
|
|
355
|
+
pulseAnimationRef.current?.stop();
|
|
404
356
|
};
|
|
405
357
|
}, []);
|
|
406
358
|
const handleOnboardingDismiss = () => {
|
|
@@ -472,8 +424,8 @@ const DialDevTools = ({
|
|
|
472
424
|
}
|
|
473
425
|
});
|
|
474
426
|
};
|
|
475
|
-
const handleIconPress =
|
|
476
|
-
setSelectedIcon(
|
|
427
|
+
const handleIconPress = icon => {
|
|
428
|
+
setSelectedIcon(1);
|
|
477
429
|
const interaction = _floatingToolsCore.dialAnimationConfig?.interaction ?? {
|
|
478
430
|
iconSelect: {
|
|
479
431
|
pulse: [{
|
|
@@ -505,9 +457,9 @@ const DialDevTools = ({
|
|
|
505
457
|
|
|
506
458
|
// Trigger action - using shared delay
|
|
507
459
|
setTimeout(() => {
|
|
508
|
-
|
|
460
|
+
icon.onPress();
|
|
509
461
|
// Only close if it's not the WiFi toggle (by id)
|
|
510
|
-
if (
|
|
462
|
+
if (icon.id !== "wifi") {
|
|
511
463
|
handleClose();
|
|
512
464
|
}
|
|
513
465
|
}, interaction.iconSelect.actionDelay);
|
|
@@ -517,14 +469,9 @@ const DialDevTools = ({
|
|
|
517
469
|
const backdropAnimatedStyle = {
|
|
518
470
|
opacity: backdropOpacity
|
|
519
471
|
};
|
|
520
|
-
const glitchAnimatedStyle = {
|
|
521
|
-
transform: [{
|
|
522
|
-
translateX: glitchOffset
|
|
523
|
-
}]
|
|
524
|
-
};
|
|
525
472
|
const centerButtonAnimatedStyle = {
|
|
526
473
|
transform: [{
|
|
527
|
-
scale:
|
|
474
|
+
scale: centerButtonScale
|
|
528
475
|
}]
|
|
529
476
|
};
|
|
530
477
|
const pulseAnimatedStyle = {
|
|
@@ -547,8 +494,6 @@ const DialDevTools = ({
|
|
|
547
494
|
left: (SCREEN_WIDTH - CIRCLE_SIZE) / 2,
|
|
548
495
|
bottom: 80,
|
|
549
496
|
transform: [{
|
|
550
|
-
translateY: floatingAnim
|
|
551
|
-
}, {
|
|
552
497
|
scale: dialScale
|
|
553
498
|
}, {
|
|
554
499
|
rotate: dialRotation.interpolate({
|
|
@@ -558,7 +503,7 @@ const DialDevTools = ({
|
|
|
558
503
|
}]
|
|
559
504
|
}],
|
|
560
505
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
|
|
561
|
-
style:
|
|
506
|
+
style: styles.circle,
|
|
562
507
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
563
508
|
style: styles.gradientBackground,
|
|
564
509
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
@@ -579,14 +524,26 @@ const DialDevTools = ({
|
|
|
579
524
|
}]
|
|
580
525
|
}, i))
|
|
581
526
|
})]
|
|
582
|
-
}),
|
|
583
|
-
|
|
527
|
+
}), allDialIcons.filter(({
|
|
528
|
+
page
|
|
529
|
+
}) => page === safePage).map(({
|
|
530
|
+
icon,
|
|
531
|
+
slot
|
|
532
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_DialIcon.DialIcon, {
|
|
584
533
|
onPress: handleIconPress,
|
|
585
534
|
iconsProgress: iconsProgress,
|
|
586
535
|
icon: icon,
|
|
587
|
-
index:
|
|
588
|
-
totalIcons:
|
|
589
|
-
|
|
536
|
+
index: slot,
|
|
537
|
+
totalIcons: _floatingToolsCore.MAX_DIAL_SLOTS,
|
|
538
|
+
active: true
|
|
539
|
+
}, icon.id ?? `page${safePage}-${slot}`)), emptySlots.map(slot => /*#__PURE__*/(0, _jsxRuntime.jsx)(_DialIcon.DialIcon, {
|
|
540
|
+
onPress: handleIconPress,
|
|
541
|
+
iconsProgress: iconsProgress,
|
|
542
|
+
icon: createEmptySlot(slot),
|
|
543
|
+
index: slot,
|
|
544
|
+
totalIcons: _floatingToolsCore.MAX_DIAL_SLOTS,
|
|
545
|
+
active: true
|
|
546
|
+
}, `empty-${slot}`))]
|
|
590
547
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
|
|
591
548
|
style: [styles.buttonContainer, centerButtonAnimatedStyle],
|
|
592
549
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
@@ -644,19 +601,32 @@ const DialDevTools = ({
|
|
|
644
601
|
})]
|
|
645
602
|
})
|
|
646
603
|
})]
|
|
647
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
648
|
-
|
|
604
|
+
}), pageCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_DialPagination.DialPagination, {
|
|
605
|
+
page: safePage,
|
|
606
|
+
pageCount: pageCount,
|
|
607
|
+
onPrev: () => handlePageChange(safePage - 1),
|
|
608
|
+
onNext: () => handlePageChange(safePage + 1),
|
|
609
|
+
animatedStyle: {
|
|
610
|
+
position: "absolute",
|
|
611
|
+
left: (SCREEN_WIDTH - CIRCLE_SIZE) / 2,
|
|
612
|
+
// Circle's bottom edge sits at bottom: 80 -> SCREEN_HEIGHT - 80
|
|
613
|
+
// from the top. Place the pager 16px below that edge.
|
|
614
|
+
top: SCREEN_HEIGHT - 80 + 16,
|
|
615
|
+
width: CIRCLE_SIZE,
|
|
616
|
+
opacity: dialScale,
|
|
617
|
+
transform: [{
|
|
618
|
+
scale: dialScale
|
|
619
|
+
}]
|
|
620
|
+
}
|
|
621
|
+
}), isSettingsModalOpen && /*#__PURE__*/(0, _jsxRuntime.jsx)(_DevToolsSettingsModal.DevToolsSettingsModal, {
|
|
622
|
+
visible: true,
|
|
649
623
|
onClose: () => {
|
|
650
624
|
setIsSettingsModalOpen(false);
|
|
651
625
|
refreshSettings(); // Refresh from storage
|
|
652
626
|
},
|
|
653
|
-
onSettingsChange: newSettings => {
|
|
654
|
-
// Immediately update local settings for instant feedback
|
|
655
|
-
setLocalSettings(newSettings);
|
|
656
|
-
},
|
|
657
627
|
availableApps: availableApps
|
|
658
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_OnboardingTooltip.OnboardingTooltip, {
|
|
659
|
-
visible:
|
|
628
|
+
}), showOnboardingTooltip && !isSettingsModalOpen && !onboardingDismissedRef.current && /*#__PURE__*/(0, _jsxRuntime.jsx)(_OnboardingTooltip.OnboardingTooltip, {
|
|
629
|
+
visible: true,
|
|
660
630
|
onDismiss: handleOnboardingDismiss
|
|
661
631
|
})]
|
|
662
632
|
});
|
|
@@ -214,8 +214,14 @@ function DialMenu({
|
|
|
214
214
|
}), []);
|
|
215
215
|
const gridRotations = (0, _react.useMemo)(() => (0, _floatingToolsReact.getGridLineRotations)(), []);
|
|
216
216
|
const positions = (0, _react.useMemo)(() => (0, _floatingToolsReact.getAllIconPositions)(_floatingToolsReact.MAX_DIAL_SLOTS, layout.iconRadius), [layout.iconRadius]);
|
|
217
|
+
|
|
218
|
+
// Pagination: tools are split across pages of MAX_DIAL_SLOTS.
|
|
219
|
+
const [currentPage, setCurrentPage] = (0, _react.useState)(0);
|
|
220
|
+
const pageCount = Math.max(1, Math.ceil(icons.length / _floatingToolsReact.MAX_DIAL_SLOTS));
|
|
221
|
+
const safePage = Math.min(currentPage, pageCount - 1);
|
|
217
222
|
const paddedIcons = (0, _react.useMemo)(() => {
|
|
218
|
-
const
|
|
223
|
+
const start = safePage * _floatingToolsReact.MAX_DIAL_SLOTS;
|
|
224
|
+
const result = [...icons.slice(start, start + _floatingToolsReact.MAX_DIAL_SLOTS)];
|
|
219
225
|
while (result.length < _floatingToolsReact.MAX_DIAL_SLOTS) {
|
|
220
226
|
result.push({
|
|
221
227
|
id: `empty-${result.length}`,
|
|
@@ -225,7 +231,7 @@ function DialMenu({
|
|
|
225
231
|
});
|
|
226
232
|
}
|
|
227
233
|
return result;
|
|
228
|
-
}, [icons]);
|
|
234
|
+
}, [icons, safePage]);
|
|
229
235
|
|
|
230
236
|
// Inject keyframes
|
|
231
237
|
(0, _react.useEffect)(() => {
|
|
@@ -402,10 +408,37 @@ function DialMenu({
|
|
|
402
408
|
}, interaction.iconSelect.actionDelay);
|
|
403
409
|
}, [interaction.iconSelect, handleClose]);
|
|
404
410
|
|
|
411
|
+
// Page navigation - icons are keyed by slot index, so this only swaps
|
|
412
|
+
// their content in place (no remount, no re-animation) for an instant page
|
|
413
|
+
// change.
|
|
414
|
+
const handlePageChange = (0, _react.useCallback)(next => {
|
|
415
|
+
if (isClosingRef.current) return;
|
|
416
|
+
const clamped = Math.max(0, Math.min(next, pageCount - 1));
|
|
417
|
+
if (clamped !== safePage) setCurrentPage(clamped);
|
|
418
|
+
}, [pageCount, safePage]);
|
|
419
|
+
|
|
405
420
|
// Computed values
|
|
406
421
|
const buttonContainerSize = layout.buttonSize * _floatingToolsReact.dialStyles.centerButton.containerRatio;
|
|
407
422
|
const buttonBorderSize = layout.buttonSize * _floatingToolsReact.dialStyles.centerButton.borderRatio;
|
|
408
423
|
const isAnimating = entranceComplete && !isExiting;
|
|
424
|
+
const pagerButtonStyle = disabled => ({
|
|
425
|
+
display: 'flex',
|
|
426
|
+
alignItems: 'center',
|
|
427
|
+
gap: 4,
|
|
428
|
+
padding: '8px 16px',
|
|
429
|
+
borderRadius: 10,
|
|
430
|
+
border: `1px solid ${_floatingToolsReact.dialColors.dialBorder}`,
|
|
431
|
+
backgroundColor: _floatingToolsReact.dialColors.dialBackground,
|
|
432
|
+
color: disabled ? _floatingToolsReact.dialColors.emptyDotBorder : _floatingToolsReact.dialColors.dialShadow,
|
|
433
|
+
fontSize: 12,
|
|
434
|
+
fontWeight: 900,
|
|
435
|
+
fontFamily: 'monospace',
|
|
436
|
+
letterSpacing: 1.5,
|
|
437
|
+
textTransform: 'uppercase',
|
|
438
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
439
|
+
opacity: disabled ? 0.4 : 1,
|
|
440
|
+
boxShadow: disabled ? 'none' : `0 0 8px ${_floatingToolsReact.dialColors.dialShadow}66`
|
|
441
|
+
});
|
|
409
442
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
410
443
|
role: "dialog",
|
|
411
444
|
"aria-label": "Dial Menu",
|
|
@@ -430,11 +463,15 @@ function DialMenu({
|
|
|
430
463
|
backgroundColor: _floatingToolsReact.dialColors.dialBackdrop,
|
|
431
464
|
opacity: backdropOpacity
|
|
432
465
|
}
|
|
433
|
-
}), /*#__PURE__*/(0, _jsxRuntime.
|
|
466
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
434
467
|
style: {
|
|
435
|
-
animation: isAnimating ? cssAnimations.floating : 'none'
|
|
468
|
+
animation: isAnimating ? cssAnimations.floating : 'none',
|
|
469
|
+
display: 'flex',
|
|
470
|
+
flexDirection: 'column',
|
|
471
|
+
alignItems: 'center',
|
|
472
|
+
gap: 16
|
|
436
473
|
},
|
|
437
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
474
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
438
475
|
style: {
|
|
439
476
|
animation: triggerGlitch > 0 && isAnimating ? cssAnimations.glitch : 'none'
|
|
440
477
|
},
|
|
@@ -497,7 +534,7 @@ function DialMenu({
|
|
|
497
534
|
position: positions[index],
|
|
498
535
|
progress: iconProgress,
|
|
499
536
|
onPress: () => handleIconPress(icon)
|
|
500
|
-
},
|
|
537
|
+
}, index)), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
501
538
|
style: {
|
|
502
539
|
position: 'absolute',
|
|
503
540
|
left: '50%',
|
|
@@ -587,7 +624,45 @@ function DialMenu({
|
|
|
587
624
|
})]
|
|
588
625
|
})]
|
|
589
626
|
})
|
|
590
|
-
}, triggerGlitch)
|
|
627
|
+
}, triggerGlitch), pageCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
628
|
+
style: {
|
|
629
|
+
display: 'flex',
|
|
630
|
+
alignItems: 'center',
|
|
631
|
+
gap: 12,
|
|
632
|
+
opacity: Math.min(1, dialScale)
|
|
633
|
+
},
|
|
634
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
635
|
+
type: "button",
|
|
636
|
+
"aria-label": "Previous dial page",
|
|
637
|
+
disabled: safePage <= 0,
|
|
638
|
+
onClick: () => handlePageChange(safePage - 1),
|
|
639
|
+
style: pagerButtonStyle(safePage <= 0),
|
|
640
|
+
children: "\u2039 PREV"
|
|
641
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
|
|
642
|
+
style: {
|
|
643
|
+
fontSize: 13,
|
|
644
|
+
fontWeight: 900,
|
|
645
|
+
fontFamily: 'monospace',
|
|
646
|
+
letterSpacing: 2,
|
|
647
|
+
color: '#FFFFFF',
|
|
648
|
+
textShadow: `0 0 6px ${_floatingToolsReact.dialColors.dialShadow}`
|
|
649
|
+
},
|
|
650
|
+
children: [String(safePage + 1).padStart(2, '0'), /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
|
|
651
|
+
style: {
|
|
652
|
+
color: _floatingToolsReact.dialColors.iconLabel,
|
|
653
|
+
textShadow: 'none'
|
|
654
|
+
},
|
|
655
|
+
children: [' / ', String(pageCount).padStart(2, '0')]
|
|
656
|
+
})]
|
|
657
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
658
|
+
type: "button",
|
|
659
|
+
"aria-label": "Next dial page",
|
|
660
|
+
disabled: safePage >= pageCount - 1,
|
|
661
|
+
onClick: () => handlePageChange(safePage + 1),
|
|
662
|
+
style: pagerButtonStyle(safePage >= pageCount - 1),
|
|
663
|
+
children: "NEXT \u203A"
|
|
664
|
+
})]
|
|
665
|
+
})]
|
|
591
666
|
})]
|
|
592
667
|
});
|
|
593
668
|
}
|