@buoy-gg/shared-ui 7.0.32 → 7.0.33
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 +13 -6
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/storage/devToolsStorageKeys.js +12 -0
- package/lib/commonjs/ui/components/EmptyState.js +10 -2
- package/lib/commonjs/ui/components/ToolEmptyState.js +154 -0
- package/lib/commonjs/ui/components/WindowControls.js +8 -5
- package/lib/commonjs/ui/components/index.js +13 -0
- package/lib/commonjs/ui/index.js +12 -0
- package/lib/commonjs/ui/otto/OttoSprite.js +510 -0
- package/lib/commonjs/ui/otto/index.js +38 -0
- package/lib/commonjs/ui/otto/ottoEngine.js +2734 -0
- package/lib/commonjs/ui/otto/ottoSheet.js +191 -0
- package/lib/module/JsModal.js +13 -6
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/storage/devToolsStorageKeys.js +12 -0
- package/lib/module/ui/components/EmptyState.js +10 -2
- package/lib/module/ui/components/ToolEmptyState.js +149 -0
- package/lib/module/ui/components/WindowControls.js +8 -5
- package/lib/module/ui/components/index.js +3 -0
- package/lib/module/ui/index.js +4 -1
- package/lib/module/ui/otto/OttoSprite.js +496 -0
- package/lib/module/ui/otto/index.js +20 -0
- package/lib/module/ui/otto/ottoEngine.js +2726 -0
- package/lib/module/ui/otto/ottoSheet.js +180 -0
- package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts +12 -0
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/EmptyState.d.ts +14 -1
- package/lib/typescript/commonjs/ui/components/EmptyState.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ToolEmptyState.d.ts +113 -0
- package/lib/typescript/commonjs/ui/components/ToolEmptyState.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/WindowControls.d.ts +7 -1
- package/lib/typescript/commonjs/ui/components/WindowControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/index.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/index.d.ts.map +1 -1
- 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/ui/otto/OttoSprite.d.ts +103 -0
- package/lib/typescript/commonjs/ui/otto/OttoSprite.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/otto/index.d.ts +19 -0
- package/lib/typescript/commonjs/ui/otto/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/otto/ottoEngine.d.ts +2205 -0
- package/lib/typescript/commonjs/ui/otto/ottoEngine.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/otto/ottoSheet.d.ts +73 -0
- package/lib/typescript/commonjs/ui/otto/ottoSheet.d.ts.map +1 -0
- package/lib/typescript/module/JsModal.d.ts.map +1 -1
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts +12 -0
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/EmptyState.d.ts +14 -1
- package/lib/typescript/module/ui/components/EmptyState.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ToolEmptyState.d.ts +113 -0
- package/lib/typescript/module/ui/components/ToolEmptyState.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/WindowControls.d.ts +7 -1
- package/lib/typescript/module/ui/components/WindowControls.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/index.d.ts +2 -0
- package/lib/typescript/module/ui/components/index.d.ts.map +1 -1
- 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/ui/otto/OttoSprite.d.ts +103 -0
- package/lib/typescript/module/ui/otto/OttoSprite.d.ts.map +1 -0
- package/lib/typescript/module/ui/otto/index.d.ts +19 -0
- package/lib/typescript/module/ui/otto/index.d.ts.map +1 -0
- package/lib/typescript/module/ui/otto/ottoEngine.d.ts +2205 -0
- package/lib/typescript/module/ui/otto/ottoEngine.d.ts.map +1 -0
- package/lib/typescript/module/ui/otto/ottoSheet.d.ts +73 -0
- package/lib/typescript/module/ui/otto/ottoSheet.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.WATER_ROWS = exports.WATER_ROW0 = exports.WATER_FRAMES = exports.WATER_CELLS = exports.TRANSPARENT = exports.MAX_PERIOD = exports.CELLS = void 0;
|
|
7
|
+
exports.cycleIndexes = cycleIndexes;
|
|
8
|
+
exports.flatColors = flatColors;
|
|
9
|
+
exports.gridKey = gridKey;
|
|
10
|
+
exports.ottoCycle = ottoCycle;
|
|
11
|
+
exports.ottoSheets = ottoSheets;
|
|
12
|
+
exports.rectsOf = rectsOf;
|
|
13
|
+
var _ottoEngine = require("./ottoEngine.js");
|
|
14
|
+
/**
|
|
15
|
+
* Otto's precomputed animation.
|
|
16
|
+
*
|
|
17
|
+
* Every state is a short loop (48 ticks at most). This module renders that
|
|
18
|
+
* loop once, splits it into a BODY layer and a 7-frame WATER layer (they
|
|
19
|
+
* composite back pixel-for-pixel: compose() only paints water into empty
|
|
20
|
+
* cells and blanks a submerged body below the waterline), dedupes the
|
|
21
|
+
* frames, and turns each distinct frame into merged rectangles. The shipping
|
|
22
|
+
* <Otto/> mounts those rectangles ONCE as a strip of static Views and slides
|
|
23
|
+
* a window over the strip on the UI thread.
|
|
24
|
+
*
|
|
25
|
+
* Why: the buoy-optimize rounds in OTTO_PERF_PLAN.md showed that any renderer
|
|
26
|
+
* re-creating Views per tick pegs the JS thread (5 FPS at 34 sprites, dropped
|
|
27
|
+
* UI frames even at 1 sprite), while "mount once, move a window natively"
|
|
28
|
+
* costs nothing per tick and keeps moving while JS is blocked.
|
|
29
|
+
*
|
|
30
|
+
* Everything here is pure `react-native`-free TypeScript, cached per state.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const CELLS = exports.CELLS = _ottoEngine.OTTO_GRID * _ottoEngine.OTTO_GRID;
|
|
34
|
+
const WATER_ROW0 = exports.WATER_ROW0 = 13;
|
|
35
|
+
const WATER_ROWS = exports.WATER_ROWS = _ottoEngine.OTTO_GRID - WATER_ROW0;
|
|
36
|
+
const WATER_CELLS = exports.WATER_CELLS = WATER_ROWS * _ottoEngine.OTTO_GRID;
|
|
37
|
+
/** The wave is a function of floor(t/2) mod 7. */
|
|
38
|
+
const WATER_FRAMES = exports.WATER_FRAMES = 7;
|
|
39
|
+
const TRANSPARENT = exports.TRANSPARENT = "transparent";
|
|
40
|
+
/** Longest state cycle in ticks (idle and sleep are 48). */
|
|
41
|
+
const MAX_PERIOD = exports.MAX_PERIOD = 48;
|
|
42
|
+
function gridKey(grid) {
|
|
43
|
+
return grid.map(r => r.join("")).join("|");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Flat 256 colours, row-major, `transparent` for empty cells. */
|
|
47
|
+
function flatColors(grid) {
|
|
48
|
+
const out = new Array(CELLS);
|
|
49
|
+
for (let y = 0; y < _ottoEngine.OTTO_GRID; y++) {
|
|
50
|
+
const row = grid[y];
|
|
51
|
+
for (let x = 0; x < _ottoEngine.OTTO_GRID; x++) out[y * _ottoEngine.OTTO_GRID + x] = _ottoEngine.OTTO_PALETTE[row[x]] ?? TRANSPARENT;
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------- cycle ---
|
|
57
|
+
|
|
58
|
+
const cycleCache = new Map();
|
|
59
|
+
function ottoCycle(state, keyline) {
|
|
60
|
+
const cacheKey = `${state}|${keyline ? 1 : 0}`;
|
|
61
|
+
const hit = cycleCache.get(cacheKey);
|
|
62
|
+
if (hit) return hit;
|
|
63
|
+
const keys = [];
|
|
64
|
+
const grids = [];
|
|
65
|
+
for (let t = 0; t < MAX_PERIOD * 2; t++) {
|
|
66
|
+
const g = (0, _ottoEngine.renderOttoFrame)(state, t, {
|
|
67
|
+
water: false,
|
|
68
|
+
keyline
|
|
69
|
+
});
|
|
70
|
+
grids.push(g);
|
|
71
|
+
keys.push(gridKey(g));
|
|
72
|
+
}
|
|
73
|
+
let period = MAX_PERIOD;
|
|
74
|
+
for (let P = 1; P <= MAX_PERIOD; P++) {
|
|
75
|
+
let ok = true;
|
|
76
|
+
for (let t = 0; t < MAX_PERIOD; t++) {
|
|
77
|
+
if (keys[t] !== keys[t + P]) {
|
|
78
|
+
ok = false;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (ok) {
|
|
83
|
+
period = P;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const body = grids.slice(0, period).map(flatColors);
|
|
88
|
+
const water = [];
|
|
89
|
+
for (let w = 0; w < WATER_FRAMES; w++) {
|
|
90
|
+
const flat = [];
|
|
91
|
+
for (const row of (0, _ottoEngine.ottoWaterRows)(w * 2)) for (const k of row) flat.push(_ottoEngine.OTTO_PALETTE[k] ?? TRANSPARENT);
|
|
92
|
+
water.push(flat);
|
|
93
|
+
}
|
|
94
|
+
const cycle = {
|
|
95
|
+
period,
|
|
96
|
+
body,
|
|
97
|
+
water
|
|
98
|
+
};
|
|
99
|
+
cycleCache.set(cacheKey, cycle);
|
|
100
|
+
return cycle;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Frame indexes for a cycle at local tick `t`. */
|
|
104
|
+
function cycleIndexes(t, period) {
|
|
105
|
+
const local = Math.max(0, t);
|
|
106
|
+
return {
|
|
107
|
+
bi: local % period,
|
|
108
|
+
wi: Math.floor(local / 2) % WATER_FRAMES
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------- rects ---
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Same-colour runs per row, then runs merged with the identical run directly
|
|
116
|
+
* below them — roughly a third fewer Views than row runs alone. Covers
|
|
117
|
+
* exactly the non-transparent cells, never overlaps (proved offline over
|
|
118
|
+
* every frame of every state).
|
|
119
|
+
*/
|
|
120
|
+
function rectsOf(colors, cols, rows) {
|
|
121
|
+
const open = [];
|
|
122
|
+
const done = [];
|
|
123
|
+
for (let y = 0; y < rows; y++) {
|
|
124
|
+
const rowRuns = [];
|
|
125
|
+
let x = 0;
|
|
126
|
+
while (x < cols) {
|
|
127
|
+
const color = colors[y * cols + x];
|
|
128
|
+
let w = 1;
|
|
129
|
+
while (x + w < cols && colors[y * cols + x + w] === color) w++;
|
|
130
|
+
if (color !== TRANSPARENT) rowRuns.push({
|
|
131
|
+
x,
|
|
132
|
+
y,
|
|
133
|
+
w,
|
|
134
|
+
h: 1,
|
|
135
|
+
color
|
|
136
|
+
});
|
|
137
|
+
x += w;
|
|
138
|
+
}
|
|
139
|
+
const next = [];
|
|
140
|
+
for (const r of rowRuns) {
|
|
141
|
+
const above = open.find(o => o.x === r.x && o.w === r.w && o.color === r.color && o.y + o.h === r.y);
|
|
142
|
+
if (above) {
|
|
143
|
+
above.h += 1;
|
|
144
|
+
next.push(above);
|
|
145
|
+
} else {
|
|
146
|
+
next.push(r);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const o of open) if (!next.includes(o)) done.push(o);
|
|
150
|
+
open.length = 0;
|
|
151
|
+
open.push(...next);
|
|
152
|
+
}
|
|
153
|
+
done.push(...open);
|
|
154
|
+
return done;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------- sheets --
|
|
158
|
+
|
|
159
|
+
/** One layer as a strip of distinct frames, and which frame each tick shows. */
|
|
160
|
+
|
|
161
|
+
const sheetCache = new Map();
|
|
162
|
+
function stripOf(frames, cols, rows) {
|
|
163
|
+
const keys = frames.map(f => f.join(","));
|
|
164
|
+
const indexOf = new Map();
|
|
165
|
+
const distinct = [];
|
|
166
|
+
keys.forEach((k, i) => {
|
|
167
|
+
if (!indexOf.has(k)) {
|
|
168
|
+
indexOf.set(k, distinct.length);
|
|
169
|
+
distinct.push(rectsOf(frames[i], cols, rows));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
colOf: keys.map(k => indexOf.get(k) ?? 0),
|
|
174
|
+
frames: distinct
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Both layers of a state as strips of rectangles. Pure; cached per state. */
|
|
179
|
+
function ottoSheets(state, keyline) {
|
|
180
|
+
const key = `${state}|${keyline ? 1 : 0}`;
|
|
181
|
+
const hit = sheetCache.get(key);
|
|
182
|
+
if (hit) return hit;
|
|
183
|
+
const cycle = ottoCycle(state, keyline);
|
|
184
|
+
const built = {
|
|
185
|
+
period: cycle.period,
|
|
186
|
+
body: stripOf(cycle.body, _ottoEngine.OTTO_GRID, _ottoEngine.OTTO_GRID),
|
|
187
|
+
water: stripOf(cycle.water, _ottoEngine.OTTO_GRID, WATER_ROWS)
|
|
188
|
+
};
|
|
189
|
+
sheetCache.set(key, built);
|
|
190
|
+
return built;
|
|
191
|
+
}
|
package/lib/module/JsModal.js
CHANGED
|
@@ -433,15 +433,19 @@ const JsModalComponent = ({
|
|
|
433
433
|
return () => clearTimeout(timeoutId);
|
|
434
434
|
}, [mode, panelHeight, dimensions, visible, persistenceKey, enablePersistence, isStateLoaded]);
|
|
435
435
|
|
|
436
|
-
//
|
|
436
|
+
// Seed the external height ONCE. This used to re-run when `isResizing`
|
|
437
|
+
// flipped, which snapped an externally-controlled sheet back to
|
|
438
|
+
// initialHeight the moment a drag-resize ended (the release handler had
|
|
439
|
+
// just written the real height). The host owns the value after the seed;
|
|
440
|
+
// persistence restore above may overwrite the seed, which is correct.
|
|
441
|
+
const externalHeightSeeded = useRef(false);
|
|
437
442
|
useEffect(() => {
|
|
438
|
-
|
|
439
|
-
|
|
443
|
+
if (externalAnimatedHeight && !externalHeightSeeded.current) {
|
|
444
|
+
externalHeightSeeded.current = true;
|
|
440
445
|
currentHeightRef.current = initialHeight;
|
|
441
446
|
externalAnimatedHeight.setValue(initialHeight);
|
|
442
|
-
// Set external height
|
|
443
447
|
}
|
|
444
|
-
}, [externalAnimatedHeight, initialHeight
|
|
448
|
+
}, [externalAnimatedHeight, initialHeight]);
|
|
445
449
|
|
|
446
450
|
// Update refs when dimensions change
|
|
447
451
|
useEffect(() => {
|
|
@@ -531,11 +535,14 @@ const JsModalComponent = ({
|
|
|
531
535
|
onModeChangeRef.current?.(savedState.mode);
|
|
532
536
|
}
|
|
533
537
|
|
|
534
|
-
// Restore bottom sheet height
|
|
538
|
+
// Restore bottom sheet height. The external animated height (when a
|
|
539
|
+
// host passes one) IS the rendered height, so it must be restored
|
|
540
|
+
// too or a persisted sheet always reopens at initialHeight.
|
|
535
541
|
if (savedState.panelHeight) {
|
|
536
542
|
setPanelHeight(savedState.panelHeight);
|
|
537
543
|
currentHeightRef.current = savedState.panelHeight;
|
|
538
544
|
animatedBottomPosition.setValue(savedState.panelHeight);
|
|
545
|
+
externalAnimatedHeight?.setValue(savedState.panelHeight);
|
|
539
546
|
}
|
|
540
547
|
|
|
541
548
|
// Restore floating dimensions and position
|
|
@@ -250,6 +250,18 @@ export const devToolsStorageKeys = {
|
|
|
250
250
|
pausedAt: () => `${devToolsStorageKeys.events.root()}_paused_at`,
|
|
251
251
|
/** Copy/export settings */
|
|
252
252
|
copySettings: () => `${devToolsStorageKeys.events.root()}_copy_settings`
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* Desktop/MCP sync identity
|
|
256
|
+
*/
|
|
257
|
+
sync: {
|
|
258
|
+
root: () => `${devToolsStorageKeys.base}_sync`,
|
|
259
|
+
/**
|
|
260
|
+
* The per-install id minted the first time this app dials the broker.
|
|
261
|
+
* It is what makes two devices on the same build register as two
|
|
262
|
+
* devices — see core's deviceIdentity.ts.
|
|
263
|
+
*/
|
|
264
|
+
deviceId: () => `${devToolsStorageKeys.sync.root()}_device_id`
|
|
253
265
|
}
|
|
254
266
|
};
|
|
255
267
|
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { StyleSheet, Text, View, TouchableOpacity } from "react-native";
|
|
4
4
|
import { macOSColors } from "../gameUI/constants/macOSDesignSystemColors.js";
|
|
5
|
+
import { Otto } from "../otto/index.js";
|
|
5
6
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
6
7
|
/**
|
|
7
8
|
* Reusable empty state component for consistent empty/no-data displays
|
|
8
9
|
*/
|
|
9
10
|
export function EmptyState({
|
|
11
|
+
otto,
|
|
10
12
|
title,
|
|
11
13
|
description,
|
|
12
14
|
icon: Icon,
|
|
@@ -21,13 +23,19 @@ export function EmptyState({
|
|
|
21
23
|
style: containerStyle,
|
|
22
24
|
children: /*#__PURE__*/_jsxs(View, {
|
|
23
25
|
style: styles.content,
|
|
24
|
-
children: [
|
|
26
|
+
children: [otto ? /*#__PURE__*/_jsx(View, {
|
|
27
|
+
style: styles.iconContainer,
|
|
28
|
+
children: /*#__PURE__*/_jsx(Otto, {
|
|
29
|
+
state: otto,
|
|
30
|
+
pixelSize: Math.max(2, Math.round(iconSize / 16))
|
|
31
|
+
})
|
|
32
|
+
}) : Icon ? /*#__PURE__*/_jsx(View, {
|
|
25
33
|
style: styles.iconContainer,
|
|
26
34
|
children: /*#__PURE__*/_jsx(Icon, {
|
|
27
35
|
size: iconSize,
|
|
28
36
|
color: iconColor
|
|
29
37
|
})
|
|
30
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
38
|
+
}) : null, /*#__PURE__*/_jsx(Text, {
|
|
31
39
|
style: styles.title,
|
|
32
40
|
children: title
|
|
33
41
|
}), description && /*#__PURE__*/_jsx(Text, {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one "nothing here" panel, for every tool that shows a list.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS
|
|
7
|
+
*
|
|
8
|
+
* Every capture tool — network, events, highlight-updates, storage, zustand,
|
|
9
|
+
* redux, jotai, console — renders the same panel with the same four causes and
|
|
10
|
+
* different nouns. Written out per tool, that produced four copies of the same
|
|
11
|
+
* layout (each of which had to be re-inset by hand the first time a sentence
|
|
12
|
+
* was longer than "No events yet"), and four independent guesses at the
|
|
13
|
+
* PRECEDENCE between those causes. The guesses disagreed, and the disagreements
|
|
14
|
+
* were bugs:
|
|
15
|
+
*
|
|
16
|
+
* - A search that matched nothing showed "Enable tracking to start
|
|
17
|
+
* capturing" — pointing at a toggle that was already on.
|
|
18
|
+
* - Turning every source chip off showed "Events will appear here as they
|
|
19
|
+
* occur" — inviting you to go generate events that were already in the
|
|
20
|
+
* store, behind your own filter.
|
|
21
|
+
* - Typing into a search box while the store was empty produced no
|
|
22
|
+
* acknowledgement of the query at all, which reads as a dead search box.
|
|
23
|
+
*
|
|
24
|
+
* So the precedence lives here, once, and each tool supplies only what is
|
|
25
|
+
* genuinely its own: the noun, and what "switched on but waiting" looks like
|
|
26
|
+
* for it (network fishes, events listens, the highlighter stares).
|
|
27
|
+
*
|
|
28
|
+
* SPLIT IN TWO ON PURPOSE
|
|
29
|
+
*
|
|
30
|
+
* `resolveListEmptyState` is a pure function returning `{ otto, title,
|
|
31
|
+
* message }`, and `ToolEmptyState` renders that object. Tools with a cause
|
|
32
|
+
* nobody else has — storage's "no issues found" (which is a PASS, not a
|
|
33
|
+
* failure), zustand's unregistered store — resolve the common cases and
|
|
34
|
+
* substitute their own, or skip the resolver and hand the shell a literal.
|
|
35
|
+
* Adding a mascot to every tool at once is then one edit in this file, which is
|
|
36
|
+
* also what makes the Flutter and Swift ports one component to mirror instead
|
|
37
|
+
* of eight.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
41
|
+
import { Otto } from "../otto/index.js";
|
|
42
|
+
import { night } from "../night/nightTheme.js";
|
|
43
|
+
|
|
44
|
+
/** What a "nothing here" panel says. */
|
|
45
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
46
|
+
/**
|
|
47
|
+
* The panel itself: Otto, a heading, a sentence. Nothing else — this only ever
|
|
48
|
+
* renders where the alternative is a blank body, so it has no actions and no
|
|
49
|
+
* decoration to compete with the list it stands in for.
|
|
50
|
+
*/
|
|
51
|
+
export function ToolEmptyState({
|
|
52
|
+
otto,
|
|
53
|
+
title,
|
|
54
|
+
message,
|
|
55
|
+
testID,
|
|
56
|
+
style
|
|
57
|
+
}) {
|
|
58
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
59
|
+
style: [styles.container, style],
|
|
60
|
+
testID: testID,
|
|
61
|
+
children: [/*#__PURE__*/_jsx(Otto, {
|
|
62
|
+
state: otto,
|
|
63
|
+
pixelSize: 2
|
|
64
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
65
|
+
style: styles.title,
|
|
66
|
+
testID: testID ? `${testID}-title` : undefined,
|
|
67
|
+
numberOfLines: 2,
|
|
68
|
+
children: title
|
|
69
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
70
|
+
style: styles.message,
|
|
71
|
+
testID: testID ? `${testID}-text` : undefined,
|
|
72
|
+
children: message
|
|
73
|
+
})]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Which "nothing here" this is, in the order the facts answer the question.
|
|
78
|
+
*
|
|
79
|
+
* A typed query first (see `search`), then the user's filters, then the
|
|
80
|
+
* capture switch, then the honest "nothing yet".
|
|
81
|
+
*/
|
|
82
|
+
export function resolveListEmptyState(facts) {
|
|
83
|
+
const {
|
|
84
|
+
subject,
|
|
85
|
+
verb = "captured",
|
|
86
|
+
search,
|
|
87
|
+
total = 0,
|
|
88
|
+
capturing,
|
|
89
|
+
waiting,
|
|
90
|
+
paused,
|
|
91
|
+
blocked
|
|
92
|
+
} = facts;
|
|
93
|
+
const term = (search ?? "").trim();
|
|
94
|
+
if (term) {
|
|
95
|
+
return {
|
|
96
|
+
otto: "sad",
|
|
97
|
+
// The query goes in the HEADING because checking what you typed is the
|
|
98
|
+
// first thing you do; the body names whichever blocker is in the way.
|
|
99
|
+
title: `No results for “${term}”`,
|
|
100
|
+
message: blocked ?? (total > 0 ? `None of the ${total} ${verb} ${subject} match. Clear the search to see them.` : capturing === false ? `Nothing is ${verb} to search — capture is off. Turn it on to start.` : `Nothing has been ${verb} yet, so there is nothing to match.`)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (blocked) {
|
|
104
|
+
return {
|
|
105
|
+
otto: "filtering",
|
|
106
|
+
title: "Filtered Out",
|
|
107
|
+
message: blocked
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (total > 0) {
|
|
111
|
+
return {
|
|
112
|
+
otto: "filtering",
|
|
113
|
+
title: "Filtered Out",
|
|
114
|
+
message: `${total} ${verb} ${subject} ${total === 1 ? "is" : "are"} hidden by the active filters.`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (capturing === false) {
|
|
118
|
+
return {
|
|
119
|
+
otto: paused?.otto ?? "paused",
|
|
120
|
+
title: paused?.title ?? "Not capturing",
|
|
121
|
+
message: paused?.message ?? "Capture is off. Turn it on to start."
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return waiting;
|
|
125
|
+
}
|
|
126
|
+
const styles = StyleSheet.create({
|
|
127
|
+
container: {
|
|
128
|
+
alignItems: "center",
|
|
129
|
+
paddingVertical: 40,
|
|
130
|
+
// The generated sentences are full sentences. Every tool that hand-rolled
|
|
131
|
+
// this panel shipped without an inset and had it run to both edges of the
|
|
132
|
+
// sheet the first time the copy grew past a few words.
|
|
133
|
+
paddingHorizontal: 24
|
|
134
|
+
},
|
|
135
|
+
title: {
|
|
136
|
+
color: night.color.text,
|
|
137
|
+
fontSize: 14,
|
|
138
|
+
fontWeight: "600",
|
|
139
|
+
marginTop: 12,
|
|
140
|
+
marginBottom: 6,
|
|
141
|
+
textAlign: "center"
|
|
142
|
+
},
|
|
143
|
+
message: {
|
|
144
|
+
color: night.color.textTertiary,
|
|
145
|
+
fontSize: 12,
|
|
146
|
+
textAlign: "center",
|
|
147
|
+
lineHeight: 17
|
|
148
|
+
}
|
|
149
|
+
});
|
|
@@ -70,7 +70,8 @@ export function WindowControls({
|
|
|
70
70
|
onMinimize,
|
|
71
71
|
onToggleMode,
|
|
72
72
|
mode,
|
|
73
|
-
variant = "night"
|
|
73
|
+
variant = "night",
|
|
74
|
+
defaultExpanded
|
|
74
75
|
}) {
|
|
75
76
|
// Show action-based icon: what will happen when clicked
|
|
76
77
|
const ToggleModeIcon = mode === "floating" ? DockBottom : FloatWindow;
|
|
@@ -125,7 +126,8 @@ export function WindowControls({
|
|
|
125
126
|
onMinimize: onMinimize,
|
|
126
127
|
onToggleMode: onToggleMode,
|
|
127
128
|
mode: mode,
|
|
128
|
-
variant: variant
|
|
129
|
+
variant: variant,
|
|
130
|
+
defaultExpanded: defaultExpanded
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
133
|
function ExpandableWindowControls({
|
|
@@ -133,11 +135,12 @@ function ExpandableWindowControls({
|
|
|
133
135
|
onMinimize,
|
|
134
136
|
onToggleMode,
|
|
135
137
|
mode,
|
|
136
|
-
variant = "night"
|
|
138
|
+
variant = "night",
|
|
139
|
+
defaultExpanded = false
|
|
137
140
|
}) {
|
|
138
141
|
const isNight = variant === "night";
|
|
139
|
-
const [expanded, setExpanded] = useState(
|
|
140
|
-
const expandAnim = useRef(new Animated.Value(0)).current;
|
|
142
|
+
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
143
|
+
const expandAnim = useRef(new Animated.Value(defaultExpanded ? 1 : 0)).current;
|
|
141
144
|
const dismissTimeout = useRef(null);
|
|
142
145
|
const ToggleModeIcon = mode === "floating" ? DockBottom : FloatWindow;
|
|
143
146
|
const toggleModeLabel = mode === "floating" ? "Dock to bottom sheet" : "Make floating window";
|
|
@@ -21,6 +21,9 @@ export { CompactRow } from "./CompactRow.js";
|
|
|
21
21
|
export { DevToolsCard, DEVTOOLS_CARD_CONSTANTS } from "./DevToolsCard.js";
|
|
22
22
|
export { HeaderSearchButton } from "./HeaderSearchButton.js";
|
|
23
23
|
export { EmptyState, NoDataEmptyState, NoResultsEmptyState, NoSearchResultsEmptyState } from "./EmptyState.js";
|
|
24
|
+
// The shared "nothing here" panel + the precedence between its causes. Every
|
|
25
|
+
// capture tool's list empty goes through this — see the file header.
|
|
26
|
+
export { ToolEmptyState, resolveListEmptyState } from "./ToolEmptyState.js";
|
|
24
27
|
export { StatusIndicator } from "./StatusIndicator.js";
|
|
25
28
|
export { TimeDisplay } from "./TimeDisplay.js";
|
|
26
29
|
export { DetailView } from "./DetailView.js";
|
package/lib/module/ui/index.js
CHANGED
|
@@ -15,4 +15,7 @@ export { toolTheme } from "./toolTheme.js";
|
|
|
15
15
|
export { CyberpunkSectionButton } from "./console/CyberpunkSectionButton.js";
|
|
16
16
|
|
|
17
17
|
// Game UI (colors, hooks, components)
|
|
18
|
-
export * from "./gameUI/index.js";
|
|
18
|
+
export * from "./gameUI/index.js";
|
|
19
|
+
|
|
20
|
+
// Otto — the Buoy mascot (see OTTO_PLACEMENT_PLAN.md for where he belongs)
|
|
21
|
+
export * from "./otto/index.js";
|