@buoy-gg/core 3.0.1 → 4.0.1
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 +1 -1
- package/lib/commonjs/floatingMenu/FloatingDevTools.js +25 -5
- package/lib/commonjs/floatingMenu/FloatingMenu.js +3 -7
- package/lib/commonjs/floatingMenu/allToolsRegistry.js +193 -0
- package/lib/commonjs/floatingMenu/autoExternalSync.js +254 -0
- package/lib/commonjs/floatingMenu/defaultConfig.js +1 -1
- package/lib/commonjs/floatingMenu/dial/DialDevTools.js +115 -53
- package/lib/commonjs/floatingMenu/dial/DialIcon.js +53 -13
- package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.js +1 -1
- package/lib/module/floatingMenu/DevToolsSettingsModal.js +2 -2
- package/lib/module/floatingMenu/FloatingDevTools.js +26 -6
- package/lib/module/floatingMenu/FloatingMenu.js +4 -7
- package/lib/module/floatingMenu/allToolsRegistry.js +190 -0
- package/lib/module/floatingMenu/autoExternalSync.js +248 -0
- package/lib/module/floatingMenu/defaultConfig.js +1 -1
- package/lib/module/floatingMenu/dial/DialDevTools.js +119 -55
- package/lib/module/floatingMenu/dial/DialIcon.js +56 -15
- package/lib/module/floatingMenu/dial/OnboardingTooltip.js +2 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +19 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/allToolsRegistry.d.ts +42 -0
- package/lib/typescript/commonjs/floatingMenu/allToolsRegistry.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts +25 -0
- package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +7 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +19 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/allToolsRegistry.d.ts +42 -0
- package/lib/typescript/module/floatingMenu/allToolsRegistry.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts +25 -0
- package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +7 -0
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -6,10 +6,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.DialDevTools = void 0;
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
10
|
var _DialIcon = require("./DialIcon.js");
|
|
11
|
+
var _allToolsRegistry = require("../allToolsRegistry.js");
|
|
10
12
|
var _DialPagination = require("./DialPagination.js");
|
|
11
13
|
var _dialUsageStore = require("./dialUsageStore.js");
|
|
12
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
13
14
|
var _DevToolsSettingsModal = require("../DevToolsSettingsModal");
|
|
14
15
|
var _license = require("@buoy-gg/license");
|
|
15
16
|
var _AppHost = require("../AppHost.js");
|
|
@@ -18,17 +19,10 @@ var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
|
|
|
18
19
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
19
20
|
// Icons are provided by installedApps; no direct icon imports here.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Use shared layout calculation from core
|
|
27
|
-
const layout = (0, _floatingToolsCore.getDialLayout)({
|
|
28
|
-
screenWidth: SCREEN_WIDTH
|
|
29
|
-
});
|
|
30
|
-
const CIRCLE_SIZE = layout.circleSize;
|
|
31
|
-
const BUTTON_SIZE = layout.buttonSize;
|
|
22
|
+
// The circle size depends on the live window width, so it's computed inside
|
|
23
|
+
// the component via useWindowDimensions — a module-scope Dimensions.get
|
|
24
|
+
// snapshot goes stale when the window resizes after load (web/desktop).
|
|
25
|
+
const BUTTON_SIZE = _floatingToolsCore.DIAL_BUTTON_SIZE;
|
|
32
26
|
const ONBOARDING_STORAGE_KEY = "@react_buoy_settings_tooltip_shown";
|
|
33
27
|
/** A non-interactive placeholder used to fill out the last dial page. */
|
|
34
28
|
const createEmptySlot = slotIndex => ({
|
|
@@ -58,7 +52,37 @@ const DialDevTools = ({
|
|
|
58
52
|
const {
|
|
59
53
|
open
|
|
60
54
|
} = (0, _AppHost.useAppHost)();
|
|
61
|
-
const
|
|
55
|
+
const {
|
|
56
|
+
isPro,
|
|
57
|
+
isWeekendFree
|
|
58
|
+
} = (0, _license.useProAccess)();
|
|
59
|
+
|
|
60
|
+
// Live window size — keeps the dial centered and sized correctly when the
|
|
61
|
+
// window resizes (Electron/web) or the device rotates.
|
|
62
|
+
const {
|
|
63
|
+
width: screenWidth,
|
|
64
|
+
height: screenHeight
|
|
65
|
+
} = (0, _reactNative.useWindowDimensions)();
|
|
66
|
+
const circleSize = (0, _floatingToolsCore.getDialLayout)({
|
|
67
|
+
screenWidth
|
|
68
|
+
}).circleSize;
|
|
69
|
+
const sizeStyles = (0, _react.useMemo)(() => ({
|
|
70
|
+
parent: {
|
|
71
|
+
width: circleSize,
|
|
72
|
+
height: circleSize
|
|
73
|
+
},
|
|
74
|
+
circle: {
|
|
75
|
+
width: circleSize,
|
|
76
|
+
height: circleSize,
|
|
77
|
+
borderRadius: circleSize / 2
|
|
78
|
+
},
|
|
79
|
+
rounded: {
|
|
80
|
+
borderRadius: circleSize / 2
|
|
81
|
+
},
|
|
82
|
+
gridLine: {
|
|
83
|
+
width: circleSize
|
|
84
|
+
}
|
|
85
|
+
}), [circleSize]);
|
|
62
86
|
|
|
63
87
|
// Load persisted settings modal state on mount
|
|
64
88
|
(0, _react.useEffect)(() => {
|
|
@@ -191,6 +215,35 @@ const DialDevTools = ({
|
|
|
191
215
|
return map;
|
|
192
216
|
}, [dialApps, actions, open, onClose]);
|
|
193
217
|
|
|
218
|
+
// Tools from the global registry that are NOT installed in this app.
|
|
219
|
+
// These are appended after all available tools so paging "next" eventually
|
|
220
|
+
// reveals them. They are display-only (real icon + real name + "Unavailable").
|
|
221
|
+
const unavailableTools = (0, _react.useMemo)(() => {
|
|
222
|
+
const installedIds = new Set(dialApps.map(a => a.id));
|
|
223
|
+
return _allToolsRegistry.ALL_TOOLS_REGISTRY.filter(t => !installedIds.has(t.id));
|
|
224
|
+
}, [dialApps]);
|
|
225
|
+
|
|
226
|
+
// Build stable IconType entries for each unavailable tool.
|
|
227
|
+
const unavailableIconsById = (0, _react.useMemo)(() => {
|
|
228
|
+
const map = new Map();
|
|
229
|
+
for (const tool of unavailableTools) {
|
|
230
|
+
map.set(tool.id, {
|
|
231
|
+
id: tool.id,
|
|
232
|
+
name: tool.name,
|
|
233
|
+
// Pre-render the icon at the standard dial slot size so it looks correct.
|
|
234
|
+
icon: tool.renderIcon(_floatingToolsCore.DIAL_ICON_SIZE),
|
|
235
|
+
iconComponent: undefined,
|
|
236
|
+
color: tool.color,
|
|
237
|
+
unavailable: true,
|
|
238
|
+
// Tapping an unavailable tool is intentionally a no-op: the dial stays
|
|
239
|
+
// open and no modal launches, signalling the tool is not installed.
|
|
240
|
+
// Future: add a brief Toast with an install link here.
|
|
241
|
+
onPress: () => {}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return map;
|
|
245
|
+
}, [unavailableTools]);
|
|
246
|
+
|
|
194
247
|
// Snapshot the usage-ranked order when the dial opens. It stays stable while
|
|
195
248
|
// open so icons don't jump positions mid-interaction.
|
|
196
249
|
const [rankedIds, setRankedIds] = (0, _react.useState)(() => (0, _dialUsageStore.getRankedToolIds)(dialApps.map(a => a.id)));
|
|
@@ -209,7 +262,9 @@ const DialDevTools = ({
|
|
|
209
262
|
cancelled = true;
|
|
210
263
|
};
|
|
211
264
|
}, [dialApps]);
|
|
212
|
-
|
|
265
|
+
|
|
266
|
+
// Page count spans available tools (usage-ranked) + unavailable tools (appended).
|
|
267
|
+
const pageCount = Math.max(1, Math.ceil((rankedIds.length + unavailableTools.length) / _floatingToolsCore.MAX_DIAL_SLOTS));
|
|
213
268
|
const [currentPage, setCurrentPage] = (0, _react.useState)(0);
|
|
214
269
|
const safePage = Math.min(currentPage, pageCount - 1);
|
|
215
270
|
|
|
@@ -217,13 +272,18 @@ const DialDevTools = ({
|
|
|
217
272
|
// snapshotted on open, so each icon's page and slot are fixed for the
|
|
218
273
|
// session — which lets us mount all icons once and paginate purely by
|
|
219
274
|
// toggling visibility (no remounts on page change).
|
|
275
|
+
//
|
|
276
|
+
// Order: usage-ranked available tools first, unavailable tools appended at
|
|
277
|
+
// the very end. This guarantees unavailable items always appear on the last
|
|
278
|
+
// pages and never displace available tools.
|
|
220
279
|
const allDialIcons = (0, _react.useMemo)(() => {
|
|
221
|
-
|
|
280
|
+
const allIds = [...rankedIds, ...unavailableTools.map(t => t.id)];
|
|
281
|
+
return allIds.map(id => iconsById.get(id) ?? unavailableIconsById.get(id)).filter(icon => Boolean(icon)).map((icon, i) => ({
|
|
222
282
|
icon,
|
|
223
283
|
page: Math.floor(i / _floatingToolsCore.MAX_DIAL_SLOTS),
|
|
224
284
|
slot: i % _floatingToolsCore.MAX_DIAL_SLOTS
|
|
225
285
|
}));
|
|
226
|
-
}, [rankedIds, iconsById]);
|
|
286
|
+
}, [rankedIds, iconsById, unavailableTools, unavailableIconsById]);
|
|
227
287
|
|
|
228
288
|
// Empty slot indices for the current page (only the last page can be
|
|
229
289
|
// partial). These are cheap placeholder dots.
|
|
@@ -425,6 +485,10 @@ const DialDevTools = ({
|
|
|
425
485
|
});
|
|
426
486
|
};
|
|
427
487
|
const handleIconPress = icon => {
|
|
488
|
+
// Unavailable tools are display-only. Return early so the dial stays open
|
|
489
|
+
// and no modal or animation is triggered. The dimmed appearance already
|
|
490
|
+
// communicates that the tool is not installed.
|
|
491
|
+
if (icon.unavailable) return;
|
|
428
492
|
setSelectedIcon(1);
|
|
429
493
|
const interaction = _floatingToolsCore.dialAnimationConfig?.interaction ?? {
|
|
430
494
|
iconSelect: {
|
|
@@ -485,13 +549,13 @@ const DialDevTools = ({
|
|
|
485
549
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
|
|
486
550
|
style: [styles.backdrop, backdropAnimatedStyle],
|
|
487
551
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
488
|
-
style:
|
|
552
|
+
style: _sharedUi.absoluteFill,
|
|
489
553
|
onPress: handleClose
|
|
490
554
|
})
|
|
491
555
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
|
|
492
|
-
style: [styles.parent, {
|
|
556
|
+
style: [styles.parent, sizeStyles.parent, {
|
|
493
557
|
position: "absolute",
|
|
494
|
-
left: (
|
|
558
|
+
left: (screenWidth - circleSize) / 2,
|
|
495
559
|
bottom: 80,
|
|
496
560
|
transform: [{
|
|
497
561
|
scale: dialScale
|
|
@@ -503,21 +567,21 @@ const DialDevTools = ({
|
|
|
503
567
|
}]
|
|
504
568
|
}],
|
|
505
569
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
|
|
506
|
-
style: styles.circle,
|
|
570
|
+
style: [styles.circle, sizeStyles.circle],
|
|
507
571
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
508
|
-
style: styles.gradientBackground,
|
|
572
|
+
style: [styles.gradientBackground, sizeStyles.rounded],
|
|
509
573
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
510
|
-
style: styles.gradientLayer1
|
|
574
|
+
style: [styles.gradientLayer1, sizeStyles.rounded]
|
|
511
575
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
512
|
-
style: styles.gradientLayer2
|
|
576
|
+
style: [styles.gradientLayer2, sizeStyles.rounded]
|
|
513
577
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
514
|
-
style: styles.gradientLayer3
|
|
578
|
+
style: [styles.gradientLayer3, sizeStyles.rounded]
|
|
515
579
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
516
580
|
style: styles.gridPattern,
|
|
517
581
|
children: Array.from({
|
|
518
582
|
length: 6
|
|
519
583
|
}).map((_, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
520
|
-
style: [styles.gridLine, {
|
|
584
|
+
style: [styles.gridLine, sizeStyles.gridLine, {
|
|
521
585
|
transform: [{
|
|
522
586
|
rotate: `${i * 60}deg`
|
|
523
587
|
}]
|
|
@@ -592,8 +656,8 @@ const DialDevTools = ({
|
|
|
592
656
|
style: styles.centerText,
|
|
593
657
|
children: "BUOY"
|
|
594
658
|
}), isPro && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
595
|
-
style: styles.proText,
|
|
596
|
-
children: "PRO"
|
|
659
|
+
style: [styles.proText, isWeekendFree && styles.weekendText],
|
|
660
|
+
children: isWeekendFree ? "WEEKEND" : "PRO"
|
|
597
661
|
})]
|
|
598
662
|
})
|
|
599
663
|
})
|
|
@@ -608,11 +672,11 @@ const DialDevTools = ({
|
|
|
608
672
|
onNext: () => handlePageChange(safePage + 1),
|
|
609
673
|
animatedStyle: {
|
|
610
674
|
position: "absolute",
|
|
611
|
-
left: (
|
|
612
|
-
// Circle's bottom edge sits at bottom: 80 ->
|
|
675
|
+
left: (screenWidth - circleSize) / 2,
|
|
676
|
+
// Circle's bottom edge sits at bottom: 80 -> screenHeight - 80
|
|
613
677
|
// from the top. Place the pager 16px below that edge.
|
|
614
|
-
top:
|
|
615
|
-
width:
|
|
678
|
+
top: screenHeight - 80 + 16,
|
|
679
|
+
width: circleSize,
|
|
616
680
|
opacity: dialScale,
|
|
617
681
|
transform: [{
|
|
618
682
|
scale: dialScale
|
|
@@ -634,23 +698,20 @@ const DialDevTools = ({
|
|
|
634
698
|
exports.DialDevTools = DialDevTools;
|
|
635
699
|
const styles = _reactNative.StyleSheet.create({
|
|
636
700
|
container: {
|
|
637
|
-
...
|
|
701
|
+
..._sharedUi.absoluteFill,
|
|
638
702
|
zIndex: 9999
|
|
639
703
|
},
|
|
640
704
|
backdrop: {
|
|
641
|
-
...
|
|
705
|
+
..._sharedUi.absoluteFill,
|
|
642
706
|
backgroundColor: _floatingToolsCore.dialColors.dialBackdrop
|
|
643
707
|
},
|
|
708
|
+
// width/height/borderRadius for the circle pieces come from sizeStyles —
|
|
709
|
+
// they track the live window width.
|
|
644
710
|
parent: {
|
|
645
|
-
width: CIRCLE_SIZE,
|
|
646
|
-
height: CIRCLE_SIZE,
|
|
647
711
|
alignItems: "center",
|
|
648
712
|
justifyContent: "center"
|
|
649
713
|
},
|
|
650
714
|
circle: {
|
|
651
|
-
width: CIRCLE_SIZE,
|
|
652
|
-
height: CIRCLE_SIZE,
|
|
653
|
-
borderRadius: CIRCLE_SIZE / 2,
|
|
654
715
|
position: "absolute",
|
|
655
716
|
backgroundColor: "transparent",
|
|
656
717
|
borderWidth: 1,
|
|
@@ -667,41 +728,36 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
667
728
|
gradientBackground: {
|
|
668
729
|
width: "100%",
|
|
669
730
|
height: "100%",
|
|
670
|
-
borderRadius: CIRCLE_SIZE / 2,
|
|
671
731
|
position: "relative",
|
|
672
732
|
backgroundColor: _floatingToolsCore.dialColors.dialBackground,
|
|
673
733
|
overflow: "hidden"
|
|
674
734
|
},
|
|
675
735
|
gradientLayer1: {
|
|
676
|
-
...
|
|
736
|
+
..._sharedUi.absoluteFill,
|
|
677
737
|
backgroundColor: _floatingToolsCore.dialColors.dialGradient1,
|
|
678
|
-
opacity: 0.6
|
|
679
|
-
borderRadius: CIRCLE_SIZE / 2
|
|
738
|
+
opacity: 0.6
|
|
680
739
|
},
|
|
681
740
|
gradientLayer2: {
|
|
682
|
-
...
|
|
741
|
+
..._sharedUi.absoluteFill,
|
|
683
742
|
backgroundColor: _floatingToolsCore.dialColors.dialGradient2,
|
|
684
743
|
opacity: 0.4,
|
|
685
744
|
top: "30%",
|
|
686
|
-
left: "30%"
|
|
687
|
-
borderRadius: CIRCLE_SIZE / 2
|
|
745
|
+
left: "30%"
|
|
688
746
|
},
|
|
689
747
|
gradientLayer3: {
|
|
690
|
-
...
|
|
748
|
+
..._sharedUi.absoluteFill,
|
|
691
749
|
backgroundColor: _floatingToolsCore.dialColors.dialGradient3,
|
|
692
750
|
opacity: 0.3,
|
|
693
751
|
top: "50%",
|
|
694
|
-
left: "50%"
|
|
695
|
-
borderRadius: CIRCLE_SIZE / 2
|
|
752
|
+
left: "50%"
|
|
696
753
|
},
|
|
697
754
|
gridPattern: {
|
|
698
|
-
...
|
|
755
|
+
..._sharedUi.absoluteFill,
|
|
699
756
|
alignItems: "center",
|
|
700
757
|
justifyContent: "center"
|
|
701
758
|
},
|
|
702
759
|
gridLine: {
|
|
703
760
|
position: "absolute",
|
|
704
|
-
width: CIRCLE_SIZE,
|
|
705
761
|
height: 1,
|
|
706
762
|
backgroundColor: _floatingToolsCore.dialColors.dialGridLine
|
|
707
763
|
},
|
|
@@ -727,13 +783,13 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
727
783
|
overflow: "hidden"
|
|
728
784
|
},
|
|
729
785
|
buttonGradientLayer1: {
|
|
730
|
-
...
|
|
786
|
+
..._sharedUi.absoluteFill,
|
|
731
787
|
backgroundColor: _floatingToolsCore.dialColors.dialGradient1,
|
|
732
788
|
opacity: 0.5,
|
|
733
789
|
borderRadius: BUTTON_SIZE
|
|
734
790
|
},
|
|
735
791
|
buttonGradientLayer2: {
|
|
736
|
-
...
|
|
792
|
+
..._sharedUi.absoluteFill,
|
|
737
793
|
backgroundColor: _floatingToolsCore.dialColors.dialGradient2,
|
|
738
794
|
opacity: 0.3,
|
|
739
795
|
top: "20%",
|
|
@@ -741,7 +797,7 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
741
797
|
borderRadius: BUTTON_SIZE
|
|
742
798
|
},
|
|
743
799
|
buttonGradientLayer3: {
|
|
744
|
-
...
|
|
800
|
+
..._sharedUi.absoluteFill,
|
|
745
801
|
backgroundColor: _floatingToolsCore.dialColors.dialGradient3,
|
|
746
802
|
opacity: 0.2,
|
|
747
803
|
top: "40%",
|
|
@@ -807,5 +863,11 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
807
863
|
height: 0
|
|
808
864
|
},
|
|
809
865
|
textShadowRadius: 4
|
|
866
|
+
},
|
|
867
|
+
// Weekend Pass variant — violet, tighter spacing so "WEEKEND" fits the dial.
|
|
868
|
+
weekendText: {
|
|
869
|
+
color: "#BF5AF2",
|
|
870
|
+
letterSpacing: 1,
|
|
871
|
+
textShadowColor: "#BF5AF2"
|
|
810
872
|
}
|
|
811
873
|
});
|
|
@@ -8,16 +8,9 @@ var _react = require("react");
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
|
|
10
10
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Use shared layout calculation
|
|
16
|
-
const layout = (0, _floatingToolsCore.getDialLayout)({
|
|
17
|
-
screenWidth: SCREEN_WIDTH
|
|
18
|
-
});
|
|
19
|
-
const VIEW_SIZE = layout.iconSize;
|
|
20
|
-
const CIRCLE_RADIUS = layout.circleRadius;
|
|
11
|
+
// The circle radius depends on the live window width and is computed inside
|
|
12
|
+
// the component (must match DialDevTools' circle, which does the same).
|
|
13
|
+
const VIEW_SIZE = _floatingToolsCore.DIAL_ICON_SIZE;
|
|
21
14
|
const DialIcon = ({
|
|
22
15
|
index,
|
|
23
16
|
icon,
|
|
@@ -28,6 +21,12 @@ const DialIcon = ({
|
|
|
28
21
|
}) => {
|
|
29
22
|
// Animation values - using interpolation for better performance
|
|
30
23
|
const scale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
24
|
+
const {
|
|
25
|
+
width: screenWidth
|
|
26
|
+
} = (0, _reactNative.useWindowDimensions)();
|
|
27
|
+
const layout = (0, _react.useMemo)(() => (0, _floatingToolsCore.getDialLayout)({
|
|
28
|
+
screenWidth
|
|
29
|
+
}), [screenWidth]);
|
|
31
30
|
|
|
32
31
|
// Hover animation on press in/out - using shared config
|
|
33
32
|
// Fallback values in case dialAnimationConfig hasn't loaded yet
|
|
@@ -123,14 +122,14 @@ const DialIcon = ({
|
|
|
123
122
|
itemOpacity,
|
|
124
123
|
progressScale: staggeredProgress
|
|
125
124
|
};
|
|
126
|
-
}, [index, totalIcons, iconsProgress]);
|
|
125
|
+
}, [index, totalIcons, iconsProgress, layout]);
|
|
127
126
|
|
|
128
127
|
// Main animated style for position and appearance
|
|
129
128
|
const animatedStyle = {
|
|
130
129
|
position: "absolute",
|
|
131
|
-
left:
|
|
130
|
+
left: layout.circleRadius - VIEW_SIZE / 2,
|
|
132
131
|
// Center position
|
|
133
|
-
top:
|
|
132
|
+
top: layout.circleRadius - VIEW_SIZE / 2,
|
|
134
133
|
// Center position
|
|
135
134
|
opacity: motion.itemOpacity,
|
|
136
135
|
transform: [{
|
|
@@ -158,6 +157,32 @@ const DialIcon = ({
|
|
|
158
157
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
159
158
|
style: styles.emptyDot
|
|
160
159
|
})
|
|
160
|
+
}) : icon.unavailable ?
|
|
161
|
+
/*#__PURE__*/
|
|
162
|
+
// ── Unavailable tool ──────────────────────────────────────────────────
|
|
163
|
+
// Renders the real icon + real name at NORMAL styling (visually identical
|
|
164
|
+
// to an installed tool). The ONLY signal that it's unavailable is the small
|
|
165
|
+
// muted "Unavailable" caption under the name. It's non-interactive: no
|
|
166
|
+
// Pressable, and the dial stays open when tapped because
|
|
167
|
+
// DialDevTools.handleIconPress returns early for unavailable icons.
|
|
168
|
+
(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
169
|
+
style: styles.pressable,
|
|
170
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
171
|
+
style: styles.iconWrapper,
|
|
172
|
+
children: icon.icon
|
|
173
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
174
|
+
style: styles.label,
|
|
175
|
+
numberOfLines: 1,
|
|
176
|
+
adjustsFontSizeToFit: true,
|
|
177
|
+
minimumFontScale: 0.7,
|
|
178
|
+
children: icon.name.toUpperCase()
|
|
179
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
180
|
+
style: styles.unavailableCaption,
|
|
181
|
+
numberOfLines: 1,
|
|
182
|
+
adjustsFontSizeToFit: true,
|
|
183
|
+
minimumFontScale: 0.6,
|
|
184
|
+
children: "Unavailable"
|
|
185
|
+
})]
|
|
161
186
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
162
187
|
onPress: () => onPress(icon),
|
|
163
188
|
onPressIn: handlePressIn,
|
|
@@ -249,5 +274,20 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
249
274
|
backgroundColor: _floatingToolsCore.dialColors.emptyDotBackground,
|
|
250
275
|
borderWidth: _floatingToolsCore.dialStyles.emptySlot.borderWidth,
|
|
251
276
|
borderColor: _floatingToolsCore.dialColors.emptyDotBorder
|
|
277
|
+
},
|
|
278
|
+
// ── Unavailable tool styles ────────────────────────────────────────────────
|
|
279
|
+
/**
|
|
280
|
+
* Small muted caption shown under the (normal-styled) tool name — the only
|
|
281
|
+
* signal that the tool is not installed. Kept compact (smaller than the label)
|
|
282
|
+
* so the extra line doesn't disturb the arc layout. Uses the shared muted
|
|
283
|
+
* theme token rather than a hardcoded color.
|
|
284
|
+
*/
|
|
285
|
+
unavailableCaption: {
|
|
286
|
+
fontSize: _floatingToolsCore.dialStyles.icon.label.fontSize - 1,
|
|
287
|
+
fontWeight: _floatingToolsCore.dialStyles.icon.label.fontWeight,
|
|
288
|
+
letterSpacing: _floatingToolsCore.dialStyles.icon.label.letterSpacing,
|
|
289
|
+
fontFamily: _floatingToolsCore.dialStyles.icon.label.fontFamily,
|
|
290
|
+
color: _floatingToolsCore.floatingToolsColors.muted,
|
|
291
|
+
textTransform: "uppercase"
|
|
252
292
|
}
|
|
253
293
|
});
|
|
@@ -133,7 +133,7 @@ const OnboardingTooltip = ({
|
|
|
133
133
|
exports.OnboardingTooltip = OnboardingTooltip;
|
|
134
134
|
const styles = _reactNative.StyleSheet.create({
|
|
135
135
|
container: {
|
|
136
|
-
...
|
|
136
|
+
..._sharedUi.absoluteFill,
|
|
137
137
|
alignItems: "center",
|
|
138
138
|
justifyContent: "center",
|
|
139
139
|
pointerEvents: "box-none"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
4
|
-
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Dimensions } from "react-native";
|
|
4
|
+
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Dimensions, Platform } from "react-native";
|
|
5
5
|
import { settingsBus } from "./settingsBus.js";
|
|
6
6
|
import { SentryBugIcon, WifiCircuitIcon, ImageOverlayIcon, RenderCountIcon, Info, Layers, ChevronRightIcon, ChevronDown, getStorageBackendType, persistentStorage, Database, Trash2, CheckCircle2, AlertTriangle, Zap, FileText, HardDrive, Copy, FileCode, copyToClipboard, LicenseEntryModal, SectionHeader } from "@buoy-gg/shared-ui";
|
|
7
7
|
import { EnvIcon, StorageIcon, RoutesIcon, NetworkIcon, QueryIcon, HighlighterIcon, ReduxIcon, EventsIcon } from "@buoy-gg/floating-tools-core";
|
|
@@ -809,7 +809,7 @@ export const DevToolsSettingsModal = ({
|
|
|
809
809
|
children: clearSuccess ? "CLEARED" : isClearing ? "CLEARING..." : "CLEAR ALL SETTINGS"
|
|
810
810
|
})]
|
|
811
811
|
})]
|
|
812
|
-
}), renderGlobalSettingCard("enableSharedModalDimensions", "SHARED MODAL SIZE", "MODAL", "Sync dimensions across all tools", "When enabled, all tool modals will share the same size and position. Resizing one modal will affect all others. When disabled, each tool remembers its own size and position independently.", "Keep OFF for the best experience. This allows you to customize each tool's modal size separately. Enable only if you prefer uniform modal sizes across all dev tools."), renderGlobalSettingCard("expandableWindowControls", "EXPAND CONTROLS", "MODAL", "iPad-style expandable window buttons", "When enabled, the window control buttons (minimize, toggle mode, close) start as small dots and expand into larger, easy-to-tap buttons when pressed — similar to iPad window controls. When disabled, buttons are directly tappable at their small size.", "Keep ON for touch devices where the small buttons are hard to press. Turn OFF if you prefer direct single-tap access (e.g. when using a mouse or simulator)."), isDevelopmentMode && /*#__PURE__*/_jsxs(View, {
|
|
812
|
+
}), renderGlobalSettingCard("enableSharedModalDimensions", "SHARED MODAL SIZE", "MODAL", "Sync dimensions across all tools", "When enabled, all tool modals will share the same size and position. Resizing one modal will affect all others. When disabled, each tool remembers its own size and position independently.", "Keep OFF for the best experience. This allows you to customize each tool's modal size separately. Enable only if you prefer uniform modal sizes across all dev tools."), Platform.OS !== "web" && renderGlobalSettingCard("expandableWindowControls", "EXPAND CONTROLS", "MODAL", "iPad-style expandable window buttons", "When enabled, the window control buttons (minimize, toggle mode, close) start as small dots and expand into larger, easy-to-tap buttons when pressed — similar to iPad window controls. When disabled, buttons are directly tappable at their small size.", "Keep ON for touch devices where the small buttons are hard to press. Turn OFF if you prefer direct single-tap access (e.g. when using a mouse or simulator)."), isDevelopmentMode && /*#__PURE__*/_jsxs(View, {
|
|
813
813
|
style: styles.exportConfigCard,
|
|
814
814
|
children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
815
815
|
activeOpacity: 0.85,
|
|
@@ -11,7 +11,8 @@ import { HintsProvider, LicenseEntryModal, normalizeEnvironment } from "@buoy-gg
|
|
|
11
11
|
import { MinimizedToolsProvider } from "./MinimizedToolsContext.js";
|
|
12
12
|
import { validateDialConfig } from "./defaultConfig.js";
|
|
13
13
|
import { DefaultConfigProvider } from "./DefaultConfigContext.js";
|
|
14
|
-
import { LicenseManager,
|
|
14
|
+
import { LicenseManager, useProAccess, useLicense } from "@buoy-gg/license";
|
|
15
|
+
import { AutoExternalSync, isExternalSyncAvailable } from "./autoExternalSync.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Environment variable configuration
|
|
@@ -109,11 +110,18 @@ export const FloatingDevTools = ({
|
|
|
109
110
|
licenseKey: licenseKeyProp,
|
|
110
111
|
zustandStores,
|
|
111
112
|
environment,
|
|
113
|
+
externalSync = true,
|
|
112
114
|
...props
|
|
113
115
|
}) => {
|
|
114
116
|
const resolvedEnvironment = environment ?? normalizeEnvironment(process.env.NODE_ENV ?? "dev");
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
+
// Production gating + the device→dashboard license handshake must use a REAL
|
|
118
|
+
// license only — NOT the free Weekend Pass. Otherwise shipping Buoy would
|
|
119
|
+
// expose the devtools to end users every weekend, and the dashboard would try
|
|
120
|
+
// to adopt a non-existent key. (Feature unlocks inside the tools still honor
|
|
121
|
+
// the Weekend Pass via their own useIsPro().)
|
|
122
|
+
const {
|
|
123
|
+
isLicensed
|
|
124
|
+
} = useProAccess();
|
|
117
125
|
|
|
118
126
|
// Get full license state for requireLicense gating
|
|
119
127
|
const {
|
|
@@ -122,10 +130,17 @@ export const FloatingDevTools = ({
|
|
|
122
130
|
} = useLicense();
|
|
123
131
|
const [showLicenseModal, setShowLicenseModal] = useState(false);
|
|
124
132
|
|
|
125
|
-
// Initialize license manager on mount, and set/clear license key when prop changes
|
|
133
|
+
// Initialize license manager on mount, and set/clear license key when prop changes.
|
|
134
|
+
// The `licenseKey` prop is the source of truth WHEN PROVIDED:
|
|
135
|
+
// - non-empty → validate + activate that key
|
|
136
|
+
// - "" (blank) → EXPLICITLY clear the cached license (key was removed)
|
|
137
|
+
// - undefined → don't touch it (the app may set a key via Buoy.init())
|
|
126
138
|
useEffect(() => {
|
|
127
139
|
if (licenseKeyProp && licenseKeyProp.trim() !== "") {
|
|
128
140
|
LicenseManager.initialize().then(() => LicenseManager.setLicenseKey(licenseKeyProp)).catch(() => {});
|
|
141
|
+
} else if (licenseKeyProp === "") {
|
|
142
|
+
// Explicit removal: wipe the 30-day cache so Pro actually turns off.
|
|
143
|
+
LicenseManager.initialize().then(() => LicenseManager.clearLicense()).catch(() => {});
|
|
129
144
|
} else {
|
|
130
145
|
LicenseManager.initialize();
|
|
131
146
|
}
|
|
@@ -302,7 +317,7 @@ export const FloatingDevTools = ({
|
|
|
302
317
|
// Gate: Free tier only works in development mode
|
|
303
318
|
// Pro users can use DevTools everywhere (dev + production)
|
|
304
319
|
const isDevelopment = typeof __DEV__ !== "undefined" && __DEV__;
|
|
305
|
-
if (!isDevelopment && !
|
|
320
|
+
if (!isDevelopment && !isLicensed) {
|
|
306
321
|
// Free user in production - don't render DevTools
|
|
307
322
|
return null;
|
|
308
323
|
}
|
|
@@ -338,7 +353,12 @@ export const FloatingDevTools = ({
|
|
|
338
353
|
environment: resolvedEnvironment
|
|
339
354
|
}), /*#__PURE__*/_jsx(AppOverlay, {})]
|
|
340
355
|
})
|
|
341
|
-
}), children, DebugBordersOverlay && /*#__PURE__*/_jsx(DebugBordersOverlay, {}), HighlightUpdatesOverlay && /*#__PURE__*/_jsx(HighlightUpdatesOverlay, {}), ImageOverlayOverlay && /*#__PURE__*/_jsx(ImageOverlayOverlay, {}), PerfMonitorOverlay && /*#__PURE__*/_jsx(PerfMonitorOverlay, {}), ImpersonateOverlay && /*#__PURE__*/_jsx(ImpersonateOverlay, {}), RouteTracker && /*#__PURE__*/_jsx(RouteTracker, {})
|
|
356
|
+
}), children, DebugBordersOverlay && /*#__PURE__*/_jsx(DebugBordersOverlay, {}), HighlightUpdatesOverlay && /*#__PURE__*/_jsx(HighlightUpdatesOverlay, {}), ImageOverlayOverlay && /*#__PURE__*/_jsx(ImageOverlayOverlay, {}), PerfMonitorOverlay && /*#__PURE__*/_jsx(PerfMonitorOverlay, {}), ImpersonateOverlay && /*#__PURE__*/_jsx(ImpersonateOverlay, {}), RouteTracker && /*#__PURE__*/_jsx(RouteTracker, {}), isDevelopment && externalSync !== false && isExternalSyncAvailable() && /*#__PURE__*/_jsx(AutoExternalSync, {
|
|
357
|
+
options: typeof externalSync === "object" ? externalSync : undefined,
|
|
358
|
+
requiredEnvVars: requiredEnvVars,
|
|
359
|
+
isPro: isLicensed,
|
|
360
|
+
licenseKey: licenseKey
|
|
361
|
+
})]
|
|
342
362
|
})
|
|
343
363
|
})
|
|
344
364
|
})
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { TouchableOpacity, StyleSheet, View
|
|
4
|
+
import { TouchableOpacity, StyleSheet, View } from "react-native";
|
|
5
|
+
import { absoluteFill } from "@buoy-gg/shared-ui";
|
|
5
6
|
import { FloatingTools, UserStatus } from "./floatingTools";
|
|
6
7
|
import { DialDevTools } from "./dial/DialDevTools";
|
|
7
8
|
import { EnvironmentIndicator, persistentStorage, useHintsDisabled, devToolsStorageKeys, buoyColors } from "@buoy-gg/shared-ui";
|
|
@@ -22,10 +23,6 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
22
23
|
*/
|
|
23
24
|
const FLOATING_MENU_ONBOARDING_KEY = "@react_buoy_floating_menu_tooltip_shown";
|
|
24
25
|
const ONBOARDING_STEP_KEY = "@react_buoy_onboarding_step";
|
|
25
|
-
const {
|
|
26
|
-
width: SCREEN_WIDTH,
|
|
27
|
-
height: SCREEN_HEIGHT
|
|
28
|
-
} = Dimensions.get("window");
|
|
29
26
|
export const FloatingMenu = ({
|
|
30
27
|
apps,
|
|
31
28
|
state,
|
|
@@ -322,11 +319,11 @@ const styles = StyleSheet.create({
|
|
|
322
319
|
fontWeight: "900"
|
|
323
320
|
},
|
|
324
321
|
onboardingContainer: {
|
|
325
|
-
...
|
|
322
|
+
...absoluteFill,
|
|
326
323
|
zIndex: 10000
|
|
327
324
|
},
|
|
328
325
|
onboardingBackdrop: {
|
|
329
|
-
...
|
|
326
|
+
...absoluteFill,
|
|
330
327
|
backgroundColor: "rgba(0, 0, 0, 0.85)"
|
|
331
328
|
}
|
|
332
329
|
});
|