@jxsuite/studio 0.14.0 → 0.16.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 +10189 -10091
- package/dist/studio.js.map +41 -41
- package/package.json +2 -2
- package/src/canvas/canvas-render.js +50 -40
- package/src/editor/context-menu.js +47 -10
- package/src/files/file-ops.js +4 -2
- package/src/files/files.js +67 -67
- package/src/panels/editors.js +10 -6
- package/src/panels/git-panel.js +17 -15
- package/src/panels/left-panel.js +4 -0
- package/src/panels/signals-panel.js +63 -40
- package/src/platforms/devserver.js +10 -0
- package/src/services/code-services.js +4 -4
- package/src/studio.js +18 -4
- package/src/tabs/transact.js +25 -3
- package/src/workspace/workspace.js +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
|
|
32
32
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
|
33
|
-
"@jxsuite/runtime": "^0.
|
|
33
|
+
"@jxsuite/runtime": "^0.15.0",
|
|
34
34
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
35
35
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
36
36
|
"@spectrum-web-components/action-button": "^1.12.1",
|
|
@@ -10,6 +10,7 @@ import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
|
|
|
10
10
|
import { canvasWrap, canvasPanels, updateCanvas } from "../store.js";
|
|
11
11
|
import { activeTab } from "../workspace/workspace.js";
|
|
12
12
|
import { view } from "../view.js";
|
|
13
|
+
import { loadMarkdown } from "../files/file-ops.js";
|
|
13
14
|
import {
|
|
14
15
|
canvasPanelTemplate,
|
|
15
16
|
applyTransform,
|
|
@@ -144,6 +145,7 @@ export function renderCanvas() {
|
|
|
144
145
|
// Reset inline style overrides from other modes
|
|
145
146
|
canvasWrap.style.padding = "";
|
|
146
147
|
canvasWrap.style.alignItems = "";
|
|
148
|
+
canvasWrap.style.flexDirection = "";
|
|
147
149
|
canvasWrap.style.display = "";
|
|
148
150
|
canvasWrap.style.overflow = "";
|
|
149
151
|
canvasWrap.style.overflow = "";
|
|
@@ -247,72 +249,79 @@ export function renderCanvas() {
|
|
|
247
249
|
return;
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
// Git diff mode — render original (left) and current (right) side-by-side
|
|
252
|
+
// Git diff mode — render original (left) and current (right) side-by-side on panzoom surface
|
|
251
253
|
if (canvasMode === "git-diff") {
|
|
252
254
|
if (!_ctx.gitDiffState) {
|
|
253
|
-
// Fallback to design mode if diff state is missing
|
|
254
255
|
_ctx.setCanvasMode("design");
|
|
255
256
|
renderCanvas();
|
|
256
257
|
return;
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
if (modeChanged) {
|
|
261
|
+
canvasWrap.style.padding = "0";
|
|
262
|
+
canvasWrap.style.overflow = "hidden";
|
|
263
|
+
}
|
|
261
264
|
|
|
262
265
|
const gitDiffState = _ctx.gitDiffState;
|
|
263
|
-
const
|
|
266
|
+
const panelWidth = 800;
|
|
267
|
+
|
|
268
|
+
const { tpl: origTpl, panel: origPanel } = canvasPanelTemplate(
|
|
264
269
|
"git-diff-original",
|
|
265
270
|
"Original",
|
|
266
271
|
false,
|
|
267
|
-
|
|
272
|
+
panelWidth,
|
|
268
273
|
);
|
|
269
|
-
const { tpl:
|
|
274
|
+
const { tpl: currTpl, panel: currPanel } = canvasPanelTemplate(
|
|
270
275
|
"git-diff-current",
|
|
271
276
|
"Current",
|
|
272
277
|
false,
|
|
273
|
-
|
|
278
|
+
panelWidth,
|
|
274
279
|
);
|
|
275
280
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
<div
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
litRender(
|
|
282
|
+
html`
|
|
283
|
+
<div
|
|
284
|
+
class="panzoom-wrap"
|
|
285
|
+
style="transform-origin:0 0"
|
|
286
|
+
${ref((el) => {
|
|
287
|
+
if (el) view.panzoomWrap = /** @type {HTMLDivElement} */ (el);
|
|
288
|
+
})}
|
|
289
|
+
>
|
|
290
|
+
${origTpl} ${currTpl}
|
|
283
291
|
</div>
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
`,
|
|
293
|
+
canvasWrap,
|
|
294
|
+
);
|
|
286
295
|
|
|
287
|
-
litRender(diffTpl, canvasWrap);
|
|
288
296
|
canvasPanels.push(/** @type {any} */ (origPanel));
|
|
289
297
|
canvasPanels.push(/** @type {any} */ (currPanel));
|
|
290
298
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
try {
|
|
299
|
+
/** @param {string} content */
|
|
300
|
+
const parseContent = (content) => {
|
|
294
301
|
if (gitDiffState.isMarkdown) {
|
|
295
|
-
|
|
296
|
-
// For now, use a simple placeholder until we have markdown parsing
|
|
297
|
-
originalDoc = {
|
|
298
|
-
tagName: "div",
|
|
299
|
-
children: [{ tagName: "p", textContent: "Original (markdown)" }],
|
|
300
|
-
};
|
|
301
|
-
} else {
|
|
302
|
-
// Parse as JSON
|
|
303
|
-
originalDoc = JSON.parse(gitDiffState.originalContent);
|
|
302
|
+
return loadMarkdown(content).then((r) => r.document);
|
|
304
303
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
304
|
+
return Promise.resolve().then(() => {
|
|
305
|
+
try {
|
|
306
|
+
return JSON.parse(content);
|
|
307
|
+
} catch {
|
|
308
|
+
return { tagName: "div", children: [{ tagName: "p", textContent: "Failed to parse" }] };
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const featureToggles = S.ui.featureToggles;
|
|
314
|
+
Promise.all([
|
|
315
|
+
parseContent(gitDiffState.originalContent || ""),
|
|
316
|
+
parseContent(gitDiffState.currentContent || ""),
|
|
317
|
+
]).then(([originalDoc, currentDoc]) => {
|
|
318
|
+
renderCanvasIntoPanel(origPanel, new Set(), featureToggles, originalDoc, gitDiffState);
|
|
319
|
+
renderCanvasIntoPanel(currPanel, new Set(), featureToggles, currentDoc, gitDiffState);
|
|
320
|
+
});
|
|
313
321
|
|
|
314
|
-
|
|
315
|
-
|
|
322
|
+
applyTransform();
|
|
323
|
+
if (modeChanged) observeCenterUntilStable();
|
|
324
|
+
renderZoomIndicator();
|
|
316
325
|
return;
|
|
317
326
|
}
|
|
318
327
|
|
|
@@ -498,6 +507,7 @@ function renderCanvasIntoPanel(
|
|
|
498
507
|
} else {
|
|
499
508
|
// Fallback to structural preview
|
|
500
509
|
updateCanvas({ status: "ready", scope: null, error: null });
|
|
510
|
+
panel.canvas.innerHTML = "";
|
|
501
511
|
renderCanvasNode(docToRender, [], panel.canvas, activeBreakpoints, featureToggles);
|
|
502
512
|
}
|
|
503
513
|
registerPanelDnD(panel);
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// ─── Clipboard & Context Menu ─────────────────────────────────────────────────
|
|
2
2
|
import { html, render as litRender } from "lit-html";
|
|
3
3
|
import { getNodeAtPath, parentElementPath, childIndex } from "../store.js";
|
|
4
|
-
import { activeTab } from "../workspace/workspace.js";
|
|
4
|
+
import { activeTab, workspace } from "../workspace/workspace.js";
|
|
5
5
|
import {
|
|
6
6
|
transactDoc,
|
|
7
7
|
mutateInsertNode,
|
|
8
8
|
mutateRemoveNode,
|
|
9
9
|
mutateDuplicateNode,
|
|
10
10
|
mutateWrapNode,
|
|
11
|
+
mutateReplaceStyle,
|
|
11
12
|
} from "../tabs/transact.js";
|
|
12
13
|
import { statusMessage } from "../panels/statusbar.js";
|
|
13
14
|
import { convertToComponent } from "./convert-to-component.js";
|
|
@@ -21,9 +22,6 @@ import { componentRegistry } from "../files/components.js";
|
|
|
21
22
|
* @typedef {import("../state.js").JxNode} JxNode
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
|
-
/** @type {JxNode | null} */
|
|
25
|
-
let clipboard = null;
|
|
26
|
-
|
|
27
25
|
// ─── Clipboard ────────────────────────────────────────────────────────────────
|
|
28
26
|
|
|
29
27
|
export function copyNode() {
|
|
@@ -31,7 +29,7 @@ export function copyNode() {
|
|
|
31
29
|
if (!tab?.session.selection) return;
|
|
32
30
|
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
33
31
|
if (!node) return;
|
|
34
|
-
clipboard = structuredClone(node);
|
|
32
|
+
workspace.clipboard = structuredClone(node);
|
|
35
33
|
statusMessage("Copied");
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -41,16 +39,16 @@ export function cutNode() {
|
|
|
41
39
|
const sel = tab.session.selection;
|
|
42
40
|
const node = getNodeAtPath(tab.doc.document, sel);
|
|
43
41
|
if (!node) return;
|
|
44
|
-
clipboard = structuredClone(node);
|
|
42
|
+
workspace.clipboard = structuredClone(node);
|
|
45
43
|
transactDoc(tab, (t) => mutateRemoveNode(t, sel));
|
|
46
44
|
statusMessage("Cut");
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
export function pasteNode() {
|
|
50
|
-
if (!clipboard) return;
|
|
48
|
+
if (!workspace.clipboard) return;
|
|
51
49
|
const tab = activeTab.value;
|
|
52
50
|
if (!tab) return;
|
|
53
|
-
const clip = clipboard;
|
|
51
|
+
const clip = workspace.clipboard;
|
|
54
52
|
const pPath = tab.session.selection || [];
|
|
55
53
|
const parent = getNodeAtPath(tab.doc.document, pPath);
|
|
56
54
|
if (!parent) return;
|
|
@@ -66,6 +64,25 @@ export function pasteNode() {
|
|
|
66
64
|
statusMessage("Pasted");
|
|
67
65
|
}
|
|
68
66
|
|
|
67
|
+
export function copyStyles() {
|
|
68
|
+
const tab = activeTab.value;
|
|
69
|
+
if (!tab?.session.selection) return;
|
|
70
|
+
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
71
|
+
if (!node?.style) return;
|
|
72
|
+
workspace.styleClipboard = JSON.parse(JSON.stringify(node.style));
|
|
73
|
+
statusMessage("Styles copied");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function pasteStyles() {
|
|
77
|
+
if (!workspace.styleClipboard) return;
|
|
78
|
+
const tab = activeTab.value;
|
|
79
|
+
if (!tab?.session.selection) return;
|
|
80
|
+
const style = JSON.parse(JSON.stringify(workspace.styleClipboard));
|
|
81
|
+
const sel = /** @type {JxPath} */ (tab.session.selection);
|
|
82
|
+
transactDoc(tab, (t) => mutateReplaceStyle(t, sel, style));
|
|
83
|
+
statusMessage("Styles pasted");
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
// ─── Context menu ─────────────────────────────────────────────────────────────
|
|
70
87
|
|
|
71
88
|
const ctxMenu = document.createElement("sp-popover");
|
|
@@ -109,6 +126,26 @@ export function showContextMenu(e, path, opts = {}) {
|
|
|
109
126
|
label: "Duplicate",
|
|
110
127
|
action: () => transactDoc(activeTab.value, (t) => mutateDuplicateNode(t, path)),
|
|
111
128
|
});
|
|
129
|
+
if (node.style) {
|
|
130
|
+
items.push({
|
|
131
|
+
label: "Copy styles",
|
|
132
|
+
action: () => {
|
|
133
|
+
workspace.styleClipboard = JSON.parse(JSON.stringify(node.style));
|
|
134
|
+
statusMessage("Styles copied");
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (workspace.styleClipboard) {
|
|
139
|
+
items.push({
|
|
140
|
+
label: "Paste styles",
|
|
141
|
+
action: () => {
|
|
142
|
+
if (!workspace.styleClipboard) return;
|
|
143
|
+
const style = JSON.parse(JSON.stringify(workspace.styleClipboard));
|
|
144
|
+
transactDoc(activeTab.value, (t) => mutateReplaceStyle(t, path, style));
|
|
145
|
+
statusMessage("Styles pasted");
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
112
149
|
items.push({ label: "—" }); // separator
|
|
113
150
|
items.push({
|
|
114
151
|
label: "Insert before",
|
|
@@ -162,8 +199,8 @@ export function showContextMenu(e, path, opts = {}) {
|
|
|
162
199
|
danger: true,
|
|
163
200
|
});
|
|
164
201
|
}
|
|
165
|
-
if (clipboard) {
|
|
166
|
-
const clip = clipboard;
|
|
202
|
+
if (workspace.clipboard) {
|
|
203
|
+
const clip = workspace.clipboard;
|
|
167
204
|
items.push({ label: "—" });
|
|
168
205
|
items.push({
|
|
169
206
|
label: "Paste inside",
|
package/src/files/file-ops.js
CHANGED
|
@@ -174,9 +174,11 @@ export async function exportFile() {
|
|
|
174
174
|
* @param {import("../tabs/tab.js").Tab} tab
|
|
175
175
|
* @returns {string}
|
|
176
176
|
*/
|
|
177
|
-
function serializeDocument(tab) {
|
|
177
|
+
export function serializeDocument(tab) {
|
|
178
178
|
if (tab.doc.sourceFormat === "md") {
|
|
179
|
-
|
|
179
|
+
const fm = tab.doc.content?.frontmatter || {};
|
|
180
|
+
const fullDoc = { ...fm, children: tab.doc.document.children ?? [] };
|
|
181
|
+
return jxDocToMd(fullDoc);
|
|
180
182
|
}
|
|
181
183
|
if (tab.doc.mode === "content") {
|
|
182
184
|
const mdast = jxToMd(tab.doc.document);
|
package/src/files/files.js
CHANGED
|
@@ -383,83 +383,83 @@ export function setupTreeKeyboard(/** @type {HTMLElement} */ tree) {
|
|
|
383
383
|
// ─── Context menu ─────────────────────────────────────────────────────────────
|
|
384
384
|
|
|
385
385
|
/** @type {HTMLElement | null} */
|
|
386
|
-
let
|
|
386
|
+
let _fileCtxHost = null;
|
|
387
|
+
|
|
388
|
+
function getFileCtxHost() {
|
|
389
|
+
if (!_fileCtxHost) {
|
|
390
|
+
_fileCtxHost = document.createElement("div");
|
|
391
|
+
_fileCtxHost.style.display = "contents";
|
|
392
|
+
(document.querySelector("sp-theme") || document.body).appendChild(_fileCtxHost);
|
|
393
|
+
document.addEventListener("click", dismissFileContextMenu);
|
|
394
|
+
}
|
|
395
|
+
return _fileCtxHost;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function dismissFileContextMenu() {
|
|
399
|
+
const host = _fileCtxHost;
|
|
400
|
+
if (!host) return;
|
|
401
|
+
const popover = host.querySelector("sp-popover");
|
|
402
|
+
if (popover) popover.removeAttribute("open");
|
|
403
|
+
}
|
|
387
404
|
|
|
388
405
|
function showFileContextMenu(
|
|
389
406
|
/** @type {MouseEvent} */ e,
|
|
390
407
|
/** @type {{ name: string; path: string; type: string }} */ entry,
|
|
391
408
|
/** @type {{ openFileFn: (path: string) => void; renderLeftPanel: () => void }} */ ctx,
|
|
392
409
|
) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
fileContextPopover = null;
|
|
396
|
-
}
|
|
397
|
-
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
const host = getFileCtxHost();
|
|
398
412
|
const isDir = entry.type === "directory";
|
|
399
|
-
fileContextPopover = document.createElement("div");
|
|
400
413
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
placement="right-start"
|
|
404
|
-
open
|
|
405
|
-
style="position:fixed;left:${e.clientX}px;top:${e.clientY}px;z-index:9999"
|
|
406
|
-
>
|
|
407
|
-
<sp-menu style="min-width:160px">
|
|
408
|
-
${!isDir
|
|
409
|
-
? html`<sp-menu-item
|
|
410
|
-
@click=${() => {
|
|
411
|
-
closeFileContextMenu();
|
|
412
|
-
ctx.openFileFn(entry.path);
|
|
413
|
-
}}
|
|
414
|
-
>Open</sp-menu-item
|
|
415
|
-
>`
|
|
416
|
-
: nothing}
|
|
417
|
-
${isDir
|
|
418
|
-
? html`<sp-menu-item
|
|
419
|
-
@click=${() => {
|
|
420
|
-
closeFileContextMenu();
|
|
421
|
-
createNewFile(entry.path, ctx.renderLeftPanel);
|
|
422
|
-
}}
|
|
423
|
-
>New File…</sp-menu-item
|
|
424
|
-
>`
|
|
425
|
-
: nothing}
|
|
426
|
-
<sp-menu-divider></sp-menu-divider>
|
|
427
|
-
<sp-menu-item
|
|
428
|
-
@click=${() => {
|
|
429
|
-
closeFileContextMenu();
|
|
430
|
-
renameFile(entry, ctx.renderLeftPanel);
|
|
431
|
-
}}
|
|
432
|
-
>Rename…</sp-menu-item
|
|
433
|
-
>
|
|
434
|
-
<sp-menu-item
|
|
435
|
-
style="color:var(--danger)"
|
|
436
|
-
@click=${() => {
|
|
437
|
-
closeFileContextMenu();
|
|
438
|
-
deleteFile(entry, ctx.renderLeftPanel);
|
|
439
|
-
}}
|
|
440
|
-
>Delete</sp-menu-item
|
|
441
|
-
>
|
|
442
|
-
</sp-menu>
|
|
443
|
-
</sp-popover>
|
|
444
|
-
`;
|
|
445
|
-
|
|
446
|
-
litRender(tpl, fileContextPopover);
|
|
447
|
-
document.body.appendChild(fileContextPopover);
|
|
448
|
-
|
|
449
|
-
const closeHandler = (/** @type {MouseEvent} */ ev) => {
|
|
450
|
-
if (!fileContextPopover?.contains(/** @type {Node} */ (ev.target))) {
|
|
451
|
-
closeFileContextMenu();
|
|
452
|
-
document.removeEventListener("mousedown", closeHandler, true);
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
setTimeout(() => document.addEventListener("mousedown", closeHandler, true), 0);
|
|
456
|
-
}
|
|
414
|
+
/** @type {{ label: string; action?: () => void; danger?: boolean }[]} */
|
|
415
|
+
const items = [];
|
|
457
416
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
fileContextPopover.remove();
|
|
461
|
-
fileContextPopover = null;
|
|
417
|
+
if (!isDir) {
|
|
418
|
+
items.push({ label: "Open", action: () => ctx.openFileFn(entry.path) });
|
|
462
419
|
}
|
|
420
|
+
if (isDir) {
|
|
421
|
+
items.push({
|
|
422
|
+
label: "New File\u2026",
|
|
423
|
+
action: () => createNewFile(entry.path, ctx.renderLeftPanel),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
items.push({ label: "\u2014" });
|
|
427
|
+
items.push({ label: "Rename\u2026", action: () => renameFile(entry, ctx.renderLeftPanel) });
|
|
428
|
+
items.push({
|
|
429
|
+
label: "Delete",
|
|
430
|
+
action: () => deleteFile(entry, ctx.renderLeftPanel),
|
|
431
|
+
danger: true,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
litRender(
|
|
435
|
+
html`<sp-popover style="position:fixed;z-index:10000">
|
|
436
|
+
<sp-menu>
|
|
437
|
+
${items.map((item) =>
|
|
438
|
+
item.label === "\u2014"
|
|
439
|
+
? html`<sp-menu-divider></sp-menu-divider>`
|
|
440
|
+
: html`<sp-menu-item
|
|
441
|
+
style=${item.danger ? "color: var(--danger)" : ""}
|
|
442
|
+
@click=${() => {
|
|
443
|
+
dismissFileContextMenu();
|
|
444
|
+
item.action?.();
|
|
445
|
+
}}
|
|
446
|
+
>${item.label}</sp-menu-item
|
|
447
|
+
>`,
|
|
448
|
+
)}
|
|
449
|
+
</sp-menu>
|
|
450
|
+
</sp-popover>`,
|
|
451
|
+
host,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const popover = /** @type {HTMLElement} */ (host.querySelector("sp-popover"));
|
|
455
|
+
popover.setAttribute("open", "");
|
|
456
|
+
const menuRect = popover.getBoundingClientRect();
|
|
457
|
+
let x = e.clientX,
|
|
458
|
+
y = e.clientY;
|
|
459
|
+
if (x + menuRect.width > window.innerWidth) x = window.innerWidth - menuRect.width - 4;
|
|
460
|
+
if (y + menuRect.height > window.innerHeight) y = window.innerHeight - menuRect.height - 4;
|
|
461
|
+
popover.style.left = `${x}px`;
|
|
462
|
+
popover.style.top = `${y}px`;
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
// ─── File CRUD ────────────────────────────────────────────────────────────────
|
package/src/panels/editors.js
CHANGED
|
@@ -57,6 +57,8 @@ export function renderFunctionEditor() {
|
|
|
57
57
|
|
|
58
58
|
litRender(nothing, canvasWrap);
|
|
59
59
|
canvasWrap.style.padding = "0";
|
|
60
|
+
canvasWrap.style.flexDirection = "column";
|
|
61
|
+
canvasWrap.style.alignItems = "stretch";
|
|
60
62
|
|
|
61
63
|
// Toolbar breadcrumb handles context display — re-render it
|
|
62
64
|
renderOnly("toolbar");
|
|
@@ -65,12 +67,14 @@ export function renderFunctionEditor() {
|
|
|
65
67
|
/** @type {HTMLDivElement | null} */
|
|
66
68
|
let editorContainer = null;
|
|
67
69
|
litRender(
|
|
68
|
-
html`<div
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
html`<div class="source-wrap">
|
|
71
|
+
<div
|
|
72
|
+
class="source-editor"
|
|
73
|
+
${ref((el) => {
|
|
74
|
+
if (el) editorContainer = /** @type {HTMLDivElement} */ (el);
|
|
75
|
+
})}
|
|
76
|
+
></div>
|
|
77
|
+
</div>`,
|
|
74
78
|
canvasWrap,
|
|
75
79
|
);
|
|
76
80
|
|
package/src/panels/git-panel.js
CHANGED
|
@@ -7,6 +7,7 @@ import { html, nothing } from "lit-html";
|
|
|
7
7
|
import { live } from "lit-html/directives/live.js";
|
|
8
8
|
import { getPlatform } from "../platform.js";
|
|
9
9
|
import { updateUi, renderOnly } from "../store.js";
|
|
10
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
10
11
|
import { view } from "../view.js";
|
|
11
12
|
|
|
12
13
|
async function refreshGitStatus() {
|
|
@@ -70,7 +71,8 @@ export function renderGitPanel(S, ctx) {
|
|
|
70
71
|
const totalChanges = status?.files?.length || 0;
|
|
71
72
|
|
|
72
73
|
const doCommit = async () => {
|
|
73
|
-
const
|
|
74
|
+
const tab = activeTab.value;
|
|
75
|
+
const msg = tab?.session.ui.gitCommitMessage?.trim();
|
|
74
76
|
if (!msg) return;
|
|
75
77
|
updateUi("gitCommitMessage", "");
|
|
76
78
|
await gitAction("gitCommit", msg);
|
|
@@ -145,11 +147,7 @@ export function renderGitPanel(S, ctx) {
|
|
|
145
147
|
}
|
|
146
148
|
}}
|
|
147
149
|
></sp-textfield>
|
|
148
|
-
<sp-action-button
|
|
149
|
-
class="git-commit-btn"
|
|
150
|
-
@click=${doCommit}
|
|
151
|
-
?disabled=${!S.ui.gitCommitMessage?.trim() || loading}
|
|
152
|
-
>
|
|
150
|
+
<sp-action-button class="git-commit-btn" @click=${doCommit} ?disabled=${loading}>
|
|
153
151
|
<sp-icon-checkmark slot="icon" size="xs"></sp-icon-checkmark>
|
|
154
152
|
Commit
|
|
155
153
|
</sp-action-button>
|
|
@@ -162,29 +160,33 @@ export function renderGitPanel(S, ctx) {
|
|
|
162
160
|
const dir = parts.join("/");
|
|
163
161
|
|
|
164
162
|
const onFileClick = async () => {
|
|
165
|
-
// Only support diff for modified/added files
|
|
166
163
|
if (file.status !== "M" && file.status !== "A") return;
|
|
167
164
|
if (!file.path.endsWith(".md") && !file.path.endsWith(".json")) return;
|
|
168
165
|
|
|
169
166
|
try {
|
|
170
167
|
const plat = getPlatform();
|
|
171
168
|
updateUi("gitLoading", true);
|
|
172
|
-
const originalContent = await plat.gitShow({ path: file.path, ref: "HEAD" });
|
|
173
169
|
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
const [originalContent, currentContent] = await Promise.all([
|
|
171
|
+
file.status === "A"
|
|
172
|
+
? Promise.resolve("")
|
|
173
|
+
: plat.gitShow({ path: file.path, ref: "HEAD" }),
|
|
174
|
+
plat.readFile(file.path),
|
|
175
|
+
]);
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
updateUi("gitDiffState", {
|
|
177
|
+
const isMarkdown = file.path.endsWith(".md");
|
|
178
|
+
const diffState = {
|
|
180
179
|
filePath: file.path,
|
|
181
180
|
originalContent,
|
|
181
|
+
currentContent,
|
|
182
182
|
isMarkdown,
|
|
183
183
|
fileStatus: file.status,
|
|
184
|
-
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
updateUi("gitDiffState", diffState);
|
|
185
187
|
|
|
186
|
-
// Trigger diff mode via context
|
|
187
188
|
if (ctx?.setCanvasMode) {
|
|
189
|
+
if (ctx.setGitDiffState) ctx.setGitDiffState(diffState);
|
|
188
190
|
ctx.setCanvasMode("git-diff");
|
|
189
191
|
}
|
|
190
192
|
} catch (/** @type {any} */ e) {
|
package/src/panels/left-panel.js
CHANGED
|
@@ -40,6 +40,7 @@ import { selectStylebookTag, stylebookMeta } from "./stylebook-panel.js";
|
|
|
40
40
|
* registerElementsDnD: () => void;
|
|
41
41
|
* registerComponentsDnD: () => void;
|
|
42
42
|
* setupTreeKeyboard: (tree: HTMLElement) => void;
|
|
43
|
+
* setGitDiffState: (state: any) => void;
|
|
43
44
|
* }} LeftPanelCtx
|
|
44
45
|
*/
|
|
45
46
|
|
|
@@ -69,6 +70,9 @@ export function mount(ctx) {
|
|
|
69
70
|
void tab.doc.mode;
|
|
70
71
|
void tab.session.selection;
|
|
71
72
|
void tab.session.ui.settingsTab;
|
|
73
|
+
void tab.session.ui.gitStatus;
|
|
74
|
+
void tab.session.ui.gitLoading;
|
|
75
|
+
void tab.session.ui.gitError;
|
|
72
76
|
render();
|
|
73
77
|
});
|
|
74
78
|
});
|