@jxsuite/studio 0.23.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +2230 -714
- package/dist/studio.js.map +121 -104
- package/package.json +8 -7
- package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
- package/src/browse/{browse.js → browse.ts} +109 -84
- package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
- package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
- package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
- package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
- package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
- package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
- package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
- package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
- package/src/editor/{context-menu.js → context-menu.ts} +38 -36
- package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
- package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
- package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
- package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
- package/src/editor/{inline-format.js → inline-format.ts} +46 -51
- package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
- package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
- package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
- package/src/files/{components.js → components.ts} +12 -15
- package/src/files/{file-ops.js → file-ops.ts} +36 -29
- package/src/files/{files.js → files.ts} +102 -84
- package/src/github/{github-auth.js → github-auth.ts} +3 -3
- package/src/github/{github-publish.js → github-publish.ts} +20 -18
- package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
- package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
- package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
- package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
- package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
- package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
- package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
- package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
- package/src/panels/dnd.ts +417 -0
- package/src/panels/{editors.js → editors.ts} +43 -50
- package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
- package/src/panels/{events-panel.js → events-panel.ts} +33 -25
- package/src/panels/{git-panel.js → git-panel.ts} +97 -83
- package/src/panels/{head-panel.js → head-panel.ts} +117 -139
- package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
- package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
- package/src/panels/{left-panel.js → left-panel.ts} +56 -63
- package/src/panels/{overlays.js → overlays.ts} +28 -36
- package/src/panels/{panel-events.js → panel-events.ts} +53 -51
- package/src/panels/{preview-render.js → preview-render.ts} +20 -15
- package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
- package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
- package/src/panels/{quick-search.js → quick-search.ts} +16 -17
- package/src/panels/{right-panel.js → right-panel.ts} +41 -59
- package/src/panels/{shared.js → shared.ts} +7 -5
- package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
- package/src/panels/{statusbar.js → statusbar.ts} +14 -14
- package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
- package/src/panels/{style-panel.js → style-panel.ts} +235 -290
- package/src/panels/{style-utils.js → style-utils.ts} +32 -31
- package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
- package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
- package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
- package/src/panels/{toolbar.js → toolbar.ts} +68 -70
- package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
- package/src/{platform.js → platform.ts} +5 -3
- package/src/platforms/{devserver.js → devserver.ts} +53 -44
- package/src/{recent-projects.js → recent-projects.ts} +17 -12
- package/src/{resize-edges.js → resize-edges.ts} +18 -13
- package/src/services/{cem-export.js → cem-export.ts} +14 -7
- package/src/services/{code-services.js → code-services.ts} +29 -20
- package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
- package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
- package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
- package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
- package/src/settings/{general-settings.js → general-settings.ts} +29 -28
- package/src/settings/{head-editor.js → head-editor.ts} +37 -35
- package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
- package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
- package/src/{site-context.js → site-context.ts} +35 -32
- package/src/{state.js → state.ts} +95 -78
- package/src/{store.js → store.ts} +60 -63
- package/src/{studio.js → studio.ts} +117 -119
- package/src/tabs/tab.ts +196 -0
- package/src/tabs/{transact.js → transact.ts} +107 -60
- package/src/types.ts +185 -0
- package/src/ui/{button-group.js → button-group.ts} +21 -21
- package/src/ui/{color-selector.js → color-selector.ts} +44 -37
- package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
- package/src/ui/{field-row.js → field-row.ts} +19 -2
- package/src/ui/{icons.js → icons.ts} +2 -2
- package/src/ui/{layers.js → layers.ts} +33 -24
- package/src/ui/{media-picker.js → media-picker.ts} +13 -9
- package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
- package/src/ui/{spectrum.js → spectrum.ts} +4 -4
- package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
- package/src/ui/{value-selector.js → value-selector.ts} +28 -28
- package/src/ui/{widgets.js → widgets.ts} +62 -15
- package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
- package/src/utils/{edit-display.js → edit-display.ts} +23 -20
- package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
- package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
- package/src/utils/{studio-utils.js → studio-utils.ts} +35 -23
- package/src/{view.js → view.ts} +53 -55
- package/src/workspace/{workspace.js → workspace.ts} +44 -33
- package/src/panels/dnd.js +0 -426
- package/src/tabs/tab.js +0 -192
- /package/src/{reactivity.js → reactivity.ts} +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* New Project modal — guides the user through creating a new Jx project. Fields mirror the CLI
|
|
3
4
|
* scaffolder: name, description, url, adapter.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { html } from "lit-html";
|
|
7
|
-
import { openModal } from "../ui/layers
|
|
8
|
-
import { getPlatform } from "../platform
|
|
8
|
+
import { openModal } from "../ui/layers";
|
|
9
|
+
import { getPlatform } from "../platform";
|
|
10
|
+
import type { ProjectConfig } from "@jxsuite/schema/types";
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
let _handle = null;
|
|
12
|
+
let _handle: ReturnType<typeof openModal> | null = null;
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @type {{
|
|
@@ -21,14 +22,12 @@ let _handle = null;
|
|
|
21
22
|
*/
|
|
22
23
|
let _form = { name: "", description: "", url: "", adapter: "static", directory: "" };
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
let _error = "";
|
|
25
|
+
let _error: string = "";
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
let _creating = false;
|
|
27
|
+
let _creating: boolean = false;
|
|
29
28
|
|
|
30
29
|
/** @type {((result: { root: string; config: object } | null) => void) | null} */
|
|
31
|
-
let _resolve = null;
|
|
30
|
+
let _resolve: ((result: { root: string; config: ProjectConfig } | null) => void) | null = null;
|
|
32
31
|
|
|
33
32
|
/**
|
|
34
33
|
* Open the New Project modal. Returns a promise that resolves with the created project info (or
|
|
@@ -36,7 +35,7 @@ let _resolve = null;
|
|
|
36
35
|
*
|
|
37
36
|
* @returns {Promise<{ root: string; config: object } | null>}
|
|
38
37
|
*/
|
|
39
|
-
export function openNewProjectModal() {
|
|
38
|
+
export function openNewProjectModal(): Promise<{ root: string; config: ProjectConfig } | null> {
|
|
40
39
|
if (_handle) return Promise.resolve(null);
|
|
41
40
|
_form = { name: "", description: "", url: "", adapter: "static", directory: "" };
|
|
42
41
|
_error = "";
|
|
@@ -60,9 +59,8 @@ export function closeNewProjectModal() {
|
|
|
60
59
|
|
|
61
60
|
function renderModal() {
|
|
62
61
|
const onInput =
|
|
63
|
-
(
|
|
64
|
-
|
|
65
|
-
_form[field] = /** @type {HTMLInputElement} */ (e.target).value;
|
|
62
|
+
(field: "name" | "description" | "url" | "adapter" | "directory") => (e: Event) => {
|
|
63
|
+
_form[field] = (e.target as HTMLInputElement).value;
|
|
66
64
|
if (field === "name" && !_form.directory) {
|
|
67
65
|
// Auto-derive directory slug from name while user hasn't manually typed one
|
|
68
66
|
_dirDerived = true;
|
|
@@ -77,8 +75,8 @@ function renderModal() {
|
|
|
77
75
|
renderModal();
|
|
78
76
|
};
|
|
79
77
|
|
|
80
|
-
const onAdapterChange = (
|
|
81
|
-
_form.adapter =
|
|
78
|
+
const onAdapterChange = (e: Event) => {
|
|
79
|
+
_form.adapter = (e.target as HTMLInputElement).value;
|
|
82
80
|
renderModal();
|
|
83
81
|
};
|
|
84
82
|
|
|
@@ -111,9 +109,9 @@ function renderModal() {
|
|
|
111
109
|
_resolve(result);
|
|
112
110
|
_resolve = null;
|
|
113
111
|
}
|
|
114
|
-
} catch (
|
|
112
|
+
} catch (e) {
|
|
115
113
|
_creating = false;
|
|
116
|
-
_error =
|
|
114
|
+
_error = (e as Error).message;
|
|
117
115
|
renderModal();
|
|
118
116
|
}
|
|
119
117
|
};
|
|
@@ -122,7 +120,7 @@ function renderModal() {
|
|
|
122
120
|
<sp-underlay open @close=${closeNewProjectModal}></sp-underlay>
|
|
123
121
|
<div
|
|
124
122
|
class="new-project-modal"
|
|
125
|
-
@keydown=${(
|
|
123
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
126
124
|
if (e.key === "Escape") closeNewProjectModal();
|
|
127
125
|
}}
|
|
128
126
|
>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/** Activity bar — tab icons for switching left panel views. */
|
|
2
3
|
|
|
3
4
|
import { html, render as litRender, nothing } from "lit-html";
|
|
4
|
-
import { activityBar, renderOnly } from "../store
|
|
5
|
-
import { effect, effectScope } from "../reactivity
|
|
6
|
-
import { activeTab } from "../workspace/workspace
|
|
7
|
-
import { view, applyPanelCollapse } from "../view
|
|
8
|
-
import { openSettingsModal } from "../settings/settings-modal
|
|
9
|
-
import { refreshGitStatus } from "./git-panel
|
|
5
|
+
import { activityBar, renderOnly } from "../store";
|
|
6
|
+
import { effect, effectScope } from "../reactivity";
|
|
7
|
+
import { activeTab } from "../workspace/workspace";
|
|
8
|
+
import { view, applyPanelCollapse } from "../view";
|
|
9
|
+
import { openSettingsModal } from "../settings/settings-modal";
|
|
10
|
+
import { refreshGitStatus } from "./git-panel";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
let _scope = null;
|
|
12
|
+
let _scope: import("@vue/reactivity").EffectScope | null = null;
|
|
13
13
|
|
|
14
14
|
export function mount() {
|
|
15
15
|
_scope = effectScope();
|
|
@@ -32,7 +32,7 @@ export function unmount() {
|
|
|
32
32
|
_scope = null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const gitBranchIcon = (
|
|
35
|
+
const gitBranchIcon = (s: string) => html`
|
|
36
36
|
<svg
|
|
37
37
|
slot="icon"
|
|
38
38
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -56,35 +56,27 @@ const gitBranchIcon = (/** @type {string} */ s) => html`
|
|
|
56
56
|
* @param {string} tag
|
|
57
57
|
* @param {string} [size]
|
|
58
58
|
*/
|
|
59
|
-
export function tabIcon(tag, size) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"sp-icon-
|
|
63
|
-
|
|
64
|
-
"sp-icon-layers": (/** @type {string} */ s) =>
|
|
65
|
-
html`<sp-icon-layers slot="icon" size=${s}></sp-icon-layers>`,
|
|
66
|
-
"sp-icon-view-grid": (/** @type {string} */ s) =>
|
|
59
|
+
export function tabIcon(tag: string, size?: string) {
|
|
60
|
+
const m: Record<string, (s: string) => import("lit-html").TemplateResult> = {
|
|
61
|
+
"sp-icon-folder": (s: string) => html`<sp-icon-folder slot="icon" size=${s}></sp-icon-folder>`,
|
|
62
|
+
"sp-icon-layers": (s: string) => html`<sp-icon-layers slot="icon" size=${s}></sp-icon-layers>`,
|
|
63
|
+
"sp-icon-view-grid": (s: string) =>
|
|
67
64
|
html`<sp-icon-view-grid slot="icon" size=${s}></sp-icon-view-grid>`,
|
|
68
|
-
"sp-icon-brackets": (
|
|
65
|
+
"sp-icon-brackets": (s: string) =>
|
|
69
66
|
html`<sp-icon-brackets slot="icon" size=${s}></sp-icon-brackets>`,
|
|
70
|
-
"sp-icon-data": (
|
|
71
|
-
|
|
72
|
-
"sp-icon-properties": (/** @type {string} */ s) =>
|
|
67
|
+
"sp-icon-data": (s: string) => html`<sp-icon-data slot="icon" size=${s}></sp-icon-data>`,
|
|
68
|
+
"sp-icon-properties": (s: string) =>
|
|
73
69
|
html`<sp-icon-properties slot="icon" size=${s}></sp-icon-properties>`,
|
|
74
|
-
"sp-icon-event": (
|
|
75
|
-
|
|
76
|
-
"sp-icon-
|
|
77
|
-
html`<sp-icon-brush slot="icon" size=${s}></sp-icon-brush>`,
|
|
78
|
-
"sp-icon-file-single-web-page": (/** @type {string} */ s) =>
|
|
70
|
+
"sp-icon-event": (s: string) => html`<sp-icon-event slot="icon" size=${s}></sp-icon-event>`,
|
|
71
|
+
"sp-icon-brush": (s: string) => html`<sp-icon-brush slot="icon" size=${s}></sp-icon-brush>`,
|
|
72
|
+
"sp-icon-file-single-web-page": (s: string) =>
|
|
79
73
|
html`<sp-icon-file-single-web-page slot="icon" size=${s}></sp-icon-file-single-web-page>`,
|
|
80
|
-
"sp-icon-view-all-tags": (
|
|
74
|
+
"sp-icon-view-all-tags": (s: string) =>
|
|
81
75
|
html`<sp-icon-view-all-tags slot="icon" size=${s}></sp-icon-view-all-tags>`,
|
|
82
|
-
"sp-icon-artboard": (
|
|
76
|
+
"sp-icon-artboard": (s: string) =>
|
|
83
77
|
html`<sp-icon-artboard slot="icon" size=${s}></sp-icon-artboard>`,
|
|
84
|
-
"sp-icon-chat": (
|
|
85
|
-
|
|
86
|
-
"sp-icon-box": (/** @type {string} */ s) =>
|
|
87
|
-
html`<sp-icon-box slot="icon" size=${s}></sp-icon-box>`,
|
|
78
|
+
"sp-icon-chat": (s: string) => html`<sp-icon-chat slot="icon" size=${s}></sp-icon-chat>`,
|
|
79
|
+
"sp-icon-box": (s: string) => html`<sp-icon-box slot="icon" size=${s}></sp-icon-box>`,
|
|
88
80
|
"sp-icon-git-branch": gitBranchIcon,
|
|
89
81
|
};
|
|
90
82
|
const fn = m[tag];
|
|
@@ -110,8 +102,8 @@ export function renderActivityBar() {
|
|
|
110
102
|
selected=${view.leftPanelCollapsed ? "" : leftTab}
|
|
111
103
|
direction="vertical"
|
|
112
104
|
quiet
|
|
113
|
-
@change=${(
|
|
114
|
-
const clicked =
|
|
105
|
+
@change=${(e: Event) => {
|
|
106
|
+
const clicked = (e.target as HTMLElement & { selected: string }).selected;
|
|
115
107
|
if (clicked === view.leftTab && !view.leftPanelCollapsed) {
|
|
116
108
|
view.leftPanelCollapsed = true;
|
|
117
109
|
applyPanelCollapse();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Ai-panel.js — Claude AI Assistant tab for the right panel
|
|
3
4
|
*
|
|
@@ -7,8 +8,8 @@
|
|
|
7
8
|
import { html, nothing } from "lit-html";
|
|
8
9
|
import { ref } from "lit-html/directives/ref.js";
|
|
9
10
|
import quikchat from "quikchat/md";
|
|
10
|
-
import { getPlatform } from "../platform
|
|
11
|
-
import { reloadFileInTab } from "../files/files
|
|
11
|
+
import { getPlatform } from "../platform";
|
|
12
|
+
import { reloadFileInTab } from "../files/files";
|
|
12
13
|
|
|
13
14
|
// ─── State (module-level, persists across tab switches) ─────────────────────
|
|
14
15
|
|
|
@@ -19,27 +20,25 @@ import { reloadFileInTab } from "../files/files.js";
|
|
|
19
20
|
* toolUse?: { tool: string; input?: Record<string, unknown> };
|
|
20
21
|
* }[]}
|
|
21
22
|
*/
|
|
22
|
-
let messages
|
|
23
|
+
let messages: {
|
|
24
|
+
role: string;
|
|
25
|
+
content: string;
|
|
26
|
+
toolUse?: { tool: string; input?: Record<string, unknown> };
|
|
27
|
+
}[] = [];
|
|
23
28
|
let streaming = false;
|
|
24
|
-
let sessionId =
|
|
25
|
-
let authStatus =
|
|
26
|
-
"unknown"
|
|
27
|
-
);
|
|
29
|
+
let sessionId = null as string | null;
|
|
30
|
+
let authStatus = "unknown" as "authenticated" | "unauthenticated" | "checking" | "unknown";
|
|
28
31
|
let authError = "";
|
|
29
32
|
let currentAssistantText = "";
|
|
30
|
-
let eventSource =
|
|
33
|
+
let eventSource = null as EventSource | null;
|
|
31
34
|
let mounted = false;
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
let
|
|
37
|
-
/** @type {HTMLElement | null} */
|
|
38
|
-
let _quikChatEl = null;
|
|
39
|
-
let currentStreamMsgId = /** @type {number | null} */ (null);
|
|
36
|
+
let chatInstance: any = null;
|
|
37
|
+
let chatContainerEl: Element | null = null;
|
|
38
|
+
let _quikChatEl: HTMLElement | null = null;
|
|
39
|
+
let currentStreamMsgId = null as number | null;
|
|
40
40
|
let streamStarted = false;
|
|
41
|
-
|
|
42
|
-
let pendingFileReloads = new Set();
|
|
41
|
+
let pendingFileReloads: Set<string> = new Set();
|
|
43
42
|
|
|
44
43
|
// ─── Lifecycle ──────────────────────────────────────────────────────────────
|
|
45
44
|
|
|
@@ -49,15 +48,16 @@ export function mountAiPanel() {
|
|
|
49
48
|
checkAuth();
|
|
50
49
|
}
|
|
51
50
|
|
|
51
|
+
const _g = globalThis as unknown as { __jxRightPanelRender?: { render: () => void } };
|
|
52
|
+
|
|
52
53
|
function rerenderPanel() {
|
|
53
|
-
const { render } =
|
|
54
|
-
{};
|
|
54
|
+
const { render } = _g.__jxRightPanelRender || {};
|
|
55
55
|
if (render) render();
|
|
56
56
|
requestAnimationFrame(() => mountQuikChat());
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export function registerRightPanelRender(
|
|
60
|
-
|
|
59
|
+
export function registerRightPanelRender(fn: () => void) {
|
|
60
|
+
_g.__jxRightPanelRender = { render: fn };
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// ─── QuikChat Mount ────────────────────────────────────────────────────────
|
|
@@ -69,7 +69,7 @@ export function mountQuikChat() {
|
|
|
69
69
|
|
|
70
70
|
chatInstance = new quikchat(
|
|
71
71
|
container,
|
|
72
|
-
(
|
|
72
|
+
(_chat: any, msg: string) => {
|
|
73
73
|
handleUserSend(msg);
|
|
74
74
|
},
|
|
75
75
|
{
|
|
@@ -130,7 +130,7 @@ async function checkAuth() {
|
|
|
130
130
|
// ─── Messaging ──────────────────────────────────────────────────────────────
|
|
131
131
|
|
|
132
132
|
/** @param {string} text */
|
|
133
|
-
async function handleUserSend(text) {
|
|
133
|
+
async function handleUserSend(text: string) {
|
|
134
134
|
if (!text.trim() || streaming) return;
|
|
135
135
|
|
|
136
136
|
messages.push({ role: "user", content: text });
|
|
@@ -151,7 +151,7 @@ async function handleUserSend(text) {
|
|
|
151
151
|
if (!sessionId) {
|
|
152
152
|
const result = await plat.aiCreateSession({ message: text });
|
|
153
153
|
sessionId = result.id;
|
|
154
|
-
await connectStream(
|
|
154
|
+
await connectStream(sessionId!);
|
|
155
155
|
} else {
|
|
156
156
|
await plat.aiSendMessage(sessionId, text);
|
|
157
157
|
}
|
|
@@ -195,7 +195,7 @@ function newChat() {
|
|
|
195
195
|
|
|
196
196
|
// ─── SSE Stream ─────────────────────────────────────────────────────────────
|
|
197
197
|
|
|
198
|
-
async function connectStream(
|
|
198
|
+
async function connectStream(id: string) {
|
|
199
199
|
disconnectStream();
|
|
200
200
|
const plat = getPlatform();
|
|
201
201
|
const url = await Promise.resolve(plat.aiStreamUrl(id));
|
|
@@ -245,7 +245,7 @@ async function connectStream(/** @type {string} */ id) {
|
|
|
245
245
|
|
|
246
246
|
eventSource.addEventListener("error", (e) => {
|
|
247
247
|
try {
|
|
248
|
-
const data = JSON.parse(
|
|
248
|
+
const data = JSON.parse((e as MessageEvent).data);
|
|
249
249
|
if (data.error) {
|
|
250
250
|
if (chatInstance && currentStreamMsgId != null) {
|
|
251
251
|
chatInstance.messageReplaceContent(currentStreamMsgId, `Error: ${data.error}`);
|
|
@@ -288,7 +288,7 @@ function disconnectStream() {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
/** @param {any} data */
|
|
291
|
-
function handleAssistantMessage(data) {
|
|
291
|
+
function handleAssistantMessage(data: any) {
|
|
292
292
|
const content = data.message?.content || data.content;
|
|
293
293
|
if (!content) return;
|
|
294
294
|
|
|
@@ -332,7 +332,7 @@ function handleAssistantMessage(data) {
|
|
|
332
332
|
* @param {string} tool
|
|
333
333
|
* @param {Record<string, unknown>} [input]
|
|
334
334
|
*/
|
|
335
|
-
function formatToolLabel(tool, input) {
|
|
335
|
+
function formatToolLabel(tool: string, input?: Record<string, unknown>) {
|
|
336
336
|
switch (tool) {
|
|
337
337
|
case "Edit":
|
|
338
338
|
case "Write":
|
|
@@ -351,7 +351,7 @@ function formatToolLabel(tool, input) {
|
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
/** @param {string} s @param {number} max */
|
|
354
|
-
function truncate(s, max) {
|
|
354
|
+
function truncate(s: string, max: number) {
|
|
355
355
|
return s.length > max ? s.slice(0, max) + "…" : s;
|
|
356
356
|
}
|
|
357
357
|
|
|
@@ -398,7 +398,7 @@ export function renderAiPanelTemplate() {
|
|
|
398
398
|
<div
|
|
399
399
|
id="ai-quikchat"
|
|
400
400
|
${ref((el) => {
|
|
401
|
-
_quikChatEl =
|
|
401
|
+
_quikChatEl = (el as HTMLElement | null) || null;
|
|
402
402
|
})}
|
|
403
403
|
></div>
|
|
404
404
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Block action bar — extracted from studio.js (Phase 4h). Floating toolbar above selected elements
|
|
3
4
|
* with parent selector, move arrows, drag handle, component actions, and inline formatting.
|
|
@@ -8,26 +9,21 @@ import { styleMap } from "lit-html/directives/style-map.js";
|
|
|
8
9
|
import { ref } from "lit-html/directives/ref.js";
|
|
9
10
|
import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
10
11
|
|
|
11
|
-
import { getNodeAtPath, nodeLabel, parentElementPath, childIndex } from "../store
|
|
12
|
-
import { activeTab } from "../workspace/workspace
|
|
13
|
-
import { transactDoc, mutateMoveNode, mutateUpdateProperty } from "../tabs/transact
|
|
14
|
-
import { view } from "../view
|
|
15
|
-
import { isEditing, getActiveElement, getInlineActions } from "../editor/inline-edit
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
* @typedef {import("../state.js").JxPath} JxPath
|
|
28
|
-
*
|
|
29
|
-
* @typedef {import("../editor/inline-edit.js").InlineAction} InlineAction
|
|
30
|
-
*/
|
|
12
|
+
import { getNodeAtPath, nodeLabel, parentElementPath, childIndex } from "../store";
|
|
13
|
+
import { activeTab } from "../workspace/workspace";
|
|
14
|
+
import { transactDoc, mutateMoveNode, mutateUpdateProperty } from "../tabs/transact";
|
|
15
|
+
import { view } from "../view";
|
|
16
|
+
import { isEditing, getActiveElement, getInlineActions } from "../editor/inline-edit";
|
|
17
|
+
import type { InlineAction } from "../editor/inline-edit";
|
|
18
|
+
import { toggleInlineFormat, isTagActiveInSelection } from "../editor/inline-format";
|
|
19
|
+
import { componentRegistry } from "../files/components";
|
|
20
|
+
import { convertToComponent } from "../editor/convert-to-component";
|
|
21
|
+
import { findCanvasElement, getActivePanel } from "../canvas/canvas-helpers";
|
|
22
|
+
import { getLayerSlot } from "../ui/layers";
|
|
23
|
+
import { showSlashMenu } from "../editor/slash-menu";
|
|
24
|
+
import { getConvertTargets } from "../editor/convert-targets";
|
|
25
|
+
|
|
26
|
+
import type { JxPath } from "../state";
|
|
31
27
|
|
|
32
28
|
/**
|
|
33
29
|
* @type {{
|
|
@@ -35,7 +31,8 @@ import { getConvertTargets } from "../editor/convert-targets.js";
|
|
|
35
31
|
* navigateToComponent: (path: string) => void;
|
|
36
32
|
* } | null}
|
|
37
33
|
*/
|
|
38
|
-
let _ctx
|
|
34
|
+
let _ctx: { getCanvasMode: () => string; navigateToComponent: (path: string) => void } | null =
|
|
35
|
+
null;
|
|
39
36
|
|
|
40
37
|
/**
|
|
41
38
|
* Initialize the block action bar module.
|
|
@@ -45,12 +42,15 @@ let _ctx = null;
|
|
|
45
42
|
* navigateToComponent: (path: string) => void;
|
|
46
43
|
* }} ctx
|
|
47
44
|
*/
|
|
48
|
-
export function initBlockActionBar(ctx
|
|
45
|
+
export function initBlockActionBar(ctx: {
|
|
46
|
+
getCanvasMode: () => string;
|
|
47
|
+
navigateToComponent: (path: string) => void;
|
|
48
|
+
}) {
|
|
49
49
|
_ctx = ctx;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** Pre-built icon templates for inline format buttons (avoids unsafeStatic) */
|
|
53
|
-
const formatIconMap =
|
|
53
|
+
const formatIconMap = {
|
|
54
54
|
"sp-icon-text-bold": html`<sp-icon-text-bold slot="icon"></sp-icon-text-bold>`,
|
|
55
55
|
"sp-icon-text-italic": html`<sp-icon-text-italic slot="icon"></sp-icon-text-italic>`,
|
|
56
56
|
"sp-icon-text-underline": html`<sp-icon-text-underline slot="icon"></sp-icon-text-underline>`,
|
|
@@ -63,17 +63,17 @@ const formatIconMap = /** @type {Record<string, import("lit-html").TemplateResul
|
|
|
63
63
|
"sp-icon-text-subscript": html`<sp-icon-text-subscript slot="icon"></sp-icon-text-subscript>`,
|
|
64
64
|
"sp-icon-code": html`<sp-icon-code slot="icon"></sp-icon-code>`,
|
|
65
65
|
"sp-icon-link": html`<sp-icon-link slot="icon"></sp-icon-link>`,
|
|
66
|
-
})
|
|
66
|
+
} as Record<string, import("lit-html").TemplateResult>;
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Prevent the bar from stealing focus from contenteditable
|
|
70
70
|
*
|
|
71
71
|
* @param {MouseEvent} e
|
|
72
72
|
*/
|
|
73
|
-
function onBarMousedown(e) {
|
|
74
|
-
if (
|
|
75
|
-
if (
|
|
76
|
-
if (
|
|
73
|
+
function onBarMousedown(e: MouseEvent) {
|
|
74
|
+
if ((e.target as HTMLElement).closest("sp-textfield")) return;
|
|
75
|
+
if ((e.target as HTMLElement).closest(".bar-drag-handle")) return;
|
|
76
|
+
if ((e.target as HTMLElement).closest(".bar-tag--interactive")) return;
|
|
77
77
|
e.preventDefault();
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -82,9 +82,13 @@ function onBarMousedown(e) {
|
|
|
82
82
|
* @param {import("../editor/convert-targets.js").SlashCommand[]} targets
|
|
83
83
|
* @param {JxPath} selection
|
|
84
84
|
*/
|
|
85
|
-
function onTagBadgeClick(
|
|
85
|
+
function onTagBadgeClick(
|
|
86
|
+
e: MouseEvent,
|
|
87
|
+
targets: import("../editor/convert-targets.js").SlashCommand[],
|
|
88
|
+
selection: JxPath,
|
|
89
|
+
) {
|
|
86
90
|
e.stopPropagation();
|
|
87
|
-
const anchorEl =
|
|
91
|
+
const anchorEl = e.currentTarget as HTMLElement;
|
|
88
92
|
showSlashMenu(anchorEl, "", {
|
|
89
93
|
showFilter: targets.length > 6,
|
|
90
94
|
commands: targets,
|
|
@@ -106,24 +110,18 @@ function captureSelectionRange() {
|
|
|
106
110
|
* @param {MouseEvent} e
|
|
107
111
|
* @param {InlineAction} action
|
|
108
112
|
*/
|
|
109
|
-
function onFormatClick(e, action) {
|
|
113
|
+
function onFormatClick(e: MouseEvent, action: InlineAction) {
|
|
110
114
|
e.stopPropagation();
|
|
111
115
|
if (action.command === "link") {
|
|
112
|
-
showLinkPopover(
|
|
113
|
-
/** @type {HTMLElement} */ (
|
|
114
|
-
/** @type {HTMLElement} */ (e.target).closest("sp-action-button")
|
|
115
|
-
),
|
|
116
|
-
);
|
|
116
|
+
showLinkPopover((e.target as HTMLElement).closest("sp-action-button") as HTMLElement);
|
|
117
117
|
} else if (view.savedRange) {
|
|
118
118
|
const sel = window.getSelection();
|
|
119
119
|
const anchor = view.savedRange.startContainer;
|
|
120
120
|
const editableRoot = (
|
|
121
|
-
anchor?.nodeType === Node.ELEMENT_NODE
|
|
122
|
-
? /** @type {Element} */ (anchor)
|
|
123
|
-
: anchor?.parentElement
|
|
121
|
+
anchor?.nodeType === Node.ELEMENT_NODE ? (anchor as Element) : anchor?.parentElement
|
|
124
122
|
)?.closest("[contenteditable]");
|
|
125
123
|
if (editableRoot) {
|
|
126
|
-
|
|
124
|
+
(editableRoot as HTMLElement).focus();
|
|
127
125
|
sel?.removeAllRanges();
|
|
128
126
|
sel?.addRange(view.savedRange);
|
|
129
127
|
applyInlineFormat(action);
|
|
@@ -142,9 +140,9 @@ function renderParentSelector() {
|
|
|
142
140
|
size="xs"
|
|
143
141
|
quiet
|
|
144
142
|
title="Select parent: ${nodeLabel(parentNode)}"
|
|
145
|
-
@click=${(
|
|
143
|
+
@click=${(e: MouseEvent) => {
|
|
146
144
|
e.stopPropagation();
|
|
147
|
-
activeTab.value
|
|
145
|
+
activeTab.value!.session.selection = pPath;
|
|
148
146
|
}}
|
|
149
147
|
>
|
|
150
148
|
<sp-icon-back slot="icon"></sp-icon-back>
|
|
@@ -156,9 +154,9 @@ function renderMoveArrows() {
|
|
|
156
154
|
const tab = activeTab.value;
|
|
157
155
|
if (!tab?.session.selection) return nothing;
|
|
158
156
|
const sel = tab.session.selection;
|
|
159
|
-
const idx =
|
|
157
|
+
const idx = childIndex(sel) as number;
|
|
160
158
|
const pPath = parentElementPath(sel);
|
|
161
|
-
const parentNode = getNodeAtPath(tab.doc.document,
|
|
159
|
+
const parentNode = pPath ? getNodeAtPath(tab.doc.document, pPath) : null;
|
|
162
160
|
const siblings = parentNode?.children;
|
|
163
161
|
return html`
|
|
164
162
|
<sp-action-button
|
|
@@ -166,7 +164,7 @@ function renderMoveArrows() {
|
|
|
166
164
|
quiet
|
|
167
165
|
title="Move up"
|
|
168
166
|
?disabled=${idx <= 0}
|
|
169
|
-
@click=${(
|
|
167
|
+
@click=${(e: MouseEvent) => {
|
|
170
168
|
e.stopPropagation();
|
|
171
169
|
moveSelectionUp();
|
|
172
170
|
}}
|
|
@@ -178,7 +176,7 @@ function renderMoveArrows() {
|
|
|
178
176
|
quiet
|
|
179
177
|
title="Move down"
|
|
180
178
|
?disabled=${!siblings || idx >= siblings.length - 1}
|
|
181
|
-
@click=${(
|
|
179
|
+
@click=${(e: MouseEvent) => {
|
|
182
180
|
e.stopPropagation();
|
|
183
181
|
moveSelectionDown();
|
|
184
182
|
}}
|
|
@@ -193,9 +191,8 @@ function renderMoveArrows() {
|
|
|
193
191
|
*
|
|
194
192
|
* @param {InlineAction} action
|
|
195
193
|
*/
|
|
196
|
-
function applyInlineFormat(action) {
|
|
197
|
-
|
|
198
|
-
const cmdToTag = {
|
|
194
|
+
function applyInlineFormat(action: InlineAction) {
|
|
195
|
+
const cmdToTag: Record<string, string> = {
|
|
199
196
|
bold: "strong",
|
|
200
197
|
italic: "em",
|
|
201
198
|
underline: "u",
|
|
@@ -225,22 +222,17 @@ export function dismissBlockActionBar() {
|
|
|
225
222
|
}
|
|
226
223
|
|
|
227
224
|
/** @param {HTMLElement} anchorBtn */
|
|
228
|
-
function showLinkPopover(anchorBtn) {
|
|
225
|
+
function showLinkPopover(anchorBtn: HTMLElement) {
|
|
229
226
|
const host = getLayerSlot("popover", "link-popover");
|
|
230
227
|
litRender(nothing, host);
|
|
231
228
|
|
|
232
229
|
const sel = window.getSelection();
|
|
233
|
-
|
|
234
|
-
let existingLink = null;
|
|
230
|
+
let existingLink: HTMLAnchorElement | null = null;
|
|
235
231
|
if (sel?.rangeCount) {
|
|
236
|
-
|
|
237
|
-
let node = sel.anchorNode;
|
|
232
|
+
let node: Node | null = sel.anchorNode;
|
|
238
233
|
while (node && node !== document.body) {
|
|
239
|
-
if (
|
|
240
|
-
node
|
|
241
|
-
/** @type {Element} */ (node).tagName.toLowerCase() === "a"
|
|
242
|
-
) {
|
|
243
|
-
existingLink = /** @type {HTMLAnchorElement} */ (node);
|
|
234
|
+
if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === "a") {
|
|
235
|
+
existingLink = node as HTMLAnchorElement;
|
|
244
236
|
break;
|
|
245
237
|
}
|
|
246
238
|
node = node.parentNode;
|
|
@@ -249,8 +241,7 @@ function showLinkPopover(anchorBtn) {
|
|
|
249
241
|
|
|
250
242
|
const rect = anchorBtn.getBoundingClientRect();
|
|
251
243
|
|
|
252
|
-
|
|
253
|
-
let _linkField = null;
|
|
244
|
+
let _linkField: HTMLInputElement | null = null;
|
|
254
245
|
|
|
255
246
|
const onApply = () => {
|
|
256
247
|
const url = _linkField?.value || "";
|
|
@@ -272,7 +263,7 @@ function showLinkPopover(anchorBtn) {
|
|
|
272
263
|
renderBlockActionBar();
|
|
273
264
|
};
|
|
274
265
|
|
|
275
|
-
const onKeydown = (
|
|
266
|
+
const onKeydown = (e: KeyboardEvent) => {
|
|
276
267
|
if (e.key === "Enter") onApply();
|
|
277
268
|
else if (e.key === "Escape") {
|
|
278
269
|
litRender(nothing, host);
|
|
@@ -298,8 +289,8 @@ function showLinkPopover(anchorBtn) {
|
|
|
298
289
|
value=${existingLink?.getAttribute("href") || ""}
|
|
299
290
|
@keydown=${onKeydown}
|
|
300
291
|
${ref((el) => {
|
|
301
|
-
_linkField =
|
|
302
|
-
if (el) requestAnimationFrame(() =>
|
|
292
|
+
_linkField = (el as HTMLInputElement | null) || null;
|
|
293
|
+
if (el) requestAnimationFrame(() => (el as HTMLElement).focus());
|
|
303
294
|
})}
|
|
304
295
|
></sp-textfield>
|
|
305
296
|
<sp-action-button size="xs" @click=${onApply}>
|
|
@@ -319,9 +310,9 @@ function moveSelectionUp() {
|
|
|
319
310
|
const tab = activeTab.value;
|
|
320
311
|
if (!tab?.session.selection || tab.session.selection.length < 2) return;
|
|
321
312
|
const sel = tab.session.selection;
|
|
322
|
-
const idx =
|
|
313
|
+
const idx = childIndex(sel) as number;
|
|
323
314
|
if (idx <= 0) return;
|
|
324
|
-
const pPath =
|
|
315
|
+
const pPath = parentElementPath(sel) as JxPath;
|
|
325
316
|
transactDoc(tab, (t) => mutateMoveNode(t, sel, pPath, idx - 1));
|
|
326
317
|
tab.session.selection = [...pPath, "children", idx - 1];
|
|
327
318
|
}
|
|
@@ -331,8 +322,8 @@ function moveSelectionDown() {
|
|
|
331
322
|
const tab = activeTab.value;
|
|
332
323
|
if (!tab?.session.selection || tab.session.selection.length < 2) return;
|
|
333
324
|
const sel = tab.session.selection;
|
|
334
|
-
const idx =
|
|
335
|
-
const pPath =
|
|
325
|
+
const idx = childIndex(sel) as number;
|
|
326
|
+
const pPath = parentElementPath(sel) as JxPath;
|
|
336
327
|
const parentNode = getNodeAtPath(tab.doc.document, pPath);
|
|
337
328
|
const siblings = parentNode?.children;
|
|
338
329
|
if (!siblings || idx >= siblings.length - 1) return;
|
|
@@ -412,7 +403,7 @@ export function renderBlockActionBar() {
|
|
|
412
403
|
<span
|
|
413
404
|
class="bar-tag${badgeInteractive ? " bar-tag--interactive" : ""}"
|
|
414
405
|
@click=${badgeInteractive
|
|
415
|
-
? (
|
|
406
|
+
? (e: MouseEvent) => onTagBadgeClick(e, convertTargets, selection)
|
|
416
407
|
: nothing}
|
|
417
408
|
>${node.$id || (node.tagName ?? "div")}</span
|
|
418
409
|
>
|
|
@@ -428,7 +419,7 @@ export function renderBlockActionBar() {
|
|
|
428
419
|
view.selDragCleanup = null;
|
|
429
420
|
}
|
|
430
421
|
view.selDragCleanup = draggable({
|
|
431
|
-
element:
|
|
422
|
+
element: el as HTMLElement,
|
|
432
423
|
getInitialData: () => ({
|
|
433
424
|
type: "tree-node",
|
|
434
425
|
path: activeTab.value?.session.selection,
|
|
@@ -455,7 +446,7 @@ export function renderBlockActionBar() {
|
|
|
455
446
|
size="xs"
|
|
456
447
|
quiet
|
|
457
448
|
title="Edit Component"
|
|
458
|
-
@click=${() => _ctx?.navigateToComponent(
|
|
449
|
+
@click=${() => _ctx?.navigateToComponent(comp?.path as string)}
|
|
459
450
|
><sp-icon-edit slot="icon" size="xs"></sp-icon-edit
|
|
460
451
|
></sp-action-button>`;
|
|
461
452
|
}
|
|
@@ -485,7 +476,7 @@ export function renderBlockActionBar() {
|
|
|
485
476
|
value=${action.tag}
|
|
486
477
|
title="${action.label}${action.shortcut ? ` (${action.shortcut})` : ""}"
|
|
487
478
|
@mousedown=${captureSelectionRange}
|
|
488
|
-
@click=${(
|
|
479
|
+
@click=${(e: MouseEvent) => onFormatClick(e, action)}
|
|
489
480
|
>
|
|
490
481
|
${action.icon ? (formatIconMap[action.icon] ?? nothing) : nothing}
|
|
491
482
|
</sp-action-button>
|
|
@@ -501,7 +492,7 @@ export function renderBlockActionBar() {
|
|
|
501
492
|
|
|
502
493
|
// Post-render side effects
|
|
503
494
|
requestAnimationFrame(() => {
|
|
504
|
-
const bar =
|
|
495
|
+
const bar = view.blockActionBarEl?.firstElementChild as HTMLElement | null;
|
|
505
496
|
if (!bar) return;
|
|
506
497
|
// Clamp to window
|
|
507
498
|
const barRect = bar.getBoundingClientRect();
|