@hypit/hypit 0.1.5 → 0.1.7
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/README.md +1 -1
- package/examples/semantic-composition/README.md +2 -1
- package/examples/semantic-composition/chat.svml +1 -1
- package/examples/semantic-composition/hypit.runtime.json +1 -1
- package/package.json +4 -1
- package/packages/caption-fine/README.md +7 -0
- package/packages/cli/src/view.ts +4 -3
- package/packages/film/src/surface.ts +4 -1
- package/packages/provider-hypihub/README.md +17 -3
- package/packages/provider-hypihub/src/oauth.ts +1 -1
- package/packages/provider-hypihub/src/provider.ts +35 -9
- package/packages/provider-hypihub/src/upload.ts +37 -4
- package/packages/runtime-local/README.md +17 -0
- package/packages/script/README.md +15 -3
- package/packages/script/src/manifest.ts +2 -1
- package/packages/script/src/parser.ts +45 -6
- package/packages/studio/LOCALIZATION.md +98 -0
- package/packages/studio/README.md +13 -0
- package/packages/studio/locales/en.json +197 -0
- package/packages/studio/locales/zh-CN.json +191 -0
- package/packages/studio/src/localization-node.ts +46 -0
- package/packages/studio/src/localization.ts +85 -0
- package/packages/studio/src/style.css +15 -3
- package/packages/studio/src/ui/artifact-name.ts +8 -6
- package/packages/studio/src/ui/artifact-preview.ts +5 -4
- package/packages/studio/src/ui/code.ts +17 -16
- package/packages/studio/src/ui/comments.ts +26 -25
- package/packages/studio/src/ui/dropdown.ts +68 -0
- package/packages/studio/src/ui/i18n.ts +109 -0
- package/packages/studio/src/ui/icons.ts +1 -0
- package/packages/studio/src/ui/library.ts +76 -65
- package/packages/studio/src/ui/main.ts +84 -131
- package/packages/studio/src/ui/scrub-preview.ts +8 -6
- package/packages/studio/src/ui/stage.ts +28 -27
- package/packages/studio/src/ui/timeline.ts +17 -14
- package/packages/studio/start.ts +39 -22
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t, uiDate, uiLabel, uiAttribute, uiText, uiAttr, type Message } from "./i18n.js";
|
|
1
2
|
import type {
|
|
2
3
|
StudioArtifactView,
|
|
3
4
|
StudioLibraryView,
|
|
@@ -38,19 +39,19 @@ function formatBytes(value: number): string {
|
|
|
38
39
|
return `${(value / 1_000_000_000).toFixed(1)} GB`;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
42
|
+
function localizedSidebarItem(input: { label: Message; icon: string; selected: boolean; select(): void }): HTMLButtonElement {
|
|
43
|
+
const button = sidebarItem(input);
|
|
44
|
+
uiAttr(button, "title", input.label);
|
|
45
|
+
uiAttr(button, "aria-label", input.label);
|
|
46
|
+
return button;
|
|
47
|
+
}
|
|
47
48
|
|
|
48
|
-
function emptyState(iconName: string, title:
|
|
49
|
+
function emptyState(iconName: string, title: Message, detail: Message): HTMLElement {
|
|
49
50
|
const node = document.createElement("div");
|
|
50
51
|
node.className = "library-empty";
|
|
51
52
|
node.innerHTML = `<span class="library-empty-icon">${icon(iconName)}</span><strong></strong><p></p>`;
|
|
52
|
-
node.querySelector("strong")
|
|
53
|
-
node.querySelector("p")
|
|
53
|
+
uiText(node.querySelector("strong")!, title);
|
|
54
|
+
uiText(node.querySelector("p")!, detail);
|
|
54
55
|
return node;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -77,9 +78,9 @@ function taskCard(task: StudioTaskView, selected: boolean, select: () => void, s
|
|
|
77
78
|
node.dataset.taskId = task.id;
|
|
78
79
|
const source = task.run ?? task.source;
|
|
79
80
|
const title = task.title ?? stem(source);
|
|
80
|
-
const statuses: Record<StudioTaskView["status"],
|
|
81
|
-
queued: "
|
|
82
|
-
failed: "
|
|
81
|
+
const statuses: Record<StudioTaskView["status"], Message> = {
|
|
82
|
+
queued: "common.queued", running: "common.running", waiting: "common.waiting", complete: "common.completed",
|
|
83
|
+
failed: "common.failed", cancelled: "common.cancelled", "saving-result": "common.saving", attention: "common.needs-attention",
|
|
83
84
|
};
|
|
84
85
|
node.innerHTML = `
|
|
85
86
|
<div class="task-state" aria-hidden="true">${icon(task.ongoing ? "when" : task.status === "complete" ? "check" : task.status === "failed" ? "alert" : "tasks")}</div>
|
|
@@ -89,18 +90,24 @@ function taskCard(task: StudioTaskView, selected: boolean, select: () => void, s
|
|
|
89
90
|
<div class="task-progress" data-progress></div>
|
|
90
91
|
<div class="task-detail" data-detail></div>
|
|
91
92
|
</div>
|
|
92
|
-
<button type="button" class="task-open" aria-label
|
|
93
|
+
<button type="button" class="task-open" ${uiAttribute("aria-label", "library.view-task-media")} ${uiAttribute("title", "library.view-media")}>${icon("arrowRight")}</button>`;
|
|
93
94
|
const name = node.querySelector<HTMLButtonElement>(".task-name")!;
|
|
94
95
|
name.textContent = title;
|
|
95
96
|
name.addEventListener("click", (event) => { event.stopPropagation(); select(); });
|
|
96
|
-
node.querySelector<HTMLElement>(".task-status")
|
|
97
|
+
uiText(node.querySelector<HTMLElement>(".task-status")!, statuses[task.status]);
|
|
97
98
|
const time = node.querySelector("time")!;
|
|
98
99
|
time.dateTime = new Date(task.createdAt).toISOString();
|
|
99
|
-
time
|
|
100
|
+
uiDate(time, task.createdAt);
|
|
100
101
|
const progress = task.ongoing ? taskProgress(task) : undefined;
|
|
101
102
|
const progressNode = node.querySelector<HTMLElement>("[data-progress]")!;
|
|
102
103
|
if (progress === undefined) progressNode.remove();
|
|
103
|
-
else {
|
|
104
|
+
else {
|
|
105
|
+
progressNode.textContent = progress; progressNode.title = progress;
|
|
106
|
+
if (!task.operations.some(operation => operation.status === "pending" && operation.phase !== undefined) && task.requests !== undefined) {
|
|
107
|
+
uiText(progressNode, "tasks.progress", task.requests);
|
|
108
|
+
uiAttr(progressNode, "title", "tasks.progress", task.requests);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
104
111
|
const detailNode = node.querySelector<HTMLElement>("[data-detail]")!;
|
|
105
112
|
if (task.detail === undefined) detailNode.remove();
|
|
106
113
|
else {
|
|
@@ -109,10 +116,10 @@ function taskCard(task: StudioTaskView, selected: boolean, select: () => void, s
|
|
|
109
116
|
message.textContent = task.detail;
|
|
110
117
|
detailNode.append(message);
|
|
111
118
|
const copy = document.createElement("button");
|
|
112
|
-
copy.type = "button"; copy.className = "task-copy-error"; copy
|
|
119
|
+
copy.type = "button"; copy.className = "task-copy-error"; uiText(copy, "tasks.copy-error");
|
|
113
120
|
copy.addEventListener("click", (event) => {
|
|
114
121
|
event.stopPropagation();
|
|
115
|
-
void navigator.clipboard.writeText(task.detail!).then(() => { copy
|
|
122
|
+
void navigator.clipboard.writeText(task.detail!).then(() => { uiText(copy, "common.copied"); }, () => { uiText(copy, "tasks.copy-manually"); });
|
|
116
123
|
});
|
|
117
124
|
detailNode.append(copy);
|
|
118
125
|
detailNode.addEventListener("click", (event) => { event.stopPropagation(); if (!node.classList.contains("selected")) select(); });
|
|
@@ -122,11 +129,11 @@ function taskCard(task: StudioTaskView, selected: boolean, select: () => void, s
|
|
|
122
129
|
return node;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
function artifactKind(mediaType: string): { readonly label:
|
|
126
|
-
if (mediaType.startsWith("video/")) return { label: "
|
|
127
|
-
if (mediaType.startsWith("image/")) return { label: "
|
|
128
|
-
if (mediaType.startsWith("audio/")) return { label: "
|
|
129
|
-
return { label: "
|
|
132
|
+
function artifactKind(mediaType: string): { readonly label: Message; readonly icon: string } {
|
|
133
|
+
if (mediaType.startsWith("video/")) return { label: "library.video", icon: "video" };
|
|
134
|
+
if (mediaType.startsWith("image/")) return { label: "library.image", icon: "image" };
|
|
135
|
+
if (mediaType.startsWith("audio/")) return { label: "library.audio", icon: "waveform" };
|
|
136
|
+
return { label: "library.data", icon: "code" };
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
function sizeThumbnail(media: HTMLElement, width: number, height: number): void {
|
|
@@ -140,7 +147,7 @@ function artifactCard(artifact: StudioArtifactView, open: (artifact: StudioArtif
|
|
|
140
147
|
const kind = artifactKind(artifact.mediaType);
|
|
141
148
|
const link = document.createElement("div");
|
|
142
149
|
link.tabIndex = 0;
|
|
143
|
-
link.className = `artifact-card artifact-${
|
|
150
|
+
link.className = `artifact-card artifact-${artifact.mediaType.split("/")[0]}${artifact.highlighted ? " artifact-highlighted" : ""}`;
|
|
144
151
|
const query = new URLSearchParams({
|
|
145
152
|
build: artifact.build,
|
|
146
153
|
output: artifact.output,
|
|
@@ -158,10 +165,10 @@ function artifactCard(artifact: StudioArtifactView, open: (artifact: StudioArtif
|
|
|
158
165
|
<span class="artifact-glyph">${icon(kind.icon)}</span>
|
|
159
166
|
<span class="artifact-type"></span>
|
|
160
167
|
</div>`;
|
|
161
|
-
link.querySelector<HTMLElement>(".artifact-type")
|
|
168
|
+
uiText(link.querySelector<HTMLElement>(".artifact-type")!, kind.label);
|
|
162
169
|
const preview = link.querySelector<HTMLElement>(".artifact-preview")!;
|
|
163
170
|
preview.setAttribute("role", "button");
|
|
164
|
-
preview
|
|
171
|
+
uiAttr(preview, "aria-label", "library.preview-name", { name: title });
|
|
165
172
|
preview.addEventListener("click", () => open(artifact));
|
|
166
173
|
const name = createArtifactName({
|
|
167
174
|
name: title,
|
|
@@ -173,11 +180,11 @@ function artifactCard(artifact: StudioArtifactView, open: (artifact: StudioArtif
|
|
|
173
180
|
body: JSON.stringify({ build: artifact.build, output: artifact.output, displayName }),
|
|
174
181
|
});
|
|
175
182
|
const result = await response.json() as { displayName?: string; error?: string };
|
|
176
|
-
if (!response.ok || typeof result.displayName !== "string") throw new Error(result.error ?? "
|
|
183
|
+
if (!response.ok || typeof result.displayName !== "string") throw new Error(result.error ?? t("library.name-failed"));
|
|
177
184
|
artifact = { ...artifact, displayName: result.displayName };
|
|
178
185
|
link.dataset.displayName = result.displayName;
|
|
179
186
|
renamed(artifact);
|
|
180
|
-
preview
|
|
187
|
+
uiAttr(preview, "aria-label", "library.preview-name", { name: result.displayName });
|
|
181
188
|
preview.title = [result.displayName, ...preview.title.split("\n").slice(1)].join("\n");
|
|
182
189
|
return result.displayName;
|
|
183
190
|
},
|
|
@@ -191,7 +198,7 @@ function artifactCard(artifact: StudioArtifactView, open: (artifact: StudioArtif
|
|
|
191
198
|
if (artifact.mediaType.startsWith("image/")) {
|
|
192
199
|
const image = document.createElement("img");
|
|
193
200
|
image.addEventListener("load", () => sizeThumbnail(image, image.naturalWidth, image.naturalHeight));
|
|
194
|
-
image.addEventListener("error", () => { link.classList.add("preview-unavailable"); link.querySelector<HTMLElement>(".artifact-type")
|
|
201
|
+
image.addEventListener("error", () => { link.classList.add("preview-unavailable"); uiText(link.querySelector<HTMLElement>(".artifact-type")!, "library.preview-unavailable"); });
|
|
195
202
|
image.src = href;
|
|
196
203
|
image.alt = "";
|
|
197
204
|
image.loading = "lazy";
|
|
@@ -204,11 +211,11 @@ function artifactCard(artifact: StudioArtifactView, open: (artifact: StudioArtif
|
|
|
204
211
|
video.dataset.previewSource = href;
|
|
205
212
|
video.setAttribute("aria-hidden", "true");
|
|
206
213
|
const label = link.querySelector<HTMLElement>(".artifact-type")!;
|
|
207
|
-
label
|
|
214
|
+
uiText(label, "library.video-loading-preview");
|
|
208
215
|
const ready = () => {
|
|
209
216
|
if (video.readyState < 2 || video.seeking) return;
|
|
210
217
|
video.classList.add("is-ready");
|
|
211
|
-
label
|
|
218
|
+
uiText(label, "library.video");
|
|
212
219
|
};
|
|
213
220
|
video.addEventListener("loadedmetadata", () => {
|
|
214
221
|
sizeThumbnail(video, video.videoWidth, video.videoHeight);
|
|
@@ -216,7 +223,7 @@ function artifactCard(artifact: StudioArtifactView, open: (artifact: StudioArtif
|
|
|
216
223
|
}, { once: true });
|
|
217
224
|
video.addEventListener("loadeddata", ready);
|
|
218
225
|
video.addEventListener("seeked", ready);
|
|
219
|
-
video.addEventListener("error", () => { label
|
|
226
|
+
video.addEventListener("error", () => { uiText(label, "library.preview-unavailable"); link.classList.add("preview-unavailable"); });
|
|
220
227
|
link.querySelector<HTMLElement>(".artifact-preview")!.prepend(video);
|
|
221
228
|
} else if (artifact.mediaType.startsWith("audio/")) {
|
|
222
229
|
const waveform = document.createElement("img");
|
|
@@ -229,7 +236,7 @@ function artifactCard(artifact: StudioArtifactView, open: (artifact: StudioArtif
|
|
|
229
236
|
title,
|
|
230
237
|
artifact.mediaType,
|
|
231
238
|
formatBytes(artifact.size),
|
|
232
|
-
|
|
239
|
+
new Date(artifact.createdAt).toLocaleString(),
|
|
233
240
|
artifact.ownerBuild === undefined ? artifact.filePath : `${artifact.ownerBuild}/${artifact.filePath}`,
|
|
234
241
|
...artifact.origins.map((origin) => `${origin.run ?? origin.source} · ${origin.build} · ${origin.output}`),
|
|
235
242
|
artifact.buildNote,
|
|
@@ -241,15 +248,15 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
241
248
|
const element = document.createElement("section");
|
|
242
249
|
element.className = "library";
|
|
243
250
|
element.innerHTML = `
|
|
244
|
-
<div class="library-tabs" role="tablist" aria-label
|
|
251
|
+
<div class="library-tabs" role="tablist" ${uiAttribute("aria-label", "library.studio-library")}>
|
|
245
252
|
<button type="button" class="library-tab active" data-library-tab="source" role="tab" aria-selected="true">
|
|
246
|
-
<span>${icon("code")}</span><strong
|
|
253
|
+
<span>${icon("code")}</span><strong>${uiLabel("library.source")}</strong>
|
|
247
254
|
</button>
|
|
248
255
|
<button type="button" class="library-tab" data-library-tab="tasks" role="tab" aria-selected="false">
|
|
249
|
-
<span>${icon("tasks")}</span><strong
|
|
256
|
+
<span>${icon("tasks")}</span><strong>${uiLabel("library.tasks")}</strong>
|
|
250
257
|
</button>
|
|
251
258
|
<button type="button" class="library-tab" data-library-tab="artifacts" role="tab" aria-selected="false">
|
|
252
|
-
<span>${icon("results")}</span><strong
|
|
259
|
+
<span>${icon("results")}</span><strong>${uiLabel("library.artifacts")}</strong>
|
|
253
260
|
</button>
|
|
254
261
|
</div>
|
|
255
262
|
<section class="library-view active" data-library-view="source">
|
|
@@ -258,19 +265,22 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
258
265
|
<section class="library-view" data-library-view="artifacts">
|
|
259
266
|
</section>`;
|
|
260
267
|
|
|
261
|
-
const sourcePanel = createSidebarPanel("panel", "
|
|
268
|
+
const sourcePanel = createSidebarPanel("panel", "library.referenced-source-files");
|
|
269
|
+
uiAttr(sourcePanel.navigation, "aria-label", "library.referenced-source-files");
|
|
262
270
|
sourcePanel.toolbar.append(code.toolbar);
|
|
263
271
|
sourcePanel.content.classList.add("source-code");
|
|
264
272
|
sourcePanel.content.append(code.element);
|
|
265
273
|
element.querySelector('[data-library-view="source"]')!.append(sourcePanel.element);
|
|
266
|
-
const taskPanel = createSidebarPanel("panel", "
|
|
267
|
-
taskPanel.
|
|
268
|
-
|
|
274
|
+
const taskPanel = createSidebarPanel("panel", "library.task-categories");
|
|
275
|
+
uiAttr(taskPanel.navigation, "aria-label", "library.task-categories");
|
|
276
|
+
taskPanel.toolbar.innerHTML = `<span class="code-location" data-task-heading>${uiLabel("library.all-tasks")}</span><div class="task-context" data-task-context hidden></div>
|
|
277
|
+
<button type="button" class="library-refresh" data-library-refresh ${uiAttribute("aria-label", "library.refresh-tasks")} ${uiAttribute("title", "common.refresh")}>${icon("refresh")}</button>`;
|
|
269
278
|
taskPanel.content.innerHTML = `<div class="media-status" data-library-status="tasks" role="status" hidden></div><div class="task-list" data-task-list></div>`;
|
|
270
279
|
element.querySelector('[data-library-view="tasks"]')!.append(taskPanel.element);
|
|
271
|
-
const mediaPanel = createSidebarPanel("panel", "
|
|
272
|
-
mediaPanel.
|
|
273
|
-
|
|
280
|
+
const mediaPanel = createSidebarPanel("panel", "library.media-categories");
|
|
281
|
+
uiAttr(mediaPanel.navigation, "aria-label", "library.media-categories");
|
|
282
|
+
mediaPanel.toolbar.innerHTML = `<span class="code-location" data-media-heading>${uiLabel("library.all-media")}</span><div class="task-context" data-task-context hidden></div>
|
|
283
|
+
<button type="button" class="library-refresh" data-library-refresh ${uiAttribute("aria-label", "library.refresh-media")} ${uiAttribute("title", "common.refresh")}>${icon("refresh")}</button>`;
|
|
274
284
|
mediaPanel.content.innerHTML = `<div class="media-status" data-library-status="artifacts" role="status" hidden></div><div class="artifact-grid" data-artifact-grid></div>`;
|
|
275
285
|
element.querySelector('[data-library-view="artifacts"]')!.append(mediaPanel.element);
|
|
276
286
|
const sourceList = sourcePanel.navigation;
|
|
@@ -300,7 +310,7 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
300
310
|
node.className = "library-continuation";
|
|
301
311
|
node.dataset.moreSection = section;
|
|
302
312
|
node.dataset.before = cursor;
|
|
303
|
-
node
|
|
313
|
+
uiText(node, "library.loading-more");
|
|
304
314
|
return node;
|
|
305
315
|
};
|
|
306
316
|
const selectArtifact = (id: string | undefined): void => {
|
|
@@ -367,7 +377,7 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
367
377
|
back.type = "button"; back.className = "task-context-name";
|
|
368
378
|
back.innerHTML = "<span></span>";
|
|
369
379
|
back.querySelector("span")!.textContent = task.title ?? stem(task.run ?? task.source);
|
|
370
|
-
back
|
|
380
|
+
uiAttr(back, "title", "library.task-return", { name: task.title ?? stem(task.run ?? task.source) });
|
|
371
381
|
back.addEventListener("click", () => {
|
|
372
382
|
taskFilter = "all";
|
|
373
383
|
switchTo("tasks", false);
|
|
@@ -376,7 +386,7 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
376
386
|
});
|
|
377
387
|
const clear = document.createElement("button");
|
|
378
388
|
clear.type = "button"; clear.className = "task-context-clear";
|
|
379
|
-
clear.innerHTML = icon("close"); clear
|
|
389
|
+
clear.innerHTML = icon("close"); uiAttr(clear, "title", "library.clear-task-filter"); uiAttr(clear, "aria-label", "library.clear-task-filter");
|
|
380
390
|
clear.addEventListener("click", () => {
|
|
381
391
|
selectedTask = undefined; delete libraries.artifacts; renderTaskContext(); renderTasks();
|
|
382
392
|
if (active === "artifacts") { renderArtifacts(); void refresh("artifacts"); }
|
|
@@ -389,11 +399,12 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
389
399
|
for (const node of Array.from(taskList.querySelectorAll("[data-more-section]"))) morePages.unobserve(node);
|
|
390
400
|
const library = libraries.tasks;
|
|
391
401
|
const tasks = library?.tasks ?? [];
|
|
392
|
-
const filters = [["all", "
|
|
402
|
+
const filters = [["all", "library.all-tasks", "component"], ["ongoing", "library.in-progress", "when"], ["finished", "library.finished", "check"]] as const;
|
|
393
403
|
const heading = taskPanel.toolbar.querySelector<HTMLElement>("[data-task-heading]")!;
|
|
394
|
-
heading
|
|
395
|
-
|
|
396
|
-
|
|
404
|
+
uiText(heading, filters.find(([id]) => id === taskFilter)![1]);
|
|
405
|
+
if (library?.runtime === undefined) uiAttr(heading, "title", "tasks.saved-only");
|
|
406
|
+
else { heading.removeAttribute("data-ui-title"); heading.title = library.runtime; }
|
|
407
|
+
taskPanel.navigation.replaceChildren(...filters.map(([id, label, mark]) => localizedSidebarItem({
|
|
397
408
|
label, icon: mark, selected: id === taskFilter,
|
|
398
409
|
select: () => { taskFilter = id; taskList.scrollTop = 0; renderTasks(); },
|
|
399
410
|
})));
|
|
@@ -413,10 +424,10 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
413
424
|
() => selectTask(task), () => { selectTask(task); switchTo("artifacts"); }));
|
|
414
425
|
renderTaskContext();
|
|
415
426
|
if (tasksToShow.length === 0) entries.push(emptyState("tasks",
|
|
416
|
-
library === undefined && request !== undefined ? "
|
|
427
|
+
library === undefined && request !== undefined ? "library.loading-tasks" : taskFilter === "ongoing" ? "library.no-tasks-in-progress" : "library.no-tasks-here",
|
|
417
428
|
taskFilter === "ongoing" && library?.runtime === undefined
|
|
418
|
-
? "
|
|
419
|
-
: "
|
|
429
|
+
? "tasks.no-runtime"
|
|
430
|
+
: "tasks.refresh-hint"));
|
|
420
431
|
// Runtime activity is returned in full on the first page. Older pages contain Results only.
|
|
421
432
|
if (taskFilter !== "ongoing" && library?.next !== undefined) entries.push(continuation("tasks", library.next));
|
|
422
433
|
taskList.replaceChildren(...entries);
|
|
@@ -429,12 +440,12 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
429
440
|
const cards = new Map(Array.from(artifactGrid.querySelectorAll<HTMLElement>("[data-artifact-id]"), (card) => [card.dataset.artifactId!, card]));
|
|
430
441
|
const library = libraries.artifacts;
|
|
431
442
|
const artifacts = library?.artifacts ?? [];
|
|
432
|
-
const filters = [["all", "
|
|
443
|
+
const filters = [["all", "library.all-media", "component"], ["video", "library.videos", "video"], ["image", "library.images", "image"], ["audio", "library.audio", "waveform"]] as const;
|
|
433
444
|
const heading = mediaPanel.toolbar.querySelector<HTMLElement>("[data-media-heading]")!;
|
|
434
|
-
heading
|
|
435
|
-
heading
|
|
445
|
+
uiText(heading, filters.find(([id]) => id === artifactFilter)?.[1] ?? "library.all-media");
|
|
446
|
+
uiAttr(heading, "title", "library.media-category");
|
|
436
447
|
renderTaskContext();
|
|
437
|
-
artifactFilters.replaceChildren(...filters.map(([id, label, mark]) =>
|
|
448
|
+
artifactFilters.replaceChildren(...filters.map(([id, label, mark]) => localizedSidebarItem({
|
|
438
449
|
label, icon: mark, selected: id === artifactFilter,
|
|
439
450
|
select: () => {
|
|
440
451
|
artifactFilter = id;
|
|
@@ -466,8 +477,8 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
466
477
|
if (video !== null) { video.removeAttribute("src"); video.load(); }
|
|
467
478
|
}
|
|
468
479
|
if (shown.length === 0) entries.push(emptyState("results",
|
|
469
|
-
library === undefined && request !== undefined ? "
|
|
470
|
-
"
|
|
480
|
+
library === undefined && request !== undefined ? "library.loading-media" : artifactFilter === "all" ? "library.no-media" : "library.no-files-in-this-category",
|
|
481
|
+
"library.media-hint"));
|
|
471
482
|
if (library?.next !== undefined) entries.push(continuation("artifacts", library.next));
|
|
472
483
|
artifactGrid.replaceChildren(...entries);
|
|
473
484
|
selectArtifact(selectedArtifact);
|
|
@@ -497,7 +508,7 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
497
508
|
const controller = new AbortController();
|
|
498
509
|
request = controller;
|
|
499
510
|
const status = element.querySelector<HTMLElement>(`[data-library-status="${section}"]`)!;
|
|
500
|
-
status
|
|
511
|
+
uiText(status, "library.refreshing");
|
|
501
512
|
status.hidden = true;
|
|
502
513
|
element.classList.add("is-refreshing");
|
|
503
514
|
try {
|
|
@@ -507,7 +518,7 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
507
518
|
if (section === "artifacts" && selectedTask !== undefined) query.set("build", selectedTask.id);
|
|
508
519
|
const response = await fetch(`/__studio/library?${query.toString()}`, { signal: controller.signal });
|
|
509
520
|
const value = await response.json() as StudioLibraryView | { readonly error: string };
|
|
510
|
-
if (!response.ok || "error" in value) throw new Error("error" in value ? value.error : "
|
|
521
|
+
if (!response.ok || "error" in value) throw new Error("error" in value ? value.error : t("library.unavailable"));
|
|
511
522
|
if (controller.signal.aborted) return;
|
|
512
523
|
const previous = libraries[section];
|
|
513
524
|
if (before === undefined || previous === undefined) libraries[section] = value;
|
|
@@ -522,18 +533,18 @@ export function createLibraryPane(code: CodePane, openArtifact: (artifact: Studi
|
|
|
522
533
|
}
|
|
523
534
|
if (section === "tasks") renderTasks();
|
|
524
535
|
else renderArtifacts();
|
|
525
|
-
status.
|
|
536
|
+
uiText(status, "library.updated", { time: new Date().toLocaleTimeString() });
|
|
526
537
|
} catch (error) {
|
|
527
538
|
if (!controller.signal.aborted) {
|
|
528
539
|
const detail = error instanceof Error ? error.message : String(error);
|
|
529
540
|
status.hidden = false;
|
|
530
|
-
status
|
|
541
|
+
uiText(status, libraries[section] === undefined ? "library.refresh-failed" : "library.refresh-failed-retained", { detail });
|
|
531
542
|
}
|
|
532
543
|
} finally {
|
|
533
544
|
if (request === controller) {
|
|
534
545
|
request = undefined;
|
|
535
546
|
element.classList.remove("is-refreshing");
|
|
536
|
-
if (controller.signal.aborted) status
|
|
547
|
+
if (controller.signal.aborted) uiText(status, "library.refresh-interrupted");
|
|
537
548
|
}
|
|
538
549
|
}
|
|
539
550
|
};
|