@hanzo/build 0.2.26 → 0.2.28
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/grip.d.ts +40 -0
- package/lib/grip.js +108 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/project.js +41 -18
- package/package.json +2 -2
package/lib/grip.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A column's free edge, which is also how wide the column is: a vertical
|
|
3
|
+
* separator a person drags, or moves with the arrow keys, to size the column
|
|
4
|
+
* beside it. The project workspace sizes its chat with it, and a host sizes its
|
|
5
|
+
* own column with it (the Hanzo app's sidebar).
|
|
6
|
+
*
|
|
7
|
+
* side the edge of the window the column is pinned to: `left` grows
|
|
8
|
+
* rightwards (a sidebar, the chat), `right` grows leftwards (an
|
|
9
|
+
* aside). One control for both, the arithmetic reflected.
|
|
10
|
+
* span the column's width now, px; `floor` and `ceil` bound it.
|
|
11
|
+
* onSpan the width while it moves, at most once a frame.
|
|
12
|
+
* onKeep the width it settled on: a drag let go, a key, a double-click.
|
|
13
|
+
* onShut when given, a drag pulled well past the floor, or a key pressed
|
|
14
|
+
* at it, puts the column away; absent, the floor holds.
|
|
15
|
+
* reset the width a double-click returns to.
|
|
16
|
+
*
|
|
17
|
+
* Keys: the arrows step 8px (32px with Shift), Home and End go to the floor and
|
|
18
|
+
* the ceiling. Pointer events, so a mouse, a trackpad, a touchscreen and a pen
|
|
19
|
+
* are one path, with the pointer captured: a drag that outruns the handle, or
|
|
20
|
+
* crosses a framed page, keeps sizing. The drag is measured from the column as
|
|
21
|
+
* drawn when it begins, so the edge never jumps to the cursor.
|
|
22
|
+
*
|
|
23
|
+
* It sits inside the column, absolutely, on the column's free edge; the rest of
|
|
24
|
+
* its props place it elsewhere (a gutter beside the column) and win.
|
|
25
|
+
*/
|
|
26
|
+
import { YStack } from '@hanzo/gui';
|
|
27
|
+
import { type ComponentProps } from 'react';
|
|
28
|
+
export interface GripProps extends Omit<ComponentProps<typeof YStack>, 'children'> {
|
|
29
|
+
side: 'left' | 'right';
|
|
30
|
+
span: number;
|
|
31
|
+
floor: number;
|
|
32
|
+
ceil: number;
|
|
33
|
+
reset: number;
|
|
34
|
+
onSpan: (n: number) => void;
|
|
35
|
+
onKeep?: (n: number) => void;
|
|
36
|
+
onShut?: () => void;
|
|
37
|
+
/** The separator's accessible name: "Resize sidebar", "Resize the chat". */
|
|
38
|
+
label: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function Grip({ side, span, floor, ceil, reset, onSpan, onKeep, onShut, label, ...rest }: GripProps): import("react").JSX.Element;
|
package/lib/grip.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A column's free edge, which is also how wide the column is: a vertical
|
|
4
|
+
* separator a person drags, or moves with the arrow keys, to size the column
|
|
5
|
+
* beside it. The project workspace sizes its chat with it, and a host sizes its
|
|
6
|
+
* own column with it (the Hanzo app's sidebar).
|
|
7
|
+
*
|
|
8
|
+
* side the edge of the window the column is pinned to: `left` grows
|
|
9
|
+
* rightwards (a sidebar, the chat), `right` grows leftwards (an
|
|
10
|
+
* aside). One control for both, the arithmetic reflected.
|
|
11
|
+
* span the column's width now, px; `floor` and `ceil` bound it.
|
|
12
|
+
* onSpan the width while it moves, at most once a frame.
|
|
13
|
+
* onKeep the width it settled on: a drag let go, a key, a double-click.
|
|
14
|
+
* onShut when given, a drag pulled well past the floor, or a key pressed
|
|
15
|
+
* at it, puts the column away; absent, the floor holds.
|
|
16
|
+
* reset the width a double-click returns to.
|
|
17
|
+
*
|
|
18
|
+
* Keys: the arrows step 8px (32px with Shift), Home and End go to the floor and
|
|
19
|
+
* the ceiling. Pointer events, so a mouse, a trackpad, a touchscreen and a pen
|
|
20
|
+
* are one path, with the pointer captured: a drag that outruns the handle, or
|
|
21
|
+
* crosses a framed page, keeps sizing. The drag is measured from the column as
|
|
22
|
+
* drawn when it begins, so the edge never jumps to the cursor.
|
|
23
|
+
*
|
|
24
|
+
* It sits inside the column, absolutely, on the column's free edge; the rest of
|
|
25
|
+
* its props place it elsewhere (a gutter beside the column) and win.
|
|
26
|
+
*/
|
|
27
|
+
import { YStack } from '@hanzo/gui';
|
|
28
|
+
import { useEffect, useRef, useState } from 'react';
|
|
29
|
+
/** A key's step, px; with Shift, the long one. */
|
|
30
|
+
const STEP = 8;
|
|
31
|
+
const LONG = 32;
|
|
32
|
+
/** How far past the floor a drag is pulled before it shuts the column, px: a deliberate pull, not a twitch. */
|
|
33
|
+
const SLACK = 48;
|
|
34
|
+
export function Grip({ side, span, floor, ceil, reset, onSpan, onKeep, onShut, label, ...rest }) {
|
|
35
|
+
// The pinned edge in client coordinates, taken as the drag begins.
|
|
36
|
+
const held = useRef(0);
|
|
37
|
+
// Whether a press holds the pointer and whether it has moved since, the width
|
|
38
|
+
// it last asked for, and the frame that will draw it. A press that never moves
|
|
39
|
+
// keeps nothing.
|
|
40
|
+
const drag = useRef('none');
|
|
41
|
+
const want = useRef(span);
|
|
42
|
+
const frame = useRef(0);
|
|
43
|
+
const [hovered, setHovered] = useState(false);
|
|
44
|
+
const [keyed, setKeyed] = useState(false);
|
|
45
|
+
const [moving, setMoving] = useState(false);
|
|
46
|
+
useEffect(() => () => cancelAnimationFrame(frame.current), []);
|
|
47
|
+
const clamp = (n) => Math.round(Math.min(ceil, Math.max(floor, n)));
|
|
48
|
+
const settle = (n) => {
|
|
49
|
+
onSpan(n);
|
|
50
|
+
onKeep?.(n);
|
|
51
|
+
};
|
|
52
|
+
const stop = () => {
|
|
53
|
+
drag.current = 'none';
|
|
54
|
+
cancelAnimationFrame(frame.current);
|
|
55
|
+
frame.current = 0;
|
|
56
|
+
setMoving(false);
|
|
57
|
+
};
|
|
58
|
+
const wider = side === 'left' ? 'ArrowRight' : 'ArrowLeft';
|
|
59
|
+
const narrower = side === 'left' ? 'ArrowLeft' : 'ArrowRight';
|
|
60
|
+
return (_jsx(YStack, { role: "separator", "aria-orientation": "vertical", "aria-label": label, "aria-valuenow": Math.round(span), "aria-valuemin": floor, "aria-valuemax": ceil, tabIndex: 0, "data-slot": "grip", "data-moving": moving ? '' : undefined, position: "absolute", t: 0, b: 0, ...(side === 'left' ? { r: 0 } : { l: 0 }), width: 8, z: 10, items: "center", cursor: "col-resize", outlineWidth: 0, style: { touchAction: 'none' }, onPointerEnter: () => setHovered(true), onPointerLeave: () => setHovered(false),
|
|
61
|
+
// The line is the focus ring, drawn for a keyboard's focus and not a pointer's.
|
|
62
|
+
onFocus: (e) => setKeyed(e.currentTarget.matches(':focus-visible')), onBlur: () => setKeyed(false), onPointerDown: (e) => {
|
|
63
|
+
if (e.button !== 0)
|
|
64
|
+
return;
|
|
65
|
+
// No text selection starts under a drag.
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
68
|
+
const drawn = e.currentTarget.parentElement?.getBoundingClientRect().width ?? span;
|
|
69
|
+
held.current = side === 'left' ? e.clientX - drawn : e.clientX + drawn;
|
|
70
|
+
want.current = clamp(drawn);
|
|
71
|
+
drag.current = 'held';
|
|
72
|
+
setMoving(true);
|
|
73
|
+
}, onPointerMove: (e) => {
|
|
74
|
+
if (drag.current === 'none')
|
|
75
|
+
return;
|
|
76
|
+
drag.current = 'moved';
|
|
77
|
+
const pulled = side === 'left' ? e.clientX - held.current : held.current - e.clientX;
|
|
78
|
+
if (onShut && pulled < floor - SLACK) {
|
|
79
|
+
stop();
|
|
80
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
81
|
+
onShut();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
want.current = clamp(pulled);
|
|
85
|
+
if (frame.current)
|
|
86
|
+
return;
|
|
87
|
+
frame.current = requestAnimationFrame(() => {
|
|
88
|
+
frame.current = 0;
|
|
89
|
+
onSpan(want.current);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
// A drag ends when the pointer is let go: lifted, or taken by the browser.
|
|
93
|
+
onLostPointerCapture: () => {
|
|
94
|
+
const moved = drag.current === 'moved';
|
|
95
|
+
stop();
|
|
96
|
+
if (moved)
|
|
97
|
+
settle(want.current);
|
|
98
|
+
}, onDoubleClick: () => settle(clamp(reset)), onKeyDown: (e) => {
|
|
99
|
+
const step = e.shiftKey ? LONG : STEP;
|
|
100
|
+
const to = e.key === wider ? span + step : e.key === narrower ? span - step : e.key === 'Home' ? floor : e.key === 'End' ? ceil : null;
|
|
101
|
+
if (to === null)
|
|
102
|
+
return;
|
|
103
|
+
e.preventDefault?.();
|
|
104
|
+
if (onShut && to < floor)
|
|
105
|
+
return onShut();
|
|
106
|
+
settle(clamp(to));
|
|
107
|
+
}, ...rest, children: _jsx(YStack, { width: 2, height: "100%", rounded: 1, bg: keyed || moving ? '$outlineColor' : hovered ? '$rim' : 'transparent', pointerEvents: "none" }) }));
|
|
108
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
* of its own mounts it with `rail={false}` and draws the builder's parts in that
|
|
8
8
|
* rail: its places (`nav`), its runs (`useSessions`, or `DevSection` for both),
|
|
9
9
|
* and at the foot the Slack card and the account menu (`Slack`, `Who`), under
|
|
10
|
-
* `HostProvider`.
|
|
10
|
+
* `HostProvider`. `Grip` sizes that rail: the edge the builder sizes its own
|
|
11
|
+
* chat with.
|
|
11
12
|
*/
|
|
12
13
|
export { Builder, nav } from './builder.tsx';
|
|
13
14
|
export { DevSection, DOTS, useSessions } from './section.tsx';
|
|
14
15
|
export { HostProvider } from './host.tsx';
|
|
15
16
|
export { Slack, useWho, Who } from './foot.tsx';
|
|
17
|
+
export { Grip, type GripProps } from './grip.tsx';
|
|
16
18
|
export { administers } from './claims.ts';
|
|
17
19
|
export type { Host, Person } from './host.tsx';
|
|
18
20
|
export type { Session, Status } from './api/sessions.ts';
|
package/lib/index.js
CHANGED
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* of its own mounts it with `rail={false}` and draws the builder's parts in that
|
|
8
8
|
* rail: its places (`nav`), its runs (`useSessions`, or `DevSection` for both),
|
|
9
9
|
* and at the foot the Slack card and the account menu (`Slack`, `Who`), under
|
|
10
|
-
* `HostProvider`.
|
|
10
|
+
* `HostProvider`. `Grip` sizes that rail: the edge the builder sizes its own
|
|
11
|
+
* chat with.
|
|
11
12
|
*/
|
|
12
13
|
export { Builder, nav } from './builder.js';
|
|
13
14
|
export { DevSection, DOTS, useSessions } from './section.js';
|
|
14
15
|
export { HostProvider } from './host.js';
|
|
15
16
|
export { Slack, useWho, Who } from './foot.js';
|
|
17
|
+
export { Grip } from './grip.js';
|
|
16
18
|
export { administers } from './claims.js';
|
|
17
19
|
export { path, route, SESSION, SLUG } from './route.js';
|
package/lib/project.js
CHANGED
|
@@ -3,13 +3,19 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
* A project's workspace, in the same window: the chat on the left, what it
|
|
4
4
|
* built on the right.
|
|
5
5
|
*
|
|
6
|
-
* bar mark · project · history · chat toggle | Preview Files Code Layers
|
|
7
|
-
*
|
|
6
|
+
* bar mark · project · history · chat toggle | Preview Files Code Layers |
|
|
7
|
+
* Share · Publish
|
|
8
8
|
* left the project's runs as a conversation, suggestions, and the ask
|
|
9
|
-
* right the deployed page, framed
|
|
10
|
-
*
|
|
9
|
+
* right the deployed page, framed under its own bar (reload · page · device
|
|
10
|
+
* · open), and kept loaded while another view is open; the repository
|
|
11
|
+
* at the run's branch as a tree and as read-only code; the element a
|
|
12
|
+
* person picked
|
|
11
13
|
* dock the run's console — its steps and its log, as they stream
|
|
12
14
|
*
|
|
15
|
+
* The edge between left and right is dragged, or moved with the arrow keys, to
|
|
16
|
+
* size the chat, and the width is kept per browser; the right side keeps room
|
|
17
|
+
* for a page whatever the chat asked for.
|
|
18
|
+
*
|
|
13
19
|
* A chat turn IS a coding run: `POST /v1/agent/coding` with the project, the
|
|
14
20
|
* mode, and the previous run as `after`, so the next ask builds on the branch
|
|
15
21
|
* the last one pushed. Files and Code read native git at that branch. The
|
|
@@ -41,6 +47,7 @@ import { outcome, pull, said, who } from './api/turn.js';
|
|
|
41
47
|
import { verdict as record } from './api/verdict.js';
|
|
42
48
|
import { useKept, useProjects, useRead, useRun } from './data.js';
|
|
43
49
|
import { Merge } from './git.js';
|
|
50
|
+
import { Grip } from './grip.js';
|
|
44
51
|
import { useHost, useTarget } from './host.js';
|
|
45
52
|
import { MODES } from './landing.js';
|
|
46
53
|
import { Out } from './out.js';
|
|
@@ -49,6 +56,14 @@ import { Attach, compose, Dictate, Files } from './tools.js';
|
|
|
49
56
|
const LIVE = new Set(['running', 'paused']);
|
|
50
57
|
/** How often a project being published is read again, ms. */
|
|
51
58
|
const AGAIN = 5000;
|
|
59
|
+
/** The chat's width beside the work, px: its default, and the two it is kept between. */
|
|
60
|
+
const CHAT_WIDTH = 360;
|
|
61
|
+
const CHAT_FLOOR = 300;
|
|
62
|
+
const CHAT_CEIL = 640;
|
|
63
|
+
/** The Workspace's gutter between the chat and the work, and its margin right of the work, px ($3). */
|
|
64
|
+
const GUTTER = 12;
|
|
65
|
+
/** What the work keeps beside the chat however wide the chat was asked to be, px. */
|
|
66
|
+
const WORK = 400 + 2 * GUTTER;
|
|
52
67
|
/** One run in the conversation: what was asked, and what it came to. */
|
|
53
68
|
function Turn({ run, open, onOpen, project }) {
|
|
54
69
|
const t = useTarget();
|
|
@@ -95,6 +110,11 @@ export function Project({ slug }) {
|
|
|
95
110
|
const [view, setView] = useKept(`hanzo.build.view.${slug}`, 'preview');
|
|
96
111
|
const [device, setDevice] = useState('desktop');
|
|
97
112
|
const [collapsed, setCollapsed] = useKept('hanzo.build.chat', false);
|
|
113
|
+
const [split, setSplit] = useKept('hanzo.build.split', CHAT_WIDTH);
|
|
114
|
+
// The workspace's width, which bounds the chat so the work keeps its room.
|
|
115
|
+
const [room, setRoom] = useState(0);
|
|
116
|
+
const most = room ? Math.max(CHAT_FLOOR, Math.min(CHAT_CEIL, room - WORK)) : CHAT_CEIL;
|
|
117
|
+
const across = Math.min(most, Math.max(CHAT_FLOOR, split));
|
|
98
118
|
// Below md the chat and the work take turns, and Chat is one more view to switch back to.
|
|
99
119
|
const [pane, setPane] = useState('chat');
|
|
100
120
|
const wide = useMedia().md;
|
|
@@ -284,7 +304,7 @@ export function Project({ slug }) {
|
|
|
284
304
|
if (!all.loading && !project && !all.error) {
|
|
285
305
|
return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", px: "$4", children: [_jsx(SizableText, { size: "$4", color: "$ink", children: host.org ? `${host.org} has no project named ${slug}.` : `There is no project named ${slug}.` }), _jsx(Button, { variant: "outline", onPress: () => host.go('-/artifacts'), children: "See what this organization has built" })] }));
|
|
286
306
|
}
|
|
287
|
-
const chat = (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$4", gap: "$4", children: ordered.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$ink", px: "$2", children: runs.loading
|
|
307
|
+
const chat = (_jsxs(YStack, { flex: 1, minH: 0, position: "relative", children: [_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$4", gap: "$4", children: ordered.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$ink", px: "$2", children: runs.loading
|
|
288
308
|
? 'Reading this project’s runs…'
|
|
289
309
|
: project?.live
|
|
290
310
|
? `${project.name} is loaded — it is in the preview, and every run on it is under the clock above. Say what to change and it gets built.`
|
|
@@ -296,25 +316,28 @@ export function Project({ slug }) {
|
|
|
296
316
|
? `${project.name} is loaded and nothing is published yet. Say what to change and it gets built.`
|
|
297
317
|
: `${slug} is loaded. Say what to change and it gets built.` })) : (ordered.map((r) => (_jsxs(YStack, { gap: "$2", children: [_jsx(Turn, { run: r, open: r.id === current, onOpen: () => setChosen(r.id), project: slug }), r.id === current ? (_jsxs(YStack, { gap: "$1", pl: "$2", borderLeftWidth: 1, borderColor: "$borderColor", children: [fold(run.events.map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e, run.record?.mode) })).filter((b) => b.text)).map((b) => (_jsx(SizableText, { size: "$1", color: "$soft", children: b.text }, b.key))), pr.href ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsxs(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: [_jsxs(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: ["Pull request ", pr.label] }), _jsx(ExternalLink, { size: 11 })] }), _jsx(Merge, { session: r.id, open: proposing, onMerged: merged })] })) : null] })) : null] }, r.id)))) }), _jsxs(YStack, { px: "$3", pb: "$3", gap: "$2", children: [!dismissed && !running ? _jsx(Suggestions, { items: SUGGESTIONS, onPick: (s) => void send(s), onDismiss: () => setDismissed(true) }) : null, note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, picked.length ? _jsx(Attachments, { items: picked, onRemove: () => setPicked([]) }) : null, attached.length ? (_jsx(XStack, { gap: 6, flexWrap: "wrap", children: _jsx(Files, { files: attached, onFiles: setAttached }) })) : null, _jsxs(Composer, { value: draft, onChange: setDraft, onSend: () => void (current && running ? steer(current) : send()), onStop: current
|
|
298
318
|
? () => void stop(t, current).then(() => setNote('Stop requested — the run keeps its work on its branch'), (e) => setNote(e.message))
|
|
299
|
-
: undefined, busy: busy || (running && !draft.trim()), rows: 3, placeholder: running ? 'Steer this run' : 'Ask Hanzo for edits', label: "Ask Hanzo for edits", children: [_jsx(Attach, { files: attached, onFiles: setAttached, onNote: setNote }), _jsx(XStack, { flex: 1 }), _jsx(ModeSelect, { modes: MODES, value: mode, onChange: (m) => setMode(m), self: "center" }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote })] })] })] }));
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
319
|
+
: undefined, busy: busy || (running && !draft.trim()), rows: 3, placeholder: running ? 'Steer this run' : 'Ask Hanzo for edits', label: "Ask Hanzo for edits", children: [_jsx(Attach, { files: attached, onFiles: setAttached, onNote: setNote }), _jsx(XStack, { flex: 1 }), _jsx(ModeSelect, { modes: MODES, value: mode, onChange: (m) => setMode(m), self: "center" }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote })] })] }), wide && !collapsed ? (_jsx(Grip, { side: "left", span: across, floor: CHAT_FLOOR, ceil: most, reset: CHAT_WIDTH, onSpan: setSplit, label: "Resize the chat", r: -GUTTER, width: GUTTER })) : null] }));
|
|
320
|
+
// The preview stays loaded while another view is open, so coming back to it
|
|
321
|
+
// is the page as it was left.
|
|
322
|
+
const preview = (_jsxs(YStack, { "data-slot": "preview", flex: 1, minH: 0, minW: 0, gap: "$2", display: view === 'preview' ? 'flex' : 'none', children: [_jsxs(XStack, { "data-slot": "preview-bar", items: "center", gap: "$1.5", minW: 0, shrink: 0, "$max-md": { px: '$2' }, children: [_jsx(XStack, { render: "button", "aria-label": "Reload the preview", onPress: () => frame.current?.reload(), p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 16 }) }), _jsx(PageSelect, { pages: pages, value: page, onChange: setPage, shrink: 1 }), _jsx(XStack, { flex: 1 }), _jsx(Views, { views: DEVICES, value: device, onChange: (d) => setDevice(d), label: "Device", labels: "none" }), src ? (_jsx(Out, { href: src, label: "Open in a new tab", children: _jsx(XStack, { p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 16 }) }) })) : null] }), _jsx(PreviewFrame, { ref: frame, src: src, title: `${project?.name ?? slug} preview`, device: device, onBridge: onBridge, empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: building ? (project?.forked ? 'Publishing your copy…' : 'Publishing…') : failed ? 'Publishing failed' : 'Nothing deployed yet' }), _jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: building ? 'Its page appears here when the build finishes.' : failed ? why : 'Publish this project and its page appears here.' })] }), toolbar: bridge ? (_jsx(XStack, { render: "button", "aria-pressed": editing, "aria-label": editing ? 'Stop picking elements' : 'Pick an element to edit', onPress: () => {
|
|
323
|
+
const next = !editing;
|
|
324
|
+
setEditing(next);
|
|
325
|
+
frame.current?.post({ type: 'preview:editable', active: next });
|
|
326
|
+
}, p: "$2", rounded: "$10", children: _jsx(MousePointerClick, { size: 16 }) })) : undefined })] }));
|
|
327
|
+
const body = view === 'preview' ? null : view === 'files' ? (repo ? (_jsx(FileTree, { load: async (dir) => (await tree(t, repo, ref, dir)).map((e) => ({ path: e.path, kind: e.dir ? 'dir' : 'file' })), value: file, onSelect: (p) => void openFile(p), label: `${repo} at ${ref}` }, round)) : (_jsx(SizableText, { size: "$2", color: "$soft", p: "$4", children: "This project has no repository yet. Its first run creates one." }))) : view === 'code' ? (_jsx(FileTabs, { files: files, value: file, onSelect: setFile, onClose: (p) => {
|
|
305
328
|
setFiles((was) => was.filter((f) => f.path !== p));
|
|
306
329
|
if (file === p)
|
|
307
330
|
setFile(null);
|
|
308
331
|
}, empty: _jsxs(SizableText, { size: "$2", color: "$soft", p: "$4", children: ["Open a file from Files. Code here is read at ", ref, "; ask in the chat to change it."] }) })) : (_jsx(YStack, { p: "$4", gap: "$2", children: picked.length ? (picked.map((p) => (_jsxs(SizableText, { size: "$2", color: "$ink", children: [p.label, " \u2014 ", p.id] }, p.id)))) : (_jsx(SizableText, { size: "$2", color: "$soft", children: bridge
|
|
309
332
|
? 'Pick an element in the preview to see it here and attach it to your next ask.'
|
|
310
333
|
: 'Layers are read through the preview bridge, and this page does not run it. Its elements cannot be listed from here.' })) }));
|
|
311
|
-
return (_jsxs(_Fragment, { children: [
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
setPublish({ repo: project.repo, title: project.name, ref, name: project.slug, project: project.slug }), children: [_jsx(Upload, { size: 14 }), " Publish"] })] }), chat: chat, dock: _jsx(Console, { lines: lines, height: dock, onHeight: setDock, onClear: () => setPageLines([]), status: run.status || end.status || undefined, label: "Console" }), collapsed: collapsed, pane: pane, children: body }), _jsx(Dialog, { open: history, onOpenChange: setHistory, children: _jsxs(DialogContent, { maxW: 480, children: [_jsxs(DialogTitle, { children: ["Runs on ", project?.name ?? slug] }), _jsx(YStack, { gap: "$1", children: runs.value.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: "No runs yet." })) : (runs.value.map((r) => (_jsxs(XStack, { render: "button", onPress: () => {
|
|
334
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Workspace, { start: _jsxs(XStack, { items: "center", gap: "$2", minW: 0, shrink: 1, children: [_jsx(XStack, { render: "button", "aria-label": "All runs", onPress: () => host.go(''), p: "$1", shrink: 0, children: _jsx(HanzoMark, { size: 20 }) }), _jsx(ProjectChip, { name: project?.name ?? slug, onPress: () => setSwitcher(true) }), _jsx(XStack, { render: "button", "aria-label": "History", onPress: () => setHistory(true), p: "$1.5", rounded: "$3", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(Clock, { size: 16 }) }), _jsx(XStack, { render: "button", "aria-label": collapsed ? 'Show the chat' : 'Hide the chat', "aria-pressed": !collapsed, onPress: () => setCollapsed(!collapsed), p: "$1.5", rounded: "$3", shrink: 0, hoverStyle: { bg: '$hover' }, "$max-md": { display: 'none' }, children: _jsx(PanelLeft, { size: 16 }) })] }), middle: _jsx(Views, { views: [...VIEWS, CHAT], value: pane === 'chat' && !wide ? 'chat' : view, onChange: (v) => {
|
|
335
|
+
if (v === 'chat')
|
|
336
|
+
return setPane('chat');
|
|
337
|
+
setView(v);
|
|
338
|
+
setPane('view');
|
|
339
|
+
}, label: "View" }), end: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { display: "none", $md: { display: 'flex' }, children: _jsxs(Button, { variant: "outline", size: "sm", onPress: () => void share(), children: [_jsx(Share2, { size: 14 }), " Share"] }) }), _jsxs(Button, { size: "sm", disabled: !project?.repo, onPress: () => project &&
|
|
340
|
+
setPublish({ repo: project.repo, title: project.name, ref, name: project.slug, project: project.slug }), children: [_jsx(Upload, { size: 14 }), " Publish"] })] }), chat: chat, dock: _jsx(Console, { lines: lines, height: dock, onHeight: setDock, onClear: () => setPageLines([]), status: run.status || end.status || undefined, label: "Console" }), collapsed: collapsed, pane: pane, width: across, onLayout: (e) => setRoom(e.nativeEvent.layout.width), children: [preview, body] }), _jsx(Dialog, { open: history, onOpenChange: setHistory, children: _jsxs(DialogContent, { maxW: 480, children: [_jsxs(DialogTitle, { children: ["Runs on ", project?.name ?? slug] }), _jsx(YStack, { gap: "$1", children: runs.value.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: "No runs yet." })) : (runs.value.map((r) => (_jsxs(XStack, { render: "button", onPress: () => {
|
|
318
341
|
setChosen(r.id);
|
|
319
342
|
setHistory(false);
|
|
320
343
|
}, justify: "space-between", px: "$2", py: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, flex: 1, children: r.title || 'Untitled run' }), _jsx(SizableText, { size: "$1", color: "$soft", children: r.status })] }, r.id)))) })] }) }), _jsx(Dialog, { open: switcher, onOpenChange: setSwitcher, children: _jsxs(DialogContent, { maxW: 420, children: [_jsx(DialogTitle, { children: "Projects" }), _jsx(YStack, { gap: "$1", maxH: 420, overflow: "scroll", children: all.value.map((p) => (_jsx(XStack, { render: "button", onPress: () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@hanzo/font": "1.9.3",
|
|
12
12
|
"@hanzo/gui": "8.3.5",
|
|
13
|
-
"@hanzo/iam": "0.21.
|
|
13
|
+
"@hanzo/iam": "0.21.21",
|
|
14
14
|
"@hanzo/ui": "8.27.16",
|
|
15
15
|
"@hanzo/vite": "0.1.4",
|
|
16
16
|
"@hanzo/voice": "0.1.8",
|