@jxsuite/studio 0.16.1 → 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
@@ -176998,12 +176998,6 @@ async function codeService(action, payload) {
176998
176998
  return null;
176999
176999
  return platform3.codeService(action, payload);
177000
177000
  }
177001
- async function locateDocument(name) {
177002
- const platform3 = getPlatform();
177003
- if (!platform3.locateFile)
177004
- return null;
177005
- return platform3.locateFile(name);
177006
- }
177007
177001
  var pluginSchemaCache = new Map;
177008
177002
  async function fetchPluginSchema(def3, state) {
177009
177003
  if (!def3.$src || !def3.$prototype)
@@ -177062,58 +177056,6 @@ function getFunctionArgs(editing, document4) {
177062
177056
  // src/files/file-ops.js
177063
177057
  init_platform();
177064
177058
  init_workspace();
177065
- async function openFile() {
177066
- try {
177067
- if ("showOpenFilePicker" in window) {
177068
- const [handle2] = await window.showOpenFilePicker({
177069
- types: [
177070
- { description: "Jx Component", accept: { "application/json": [".json"] } },
177071
- { description: "Markdown Content", accept: { "text/markdown": [".md"] } }
177072
- ]
177073
- });
177074
- const file = await handle2.getFile();
177075
- const text6 = await file.text();
177076
- const documentPath = await locateDocument(handle2.name);
177077
- if (handle2.name.endsWith(".md")) {
177078
- const { document: document4, frontmatter: frontmatter2 } = await loadMarkdown(text6);
177079
- openTab({
177080
- id: handle2.name,
177081
- documentPath,
177082
- fileHandle: handle2,
177083
- document: document4,
177084
- frontmatter: frontmatter2,
177085
- sourceFormat: "md"
177086
- });
177087
- } else {
177088
- const document4 = JSON.parse(text6);
177089
- openTab({ id: handle2.name, documentPath, fileHandle: handle2, document: document4 });
177090
- }
177091
- statusMessage(`Opened ${handle2.name}`);
177092
- } else {
177093
- const input = document.createElement("input");
177094
- input.type = "file";
177095
- input.accept = ".json,.md";
177096
- input.onchange = async () => {
177097
- const file = input.files?.[0];
177098
- if (!file)
177099
- return;
177100
- const text6 = await file.text();
177101
- if (file.name.endsWith(".md")) {
177102
- const { document: document4, frontmatter: frontmatter2 } = await loadMarkdown(text6);
177103
- openTab({ id: file.name, document: document4, frontmatter: frontmatter2, sourceFormat: "md" });
177104
- } else {
177105
- const document4 = JSON.parse(text6);
177106
- openTab({ id: file.name, document: document4 });
177107
- }
177108
- statusMessage(`Opened ${file.name}`);
177109
- };
177110
- input.click();
177111
- }
177112
- } catch (e) {
177113
- if (e.name !== "AbortError")
177114
- statusMessage(`Error: ${e.message}`);
177115
- }
177116
- }
177117
177059
  async function loadMarkdown(source) {
177118
177060
  const { transpileJxMarkdown: transpileJxMarkdown2 } = await Promise.resolve().then(() => (init_transpile(), exports_transpile));
177119
177061
  const doc = transpileJxMarkdown2(source);
@@ -183405,6 +183347,19 @@ function extOf(name) {
183405
183347
  const dot = name.lastIndexOf(".");
183406
183348
  return dot > 0 ? name.slice(dot).toLowerCase() : "";
183407
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
+ }
183408
183363
  function categoryFor(dir, ext) {
183409
183364
  if (ext && MEDIA_EXTENSIONS2.has(ext))
183410
183365
  return "Media";
@@ -183662,9 +183617,12 @@ async function renderBrowse(container, ctx) {
183662
183617
  <sp-table-row
183663
183618
  value=${f.path}
183664
183619
  class="browse-row"
183665
- @click=${() => ctx.openFile(f.path)}
183620
+ style=${isImage(f.ext) ? "cursor:default" : ""}
183621
+ @click=${isImage(f.ext) ? nothing : () => ctx.openFile(f.path)}
183666
183622
  >
183667
- <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
+ >
183668
183626
  <sp-table-cell>${f.category}</sp-table-cell>
183669
183627
  <sp-table-cell>${f.type}</sp-table-cell>
183670
183628
  <sp-table-cell class="browse-path-cell">${f.path}</sp-table-cell>
@@ -184355,6 +184313,48 @@ init_store();
184355
184313
  init_platform();
184356
184314
  init_components();
184357
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
184358
184358
  var fileIconMap = {
184359
184359
  "sp-icon-folder-open": html`<sp-icon-folder-open></sp-icon-folder-open>`,
184360
184360
  "sp-icon-folder": html`<sp-icon-folder></sp-icon-folder>`,
@@ -184440,6 +184440,7 @@ async function openProject({ renderActivityBar, renderLeftPanel }) {
184440
184440
  }
184441
184441
  projectState.projectDirs = foundDirs;
184442
184442
  view.leftTab = "files";
184443
+ addRecentProject(projectState.name, projectState.projectRoot);
184443
184444
  renderActivityBar();
184444
184445
  renderLeftPanel();
184445
184446
  statusMessage(`Opened project: ${projectState.name}`);
@@ -184808,6 +184809,7 @@ async function openFileInTab(path2) {
184808
184809
  sourceFormat: path2.endsWith(".md") ? "md" : null
184809
184810
  });
184810
184811
  projectState.selectedPath = path2;
184812
+ trackRecentFile({ path: path2, name: path2.split("/").pop() || path2 });
184811
184813
  statusMessage(`Opened ${path2.split("/").pop()}`);
184812
184814
  } catch (e) {
184813
184815
  statusMessage(`Error: ${e.message}`);
@@ -185771,6 +185773,16 @@ function createDevServerPlatform() {
185771
185773
  } catch {}
185772
185774
  return null;
185773
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
+ },
185774
185786
  async fetchPluginSchema(src, prototype, base2) {
185775
185787
  const params = new URLSearchParams({ src });
185776
185788
  if (prototype)
@@ -210032,6 +210044,67 @@ class IconRemove extends IconBase {
210032
210044
  }
210033
210045
  }
210034
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
+
210035
210108
  // ../../node_modules/@spectrum-web-components/icons-workflow/src/elements/IconViewColumn.js
210036
210109
  init_index_dev();
210037
210110
 
@@ -211157,6 +211230,7 @@ var components = [
211157
211230
  ["sp-icon-text-baseline-shift", IconTextBaselineShift],
211158
211231
  ["sp-icon-flip-vertical", IconFlipVertical],
211159
211232
  ["sp-icon-remove", IconRemove],
211233
+ ["sp-icon-full-screen", IconFullScreen],
211160
211234
  ["sp-icon-download", IconDownload],
211161
211235
  ["sp-icon-checkmark", IconCheckmark],
211162
211236
  ["sp-icon-view-column", IconViewColumn],
@@ -211185,14 +211259,14 @@ Theme.registerThemeFragment("dark", "color", theme_dark_css_default);
211185
211259
  Theme.registerThemeFragment("medium", "scale", scale_medium_css_default);
211186
211260
 
211187
211261
  // src/ui/panel-resize.js
211188
- var STORAGE_KEY = "jx-studio-panel-widths";
211262
+ var STORAGE_KEY2 = "jx-studio-panel-widths";
211189
211263
  var MIN_WIDTH = 160;
211190
211264
  var MAX_RATIO = 0.5;
211191
211265
  var DEFAULT_LEFT = 240;
211192
211266
  var DEFAULT_RIGHT = 280;
211193
211267
  var root2 = document.documentElement;
211194
211268
  try {
211195
- const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
211269
+ const saved = JSON.parse(localStorage.getItem(STORAGE_KEY2) || "{}");
211196
211270
  if (saved.left)
211197
211271
  root2.style.setProperty("--panel-w-left", `${saved.left}px`);
211198
211272
  if (saved.right)
@@ -211238,7 +211312,7 @@ function persistWidths() {
211238
211312
  const left = parseInt(getComputedStyle(root2).getPropertyValue("--panel-w-left")) || DEFAULT_LEFT;
211239
211313
  const right = parseInt(getComputedStyle(root2).getPropertyValue("--panel-w-right")) || DEFAULT_RIGHT;
211240
211314
  try {
211241
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, right }));
211315
+ localStorage.setItem(STORAGE_KEY2, JSON.stringify({ left, right }));
211242
211316
  } catch {}
211243
211317
  }
211244
211318
  var resizeLeft = document.getElementById("resize-left");
@@ -211251,11 +211325,162 @@ if (resizeRight)
211251
211325
  // src/editor/shortcuts.js
211252
211326
  init_store();
211253
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
211254
211477
  function initShortcuts(getContext) {
211255
211478
  canvasWrap.addEventListener("wheel", (e20) => {
211256
211479
  const { canvasMode, panX, panY, setPan, applyTransform: applyTransform2 } = getContext();
211257
211480
  if (canvasMode === "edit")
211258
211481
  return;
211482
+ if (canvasMode === "manage")
211483
+ return;
211259
211484
  e20.preventDefault();
211260
211485
  if (e20.ctrlKey || e20.metaKey) {
211261
211486
  const rect = canvasWrap.getBoundingClientRect();
@@ -211359,6 +211584,10 @@ function initShortcuts(getContext) {
211359
211584
  e20.preventDefault();
211360
211585
  openProject2();
211361
211586
  break;
211587
+ case "p":
211588
+ e20.preventDefault();
211589
+ openQuickSearch();
211590
+ break;
211362
211591
  case "s":
211363
211592
  e20.preventDefault();
211364
211593
  saveFile2();
@@ -211614,6 +211843,9 @@ function tbBtnTpl(label4, onClick, iconTag) {
211614
211843
  function mount5(rootEl, ctx) {
211615
211844
  _rootEl = rootEl;
211616
211845
  _ctx11 = ctx;
211846
+ if (globalThis.__jxPlatform?.windowControls) {
211847
+ rootEl.classList.add("electrobun-webkit-app-region-drag");
211848
+ }
211617
211849
  _scope4 = effectScope();
211618
211850
  _scope4.run(() => {
211619
211851
  effect(() => {
@@ -211750,10 +211982,52 @@ function toolbarTemplate() {
211750
211982
  `)}
211751
211983
  </sp-action-group>
211752
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;
211753
212028
  return html`
211754
212029
  <sp-action-group compact size="s">
211755
- ${tbBtnTpl("Open Project", _ctx11.openProject, "sp-icon-folder-open")}
211756
- ${tbBtnTpl("Open File", _ctx11.openFile, "sp-icon-document")}
212030
+ ${tbBtnTpl("Open Project", _ctx11.openProject, "sp-icon-folder-open")} ${recentProjectsTpl}
211757
212031
  ${tbBtnTpl("Save", _ctx11.saveFile, "sp-icon-save-floppy")}
211758
212032
  </sp-action-group>
211759
212033
  <sp-action-group compact size="s">
@@ -211761,14 +212035,12 @@ function toolbarTemplate() {
211761
212035
  ${tbBtnTpl("Redo", () => redo(activeTab.value), "sp-icon-redo")}
211762
212036
  </sp-action-group>
211763
212037
  <div class="tb-spacer"></div>
211764
- ${S.documentPath ? html`<span class="tb-file-title" title=${S.documentPath}
211765
- >${S.documentPath}${S.dirty ? html`<span class="tb-dirty">●</span>` : nothing}</span
211766
- >` : S.fileHandle ? html`<span class="tb-file-title"
211767
- >${S.fileHandle.name}${S.dirty ? html`<span class="tb-dirty">●</span>` : nothing}</span
211768
- >` : nothing}
211769
- ${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>
211770
212042
  <div class="tb-spacer"></div>
211771
- ${togglesTpl} ${modeSwitcherTpl}
212043
+ ${breadcrumbTpl} ${togglesTpl} ${modeSwitcherTpl} ${csdTpl}
211772
212044
  `;
211773
212045
  }
211774
212046
 
@@ -214908,15 +215180,15 @@ function bindableFieldRow(label4, type2, rawValue, onChange, filterFn = null, ex
214908
215180
  const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
214909
215181
  const signalDefs = Object.entries(defs).filter(([, d5]) => filterFn ? filterFn(d5) : !d5.$handler && d5.$prototype !== "Function");
214910
215182
  let debounce;
214911
- const onInput = (e20) => {
215183
+ const onInput2 = (e20) => {
214912
215184
  clearTimeout(debounce);
214913
215185
  debounce = setTimeout(() => onChange(e20.target.value), 400);
214914
215186
  };
214915
215187
  const staticVal = isBound ? "" : rawValue ?? "";
214916
- 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
214917
215189
  ?checked=${!!staticVal}
214918
215190
  @change=${(e20) => onChange(e20.target.checked)}
214919
- ></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>`;
214920
215192
  const boundTpl = html`
214921
215193
  <sp-picker
214922
215194
  size="s"
@@ -217056,7 +217328,7 @@ mount5(toolbarEl, {
217056
217328
  navigateBack: () => navigateBack(),
217057
217329
  closeFunctionEditor: () => closeFunctionEditor(),
217058
217330
  openProject: () => openProject2(),
217059
- openFile: () => openFile(),
217331
+ openRecentProject: (root3) => openRecentProject(root3),
217060
217332
  saveFile: () => saveFile(),
217061
217333
  parseMediaEntries,
217062
217334
  getCanvasMode,
@@ -217064,6 +217336,7 @@ mount5(toolbarEl, {
217064
217336
  renderCanvas: () => renderCanvas(),
217065
217337
  safeRenderRightPanel: () => safeRenderRightPanel()
217066
217338
  });
217339
+ initQuickSearch();
217067
217340
  mount8(document.querySelector("#tab-strip"));
217068
217341
  mount3({
217069
217342
  getCanvasMode,
@@ -217284,6 +217557,51 @@ function openProject2() {
217284
217557
  renderLeftPanel
217285
217558
  });
217286
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
+ }
217287
217605
  function renderFilesTemplate2() {
217288
217606
  return renderFilesTemplate({ openProject: openProject2, openFileFromTree, renderLeftPanel });
217289
217607
  }
@@ -217340,5 +217658,5 @@ effect(() => {
217340
217658
  scheduleAutosave();
217341
217659
  });
217342
217660
 
217343
- //# debugId=E697E6D3C357567164756E2164756E21
217661
+ //# debugId=D4BE66DA4C58521864756E2164756E21
217344
217662
  //# sourceMappingURL=studio.js.map