@jxsuite/studio 0.19.0 → 0.21.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.
Files changed (99) hide show
  1. package/dist/studio.css +98 -98
  2. package/dist/studio.js +13476 -12330
  3. package/dist/studio.js.map +152 -148
  4. package/dist/workers/editor.worker.js +79 -79
  5. package/dist/workers/json.worker.js +109 -109
  6. package/dist/workers/ts.worker.js +82 -82
  7. package/package.json +19 -7
  8. package/src/browse/browse-modal.js +16 -13
  9. package/src/browse/browse.js +44 -35
  10. package/src/canvas/canvas-diff.js +24 -16
  11. package/src/canvas/canvas-helpers.js +24 -19
  12. package/src/canvas/canvas-live-render.js +113 -39
  13. package/src/canvas/canvas-render.js +119 -73
  14. package/src/canvas/canvas-utils.js +51 -21
  15. package/src/canvas/nested-site-style.js +50 -0
  16. package/src/editor/component-inline-edit.js +14 -13
  17. package/src/editor/content-inline-edit.js +22 -10
  18. package/src/editor/context-menu.js +28 -14
  19. package/src/editor/convert-targets.js +60 -0
  20. package/src/editor/convert-to-component.js +20 -19
  21. package/src/editor/convert-to-repeater.js +226 -0
  22. package/src/editor/inline-edit.js +69 -28
  23. package/src/editor/insertion-helper.js +16 -12
  24. package/src/editor/shortcuts.js +14 -4
  25. package/src/editor/slash-menu.js +40 -19
  26. package/src/files/components.js +1 -1
  27. package/src/files/file-ops.js +37 -16
  28. package/src/files/files.js +322 -68
  29. package/src/github/github-auth.js +122 -0
  30. package/src/github/github-publish.js +126 -0
  31. package/src/markdown/md-convert.js +172 -107
  32. package/src/new-project/new-project-modal.js +204 -0
  33. package/src/panels/activity-bar.js +27 -25
  34. package/src/panels/ai-panel.js +406 -0
  35. package/src/panels/block-action-bar.js +77 -28
  36. package/src/panels/canvas-dnd.js +8 -2
  37. package/src/panels/data-explorer.js +19 -13
  38. package/src/panels/dnd.js +144 -92
  39. package/src/panels/editors.js +55 -35
  40. package/src/panels/elements-panel.js +33 -19
  41. package/src/panels/events-panel.js +15 -10
  42. package/src/panels/git-panel.js +475 -99
  43. package/src/panels/head-panel.js +121 -81
  44. package/src/panels/imports-panel.js +94 -65
  45. package/src/panels/layers-panel.js +64 -43
  46. package/src/panels/left-panel.js +105 -48
  47. package/src/panels/overlays.js +1 -0
  48. package/src/panels/panel-events.js +45 -30
  49. package/src/panels/preview-render.js +23 -18
  50. package/src/panels/properties-panel.js +378 -257
  51. package/src/panels/pseudo-preview.js +2 -2
  52. package/src/panels/quick-search.js +11 -9
  53. package/src/panels/right-panel.js +42 -16
  54. package/src/panels/shared.js +4 -4
  55. package/src/panels/signals-panel.js +415 -166
  56. package/src/panels/statusbar.js +10 -6
  57. package/src/panels/style-inputs.js +67 -35
  58. package/src/panels/style-panel.js +239 -134
  59. package/src/panels/style-utils.js +34 -16
  60. package/src/panels/stylebook-layers-panel.js +12 -10
  61. package/src/panels/stylebook-panel.js +134 -66
  62. package/src/panels/tab-strip.js +5 -2
  63. package/src/panels/toolbar.js +333 -109
  64. package/src/panels/welcome-screen.js +121 -0
  65. package/src/platform.js +2 -4
  66. package/src/platforms/devserver.js +113 -7
  67. package/src/resize-edges.js +98 -0
  68. package/src/services/cem-export.js +12 -7
  69. package/src/services/code-services.js +30 -12
  70. package/src/settings/content-types-editor.js +8 -8
  71. package/src/settings/css-vars-editor.js +30 -24
  72. package/src/settings/defs-editor.js +12 -7
  73. package/src/settings/general-settings.js +85 -3
  74. package/src/settings/head-editor.js +30 -24
  75. package/src/settings/schema-field-ui.js +58 -39
  76. package/src/settings/settings-modal.js +12 -9
  77. package/src/site-context.js +41 -31
  78. package/src/state.js +70 -29
  79. package/src/store.js +36 -35
  80. package/src/studio.js +120 -90
  81. package/src/tabs/tab.js +67 -43
  82. package/src/tabs/transact.js +23 -13
  83. package/src/ui/button-group.js +24 -24
  84. package/src/ui/color-selector.js +28 -23
  85. package/src/ui/field-row.js +3 -3
  86. package/src/ui/icons.js +2 -2
  87. package/src/ui/layers.js +31 -1
  88. package/src/ui/media-picker.js +7 -7
  89. package/src/ui/spectrum.js +5 -1
  90. package/src/ui/unit-selector.js +18 -16
  91. package/src/ui/value-selector.js +21 -15
  92. package/src/ui/widgets.js +22 -19
  93. package/src/utils/canvas-media.js +5 -4
  94. package/src/utils/edit-display.js +32 -26
  95. package/src/utils/google-fonts.js +11 -11
  96. package/src/utils/inherited-style.js +9 -8
  97. package/src/utils/studio-utils.js +18 -18
  98. package/src/view.js +58 -1
  99. package/src/workspace/workspace.js +29 -7
@@ -16,17 +16,39 @@ import { openQuickSearch } from "./quick-search.js";
16
16
  import { getPlatform } from "../platform.js";
17
17
  import { refreshGitStatus } from "./git-panel.js";
18
18
  import { openBrowseModal } from "../browse/browse-modal.js";
19
+ import { openNewProjectModal } from "../new-project/new-project-modal.js";
19
20
 
20
21
  /** @type {HTMLElement | null} */
21
22
  let _rootEl = null;
22
23
 
23
- /** @type {any} */
24
+ /**
25
+ * @typedef {{
26
+ * navigateBack: () => void;
27
+ * navigateToLevel: (level: number) => void;
28
+ * openProject: () => void;
29
+ * openFile?: (path: string) => void;
30
+ * saveFile: () => void;
31
+ * parseMediaEntries: (media: Record<string, string> | null | undefined) => {
32
+ * sizeBreakpoints: { name: string; query: string; width: number; type: string }[];
33
+ * featureQueries: { name: string; query: string }[];
34
+ * baseWidth: number;
35
+ * };
36
+ * getCanvasMode: () => string;
37
+ * setCanvasMode: (mode: string) => void;
38
+ * renderCanvas: () => void;
39
+ * safeRenderRightPanel: () => void;
40
+ * openRecentProject: (root: string) => Promise<void>;
41
+ * closeFunctionEditor: () => void;
42
+ * }} ToolbarCtx
43
+ */
44
+
45
+ /** @type {ToolbarCtx | null} */
24
46
  let _ctx = null;
25
47
 
26
48
  /** @type {import("@vue/reactivity").EffectScope | null} */
27
49
  let _scope = null;
28
50
 
29
- const toolbarIconMap = /** @type {Record<string, any>} */ ({
51
+ const toolbarIconMap = /** @type {Record<string, import("lit-html").TemplateResult>} */ ({
30
52
  "sp-icon-folder-open": html`<sp-icon-folder-open slot="icon"></sp-icon-folder-open>`,
31
53
  "sp-icon-save-floppy": html`<sp-icon-save-floppy slot="icon"></sp-icon-save-floppy>`,
32
54
  "sp-icon-back": html`<sp-icon-back slot="icon"></sp-icon-back>`,
@@ -45,9 +67,9 @@ const toolbarIconMap = /** @type {Record<string, any>} */ ({
45
67
  });
46
68
 
47
69
  /**
48
- * @param {any} label
49
- * @param {any} onClick
50
- * @param {any} iconTag
70
+ * @param {string} label
71
+ * @param {Function} onClick
72
+ * @param {string} [iconTag]
51
73
  */
52
74
  function tbBtnTpl(label, onClick, iconTag) {
53
75
  return html`
@@ -61,30 +83,37 @@ function tbBtnTpl(label, onClick, iconTag) {
61
83
  * Mount the toolbar panel.
62
84
  *
63
85
  * @param {HTMLElement} rootEl
64
- * @param {any} ctx — { navigateBack, closeFunctionEditor, openProject, openFile, saveFile,
86
+ * @param {ToolbarCtx} ctx — { navigateBack, closeFunctionEditor, openProject, openFile, saveFile,
65
87
  * parseMediaEntries, getCanvasMode, setCanvasMode, renderCanvas, safeRenderRightPanel }
66
88
  */
67
89
  export function mount(rootEl, ctx) {
68
90
  _rootEl = rootEl;
69
91
  _ctx = ctx;
70
- if (/** @type {any} */ (globalThis).__jxPlatform?.windowControls) {
92
+ if (
93
+ /** @type {{ __jxPlatform?: { windowControls?: unknown } }} */ (
94
+ /** @type {unknown} */ (globalThis)
95
+ ).__jxPlatform?.windowControls
96
+ ) {
71
97
  rootEl.classList.add("electrobun-webkit-app-region-drag");
72
98
  }
73
99
  _scope = effectScope();
74
100
  _scope.run(() => {
75
101
  effect(() => {
76
102
  const tab = activeTab.value;
77
- if (!tab) return;
78
- // Read reactive properties to establish tracking
79
- void tab.doc.document;
80
- void tab.doc.dirty;
81
- void tab.doc.mode;
82
- void tab.session.selection;
83
- void tab.session.ui.canvasMode;
84
- void tab.session.ui.editingFunction;
85
- void tab.session.ui.featureToggles;
86
- void tab.session.ui.rightTab;
87
- void tab.session.ui.gitStatus;
103
+ if (tab) {
104
+ // Read reactive properties to establish tracking
105
+ void tab.doc.document;
106
+ void tab.doc.dirty;
107
+ void tab.doc.mode;
108
+ void tab.session.selection;
109
+ void tab.session.ui.canvasMode;
110
+ void tab.session.ui.editingFunction;
111
+ void tab.session.ui.featureToggles;
112
+ void tab.session.ui.rightTab;
113
+ void tab.session.ui.gitStatus;
114
+ void tab.history.index;
115
+ void tab.history.snapshots.length;
116
+ }
88
117
  render();
89
118
  });
90
119
  });
@@ -106,10 +135,155 @@ export function render() {
106
135
  }
107
136
  }
108
137
 
138
+ async function handleNewProject() {
139
+ const result = await openNewProjectModal();
140
+ if (result && _ctx) {
141
+ await _ctx.openRecentProject(result.root);
142
+ }
143
+ }
144
+
145
+ /** @param {ToolbarCtx} ctx */
146
+ function minimalToolbarTemplate(ctx) {
147
+ const recentProjects = getRecentProjects();
148
+ const recentProjectsTpl = html`
149
+ <overlay-trigger placement="bottom-start" triggered-by="click">
150
+ <sp-action-button size="s" slot="trigger" title="Recent projects" class="tb-split-trigger">
151
+ <sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>
152
+ </sp-action-button>
153
+ <sp-popover slot="click-content" tip>
154
+ <sp-menu
155
+ @change=${(/** @type {Event} */ e) => {
156
+ const val = /** @type {HTMLInputElement} */ (/** @type {unknown} */ (e.target)).value;
157
+ if (val === "__new__") {
158
+ handleNewProject();
159
+ } else {
160
+ ctx.openRecentProject(val);
161
+ }
162
+ }}
163
+ >
164
+ <sp-menu-item value="__new__">New Project…</sp-menu-item>
165
+ ${recentProjects.length
166
+ ? html`<sp-menu-divider></sp-menu-divider> ${recentProjects.map(
167
+ (p) => html`<sp-menu-item value=${p.root}>${p.name}</sp-menu-item>`,
168
+ )}`
169
+ : nothing}
170
+ </sp-menu>
171
+ </sp-popover>
172
+ </overlay-trigger>
173
+ `;
174
+
175
+ const windowControls = /**
176
+ * @type {{
177
+ * __jxPlatform?: {
178
+ * windowControls?: { minimize: () => void; maximize: () => void; close: () => void };
179
+ * };
180
+ * }}
181
+ */ (/** @type {unknown} */ (globalThis)).__jxPlatform?.windowControls;
182
+ const csdTpl = windowControls
183
+ ? html`
184
+ <sp-action-group class="window-controls" size="s">
185
+ <sp-action-button
186
+ quiet
187
+ size="s"
188
+ title="Minimize"
189
+ @click=${() => windowControls.minimize()}
190
+ >
191
+ <sp-icon-remove slot="icon"></sp-icon-remove>
192
+ </sp-action-button>
193
+ <sp-action-button
194
+ quiet
195
+ size="s"
196
+ title="Maximize"
197
+ @click=${() => windowControls.maximize()}
198
+ >
199
+ <sp-icon-rectangle slot="icon"></sp-icon-rectangle>
200
+ </sp-action-button>
201
+ <sp-action-button
202
+ quiet
203
+ size="s"
204
+ title="Close"
205
+ class="csd-close"
206
+ @click=${() => windowControls.close()}
207
+ >
208
+ <sp-icon-close slot="icon"></sp-icon-close>
209
+ </sp-action-button>
210
+ </sp-action-group>
211
+ `
212
+ : nothing;
213
+
214
+ return html`
215
+ <div class="tb-split-btn">
216
+ <sp-action-button size="s" class="tb-split-main" @click=${ctx.openProject}>
217
+ ${toolbarIconMap["sp-icon-folder-open"]} Open Project
218
+ </sp-action-button>
219
+ ${recentProjectsTpl}
220
+ </div>
221
+ ${tbBtnTpl("Manage", openBrowseModal, "sp-icon-view-list")}
222
+ <sp-action-button size="s" disabled>
223
+ ${toolbarIconMap["sp-icon-save-floppy"]} Save
224
+ </sp-action-button>
225
+ <sp-action-group compact size="s">
226
+ <sp-action-button size="s" disabled>
227
+ ${toolbarIconMap["sp-icon-undo"]} Undo
228
+ </sp-action-button>
229
+ <sp-action-button size="s" disabled>
230
+ ${toolbarIconMap["sp-icon-redo"]} Redo
231
+ </sp-action-button>
232
+ </sp-action-group>
233
+ <div class="tb-spacer"></div>
234
+ <sp-action-button class="tb-search-trigger" size="s" quiet @click=${openQuickSearch}>
235
+ <sp-icon-search slot="icon"></sp-icon-search>
236
+ <span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
237
+ </sp-action-button>
238
+ <div class="tb-spacer"></div>
239
+ <sp-action-group selects="single" size="s" compact>
240
+ ${modes.map(
241
+ (m) => html`
242
+ <sp-action-button size="s" disabled ?selected=${m.key === "design"}>
243
+ ${toolbarIconMap[m.iconTag]}${m.label}
244
+ </sp-action-button>
245
+ `,
246
+ )}
247
+ </sp-action-group>
248
+ <sp-action-button
249
+ quiet
250
+ size="s"
251
+ title="Toggle Right Panel"
252
+ @click=${() => {
253
+ view.rightPanelCollapsed = !view.rightPanelCollapsed;
254
+ applyPanelCollapse();
255
+ render();
256
+ }}
257
+ >
258
+ ${view.rightPanelCollapsed
259
+ ? html`<sp-icon-rail-right-open slot="icon"></sp-icon-rail-right-open>`
260
+ : html`<sp-icon-rail-right-close slot="icon"></sp-icon-rail-right-close>`}
261
+ </sp-action-button>
262
+ ${csdTpl}
263
+ `;
264
+ }
265
+
266
+ const modes = [
267
+ { key: "edit", label: "Edit", iconTag: "sp-icon-edit" },
268
+ { key: "design", label: "Design", iconTag: "sp-icon-artboard" },
269
+ { key: "preview", label: "Preview", iconTag: "sp-icon-preview" },
270
+ { key: "source", label: "Code", iconTag: "sp-icon-code" },
271
+ { key: "stylebook", label: "Stylebook", iconTag: "sp-icon-brush" },
272
+ ];
273
+
109
274
  function toolbarTemplate() {
110
275
  const tab = activeTab.value;
111
- if (!tab) return html``;
112
- const S = /** @type {any} */ ({
276
+ if (!_ctx) return html``;
277
+ const ctx = _ctx;
278
+
279
+ if (!tab) return minimalToolbarTemplate(ctx);
280
+
281
+ const allowedModes = new Set(tab.capabilities.modes);
282
+ const canUndo = tab.history.index > 0;
283
+ const canRedo = tab.history.index < tab.history.snapshots.length - 1;
284
+ const canSave = tab.doc.dirty;
285
+
286
+ const S = {
113
287
  document: tab.doc.document,
114
288
  ui: tab.session.ui,
115
289
  mode: tab.doc.mode,
@@ -118,19 +292,19 @@ function toolbarTemplate() {
118
292
  documentPath: tab.documentPath,
119
293
  fileHandle: tab.fileHandle,
120
294
  documentStack: tab.session.documentStack,
121
- });
122
- const canvasMode = _ctx.getCanvasMode();
295
+ };
296
+ const canvasMode = ctx.getCanvasMode();
123
297
  const hasStack = S.documentStack && S.documentStack.length > 0;
124
298
 
125
299
  const breadcrumbTpl = hasStack
126
300
  ? html`
127
301
  <div class="breadcrumb">
128
- <sp-action-button size="s" title="Return to parent document" @click=${_ctx.navigateBack}>
302
+ <sp-action-button size="s" title="Return to parent document" @click=${ctx.navigateBack}>
129
303
  ${toolbarIconMap["sp-icon-back"]}Back
130
304
  </sp-action-button>
131
305
  ${S.documentStack.map(
132
- (/** @type {any} */ frame, /** @type {number} */ i) => html`
133
- <span class="breadcrumb-item clickable" @click=${() => _ctx.navigateToLevel(i)}
306
+ (/** @type {DocumentStackEntry} */ frame, /** @type {number} */ i) => html`
307
+ <span class="breadcrumb-item clickable" @click=${() => ctx.navigateToLevel(i)}
134
308
  >${frame.documentPath?.split("/").pop() || "untitled"}</span
135
309
  >
136
310
  <span class="breadcrumb-sep"> › </span>
@@ -143,13 +317,13 @@ function toolbarTemplate() {
143
317
  `
144
318
  : nothing;
145
319
 
146
- const { featureQueries } = _ctx.parseMediaEntries(getEffectiveMedia(S.document.$media));
320
+ const { featureQueries } = ctx.parseMediaEntries(getEffectiveMedia(S.document.$media));
147
321
  const togglesTpl =
148
322
  featureQueries.length > 0
149
323
  ? html`
150
324
  <sp-action-group compact size="s">
151
325
  ${featureQueries.map(
152
- (/** @type {any} */ { name, query }) => html`
326
+ (/** @type {{ name: string; query: string }} */ { name, query }) => html`
153
327
  <sp-action-button
154
328
  toggles
155
329
  size="s"
@@ -171,17 +345,6 @@ function toolbarTemplate() {
171
345
  `
172
346
  : nothing;
173
347
 
174
- const modes = [
175
- { key: "edit", label: "Edit", iconTag: "sp-icon-edit" },
176
- { key: "design", label: "Design", iconTag: "sp-icon-artboard" },
177
- { key: "preview", label: "Preview", iconTag: "sp-icon-preview" },
178
- { key: "source", label: "Code", iconTag: "sp-icon-code" },
179
- { key: "stylebook", label: "Stylebook", iconTag: "sp-icon-brush" },
180
- ];
181
-
182
- const isProjectFile = S.documentPath === "project.json";
183
- const allowedModes = isProjectFile ? new Set(["stylebook", "source"]) : null;
184
-
185
348
  const modeSwitcherTpl = html`
186
349
  <sp-action-group selects="single" size="s" compact>
187
350
  ${modes.map(
@@ -189,25 +352,25 @@ function toolbarTemplate() {
189
352
  <sp-action-button
190
353
  size="s"
191
354
  ?selected=${canvasMode === m.key}
192
- ?disabled=${allowedModes && !allowedModes.has(m.key)}
355
+ ?disabled=${!allowedModes.has(m.key)}
193
356
  @click=${() => {
194
357
  if (canvasMode === m.key) return;
195
- if (allowedModes && !allowedModes.has(m.key)) return;
358
+ if (!allowedModes.has(m.key)) return;
196
359
  if (S.ui.editingFunction) {
197
360
  if (view.functionEditor) {
198
361
  view.functionEditor.dispose();
199
362
  view.functionEditor = null;
200
363
  }
201
364
  }
202
- _ctx.setCanvasMode(m.key);
365
+ ctx.setCanvasMode(m.key);
203
366
  view.panX = 0;
204
367
  view.panY = 0;
205
- /** @type {Record<string, any>} */
368
+ /** @type {{ editingFunction: null; rightTab?: string }} */
206
369
  const uiPatch = { editingFunction: null };
207
370
  if (m.key === "stylebook") uiPatch.rightTab = "style";
208
371
  updateSession({ ui: uiPatch });
209
- _ctx.renderCanvas();
210
- _ctx.safeRenderRightPanel();
372
+ ctx.renderCanvas();
373
+ ctx.safeRenderRightPanel();
211
374
  }}
212
375
  >
213
376
  ${toolbarIconMap[m.iconTag]}${m.label}
@@ -217,84 +380,145 @@ function toolbarTemplate() {
217
380
  </sp-action-group>
218
381
  `;
219
382
 
220
- const windowControls = /** @type {any} */ (globalThis).__jxPlatform?.windowControls;
383
+ const windowControls = /**
384
+ * @type {{
385
+ * __jxPlatform?: {
386
+ * windowControls?: { minimize: () => void; maximize: () => void; close: () => void };
387
+ * };
388
+ * }}
389
+ */ (/** @type {unknown} */ (globalThis)).__jxPlatform?.windowControls;
390
+ const isMac = navigator.platform.startsWith("Mac");
221
391
  const csdTpl = windowControls
222
- ? html`
223
- <sp-action-group class="window-controls" size="s">
224
- <sp-action-button
225
- quiet
226
- size="s"
227
- title="Minimize"
228
- @click=${() => windowControls.minimize()}
229
- >
230
- <sp-icon-remove slot="icon"></sp-icon-remove>
231
- </sp-action-button>
232
- <sp-action-button
233
- quiet
234
- size="s"
235
- title="Maximize"
236
- @click=${() => windowControls.maximize()}
237
- >
238
- <sp-icon-rectangle slot="icon"></sp-icon-rectangle>
239
- </sp-action-button>
240
- <sp-action-button
241
- quiet
242
- size="s"
243
- title="Close"
244
- class="csd-close"
245
- @click=${() => windowControls.close()}
246
- >
247
- <sp-icon-close slot="icon"></sp-icon-close>
248
- </sp-action-button>
249
- </sp-action-group>
250
- `
392
+ ? isMac
393
+ ? html`
394
+ <sp-action-group class="window-controls mac" size="s">
395
+ <sp-action-button
396
+ quiet
397
+ size="s"
398
+ title="Close"
399
+ class="csd-close"
400
+ @click=${() => windowControls.close()}
401
+ >
402
+ <sp-icon-close slot="icon"></sp-icon-close>
403
+ </sp-action-button>
404
+ <sp-action-button
405
+ quiet
406
+ size="s"
407
+ title="Minimize"
408
+ class="csd-minimize"
409
+ @click=${() => windowControls.minimize()}
410
+ >
411
+ <sp-icon-remove slot="icon"></sp-icon-remove>
412
+ </sp-action-button>
413
+ <sp-action-button
414
+ quiet
415
+ size="s"
416
+ title="Maximize"
417
+ class="csd-maximize"
418
+ @click=${() => windowControls.maximize()}
419
+ >
420
+ <sp-icon-rectangle slot="icon"></sp-icon-rectangle>
421
+ </sp-action-button>
422
+ </sp-action-group>
423
+ `
424
+ : html`
425
+ <sp-action-group class="window-controls" size="s">
426
+ <sp-action-button
427
+ quiet
428
+ size="s"
429
+ title="Minimize"
430
+ class="csd-minimize"
431
+ @click=${() => windowControls.minimize()}
432
+ >
433
+ <sp-icon-remove slot="icon"></sp-icon-remove>
434
+ </sp-action-button>
435
+ <sp-action-button
436
+ quiet
437
+ size="s"
438
+ title="Maximize"
439
+ class="csd-maximize"
440
+ @click=${() => windowControls.maximize()}
441
+ >
442
+ <sp-icon-rectangle slot="icon"></sp-icon-rectangle>
443
+ </sp-action-button>
444
+ <sp-action-button
445
+ quiet
446
+ size="s"
447
+ title="Close"
448
+ class="csd-close"
449
+ @click=${() => windowControls.close()}
450
+ >
451
+ <sp-icon-close slot="icon"></sp-icon-close>
452
+ </sp-action-button>
453
+ </sp-action-group>
454
+ `
251
455
  : nothing;
252
456
 
253
457
  const recentProjects = getRecentProjects();
254
- const recentProjectsTpl = recentProjects.length
255
- ? html`
256
- <overlay-trigger placement="bottom-start" triggered-by="click">
257
- <sp-action-button size="s" slot="trigger" title="Recent projects">
258
- <sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>
259
- </sp-action-button>
260
- <sp-popover slot="click-content" tip>
261
- <sp-menu @change=${(/** @type {any} */ e) => _ctx.openRecentProject(e.target.value)}>
262
- ${recentProjects.map(
263
- (p) => html`<sp-menu-item value=${p.root}>${p.name}</sp-menu-item>`,
264
- )}
265
- </sp-menu>
266
- </sp-popover>
267
- </overlay-trigger>
268
- `
269
- : nothing;
458
+ const recentProjectsTpl = html`
459
+ <overlay-trigger placement="bottom-start" triggered-by="click">
460
+ <sp-action-button size="s" slot="trigger" title="Recent projects" class="tb-split-trigger">
461
+ <sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>
462
+ </sp-action-button>
463
+ <sp-popover slot="click-content" tip>
464
+ <sp-menu
465
+ @change=${(/** @type {Event} */ e) => {
466
+ const val = /** @type {HTMLInputElement} */ (/** @type {unknown} */ (e.target)).value;
467
+ if (val === "__new__") {
468
+ handleNewProject();
469
+ } else {
470
+ ctx.openRecentProject(val);
471
+ }
472
+ }}
473
+ >
474
+ <sp-menu-item value="__new__">New Project…</sp-menu-item>
475
+ ${recentProjects.length
476
+ ? html`<sp-menu-divider></sp-menu-divider> ${recentProjects.map(
477
+ (p) => html`<sp-menu-item value=${p.root}>${p.name}</sp-menu-item>`,
478
+ )}`
479
+ : nothing}
480
+ </sp-menu>
481
+ </sp-popover>
482
+ </overlay-trigger>
483
+ `;
270
484
 
271
485
  return html`
272
- ${tbBtnTpl("Open Project", _ctx.openProject, "sp-icon-folder-open")} ${recentProjectsTpl}
486
+ ${isMac ? csdTpl : nothing}
487
+ <div class="tb-split-btn">
488
+ <sp-action-button size="s" class="tb-split-main" @click=${ctx.openProject}>
489
+ ${toolbarIconMap["sp-icon-folder-open"]} Open Project
490
+ </sp-action-button>
491
+ ${recentProjectsTpl}
492
+ </div>
273
493
  ${tbBtnTpl("Manage", openBrowseModal, "sp-icon-view-list")}
274
- ${tbBtnTpl("Save", _ctx.saveFile, "sp-icon-save-floppy")}
494
+ <sp-action-button size="s" ?disabled=${!canSave} @click=${ctx.saveFile}>
495
+ ${toolbarIconMap["sp-icon-save-floppy"]} Save
496
+ </sp-action-button>
275
497
  <sp-action-group compact size="s">
276
- ${tbBtnTpl("Undo", () => tabUndo(activeTab.value), "sp-icon-undo")}
277
- ${tbBtnTpl("Redo", () => tabRedo(activeTab.value), "sp-icon-redo")}
498
+ <sp-action-button size="s" ?disabled=${!canUndo} @click=${() => tabUndo(activeTab.value)}>
499
+ ${toolbarIconMap["sp-icon-undo"]} Undo
500
+ </sp-action-button>
501
+ <sp-action-button size="s" ?disabled=${!canRedo} @click=${() => tabRedo(activeTab.value)}>
502
+ ${toolbarIconMap["sp-icon-redo"]} Redo
503
+ </sp-action-button>
278
504
  </sp-action-group>
279
505
  <div class="tb-spacer"></div>
280
506
  <sp-action-button class="tb-search-trigger" size="s" quiet @click=${openQuickSearch}>
281
507
  <sp-icon-search slot="icon"></sp-icon-search>
282
508
  <span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
283
509
  </sp-action-button>
284
- ${
285
- /** @type {any} */ (activeTab.value?.session.ui.gitStatus)?.behind > 0
286
- ? html`<sp-action-button
287
- size="s"
288
- @click=${async () => {
289
- await getPlatform().gitPull();
290
- await refreshGitStatus();
291
- }}
292
- >
293
- <sp-icon-download slot="icon"></sp-icon-download>
294
- Sync Project
295
- </sp-action-button>`
296
- : nothing
297
- }
510
+ ${(activeTab.value?.session.ui.gitStatus?.behind ?? 0) > 0
511
+ ? html`<sp-action-button
512
+ size="s"
513
+ @click=${async () => {
514
+ await getPlatform().gitPull();
515
+ await refreshGitStatus();
516
+ }}
517
+ >
518
+ <sp-icon-download slot="icon"></sp-icon-download>
519
+ Sync Project
520
+ </sp-action-button>`
521
+ : nothing}
298
522
  <div class="tb-spacer"></div>
299
523
  ${breadcrumbTpl} ${togglesTpl} ${modeSwitcherTpl}
300
524
  <sp-action-button
@@ -311,6 +535,6 @@ function toolbarTemplate() {
311
535
  ? html`<sp-icon-rail-right-open slot="icon"></sp-icon-rail-right-open>`
312
536
  : html`<sp-icon-rail-right-close slot="icon"></sp-icon-rail-right-close>`}
313
537
  </sp-action-button>
314
- ${csdTpl}
538
+ ${isMac ? nothing : csdTpl}
315
539
  `;
316
540
  }