@buoy-gg/shared-ui 7.0.21 → 7.0.23
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 +64 -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/commonjs/utils/devServer.js +62 -0
- package/lib/commonjs/utils/index.js +13 -0
- package/lib/commonjs/utils/renderCaptureRegistry.js +32 -3
- package/lib/commonjs/utils/safeExpoRouter.js +27 -2
- 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 +5 -3
- 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/module/utils/devServer.js +58 -0
- package/lib/module/utils/index.js +2 -1
- package/lib/module/utils/renderCaptureRegistry.js +32 -3
- package/lib/module/utils/safeExpoRouter.js +26 -2
- 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 +4 -3
- 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/commonjs/utils/devServer.d.ts +24 -0
- package/lib/typescript/commonjs/utils/devServer.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/index.d.ts +3 -2
- package/lib/typescript/commonjs/utils/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/renderCaptureRegistry.d.ts +52 -2
- package/lib/typescript/commonjs/utils/renderCaptureRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/safeExpoRouter.d.ts +23 -0
- package/lib/typescript/commonjs/utils/safeExpoRouter.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 +4 -3
- 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/lib/typescript/module/utils/devServer.d.ts +24 -0
- package/lib/typescript/module/utils/devServer.d.ts.map +1 -0
- package/lib/typescript/module/utils/index.d.ts +3 -2
- package/lib/typescript/module/utils/index.d.ts.map +1 -1
- package/lib/typescript/module/utils/renderCaptureRegistry.d.ts +52 -2
- package/lib/typescript/module/utils/renderCaptureRegistry.d.ts.map +1 -1
- package/lib/typescript/module/utils/safeExpoRouter.d.ts +23 -0
- package/lib/typescript/module/utils/safeExpoRouter.d.ts.map +1 -1
- package/package.json +4 -4
- package/scripts/detect-safe-area.js +1 -0
|
@@ -2,43 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
export { FeatureGate, UpgradePrompt, useFeatureGate, ProFeatureBanner, UpgradeModal, ProBadge, ProUpgradeModal } from "./FeatureGate.js";
|
|
4
4
|
export { LicenseEntryModal } from "./LicenseEntryModal.js";
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function loadLicenseModuleCentral() {
|
|
11
|
-
if (_licenseLoadAttempted) return;
|
|
12
|
-
_licenseLoadAttempted = true;
|
|
13
|
-
try {
|
|
14
|
-
const mod = require("@buoy-gg/license");
|
|
15
|
-
if (mod) {
|
|
16
|
-
_useIsPro = mod.useIsPro ?? null;
|
|
17
|
-
_useLicense = mod.useLicense ?? null;
|
|
18
|
-
}
|
|
19
|
-
} catch {
|
|
20
|
-
// License package not available - will use fallback
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get the useIsPro hook from @buoy-gg/license
|
|
26
|
-
* Falls back to () => false if license package is not available
|
|
27
|
-
*
|
|
28
|
-
* IMPORTANT: Use this instead of doing your own lazy loading in other packages
|
|
29
|
-
*/
|
|
30
|
-
export function getUseIsPro() {
|
|
31
|
-
loadLicenseModuleCentral();
|
|
32
|
-
return _useIsPro ?? (() => false);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get the useLicense hook from @buoy-gg/license
|
|
37
|
-
* Returns null if license package is not available
|
|
38
|
-
*
|
|
39
|
-
* IMPORTANT: Use this instead of doing your own lazy loading in other packages
|
|
40
|
-
*/
|
|
41
|
-
export function getUseLicense() {
|
|
42
|
-
loadLicenseModuleCentral();
|
|
43
|
-
return _useLicense;
|
|
44
|
-
}
|
|
5
|
+
export { FreeAccountPrompt, LOGIN_COMMAND } from "./FreeAccountPrompt.js";
|
|
6
|
+
// Lazy license-module access lives in ./licenseModule so tierLimits can use it
|
|
7
|
+
// without an import cycle. Re-exported here to keep the existing surface.
|
|
8
|
+
export { getUseIsPro, getUseLicense, getUseTier } from "./licenseModule.js";
|
|
9
|
+
export { TIER_LIMITS, useLimit, resolveEffectiveTier, freeKeyRaises } from "./tierLimits.js";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Centralized lazy loading of `@buoy-gg/license`.
|
|
5
|
+
*
|
|
6
|
+
* This is the SINGLE place any package resolves license hooks — tools must not
|
|
7
|
+
* `require("@buoy-gg/license")` themselves. Lives in its own module (rather
|
|
8
|
+
* than `./index`) so `tierLimits` can consume it without an import cycle.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Access tier. Mirrors BuoyTier in @buoy-gg/license. */
|
|
12
|
+
|
|
13
|
+
let _useIsPro = null;
|
|
14
|
+
let _useLicense = null;
|
|
15
|
+
let _useTier = null;
|
|
16
|
+
let _anonymousTierEnabled = null;
|
|
17
|
+
let _licenseLoadAttempted = false;
|
|
18
|
+
function loadLicenseModuleCentral() {
|
|
19
|
+
if (_licenseLoadAttempted) return;
|
|
20
|
+
_licenseLoadAttempted = true;
|
|
21
|
+
try {
|
|
22
|
+
const mod = require("@buoy-gg/license");
|
|
23
|
+
if (mod) {
|
|
24
|
+
_useIsPro = mod.useIsPro ?? null;
|
|
25
|
+
_useLicense = mod.useLicense ?? null;
|
|
26
|
+
_useTier = mod.useTier ?? null;
|
|
27
|
+
_anonymousTierEnabled = typeof mod.ANONYMOUS_TIER_ENABLED === "boolean" ? mod.ANONYMOUS_TIER_ENABLED : null;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// License package not available - will use fallback
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the useIsPro hook from @buoy-gg/license.
|
|
36
|
+
* Falls back to () => false if the license package is not available.
|
|
37
|
+
*
|
|
38
|
+
* IMPORTANT: Use this instead of doing your own lazy loading in other packages
|
|
39
|
+
*/
|
|
40
|
+
export function getUseIsPro() {
|
|
41
|
+
loadLicenseModuleCentral();
|
|
42
|
+
return _useIsPro ?? (() => false);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the useLicense hook from @buoy-gg/license.
|
|
47
|
+
* Returns null if the license package is not available.
|
|
48
|
+
*
|
|
49
|
+
* IMPORTANT: Use this instead of doing your own lazy loading in other packages
|
|
50
|
+
*/
|
|
51
|
+
export function getUseLicense() {
|
|
52
|
+
loadLicenseModuleCentral();
|
|
53
|
+
return _useLicense;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the useTier hook from @buoy-gg/license.
|
|
58
|
+
*
|
|
59
|
+
* Falls back to `"free"` — NOT `"anonymous"` — when the license package can't
|
|
60
|
+
* be resolved. That deliberately matches the existing `getUseIsPro()` fallback,
|
|
61
|
+
* which yields free-tier behavior today, and it picks the safer failure:
|
|
62
|
+
*
|
|
63
|
+
* - falling back to "anonymous" would let one bundler quirk silently cut every
|
|
64
|
+
* legitimate user's limits by ~80%;
|
|
65
|
+
* - falling back to "free" costs no revenue, because Pro still requires a key
|
|
66
|
+
* that a live license server validated — this path can never grant Pro.
|
|
67
|
+
*
|
|
68
|
+
* Reaching it at all requires `@buoy-gg/license` to be missing, and it is a
|
|
69
|
+
* hard dependency of `@buoy-gg/core`.
|
|
70
|
+
*/
|
|
71
|
+
export function getUseTier() {
|
|
72
|
+
loadLicenseModuleCentral();
|
|
73
|
+
return _useTier ?? (() => "free");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether the anonymous tier is switched on.
|
|
78
|
+
*
|
|
79
|
+
* The switch itself lives in @buoy-gg/license (see its config.ts) because the
|
|
80
|
+
* Weekend Pass change has to flip at the same moment as the reduced limits, and
|
|
81
|
+
* those live in different packages. Read through the same lazy loader as the
|
|
82
|
+
* hooks so a missing license package degrades gracefully rather than throwing
|
|
83
|
+
* at module load.
|
|
84
|
+
*
|
|
85
|
+
* Falls back to `false` — the anonymous tier off, everyone on free limits —
|
|
86
|
+
* which matches the `getUseTier()` fallback of "free". A build that cannot
|
|
87
|
+
* resolve the license package must never take limits AWAY from someone.
|
|
88
|
+
*/
|
|
89
|
+
export function isAnonymousTierEnabled() {
|
|
90
|
+
loadLicenseModuleCentral();
|
|
91
|
+
return _anonymousTierEnabled ?? false;
|
|
92
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-tier numeric limits — the ONLY thing that separates anonymous from free.
|
|
5
|
+
*
|
|
6
|
+
* Every binary Pro gate (copy-to-clipboard, state editing, storage mutations,
|
|
7
|
+
* navigation) is identical for both: locked. That keeps the tier split to one
|
|
8
|
+
* table instead of scattered conditionals.
|
|
9
|
+
*
|
|
10
|
+
* The governing rule: **the free column never moves.** Anonymous is carved out
|
|
11
|
+
* BELOW today's free tier — nothing is ever added to free in exchange for an
|
|
12
|
+
* account, because that would come straight out of the Pro upgrade.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getUseIsPro, getUseTier, isAnonymousTierEnabled } from "./licenseModule.js";
|
|
16
|
+
const LIMITS = {
|
|
17
|
+
// ── network ───────────────────────────────────────────────────────────────
|
|
18
|
+
"network.requests": {
|
|
19
|
+
anonymous: 5,
|
|
20
|
+
free: 25,
|
|
21
|
+
pro: Infinity
|
|
22
|
+
},
|
|
23
|
+
"network.saved": {
|
|
24
|
+
anonymous: 1,
|
|
25
|
+
free: 5,
|
|
26
|
+
pro: 50
|
|
27
|
+
},
|
|
28
|
+
// Already 1 for free, so 25% would be 0 — which deletes the feature rather
|
|
29
|
+
// than trialling it. Raising the FREE value to make room is not an option.
|
|
30
|
+
"network.overrideRules": {
|
|
31
|
+
anonymous: 1,
|
|
32
|
+
free: 1,
|
|
33
|
+
pro: 50
|
|
34
|
+
},
|
|
35
|
+
// ── storage ───────────────────────────────────────────────────────────────
|
|
36
|
+
"storage.events": {
|
|
37
|
+
anonymous: 5,
|
|
38
|
+
free: 25,
|
|
39
|
+
pro: Infinity
|
|
40
|
+
},
|
|
41
|
+
"storage.keys": {
|
|
42
|
+
anonymous: 5,
|
|
43
|
+
free: 25,
|
|
44
|
+
pro: Infinity
|
|
45
|
+
},
|
|
46
|
+
// Same floor-at-1 reasoning as network.overrideRules.
|
|
47
|
+
"storage.mmkvInstances": {
|
|
48
|
+
anonymous: 1,
|
|
49
|
+
free: 1,
|
|
50
|
+
pro: Infinity
|
|
51
|
+
},
|
|
52
|
+
// ── state tools ───────────────────────────────────────────────────────────
|
|
53
|
+
"redux.actions": {
|
|
54
|
+
anonymous: 5,
|
|
55
|
+
free: 25,
|
|
56
|
+
pro: Infinity
|
|
57
|
+
},
|
|
58
|
+
"zustand.changes": {
|
|
59
|
+
anonymous: 5,
|
|
60
|
+
free: 25,
|
|
61
|
+
pro: Infinity
|
|
62
|
+
},
|
|
63
|
+
"jotai.changes": {
|
|
64
|
+
anonymous: 5,
|
|
65
|
+
free: 25,
|
|
66
|
+
pro: Infinity
|
|
67
|
+
},
|
|
68
|
+
"events.entries": {
|
|
69
|
+
anonymous: 5,
|
|
70
|
+
free: 25,
|
|
71
|
+
pro: Infinity
|
|
72
|
+
},
|
|
73
|
+
// ── render / navigation ───────────────────────────────────────────────────
|
|
74
|
+
"highlightUpdates.components": {
|
|
75
|
+
anonymous: 5,
|
|
76
|
+
free: 25,
|
|
77
|
+
pro: Infinity
|
|
78
|
+
},
|
|
79
|
+
"highlightUpdates.historyEvents": {
|
|
80
|
+
anonymous: 1,
|
|
81
|
+
free: 3,
|
|
82
|
+
pro: Infinity
|
|
83
|
+
},
|
|
84
|
+
"highlightUpdates.detailEvents": {
|
|
85
|
+
anonymous: 2,
|
|
86
|
+
free: 5,
|
|
87
|
+
pro: Infinity
|
|
88
|
+
},
|
|
89
|
+
"routeEvents.entries": {
|
|
90
|
+
anonymous: 1,
|
|
91
|
+
free: 3,
|
|
92
|
+
pro: Infinity
|
|
93
|
+
},
|
|
94
|
+
"timeMachine.snapshots": {
|
|
95
|
+
anonymous: 1,
|
|
96
|
+
free: 2,
|
|
97
|
+
pro: Infinity
|
|
98
|
+
},
|
|
99
|
+
"perfMonitor.automationCases": {
|
|
100
|
+
anonymous: 1,
|
|
101
|
+
free: 3,
|
|
102
|
+
pro: Infinity
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
export const TIER_LIMITS = LIMITS;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The tier whose limits actually apply.
|
|
109
|
+
*
|
|
110
|
+
* `hasProAccess` is `useIsPro()`, which is true for a real Pro license OR the
|
|
111
|
+
* free Weekend Pass. Routing the Weekend Pass through here is what keeps
|
|
112
|
+
* today's behavior intact: weekend users get Pro limits, exactly as they do
|
|
113
|
+
* now. When the Weekend Pass is later gated to key-holders, that change lands
|
|
114
|
+
* here for free — no limit call site has to know about it.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveEffectiveTier(tier, hasProAccess) {
|
|
117
|
+
if (hasProAccess) return "pro";
|
|
118
|
+
// The switch lives in @buoy-gg/license so that this and the Weekend Pass
|
|
119
|
+
// change flip together — see isAnonymousTierEnabled().
|
|
120
|
+
if (!isAnonymousTierEnabled() && tier === "anonymous") return "free";
|
|
121
|
+
return tier;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The limit for the current tier.
|
|
126
|
+
*
|
|
127
|
+
* Replaces the per-package `const FREE_TIER_X = 25` + `isPro ? Infinity : X`
|
|
128
|
+
* pattern. `Infinity` is returned for uncapped tiers and is safe to use
|
|
129
|
+
* directly in `slice(0, limit)` and `length >= limit` comparisons.
|
|
130
|
+
*/
|
|
131
|
+
export function useLimit(key) {
|
|
132
|
+
const hasProAccess = getUseIsPro()();
|
|
133
|
+
const tier = getUseTier()();
|
|
134
|
+
return TIER_LIMITS[key][resolveEffectiveTier(tier, hasProAccess)];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Whether claiming a FREE key would actually raise this limit right now.
|
|
139
|
+
*
|
|
140
|
+
* The anonymous upsell must never promise something a free key won't deliver.
|
|
141
|
+
* Two ways it could lie, and this closes both:
|
|
142
|
+
*
|
|
143
|
+
* - **Pro-exclusive features.** Copy-to-clipboard, state editing, storage
|
|
144
|
+
* mutations and navigation are locked for anonymous AND free alike. Telling
|
|
145
|
+
* an anonymous user a free key opens them would be false.
|
|
146
|
+
* - **Limits that are already equal.** Override rules and MMKV instances sit
|
|
147
|
+
* at 1 for both tiers, so a key changes nothing there either.
|
|
148
|
+
*
|
|
149
|
+
* It also respects `ANONYMOUS_TIER_ENABLED`: while the anonymous tier is off,
|
|
150
|
+
* an anonymous user is *already* getting the free limits, so there is nothing
|
|
151
|
+
* to claim and this correctly returns false everywhere. That falls out of
|
|
152
|
+
* asking `resolveEffectiveTier` rather than reading the table directly, so the
|
|
153
|
+
* switch and the copy can never disagree.
|
|
154
|
+
*/
|
|
155
|
+
export function freeKeyRaises(key) {
|
|
156
|
+
const limits = TIER_LIMITS[key];
|
|
157
|
+
return limits[resolveEffectiveTier("anonymous", false)] < limits[resolveEffectiveTier("free", false)];
|
|
158
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The ‹ prev / next › strip for auditioning backgrounds.
|
|
5
|
+
*
|
|
6
|
+
* This is a REVIEW control, not shipped chrome: it exists so the whole
|
|
7
|
+
* catalogue can be walked without a rebuild, with the cost of each preset shown
|
|
8
|
+
* next to its name. Mount it at the top of a tool's body while evaluating, and
|
|
9
|
+
* pull it once a winner is picked (or keep it behind a setting).
|
|
10
|
+
*
|
|
11
|
+
* It writes to the shared `backgroundStore`, so stepping here changes the
|
|
12
|
+
* background in every tool at once.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { memo } from "react";
|
|
16
|
+
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
17
|
+
import { ChevronLeft, ChevronRight, Palette } from "../../icons/lucide-icons.js";
|
|
18
|
+
import { macOSColors } from "../gameUI/constants/macOSDesignSystemColors.js";
|
|
19
|
+
import { useToolBackground } from "./backgroundStore.js";
|
|
20
|
+
import { getPreset } from "./registry.js";
|
|
21
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
22
|
+
export const BackgroundSwitcher = /*#__PURE__*/memo(function BackgroundSwitcher() {
|
|
23
|
+
const {
|
|
24
|
+
id,
|
|
25
|
+
index,
|
|
26
|
+
count,
|
|
27
|
+
motion,
|
|
28
|
+
next,
|
|
29
|
+
previous,
|
|
30
|
+
setMotion
|
|
31
|
+
} = useToolBackground();
|
|
32
|
+
const preset = getPreset(id);
|
|
33
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
34
|
+
style: styles.root,
|
|
35
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
36
|
+
onPress: previous,
|
|
37
|
+
style: styles.step,
|
|
38
|
+
hitSlop: HIT_SLOP,
|
|
39
|
+
accessibilityLabel: "Previous background",
|
|
40
|
+
"sentry-label": "ignore background previous",
|
|
41
|
+
children: /*#__PURE__*/_jsx(ChevronLeft, {
|
|
42
|
+
size: 16,
|
|
43
|
+
color: macOSColors.text.secondary
|
|
44
|
+
})
|
|
45
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
46
|
+
style: styles.center,
|
|
47
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
48
|
+
style: styles.titleRow,
|
|
49
|
+
children: [/*#__PURE__*/_jsx(Palette, {
|
|
50
|
+
size: 11,
|
|
51
|
+
color: macOSColors.semantic.info
|
|
52
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
53
|
+
style: styles.title,
|
|
54
|
+
numberOfLines: 1,
|
|
55
|
+
children: preset.label
|
|
56
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
57
|
+
style: styles.counter,
|
|
58
|
+
children: [index + 1, "/", count]
|
|
59
|
+
})]
|
|
60
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
61
|
+
style: styles.blurb,
|
|
62
|
+
numberOfLines: 1,
|
|
63
|
+
children: preset.blurb
|
|
64
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
65
|
+
style: styles.cost,
|
|
66
|
+
numberOfLines: 1,
|
|
67
|
+
children: [preset.nodes === 0 ? "0 animated nodes" : `${preset.nodes} native node${preset.nodes === 1 ? "" : "s"}`, " · ", "~", preset.views, " static views"]
|
|
68
|
+
})]
|
|
69
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
70
|
+
onPress: () => setMotion(!motion),
|
|
71
|
+
style: [styles.motion, motion ? styles.motionOn : null],
|
|
72
|
+
hitSlop: HIT_SLOP,
|
|
73
|
+
accessibilityLabel: motion ? "Turn motion off" : "Turn motion on",
|
|
74
|
+
"sentry-label": "ignore background motion",
|
|
75
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
76
|
+
style: [styles.motionText, motion ? styles.motionTextOn : null],
|
|
77
|
+
children: motion ? "MOTION" : "STATIC"
|
|
78
|
+
})
|
|
79
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
80
|
+
onPress: next,
|
|
81
|
+
style: styles.step,
|
|
82
|
+
hitSlop: HIT_SLOP,
|
|
83
|
+
accessibilityLabel: "Next background",
|
|
84
|
+
"sentry-label": "ignore background next",
|
|
85
|
+
children: /*#__PURE__*/_jsx(ChevronRight, {
|
|
86
|
+
size: 16,
|
|
87
|
+
color: macOSColors.text.secondary
|
|
88
|
+
})
|
|
89
|
+
})]
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
const HIT_SLOP = {
|
|
93
|
+
top: 8,
|
|
94
|
+
bottom: 8,
|
|
95
|
+
left: 8,
|
|
96
|
+
right: 8
|
|
97
|
+
};
|
|
98
|
+
const styles = StyleSheet.create({
|
|
99
|
+
root: {
|
|
100
|
+
flexDirection: "row",
|
|
101
|
+
alignItems: "center",
|
|
102
|
+
gap: 8,
|
|
103
|
+
paddingHorizontal: 8,
|
|
104
|
+
paddingVertical: 6,
|
|
105
|
+
marginHorizontal: 8,
|
|
106
|
+
marginTop: 6,
|
|
107
|
+
marginBottom: 2,
|
|
108
|
+
borderRadius: 10,
|
|
109
|
+
borderWidth: 1,
|
|
110
|
+
borderColor: macOSColors.border.default,
|
|
111
|
+
// Deliberately translucent: the strip sits ON the background it is
|
|
112
|
+
// previewing, so you can see the field through it.
|
|
113
|
+
backgroundColor: "rgba(26,26,28,0.72)"
|
|
114
|
+
},
|
|
115
|
+
step: {
|
|
116
|
+
width: 28,
|
|
117
|
+
height: 28,
|
|
118
|
+
borderRadius: 8,
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
justifyContent: "center",
|
|
121
|
+
backgroundColor: "rgba(38,38,42,0.8)",
|
|
122
|
+
borderWidth: 1,
|
|
123
|
+
borderColor: macOSColors.border.default
|
|
124
|
+
},
|
|
125
|
+
center: {
|
|
126
|
+
flex: 1,
|
|
127
|
+
minWidth: 0
|
|
128
|
+
},
|
|
129
|
+
titleRow: {
|
|
130
|
+
flexDirection: "row",
|
|
131
|
+
alignItems: "center",
|
|
132
|
+
gap: 5
|
|
133
|
+
},
|
|
134
|
+
title: {
|
|
135
|
+
color: macOSColors.text.primary,
|
|
136
|
+
fontSize: 12,
|
|
137
|
+
fontWeight: "600"
|
|
138
|
+
},
|
|
139
|
+
counter: {
|
|
140
|
+
color: macOSColors.text.muted,
|
|
141
|
+
fontSize: 10,
|
|
142
|
+
fontVariant: ["tabular-nums"]
|
|
143
|
+
},
|
|
144
|
+
blurb: {
|
|
145
|
+
color: macOSColors.text.secondary,
|
|
146
|
+
fontSize: 10,
|
|
147
|
+
marginTop: 1
|
|
148
|
+
},
|
|
149
|
+
cost: {
|
|
150
|
+
color: macOSColors.text.icon,
|
|
151
|
+
fontSize: 9,
|
|
152
|
+
marginTop: 1,
|
|
153
|
+
fontVariant: ["tabular-nums"]
|
|
154
|
+
},
|
|
155
|
+
motion: {
|
|
156
|
+
paddingHorizontal: 7,
|
|
157
|
+
paddingVertical: 4,
|
|
158
|
+
borderRadius: 6,
|
|
159
|
+
borderWidth: 1,
|
|
160
|
+
borderColor: macOSColors.border.default,
|
|
161
|
+
backgroundColor: "rgba(38,38,42,0.8)"
|
|
162
|
+
},
|
|
163
|
+
motionOn: {
|
|
164
|
+
borderColor: macOSColors.semantic.info + "55",
|
|
165
|
+
backgroundColor: macOSColors.semantic.infoBackground
|
|
166
|
+
},
|
|
167
|
+
motionText: {
|
|
168
|
+
fontSize: 9,
|
|
169
|
+
fontWeight: "700",
|
|
170
|
+
letterSpacing: 0.6,
|
|
171
|
+
color: macOSColors.text.muted
|
|
172
|
+
},
|
|
173
|
+
motionTextOn: {
|
|
174
|
+
color: macOSColors.semantic.info
|
|
175
|
+
}
|
|
176
|
+
});
|
|
@@ -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,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drop-in animated backdrop for a Buoy tool surface.
|
|
5
|
+
*
|
|
6
|
+
* Mount it as the FIRST child of a tool's body container and everything after
|
|
7
|
+
* it draws on top:
|
|
8
|
+
*
|
|
9
|
+
* <View style={styles.container}>
|
|
10
|
+
* <ToolBackground />
|
|
11
|
+
* ...screens...
|
|
12
|
+
* </View>
|
|
13
|
+
*
|
|
14
|
+
* It is `position: absolute` + `pointerEvents: "none"`, so it never affects
|
|
15
|
+
* layout and never eats a touch. The preset comes from the shared persisted
|
|
16
|
+
* selection (`backgroundStore`), so switching it in one tool switches it in all
|
|
17
|
+
* of them.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { memo, useCallback, useState } from "react";
|
|
21
|
+
import { View } from "react-native";
|
|
22
|
+
import { absoluteFill } from "../../utils/absoluteFill.js";
|
|
23
|
+
import { useToolBackground } from "./backgroundStore.js";
|
|
24
|
+
import { getPreset } from "./registry.js";
|
|
25
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
26
|
+
/**
|
|
27
|
+
* Round the measured size into 8px buckets. Presets lay out from width/height,
|
|
28
|
+
* so an unbucketed measurement would rebuild the whole field on every frame of
|
|
29
|
+
* a modal resize drag; at this granularity nobody can see the difference and a
|
|
30
|
+
* drag costs a handful of rebuilds instead of hundreds.
|
|
31
|
+
*/
|
|
32
|
+
const bucket = n => Math.max(0, Math.ceil(n / 8) * 8);
|
|
33
|
+
export const ToolBackground = /*#__PURE__*/memo(function ToolBackground({
|
|
34
|
+
id,
|
|
35
|
+
motion,
|
|
36
|
+
opacity = 1
|
|
37
|
+
}) {
|
|
38
|
+
const shared = useToolBackground();
|
|
39
|
+
const [size, setSize] = useState({
|
|
40
|
+
width: 0,
|
|
41
|
+
height: 0
|
|
42
|
+
});
|
|
43
|
+
const onLayout = useCallback(event => {
|
|
44
|
+
const {
|
|
45
|
+
width,
|
|
46
|
+
height
|
|
47
|
+
} = event.nativeEvent.layout;
|
|
48
|
+
const next = {
|
|
49
|
+
width: bucket(width),
|
|
50
|
+
height: bucket(height)
|
|
51
|
+
};
|
|
52
|
+
setSize(prev => prev.width === next.width && prev.height === next.height ? prev : next);
|
|
53
|
+
}, []);
|
|
54
|
+
const preset = getPreset(id ?? shared.id);
|
|
55
|
+
const animated = motion ?? shared.motion;
|
|
56
|
+
const Variant = preset.Component;
|
|
57
|
+
const ready = size.width > 0 && size.height > 0;
|
|
58
|
+
return /*#__PURE__*/_jsx(View, {
|
|
59
|
+
style: [absoluteFill, opacity === 1 ? null : {
|
|
60
|
+
opacity
|
|
61
|
+
}],
|
|
62
|
+
pointerEvents: "none",
|
|
63
|
+
onLayout: onLayout,
|
|
64
|
+
children: Variant && ready ? /*#__PURE__*/_jsx(Variant, {
|
|
65
|
+
width: size.width,
|
|
66
|
+
height: size.height,
|
|
67
|
+
animated: animated
|
|
68
|
+
}) : null
|
|
69
|
+
});
|
|
70
|
+
});
|