@jxsuite/studio 0.31.1 → 0.32.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 +341 -146
- package/dist/studio.js.map +10 -10
- package/package.json +4 -4
- package/src/files/files.ts +6 -1
- package/src/panels/quick-search.ts +116 -31
- package/src/panels/toolbar.ts +101 -58
- package/src/panels/welcome-screen.ts +34 -10
- package/src/recent-projects.ts +119 -21
- package/src/studio.ts +15 -2
- package/src/types.ts +16 -0
- package/src/ui/media-picker.ts +5 -1
- package/src/utils/studio-utils.ts +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@atlaskit/pragmatic-drag-and-drop": "^2.0.1",
|
|
36
36
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.0.0",
|
|
37
|
-
"@jxsuite/parser": "^0.
|
|
38
|
-
"@jxsuite/runtime": "^0.
|
|
39
|
-
"@jxsuite/schema": "^0.
|
|
37
|
+
"@jxsuite/parser": "^0.32.0",
|
|
38
|
+
"@jxsuite/runtime": "^0.32.0",
|
|
39
|
+
"@jxsuite/schema": "^0.32.0",
|
|
40
40
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
41
41
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
42
42
|
"@spectrum-web-components/action-button": "^1.12.1",
|
package/src/files/files.ts
CHANGED
|
@@ -95,6 +95,7 @@ export async function loadProject() {
|
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
if (info.isSiteProject) {
|
|
98
|
+
addRecentProject(requireProjectState().name, meta.root);
|
|
98
99
|
await ensureDependenciesInstalled();
|
|
99
100
|
await loadDirectory(".");
|
|
100
101
|
await loadComponentRegistry();
|
|
@@ -1053,7 +1054,11 @@ export async function openFileInTab(path: string) {
|
|
|
1053
1054
|
sourceFormat: format?.name ?? null,
|
|
1054
1055
|
});
|
|
1055
1056
|
requireProjectState().selectedPath = path;
|
|
1056
|
-
trackRecentFile({
|
|
1057
|
+
trackRecentFile({
|
|
1058
|
+
name: path.split("/").pop() || path,
|
|
1059
|
+
path,
|
|
1060
|
+
root: requireProjectState().projectRoot,
|
|
1061
|
+
});
|
|
1057
1062
|
|
|
1058
1063
|
statusMessage(`Opened ${path.split("/").pop()}`);
|
|
1059
1064
|
} catch (error) {
|
|
@@ -4,15 +4,37 @@ import { classMap } from "lit-html/directives/class-map.js";
|
|
|
4
4
|
import { live } from "lit-html/directives/live.js";
|
|
5
5
|
import { ref } from "lit-html/directives/ref.js";
|
|
6
6
|
import { getPlatform } from "../platform";
|
|
7
|
+
import { projectState } from "../store";
|
|
7
8
|
import { documentExtensions, formatByExtension, loadFormats } from "../format/format-host";
|
|
8
9
|
import { openFileInTab } from "../files/files";
|
|
9
|
-
import { getRecentFiles, trackRecentFile } from "../recent-projects";
|
|
10
|
+
import { getRecentFiles, getRecentProjects, trackRecentFile } from "../recent-projects";
|
|
10
11
|
import { getLayerSlot } from "../ui/layers";
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* A row in the Quick Access modal. With a project open the modal lists/searches that project's
|
|
15
|
+
* files; with no project open it lists recent projects to re-open. The two never mix — the modal
|
|
16
|
+
* only ever shows files from the current project.
|
|
17
|
+
*/
|
|
18
|
+
interface FileItem {
|
|
19
|
+
kind: "file";
|
|
20
|
+
path: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
interface ProjectItem {
|
|
24
|
+
kind: "project";
|
|
25
|
+
root: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
type QuickItem = FileItem | ProjectItem;
|
|
29
|
+
|
|
30
|
+
interface QuickCtx {
|
|
31
|
+
openRecentProject: (root: string) => void | Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let _ctx: QuickCtx | null = null;
|
|
12
35
|
let _open = false;
|
|
13
36
|
let _query = "";
|
|
14
|
-
|
|
15
|
-
let _results: { path: string; name?: string }[] = [];
|
|
37
|
+
let _results: FileItem[] = [];
|
|
16
38
|
let _selectedIndex = 0;
|
|
17
39
|
let _debounceTimer = 0;
|
|
18
40
|
|
|
@@ -21,8 +43,9 @@ function getContainer() {
|
|
|
21
43
|
return getLayerSlot("popover", "quick-search");
|
|
22
44
|
}
|
|
23
45
|
|
|
24
|
-
|
|
25
|
-
|
|
46
|
+
/** @param {QuickCtx} [ctx] */
|
|
47
|
+
export function initQuickSearch(ctx?: QuickCtx) {
|
|
48
|
+
_ctx = ctx ?? null;
|
|
26
49
|
}
|
|
27
50
|
|
|
28
51
|
export function openQuickSearch() {
|
|
@@ -38,6 +61,39 @@ export function closeQuickSearch() {
|
|
|
38
61
|
renderOverlay();
|
|
39
62
|
}
|
|
40
63
|
|
|
64
|
+
/** The project root scoping the modal, or null when no project is open. */
|
|
65
|
+
function scopeRoot(): string | null {
|
|
66
|
+
return projectState ? (projectState.projectRoot ?? null) : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the rows to display for the current query/mode. File search is async (populates
|
|
71
|
+
* `_results`); recent files and recent-project filtering are synchronous.
|
|
72
|
+
*/
|
|
73
|
+
function currentItems(): { items: QuickItem[]; showingRecent: boolean } {
|
|
74
|
+
const q = _query.trim();
|
|
75
|
+
if (!projectState) {
|
|
76
|
+
// No project open → offer recent projects to re-open, filtered by the query.
|
|
77
|
+
const needle = q.toLowerCase();
|
|
78
|
+
const projects = getRecentProjects().filter(
|
|
79
|
+
(p) =>
|
|
80
|
+
!needle || p.name.toLowerCase().includes(needle) || p.root.toLowerCase().includes(needle),
|
|
81
|
+
);
|
|
82
|
+
return {
|
|
83
|
+
items: projects.map((p) => ({ kind: "project", name: p.name, root: p.root })),
|
|
84
|
+
showingRecent: !q,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (!q) {
|
|
88
|
+
const recent = getRecentFiles(scopeRoot() ?? undefined);
|
|
89
|
+
return {
|
|
90
|
+
items: recent.map((f) => ({ kind: "file", name: f.name, path: f.path })),
|
|
91
|
+
showingRecent: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return { items: _results, showingRecent: false };
|
|
95
|
+
}
|
|
96
|
+
|
|
41
97
|
async function doSearch(query: string) {
|
|
42
98
|
if (!query.trim()) {
|
|
43
99
|
_results = [];
|
|
@@ -48,7 +104,12 @@ async function doSearch(query: string) {
|
|
|
48
104
|
try {
|
|
49
105
|
const platform = getPlatform();
|
|
50
106
|
await loadFormats();
|
|
51
|
-
|
|
107
|
+
const hits = await platform.searchFiles(query.trim().toLowerCase(), documentExtensions());
|
|
108
|
+
_results = hits.map((h) => ({
|
|
109
|
+
kind: "file",
|
|
110
|
+
name: h.name ?? h.path.split("/").pop() ?? "",
|
|
111
|
+
path: h.path,
|
|
112
|
+
}));
|
|
52
113
|
_selectedIndex = 0;
|
|
53
114
|
renderOverlay();
|
|
54
115
|
} catch {
|
|
@@ -60,12 +121,16 @@ async function doSearch(query: string) {
|
|
|
60
121
|
function onInput(e: Event) {
|
|
61
122
|
_query = (e.target as HTMLInputElement).value;
|
|
62
123
|
clearTimeout(_debounceTimer);
|
|
63
|
-
|
|
124
|
+
// Only file search needs the backend (and debouncing); recent-project filtering is synchronous.
|
|
125
|
+
if (projectState) {
|
|
126
|
+
_debounceTimer = setTimeout(() => doSearch(_query), 150) as unknown as number;
|
|
127
|
+
}
|
|
128
|
+
_selectedIndex = 0;
|
|
64
129
|
renderOverlay();
|
|
65
130
|
}
|
|
66
131
|
|
|
67
132
|
function onKeydown(e: KeyboardEvent) {
|
|
68
|
-
const items =
|
|
133
|
+
const { items } = currentItems();
|
|
69
134
|
switch (e.key) {
|
|
70
135
|
case "ArrowDown": {
|
|
71
136
|
e.preventDefault();
|
|
@@ -97,11 +162,14 @@ function onKeydown(e: KeyboardEvent) {
|
|
|
97
162
|
}
|
|
98
163
|
}
|
|
99
164
|
|
|
100
|
-
function selectItem(item:
|
|
165
|
+
function selectItem(item: QuickItem) {
|
|
101
166
|
closeQuickSearch();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
167
|
+
if (item.kind === "project") {
|
|
168
|
+
void _ctx?.openRecentProject(item.root);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
trackRecentFile({ name: item.name, path: item.path, root: scopeRoot() ?? "" });
|
|
172
|
+
void openFileInTab(item.path);
|
|
105
173
|
}
|
|
106
174
|
|
|
107
175
|
function fileIcon(name: string) {
|
|
@@ -121,6 +189,14 @@ function dirPart(path: string) {
|
|
|
121
189
|
return parts.length > 0 ? parts.join("/") : "";
|
|
122
190
|
}
|
|
123
191
|
|
|
192
|
+
/** Collapse a home-prefixed absolute path for compact display. */
|
|
193
|
+
function shortenPath(path: string) {
|
|
194
|
+
if (path.startsWith("/home/")) {
|
|
195
|
+
return `~/${path.split("/").slice(3).join("/")}`;
|
|
196
|
+
}
|
|
197
|
+
return path;
|
|
198
|
+
}
|
|
199
|
+
|
|
124
200
|
function renderOverlay() {
|
|
125
201
|
const container = getContainer();
|
|
126
202
|
if (!_open) {
|
|
@@ -128,9 +204,15 @@ function renderOverlay() {
|
|
|
128
204
|
return;
|
|
129
205
|
}
|
|
130
206
|
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const
|
|
207
|
+
const hasProject = projectState != null;
|
|
208
|
+
const { items, showingRecent } = currentItems();
|
|
209
|
+
const hasQuery = _query.trim().length > 0;
|
|
210
|
+
|
|
211
|
+
const placeholder = hasProject ? "Search project files…" : "Open a recent project…";
|
|
212
|
+
const sectionLabel = hasProject ? "Recently opened" : "Recent projects";
|
|
213
|
+
const emptyHint = hasProject
|
|
214
|
+
? "Type to search project files"
|
|
215
|
+
: "No recent projects — open one to get started";
|
|
134
216
|
|
|
135
217
|
const tpl = html`
|
|
136
218
|
<div class="quick-search-overlay" @click=${closeQuickSearch}>
|
|
@@ -138,7 +220,7 @@ function renderOverlay() {
|
|
|
138
220
|
<input
|
|
139
221
|
class="quick-search-input"
|
|
140
222
|
type="text"
|
|
141
|
-
placeholder
|
|
223
|
+
placeholder=${placeholder}
|
|
142
224
|
.value=${live(_query)}
|
|
143
225
|
@input=${onInput}
|
|
144
226
|
@keydown=${onKeydown}
|
|
@@ -149,17 +231,22 @@ function renderOverlay() {
|
|
|
149
231
|
})}
|
|
150
232
|
/>
|
|
151
233
|
<div class="quick-search-results">
|
|
152
|
-
${items.length === 0 &&
|
|
234
|
+
${items.length === 0 && hasQuery
|
|
153
235
|
? html`<div class="quick-search-empty">No results</div>`
|
|
154
236
|
: nothing}
|
|
155
|
-
${items.length === 0 && !
|
|
156
|
-
? html`<div class="quick-search-empty"
|
|
237
|
+
${items.length === 0 && !hasQuery
|
|
238
|
+
? html`<div class="quick-search-empty">${emptyHint}</div>`
|
|
157
239
|
: nothing}
|
|
158
|
-
${
|
|
159
|
-
? html`<div class="quick-search-section-label"
|
|
240
|
+
${showingRecent && items.length > 0
|
|
241
|
+
? html`<div class="quick-search-section-label">${sectionLabel}</div>`
|
|
160
242
|
: nothing}
|
|
161
|
-
${items.map(
|
|
162
|
-
|
|
243
|
+
${items.map((item, i) => {
|
|
244
|
+
const icon =
|
|
245
|
+
item.kind === "project"
|
|
246
|
+
? html`<sp-icon-folder-open size="s"></sp-icon-folder-open>`
|
|
247
|
+
: fileIcon(item.name);
|
|
248
|
+
const pathText = item.kind === "project" ? shortenPath(item.root) : dirPart(item.path);
|
|
249
|
+
return html`
|
|
163
250
|
<div
|
|
164
251
|
class=${classMap({
|
|
165
252
|
"quick-search-item": true,
|
|
@@ -171,15 +258,13 @@ function renderOverlay() {
|
|
|
171
258
|
renderOverlay();
|
|
172
259
|
}}
|
|
173
260
|
>
|
|
174
|
-
<span class="quick-search-icon"
|
|
175
|
-
|
|
176
|
-
>
|
|
177
|
-
|
|
178
|
-
<span class="quick-search-path">${dirPart(item.path)}</span>
|
|
179
|
-
${showRecent ? html`<span class="quick-search-badge">recent</span>` : nothing}
|
|
261
|
+
<span class="quick-search-icon">${icon}</span>
|
|
262
|
+
<span class="quick-search-name">${item.name}</span>
|
|
263
|
+
<span class="quick-search-path">${pathText}</span>
|
|
264
|
+
${showingRecent ? html`<span class="quick-search-badge">recent</span>` : nothing}
|
|
180
265
|
</div>
|
|
181
|
-
|
|
182
|
-
)}
|
|
266
|
+
`;
|
|
267
|
+
})}
|
|
183
268
|
</div>
|
|
184
269
|
</div>
|
|
185
270
|
</div>
|
package/src/panels/toolbar.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { redo as tabRedo, undo as tabUndo } from "../tabs/transact";
|
|
|
10
10
|
import { effect, effectScope } from "../reactivity";
|
|
11
11
|
import { activeTab } from "../workspace/workspace";
|
|
12
12
|
import { applyPanelCollapse, view } from "../view";
|
|
13
|
-
import { getRecentProjects } from "../recent-projects";
|
|
13
|
+
import { clearRecentProjects, getRecentProjects, removeRecentProject } from "../recent-projects";
|
|
14
14
|
import { openQuickSearch } from "./quick-search";
|
|
15
15
|
import { getPlatform } from "../platform";
|
|
16
16
|
import { refreshGitStatus } from "./git-panel";
|
|
@@ -61,8 +61,8 @@ const toolbarIconMap = {
|
|
|
61
61
|
*/
|
|
62
62
|
function tbBtnTpl(label: string, onClick: () => void, iconTag?: string) {
|
|
63
63
|
return html`
|
|
64
|
-
<sp-action-button size="s" @click=${onClick}>
|
|
65
|
-
${iconTag ? toolbarIconMap[iconTag] : nothing}
|
|
64
|
+
<sp-action-button size="s" title=${label} @click=${onClick}>
|
|
65
|
+
${iconTag ? toolbarIconMap[iconTag] : nothing}<span class="tb-label">${label}</span>
|
|
66
66
|
</sp-action-button>
|
|
67
67
|
`;
|
|
68
68
|
}
|
|
@@ -131,10 +131,15 @@ async function handleNewProject() {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
/**
|
|
135
|
-
|
|
134
|
+
/**
|
|
135
|
+
* The chevron dropdown beside "Open Project": New Project, the recent-projects list (each with a
|
|
136
|
+
* remove affordance), and a clear-all action. Shared by both the minimal and full toolbars.
|
|
137
|
+
*
|
|
138
|
+
* @param {ToolbarCtx} ctx
|
|
139
|
+
*/
|
|
140
|
+
function recentMenuTpl(ctx: ToolbarCtx) {
|
|
136
141
|
const recentProjects = getRecentProjects();
|
|
137
|
-
|
|
142
|
+
return html`
|
|
138
143
|
<overlay-trigger placement="bottom-start" triggered-by="click">
|
|
139
144
|
<sp-action-button size="s" slot="trigger" title="Recent projects" class="tb-split-trigger">
|
|
140
145
|
<sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>
|
|
@@ -145,6 +150,9 @@ function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
|
145
150
|
const val = (e.target as unknown as HTMLInputElement).value;
|
|
146
151
|
if (val === "__new__") {
|
|
147
152
|
void handleNewProject();
|
|
153
|
+
} else if (val === "__clear__") {
|
|
154
|
+
clearRecentProjects();
|
|
155
|
+
render();
|
|
148
156
|
} else {
|
|
149
157
|
void ctx.openRecentProject(val);
|
|
150
158
|
}
|
|
@@ -152,14 +160,41 @@ function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
|
152
160
|
>
|
|
153
161
|
<sp-menu-item value="__new__">New Project…</sp-menu-item>
|
|
154
162
|
${recentProjects.length > 0
|
|
155
|
-
? html
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
? html`
|
|
164
|
+
<sp-menu-divider></sp-menu-divider>
|
|
165
|
+
${recentProjects.map(
|
|
166
|
+
(p) => html`
|
|
167
|
+
<sp-menu-item value=${p.root} title=${p.root}>
|
|
168
|
+
${p.name}
|
|
169
|
+
<sp-action-button
|
|
170
|
+
slot="end"
|
|
171
|
+
quiet
|
|
172
|
+
size="s"
|
|
173
|
+
title="Remove from recent"
|
|
174
|
+
@click=${(e: Event) => {
|
|
175
|
+
e.stopPropagation();
|
|
176
|
+
removeRecentProject(p.root);
|
|
177
|
+
render();
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<sp-icon-close slot="icon"></sp-icon-close>
|
|
181
|
+
</sp-action-button>
|
|
182
|
+
</sp-menu-item>
|
|
183
|
+
`,
|
|
184
|
+
)}
|
|
185
|
+
<sp-menu-divider></sp-menu-divider>
|
|
186
|
+
<sp-menu-item value="__clear__">Clear recent projects</sp-menu-item>
|
|
187
|
+
`
|
|
158
188
|
: nothing}
|
|
159
189
|
</sp-menu>
|
|
160
190
|
</sp-popover>
|
|
161
191
|
</overlay-trigger>
|
|
162
192
|
`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** @param {ToolbarCtx} ctx */
|
|
196
|
+
function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
197
|
+
const recentProjectsTpl = recentMenuTpl(ctx);
|
|
163
198
|
|
|
164
199
|
const windowControls = (
|
|
165
200
|
globalThis as unknown as {
|
|
@@ -206,25 +241,36 @@ function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
|
206
241
|
|
|
207
242
|
return html`
|
|
208
243
|
<div class="tb-split-btn">
|
|
209
|
-
<sp-action-button
|
|
210
|
-
|
|
244
|
+
<sp-action-button
|
|
245
|
+
size="s"
|
|
246
|
+
class="tb-split-main"
|
|
247
|
+
title="Open Project"
|
|
248
|
+
@click=${ctx.openProject}
|
|
249
|
+
>
|
|
250
|
+
${toolbarIconMap["sp-icon-folder-open"]}<span class="tb-label">Open Project</span>
|
|
211
251
|
</sp-action-button>
|
|
212
252
|
${recentProjectsTpl}
|
|
213
253
|
</div>
|
|
214
254
|
${tbBtnTpl("Manage", openBrowseModal, "sp-icon-view-list")}
|
|
215
|
-
<sp-action-button size="s" disabled>
|
|
216
|
-
${toolbarIconMap["sp-icon-save-floppy"]} Save
|
|
255
|
+
<sp-action-button size="s" title="Save" disabled>
|
|
256
|
+
${toolbarIconMap["sp-icon-save-floppy"]}<span class="tb-label">Save</span>
|
|
217
257
|
</sp-action-button>
|
|
218
258
|
<sp-action-group compact size="s">
|
|
219
|
-
<sp-action-button size="s" disabled>
|
|
220
|
-
${toolbarIconMap["sp-icon-undo"]} Undo
|
|
259
|
+
<sp-action-button size="s" title="Undo" disabled>
|
|
260
|
+
${toolbarIconMap["sp-icon-undo"]}<span class="tb-label">Undo</span>
|
|
221
261
|
</sp-action-button>
|
|
222
|
-
<sp-action-button size="s" disabled>
|
|
223
|
-
${toolbarIconMap["sp-icon-redo"]} Redo
|
|
262
|
+
<sp-action-button size="s" title="Redo" disabled>
|
|
263
|
+
${toolbarIconMap["sp-icon-redo"]}<span class="tb-label">Redo</span>
|
|
224
264
|
</sp-action-button>
|
|
225
265
|
</sp-action-group>
|
|
226
266
|
<div class="tb-spacer"></div>
|
|
227
|
-
<sp-action-button
|
|
267
|
+
<sp-action-button
|
|
268
|
+
class="tb-search-trigger"
|
|
269
|
+
size="s"
|
|
270
|
+
quiet
|
|
271
|
+
title="Search files (⌘P)"
|
|
272
|
+
@click=${openQuickSearch}
|
|
273
|
+
>
|
|
228
274
|
<sp-icon-search slot="icon"></sp-icon-search>
|
|
229
275
|
<span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
|
|
230
276
|
</sp-action-button>
|
|
@@ -232,8 +278,8 @@ function minimalToolbarTemplate(ctx: ToolbarCtx) {
|
|
|
232
278
|
<sp-action-group selects="single" size="s" compact>
|
|
233
279
|
${modes.map(
|
|
234
280
|
(m) => html`
|
|
235
|
-
<sp-action-button size="s" disabled ?selected=${m.key === "design"}>
|
|
236
|
-
${toolbarIconMap[m.iconTag]}
|
|
281
|
+
<sp-action-button size="s" title=${m.label} disabled ?selected=${m.key === "design"}>
|
|
282
|
+
${toolbarIconMap[m.iconTag]}<span class="tb-label">${m.label}</span>
|
|
237
283
|
</sp-action-button>
|
|
238
284
|
`,
|
|
239
285
|
)}
|
|
@@ -295,6 +341,7 @@ function toolbarTemplate() {
|
|
|
295
341
|
(m) => html`
|
|
296
342
|
<sp-action-button
|
|
297
343
|
size="s"
|
|
344
|
+
title=${m.label}
|
|
298
345
|
?selected=${canvasMode === m.key}
|
|
299
346
|
?disabled=${!allowedModes.has(m.key)}
|
|
300
347
|
@click=${() => {
|
|
@@ -323,7 +370,7 @@ function toolbarTemplate() {
|
|
|
323
370
|
ctx.safeRenderRightPanel();
|
|
324
371
|
}}
|
|
325
372
|
>
|
|
326
|
-
${toolbarIconMap[m.iconTag]}
|
|
373
|
+
${toolbarIconMap[m.iconTag]}<span class="tb-label">${m.label}</span>
|
|
327
374
|
</sp-action-button>
|
|
328
375
|
`,
|
|
329
376
|
)}
|
|
@@ -408,69 +455,65 @@ function toolbarTemplate() {
|
|
|
408
455
|
`
|
|
409
456
|
: nothing;
|
|
410
457
|
|
|
411
|
-
const
|
|
412
|
-
const recentProjectsTpl = html`
|
|
413
|
-
<overlay-trigger placement="bottom-start" triggered-by="click">
|
|
414
|
-
<sp-action-button size="s" slot="trigger" title="Recent projects" class="tb-split-trigger">
|
|
415
|
-
<sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>
|
|
416
|
-
</sp-action-button>
|
|
417
|
-
<sp-popover slot="click-content" tip>
|
|
418
|
-
<sp-menu
|
|
419
|
-
@change=${(e: Event) => {
|
|
420
|
-
const val = (e.target as unknown as HTMLInputElement).value;
|
|
421
|
-
if (val === "__new__") {
|
|
422
|
-
void handleNewProject();
|
|
423
|
-
} else {
|
|
424
|
-
void ctx.openRecentProject(val);
|
|
425
|
-
}
|
|
426
|
-
}}
|
|
427
|
-
>
|
|
428
|
-
<sp-menu-item value="__new__">New Project…</sp-menu-item>
|
|
429
|
-
${recentProjects.length > 0
|
|
430
|
-
? html`<sp-menu-divider></sp-menu-divider> ${recentProjects.map(
|
|
431
|
-
(p) => html`<sp-menu-item value=${p.root}>${p.name}</sp-menu-item>`,
|
|
432
|
-
)}`
|
|
433
|
-
: nothing}
|
|
434
|
-
</sp-menu>
|
|
435
|
-
</sp-popover>
|
|
436
|
-
</overlay-trigger>
|
|
437
|
-
`;
|
|
458
|
+
const recentProjectsTpl = recentMenuTpl(ctx);
|
|
438
459
|
|
|
439
460
|
return html`
|
|
440
461
|
${isMac ? csdTpl : nothing}
|
|
441
462
|
<div class="tb-split-btn">
|
|
442
|
-
<sp-action-button
|
|
443
|
-
|
|
463
|
+
<sp-action-button
|
|
464
|
+
size="s"
|
|
465
|
+
class="tb-split-main"
|
|
466
|
+
title="Open Project"
|
|
467
|
+
@click=${ctx.openProject}
|
|
468
|
+
>
|
|
469
|
+
${toolbarIconMap["sp-icon-folder-open"]}<span class="tb-label">Open Project</span>
|
|
444
470
|
</sp-action-button>
|
|
445
471
|
${recentProjectsTpl}
|
|
446
472
|
</div>
|
|
447
473
|
${tbBtnTpl("Manage", openBrowseModal, "sp-icon-view-list")}
|
|
448
|
-
<sp-action-button size="s" ?disabled=${!canSave} @click=${ctx.saveFile}>
|
|
449
|
-
${toolbarIconMap["sp-icon-save-floppy"]} Save
|
|
474
|
+
<sp-action-button size="s" title="Save" ?disabled=${!canSave} @click=${ctx.saveFile}>
|
|
475
|
+
${toolbarIconMap["sp-icon-save-floppy"]}<span class="tb-label">Save</span>
|
|
450
476
|
</sp-action-button>
|
|
451
477
|
<sp-action-group compact size="s">
|
|
452
|
-
<sp-action-button
|
|
453
|
-
|
|
478
|
+
<sp-action-button
|
|
479
|
+
size="s"
|
|
480
|
+
title="Undo"
|
|
481
|
+
?disabled=${!canUndo}
|
|
482
|
+
@click=${() => tabUndo(activeTab.value!)}
|
|
483
|
+
>
|
|
484
|
+
${toolbarIconMap["sp-icon-undo"]}<span class="tb-label">Undo</span>
|
|
454
485
|
</sp-action-button>
|
|
455
|
-
<sp-action-button
|
|
456
|
-
|
|
486
|
+
<sp-action-button
|
|
487
|
+
size="s"
|
|
488
|
+
title="Redo"
|
|
489
|
+
?disabled=${!canRedo}
|
|
490
|
+
@click=${() => tabRedo(activeTab.value!)}
|
|
491
|
+
>
|
|
492
|
+
${toolbarIconMap["sp-icon-redo"]}<span class="tb-label">Redo</span>
|
|
457
493
|
</sp-action-button>
|
|
458
494
|
</sp-action-group>
|
|
459
495
|
<div class="tb-spacer"></div>
|
|
460
|
-
<sp-action-button
|
|
496
|
+
<sp-action-button
|
|
497
|
+
class="tb-search-trigger"
|
|
498
|
+
size="s"
|
|
499
|
+
quiet
|
|
500
|
+
title="Search files (⌘P)"
|
|
501
|
+
@click=${openQuickSearch}
|
|
502
|
+
>
|
|
461
503
|
<sp-icon-search slot="icon"></sp-icon-search>
|
|
462
504
|
<span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
|
|
463
505
|
</sp-action-button>
|
|
464
506
|
${(activeTab.value?.session.ui.gitStatus?.behind ?? 0) > 0
|
|
465
507
|
? html`<sp-action-button
|
|
466
508
|
size="s"
|
|
509
|
+
title="Sync Project"
|
|
467
510
|
@click=${async () => {
|
|
468
511
|
await getPlatform().gitPull();
|
|
469
512
|
await refreshGitStatus();
|
|
470
513
|
}}
|
|
471
514
|
>
|
|
472
515
|
<sp-icon-download slot="icon"></sp-icon-download>
|
|
473
|
-
Sync Project
|
|
516
|
+
<span class="tb-label">Sync Project</span>
|
|
474
517
|
</sp-action-button>`
|
|
475
518
|
: nothing}
|
|
476
519
|
<div class="tb-spacer"></div>
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { html, render as litRender, nothing } from "lit-html";
|
|
8
|
-
import { getRecentProjects } from "../recent-projects";
|
|
8
|
+
import { clearRecentProjects, getRecentProjects, removeRecentProject } from "../recent-projects";
|
|
9
|
+
import { renderOnly } from "../store";
|
|
9
10
|
import { platformSupportsClone } from "./git-panel";
|
|
10
11
|
|
|
11
12
|
interface WelcomeCtx {
|
|
@@ -86,17 +87,40 @@ export function renderWelcome(host: HTMLElement) {
|
|
|
86
87
|
${recent.length > 0
|
|
87
88
|
? html`
|
|
88
89
|
<div class="welcome-section">
|
|
89
|
-
<
|
|
90
|
+
<div class="welcome-section-header">
|
|
91
|
+
<h2 class="welcome-section-title">Recent</h2>
|
|
92
|
+
<button
|
|
93
|
+
class="welcome-clear"
|
|
94
|
+
@click=${() => {
|
|
95
|
+
clearRecentProjects();
|
|
96
|
+
renderOnly("canvas");
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
Clear
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
90
102
|
${recent.map(
|
|
91
103
|
(p) => html`
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
<div class="welcome-recent-row">
|
|
105
|
+
<button
|
|
106
|
+
class="welcome-recent"
|
|
107
|
+
@click=${() => ctx.openRecentProject(p.root)}
|
|
108
|
+
title=${p.root}
|
|
109
|
+
>
|
|
110
|
+
<span class="welcome-recent-name">${p.name}</span>
|
|
111
|
+
<span class="welcome-recent-path">${shortenPath(p.root)}</span>
|
|
112
|
+
</button>
|
|
113
|
+
<button
|
|
114
|
+
class="welcome-recent-remove"
|
|
115
|
+
title="Remove from recent"
|
|
116
|
+
@click=${() => {
|
|
117
|
+
removeRecentProject(p.root);
|
|
118
|
+
renderOnly("canvas");
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
✕
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
100
124
|
`,
|
|
101
125
|
)}
|
|
102
126
|
</div>
|