@jxsuite/studio 0.16.0 → 0.17.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 CHANGED
@@ -1508,8 +1508,10 @@ var init_tab = __esm(() => {
1508
1508
  var exports_workspace = {};
1509
1509
  __export(exports_workspace, {
1510
1510
  workspace: () => workspace,
1511
+ replaceAllTabs: () => replaceAllTabs,
1511
1512
  openTab: () => openTab,
1512
1513
  closeTab: () => closeTab,
1514
+ closeAllTabs: () => closeAllTabs,
1513
1515
  activeTab: () => activeTab,
1514
1516
  activateTab: () => activateTab
1515
1517
  });
@@ -1531,6 +1533,32 @@ function closeTab(tabId) {
1531
1533
  workspace.activeTabId = workspace.tabOrder[workspace.tabOrder.length - 1] || null;
1532
1534
  }
1533
1535
  }
1536
+ function closeAllTabs() {
1537
+ const tabs = [...workspace.tabs.values()];
1538
+ workspace.tabs.clear();
1539
+ workspace.tabOrder = [];
1540
+ workspace.activeTabId = null;
1541
+ for (const tab of tabs) {
1542
+ disposeTab(tab);
1543
+ }
1544
+ }
1545
+ function replaceAllTabs(newTabOpts) {
1546
+ const oldIds = [...workspace.tabs.keys()];
1547
+ const oldTabs = [...workspace.tabs.values()];
1548
+ const newTab = createTab(newTabOpts);
1549
+ workspace.tabs.set(newTab.id, newTab);
1550
+ workspace.activeTabId = newTab.id;
1551
+ workspace.tabOrder = [newTab.id];
1552
+ for (const id of oldIds) {
1553
+ if (id === newTab.id)
1554
+ continue;
1555
+ workspace.tabs.delete(id);
1556
+ }
1557
+ for (const tab of oldTabs) {
1558
+ disposeTab(tab);
1559
+ }
1560
+ return newTab;
1561
+ }
1534
1562
  function activateTab(tabId) {
1535
1563
  if (workspace.tabs.has(tabId))
1536
1564
  workspace.activeTabId = tabId;
@@ -176970,12 +176998,6 @@ async function codeService(action, payload) {
176970
176998
  return null;
176971
176999
  return platform3.codeService(action, payload);
176972
177000
  }
176973
- async function locateDocument(name) {
176974
- const platform3 = getPlatform();
176975
- if (!platform3.locateFile)
176976
- return null;
176977
- return platform3.locateFile(name);
176978
- }
176979
177001
  var pluginSchemaCache = new Map;
176980
177002
  async function fetchPluginSchema(def3, state) {
176981
177003
  if (!def3.$src || !def3.$prototype)
@@ -177034,58 +177056,6 @@ function getFunctionArgs(editing, document4) {
177034
177056
  // src/files/file-ops.js
177035
177057
  init_platform();
177036
177058
  init_workspace();
177037
- async function openFile() {
177038
- try {
177039
- if ("showOpenFilePicker" in window) {
177040
- const [handle2] = await window.showOpenFilePicker({
177041
- types: [
177042
- { description: "Jx Component", accept: { "application/json": [".json"] } },
177043
- { description: "Markdown Content", accept: { "text/markdown": [".md"] } }
177044
- ]
177045
- });
177046
- const file = await handle2.getFile();
177047
- const text6 = await file.text();
177048
- const documentPath = await locateDocument(handle2.name);
177049
- if (handle2.name.endsWith(".md")) {
177050
- const { document: document4, frontmatter: frontmatter2 } = await loadMarkdown(text6);
177051
- openTab({
177052
- id: handle2.name,
177053
- documentPath,
177054
- fileHandle: handle2,
177055
- document: document4,
177056
- frontmatter: frontmatter2,
177057
- sourceFormat: "md"
177058
- });
177059
- } else {
177060
- const document4 = JSON.parse(text6);
177061
- openTab({ id: handle2.name, documentPath, fileHandle: handle2, document: document4 });
177062
- }
177063
- statusMessage(`Opened ${handle2.name}`);
177064
- } else {
177065
- const input = document.createElement("input");
177066
- input.type = "file";
177067
- input.accept = ".json,.md";
177068
- input.onchange = async () => {
177069
- const file = input.files?.[0];
177070
- if (!file)
177071
- return;
177072
- const text6 = await file.text();
177073
- if (file.name.endsWith(".md")) {
177074
- const { document: document4, frontmatter: frontmatter2 } = await loadMarkdown(text6);
177075
- openTab({ id: file.name, document: document4, frontmatter: frontmatter2, sourceFormat: "md" });
177076
- } else {
177077
- const document4 = JSON.parse(text6);
177078
- openTab({ id: file.name, document: document4 });
177079
- }
177080
- statusMessage(`Opened ${file.name}`);
177081
- };
177082
- input.click();
177083
- }
177084
- } catch (e) {
177085
- if (e.name !== "AbortError")
177086
- statusMessage(`Error: ${e.message}`);
177087
- }
177088
- }
177089
177059
  async function loadMarkdown(source) {
177090
177060
  const { transpileJxMarkdown: transpileJxMarkdown2 } = await Promise.resolve().then(() => (init_transpile(), exports_transpile));
177091
177061
  const doc = transpileJxMarkdown2(source);
@@ -178841,23 +178811,6 @@ async function renderCanvasLive(gen, doc, canvasEl) {
178841
178811
  } else {
178842
178812
  canvasEl.removeAttribute("data-content-mode");
178843
178813
  }
178844
- const platform3 = globalThis.__jxPlatform;
178845
- if (platform3?.resolveAssetUrl && docBase) {
178846
- const mediaEls = el.querySelectorAll("img[src], video[src], source[src], video[poster]");
178847
- for (const node2 of mediaEls) {
178848
- for (const attr of ["src", "poster"]) {
178849
- const val = node2.getAttribute(attr);
178850
- if (val && !val.startsWith("data:") && !val.startsWith("blob:") && !val.startsWith("http")) {
178851
- try {
178852
- const resolved = new URL(val, docBase).pathname.replace(/^\//, "");
178853
- const dataUrl = await platform3.resolveAssetUrl(resolved);
178854
- if (dataUrl)
178855
- node2.setAttribute(attr, dataUrl);
178856
- } catch {}
178857
- }
178858
- }
178859
- }
178860
- }
178861
178814
  canvasEl.appendChild(el);
178862
178815
  if (canvasMode === "design" || canvasMode === "edit") {
178863
178816
  requestAnimationFrame(() => {
@@ -183083,38 +183036,40 @@ function registerPanelEvents(panel) {
183083
183036
  const tab = activeTab.value;
183084
183037
  const canvasMode = _ctx8.getCanvasMode();
183085
183038
  const elements = withPanelPointerEvents(() => document.elementsFromPoint(e.clientX, e.clientY));
183039
+ if (!tab)
183040
+ return;
183086
183041
  for (const el of elements) {
183087
183042
  if (canvas.contains(el) && el !== canvas) {
183088
183043
  if (layoutElements.has(el)) {
183089
183044
  view.layoutSelection = { el, layoutPath: activeLayoutPath };
183090
- activeTab.value.session.selection = null;
183045
+ tab.session.selection = null;
183091
183046
  renderOnly("rightPanel");
183092
183047
  return;
183093
183048
  }
183094
183049
  view.layoutSelection = null;
183095
183050
  const originalPath = elToPath.get(el);
183096
183051
  if (originalPath) {
183097
- let path2 = bubbleInlinePath(tab?.doc.document, originalPath);
183052
+ let path2 = bubbleInlinePath(tab.doc.document, originalPath);
183098
183053
  const newMedia = mediaName === "base" ? null : mediaName ?? null;
183099
183054
  const resolvedEl = path2 === originalPath ? el : findCanvasElement(path2, canvas) || el;
183100
- if (pathsEqual(path2, tab?.session.selection) && isEditableBlock(resolvedEl) && (canvasMode === "edit" || tab?.doc.mode === "content")) {
183101
- activeTab.value.session.ui.activeMedia = newMedia;
183055
+ if (pathsEqual(path2, tab.session.selection) && isEditableBlock(resolvedEl) && (canvasMode === "edit" || tab.doc.mode === "content")) {
183056
+ tab.session.ui.activeMedia = newMedia;
183102
183057
  _ctx8.enterInlineEdit(resolvedEl, path2);
183103
183058
  return;
183104
183059
  }
183105
- if (canvasMode === "design" && tab?.doc.mode !== "content") {
183060
+ if (canvasMode === "design" && tab.doc.mode !== "content") {
183106
183061
  updateUi("pendingInlineEdit", { path: path2, mediaName });
183107
- activeTab.value.session.ui.activeMedia = newMedia;
183108
- activeTab.value.session.selection = path2;
183062
+ tab.session.ui.activeMedia = newMedia;
183063
+ tab.session.selection = path2;
183109
183064
  return;
183110
183065
  }
183111
- activeTab.value.session.ui.activeMedia = newMedia;
183112
- activeTab.value.session.selection = path2;
183066
+ tab.session.ui.activeMedia = newMedia;
183067
+ tab.session.selection = path2;
183113
183068
  return;
183114
183069
  }
183115
183070
  }
183116
183071
  }
183117
- activeTab.value.session.selection = null;
183072
+ tab.session.selection = null;
183118
183073
  }, opts);
183119
183074
  overlayClk.addEventListener("dblclick", (e) => {
183120
183075
  const barInner = view.blockActionBarEl?.firstElementChild;
@@ -183128,16 +183083,18 @@ function registerPanelEvents(panel) {
183128
183083
  return;
183129
183084
  const tab = activeTab.value;
183130
183085
  const elements = withPanelPointerEvents(() => document.elementsFromPoint(e.clientX, e.clientY));
183086
+ if (!tab)
183087
+ return;
183131
183088
  for (const el of elements) {
183132
183089
  if (canvas.contains(el) && el !== canvas) {
183133
183090
  const originalPath = elToPath.get(el);
183134
183091
  if (originalPath) {
183135
- const path2 = bubbleInlinePath(tab?.doc.document, originalPath);
183092
+ const path2 = bubbleInlinePath(tab.doc.document, originalPath);
183136
183093
  const resolvedEl = path2 === originalPath ? el : findCanvasElement(path2, canvas) || el;
183137
183094
  if (isEditableBlock(resolvedEl)) {
183138
183095
  const newMedia = mediaName === "base" ? null : mediaName ?? null;
183139
- activeTab.value.session.ui.activeMedia = newMedia;
183140
- activeTab.value.session.selection = path2;
183096
+ tab.session.ui.activeMedia = newMedia;
183097
+ tab.session.selection = path2;
183141
183098
  _ctx8.enterInlineEdit(resolvedEl, path2);
183142
183099
  return;
183143
183100
  }
@@ -183174,18 +183131,20 @@ function registerPanelEvents(panel) {
183174
183131
  return;
183175
183132
  }
183176
183133
  const tab = activeTab.value;
183134
+ if (!tab)
183135
+ return;
183177
183136
  const el = withPanelPointerEvents(() => document.elementFromPoint(e.clientX, e.clientY));
183178
183137
  if (el && canvas.contains(el) && el !== canvas) {
183179
183138
  let path2 = elToPath.get(el);
183180
183139
  if (path2) {
183181
- path2 = bubbleInlinePath(tab?.doc.document, path2);
183182
- if (!pathsEqual(path2, tab?.session.hover)) {
183183
- activeTab.value.session.hover = path2;
183140
+ path2 = bubbleInlinePath(tab.doc.document, path2);
183141
+ if (!pathsEqual(path2, tab.session.hover)) {
183142
+ tab.session.hover = path2;
183184
183143
  renderOnly("overlays");
183185
183144
  }
183186
183145
  }
183187
- } else if (tab?.session.hover) {
183188
- activeTab.value.session.hover = null;
183146
+ } else if (tab.session.hover) {
183147
+ tab.session.hover = null;
183189
183148
  renderOnly("overlays");
183190
183149
  }
183191
183150
  }, opts);
@@ -183388,6 +183347,19 @@ function extOf(name) {
183388
183347
  const dot = name.lastIndexOf(".");
183389
183348
  return dot > 0 ? name.slice(dot).toLowerCase() : "";
183390
183349
  }
183350
+ var IMAGE_EXTENSIONS2 = new Set([
183351
+ ".jpg",
183352
+ ".jpeg",
183353
+ ".png",
183354
+ ".gif",
183355
+ ".svg",
183356
+ ".webp",
183357
+ ".avif",
183358
+ ".ico"
183359
+ ]);
183360
+ function isImage(ext) {
183361
+ return IMAGE_EXTENSIONS2.has(ext);
183362
+ }
183391
183363
  function categoryFor(dir, ext) {
183392
183364
  if (ext && MEDIA_EXTENSIONS2.has(ext))
183393
183365
  return "Media";
@@ -183645,9 +183617,12 @@ async function renderBrowse(container, ctx) {
183645
183617
  <sp-table-row
183646
183618
  value=${f.path}
183647
183619
  class="browse-row"
183648
- @click=${() => ctx.openFile(f.path)}
183620
+ style=${isImage(f.ext) ? "cursor:default" : ""}
183621
+ @click=${isImage(f.ext) ? nothing : () => ctx.openFile(f.path)}
183649
183622
  >
183650
- <sp-table-cell class="browse-name-cell">${f.name}</sp-table-cell>
183623
+ <sp-table-cell class="browse-name-cell"
183624
+ >${isImage(f.ext) ? html`<img class="browse-thumb" src="/${f.path}" />` : nothing}${f.name}</sp-table-cell
183625
+ >
183651
183626
  <sp-table-cell>${f.category}</sp-table-cell>
183652
183627
  <sp-table-cell>${f.type}</sp-table-cell>
183653
183628
  <sp-table-cell class="browse-path-cell">${f.path}</sp-table-cell>
@@ -184338,6 +184313,48 @@ init_store();
184338
184313
  init_platform();
184339
184314
  init_components();
184340
184315
  init_workspace();
184316
+
184317
+ // src/recent-projects.js
184318
+ var STORAGE_KEY = "jx-studio-recent-projects";
184319
+ var FILES_STORAGE_KEY = "jx-studio-recent-files";
184320
+ var MAX_RECENT = 8;
184321
+ var MAX_RECENT_FILES = 10;
184322
+ function getRecentProjects() {
184323
+ try {
184324
+ const raw = localStorage.getItem(STORAGE_KEY);
184325
+ if (!raw)
184326
+ return [];
184327
+ return JSON.parse(raw).sort((a, b) => b.timestamp - a.timestamp);
184328
+ } catch {
184329
+ return [];
184330
+ }
184331
+ }
184332
+ function addRecentProject(name, root2) {
184333
+ const projects = getRecentProjects().filter((p) => p.root !== root2);
184334
+ projects.unshift({ name, root: root2, timestamp: Date.now() });
184335
+ if (projects.length > MAX_RECENT)
184336
+ projects.length = MAX_RECENT;
184337
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(projects));
184338
+ }
184339
+ function getRecentFiles() {
184340
+ try {
184341
+ const raw = localStorage.getItem(FILES_STORAGE_KEY);
184342
+ if (!raw)
184343
+ return [];
184344
+ return JSON.parse(raw).sort((a, b) => b.timestamp - a.timestamp);
184345
+ } catch {
184346
+ return [];
184347
+ }
184348
+ }
184349
+ function trackRecentFile(file) {
184350
+ const recent = getRecentFiles().filter((f) => f.path !== file.path);
184351
+ recent.unshift({ path: file.path, name: file.name, timestamp: Date.now() });
184352
+ if (recent.length > MAX_RECENT_FILES)
184353
+ recent.length = MAX_RECENT_FILES;
184354
+ localStorage.setItem(FILES_STORAGE_KEY, JSON.stringify(recent));
184355
+ }
184356
+
184357
+ // src/files/files.js
184341
184358
  var fileIconMap = {
184342
184359
  "sp-icon-folder-open": html`<sp-icon-folder-open></sp-icon-folder-open>`,
184343
184360
  "sp-icon-folder": html`<sp-icon-folder></sp-icon-folder>`,
@@ -184389,6 +184406,7 @@ async function openProject({ renderActivityBar, renderLeftPanel }) {
184389
184406
  if (!result)
184390
184407
  return;
184391
184408
  const { config, handle: handle2 } = result;
184409
+ replaceAllTabs({ id: "initial", document: { tagName: "div", children: [] } });
184392
184410
  setProjectState({
184393
184411
  ...projectState,
184394
184412
  projectRoot: handle2.root,
@@ -184422,6 +184440,7 @@ async function openProject({ renderActivityBar, renderLeftPanel }) {
184422
184440
  }
184423
184441
  projectState.projectDirs = foundDirs;
184424
184442
  view.leftTab = "files";
184443
+ addRecentProject(projectState.name, projectState.projectRoot);
184425
184444
  renderActivityBar();
184426
184445
  renderLeftPanel();
184427
184446
  statusMessage(`Opened project: ${projectState.name}`);
@@ -184790,6 +184809,7 @@ async function openFileInTab(path2) {
184790
184809
  sourceFormat: path2.endsWith(".md") ? "md" : null
184791
184810
  });
184792
184811
  projectState.selectedPath = path2;
184812
+ trackRecentFile({ path: path2, name: path2.split("/").pop() || path2 });
184793
184813
  statusMessage(`Opened ${path2.split("/").pop()}`);
184794
184814
  } catch (e) {
184795
184815
  statusMessage(`Error: ${e.message}`);
@@ -185753,6 +185773,16 @@ function createDevServerPlatform() {
185753
185773
  } catch {}
185754
185774
  return null;
185755
185775
  },
185776
+ async searchFiles(query) {
185777
+ const glob = `**/*${query}*.{json,md}`;
185778
+ const res = await fetch(`/__studio/files?dir=${encodeURIComponent(serverPath("."))}&glob=${encodeURIComponent(glob)}`);
185779
+ if (!res.ok)
185780
+ return [];
185781
+ const entries2 = await res.json();
185782
+ for (const e of entries2)
185783
+ e.path = stripRoot(e.path);
185784
+ return entries2;
185785
+ },
185756
185786
  async fetchPluginSchema(src, prototype, base2) {
185757
185787
  const params = new URLSearchParams({ src });
185758
185788
  if (prototype)
@@ -210014,6 +210044,67 @@ class IconRemove extends IconBase {
210014
210044
  }
210015
210045
  }
210016
210046
 
210047
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/elements/IconFullScreen.js
210048
+ init_index_dev();
210049
+
210050
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/icons-s2/FullScreen.js
210051
+ var FullScreenIcon = ({ width: l = 24, height: r8 = 24, hidden: t15 = false, title: c6 = "Full Screen" } = {}) => tag6`<svg
210052
+ xmlns="http://www.w3.org/2000/svg"
210053
+ width="${l}"
210054
+ height="${r8}"
210055
+ viewBox="0 0 20 20"
210056
+ aria-hidden=${t15 ? "true" : "false"}
210057
+ role="img"
210058
+ fill="currentColor"
210059
+ aria-label="${c6}"
210060
+ >
210061
+ <path
210062
+ d="M12.75,14.93652h-5.5c-1.24072,0-2.25-1.00928-2.25-2.25v-5.5c0-1.24072,1.00928-2.25,2.25-2.25h5.5c1.24072,0,2.25,1.00928,2.25,2.25v5.5c0,1.24072-1.00928,2.25-2.25,2.25ZM7.25,6.43652c-.41357,0-.75.33643-.75.75v5.5c0,.41357.33643.75.75.75h5.5c.41357,0,.75-.33643.75-.75v-5.5c0-.41357-.33643-.75-.75-.75h-5.5Z"
210063
+ fill="currentColor"
210064
+ />
210065
+ <path
210066
+ d="M4.5,19h-2.25c-.68945,0-1.25-.56055-1.25-1.25v-2.25c0-.41406.33594-.75.75-.75s.75.33594.75.75v2h2c.41406,0,.75.33594.75.75s-.33594.75-.75.75Z"
210067
+ fill="currentColor"
210068
+ />
210069
+ <path
210070
+ d="M17.75,19h-2.25c-.41406,0-.75-.33594-.75-.75s.33594-.75.75-.75h2v-2c0-.41406.33594-.75.75-.75s.75.33594.75.75v2.25c0,.68945-.56055,1.25-1.25,1.25Z"
210071
+ fill="currentColor"
210072
+ stroke-width="0"
210073
+ />
210074
+ <path
210075
+ d="M18.25,5.25c-.41406,0-.75-.33594-.75-.75v-2h-2c-.41406,0-.75-.33594-.75-.75s.33594-.75.75-.75h2.25c.68945,0,1.25.56055,1.25,1.25v2.25c0,.41406-.33594.75-.75.75ZM17.75,2.5h.00977-.00977Z"
210076
+ fill="currentColor"
210077
+ />
210078
+ <path
210079
+ d="M1.75,5.25c-.41406,0-.75-.33594-.75-.75v-2.25c0-.68945.56055-1.25,1.25-1.25h2.25c.41406,0,.75.33594.75.75s-.33594.75-.75.75h-2v2c0,.41406-.33594.75-.75.75Z"
210080
+ fill="currentColor"
210081
+ />
210082
+ </svg>`;
210083
+
210084
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/icons/FullScreen.js
210085
+ var FullScreenIcon2 = ({ width: a5 = 24, height: e20 = 24, hidden: t15 = false, title: r8 = "Full Screen" } = {}) => tag6`<svg
210086
+ xmlns="http://www.w3.org/2000/svg"
210087
+ height="${e20}"
210088
+ viewBox="0 0 36 36"
210089
+ width="${a5}"
210090
+ aria-hidden=${t15 ? "true" : "false"}
210091
+ role="img"
210092
+ fill="currentColor"
210093
+ aria-label="${r8}"
210094
+ >
210095
+ <path
210096
+ d="M32 24.5V30h-5.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5H34v-7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5ZM4 30v-5.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5V32h7.5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5ZM26 4.5v1a.5.5 0 0 0 .5.5H32v5.5a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5V4h-7.5a.5.5 0 0 0-.5.5ZM4 6h5.5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5H2v7.5a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5Z"
210097
+ />
210098
+ <rect height="16" rx=".5" ry=".5" width="20" x="8" y="10" />
210099
+ </svg>`;
210100
+
210101
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/elements/IconFullScreen.js
210102
+ class IconFullScreen extends IconBase {
210103
+ render() {
210104
+ return setCustomTemplateLiteralTag2(html8), this.spectrumVersion === 2 ? FullScreenIcon({ hidden: !this.label, title: this.label }) : FullScreenIcon2({ hidden: !this.label, title: this.label });
210105
+ }
210106
+ }
210107
+
210017
210108
  // ../../node_modules/@spectrum-web-components/icons-workflow/src/elements/IconViewColumn.js
210018
210109
  init_index_dev();
210019
210110
 
@@ -211139,6 +211230,7 @@ var components = [
211139
211230
  ["sp-icon-text-baseline-shift", IconTextBaselineShift],
211140
211231
  ["sp-icon-flip-vertical", IconFlipVertical],
211141
211232
  ["sp-icon-remove", IconRemove],
211233
+ ["sp-icon-full-screen", IconFullScreen],
211142
211234
  ["sp-icon-download", IconDownload],
211143
211235
  ["sp-icon-checkmark", IconCheckmark],
211144
211236
  ["sp-icon-view-column", IconViewColumn],
@@ -211167,14 +211259,14 @@ Theme.registerThemeFragment("dark", "color", theme_dark_css_default);
211167
211259
  Theme.registerThemeFragment("medium", "scale", scale_medium_css_default);
211168
211260
 
211169
211261
  // src/ui/panel-resize.js
211170
- var STORAGE_KEY = "jx-studio-panel-widths";
211262
+ var STORAGE_KEY2 = "jx-studio-panel-widths";
211171
211263
  var MIN_WIDTH = 160;
211172
211264
  var MAX_RATIO = 0.5;
211173
211265
  var DEFAULT_LEFT = 240;
211174
211266
  var DEFAULT_RIGHT = 280;
211175
211267
  var root2 = document.documentElement;
211176
211268
  try {
211177
- const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
211269
+ const saved = JSON.parse(localStorage.getItem(STORAGE_KEY2) || "{}");
211178
211270
  if (saved.left)
211179
211271
  root2.style.setProperty("--panel-w-left", `${saved.left}px`);
211180
211272
  if (saved.right)
@@ -211220,7 +211312,7 @@ function persistWidths() {
211220
211312
  const left = parseInt(getComputedStyle(root2).getPropertyValue("--panel-w-left")) || DEFAULT_LEFT;
211221
211313
  const right = parseInt(getComputedStyle(root2).getPropertyValue("--panel-w-right")) || DEFAULT_RIGHT;
211222
211314
  try {
211223
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, right }));
211315
+ localStorage.setItem(STORAGE_KEY2, JSON.stringify({ left, right }));
211224
211316
  } catch {}
211225
211317
  }
211226
211318
  var resizeLeft = document.getElementById("resize-left");
@@ -211233,11 +211325,162 @@ if (resizeRight)
211233
211325
  // src/editor/shortcuts.js
211234
211326
  init_store();
211235
211327
  init_workspace();
211328
+
211329
+ // src/panels/quick-search.js
211330
+ init_platform();
211331
+ var _open = false;
211332
+ var _query = "";
211333
+ var _results = [];
211334
+ var _selectedIndex = 0;
211335
+ var _debounceTimer = 0;
211336
+ var _container = null;
211337
+ function initQuickSearch() {
211338
+ _container = document.createElement("div");
211339
+ _container.style.display = "contents";
211340
+ (document.querySelector("sp-theme") || document.body).appendChild(_container);
211341
+ }
211342
+ function openQuickSearch() {
211343
+ _open = true;
211344
+ _query = "";
211345
+ _results = [];
211346
+ _selectedIndex = 0;
211347
+ renderOverlay();
211348
+ requestAnimationFrame(() => {
211349
+ const input = _container?.querySelector(".quick-search-input");
211350
+ if (input)
211351
+ input.focus();
211352
+ });
211353
+ }
211354
+ function closeQuickSearch() {
211355
+ _open = false;
211356
+ renderOverlay();
211357
+ }
211358
+ async function doSearch(query3) {
211359
+ if (!query3.trim()) {
211360
+ _results = [];
211361
+ _selectedIndex = 0;
211362
+ renderOverlay();
211363
+ return;
211364
+ }
211365
+ try {
211366
+ const platform4 = getPlatform();
211367
+ _results = await platform4.searchFiles(query3.trim().toLowerCase());
211368
+ _selectedIndex = 0;
211369
+ renderOverlay();
211370
+ } catch {
211371
+ _results = [];
211372
+ renderOverlay();
211373
+ }
211374
+ }
211375
+ function onInput(e20) {
211376
+ _query = e20.target.value;
211377
+ clearTimeout(_debounceTimer);
211378
+ _debounceTimer = setTimeout(() => doSearch(_query), 150);
211379
+ renderOverlay();
211380
+ }
211381
+ function onKeydown2(e20) {
211382
+ const items = _query.trim() ? _results : getRecentFiles();
211383
+ switch (e20.key) {
211384
+ case "ArrowDown":
211385
+ e20.preventDefault();
211386
+ _selectedIndex = Math.min(_selectedIndex + 1, items.length - 1);
211387
+ renderOverlay();
211388
+ break;
211389
+ case "ArrowUp":
211390
+ e20.preventDefault();
211391
+ _selectedIndex = Math.max(_selectedIndex - 1, 0);
211392
+ renderOverlay();
211393
+ break;
211394
+ case "Enter":
211395
+ e20.preventDefault();
211396
+ if (items[_selectedIndex])
211397
+ selectItem(items[_selectedIndex]);
211398
+ break;
211399
+ case "Escape":
211400
+ e20.preventDefault();
211401
+ closeQuickSearch();
211402
+ break;
211403
+ }
211404
+ }
211405
+ function selectItem(item) {
211406
+ closeQuickSearch();
211407
+ const path2 = item.path;
211408
+ trackRecentFile({ path: path2, name: path2.split("/").pop() });
211409
+ openFileInTab(path2);
211410
+ }
211411
+ function fileIcon(name) {
211412
+ const ext = name.split(".").pop()?.toLowerCase();
211413
+ switch (ext) {
211414
+ case "json":
211415
+ return html`<sp-icon-file-code size="s"></sp-icon-file-code>`;
211416
+ case "md":
211417
+ return html`<sp-icon-file-txt size="s"></sp-icon-file-txt>`;
211418
+ default:
211419
+ return html`<sp-icon-document size="s"></sp-icon-document>`;
211420
+ }
211421
+ }
211422
+ function dirPart(path2) {
211423
+ const parts = path2.split("/");
211424
+ parts.pop();
211425
+ return parts.length ? parts.join("/") : "";
211426
+ }
211427
+ function renderOverlay() {
211428
+ if (!_container)
211429
+ return;
211430
+ if (!_open) {
211431
+ render2(nothing, _container);
211432
+ return;
211433
+ }
211434
+ const recentFiles = getRecentFiles();
211435
+ const showRecent = !_query.trim();
211436
+ const items = showRecent ? recentFiles : _results;
211437
+ const tpl = html`
211438
+ <div class="quick-search-overlay" @click=${closeQuickSearch}>
211439
+ <div class="quick-search-panel" @click=${(e20) => e20.stopPropagation()}>
211440
+ <input
211441
+ class="quick-search-input"
211442
+ type="text"
211443
+ placeholder="Search project files…"
211444
+ .value=${_query}
211445
+ @input=${onInput}
211446
+ @keydown=${onKeydown2}
211447
+ />
211448
+ <div class="quick-search-results">
211449
+ ${items.length === 0 && _query.trim() ? html`<div class="quick-search-empty">No results</div>` : nothing}
211450
+ ${items.length === 0 && !_query.trim() && recentFiles.length === 0 ? html`<div class="quick-search-empty">Type to search project files</div>` : nothing}
211451
+ ${showRecent && recentFiles.length ? html`<div class="quick-search-section-label">Recently opened</div>` : nothing}
211452
+ ${items.map((item, i6) => html`
211453
+ <div
211454
+ class="quick-search-item ${i6 === _selectedIndex ? "selected" : ""}"
211455
+ @click=${() => selectItem(item)}
211456
+ @mouseenter=${() => {
211457
+ _selectedIndex = i6;
211458
+ renderOverlay();
211459
+ }}
211460
+ >
211461
+ <span class="quick-search-icon"
211462
+ >${fileIcon(item.name || item.path.split("/").pop())}</span
211463
+ >
211464
+ <span class="quick-search-name">${item.name || item.path.split("/").pop()}</span>
211465
+ <span class="quick-search-path">${dirPart(item.path)}</span>
211466
+ ${showRecent ? html`<span class="quick-search-badge">recent</span>` : nothing}
211467
+ </div>
211468
+ `)}
211469
+ </div>
211470
+ </div>
211471
+ </div>
211472
+ `;
211473
+ render2(tpl, _container);
211474
+ }
211475
+
211476
+ // src/editor/shortcuts.js
211236
211477
  function initShortcuts(getContext) {
211237
211478
  canvasWrap.addEventListener("wheel", (e20) => {
211238
211479
  const { canvasMode, panX, panY, setPan, applyTransform: applyTransform2 } = getContext();
211239
211480
  if (canvasMode === "edit")
211240
211481
  return;
211482
+ if (canvasMode === "manage")
211483
+ return;
211241
211484
  e20.preventDefault();
211242
211485
  if (e20.ctrlKey || e20.metaKey) {
211243
211486
  const rect = canvasWrap.getBoundingClientRect();
@@ -211341,6 +211584,10 @@ function initShortcuts(getContext) {
211341
211584
  e20.preventDefault();
211342
211585
  openProject2();
211343
211586
  break;
211587
+ case "p":
211588
+ e20.preventDefault();
211589
+ openQuickSearch();
211590
+ break;
211344
211591
  case "s":
211345
211592
  e20.preventDefault();
211346
211593
  saveFile2();
@@ -211596,6 +211843,9 @@ function tbBtnTpl(label4, onClick, iconTag) {
211596
211843
  function mount5(rootEl, ctx) {
211597
211844
  _rootEl = rootEl;
211598
211845
  _ctx11 = ctx;
211846
+ if (globalThis.__jxPlatform?.windowControls) {
211847
+ rootEl.classList.add("electrobun-webkit-app-region-drag");
211848
+ }
211599
211849
  _scope4 = effectScope();
211600
211850
  _scope4.run(() => {
211601
211851
  effect(() => {
@@ -211732,10 +211982,52 @@ function toolbarTemplate() {
211732
211982
  `)}
211733
211983
  </sp-action-group>
211734
211984
  `;
211985
+ const windowControls = globalThis.__jxPlatform?.windowControls;
211986
+ const csdTpl = windowControls ? html`
211987
+ <sp-action-group class="window-controls" size="s">
211988
+ <sp-action-button
211989
+ quiet
211990
+ size="s"
211991
+ title="Minimize"
211992
+ @click=${() => windowControls.minimize()}
211993
+ >
211994
+ <sp-icon-remove slot="icon"></sp-icon-remove>
211995
+ </sp-action-button>
211996
+ <sp-action-button
211997
+ quiet
211998
+ size="s"
211999
+ title="Maximize"
212000
+ @click=${() => windowControls.maximize()}
212001
+ >
212002
+ <sp-icon-full-screen slot="icon"></sp-icon-full-screen>
212003
+ </sp-action-button>
212004
+ <sp-action-button
212005
+ quiet
212006
+ size="s"
212007
+ title="Close"
212008
+ class="csd-close"
212009
+ @click=${() => windowControls.close()}
212010
+ >
212011
+ <sp-icon-close slot="icon"></sp-icon-close>
212012
+ </sp-action-button>
212013
+ </sp-action-group>
212014
+ ` : nothing;
212015
+ const recentProjects = getRecentProjects();
212016
+ const recentProjectsTpl = recentProjects.length ? html`
212017
+ <overlay-trigger placement="bottom-start">
212018
+ <sp-action-button size="s" slot="trigger" title="Recent projects">
212019
+ <sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>
212020
+ </sp-action-button>
212021
+ <sp-popover slot="click-content" tip>
212022
+ <sp-menu @change=${(e20) => _ctx11.openRecentProject(e20.target.value)}>
212023
+ ${recentProjects.map((p2) => html`<sp-menu-item value=${p2.root}>${p2.name}</sp-menu-item>`)}
212024
+ </sp-menu>
212025
+ </sp-popover>
212026
+ </overlay-trigger>
212027
+ ` : nothing;
211735
212028
  return html`
211736
212029
  <sp-action-group compact size="s">
211737
- ${tbBtnTpl("Open Project", _ctx11.openProject, "sp-icon-folder-open")}
211738
- ${tbBtnTpl("Open File", _ctx11.openFile, "sp-icon-document")}
212030
+ ${tbBtnTpl("Open Project", _ctx11.openProject, "sp-icon-folder-open")} ${recentProjectsTpl}
211739
212031
  ${tbBtnTpl("Save", _ctx11.saveFile, "sp-icon-save-floppy")}
211740
212032
  </sp-action-group>
211741
212033
  <sp-action-group compact size="s">
@@ -211743,14 +212035,12 @@ function toolbarTemplate() {
211743
212035
  ${tbBtnTpl("Redo", () => redo(activeTab.value), "sp-icon-redo")}
211744
212036
  </sp-action-group>
211745
212037
  <div class="tb-spacer"></div>
211746
- ${S.documentPath ? html`<span class="tb-file-title" title=${S.documentPath}
211747
- >${S.documentPath}${S.dirty ? html`<span class="tb-dirty">●</span>` : nothing}</span
211748
- >` : S.fileHandle ? html`<span class="tb-file-title"
211749
- >${S.fileHandle.name}${S.dirty ? html`<span class="tb-dirty">●</span>` : nothing}</span
211750
- >` : nothing}
211751
- ${breadcrumbTpl}
212038
+ <sp-action-button class="tb-search-trigger" size="s" quiet @click=${openQuickSearch}>
212039
+ <sp-icon-search slot="icon"></sp-icon-search>
212040
+ <span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
212041
+ </sp-action-button>
211752
212042
  <div class="tb-spacer"></div>
211753
- ${togglesTpl} ${modeSwitcherTpl}
212043
+ ${breadcrumbTpl} ${togglesTpl} ${modeSwitcherTpl} ${csdTpl}
211754
212044
  `;
211755
212045
  }
211756
212046
 
@@ -214890,15 +215180,15 @@ function bindableFieldRow(label4, type2, rawValue, onChange, filterFn = null, ex
214890
215180
  const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
214891
215181
  const signalDefs = Object.entries(defs).filter(([, d5]) => filterFn ? filterFn(d5) : !d5.$handler && d5.$prototype !== "Function");
214892
215182
  let debounce;
214893
- const onInput = (e20) => {
215183
+ const onInput2 = (e20) => {
214894
215184
  clearTimeout(debounce);
214895
215185
  debounce = setTimeout(() => onChange(e20.target.value), 400);
214896
215186
  };
214897
215187
  const staticVal = isBound ? "" : rawValue ?? "";
214898
- const staticTpl = type2 === "textarea" ? html`<sp-textfield multiline size="s" value=${staticVal} @input=${onInput}></sp-textfield>` : type2 === "checkbox" ? html`<sp-checkbox
215188
+ const staticTpl = type2 === "textarea" ? html`<sp-textfield multiline size="s" value=${staticVal} @input=${onInput2}></sp-textfield>` : type2 === "checkbox" ? html`<sp-checkbox
214899
215189
  ?checked=${!!staticVal}
214900
215190
  @change=${(e20) => onChange(e20.target.checked)}
214901
- ></sp-checkbox>` : html`<sp-textfield size="s" value=${staticVal} @input=${onInput}></sp-textfield>`;
215191
+ ></sp-checkbox>` : html`<sp-textfield size="s" value=${staticVal} @input=${onInput2}></sp-textfield>`;
214902
215192
  const boundTpl = html`
214903
215193
  <sp-picker
214904
215194
  size="s"
@@ -215003,16 +215293,18 @@ function renderFrontmatterOnlyPanel() {
215003
215293
  fields.push({ field, entry: fieldSchema, value: fm[field] });
215004
215294
  }
215005
215295
  for (const [field, value2] of Object.entries(fm)) {
215006
- if (!schemaProps[field]) {
215007
- fields.push({
215008
- field,
215009
- entry: { type: typeof value2 === "boolean" ? "boolean" : "string" },
215010
- value: value2
215011
- });
215012
- }
215296
+ if (schemaProps[field] || field.startsWith("$"))
215297
+ continue;
215298
+ fields.push({
215299
+ field,
215300
+ entry: { type: typeof value2 === "boolean" ? "boolean" : "string" },
215301
+ value: value2
215302
+ });
215013
215303
  }
215014
215304
  } else {
215015
215305
  for (const [field, value2] of Object.entries(fm)) {
215306
+ if (field.startsWith("$"))
215307
+ continue;
215016
215308
  fields.push({
215017
215309
  field,
215018
215310
  entry: { type: typeof value2 === "boolean" ? "boolean" : "string" },
@@ -216094,16 +216386,18 @@ function renderPropertiesPanelTemplate(ctx) {
216094
216386
  fields.push({ field, entry: fieldSchema, value: fm[field] });
216095
216387
  }
216096
216388
  for (const [field, value2] of Object.entries(fm)) {
216097
- if (!schemaProps[field]) {
216098
- fields.push({
216099
- field,
216100
- entry: { type: typeof value2 === "boolean" ? "boolean" : "string" },
216101
- value: value2
216102
- });
216103
- }
216389
+ if (schemaProps[field] || field.startsWith("$"))
216390
+ continue;
216391
+ fields.push({
216392
+ field,
216393
+ entry: { type: typeof value2 === "boolean" ? "boolean" : "string" },
216394
+ value: value2
216395
+ });
216104
216396
  }
216105
216397
  } else {
216106
216398
  for (const [field, value2] of Object.entries(fm)) {
216399
+ if (field.startsWith("$"))
216400
+ continue;
216107
216401
  fields.push({
216108
216402
  field,
216109
216403
  entry: { type: typeof value2 === "boolean" ? "boolean" : "string" },
@@ -217034,7 +217328,7 @@ mount5(toolbarEl, {
217034
217328
  navigateBack: () => navigateBack(),
217035
217329
  closeFunctionEditor: () => closeFunctionEditor(),
217036
217330
  openProject: () => openProject2(),
217037
- openFile: () => openFile(),
217331
+ openRecentProject: (root3) => openRecentProject(root3),
217038
217332
  saveFile: () => saveFile(),
217039
217333
  parseMediaEntries,
217040
217334
  getCanvasMode,
@@ -217042,6 +217336,7 @@ mount5(toolbarEl, {
217042
217336
  renderCanvas: () => renderCanvas(),
217043
217337
  safeRenderRightPanel: () => safeRenderRightPanel()
217044
217338
  });
217339
+ initQuickSearch();
217045
217340
  mount8(document.querySelector("#tab-strip"));
217046
217341
  mount3({
217047
217342
  getCanvasMode,
@@ -217262,6 +217557,51 @@ function openProject2() {
217262
217557
  renderLeftPanel
217263
217558
  });
217264
217559
  }
217560
+ async function openRecentProject(root3) {
217561
+ try {
217562
+ const platform4 = getPlatform();
217563
+ platform4.projectRoot = root3;
217564
+ const content3 = await platform4.readFile("project.json");
217565
+ const config = JSON.parse(content3);
217566
+ replaceAllTabs({ id: "initial", document: { tagName: "div", children: [] } });
217567
+ setProjectState({
217568
+ ...projectState,
217569
+ projectRoot: root3,
217570
+ isSiteProject: true,
217571
+ projectConfig: config,
217572
+ name: config.name || root3.split("/").pop(),
217573
+ dirs: new Map,
217574
+ expanded: new Set,
217575
+ selectedPath: null,
217576
+ searchQuery: ""
217577
+ });
217578
+ await loadDirectory(".");
217579
+ await loadComponentRegistry();
217580
+ const conventionalDirs = [
217581
+ "pages",
217582
+ "layouts",
217583
+ "components",
217584
+ "content",
217585
+ "data",
217586
+ "public",
217587
+ "styles"
217588
+ ];
217589
+ const entries2 = projectState.dirs.get(".") || [];
217590
+ for (const e20 of entries2) {
217591
+ if (e20.type === "directory" && conventionalDirs.includes(e20.name)) {
217592
+ projectState.expanded.add(e20.path || e20.name);
217593
+ await loadDirectory(e20.path || e20.name);
217594
+ }
217595
+ }
217596
+ addRecentProject(projectState.name, root3);
217597
+ view.leftTab = "files";
217598
+ renderActivityBar();
217599
+ renderLeftPanel();
217600
+ statusMessage(`Opened project: ${projectState.name}`);
217601
+ } catch (e20) {
217602
+ statusMessage(`Error: ${e20.message}`);
217603
+ }
217604
+ }
217265
217605
  function renderFilesTemplate2() {
217266
217606
  return renderFilesTemplate({ openProject: openProject2, openFileFromTree, renderLeftPanel });
217267
217607
  }
@@ -217318,5 +217658,5 @@ effect(() => {
217318
217658
  scheduleAutosave();
217319
217659
  });
217320
217660
 
217321
- //# debugId=441141DC7697869B64756E2164756E21
217661
+ //# debugId=D4BE66DA4C58521864756E2164756E21
217322
217662
  //# sourceMappingURL=studio.js.map