@jxsuite/studio 0.23.2 → 0.25.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 +2311 -718
- 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} +8 -6
- 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.ts +298 -0
- 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/utils/edit-display.js +0 -203
- /package/src/{reactivity.js → reactivity.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
],
|
|
16
16
|
"type": "module",
|
|
17
17
|
"exports": {
|
|
18
|
-
".": "./src/studio.
|
|
19
|
-
"./platform
|
|
18
|
+
".": "./src/studio.ts",
|
|
19
|
+
"./platform": "./src/platform.ts",
|
|
20
|
+
"./types": "./src/types.ts"
|
|
20
21
|
},
|
|
21
22
|
"publishConfig": {
|
|
22
23
|
"provenance": true
|
|
23
24
|
},
|
|
24
25
|
"scripts": {
|
|
25
|
-
"build": "bun build ./src/studio.
|
|
26
|
+
"build": "bun build ./src/studio.ts --outdir dist --target browser --sourcemap=linked && bun build ./node_modules/monaco-editor/esm/vs/editor/editor.worker.js --outdir dist/workers --target browser --entry-naming editor.worker.js && bun build ./node_modules/monaco-editor/esm/vs/language/json/json.worker.js --outdir dist/workers --target browser --entry-naming json.worker.js && bun build ./node_modules/monaco-editor/esm/vs/language/typescript/ts.worker.js --outdir dist/workers --target browser --entry-naming ts.worker.js",
|
|
26
27
|
"gen:webdata": "bun run scripts/gen-webdata.js",
|
|
27
28
|
"test": "bun test --isolate",
|
|
28
29
|
"test:coverage": "bun test --isolate --coverage",
|
|
@@ -31,9 +32,9 @@
|
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
|
|
33
34
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
|
34
|
-
"@jxsuite/parser": "^0.
|
|
35
|
-
"@jxsuite/runtime": "^0.
|
|
36
|
-
"@jxsuite/schema": "^0.
|
|
35
|
+
"@jxsuite/parser": "^0.25.0",
|
|
36
|
+
"@jxsuite/runtime": "^0.25.0",
|
|
37
|
+
"@jxsuite/schema": "^0.25.0",
|
|
37
38
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
38
39
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
39
40
|
"@spectrum-web-components/action-button": "^1.12.1",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Browse modal — fullscreen overlay for the project file browser (Manage view). Opens via toolbar
|
|
3
4
|
* button; selecting a file closes the modal and opens it in the editor.
|
|
@@ -5,20 +6,18 @@
|
|
|
5
6
|
|
|
6
7
|
import { html } from "lit-html";
|
|
7
8
|
import { ref } from "lit-html/directives/ref.js";
|
|
8
|
-
import { renderBrowse } from "./browse
|
|
9
|
-
import { openFileInTab } from "../files/files
|
|
10
|
-
import { openModal } from "../ui/layers
|
|
9
|
+
import { renderBrowse } from "./browse";
|
|
10
|
+
import { openFileInTab } from "../files/files";
|
|
11
|
+
import { openModal } from "../ui/layers";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
let _handle = null;
|
|
13
|
+
let _handle: ReturnType<typeof openModal> | null = null;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
let _escHandler = null;
|
|
15
|
+
let _escHandler: ((e: KeyboardEvent) => void) | null = null;
|
|
17
16
|
|
|
18
17
|
export function openBrowseModal() {
|
|
19
18
|
if (_handle) return;
|
|
20
19
|
|
|
21
|
-
_escHandler = (
|
|
20
|
+
_escHandler = (e: KeyboardEvent) => {
|
|
22
21
|
if (e.key === "Escape") closeBrowseModal();
|
|
23
22
|
};
|
|
24
23
|
document.addEventListener("keydown", _escHandler, true);
|
|
@@ -37,8 +36,8 @@ export function openBrowseModal() {
|
|
|
37
36
|
${ref((el) => {
|
|
38
37
|
if (el) {
|
|
39
38
|
requestAnimationFrame(() => {
|
|
40
|
-
renderBrowse(
|
|
41
|
-
openFile: (
|
|
39
|
+
renderBrowse(el as HTMLElement, {
|
|
40
|
+
openFile: (path: string) => {
|
|
42
41
|
closeBrowseModal();
|
|
43
42
|
openFileInTab(path);
|
|
44
43
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Manage view — project-level file browser with grid and table views.
|
|
3
4
|
*
|
|
@@ -9,18 +10,20 @@
|
|
|
9
10
|
import { html, render as litRender, nothing } from "lit-html";
|
|
10
11
|
import { ref } from "lit-html/directives/ref.js";
|
|
11
12
|
import { repeat } from "lit-html/directives/repeat.js";
|
|
12
|
-
import { getPlatform } from "../platform
|
|
13
|
-
import { projectState } from "../store
|
|
14
|
-
import { yamlDefault } from "../settings/schema-field-ui
|
|
15
|
-
import { invalidateMediaCache } from "../ui/media-picker
|
|
16
|
-
import { statusMessage } from "../panels/statusbar
|
|
17
|
-
import { componentRegistry } from "../files/components
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
import { renderComponentPreview } from "../panels/stylebook-panel.js";
|
|
13
|
+
import { getPlatform } from "../platform";
|
|
14
|
+
import { projectState } from "../store";
|
|
15
|
+
import { yamlDefault } from "../settings/schema-field-ui";
|
|
16
|
+
import { invalidateMediaCache } from "../ui/media-picker";
|
|
17
|
+
import { statusMessage } from "../panels/statusbar";
|
|
18
|
+
import { componentRegistry } from "../files/components";
|
|
19
|
+
|
|
20
|
+
import { showDialog, renderPopover } from "../ui/layers";
|
|
21
|
+
import { renderComponentPreview } from "../panels/stylebook-panel";
|
|
22
22
|
import { renderNode, buildScope, setSkipServerFunctions } from "@jxsuite/runtime";
|
|
23
|
-
import { loadMarkdown } from "../files/file-ops
|
|
23
|
+
import { loadMarkdown } from "../files/file-ops";
|
|
24
|
+
|
|
25
|
+
import type { ComponentEntry } from "../files/components";
|
|
26
|
+
import type { ContentTypeDef } from "@jxsuite/schema/types";
|
|
24
27
|
|
|
25
28
|
// ─── Category definitions ────────────────────────────────────────────────────
|
|
26
29
|
|
|
@@ -58,10 +61,8 @@ const MEDIA_EXTENSIONS = new Set([
|
|
|
58
61
|
|
|
59
62
|
let activeCategory = "all";
|
|
60
63
|
let searchQuery = "";
|
|
61
|
-
|
|
62
|
-
let
|
|
63
|
-
/** @type {{ name: string; path: string; type: string; category: string; ext: string }[]} */
|
|
64
|
-
let fileCache = [];
|
|
64
|
+
let viewMode: "grid" | "table" = "grid";
|
|
65
|
+
let fileCache: { name: string; path: string; type: string; category: string; ext: string }[] = [];
|
|
65
66
|
let loading = false;
|
|
66
67
|
/** Track which projectDirs were used for the last load, so we re-scan when they change. */
|
|
67
68
|
let lastProjectDirsKey = "";
|
|
@@ -69,7 +70,7 @@ let lastProjectDirsKey = "";
|
|
|
69
70
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
70
71
|
|
|
71
72
|
/** @param {string} name */
|
|
72
|
-
function extOf(name) {
|
|
73
|
+
function extOf(name: string) {
|
|
73
74
|
const dot = name.lastIndexOf(".");
|
|
74
75
|
return dot > 0 ? name.slice(dot).toLowerCase() : "";
|
|
75
76
|
}
|
|
@@ -86,12 +87,12 @@ const IMAGE_EXTENSIONS = new Set([
|
|
|
86
87
|
]);
|
|
87
88
|
|
|
88
89
|
/** @param {string} ext */
|
|
89
|
-
function isImage(ext) {
|
|
90
|
+
function isImage(ext: string) {
|
|
90
91
|
return IMAGE_EXTENSIONS.has(ext);
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/** Map a file path to a display category. Media files override by extension. */
|
|
94
|
-
function categoryFor(
|
|
95
|
+
function categoryFor(dir: string, ext: string) {
|
|
95
96
|
if (ext && MEDIA_EXTENSIONS.has(ext)) return "Media";
|
|
96
97
|
if (dir.startsWith("pages")) return "Pages";
|
|
97
98
|
if (dir.startsWith("layouts")) return "Layouts";
|
|
@@ -112,9 +113,11 @@ function categoryFor(/** @type {string} */ dir, /** @type {string} */ ext) {
|
|
|
112
113
|
* { name: string; path: string; type: string; category: string; ext: string }[]
|
|
113
114
|
* >}
|
|
114
115
|
*/
|
|
115
|
-
async function collectFiles(
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
async function collectFiles(
|
|
117
|
+
dir: string,
|
|
118
|
+
platform: ReturnType<typeof getPlatform>,
|
|
119
|
+
): Promise<{ name: string; path: string; type: string; category: string; ext: string }[]> {
|
|
120
|
+
const results: { name: string; path: string; type: string; category: string; ext: string }[] = [];
|
|
118
121
|
try {
|
|
119
122
|
const entries = await platform.listDirectory(dir);
|
|
120
123
|
for (const entry of entries) {
|
|
@@ -148,11 +151,11 @@ async function collectFiles(dir, platform) {
|
|
|
148
151
|
* @param {string} filePath
|
|
149
152
|
* @returns {string | null}
|
|
150
153
|
*/
|
|
151
|
-
function contentTypeFor(filePath) {
|
|
154
|
+
function contentTypeFor(filePath: string) {
|
|
152
155
|
const config = projectState?.projectConfig;
|
|
153
156
|
if (!config?.contentTypes) return null;
|
|
154
157
|
for (const [name, def] of Object.entries(config.contentTypes)) {
|
|
155
|
-
const d =
|
|
158
|
+
const d = def as ContentTypeDef;
|
|
156
159
|
if (!d.source) continue;
|
|
157
160
|
const prefix = d.source.replace(/^\.\//, "").replace(/\/$/, "");
|
|
158
161
|
if (filePath.startsWith(prefix + "/") || filePath === prefix) {
|
|
@@ -170,7 +173,7 @@ async function loadFiles() {
|
|
|
170
173
|
const platform = getPlatform();
|
|
171
174
|
const dirs = projectState.projectDirs || [];
|
|
172
175
|
lastProjectDirsKey = dirs.join(",");
|
|
173
|
-
const all = await Promise.all(dirs.map((
|
|
176
|
+
const all = await Promise.all(dirs.map((d: string) => collectFiles(d, platform)));
|
|
174
177
|
fileCache = all.flat();
|
|
175
178
|
fileCache.sort((a, b) => a.path.localeCompare(b.path));
|
|
176
179
|
loading = false;
|
|
@@ -210,7 +213,7 @@ const ENTITY_TYPES = [
|
|
|
210
213
|
* @param {string} contentTypeName
|
|
211
214
|
* @returns {string}
|
|
212
215
|
*/
|
|
213
|
-
function buildFrontmatterYaml(contentTypeName) {
|
|
216
|
+
function buildFrontmatterYaml(contentTypeName: string) {
|
|
214
217
|
const config = projectState?.projectConfig;
|
|
215
218
|
const col = config?.contentTypes?.[contentTypeName];
|
|
216
219
|
if (!col?.schema?.properties) return "title: Untitled\n";
|
|
@@ -232,7 +235,7 @@ function getContentTypeTypes() {
|
|
|
232
235
|
const config = projectState?.projectConfig;
|
|
233
236
|
if (!config?.contentTypes) return [];
|
|
234
237
|
return Object.entries(config.contentTypes).map(([name, def]) => {
|
|
235
|
-
const d =
|
|
238
|
+
const d = def as ContentTypeDef;
|
|
236
239
|
const dir = d.source ? d.source.replace(/^\.\//, "").replace(/\/$/, "") : name;
|
|
237
240
|
return {
|
|
238
241
|
key: `contentType:${name}`,
|
|
@@ -251,7 +254,11 @@ function getContentTypeTypes() {
|
|
|
251
254
|
* @param {HTMLElement} container
|
|
252
255
|
* @param {{ openFile: (path: string) => void }} ctx
|
|
253
256
|
*/
|
|
254
|
-
async function handleNewEntity(
|
|
257
|
+
async function handleNewEntity(
|
|
258
|
+
typeKey: string,
|
|
259
|
+
container: HTMLElement,
|
|
260
|
+
ctx: { openFile: (path: string) => void },
|
|
261
|
+
) {
|
|
255
262
|
const isContentType = typeKey.startsWith("contentType:");
|
|
256
263
|
const contentTypeName = isContentType ? typeKey.slice("contentType:".length) : null;
|
|
257
264
|
const allTypes = [...ENTITY_TYPES, ...getContentTypeTypes()];
|
|
@@ -304,7 +311,11 @@ const UPLOAD_ACCEPT = [
|
|
|
304
311
|
* @param {HTMLElement} container
|
|
305
312
|
* @param {{ openFile: (path: string) => void }} ctx
|
|
306
313
|
*/
|
|
307
|
-
async function handleUpload(
|
|
314
|
+
async function handleUpload(
|
|
315
|
+
files: FileList | File[],
|
|
316
|
+
container: HTMLElement,
|
|
317
|
+
ctx: { openFile: (path: string) => void },
|
|
318
|
+
) {
|
|
308
319
|
const platform = getPlatform();
|
|
309
320
|
for (const file of files) {
|
|
310
321
|
const destPath = `public/${file.name}`;
|
|
@@ -317,8 +328,7 @@ async function handleUpload(files, container, ctx) {
|
|
|
317
328
|
|
|
318
329
|
// ─── Context menu ───────────────────────────────────────────────────────────
|
|
319
330
|
|
|
320
|
-
|
|
321
|
-
let _browseCtxHandle = null;
|
|
331
|
+
let _browseCtxHandle: ReturnType<typeof renderPopover> | null = null;
|
|
322
332
|
|
|
323
333
|
function dismissBrowseContextMenu() {
|
|
324
334
|
if (_browseCtxHandle) {
|
|
@@ -333,7 +343,12 @@ function dismissBrowseContextMenu() {
|
|
|
333
343
|
* @param {HTMLElement} container
|
|
334
344
|
* @param {{ openFile: (path: string) => void }} ctx
|
|
335
345
|
*/
|
|
336
|
-
function showBrowseContextMenu(
|
|
346
|
+
function showBrowseContextMenu(
|
|
347
|
+
e: MouseEvent,
|
|
348
|
+
file: { name: string; path: string; ext: string },
|
|
349
|
+
container: HTMLElement,
|
|
350
|
+
ctx: { openFile: (path: string) => void },
|
|
351
|
+
) {
|
|
337
352
|
e.preventDefault();
|
|
338
353
|
e.stopPropagation();
|
|
339
354
|
dismissBrowseContextMenu();
|
|
@@ -368,7 +383,7 @@ function showBrowseContextMenu(e, file, container, ctx) {
|
|
|
368
383
|
${ref((el) => {
|
|
369
384
|
if (!el) return;
|
|
370
385
|
requestAnimationFrame(() => {
|
|
371
|
-
const popover =
|
|
386
|
+
const popover = el as HTMLElement;
|
|
372
387
|
const menuRect = popover.getBoundingClientRect();
|
|
373
388
|
if (x + menuRect.width > window.innerWidth) x = window.innerWidth - menuRect.width - 4;
|
|
374
389
|
if (y + menuRect.height > window.innerHeight)
|
|
@@ -408,7 +423,11 @@ function showBrowseContextMenu(e, file, container, ctx) {
|
|
|
408
423
|
* @param {HTMLElement} container
|
|
409
424
|
* @param {{ openFile: (path: string) => void }} ctx
|
|
410
425
|
*/
|
|
411
|
-
async function browseRenameFile(
|
|
426
|
+
async function browseRenameFile(
|
|
427
|
+
file: { name: string; path: string; ext: string },
|
|
428
|
+
container: HTMLElement,
|
|
429
|
+
ctx: { openFile: (path: string) => void },
|
|
430
|
+
) {
|
|
412
431
|
const newName = await showRenameDialog(file.name);
|
|
413
432
|
if (!newName || newName === file.name) return;
|
|
414
433
|
const filePath = file.path.replaceAll("\\", "/");
|
|
@@ -420,8 +439,8 @@ async function browseRenameFile(file, container, ctx) {
|
|
|
420
439
|
invalidateBrowseCache();
|
|
421
440
|
renderBrowse(container, ctx);
|
|
422
441
|
statusMessage(`Renamed to ${newName}`);
|
|
423
|
-
} catch (
|
|
424
|
-
statusMessage(`Error: ${
|
|
442
|
+
} catch (e) {
|
|
443
|
+
statusMessage(`Error: ${(e as Error).message}`);
|
|
425
444
|
}
|
|
426
445
|
}
|
|
427
446
|
|
|
@@ -430,7 +449,11 @@ async function browseRenameFile(file, container, ctx) {
|
|
|
430
449
|
* @param {HTMLElement} container
|
|
431
450
|
* @param {{ openFile: (path: string) => void }} ctx
|
|
432
451
|
*/
|
|
433
|
-
async function browseDuplicateFile(
|
|
452
|
+
async function browseDuplicateFile(
|
|
453
|
+
file: { name: string; path: string; ext: string },
|
|
454
|
+
container: HTMLElement,
|
|
455
|
+
ctx: { openFile: (path: string) => void },
|
|
456
|
+
) {
|
|
434
457
|
const filePath = file.path.replaceAll("\\", "/");
|
|
435
458
|
const parentDir = filePath.includes("/") ? filePath.substring(0, filePath.lastIndexOf("/")) : ".";
|
|
436
459
|
const baseName = file.name.replace(/(\.[^.]+)$/, "");
|
|
@@ -444,8 +467,8 @@ async function browseDuplicateFile(file, container, ctx) {
|
|
|
444
467
|
invalidateBrowseCache();
|
|
445
468
|
renderBrowse(container, ctx);
|
|
446
469
|
statusMessage(`Duplicated as ${copyName}`);
|
|
447
|
-
} catch (
|
|
448
|
-
statusMessage(`Error: ${
|
|
470
|
+
} catch (e) {
|
|
471
|
+
statusMessage(`Error: ${(e as Error).message}`);
|
|
449
472
|
}
|
|
450
473
|
}
|
|
451
474
|
|
|
@@ -454,7 +477,11 @@ async function browseDuplicateFile(file, container, ctx) {
|
|
|
454
477
|
* @param {HTMLElement} container
|
|
455
478
|
* @param {{ openFile: (path: string) => void }} ctx
|
|
456
479
|
*/
|
|
457
|
-
async function browseDeleteFile(
|
|
480
|
+
async function browseDeleteFile(
|
|
481
|
+
file: { name: string; path: string; ext: string },
|
|
482
|
+
container: HTMLElement,
|
|
483
|
+
ctx: { openFile: (path: string) => void },
|
|
484
|
+
) {
|
|
458
485
|
const confirmed = await showDeleteDialog(file.name);
|
|
459
486
|
if (!confirmed) return;
|
|
460
487
|
try {
|
|
@@ -463,8 +490,8 @@ async function browseDeleteFile(file, container, ctx) {
|
|
|
463
490
|
invalidateBrowseCache();
|
|
464
491
|
renderBrowse(container, ctx);
|
|
465
492
|
statusMessage(`Deleted ${file.name}`);
|
|
466
|
-
} catch (
|
|
467
|
-
statusMessage(`Error: ${
|
|
493
|
+
} catch (e) {
|
|
494
|
+
statusMessage(`Error: ${(e as Error).message}`);
|
|
468
495
|
}
|
|
469
496
|
}
|
|
470
497
|
|
|
@@ -474,10 +501,10 @@ async function browseDeleteFile(file, container, ctx) {
|
|
|
474
501
|
* @param {string} currentName
|
|
475
502
|
* @returns {Promise<string | null>}
|
|
476
503
|
*/
|
|
477
|
-
function showRenameDialog(currentName) {
|
|
504
|
+
function showRenameDialog(currentName: string): Promise<string | null> {
|
|
478
505
|
let value = currentName;
|
|
479
506
|
|
|
480
|
-
return showDialog((done) => {
|
|
507
|
+
return showDialog<string | null>((done) => {
|
|
481
508
|
function confirm() {
|
|
482
509
|
const trimmed = value.trim();
|
|
483
510
|
if (!trimmed) return;
|
|
@@ -499,10 +526,10 @@ function showRenameDialog(currentName) {
|
|
|
499
526
|
<sp-textfield
|
|
500
527
|
style="width:100%"
|
|
501
528
|
value=${value}
|
|
502
|
-
@input=${(
|
|
503
|
-
value =
|
|
529
|
+
@input=${(e: Event) => {
|
|
530
|
+
value = (e.target as HTMLInputElement).value || "";
|
|
504
531
|
}}
|
|
505
|
-
@keydown=${(
|
|
532
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
506
533
|
if (e.key === "Enter") confirm();
|
|
507
534
|
}}
|
|
508
535
|
></sp-textfield>
|
|
@@ -511,7 +538,7 @@ function showRenameDialog(currentName) {
|
|
|
511
538
|
|
|
512
539
|
requestAnimationFrame(() => {
|
|
513
540
|
const layer = document.getElementById("layer-dialog");
|
|
514
|
-
const tf =
|
|
541
|
+
const tf = layer?.querySelector("sp-textfield") as HTMLElement | null;
|
|
515
542
|
if (tf) {
|
|
516
543
|
tf.focus();
|
|
517
544
|
const input = tf.shadowRoot?.querySelector("input");
|
|
@@ -527,7 +554,7 @@ function showRenameDialog(currentName) {
|
|
|
527
554
|
* @param {string} fileName
|
|
528
555
|
* @returns {Promise<boolean>}
|
|
529
556
|
*/
|
|
530
|
-
function showDeleteDialog(fileName) {
|
|
557
|
+
function showDeleteDialog(fileName: string) {
|
|
531
558
|
return showDialog(
|
|
532
559
|
(done) => html`
|
|
533
560
|
<sp-dialog-wrapper
|
|
@@ -549,8 +576,7 @@ function showDeleteDialog(fileName) {
|
|
|
549
576
|
|
|
550
577
|
// ─── Grid view helpers ──────────────────────────────────────────────────────
|
|
551
578
|
|
|
552
|
-
|
|
553
|
-
const _previewCache = new Map();
|
|
579
|
+
const _previewCache: Map<string, HTMLElement> = new Map();
|
|
554
580
|
|
|
555
581
|
/**
|
|
556
582
|
* Render a live preview for a page or layout file (JSON or Markdown).
|
|
@@ -558,7 +584,7 @@ const _previewCache = new Map();
|
|
|
558
584
|
* @param {string} filePath
|
|
559
585
|
* @returns {Promise<HTMLElement | null>}
|
|
560
586
|
*/
|
|
561
|
-
async function renderDocPreview(filePath) {
|
|
587
|
+
async function renderDocPreview(filePath: string) {
|
|
562
588
|
try {
|
|
563
589
|
const platform = getPlatform();
|
|
564
590
|
const content = await platform.readFile(filePath);
|
|
@@ -572,7 +598,7 @@ async function renderDocPreview(filePath) {
|
|
|
572
598
|
}
|
|
573
599
|
const scope = buildScope(doc.state || {});
|
|
574
600
|
const el = renderNode(doc, scope);
|
|
575
|
-
return
|
|
601
|
+
return el as HTMLElement | null;
|
|
576
602
|
} catch {
|
|
577
603
|
return null;
|
|
578
604
|
}
|
|
@@ -584,22 +610,18 @@ async function renderDocPreview(filePath) {
|
|
|
584
610
|
* @param {Element} el — the .element-card-preview div
|
|
585
611
|
* @param {{ path: string; category: string }} file
|
|
586
612
|
*/
|
|
587
|
-
async function loadPreview(el, file) {
|
|
613
|
+
async function loadPreview(el: Element, file: { path: string; category: string }) {
|
|
588
614
|
// Already populated
|
|
589
615
|
if (el.firstElementChild) return;
|
|
590
616
|
|
|
591
|
-
|
|
592
|
-
let preview = _previewCache.get(file.path);
|
|
617
|
+
let preview: HTMLElement | undefined = _previewCache.get(file.path);
|
|
593
618
|
if (!preview) {
|
|
594
619
|
try {
|
|
595
|
-
const comp = componentRegistry.find(
|
|
596
|
-
(/** @type {ComponentEntry} */ c) => c.path === file.path,
|
|
597
|
-
);
|
|
620
|
+
const comp = componentRegistry.find((c: ComponentEntry) => c.path === file.path);
|
|
598
621
|
if (comp) {
|
|
599
|
-
preview =
|
|
622
|
+
preview = (await renderComponentPreview(comp)) as HTMLElement | undefined;
|
|
600
623
|
} else {
|
|
601
|
-
preview =
|
|
602
|
-
undefined;
|
|
624
|
+
preview = ((await renderDocPreview(file.path)) as HTMLElement | undefined) || undefined;
|
|
603
625
|
}
|
|
604
626
|
if (preview) _previewCache.set(file.path, /** @type {HTMLElement} */ (preview));
|
|
605
627
|
} catch {
|
|
@@ -616,7 +638,11 @@ async function loadPreview(el, file) {
|
|
|
616
638
|
* @param {HTMLElement} container
|
|
617
639
|
* @param {{ openFile: (path: string) => void }} ctx
|
|
618
640
|
*/
|
|
619
|
-
function renderCard(
|
|
641
|
+
function renderCard(
|
|
642
|
+
file: { name: string; path: string; type: string; category: string; ext: string },
|
|
643
|
+
container: HTMLElement,
|
|
644
|
+
ctx: { openFile: (path: string) => void },
|
|
645
|
+
) {
|
|
620
646
|
const isImg = isImage(file.ext);
|
|
621
647
|
const needsPreview =
|
|
622
648
|
file.category === "Components" ||
|
|
@@ -628,13 +654,12 @@ function renderCard(file, container, ctx) {
|
|
|
628
654
|
<div
|
|
629
655
|
class="element-card"
|
|
630
656
|
@click=${() => ctx.openFile(file.path)}
|
|
631
|
-
@contextmenu=${(
|
|
632
|
-
showBrowseContextMenu(e, file, container, ctx)}
|
|
657
|
+
@contextmenu=${(e: MouseEvent) => showBrowseContextMenu(e, file, container, ctx)}
|
|
633
658
|
>
|
|
634
659
|
<div
|
|
635
660
|
class="element-card-preview"
|
|
636
661
|
${needsPreview
|
|
637
|
-
? ref((
|
|
662
|
+
? ref((el: Element | undefined) => {
|
|
638
663
|
if (el) loadPreview(el, file);
|
|
639
664
|
})
|
|
640
665
|
: nothing}
|
|
@@ -661,7 +686,10 @@ function renderCard(file, container, ctx) {
|
|
|
661
686
|
* @param {HTMLElement} container — the #canvas-wrap element
|
|
662
687
|
* @param {{ openFile: (path: string) => void }} ctx — callbacks from studio.js
|
|
663
688
|
*/
|
|
664
|
-
export async function renderBrowse(
|
|
689
|
+
export async function renderBrowse(
|
|
690
|
+
container: HTMLElement,
|
|
691
|
+
ctx: { openFile: (path: string) => void },
|
|
692
|
+
) {
|
|
665
693
|
// Re-load when projectDirs changed (e.g. project opened after initial render)
|
|
666
694
|
const currentKey = (projectState?.projectDirs || []).join(",");
|
|
667
695
|
if ((!fileCache.length && !loading) || currentKey !== lastProjectDirsKey) {
|
|
@@ -693,11 +721,11 @@ export async function renderBrowse(container, ctx) {
|
|
|
693
721
|
size="s"
|
|
694
722
|
placeholder="Filter files..."
|
|
695
723
|
.value=${searchQuery}
|
|
696
|
-
@input=${(
|
|
697
|
-
searchQuery =
|
|
724
|
+
@input=${(e: Event) => {
|
|
725
|
+
searchQuery = (e.target as HTMLInputElement).value;
|
|
698
726
|
renderBrowse(container, ctx);
|
|
699
727
|
}}
|
|
700
|
-
@submit=${(
|
|
728
|
+
@submit=${(e: Event) => e.preventDefault()}
|
|
701
729
|
></sp-search>
|
|
702
730
|
<overlay-trigger placement="bottom-start" triggered-by="click">
|
|
703
731
|
<sp-action-button size="s" slot="trigger">
|
|
@@ -705,8 +733,8 @@ export async function renderBrowse(container, ctx) {
|
|
|
705
733
|
</sp-action-button>
|
|
706
734
|
<sp-popover slot="click-content" tip>
|
|
707
735
|
<sp-menu
|
|
708
|
-
@change=${(
|
|
709
|
-
handleNewEntity(
|
|
736
|
+
@change=${(e: Event) =>
|
|
737
|
+
handleNewEntity((e.target as HTMLSelectElement).value, container, ctx)}
|
|
710
738
|
>
|
|
711
739
|
${ENTITY_TYPES.map((t) => html`<sp-menu-item value=${t.key}>${t.label}</sp-menu-item>`)}
|
|
712
740
|
${contentTypeTypes.length
|
|
@@ -720,9 +748,7 @@ export async function renderBrowse(container, ctx) {
|
|
|
720
748
|
<sp-action-button
|
|
721
749
|
size="s"
|
|
722
750
|
@click=${() => {
|
|
723
|
-
const input =
|
|
724
|
-
container.querySelector(".browse-upload-input")
|
|
725
|
-
);
|
|
751
|
+
const input = container.querySelector(".browse-upload-input") as HTMLInputElement;
|
|
726
752
|
if (input) input.click();
|
|
727
753
|
}}
|
|
728
754
|
>
|
|
@@ -734,8 +760,8 @@ export async function renderBrowse(container, ctx) {
|
|
|
734
760
|
accept=${UPLOAD_ACCEPT}
|
|
735
761
|
class="browse-upload-input"
|
|
736
762
|
style="display:none"
|
|
737
|
-
@change=${(
|
|
738
|
-
const input =
|
|
763
|
+
@change=${(e: Event) => {
|
|
764
|
+
const input = e.target as HTMLInputElement;
|
|
739
765
|
if (input.files?.length) handleUpload(input.files, container, ctx);
|
|
740
766
|
input.value = "";
|
|
741
767
|
}}
|
|
@@ -790,8 +816,7 @@ export async function renderBrowse(container, ctx) {
|
|
|
790
816
|
class="browse-row"
|
|
791
817
|
style=${isImage(f.ext) ? "cursor:default" : ""}
|
|
792
818
|
@click=${isImage(f.ext) ? nothing : () => ctx.openFile(f.path)}
|
|
793
|
-
@contextmenu=${(
|
|
794
|
-
showBrowseContextMenu(e, f, container, ctx)}
|
|
819
|
+
@contextmenu=${(e: MouseEvent) => showBrowseContextMenu(e, f, container, ctx)}
|
|
795
820
|
>
|
|
796
821
|
<sp-table-cell class="browse-name-cell"
|
|
797
822
|
>${isImage(f.ext)
|
|
@@ -824,16 +849,16 @@ export async function renderBrowse(container, ctx) {
|
|
|
824
849
|
const tpl = html`
|
|
825
850
|
<div
|
|
826
851
|
class="browse-view"
|
|
827
|
-
@dragover=${(
|
|
852
|
+
@dragover=${(e: DragEvent) => {
|
|
828
853
|
e.preventDefault();
|
|
829
|
-
|
|
854
|
+
(e.currentTarget as HTMLElement).classList.add("browse-drop-active");
|
|
830
855
|
}}
|
|
831
|
-
@dragleave=${(
|
|
832
|
-
|
|
856
|
+
@dragleave=${(e: DragEvent) => {
|
|
857
|
+
(e.currentTarget as HTMLElement).classList.remove("browse-drop-active");
|
|
833
858
|
}}
|
|
834
|
-
@drop=${(
|
|
859
|
+
@drop=${(e: DragEvent) => {
|
|
835
860
|
e.preventDefault();
|
|
836
|
-
|
|
861
|
+
(e.currentTarget as HTMLElement).classList.remove("browse-drop-active");
|
|
837
862
|
const droppedFiles = e.dataTransfer?.files;
|
|
838
863
|
if (droppedFiles?.length) handleUpload(droppedFiles, container, ctx);
|
|
839
864
|
}}
|