@mulmoclaude/mulmoscript-plugin 0.2.3 → 1.1.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/core/paths.d.ts +2 -5
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/plugin.d.ts.map +1 -1
- package/dist/core/validate.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/lang/index.d.ts +1 -2
- package/dist/lang/index.d.ts.map +1 -1
- package/dist/{plugin-Bi986Wga.cjs → plugin-BLp8tSYt.cjs} +86 -46
- package/dist/plugin-BLp8tSYt.cjs.map +1 -0
- package/dist/{plugin-BQyzxkui.js → plugin-CXhOP_xU.js} +81 -47
- package/dist/plugin-CXhOP_xU.js.map +1 -0
- package/dist/server/mulmoErrorCapture.d.ts.map +1 -1
- package/dist/server/ops.d.ts.map +1 -1
- package/dist/server/support.d.ts +0 -5
- package/dist/server/support.d.ts.map +1 -1
- package/dist/server/types.d.ts +3 -7
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server.cjs +34 -58
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +33 -57
- package/dist/server.js.map +1 -1
- package/dist/style.css +36 -18
- package/dist/vue/View.vue.d.ts.map +1 -1
- package/dist/vue/components/BeatLightbox.vue.d.ts +25 -0
- package/dist/vue/components/BeatLightbox.vue.d.ts.map +1 -0
- package/dist/vue/components/CharacterStrip.vue.d.ts +29 -0
- package/dist/vue/components/CharacterStrip.vue.d.ts.map +1 -0
- package/dist/vue/components/MulmoScriptToolbar.vue.d.ts +26 -0
- package/dist/vue/components/MulmoScriptToolbar.vue.d.ts.map +1 -0
- package/dist/vue/composables/useBeatMovie.d.ts +20 -0
- package/dist/vue/composables/useBeatMovie.d.ts.map +1 -0
- package/dist/vue/composables/useCharacterImages.d.ts +31 -0
- package/dist/vue/composables/useCharacterImages.d.ts.map +1 -0
- package/dist/vue/composables/useDeckEditor.d.ts +18 -0
- package/dist/vue/composables/useDeckEditor.d.ts.map +1 -0
- package/dist/vue/composables/useMediaExport.d.ts +26 -0
- package/dist/vue/composables/useMediaExport.d.ts.map +1 -0
- package/dist/vue/helpers.d.ts +78 -0
- package/dist/vue/helpers.d.ts.map +1 -1
- package/dist/vue/index.d.ts +1 -1
- package/dist/vue/support.d.ts +6 -4
- package/dist/vue/support.d.ts.map +1 -1
- package/dist/vue/transport.d.ts.map +1 -1
- package/dist/vue/viewTypes.d.ts +49 -0
- package/dist/vue/viewTypes.d.ts.map +1 -0
- package/dist/vue.cjs +1021 -729
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +1019 -727
- package/dist/vue.js.map +1 -1
- package/package.json +11 -7
- package/dist/plugin-BQyzxkui.js.map +0 -1
- package/dist/plugin-Bi986Wga.cjs.map +0 -1
package/dist/vue.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
import { a as pluginCore, d as
|
|
1
|
+
import { a as pluginCore, d as errorMessage, f as TOOL_DEFINITION, p as TOOL_NAME } from "./plugin-CXhOP_xU.js";
|
|
2
2
|
import { t as GENERATION_EVENT } from "./contract-vY0niHkh.js";
|
|
3
3
|
import { mulmoBeatSchema, mulmoScriptSchema } from "@mulmocast/types";
|
|
4
|
-
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineAsyncComponent, defineComponent, inject, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, reactive, ref, renderList, toDisplayString, unref, vModelText, vShow, watch, withDirectives, withModifiers } from "vue";
|
|
5
|
-
import {
|
|
4
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineAsyncComponent, defineComponent, inject, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, reactive, ref, renderList, toDisplayString, unref, vModelText, vShow, watch, withDirectives, withModifiers } from "vue";
|
|
5
|
+
import { createUseT, useRuntime } from "gui-chat-protocol/vue";
|
|
6
6
|
//#region src/vue/support.ts
|
|
7
7
|
function isRecord(value) {
|
|
8
8
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
/** Read a dropped image File as a base64 data URL, the form the upload
|
|
11
|
+
* dispatches expect. Shared by the beat and character drop handlers.
|
|
12
|
+
* `readAsDataURL` always yields a string on load; the non-string reject
|
|
13
|
+
* is an unreachable guard kept so the resolve type stays `string` without
|
|
14
|
+
* a cast. */
|
|
15
|
+
function readFileAsDataUrl(file) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const reader = new FileReader();
|
|
18
|
+
reader.onload = () => {
|
|
19
|
+
const { result } = reader;
|
|
20
|
+
if (typeof result === "string") resolve(result);
|
|
21
|
+
else reject(/* @__PURE__ */ new Error("FileReader did not return a data URL string"));
|
|
22
|
+
};
|
|
23
|
+
reader.onerror = reject;
|
|
24
|
+
reader.readAsDataURL(file);
|
|
25
|
+
});
|
|
22
26
|
}
|
|
23
27
|
/** Clipboard failures (permissions, insecure context) are swallowed on
|
|
24
28
|
* purpose: the UI just leaves the "Copied!" hint off, which is what
|
|
@@ -100,6 +104,16 @@ function beatMayHaveMovie(beat) {
|
|
|
100
104
|
if (beat.moviePrompt) return true;
|
|
101
105
|
return beat.image?.type === "html_tailwind" && Boolean(beat.image.animation);
|
|
102
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* True for a beat whose image merely REFERENCES another beat's image
|
|
109
|
+
* (`image: { type: "beat", id }` — mulmoBeatReferenceMediaSchema). Such a
|
|
110
|
+
* beat owns no asset of its own, so there is nothing to generate for it:
|
|
111
|
+
* the View hides the Generate button (offering it produced a render that
|
|
112
|
+
* could never succeed on its own terms).
|
|
113
|
+
*/
|
|
114
|
+
function isBeatImageReference(beat) {
|
|
115
|
+
return beat.image?.type === "beat";
|
|
116
|
+
}
|
|
103
117
|
/** Pure check: is every beat in the script a `slide`-typed beat?
|
|
104
118
|
* When true, the View mounts `@mulmocast/deck-web`'s
|
|
105
119
|
* `MulmoScriptDeckEditor` instead of the per-beat list UI (#1575).
|
|
@@ -117,6 +131,74 @@ function isAllSlideDeck(script) {
|
|
|
117
131
|
return isRecord(image) && image.type === "slide";
|
|
118
132
|
});
|
|
119
133
|
}
|
|
134
|
+
/** Resolve the beat the View should render at `index`: the user's
|
|
135
|
+
* in-place edit (`overrides`) wins over the on-disk beat, and an
|
|
136
|
+
* out-of-range index yields an empty beat so callers can read
|
|
137
|
+
* `.text` / `.image` without a guard. */
|
|
138
|
+
function effectiveBeat(overrides, beats, index) {
|
|
139
|
+
return overrides[index] ?? beats[index] ?? {};
|
|
140
|
+
}
|
|
141
|
+
var BEAT_TOOLTIP_MAX_CHARS = 80;
|
|
142
|
+
/** Beat-strip hover tooltip: the beat text, truncated with an ellipsis
|
|
143
|
+
* past the cap. Missing text yields an empty string. Text of exactly
|
|
144
|
+
* the cap length is returned whole (only a longer string is cut). */
|
|
145
|
+
function beatTooltip(text) {
|
|
146
|
+
const value = text ?? "";
|
|
147
|
+
return value.length > BEAT_TOOLTIP_MAX_CHARS ? `${value.slice(0, BEAT_TOOLTIP_MAX_CHARS)}…` : value;
|
|
148
|
+
}
|
|
149
|
+
/** The prompt for a character image, or "" when the key or its prompt
|
|
150
|
+
* is absent — the character strip renders the empty string as no
|
|
151
|
+
* caption rather than `undefined`. */
|
|
152
|
+
function characterPrompt(images, key) {
|
|
153
|
+
return images?.[key]?.prompt ?? "";
|
|
154
|
+
}
|
|
155
|
+
/** Is the in-editor JSON for a beat currently valid? A missing entry
|
|
156
|
+
* (source editor never opened) validates the empty string, which is
|
|
157
|
+
* not parseable JSON, so it reports invalid rather than throwing. */
|
|
158
|
+
function isValidBeat(source, schema) {
|
|
159
|
+
return validateBeatJSON(source ?? "", schema);
|
|
160
|
+
}
|
|
161
|
+
/** Stale-response guard: a per-beat / per-character response is stale
|
|
162
|
+
* once the View has navigated to a different result, i.e. the current
|
|
163
|
+
* file path no longer matches the one captured when the call was made.
|
|
164
|
+
* Keeping the direction pinned matters — an inverted check would let
|
|
165
|
+
* script A's late responses write into script B's state. */
|
|
166
|
+
function staleSince(currentFilePath, requestedFilePath) {
|
|
167
|
+
return currentFilePath !== requestedFilePath;
|
|
168
|
+
}
|
|
169
|
+
var JSON_INDENT = 2;
|
|
170
|
+
/** Pretty-print a script (or any value) as the source-editor / clipboard
|
|
171
|
+
* text — two-space indent, matching what the beat and disk views emit. */
|
|
172
|
+
function scriptSourceText(value) {
|
|
173
|
+
return JSON.stringify(value, null, JSON_INDENT);
|
|
174
|
+
}
|
|
175
|
+
/** Basename for a download `<a download>` attribute, falling back when
|
|
176
|
+
* the path has no basename. Mirrors the exact existing behaviour, and
|
|
177
|
+
* it has a sharp edge: `.pop()` returns "" (not undefined) for a
|
|
178
|
+
* trailing slash or empty path, and `??` does NOT replace "", so those
|
|
179
|
+
* yield an empty filename rather than the fallback. Server paths always
|
|
180
|
+
* carry a basename, so this never bites in practice — pinned so a later
|
|
181
|
+
* reader doesn't "simplify" `??` to `||` and change behaviour. */
|
|
182
|
+
function downloadFilename(path, fallback) {
|
|
183
|
+
return path.split("/").pop() ?? fallback;
|
|
184
|
+
}
|
|
185
|
+
/** Narrow a script-supplied silent-beat duration to a safe positive number.
|
|
186
|
+
* Zero / negative / NaN / Infinity / non-number collapse the auto-advance
|
|
187
|
+
* timer to an immediate fire, which races the Play loop through every silent
|
|
188
|
+
* beat in a single tick (#1365) — fall back to the default so a run of silent
|
|
189
|
+
* beats stays watchable. The script's own valid `duration` always wins. */
|
|
190
|
+
function resolveSilentAdvanceSeconds(raw, defaultSec) {
|
|
191
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : defaultSec;
|
|
192
|
+
}
|
|
193
|
+
/** Delete every own enumerable key of each record, in place. Used to reset the
|
|
194
|
+
* View's per-beat / per-character reactive maps between scripts — passing the
|
|
195
|
+
* reactive proxies mutates them so the template re-renders empty. Replaces a
|
|
196
|
+
* wall of hand-rolled `Object.keys(map).forEach(delete)` loops. */
|
|
197
|
+
function clearReactiveRecords(...records) {
|
|
198
|
+
records.forEach((record) => {
|
|
199
|
+
Object.keys(record).forEach((key) => Reflect.deleteProperty(record, key));
|
|
200
|
+
});
|
|
201
|
+
}
|
|
120
202
|
//#endregion
|
|
121
203
|
//#region src/vue/transport.ts
|
|
122
204
|
var GENERATION_EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -185,8 +267,337 @@ function useHostAdapter() {
|
|
|
185
267
|
return inject(MULMOSCRIPT_HOST_ADAPTER_KEY, EMPTY_ADAPTER);
|
|
186
268
|
}
|
|
187
269
|
//#endregion
|
|
188
|
-
//#region src/
|
|
189
|
-
|
|
270
|
+
//#region src/vue/composables/useMediaExport.ts
|
|
271
|
+
function useMediaExport({ api, adapter, filePath, chatSessionId }) {
|
|
272
|
+
const movieGenerating = ref(false);
|
|
273
|
+
const movieDownloading = ref(false);
|
|
274
|
+
const moviePath = ref(null);
|
|
275
|
+
const movieError = ref(null);
|
|
276
|
+
const pdfGenerating = ref(false);
|
|
277
|
+
const pdfDownloading = ref(false);
|
|
278
|
+
const pdfPath = ref(null);
|
|
279
|
+
async function generateMovie() {
|
|
280
|
+
const requestedFilePath = filePath.value;
|
|
281
|
+
movieGenerating.value = true;
|
|
282
|
+
movieError.value = null;
|
|
283
|
+
const response = await api.call("generateMovie", {
|
|
284
|
+
filePath: requestedFilePath,
|
|
285
|
+
chatSessionId: chatSessionId.value
|
|
286
|
+
});
|
|
287
|
+
if (filePath.value !== requestedFilePath) return;
|
|
288
|
+
movieGenerating.value = false;
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
movieError.value = response.error;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
moviePath.value = response.data.moviePath;
|
|
294
|
+
}
|
|
295
|
+
async function generatePdf() {
|
|
296
|
+
const requestedFilePath = filePath.value;
|
|
297
|
+
pdfGenerating.value = true;
|
|
298
|
+
const response = await api.call("generatePdf", {
|
|
299
|
+
filePath: requestedFilePath,
|
|
300
|
+
chatSessionId: chatSessionId.value
|
|
301
|
+
});
|
|
302
|
+
if (filePath.value !== requestedFilePath) return;
|
|
303
|
+
pdfGenerating.value = false;
|
|
304
|
+
if (!response.ok) {
|
|
305
|
+
alert(response.error);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
pdfPath.value = response.data.pdfPath;
|
|
309
|
+
}
|
|
310
|
+
async function refreshMoviePath() {
|
|
311
|
+
const requestedFilePath = filePath.value;
|
|
312
|
+
if (!requestedFilePath) return;
|
|
313
|
+
const response = await api.call("movieStatus", { filePath: requestedFilePath });
|
|
314
|
+
if (filePath.value !== requestedFilePath) return;
|
|
315
|
+
if (response.ok && response.data.moviePath) moviePath.value = response.data.moviePath;
|
|
316
|
+
}
|
|
317
|
+
async function refreshPdfPath() {
|
|
318
|
+
const requestedFilePath = filePath.value;
|
|
319
|
+
if (!requestedFilePath) return;
|
|
320
|
+
const response = await api.call("pdfStatus", { filePath: requestedFilePath });
|
|
321
|
+
if (filePath.value !== requestedFilePath) return;
|
|
322
|
+
if (response.ok && response.data.pdfPath) pdfPath.value = response.data.pdfPath;
|
|
323
|
+
}
|
|
324
|
+
async function downloadMedia(kind, sourcePath, fallbackName, downloading) {
|
|
325
|
+
const fetchMediaBlob = adapter.fetchMediaBlob;
|
|
326
|
+
if (!fetchMediaBlob || !sourcePath || downloading.value) return;
|
|
327
|
+
downloading.value = true;
|
|
328
|
+
let objectUrl = null;
|
|
329
|
+
try {
|
|
330
|
+
const blob = await fetchMediaBlob(kind === "movie" ? { moviePath: sourcePath } : { pdfPath: sourcePath });
|
|
331
|
+
objectUrl = URL.createObjectURL(blob);
|
|
332
|
+
clickDownloadAnchor(objectUrl, downloadFilename(sourcePath, fallbackName));
|
|
333
|
+
} catch (err) {
|
|
334
|
+
alert(errorMessage(err));
|
|
335
|
+
} finally {
|
|
336
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
337
|
+
downloading.value = false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function downloadMovie() {
|
|
341
|
+
return downloadMedia("movie", moviePath.value, "movie.mp4", movieDownloading);
|
|
342
|
+
}
|
|
343
|
+
function downloadPdf() {
|
|
344
|
+
return downloadMedia("pdf", pdfPath.value, "deck.pdf", pdfDownloading);
|
|
345
|
+
}
|
|
346
|
+
function resetMedia() {
|
|
347
|
+
moviePath.value = null;
|
|
348
|
+
pdfPath.value = null;
|
|
349
|
+
movieGenerating.value = false;
|
|
350
|
+
pdfGenerating.value = false;
|
|
351
|
+
movieError.value = null;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
moviePath,
|
|
355
|
+
movieGenerating,
|
|
356
|
+
movieDownloading,
|
|
357
|
+
movieError,
|
|
358
|
+
pdfPath,
|
|
359
|
+
pdfGenerating,
|
|
360
|
+
pdfDownloading,
|
|
361
|
+
generateMovie,
|
|
362
|
+
downloadMovie,
|
|
363
|
+
refreshMoviePath,
|
|
364
|
+
generatePdf,
|
|
365
|
+
downloadPdf,
|
|
366
|
+
refreshPdfPath,
|
|
367
|
+
resetMedia
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function clickDownloadAnchor(href, filename) {
|
|
371
|
+
const anchor = document.createElement("a");
|
|
372
|
+
anchor.href = href;
|
|
373
|
+
anchor.download = filename;
|
|
374
|
+
document.body.appendChild(anchor);
|
|
375
|
+
anchor.click();
|
|
376
|
+
anchor.remove();
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/vue/composables/useBeatMovie.ts
|
|
380
|
+
function useBeatMovie({ api, adapter, filePath }) {
|
|
381
|
+
const beatMovies = reactive({});
|
|
382
|
+
const beatMovieUrls = reactive({});
|
|
383
|
+
const beatMovieOpen = reactive({});
|
|
384
|
+
const beatMovieLoading = reactive({});
|
|
385
|
+
const staleSince$3 = (requestedFilePath) => staleSince(filePath.value, requestedFilePath);
|
|
386
|
+
async function loadExistingBeatMovie(index) {
|
|
387
|
+
const requestedFilePath = filePath.value;
|
|
388
|
+
const response = await api.call("beatMovie", {
|
|
389
|
+
filePath: requestedFilePath,
|
|
390
|
+
beatIndex: index
|
|
391
|
+
});
|
|
392
|
+
if (staleSince$3(requestedFilePath)) return;
|
|
393
|
+
if (response.ok && response.data.moviePath) beatMovies[index] = response.data.moviePath;
|
|
394
|
+
}
|
|
395
|
+
async function playBeatMovie(index) {
|
|
396
|
+
const fetchMediaBlob = adapter.fetchMediaBlob;
|
|
397
|
+
if (!fetchMediaBlob || !beatMovies[index] || beatMovieLoading[index]) return;
|
|
398
|
+
if (beatMovieUrls[index]) {
|
|
399
|
+
beatMovieOpen[index] = true;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
beatMovieLoading[index] = true;
|
|
403
|
+
try {
|
|
404
|
+
const blob = new Blob([await fetchMediaBlob({ moviePath: beatMovies[index] })], { type: "video/mp4" });
|
|
405
|
+
beatMovieUrls[index] = URL.createObjectURL(blob);
|
|
406
|
+
beatMovieOpen[index] = true;
|
|
407
|
+
} catch (err) {
|
|
408
|
+
alert(errorMessage(err));
|
|
409
|
+
} finally {
|
|
410
|
+
Reflect.deleteProperty(beatMovieLoading, index);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function closeBeatMovie(index) {
|
|
414
|
+
Reflect.deleteProperty(beatMovieOpen, index);
|
|
415
|
+
}
|
|
416
|
+
function invalidateBeatMovie(index) {
|
|
417
|
+
if (beatMovieUrls[index]) URL.revokeObjectURL(beatMovieUrls[index]);
|
|
418
|
+
[
|
|
419
|
+
beatMovies,
|
|
420
|
+
beatMovieUrls,
|
|
421
|
+
beatMovieOpen
|
|
422
|
+
].forEach((map) => Reflect.deleteProperty(map, index));
|
|
423
|
+
}
|
|
424
|
+
function resetBeatMovies() {
|
|
425
|
+
Object.values(beatMovieUrls).forEach((url) => URL.revokeObjectURL(url));
|
|
426
|
+
clearReactiveRecords(beatMovies, beatMovieUrls, beatMovieOpen, beatMovieLoading);
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
beatMovies,
|
|
430
|
+
beatMovieUrls,
|
|
431
|
+
beatMovieOpen,
|
|
432
|
+
beatMovieLoading,
|
|
433
|
+
loadExistingBeatMovie,
|
|
434
|
+
playBeatMovie,
|
|
435
|
+
closeBeatMovie,
|
|
436
|
+
invalidateBeatMovie,
|
|
437
|
+
resetBeatMovies
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/vue/composables/useCharacterImages.ts
|
|
442
|
+
function useCharacterImages({ api, filePath, chatSessionId, getImages }) {
|
|
443
|
+
const charRenderState = reactive({});
|
|
444
|
+
const charImages = reactive({});
|
|
445
|
+
const charErrors = reactive({});
|
|
446
|
+
const charDragOver = reactive({});
|
|
447
|
+
const staleSince$2 = (requestedFilePath) => staleSince(filePath.value, requestedFilePath);
|
|
448
|
+
const characterKeys = computed(() => {
|
|
449
|
+
const imgs = getImages() ?? {};
|
|
450
|
+
return Object.keys(imgs).filter((key) => imgs[key]?.type === "imagePrompt");
|
|
451
|
+
});
|
|
452
|
+
function characterPrompt$1(key) {
|
|
453
|
+
return characterPrompt(getImages(), key);
|
|
454
|
+
}
|
|
455
|
+
function onCharDragOver(event, key) {
|
|
456
|
+
if (!event.dataTransfer?.types.includes("Files")) return;
|
|
457
|
+
event.preventDefault();
|
|
458
|
+
charDragOver[key] = true;
|
|
459
|
+
}
|
|
460
|
+
function onCharDragLeave(key) {
|
|
461
|
+
charDragOver[key] = false;
|
|
462
|
+
}
|
|
463
|
+
async function onCharDrop(event, key) {
|
|
464
|
+
event.preventDefault();
|
|
465
|
+
charDragOver[key] = false;
|
|
466
|
+
const file = event.dataTransfer?.files[0];
|
|
467
|
+
if (!file || !file.type.startsWith("image/")) return;
|
|
468
|
+
charRenderState[key] = "rendering";
|
|
469
|
+
Reflect.deleteProperty(charErrors, key);
|
|
470
|
+
let imageData;
|
|
471
|
+
try {
|
|
472
|
+
imageData = await readFileAsDataUrl(file);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
charErrors[key] = errorMessage(err);
|
|
475
|
+
charRenderState[key] = "error";
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const requestedFilePath = filePath.value;
|
|
479
|
+
const response = await api.call("uploadCharacterImage", {
|
|
480
|
+
filePath: requestedFilePath,
|
|
481
|
+
key,
|
|
482
|
+
imageData
|
|
483
|
+
});
|
|
484
|
+
if (staleSince$2(requestedFilePath)) return;
|
|
485
|
+
if (!response.ok) {
|
|
486
|
+
charErrors[key] = response.error || "Upload failed";
|
|
487
|
+
charRenderState[key] = "error";
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
charImages[key] = response.data.image ?? "";
|
|
491
|
+
charRenderState[key] = "done";
|
|
492
|
+
}
|
|
493
|
+
async function loadExistingCharacterImage(key) {
|
|
494
|
+
const requestedFilePath = filePath.value;
|
|
495
|
+
const response = await api.call("characterImage", {
|
|
496
|
+
filePath: requestedFilePath,
|
|
497
|
+
key
|
|
498
|
+
});
|
|
499
|
+
if (staleSince$2(requestedFilePath)) return;
|
|
500
|
+
if (response.ok && response.data.image) {
|
|
501
|
+
charImages[key] = response.data.image;
|
|
502
|
+
charRenderState[key] = "done";
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function refreshMissingCharacterImages() {
|
|
506
|
+
getMissingCharacterKeys(characterKeys.value, charImages, charRenderState).forEach((key) => loadExistingCharacterImage(key));
|
|
507
|
+
}
|
|
508
|
+
async function renderCharacter(key, force) {
|
|
509
|
+
const requestedFilePath = filePath.value;
|
|
510
|
+
charRenderState[key] = "rendering";
|
|
511
|
+
Reflect.deleteProperty(charErrors, key);
|
|
512
|
+
const response = await api.call("renderCharacter", {
|
|
513
|
+
filePath: requestedFilePath,
|
|
514
|
+
key,
|
|
515
|
+
force,
|
|
516
|
+
chatSessionId: chatSessionId.value
|
|
517
|
+
});
|
|
518
|
+
if (staleSince$2(requestedFilePath)) return;
|
|
519
|
+
if (!response.ok) {
|
|
520
|
+
charErrors[key] = response.error || "Render failed";
|
|
521
|
+
charRenderState[key] = "error";
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
charImages[key] = response.data.image ?? "";
|
|
525
|
+
charRenderState[key] = "done";
|
|
526
|
+
}
|
|
527
|
+
async function generateAllCharacters() {
|
|
528
|
+
await Promise.all(characterKeys.value.filter((key) => charRenderState[key] !== "rendering").map((key) => renderCharacter(key, false)));
|
|
529
|
+
}
|
|
530
|
+
function resetCharacters() {
|
|
531
|
+
clearReactiveRecords(charRenderState, charImages, charErrors, charDragOver);
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
charRenderState,
|
|
535
|
+
charImages,
|
|
536
|
+
charErrors,
|
|
537
|
+
charDragOver,
|
|
538
|
+
characterKeys,
|
|
539
|
+
characterPrompt: characterPrompt$1,
|
|
540
|
+
onCharDragOver,
|
|
541
|
+
onCharDragLeave,
|
|
542
|
+
onCharDrop,
|
|
543
|
+
loadExistingCharacterImage,
|
|
544
|
+
refreshMissingCharacterImages,
|
|
545
|
+
renderCharacter,
|
|
546
|
+
generateAllCharacters,
|
|
547
|
+
resetCharacters
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/vue/composables/useDeckEditor.ts
|
|
552
|
+
var DECK_SAVE_DEBOUNCE_MS = 300;
|
|
553
|
+
function useDeckEditor({ api, filePath, effectiveScript, commitScript }) {
|
|
554
|
+
const isDeck = computed(() => isAllSlideDeck(effectiveScript.value));
|
|
555
|
+
const deckScriptInput = computed(() => effectiveScript.value);
|
|
556
|
+
let deckSaveTimer = null;
|
|
557
|
+
let pendingDeckScript = null;
|
|
558
|
+
function scheduleDeckSave(next) {
|
|
559
|
+
pendingDeckScript = next;
|
|
560
|
+
if (deckSaveTimer) clearTimeout(deckSaveTimer);
|
|
561
|
+
deckSaveTimer = setTimeout(() => {
|
|
562
|
+
flushDeckSave();
|
|
563
|
+
}, DECK_SAVE_DEBOUNCE_MS);
|
|
564
|
+
}
|
|
565
|
+
async function flushDeckSave() {
|
|
566
|
+
deckSaveTimer = null;
|
|
567
|
+
const next = pendingDeckScript;
|
|
568
|
+
pendingDeckScript = null;
|
|
569
|
+
if (!next || !filePath.value) return;
|
|
570
|
+
const response = await api.call("updateScript", {
|
|
571
|
+
filePath: filePath.value,
|
|
572
|
+
script: next
|
|
573
|
+
});
|
|
574
|
+
if (!response.ok) {
|
|
575
|
+
console.error("[presentMulmoScript] deck save failed:", response.error);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
commitScript(next);
|
|
579
|
+
}
|
|
580
|
+
function onDeckUpdate(next) {
|
|
581
|
+
scheduleDeckSave(next);
|
|
582
|
+
}
|
|
583
|
+
function flushPendingDeckSave() {
|
|
584
|
+
if (deckSaveTimer) {
|
|
585
|
+
clearTimeout(deckSaveTimer);
|
|
586
|
+
flushDeckSave();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
isDeck,
|
|
591
|
+
deckScriptInput,
|
|
592
|
+
onDeckUpdate,
|
|
593
|
+
flushPendingDeckSave
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
/** Reactive message bundle for the active host locale. The plugin carries its
|
|
597
|
+
* own translations (no host i18n dependency); it reads the locale off the
|
|
598
|
+
* injected `BrowserPluginRuntime.locale` ref and falls back to English.
|
|
599
|
+
* Same pattern as @mulmoclaude/html-plugin. */
|
|
600
|
+
var useT = createUseT({
|
|
190
601
|
de: {
|
|
191
602
|
beatCount: (count) => count === 1 ? `${count} Beat` : `${count} Beats`,
|
|
192
603
|
movie: "Video",
|
|
@@ -443,267 +854,519 @@ var MESSAGES = {
|
|
|
443
854
|
close: "关闭",
|
|
444
855
|
cancel: "取消"
|
|
445
856
|
}
|
|
446
|
-
};
|
|
447
|
-
function isSupportedLocale(value) {
|
|
448
|
-
return Object.hasOwn(MESSAGES, value);
|
|
449
|
-
}
|
|
450
|
-
/** Reactive message bundle for the active host locale. The plugin carries its
|
|
451
|
-
* own translations (no host i18n dependency); it reads the locale off the
|
|
452
|
-
* injected `BrowserPluginRuntime.locale` ref and falls back to English.
|
|
453
|
-
* Same pattern as @mulmoclaude/html-plugin. */
|
|
454
|
-
function useT() {
|
|
455
|
-
const locale = inject(PLUGIN_RUNTIME_KEY, void 0)?.locale ?? ref("en");
|
|
456
|
-
return computed(() => isSupportedLocale(locale.value) ? MESSAGES[locale.value] : MESSAGES.en);
|
|
457
|
-
}
|
|
857
|
+
});
|
|
458
858
|
//#endregion
|
|
459
|
-
//#region src/vue/
|
|
460
|
-
var _hoisted_1$
|
|
461
|
-
var _hoisted_2$
|
|
462
|
-
var _hoisted_3$
|
|
463
|
-
var _hoisted_4 = {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
};
|
|
467
|
-
var _hoisted_5 = {
|
|
859
|
+
//#region src/vue/components/BeatLightbox.vue?vue&type=script&setup=true&lang.ts
|
|
860
|
+
var _hoisted_1$4 = ["title"];
|
|
861
|
+
var _hoisted_2$4 = { class: "flex items-center gap-4" };
|
|
862
|
+
var _hoisted_3$4 = ["disabled"];
|
|
863
|
+
var _hoisted_4$3 = { class: "flex flex-col items-center" };
|
|
864
|
+
var _hoisted_5$3 = ["src"];
|
|
865
|
+
var _hoisted_6$3 = {
|
|
468
866
|
key: 0,
|
|
469
|
-
class: "
|
|
470
|
-
"data-testid": "mulmo-script-description"
|
|
471
|
-
};
|
|
472
|
-
var _hoisted_6 = { class: "flex items-center gap-3 mt-1 text-xs text-gray-400" };
|
|
473
|
-
var _hoisted_7 = { key: 0 };
|
|
474
|
-
var _hoisted_8 = {
|
|
475
|
-
key: 1,
|
|
476
|
-
class: "truncate"
|
|
867
|
+
class: "relative w-full h-1"
|
|
477
868
|
};
|
|
478
|
-
var
|
|
479
|
-
var
|
|
480
|
-
|
|
481
|
-
"title",
|
|
482
|
-
"aria-label"
|
|
483
|
-
];
|
|
484
|
-
var _hoisted_11 = ["disabled"];
|
|
485
|
-
var _hoisted_12 = ["title", "aria-label"];
|
|
486
|
-
var _hoisted_13 = ["disabled"];
|
|
487
|
-
var _hoisted_14 = {
|
|
869
|
+
var _hoisted_7$3 = { class: "flex gap-1 h-full" };
|
|
870
|
+
var _hoisted_8$3 = ["onClick"];
|
|
871
|
+
var _hoisted_9$3 = {
|
|
488
872
|
key: 0,
|
|
489
|
-
class: "
|
|
490
|
-
viewBox: "0 0 24 24",
|
|
491
|
-
fill: "none"
|
|
873
|
+
class: "absolute bottom-full mb-2 left-1/2 -translate-x-1/2 z-20 px-2 py-1 rounded bg-black/90 text-white text-xs leading-tight w-48 max-h-[53px] overflow-hidden opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity"
|
|
492
874
|
};
|
|
493
|
-
var
|
|
494
|
-
var
|
|
495
|
-
var _hoisted_17 = ["title", "aria-label"];
|
|
496
|
-
var _hoisted_18 = ["disabled"];
|
|
497
|
-
var _hoisted_19 = {
|
|
875
|
+
var _hoisted_10$3 = ["disabled"];
|
|
876
|
+
var _hoisted_11$3 = {
|
|
498
877
|
key: 0,
|
|
499
|
-
class: "
|
|
500
|
-
viewBox: "0 0 24 24",
|
|
501
|
-
fill: "none"
|
|
878
|
+
class: "relative w-screen flex justify-center px-16"
|
|
502
879
|
};
|
|
503
|
-
var
|
|
504
|
-
var _hoisted_21 = {
|
|
880
|
+
var _hoisted_12$3 = {
|
|
505
881
|
key: 0,
|
|
506
|
-
|
|
507
|
-
class: "bg-red-50 border border-red-200 text-red-800 text-xs px-3 py-2 mx-4 mt-3 mb-1 rounded flex items-start gap-2"
|
|
508
|
-
};
|
|
509
|
-
var _hoisted_22 = { class: "flex-1 min-w-0" };
|
|
510
|
-
var _hoisted_23 = { class: "font-medium" };
|
|
511
|
-
var _hoisted_24 = { class: "break-words whitespace-pre-wrap mt-0.5" };
|
|
512
|
-
var _hoisted_25 = ["disabled"];
|
|
513
|
-
var _hoisted_26 = {
|
|
514
|
-
key: 1,
|
|
515
|
-
class: "border-b border-gray-100 shrink-0 px-4 py-3"
|
|
882
|
+
class: "max-w-[80vw] text-center text-white leading-relaxed text-[clamp(0.8rem,1.76vw,1.6rem)]"
|
|
516
883
|
};
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
var
|
|
520
|
-
|
|
521
|
-
|
|
884
|
+
//#endregion
|
|
885
|
+
//#region src/vue/components/BeatLightbox.vue
|
|
886
|
+
var BeatLightbox_default = /* @__PURE__ */ defineComponent({
|
|
887
|
+
__name: "BeatLightbox",
|
|
888
|
+
props: {
|
|
889
|
+
lightbox: {},
|
|
890
|
+
beatCount: {},
|
|
891
|
+
beatTexts: {},
|
|
892
|
+
hasPrev: { type: Boolean },
|
|
893
|
+
hasNext: { type: Boolean },
|
|
894
|
+
playingAudioIndex: {},
|
|
895
|
+
audioProgress: {},
|
|
896
|
+
hasCurrentAudio: { type: Boolean }
|
|
897
|
+
},
|
|
898
|
+
emits: [
|
|
899
|
+
"close",
|
|
900
|
+
"move",
|
|
901
|
+
"jump",
|
|
902
|
+
"playAudio"
|
|
903
|
+
],
|
|
904
|
+
setup(__props, { emit: __emit }) {
|
|
905
|
+
const m = useT();
|
|
906
|
+
const emit = __emit;
|
|
907
|
+
return (_ctx, _cache) => {
|
|
908
|
+
return openBlock(), createElementBlock("div", {
|
|
909
|
+
class: "fixed inset-0 z-50 bg-black/80 overflow-y-auto",
|
|
910
|
+
onClick: _cache[5] || (_cache[5] = ($event) => emit("close"))
|
|
911
|
+
}, [createElementVNode("button", {
|
|
912
|
+
class: "fixed top-2 right-4 z-10 text-white/60 hover:text-white text-3xl leading-none",
|
|
913
|
+
title: unref(m).close,
|
|
914
|
+
onClick: _cache[0] || (_cache[0] = withModifiers(($event) => emit("close"), ["stop"]))
|
|
915
|
+
}, "✕", 8, _hoisted_1$4), createElementVNode("div", {
|
|
916
|
+
class: "flex flex-col items-center gap-4 pt-4 pb-8",
|
|
917
|
+
onClick: _cache[4] || (_cache[4] = withModifiers(() => {}, ["stop"]))
|
|
918
|
+
}, [createElementVNode("div", _hoisted_2$4, [
|
|
919
|
+
!__props.lightbox.isCharacter ? (openBlock(), createElementBlock("button", {
|
|
920
|
+
key: 0,
|
|
921
|
+
class: "text-white/60 hover:text-white disabled:opacity-20 text-5xl leading-none",
|
|
922
|
+
disabled: !__props.hasPrev,
|
|
923
|
+
onClick: _cache[1] || (_cache[1] = ($event) => emit("move", -1))
|
|
924
|
+
}, " ‹ ", 8, _hoisted_3$4)) : createCommentVNode("", true),
|
|
925
|
+
createElementVNode("div", _hoisted_4$3, [createElementVNode("img", {
|
|
926
|
+
src: __props.lightbox.src,
|
|
927
|
+
class: "max-w-[80vw] max-h-[85vh] object-contain rounded shadow-2xl"
|
|
928
|
+
}, null, 8, _hoisted_5$3), !__props.lightbox.isCharacter && __props.beatCount > 1 ? (openBlock(), createElementBlock("div", _hoisted_6$3, [createElementVNode("div", _hoisted_7$3, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.beatCount, (i) => {
|
|
929
|
+
return openBlock(), createElementBlock("div", {
|
|
930
|
+
key: i - 1,
|
|
931
|
+
class: normalizeClass(["group flex-1 cursor-pointer relative transition-colors", i - 1 === __props.lightbox.index ? "bg-white/80 hover:bg-white" : i - 1 < __props.lightbox.index ? "bg-white/40 hover:bg-white/60" : "bg-white/20 hover:bg-white/40"]),
|
|
932
|
+
onClick: ($event) => emit("jump", i - 1)
|
|
933
|
+
}, [_cache[6] || (_cache[6] = createElementVNode("span", { class: "absolute -inset-y-3 inset-x-0" }, null, -1)), unref(beatTooltip)(__props.beatTexts[i - 1]) ? (openBlock(), createElementBlock("div", _hoisted_9$3, toDisplayString(unref(beatTooltip)(__props.beatTexts[i - 1])), 1)) : createCommentVNode("", true)], 10, _hoisted_8$3);
|
|
934
|
+
}), 128))]), __props.playingAudioIndex !== null && __props.playingAudioIndex === __props.lightbox.index ? (openBlock(), createElementBlock("div", {
|
|
935
|
+
key: 0,
|
|
936
|
+
class: "absolute top-1/2 w-3.5 h-3.5 rounded-full bg-white shadow ring-2 ring-black/30 -translate-y-1/2 -translate-x-1/2 pointer-events-none",
|
|
937
|
+
style: normalizeStyle({ left: `${(__props.lightbox.index + __props.audioProgress) / __props.beatCount * 100}%` })
|
|
938
|
+
}, null, 4)) : createCommentVNode("", true)])) : createCommentVNode("", true)]),
|
|
939
|
+
!__props.lightbox.isCharacter ? (openBlock(), createElementBlock("button", {
|
|
940
|
+
key: 1,
|
|
941
|
+
class: "text-white/60 hover:text-white disabled:opacity-20 text-5xl leading-none",
|
|
942
|
+
disabled: !__props.hasNext,
|
|
943
|
+
onClick: _cache[2] || (_cache[2] = ($event) => emit("move", 1))
|
|
944
|
+
}, " › ", 8, _hoisted_10$3)) : createCommentVNode("", true)
|
|
945
|
+
]), __props.lightbox.text || __props.hasCurrentAudio ? (openBlock(), createElementBlock("div", _hoisted_11$3, [__props.lightbox.text ? (openBlock(), createElementBlock("p", _hoisted_12$3, toDisplayString(__props.lightbox.text), 1)) : createCommentVNode("", true), __props.hasCurrentAudio ? (openBlock(), createElementBlock("button", {
|
|
946
|
+
key: 1,
|
|
947
|
+
class: "absolute top-0 right-4 text-sm px-3 py-1 rounded border border-white/60 text-white/60 hover:bg-white/20",
|
|
948
|
+
onClick: _cache[3] || (_cache[3] = ($event) => emit("playAudio", __props.lightbox.index))
|
|
949
|
+
}, toDisplayString(__props.playingAudioIndex === __props.lightbox.index ? unref(m).stop : unref(m).play), 1)) : createCommentVNode("", true)])) : createCommentVNode("", true)])]);
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region src/vue/components/CharacterStrip.vue?vue&type=script&setup=true&lang.ts
|
|
955
|
+
var _hoisted_1$3 = { class: "border-b border-gray-100 shrink-0 px-4 py-3" };
|
|
956
|
+
var _hoisted_2$3 = { class: "flex items-center justify-between mb-2" };
|
|
957
|
+
var _hoisted_3$3 = { class: "text-xs font-semibold text-gray-500 uppercase tracking-wide" };
|
|
958
|
+
var _hoisted_4$2 = ["disabled"];
|
|
959
|
+
var _hoisted_5$2 = { class: "flex gap-3 flex-wrap" };
|
|
960
|
+
var _hoisted_6$2 = [
|
|
522
961
|
"onDragover",
|
|
523
962
|
"onDragleave",
|
|
524
963
|
"onDrop"
|
|
525
964
|
];
|
|
526
|
-
var
|
|
965
|
+
var _hoisted_7$2 = [
|
|
527
966
|
"src",
|
|
528
967
|
"alt",
|
|
529
968
|
"onClick"
|
|
530
969
|
];
|
|
531
|
-
var
|
|
970
|
+
var _hoisted_8$2 = {
|
|
532
971
|
key: 1,
|
|
533
972
|
class: "animate-spin w-4 h-4 text-green-400",
|
|
534
973
|
viewBox: "0 0 24 24",
|
|
535
974
|
fill: "none"
|
|
536
975
|
};
|
|
537
|
-
var
|
|
976
|
+
var _hoisted_9$2 = {
|
|
538
977
|
key: 2,
|
|
539
978
|
class: "text-xs text-red-400 text-center px-1"
|
|
540
979
|
};
|
|
541
|
-
var
|
|
980
|
+
var _hoisted_10$2 = {
|
|
542
981
|
key: 3,
|
|
543
982
|
class: "text-xs text-gray-300 text-center px-1 leading-tight"
|
|
544
983
|
};
|
|
545
|
-
var
|
|
984
|
+
var _hoisted_11$2 = {
|
|
546
985
|
key: 4,
|
|
547
986
|
class: "absolute bottom-0 inset-x-0 text-center text-xs text-gray-400 bg-white/70 py-0.5 pointer-events-none"
|
|
548
987
|
};
|
|
549
|
-
var
|
|
988
|
+
var _hoisted_12$2 = {
|
|
550
989
|
key: 5,
|
|
551
990
|
class: "absolute inset-0 flex items-center justify-center bg-blue-50/80 pointer-events-none"
|
|
552
991
|
};
|
|
553
|
-
var
|
|
554
|
-
var
|
|
555
|
-
var
|
|
992
|
+
var _hoisted_13$1 = { class: "text-xs text-blue-500 font-medium" };
|
|
993
|
+
var _hoisted_14$1 = ["disabled", "onClick"];
|
|
994
|
+
var _hoisted_15$1 = {
|
|
556
995
|
key: 0,
|
|
557
996
|
class: "inline-block animate-spin"
|
|
558
997
|
};
|
|
559
|
-
var
|
|
560
|
-
var
|
|
561
|
-
var
|
|
998
|
+
var _hoisted_16$1 = { key: 1 };
|
|
999
|
+
var _hoisted_17$1 = ["disabled", "onClick"];
|
|
1000
|
+
var _hoisted_18$1 = {
|
|
562
1001
|
key: 0,
|
|
563
1002
|
class: "animate-spin w-3 h-3",
|
|
564
1003
|
viewBox: "0 0 24 24",
|
|
565
1004
|
fill: "none"
|
|
566
1005
|
};
|
|
567
|
-
var
|
|
568
|
-
var
|
|
569
|
-
|
|
1006
|
+
var _hoisted_19$1 = { key: 1 };
|
|
1007
|
+
var _hoisted_20$1 = { class: "text-xs text-gray-600 text-center truncate w-full" };
|
|
1008
|
+
//#endregion
|
|
1009
|
+
//#region src/vue/components/CharacterStrip.vue
|
|
1010
|
+
var CharacterStrip_default = /* @__PURE__ */ defineComponent({
|
|
1011
|
+
__name: "CharacterStrip",
|
|
1012
|
+
props: {
|
|
1013
|
+
characterKeys: {},
|
|
1014
|
+
images: {},
|
|
1015
|
+
thumbnails: {},
|
|
1016
|
+
renderState: {},
|
|
1017
|
+
errors: {},
|
|
1018
|
+
dragOver: {},
|
|
1019
|
+
movieGenerating: { type: Boolean },
|
|
1020
|
+
anyBeatRendering: { type: Boolean }
|
|
1021
|
+
},
|
|
1022
|
+
emits: [
|
|
1023
|
+
"generateAll",
|
|
1024
|
+
"charDragOver",
|
|
1025
|
+
"charDragLeave",
|
|
1026
|
+
"charDrop",
|
|
1027
|
+
"openLightbox",
|
|
1028
|
+
"renderCharacter"
|
|
1029
|
+
],
|
|
1030
|
+
setup(__props, { emit: __emit }) {
|
|
1031
|
+
const m = useT();
|
|
1032
|
+
const props = __props;
|
|
1033
|
+
const emit = __emit;
|
|
1034
|
+
const busy = computed(() => props.movieGenerating || props.anyBeatRendering);
|
|
1035
|
+
return (_ctx, _cache) => {
|
|
1036
|
+
return openBlock(), createElementBlock("div", _hoisted_1$3, [createElementVNode("div", _hoisted_2$3, [createElementVNode("span", _hoisted_3$3, toDisplayString(unref(m).characters), 1), createElementVNode("button", {
|
|
1037
|
+
class: "px-2 py-0.5 text-xs rounded border border-gray-300 text-gray-500 hover:bg-gray-50 disabled:opacity-50",
|
|
1038
|
+
disabled: busy.value || __props.characterKeys.every((key) => __props.renderState[key] === "rendering"),
|
|
1039
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("generateAll"))
|
|
1040
|
+
}, toDisplayString(unref(m).generateAll), 9, _hoisted_4$2)]), createElementVNode("div", _hoisted_5$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.characterKeys, (key) => {
|
|
1041
|
+
return openBlock(), createElementBlock("div", {
|
|
1042
|
+
key,
|
|
1043
|
+
class: "flex flex-col items-center gap-1 w-36"
|
|
1044
|
+
}, [createElementVNode("div", {
|
|
1045
|
+
class: normalizeClass(["relative w-36 h-36 rounded-lg border overflow-hidden bg-gray-50 flex items-center justify-center transition-colors", __props.dragOver[key] ? "border-blue-400 bg-blue-50" : "border-gray-200"]),
|
|
1046
|
+
onDragover: ($event) => emit("charDragOver", $event, key),
|
|
1047
|
+
onDragleave: ($event) => emit("charDragLeave", key),
|
|
1048
|
+
onDrop: ($event) => emit("charDrop", $event, key)
|
|
1049
|
+
}, [
|
|
1050
|
+
__props.thumbnails[key] ? (openBlock(), createElementBlock("img", {
|
|
1051
|
+
key: 0,
|
|
1052
|
+
src: __props.thumbnails[key],
|
|
1053
|
+
class: "w-full h-full object-cover cursor-zoom-in",
|
|
1054
|
+
alt: key,
|
|
1055
|
+
onClick: ($event) => emit("openLightbox", key)
|
|
1056
|
+
}, null, 8, _hoisted_7$2)) : __props.renderState[key] === "rendering" ? (openBlock(), createElementBlock("svg", _hoisted_8$2, [..._cache[1] || (_cache[1] = [createElementVNode("circle", {
|
|
1057
|
+
class: "opacity-25",
|
|
1058
|
+
cx: "12",
|
|
1059
|
+
cy: "12",
|
|
1060
|
+
r: "10",
|
|
1061
|
+
stroke: "currentColor",
|
|
1062
|
+
"stroke-width": "4"
|
|
1063
|
+
}, null, -1), createElementVNode("path", {
|
|
1064
|
+
class: "opacity-75",
|
|
1065
|
+
fill: "currentColor",
|
|
1066
|
+
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1067
|
+
}, null, -1)])])) : __props.renderState[key] === "error" ? (openBlock(), createElementBlock("span", _hoisted_9$2, toDisplayString(__props.errors[key]), 1)) : (openBlock(), createElementBlock("span", _hoisted_10$2, toDisplayString(unref(characterPrompt)(__props.images, key)), 1)),
|
|
1068
|
+
!__props.dragOver[key] ? (openBlock(), createElementBlock("div", _hoisted_11$2, toDisplayString(unref(m).orDropImage), 1)) : createCommentVNode("", true),
|
|
1069
|
+
__props.dragOver[key] ? (openBlock(), createElementBlock("div", _hoisted_12$2, [createElementVNode("span", _hoisted_13$1, toDisplayString(unref(m).drop), 1)])) : createCommentVNode("", true),
|
|
1070
|
+
__props.thumbnails[key] && __props.renderState[key] !== "rendering" ? (openBlock(), createElementBlock("button", {
|
|
1071
|
+
key: 6,
|
|
1072
|
+
class: normalizeClass(["absolute top-0.5 right-0.5 px-1 py-0.5 text-xs rounded border bg-white", busy.value ? "border-yellow-400 text-yellow-500 cursor-not-allowed" : "border-gray-400 text-gray-600 hover:bg-gray-50"]),
|
|
1073
|
+
disabled: busy.value,
|
|
1074
|
+
onClick: withModifiers(($event) => emit("renderCharacter", key, true), ["stop"])
|
|
1075
|
+
}, [busy.value ? (openBlock(), createElementBlock("span", _hoisted_15$1, "↺")) : (openBlock(), createElementBlock("span", _hoisted_16$1, "↺"))], 10, _hoisted_14$1)) : !__props.thumbnails[key] && __props.renderState[key] !== "rendering" ? (openBlock(), createElementBlock("button", {
|
|
1076
|
+
key: 7,
|
|
1077
|
+
class: normalizeClass(["absolute top-0.5 right-0.5 px-1 py-0.5 text-xs rounded border bg-white", busy.value ? "border-yellow-400 text-yellow-500 cursor-not-allowed" : "border-blue-400 text-blue-600 hover:bg-blue-50"]),
|
|
1078
|
+
disabled: busy.value,
|
|
1079
|
+
onClick: withModifiers(($event) => emit("renderCharacter", key, false), ["stop"])
|
|
1080
|
+
}, [busy.value ? (openBlock(), createElementBlock("svg", _hoisted_18$1, [..._cache[2] || (_cache[2] = [createElementVNode("circle", {
|
|
1081
|
+
class: "opacity-25",
|
|
1082
|
+
cx: "12",
|
|
1083
|
+
cy: "12",
|
|
1084
|
+
r: "10",
|
|
1085
|
+
stroke: "currentColor",
|
|
1086
|
+
"stroke-width": "4"
|
|
1087
|
+
}, null, -1), createElementVNode("path", {
|
|
1088
|
+
class: "opacity-75",
|
|
1089
|
+
fill: "currentColor",
|
|
1090
|
+
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1091
|
+
}, null, -1)])])) : (openBlock(), createElementBlock("span", _hoisted_19$1, toDisplayString(unref(m).gen), 1))], 10, _hoisted_17$1)) : createCommentVNode("", true)
|
|
1092
|
+
], 42, _hoisted_6$2), createElementVNode("span", _hoisted_20$1, toDisplayString(key), 1)]);
|
|
1093
|
+
}), 128))])]);
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
//#endregion
|
|
1098
|
+
//#region src/vue/components/MulmoScriptToolbar.vue?vue&type=script&setup=true&lang.ts
|
|
1099
|
+
var _hoisted_1$2 = { class: "ml-4 shrink-0 flex items-center gap-2" };
|
|
1100
|
+
var _hoisted_2$2 = [
|
|
1101
|
+
"disabled",
|
|
1102
|
+
"title",
|
|
1103
|
+
"aria-label"
|
|
1104
|
+
];
|
|
1105
|
+
var _hoisted_3$2 = ["disabled"];
|
|
1106
|
+
var _hoisted_4$1 = ["title", "aria-label"];
|
|
1107
|
+
var _hoisted_5$1 = ["disabled"];
|
|
1108
|
+
var _hoisted_6$1 = {
|
|
1109
|
+
key: 0,
|
|
1110
|
+
class: "animate-spin w-4 h-4 shrink-0",
|
|
1111
|
+
viewBox: "0 0 24 24",
|
|
1112
|
+
fill: "none"
|
|
1113
|
+
};
|
|
1114
|
+
var _hoisted_7$1 = { key: 1 };
|
|
1115
|
+
var _hoisted_8$1 = ["disabled"];
|
|
1116
|
+
var _hoisted_9$1 = ["title", "aria-label"];
|
|
1117
|
+
var _hoisted_10$1 = ["disabled"];
|
|
1118
|
+
var _hoisted_11$1 = {
|
|
1119
|
+
key: 0,
|
|
1120
|
+
class: "animate-spin w-4 h-4 shrink-0",
|
|
1121
|
+
viewBox: "0 0 24 24",
|
|
1122
|
+
fill: "none"
|
|
1123
|
+
};
|
|
1124
|
+
var _hoisted_12$1 = { key: 1 };
|
|
1125
|
+
//#endregion
|
|
1126
|
+
//#region src/vue/components/MulmoScriptToolbar.vue
|
|
1127
|
+
var MulmoScriptToolbar_default = /* @__PURE__ */ defineComponent({
|
|
1128
|
+
__name: "MulmoScriptToolbar",
|
|
1129
|
+
props: {
|
|
1130
|
+
moviePath: {},
|
|
1131
|
+
movieGenerating: { type: Boolean },
|
|
1132
|
+
movieDownloading: { type: Boolean },
|
|
1133
|
+
isPlayReady: { type: Boolean },
|
|
1134
|
+
canFetchMedia: { type: Boolean },
|
|
1135
|
+
pdfPath: {},
|
|
1136
|
+
pdfGenerating: { type: Boolean },
|
|
1137
|
+
pdfDownloading: { type: Boolean }
|
|
1138
|
+
},
|
|
1139
|
+
emits: [
|
|
1140
|
+
"play",
|
|
1141
|
+
"generateMovie",
|
|
1142
|
+
"downloadMovie",
|
|
1143
|
+
"generatePdf",
|
|
1144
|
+
"downloadPdf"
|
|
1145
|
+
],
|
|
1146
|
+
setup(__props, { emit: __emit }) {
|
|
1147
|
+
const m = useT();
|
|
1148
|
+
const emit = __emit;
|
|
1149
|
+
return (_ctx, _cache) => {
|
|
1150
|
+
return openBlock(), createElementBlock("div", _hoisted_1$2, [
|
|
1151
|
+
__props.moviePath && !__props.movieGenerating ? (openBlock(), createElementBlock("button", {
|
|
1152
|
+
key: 0,
|
|
1153
|
+
class: "h-8 w-8 flex items-center justify-center rounded border border-green-600 text-green-600 hover:bg-green-50 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
1154
|
+
disabled: !__props.isPlayReady,
|
|
1155
|
+
title: unref(m).playPresentation,
|
|
1156
|
+
"aria-label": unref(m).playPresentation,
|
|
1157
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("play"))
|
|
1158
|
+
}, [..._cache[7] || (_cache[7] = [createElementVNode("span", { class: "material-icons text-base" }, "play_arrow", -1)])], 8, _hoisted_2$2)) : createCommentVNode("", true),
|
|
1159
|
+
__props.moviePath && !__props.movieGenerating && __props.canFetchMedia ? (openBlock(), createElementBlock("button", {
|
|
1160
|
+
key: 1,
|
|
1161
|
+
class: "h-8 px-2.5 flex items-center gap-1 rounded bg-green-600 hover:bg-green-700 text-white text-sm disabled:opacity-60 disabled:cursor-not-allowed transition-colors",
|
|
1162
|
+
disabled: __props.movieDownloading,
|
|
1163
|
+
"data-testid": "mulmo-script-download-movie-button",
|
|
1164
|
+
onClick: _cache[1] || (_cache[1] = ($event) => emit("downloadMovie"))
|
|
1165
|
+
}, [_cache[8] || (_cache[8] = createElementVNode("span", { class: "material-icons text-base" }, "download", -1)), createElementVNode("span", null, toDisplayString(unref(m).movie), 1)], 8, _hoisted_3$2)) : createCommentVNode("", true),
|
|
1166
|
+
__props.moviePath && !__props.movieGenerating ? (openBlock(), createElementBlock("button", {
|
|
1167
|
+
key: 2,
|
|
1168
|
+
class: "h-8 w-8 flex items-center justify-center rounded border border-gray-200 text-gray-600 hover:bg-gray-100 transition-colors",
|
|
1169
|
+
title: unref(m).regenerateMovie,
|
|
1170
|
+
"aria-label": unref(m).regenerateMovie,
|
|
1171
|
+
"data-testid": "mulmo-script-regenerate-movie-button",
|
|
1172
|
+
onClick: _cache[2] || (_cache[2] = ($event) => emit("generateMovie"))
|
|
1173
|
+
}, [..._cache[9] || (_cache[9] = [createElementVNode("span", { class: "material-icons text-base" }, "refresh", -1)])], 8, _hoisted_4$1)) : (openBlock(), createElementBlock("button", {
|
|
1174
|
+
key: 3,
|
|
1175
|
+
class: "h-8 px-2.5 flex items-center gap-1 text-sm rounded border border-gray-200 text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
1176
|
+
disabled: __props.movieGenerating,
|
|
1177
|
+
"data-testid": "mulmo-script-generate-movie-button",
|
|
1178
|
+
onClick: _cache[3] || (_cache[3] = ($event) => emit("generateMovie"))
|
|
1179
|
+
}, [__props.movieGenerating ? (openBlock(), createElementBlock("svg", _hoisted_6$1, [..._cache[10] || (_cache[10] = [createElementVNode("circle", {
|
|
1180
|
+
class: "opacity-25",
|
|
1181
|
+
cx: "12",
|
|
1182
|
+
cy: "12",
|
|
1183
|
+
r: "10",
|
|
1184
|
+
stroke: "currentColor",
|
|
1185
|
+
"stroke-width": "4"
|
|
1186
|
+
}, null, -1), createElementVNode("path", {
|
|
1187
|
+
class: "opacity-75",
|
|
1188
|
+
fill: "currentColor",
|
|
1189
|
+
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1190
|
+
}, null, -1)])])) : createCommentVNode("", true), __props.movieGenerating ? (openBlock(), createElementBlock("span", _hoisted_7$1, toDisplayString(unref(m).generating), 1)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [_cache[11] || (_cache[11] = createElementVNode("span", { class: "material-icons text-sm" }, "refresh", -1)), createElementVNode("span", null, toDisplayString(unref(m).movie), 1)], 64))], 8, _hoisted_5$1)),
|
|
1191
|
+
__props.pdfPath && !__props.pdfGenerating && __props.canFetchMedia ? (openBlock(), createElementBlock("button", {
|
|
1192
|
+
key: 4,
|
|
1193
|
+
class: "h-8 px-2.5 flex items-center gap-1 rounded bg-red-600 hover:bg-red-700 text-white text-sm disabled:opacity-60 disabled:cursor-not-allowed transition-colors",
|
|
1194
|
+
disabled: __props.pdfDownloading,
|
|
1195
|
+
"data-testid": "mulmo-script-download-pdf-button",
|
|
1196
|
+
onClick: _cache[4] || (_cache[4] = ($event) => emit("downloadPdf"))
|
|
1197
|
+
}, [_cache[12] || (_cache[12] = createElementVNode("span", { class: "material-icons text-base" }, "download", -1)), createElementVNode("span", null, toDisplayString(unref(m).pdf), 1)], 8, _hoisted_8$1)) : createCommentVNode("", true),
|
|
1198
|
+
__props.pdfPath && !__props.pdfGenerating ? (openBlock(), createElementBlock("button", {
|
|
1199
|
+
key: 5,
|
|
1200
|
+
class: "h-8 w-8 flex items-center justify-center rounded border border-gray-200 text-gray-600 hover:bg-gray-100 transition-colors",
|
|
1201
|
+
title: unref(m).regeneratePdf,
|
|
1202
|
+
"aria-label": unref(m).regeneratePdf,
|
|
1203
|
+
"data-testid": "mulmo-script-regenerate-pdf-button",
|
|
1204
|
+
onClick: _cache[5] || (_cache[5] = ($event) => emit("generatePdf"))
|
|
1205
|
+
}, [..._cache[13] || (_cache[13] = [createElementVNode("span", { class: "material-icons text-base" }, "refresh", -1)])], 8, _hoisted_9$1)) : (openBlock(), createElementBlock("button", {
|
|
1206
|
+
key: 6,
|
|
1207
|
+
class: "h-8 px-2.5 flex items-center gap-1 text-sm rounded border border-gray-200 text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
1208
|
+
disabled: __props.pdfGenerating,
|
|
1209
|
+
"data-testid": "mulmo-script-generate-pdf-button",
|
|
1210
|
+
onClick: _cache[6] || (_cache[6] = ($event) => emit("generatePdf"))
|
|
1211
|
+
}, [__props.pdfGenerating ? (openBlock(), createElementBlock("svg", _hoisted_11$1, [..._cache[14] || (_cache[14] = [createElementVNode("circle", {
|
|
1212
|
+
class: "opacity-25",
|
|
1213
|
+
cx: "12",
|
|
1214
|
+
cy: "12",
|
|
1215
|
+
r: "10",
|
|
1216
|
+
stroke: "currentColor",
|
|
1217
|
+
"stroke-width": "4"
|
|
1218
|
+
}, null, -1), createElementVNode("path", {
|
|
1219
|
+
class: "opacity-75",
|
|
1220
|
+
fill: "currentColor",
|
|
1221
|
+
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1222
|
+
}, null, -1)])])) : createCommentVNode("", true), __props.pdfGenerating ? (openBlock(), createElementBlock("span", _hoisted_12$1, toDisplayString(unref(m).generatingPdf), 1)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [_cache[15] || (_cache[15] = createElementVNode("span", { class: "material-icons text-sm" }, "picture_as_pdf", -1)), createElementVNode("span", null, toDisplayString(unref(m).pdf), 1)], 64))], 8, _hoisted_10$1))
|
|
1223
|
+
]);
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
//#endregion
|
|
1228
|
+
//#region src/vue/View.vue?vue&type=script&setup=true&lang.ts
|
|
1229
|
+
var _hoisted_1$1 = { class: "h-full bg-white flex flex-col overflow-hidden" };
|
|
1230
|
+
var _hoisted_2$1 = { class: "flex items-start justify-between px-6 py-4 border-b border-gray-100 shrink-0" };
|
|
1231
|
+
var _hoisted_3$1 = { class: "min-w-0 flex-1" };
|
|
1232
|
+
var _hoisted_4 = {
|
|
1233
|
+
class: "text-lg font-semibold text-gray-800 truncate",
|
|
1234
|
+
"data-testid": "mulmo-script-title"
|
|
1235
|
+
};
|
|
1236
|
+
var _hoisted_5 = {
|
|
1237
|
+
key: 0,
|
|
1238
|
+
class: "text-sm text-gray-500 mt-0.5 truncate",
|
|
1239
|
+
"data-testid": "mulmo-script-description"
|
|
1240
|
+
};
|
|
1241
|
+
var _hoisted_6 = { class: "flex items-center gap-3 mt-1 text-xs text-gray-400" };
|
|
1242
|
+
var _hoisted_7 = { key: 0 };
|
|
1243
|
+
var _hoisted_8 = {
|
|
1244
|
+
key: 1,
|
|
1245
|
+
class: "truncate"
|
|
1246
|
+
};
|
|
1247
|
+
var _hoisted_9 = {
|
|
1248
|
+
key: 0,
|
|
1249
|
+
"data-testid": "mulmo-script-movie-error-chip",
|
|
1250
|
+
class: "bg-red-50 border border-red-200 text-red-800 text-xs px-3 py-2 mx-4 mt-3 mb-1 rounded flex items-start gap-2"
|
|
1251
|
+
};
|
|
1252
|
+
var _hoisted_10 = { class: "flex-1 min-w-0" };
|
|
1253
|
+
var _hoisted_11 = { class: "font-medium" };
|
|
1254
|
+
var _hoisted_12 = { class: "break-words whitespace-pre-wrap mt-0.5" };
|
|
1255
|
+
var _hoisted_13 = ["disabled"];
|
|
1256
|
+
var _hoisted_14 = {
|
|
570
1257
|
key: 2,
|
|
571
1258
|
class: "flex-1 overflow-hidden",
|
|
572
1259
|
"data-testid": "mulmo-script-deck-editor"
|
|
573
1260
|
};
|
|
574
|
-
var
|
|
575
|
-
var
|
|
1261
|
+
var _hoisted_15 = { class: "flex gap-3 items-stretch" };
|
|
1262
|
+
var _hoisted_16 = [
|
|
576
1263
|
"onDragover",
|
|
577
1264
|
"onDragleave",
|
|
578
1265
|
"onDrop"
|
|
579
1266
|
];
|
|
580
|
-
var
|
|
581
|
-
var
|
|
1267
|
+
var _hoisted_17 = ["data-testid"];
|
|
1268
|
+
var _hoisted_18 = ["src", "data-testid"];
|
|
1269
|
+
var _hoisted_19 = [
|
|
582
1270
|
"title",
|
|
583
1271
|
"aria-label",
|
|
584
1272
|
"data-testid",
|
|
585
1273
|
"onClick"
|
|
586
1274
|
];
|
|
587
|
-
var
|
|
1275
|
+
var _hoisted_20 = [
|
|
588
1276
|
"src",
|
|
589
1277
|
"alt",
|
|
590
1278
|
"onClick"
|
|
591
1279
|
];
|
|
592
|
-
var
|
|
1280
|
+
var _hoisted_21 = [
|
|
593
1281
|
"title",
|
|
594
1282
|
"aria-label",
|
|
595
1283
|
"data-testid",
|
|
596
1284
|
"onClick"
|
|
597
1285
|
];
|
|
598
|
-
var
|
|
1286
|
+
var _hoisted_22 = {
|
|
599
1287
|
key: 0,
|
|
600
1288
|
class: "animate-spin w-5 h-5",
|
|
601
1289
|
viewBox: "0 0 24 24",
|
|
602
1290
|
fill: "none"
|
|
603
1291
|
};
|
|
604
|
-
var
|
|
1292
|
+
var _hoisted_23 = {
|
|
605
1293
|
key: 1,
|
|
606
1294
|
class: "material-icons text-3xl"
|
|
607
1295
|
};
|
|
608
|
-
var
|
|
609
|
-
var
|
|
1296
|
+
var _hoisted_24 = ["disabled", "onClick"];
|
|
1297
|
+
var _hoisted_25 = {
|
|
610
1298
|
key: 3,
|
|
611
1299
|
class: "w-full aspect-video flex flex-col items-center justify-center gap-1 p-2"
|
|
612
1300
|
};
|
|
613
|
-
var
|
|
614
|
-
var
|
|
1301
|
+
var _hoisted_26 = { class: "text-xs text-green-500" };
|
|
1302
|
+
var _hoisted_27 = {
|
|
615
1303
|
key: 1,
|
|
616
1304
|
class: "text-xs text-red-400 text-center"
|
|
617
1305
|
};
|
|
618
|
-
var
|
|
1306
|
+
var _hoisted_28 = {
|
|
619
1307
|
key: 0,
|
|
620
1308
|
class: "text-xs text-gray-400 text-center italic leading-relaxed px-1"
|
|
621
1309
|
};
|
|
622
|
-
var
|
|
1310
|
+
var _hoisted_29 = {
|
|
623
1311
|
key: 1,
|
|
624
1312
|
class: "text-xs text-gray-300"
|
|
625
1313
|
};
|
|
626
|
-
var
|
|
1314
|
+
var _hoisted_30 = {
|
|
627
1315
|
key: 2,
|
|
628
1316
|
class: "absolute inset-0 flex items-center justify-center bg-blue-50/80 pointer-events-none"
|
|
629
1317
|
};
|
|
630
|
-
var
|
|
631
|
-
var
|
|
1318
|
+
var _hoisted_31 = { class: "text-xs text-blue-500 font-medium" };
|
|
1319
|
+
var _hoisted_32 = {
|
|
632
1320
|
key: 3,
|
|
633
1321
|
class: "absolute bottom-0 inset-x-0 text-center text-xs text-gray-400 bg-white/70 py-0.5 pointer-events-none"
|
|
634
1322
|
};
|
|
635
|
-
var
|
|
636
|
-
var
|
|
637
|
-
var
|
|
638
|
-
var
|
|
639
|
-
var
|
|
640
|
-
var
|
|
1323
|
+
var _hoisted_33 = ["onClick"];
|
|
1324
|
+
var _hoisted_34 = { class: "flex flex-col flex-1 min-w-0 px-2 py-1.5" };
|
|
1325
|
+
var _hoisted_35 = { class: "text-sm text-gray-800 leading-relaxed" };
|
|
1326
|
+
var _hoisted_36 = { class: "flex justify-between mt-auto pt-1" };
|
|
1327
|
+
var _hoisted_37 = { class: "flex items-center gap-1" };
|
|
1328
|
+
var _hoisted_38 = {
|
|
641
1329
|
key: 0,
|
|
642
1330
|
class: "animate-spin w-3 h-3 text-green-400",
|
|
643
1331
|
viewBox: "0 0 24 24",
|
|
644
1332
|
fill: "none"
|
|
645
1333
|
};
|
|
646
|
-
var
|
|
647
|
-
var
|
|
648
|
-
var
|
|
649
|
-
var
|
|
650
|
-
var
|
|
1334
|
+
var _hoisted_39 = ["onClick"];
|
|
1335
|
+
var _hoisted_40 = ["title"];
|
|
1336
|
+
var _hoisted_41 = ["disabled", "onClick"];
|
|
1337
|
+
var _hoisted_42 = ["onClick"];
|
|
1338
|
+
var _hoisted_43 = [
|
|
651
1339
|
"title",
|
|
652
1340
|
"data-testid",
|
|
653
1341
|
"onClick"
|
|
654
1342
|
];
|
|
655
|
-
var
|
|
1343
|
+
var _hoisted_44 = {
|
|
656
1344
|
key: 0,
|
|
657
1345
|
class: "border-t border-gray-100"
|
|
658
1346
|
};
|
|
659
|
-
var
|
|
660
|
-
var
|
|
661
|
-
var
|
|
1347
|
+
var _hoisted_45 = ["onUpdate:modelValue", "data-testid"];
|
|
1348
|
+
var _hoisted_46 = { class: "flex items-center justify-end gap-2 px-2 pb-2" };
|
|
1349
|
+
var _hoisted_47 = {
|
|
662
1350
|
key: 0,
|
|
663
1351
|
class: "text-xs text-red-600",
|
|
664
1352
|
role: "alert"
|
|
665
1353
|
};
|
|
666
|
-
var
|
|
1354
|
+
var _hoisted_48 = [
|
|
667
1355
|
"disabled",
|
|
668
1356
|
"data-testid",
|
|
669
1357
|
"onClick"
|
|
670
1358
|
];
|
|
671
|
-
var
|
|
1359
|
+
var _hoisted_49 = {
|
|
672
1360
|
key: 0,
|
|
673
1361
|
class: "flex items-center justify-center h-32 text-gray-400 text-sm"
|
|
674
1362
|
};
|
|
675
|
-
var
|
|
676
|
-
var
|
|
677
|
-
var
|
|
678
|
-
var
|
|
679
|
-
var
|
|
680
|
-
var _hoisted_86 = ["title"];
|
|
681
|
-
var _hoisted_87 = { class: "flex items-center gap-4" };
|
|
682
|
-
var _hoisted_88 = ["disabled"];
|
|
683
|
-
var _hoisted_89 = { class: "flex flex-col items-center" };
|
|
684
|
-
var _hoisted_90 = ["src"];
|
|
685
|
-
var _hoisted_91 = {
|
|
686
|
-
key: 0,
|
|
687
|
-
class: "relative w-full h-1"
|
|
688
|
-
};
|
|
689
|
-
var _hoisted_92 = { class: "flex gap-1 h-full" };
|
|
690
|
-
var _hoisted_93 = ["onClick"];
|
|
691
|
-
var _hoisted_94 = {
|
|
692
|
-
key: 0,
|
|
693
|
-
class: "absolute bottom-full mb-2 left-1/2 -translate-x-1/2 z-20 px-2 py-1 rounded bg-black/90 text-white text-xs leading-tight w-48 max-h-[53px] overflow-hidden opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity"
|
|
694
|
-
};
|
|
695
|
-
var _hoisted_95 = ["disabled"];
|
|
696
|
-
var _hoisted_96 = {
|
|
697
|
-
key: 0,
|
|
698
|
-
class: "relative w-screen flex justify-center px-16"
|
|
699
|
-
};
|
|
700
|
-
var _hoisted_97 = {
|
|
701
|
-
key: 0,
|
|
702
|
-
class: "max-w-[80vw] text-center text-white leading-relaxed text-[clamp(0.8rem,1.76vw,1.6rem)]"
|
|
703
|
-
};
|
|
1363
|
+
var _hoisted_50 = { class: "bottom-bar-wrapper" };
|
|
1364
|
+
var _hoisted_51 = { class: "editor-actions" };
|
|
1365
|
+
var _hoisted_52 = ["disabled"];
|
|
1366
|
+
var _hoisted_53 = ["title"];
|
|
1367
|
+
var _hoisted_54 = { class: "material-icons" };
|
|
704
1368
|
var SILENT_BEAT_DEFAULT_SEC = 3;
|
|
705
1369
|
var MS_PER_SECOND = 1e3;
|
|
706
|
-
var DECK_SAVE_DEBOUNCE_MS = 300;
|
|
707
1370
|
var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
708
1371
|
__name: "View",
|
|
709
1372
|
props: { selectedResult: {} },
|
|
@@ -728,39 +1391,34 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
728
1391
|
const beatSaveErrors = reactive({});
|
|
729
1392
|
const beatSaving = reactive({});
|
|
730
1393
|
const localOverrides = reactive({});
|
|
731
|
-
const movieGenerating = ref(false);
|
|
732
|
-
const movieDownloading = ref(false);
|
|
733
|
-
const moviePath = ref(null);
|
|
734
|
-
const movieError = ref(null);
|
|
735
|
-
const pdfGenerating = ref(false);
|
|
736
|
-
const pdfDownloading = ref(false);
|
|
737
|
-
const pdfPath = ref(null);
|
|
738
1394
|
const beatAudios = reactive({});
|
|
739
1395
|
const audioState = reactive({});
|
|
740
1396
|
const audioErrors = reactive({});
|
|
741
|
-
const beatMovies = reactive({});
|
|
742
|
-
const beatMovieUrls = reactive({});
|
|
743
|
-
const beatMovieOpen = reactive({});
|
|
744
|
-
const beatMovieLoading = reactive({});
|
|
745
1397
|
const playingAudio = ref(null);
|
|
746
1398
|
const silentPlaybackTimer = ref(null);
|
|
747
1399
|
const audioProgress = ref(0);
|
|
748
1400
|
const beatListEl = ref(null);
|
|
749
1401
|
const lightbox = ref(null);
|
|
750
|
-
const charRenderState = reactive({});
|
|
751
|
-
const charImages = reactive({});
|
|
752
|
-
const charErrors = reactive({});
|
|
753
|
-
const charDragOver = reactive({});
|
|
754
1402
|
const beatDragOver = reactive({});
|
|
755
1403
|
const anyBeatRendering = computed(() => Object.values(renderState).some((state) => state === "rendering"));
|
|
756
|
-
const characterKeys = computed(() => {
|
|
757
|
-
const imgs = script.value.imageParams?.images ?? {};
|
|
758
|
-
return Object.keys(imgs).filter((key) => imgs[key]?.type === "imagePrompt");
|
|
759
|
-
});
|
|
760
1404
|
const chatSessionId = computed(() => adapter.chatSessionId?.value);
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1405
|
+
const { moviePath, movieGenerating, movieDownloading, movieError, pdfPath, pdfGenerating, pdfDownloading, generateMovie, downloadMovie, refreshMoviePath, generatePdf, downloadPdf, refreshPdfPath, resetMedia } = useMediaExport({
|
|
1406
|
+
api,
|
|
1407
|
+
adapter,
|
|
1408
|
+
filePath,
|
|
1409
|
+
chatSessionId
|
|
1410
|
+
});
|
|
1411
|
+
const { beatMovies, beatMovieUrls, beatMovieOpen, beatMovieLoading, loadExistingBeatMovie, playBeatMovie, closeBeatMovie, invalidateBeatMovie, resetBeatMovies } = useBeatMovie({
|
|
1412
|
+
api,
|
|
1413
|
+
adapter,
|
|
1414
|
+
filePath
|
|
1415
|
+
});
|
|
1416
|
+
const { charRenderState, charImages, charErrors, charDragOver, characterKeys, onCharDragOver, onCharDragLeave, onCharDrop, loadExistingCharacterImage, refreshMissingCharacterImages, renderCharacter, generateAllCharacters, resetCharacters } = useCharacterImages({
|
|
1417
|
+
api,
|
|
1418
|
+
filePath,
|
|
1419
|
+
chatSessionId,
|
|
1420
|
+
getImages: () => script.value.imageParams?.images
|
|
1421
|
+
});
|
|
764
1422
|
function stopPlayingAudio() {
|
|
765
1423
|
stopAllPlayback();
|
|
766
1424
|
}
|
|
@@ -768,7 +1426,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
768
1426
|
stopPlayingAudio();
|
|
769
1427
|
lightbox.value = {
|
|
770
1428
|
src: renderedImages[index],
|
|
771
|
-
text: effectiveBeat(index).text,
|
|
1429
|
+
text: effectiveBeat$1(index).text,
|
|
772
1430
|
index
|
|
773
1431
|
};
|
|
774
1432
|
}
|
|
@@ -779,7 +1437,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
779
1437
|
const isPlayReady = computed(() => {
|
|
780
1438
|
if (beats.value.length === 0) return false;
|
|
781
1439
|
if (!renderedImages[0]) return false;
|
|
782
|
-
if (effectiveBeat(0).text && !beatAudios[0]) return false;
|
|
1440
|
+
if (effectiveBeat$1(0).text && !beatAudios[0]) return false;
|
|
783
1441
|
return true;
|
|
784
1442
|
});
|
|
785
1443
|
function playPresentation() {
|
|
@@ -800,19 +1458,19 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
800
1458
|
}
|
|
801
1459
|
function playBeat(index) {
|
|
802
1460
|
stopAllPlayback();
|
|
803
|
-
if (!Boolean(effectiveBeat(index).text)) {
|
|
1461
|
+
if (!Boolean(effectiveBeat$1(index).text)) {
|
|
804
1462
|
scheduleSilentAdvance(index);
|
|
805
1463
|
return;
|
|
806
1464
|
}
|
|
807
1465
|
if (beatAudios[index]) playAudio(index);
|
|
808
1466
|
}
|
|
809
1467
|
function scheduleSilentAdvance(index) {
|
|
810
|
-
const
|
|
1468
|
+
const seconds = resolveSilentAdvanceSeconds(effectiveBeat$1(index).duration, SILENT_BEAT_DEFAULT_SEC);
|
|
811
1469
|
const timer = setTimeout(() => {
|
|
812
1470
|
if (silentPlaybackTimer.value?.index !== index) return;
|
|
813
1471
|
silentPlaybackTimer.value = null;
|
|
814
1472
|
if (lightbox.value?.index === index) advanceFromBeat(index);
|
|
815
|
-
},
|
|
1473
|
+
}, seconds * MS_PER_SECOND);
|
|
816
1474
|
silentPlaybackTimer.value = {
|
|
817
1475
|
index,
|
|
818
1476
|
timer
|
|
@@ -834,6 +1492,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
834
1492
|
for (let i = lightbox.value.index + 1; i < beats.value.length; i++) if (renderedImages[i]) return true;
|
|
835
1493
|
return false;
|
|
836
1494
|
});
|
|
1495
|
+
const beatTexts = computed(() => beats.value.map((_, index) => effectiveBeat$1(index).text));
|
|
837
1496
|
function jumpToBeat(index) {
|
|
838
1497
|
if (!lightbox.value) return;
|
|
839
1498
|
if (index === lightbox.value.index) return;
|
|
@@ -842,10 +1501,6 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
842
1501
|
openLightbox(index);
|
|
843
1502
|
if (wasPlaying) playBeat(index);
|
|
844
1503
|
}
|
|
845
|
-
function beatTooltip(index) {
|
|
846
|
-
const text = effectiveBeat(index).text ?? "";
|
|
847
|
-
return text.length > 80 ? `${text.slice(0, 80)}…` : text;
|
|
848
|
-
}
|
|
849
1504
|
function lightboxMove(delta) {
|
|
850
1505
|
if (!lightbox.value) return;
|
|
851
1506
|
const total = beats.value.length;
|
|
@@ -868,31 +1523,8 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
868
1523
|
...script.value,
|
|
869
1524
|
beats: beats.value.map((beat, i) => localOverrides[i] ?? beat)
|
|
870
1525
|
}));
|
|
871
|
-
const scriptSourceText = computed(() =>
|
|
872
|
-
|
|
873
|
-
const deckScriptInput = computed(() => effectiveScript.value);
|
|
874
|
-
let deckSaveTimer = null;
|
|
875
|
-
let pendingDeckScript = null;
|
|
876
|
-
function scheduleDeckSave(next) {
|
|
877
|
-
pendingDeckScript = next;
|
|
878
|
-
if (deckSaveTimer) clearTimeout(deckSaveTimer);
|
|
879
|
-
deckSaveTimer = setTimeout(() => {
|
|
880
|
-
flushDeckSave();
|
|
881
|
-
}, DECK_SAVE_DEBOUNCE_MS);
|
|
882
|
-
}
|
|
883
|
-
async function flushDeckSave() {
|
|
884
|
-
deckSaveTimer = null;
|
|
885
|
-
const next = pendingDeckScript;
|
|
886
|
-
pendingDeckScript = null;
|
|
887
|
-
if (!next || !filePath.value) return;
|
|
888
|
-
const response = await api.call("updateScript", {
|
|
889
|
-
filePath: filePath.value,
|
|
890
|
-
script: next
|
|
891
|
-
});
|
|
892
|
-
if (!response.ok) {
|
|
893
|
-
console.error("[presentMulmoScript] deck save failed:", response.error);
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
1526
|
+
const scriptSourceText$1 = computed(() => scriptSourceText(effectiveScript.value));
|
|
1527
|
+
function commitScript(next) {
|
|
896
1528
|
emit("updateResult", {
|
|
897
1529
|
...props.selectedResult,
|
|
898
1530
|
data: {
|
|
@@ -901,14 +1533,14 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
901
1533
|
}
|
|
902
1534
|
});
|
|
903
1535
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1536
|
+
const { isDeck, deckScriptInput, onDeckUpdate, flushPendingDeckSave } = useDeckEditor({
|
|
1537
|
+
api,
|
|
1538
|
+
filePath,
|
|
1539
|
+
effectiveScript,
|
|
1540
|
+
commitScript
|
|
1541
|
+
});
|
|
907
1542
|
onBeforeUnmount(() => {
|
|
908
|
-
|
|
909
|
-
clearTimeout(deckSaveTimer);
|
|
910
|
-
flushDeckSave();
|
|
911
|
-
}
|
|
1543
|
+
flushPendingDeckSave();
|
|
912
1544
|
resetBeatMovies();
|
|
913
1545
|
unsubscribeGenerationEvents();
|
|
914
1546
|
});
|
|
@@ -925,11 +1557,11 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
925
1557
|
async function onSourceToggle(open) {
|
|
926
1558
|
editing.value = open;
|
|
927
1559
|
if (open) {
|
|
928
|
-
let text = scriptSourceText.value;
|
|
1560
|
+
let text = scriptSourceText$1.value;
|
|
929
1561
|
if (filePath.value) {
|
|
930
1562
|
const response = await api.call("save", { filePath: filePath.value });
|
|
931
1563
|
const diskScript = response.ok ? response.data.script : void 0;
|
|
932
|
-
if (diskScript) text =
|
|
1564
|
+
if (diskScript) text = scriptSourceText(diskScript);
|
|
933
1565
|
}
|
|
934
1566
|
editableSource.value = text;
|
|
935
1567
|
loadedSource.value = text;
|
|
@@ -954,31 +1586,25 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
954
1586
|
alert(response.error || "Update failed");
|
|
955
1587
|
return;
|
|
956
1588
|
}
|
|
957
|
-
|
|
958
|
-
...props.selectedResult,
|
|
959
|
-
data: {
|
|
960
|
-
...props.selectedResult.data,
|
|
961
|
-
script: parsed
|
|
962
|
-
}
|
|
963
|
-
});
|
|
1589
|
+
commitScript(parsed);
|
|
964
1590
|
if (sourceDetails.value) sourceDetails.value.open = false;
|
|
965
1591
|
await initializeScript();
|
|
966
1592
|
}
|
|
967
1593
|
async function copyText() {
|
|
968
|
-
await copy(scriptSourceText.value);
|
|
1594
|
+
await copy(scriptSourceText$1.value);
|
|
969
1595
|
}
|
|
970
|
-
function effectiveBeat(index) {
|
|
971
|
-
return localOverrides
|
|
1596
|
+
function effectiveBeat$1(index) {
|
|
1597
|
+
return effectiveBeat(localOverrides, beats.value, index);
|
|
972
1598
|
}
|
|
973
1599
|
function toggleSource(index) {
|
|
974
1600
|
if (!sourceOpen[index]) {
|
|
975
|
-
sourceText[index] =
|
|
1601
|
+
sourceText[index] = scriptSourceText(effectiveBeat$1(index));
|
|
976
1602
|
Reflect.deleteProperty(beatSaveErrors, index);
|
|
977
1603
|
}
|
|
978
1604
|
sourceOpen[index] = !sourceOpen[index];
|
|
979
1605
|
}
|
|
980
|
-
function isValidBeat(index) {
|
|
981
|
-
return
|
|
1606
|
+
function isValidBeat$1(index) {
|
|
1607
|
+
return isValidBeat(sourceText[index], mulmoBeatSchema);
|
|
982
1608
|
}
|
|
983
1609
|
async function updateBeat(index) {
|
|
984
1610
|
let beat;
|
|
@@ -991,8 +1617,8 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
991
1617
|
};
|
|
992
1618
|
return;
|
|
993
1619
|
}
|
|
994
|
-
const prevImage = JSON.stringify(effectiveBeat(index).image);
|
|
995
|
-
const prevText = effectiveBeat(index).text;
|
|
1620
|
+
const prevImage = JSON.stringify(effectiveBeat$1(index).image);
|
|
1621
|
+
const prevText = effectiveBeat$1(index).text;
|
|
996
1622
|
const requestedFilePath = filePath.value;
|
|
997
1623
|
Reflect.deleteProperty(beatSaveErrors, index);
|
|
998
1624
|
beatSaving[index] = true;
|
|
@@ -1001,7 +1627,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1001
1627
|
beatIndex: index,
|
|
1002
1628
|
beat
|
|
1003
1629
|
});
|
|
1004
|
-
if (staleSince(requestedFilePath)) return;
|
|
1630
|
+
if (staleSince$1(requestedFilePath)) return;
|
|
1005
1631
|
Reflect.deleteProperty(beatSaving, index);
|
|
1006
1632
|
if (!response.ok) {
|
|
1007
1633
|
beatSaveErrors[index] = {
|
|
@@ -1032,7 +1658,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1032
1658
|
beatIndex: index,
|
|
1033
1659
|
chatSessionId: chatSessionId.value
|
|
1034
1660
|
});
|
|
1035
|
-
if (staleSince(requestedFilePath)) return;
|
|
1661
|
+
if (staleSince$1(requestedFilePath)) return;
|
|
1036
1662
|
if (!response.ok) {
|
|
1037
1663
|
renderErrors[index] = response.error || "Render failed";
|
|
1038
1664
|
renderState[index] = "error";
|
|
@@ -1041,7 +1667,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1041
1667
|
renderedImages[index] = response.data.image ?? "";
|
|
1042
1668
|
renderState[index] = "done";
|
|
1043
1669
|
refreshMissingCharacterImages();
|
|
1044
|
-
if (beatMayHaveMovie(effectiveBeat(index))) loadExistingBeatMovie(index);
|
|
1670
|
+
if (beatMayHaveMovie(effectiveBeat$1(index))) loadExistingBeatMovie(index);
|
|
1045
1671
|
}
|
|
1046
1672
|
async function regenerateBeat(index) {
|
|
1047
1673
|
const requestedFilePath = filePath.value;
|
|
@@ -1054,7 +1680,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1054
1680
|
force: true,
|
|
1055
1681
|
chatSessionId: chatSessionId.value
|
|
1056
1682
|
});
|
|
1057
|
-
if (staleSince(requestedFilePath)) return;
|
|
1683
|
+
if (staleSince$1(requestedFilePath)) return;
|
|
1058
1684
|
if (!response.ok) {
|
|
1059
1685
|
renderErrors[index] = response.error || "Render failed";
|
|
1060
1686
|
renderState[index] = "error";
|
|
@@ -1062,10 +1688,10 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1062
1688
|
}
|
|
1063
1689
|
renderedImages[index] = response.data.image ?? "";
|
|
1064
1690
|
renderState[index] = "done";
|
|
1065
|
-
if (beatMayHaveMovie(effectiveBeat(index))) loadExistingBeatMovie(index);
|
|
1691
|
+
if (beatMayHaveMovie(effectiveBeat$1(index))) loadExistingBeatMovie(index);
|
|
1066
1692
|
}
|
|
1067
|
-
function staleSince(requestedFilePath) {
|
|
1068
|
-
return filePath.value
|
|
1693
|
+
function staleSince$1(requestedFilePath) {
|
|
1694
|
+
return staleSince(filePath.value, requestedFilePath);
|
|
1069
1695
|
}
|
|
1070
1696
|
async function loadExistingBeatImage(index) {
|
|
1071
1697
|
const requestedFilePath = filePath.value;
|
|
@@ -1073,7 +1699,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1073
1699
|
filePath: requestedFilePath,
|
|
1074
1700
|
beatIndex: index
|
|
1075
1701
|
});
|
|
1076
|
-
if (staleSince(requestedFilePath)) return;
|
|
1702
|
+
if (staleSince$1(requestedFilePath)) return;
|
|
1077
1703
|
if (response.ok && response.data.image) {
|
|
1078
1704
|
renderedImages[index] = response.data.image;
|
|
1079
1705
|
renderState[index] = "done";
|
|
@@ -1085,61 +1711,12 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1085
1711
|
filePath: requestedFilePath,
|
|
1086
1712
|
beatIndex: index
|
|
1087
1713
|
});
|
|
1088
|
-
if (staleSince(requestedFilePath)) return;
|
|
1714
|
+
if (staleSince$1(requestedFilePath)) return;
|
|
1089
1715
|
if (response.ok && response.data.audio) {
|
|
1090
1716
|
beatAudios[index] = response.data.audio;
|
|
1091
1717
|
audioState[index] = "done";
|
|
1092
1718
|
}
|
|
1093
1719
|
}
|
|
1094
|
-
async function loadExistingBeatMovie(index) {
|
|
1095
|
-
const requestedFilePath = filePath.value;
|
|
1096
|
-
const response = await api.call("beatMovie", {
|
|
1097
|
-
filePath: requestedFilePath,
|
|
1098
|
-
beatIndex: index
|
|
1099
|
-
});
|
|
1100
|
-
if (staleSince(requestedFilePath)) return;
|
|
1101
|
-
if (response.ok && response.data.moviePath) beatMovies[index] = response.data.moviePath;
|
|
1102
|
-
}
|
|
1103
|
-
async function playBeatMovie(index) {
|
|
1104
|
-
const fetchMediaBlob = adapter.fetchMediaBlob;
|
|
1105
|
-
if (!fetchMediaBlob || !beatMovies[index] || beatMovieLoading[index]) return;
|
|
1106
|
-
if (beatMovieUrls[index]) {
|
|
1107
|
-
beatMovieOpen[index] = true;
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
beatMovieLoading[index] = true;
|
|
1111
|
-
try {
|
|
1112
|
-
const blob = new Blob([await fetchMediaBlob({ moviePath: beatMovies[index] })], { type: "video/mp4" });
|
|
1113
|
-
beatMovieUrls[index] = URL.createObjectURL(blob);
|
|
1114
|
-
beatMovieOpen[index] = true;
|
|
1115
|
-
} catch (err) {
|
|
1116
|
-
alert(errorMessage(err));
|
|
1117
|
-
} finally {
|
|
1118
|
-
Reflect.deleteProperty(beatMovieLoading, index);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
function closeBeatMovie(index) {
|
|
1122
|
-
Reflect.deleteProperty(beatMovieOpen, index);
|
|
1123
|
-
}
|
|
1124
|
-
function invalidateBeatMovie(index) {
|
|
1125
|
-
if (beatMovieUrls[index]) URL.revokeObjectURL(beatMovieUrls[index]);
|
|
1126
|
-
[
|
|
1127
|
-
beatMovies,
|
|
1128
|
-
beatMovieUrls,
|
|
1129
|
-
beatMovieOpen
|
|
1130
|
-
].forEach((map) => Reflect.deleteProperty(map, index));
|
|
1131
|
-
}
|
|
1132
|
-
function resetBeatMovies() {
|
|
1133
|
-
Object.values(beatMovieUrls).forEach((url) => URL.revokeObjectURL(url));
|
|
1134
|
-
[
|
|
1135
|
-
beatMovies,
|
|
1136
|
-
beatMovieUrls,
|
|
1137
|
-
beatMovieOpen,
|
|
1138
|
-
beatMovieLoading
|
|
1139
|
-
].forEach((map) => {
|
|
1140
|
-
Object.keys(map).forEach((key) => Reflect.deleteProperty(map, key));
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
1720
|
async function generateAudio(index) {
|
|
1144
1721
|
const requestedFilePath = filePath.value;
|
|
1145
1722
|
audioState[index] = "generating";
|
|
@@ -1149,7 +1726,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1149
1726
|
beatIndex: index,
|
|
1150
1727
|
chatSessionId: chatSessionId.value
|
|
1151
1728
|
});
|
|
1152
|
-
if (staleSince(requestedFilePath)) return;
|
|
1729
|
+
if (staleSince$1(requestedFilePath)) return;
|
|
1153
1730
|
if (!response.ok) {
|
|
1154
1731
|
audioErrors[index] = response.error || "Audio generation failed";
|
|
1155
1732
|
audioState[index] = "error";
|
|
@@ -1202,12 +1779,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1202
1779
|
Reflect.deleteProperty(renderErrors, index);
|
|
1203
1780
|
let imageData;
|
|
1204
1781
|
try {
|
|
1205
|
-
imageData = await
|
|
1206
|
-
const reader = new FileReader();
|
|
1207
|
-
reader.onload = () => resolve(reader.result);
|
|
1208
|
-
reader.onerror = reject;
|
|
1209
|
-
reader.readAsDataURL(file);
|
|
1210
|
-
});
|
|
1782
|
+
imageData = await readFileAsDataUrl(file);
|
|
1211
1783
|
} catch (err) {
|
|
1212
1784
|
renderErrors[index] = errorMessage(err);
|
|
1213
1785
|
renderState[index] = "error";
|
|
@@ -1219,7 +1791,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1219
1791
|
beatIndex: index,
|
|
1220
1792
|
imageData
|
|
1221
1793
|
});
|
|
1222
|
-
if (staleSince(requestedFilePath)) return;
|
|
1794
|
+
if (staleSince$1(requestedFilePath)) return;
|
|
1223
1795
|
if (!response.ok) {
|
|
1224
1796
|
renderErrors[index] = response.error || "Upload failed";
|
|
1225
1797
|
renderState[index] = "error";
|
|
@@ -1228,49 +1800,6 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1228
1800
|
renderedImages[index] = response.data.image ?? "";
|
|
1229
1801
|
renderState[index] = "done";
|
|
1230
1802
|
}
|
|
1231
|
-
function onCharDragOver(event, key) {
|
|
1232
|
-
if (!event.dataTransfer?.types.includes("Files")) return;
|
|
1233
|
-
event.preventDefault();
|
|
1234
|
-
charDragOver[key] = true;
|
|
1235
|
-
}
|
|
1236
|
-
function onCharDragLeave(key) {
|
|
1237
|
-
charDragOver[key] = false;
|
|
1238
|
-
}
|
|
1239
|
-
async function onCharDrop(event, key) {
|
|
1240
|
-
event.preventDefault();
|
|
1241
|
-
charDragOver[key] = false;
|
|
1242
|
-
const file = event.dataTransfer?.files[0];
|
|
1243
|
-
if (!file || !file.type.startsWith("image/")) return;
|
|
1244
|
-
charRenderState[key] = "rendering";
|
|
1245
|
-
Reflect.deleteProperty(charErrors, key);
|
|
1246
|
-
let imageData;
|
|
1247
|
-
try {
|
|
1248
|
-
imageData = await new Promise((resolve, reject) => {
|
|
1249
|
-
const reader = new FileReader();
|
|
1250
|
-
reader.onload = () => resolve(reader.result);
|
|
1251
|
-
reader.onerror = reject;
|
|
1252
|
-
reader.readAsDataURL(file);
|
|
1253
|
-
});
|
|
1254
|
-
} catch (err) {
|
|
1255
|
-
charErrors[key] = errorMessage(err);
|
|
1256
|
-
charRenderState[key] = "error";
|
|
1257
|
-
return;
|
|
1258
|
-
}
|
|
1259
|
-
const requestedFilePath = filePath.value;
|
|
1260
|
-
const response = await api.call("uploadCharacterImage", {
|
|
1261
|
-
filePath: requestedFilePath,
|
|
1262
|
-
key,
|
|
1263
|
-
imageData
|
|
1264
|
-
});
|
|
1265
|
-
if (staleSince(requestedFilePath)) return;
|
|
1266
|
-
if (!response.ok) {
|
|
1267
|
-
charErrors[key] = response.error || "Upload failed";
|
|
1268
|
-
charRenderState[key] = "error";
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
charImages[key] = response.data.image ?? "";
|
|
1272
|
-
charRenderState[key] = "done";
|
|
1273
|
-
}
|
|
1274
1803
|
function openCharacterLightbox(key) {
|
|
1275
1804
|
stopAllPlayback();
|
|
1276
1805
|
lightbox.value = {
|
|
@@ -1280,43 +1809,6 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1280
1809
|
isCharacter: true
|
|
1281
1810
|
};
|
|
1282
1811
|
}
|
|
1283
|
-
async function loadExistingCharacterImage(key) {
|
|
1284
|
-
const requestedFilePath = filePath.value;
|
|
1285
|
-
const response = await api.call("characterImage", {
|
|
1286
|
-
filePath: requestedFilePath,
|
|
1287
|
-
key
|
|
1288
|
-
});
|
|
1289
|
-
if (staleSince(requestedFilePath)) return;
|
|
1290
|
-
if (response.ok && response.data.image) {
|
|
1291
|
-
charImages[key] = response.data.image;
|
|
1292
|
-
charRenderState[key] = "done";
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
function refreshMissingCharacterImages() {
|
|
1296
|
-
getMissingCharacterKeys(characterKeys.value, charImages, charRenderState).forEach((key) => loadExistingCharacterImage(key));
|
|
1297
|
-
}
|
|
1298
|
-
async function renderCharacter(key, force) {
|
|
1299
|
-
const requestedFilePath = filePath.value;
|
|
1300
|
-
charRenderState[key] = "rendering";
|
|
1301
|
-
Reflect.deleteProperty(charErrors, key);
|
|
1302
|
-
const response = await api.call("renderCharacter", {
|
|
1303
|
-
filePath: requestedFilePath,
|
|
1304
|
-
key,
|
|
1305
|
-
force,
|
|
1306
|
-
chatSessionId: chatSessionId.value
|
|
1307
|
-
});
|
|
1308
|
-
if (staleSince(requestedFilePath)) return;
|
|
1309
|
-
if (!response.ok) {
|
|
1310
|
-
charErrors[key] = response.error || "Render failed";
|
|
1311
|
-
charRenderState[key] = "error";
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
charImages[key] = response.data.image ?? "";
|
|
1315
|
-
charRenderState[key] = "done";
|
|
1316
|
-
}
|
|
1317
|
-
async function generateAllCharacters() {
|
|
1318
|
-
await Promise.all(characterKeys.value.filter((key) => charRenderState[key] !== "rendering").map((key) => renderCharacter(key, false)));
|
|
1319
|
-
}
|
|
1320
1812
|
async function hydrateBeatImage(beat, index, hasCharacters, autoRenderTypes) {
|
|
1321
1813
|
await loadExistingBeatImage(index);
|
|
1322
1814
|
if (renderedImages[index]) return;
|
|
@@ -1358,39 +1850,16 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1358
1850
|
const diskScript = response.data.script;
|
|
1359
1851
|
if (!diskScript) return;
|
|
1360
1852
|
if (isSameScript(diskScript, script.value)) return;
|
|
1361
|
-
|
|
1362
|
-
...props.selectedResult,
|
|
1363
|
-
data: {
|
|
1364
|
-
...props.selectedResult.data,
|
|
1365
|
-
script: diskScript
|
|
1366
|
-
}
|
|
1367
|
-
});
|
|
1853
|
+
commitScript(diskScript);
|
|
1368
1854
|
}
|
|
1369
1855
|
async function initializeScript() {
|
|
1370
1856
|
stopAllPlayback();
|
|
1371
1857
|
lightbox.value = null;
|
|
1372
1858
|
if (beatListEl.value) beatListEl.value.scrollTop = 0;
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
Object.keys(renderErrors).forEach((key) => Reflect.deleteProperty(renderErrors, key));
|
|
1376
|
-
Object.keys(sourceOpen).forEach((key) => Reflect.deleteProperty(sourceOpen, key));
|
|
1377
|
-
Object.keys(sourceText).forEach((key) => Reflect.deleteProperty(sourceText, key));
|
|
1378
|
-
Object.keys(beatSaveErrors).forEach((key) => Reflect.deleteProperty(beatSaveErrors, key));
|
|
1379
|
-
Object.keys(beatSaving).forEach((key) => Reflect.deleteProperty(beatSaving, key));
|
|
1380
|
-
Object.keys(localOverrides).forEach((key) => Reflect.deleteProperty(localOverrides, key));
|
|
1381
|
-
Object.keys(beatAudios).forEach((key) => Reflect.deleteProperty(beatAudios, key));
|
|
1382
|
-
Object.keys(audioState).forEach((key) => Reflect.deleteProperty(audioState, key));
|
|
1383
|
-
Object.keys(audioErrors).forEach((key) => Reflect.deleteProperty(audioErrors, key));
|
|
1384
|
-
Object.keys(charRenderState).forEach((key) => Reflect.deleteProperty(charRenderState, key));
|
|
1385
|
-
Object.keys(charImages).forEach((key) => Reflect.deleteProperty(charImages, key));
|
|
1386
|
-
Object.keys(charErrors).forEach((key) => Reflect.deleteProperty(charErrors, key));
|
|
1387
|
-
Object.keys(beatDragOver).forEach((key) => Reflect.deleteProperty(beatDragOver, key));
|
|
1859
|
+
clearReactiveRecords(renderState, renderedImages, renderErrors, sourceOpen, sourceText, beatSaveErrors, beatSaving, localOverrides, beatAudios, audioState, audioErrors, beatDragOver);
|
|
1860
|
+
resetCharacters();
|
|
1388
1861
|
resetBeatMovies();
|
|
1389
|
-
|
|
1390
|
-
pdfPath.value = null;
|
|
1391
|
-
movieGenerating.value = false;
|
|
1392
|
-
pdfGenerating.value = false;
|
|
1393
|
-
movieError.value = null;
|
|
1862
|
+
resetMedia();
|
|
1394
1863
|
if (sourceDetails.value) sourceDetails.value.open = false;
|
|
1395
1864
|
await refreshScriptFromDisk();
|
|
1396
1865
|
const AUTO_RENDER_TYPES = [
|
|
@@ -1449,7 +1918,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1449
1918
|
if (entry.kind === "beatImage") {
|
|
1450
1919
|
const idx = Number(entry.key);
|
|
1451
1920
|
await loadExistingBeatImage(idx);
|
|
1452
|
-
if (beatMayHaveMovie(effectiveBeat(idx))) await loadExistingBeatMovie(idx);
|
|
1921
|
+
if (beatMayHaveMovie(effectiveBeat$1(idx))) await loadExistingBeatMovie(idx);
|
|
1453
1922
|
if (renderState[idx] === "rendering") Reflect.deleteProperty(renderState, idx);
|
|
1454
1923
|
refreshMissingCharacterImages();
|
|
1455
1924
|
} else if (entry.kind === "beatAudio") {
|
|
@@ -1467,95 +1936,6 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1467
1936
|
await refreshPdfPath();
|
|
1468
1937
|
}
|
|
1469
1938
|
}
|
|
1470
|
-
async function refreshMoviePath() {
|
|
1471
|
-
const requestedFilePath = filePath.value;
|
|
1472
|
-
if (!requestedFilePath) return;
|
|
1473
|
-
const response = await api.call("movieStatus", { filePath: requestedFilePath });
|
|
1474
|
-
if (filePath.value !== requestedFilePath) return;
|
|
1475
|
-
if (response.ok && response.data.moviePath) moviePath.value = response.data.moviePath;
|
|
1476
|
-
}
|
|
1477
|
-
async function generateMovie() {
|
|
1478
|
-
const requestedFilePath = filePath.value;
|
|
1479
|
-
movieGenerating.value = true;
|
|
1480
|
-
movieError.value = null;
|
|
1481
|
-
const response = await api.call("generateMovie", {
|
|
1482
|
-
filePath: requestedFilePath,
|
|
1483
|
-
chatSessionId: chatSessionId.value
|
|
1484
|
-
});
|
|
1485
|
-
if (filePath.value !== requestedFilePath) return;
|
|
1486
|
-
movieGenerating.value = false;
|
|
1487
|
-
if (!response.ok) {
|
|
1488
|
-
movieError.value = response.error;
|
|
1489
|
-
return;
|
|
1490
|
-
}
|
|
1491
|
-
moviePath.value = response.data.moviePath;
|
|
1492
|
-
}
|
|
1493
|
-
async function downloadMovie() {
|
|
1494
|
-
const fetchMediaBlob = adapter.fetchMediaBlob;
|
|
1495
|
-
if (!fetchMediaBlob || !moviePath.value || movieDownloading.value) return;
|
|
1496
|
-
movieDownloading.value = true;
|
|
1497
|
-
let objectUrl = null;
|
|
1498
|
-
try {
|
|
1499
|
-
const blob = await fetchMediaBlob({ moviePath: moviePath.value });
|
|
1500
|
-
objectUrl = URL.createObjectURL(blob);
|
|
1501
|
-
const filename = moviePath.value.split("/").pop() ?? "movie.mp4";
|
|
1502
|
-
const anchor = document.createElement("a");
|
|
1503
|
-
anchor.href = objectUrl;
|
|
1504
|
-
anchor.download = filename;
|
|
1505
|
-
document.body.appendChild(anchor);
|
|
1506
|
-
anchor.click();
|
|
1507
|
-
anchor.remove();
|
|
1508
|
-
} catch (err) {
|
|
1509
|
-
alert(errorMessage(err));
|
|
1510
|
-
} finally {
|
|
1511
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
1512
|
-
movieDownloading.value = false;
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
async function refreshPdfPath() {
|
|
1516
|
-
const requestedFilePath = filePath.value;
|
|
1517
|
-
if (!requestedFilePath) return;
|
|
1518
|
-
const response = await api.call("pdfStatus", { filePath: requestedFilePath });
|
|
1519
|
-
if (filePath.value !== requestedFilePath) return;
|
|
1520
|
-
if (response.ok && response.data.pdfPath) pdfPath.value = response.data.pdfPath;
|
|
1521
|
-
}
|
|
1522
|
-
async function generatePdf() {
|
|
1523
|
-
const requestedFilePath = filePath.value;
|
|
1524
|
-
pdfGenerating.value = true;
|
|
1525
|
-
const response = await api.call("generatePdf", {
|
|
1526
|
-
filePath: requestedFilePath,
|
|
1527
|
-
chatSessionId: chatSessionId.value
|
|
1528
|
-
});
|
|
1529
|
-
if (filePath.value !== requestedFilePath) return;
|
|
1530
|
-
pdfGenerating.value = false;
|
|
1531
|
-
if (!response.ok) {
|
|
1532
|
-
alert(response.error);
|
|
1533
|
-
return;
|
|
1534
|
-
}
|
|
1535
|
-
pdfPath.value = response.data.pdfPath;
|
|
1536
|
-
}
|
|
1537
|
-
async function downloadPdf() {
|
|
1538
|
-
const fetchMediaBlob = adapter.fetchMediaBlob;
|
|
1539
|
-
if (!fetchMediaBlob || !pdfPath.value || pdfDownloading.value) return;
|
|
1540
|
-
pdfDownloading.value = true;
|
|
1541
|
-
let objectUrl = null;
|
|
1542
|
-
try {
|
|
1543
|
-
const blob = await fetchMediaBlob({ pdfPath: pdfPath.value });
|
|
1544
|
-
objectUrl = URL.createObjectURL(blob);
|
|
1545
|
-
const filename = pdfPath.value.split("/").pop() ?? "deck.pdf";
|
|
1546
|
-
const anchor = document.createElement("a");
|
|
1547
|
-
anchor.href = objectUrl;
|
|
1548
|
-
anchor.download = filename;
|
|
1549
|
-
document.body.appendChild(anchor);
|
|
1550
|
-
anchor.click();
|
|
1551
|
-
anchor.remove();
|
|
1552
|
-
} catch (err) {
|
|
1553
|
-
alert(errorMessage(err));
|
|
1554
|
-
} finally {
|
|
1555
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
1556
|
-
pdfDownloading.value = false;
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
1939
|
return (_ctx, _cache) => {
|
|
1560
1940
|
return openBlock(), createElementBlock("div", _hoisted_1$1, [
|
|
1561
1941
|
createElementVNode("div", _hoisted_2$1, [createElementVNode("div", _hoisted_3$1, [
|
|
@@ -1566,153 +1946,80 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1566
1946
|
script.value.lang ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(script.value.lang), 1)) : createCommentVNode("", true),
|
|
1567
1947
|
filePath.value ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(filePath.value), 1)) : createCommentVNode("", true)
|
|
1568
1948
|
])
|
|
1569
|
-
]),
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
"data-testid": "mulmo-script-generate-movie-button",
|
|
1597
|
-
onClick: generateMovie
|
|
1598
|
-
}, [movieGenerating.value ? (openBlock(), createElementBlock("svg", _hoisted_14, [..._cache[9] || (_cache[9] = [createElementVNode("circle", {
|
|
1599
|
-
class: "opacity-25",
|
|
1600
|
-
cx: "12",
|
|
1601
|
-
cy: "12",
|
|
1602
|
-
r: "10",
|
|
1603
|
-
stroke: "currentColor",
|
|
1604
|
-
"stroke-width": "4"
|
|
1605
|
-
}, null, -1), createElementVNode("path", {
|
|
1606
|
-
class: "opacity-75",
|
|
1607
|
-
fill: "currentColor",
|
|
1608
|
-
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1609
|
-
}, null, -1)])])) : createCommentVNode("", true), movieGenerating.value ? (openBlock(), createElementBlock("span", _hoisted_15, toDisplayString(unref(m).generating), 1)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [_cache[10] || (_cache[10] = createElementVNode("span", { class: "material-icons text-sm" }, "refresh", -1)), createElementVNode("span", null, toDisplayString(unref(m).movie), 1)], 64))], 8, _hoisted_13)),
|
|
1610
|
-
pdfPath.value && !pdfGenerating.value && canFetchMedia.value ? (openBlock(), createElementBlock("button", {
|
|
1611
|
-
key: 4,
|
|
1612
|
-
class: "h-8 px-2.5 flex items-center gap-1 rounded bg-red-600 hover:bg-red-700 text-white text-sm disabled:opacity-60 disabled:cursor-not-allowed transition-colors",
|
|
1613
|
-
disabled: pdfDownloading.value,
|
|
1614
|
-
"data-testid": "mulmo-script-download-pdf-button",
|
|
1615
|
-
onClick: downloadPdf
|
|
1616
|
-
}, [_cache[11] || (_cache[11] = createElementVNode("span", { class: "material-icons text-base" }, "download", -1)), createElementVNode("span", null, toDisplayString(unref(m).pdf), 1)], 8, _hoisted_16)) : createCommentVNode("", true),
|
|
1617
|
-
pdfPath.value && !pdfGenerating.value ? (openBlock(), createElementBlock("button", {
|
|
1618
|
-
key: 5,
|
|
1619
|
-
class: "h-8 w-8 flex items-center justify-center rounded border border-gray-200 text-gray-600 hover:bg-gray-100 transition-colors",
|
|
1620
|
-
title: unref(m).regeneratePdf,
|
|
1621
|
-
"aria-label": unref(m).regeneratePdf,
|
|
1622
|
-
"data-testid": "mulmo-script-regenerate-pdf-button",
|
|
1623
|
-
onClick: generatePdf
|
|
1624
|
-
}, [..._cache[12] || (_cache[12] = [createElementVNode("span", { class: "material-icons text-base" }, "refresh", -1)])], 8, _hoisted_17)) : (openBlock(), createElementBlock("button", {
|
|
1625
|
-
key: 6,
|
|
1626
|
-
class: "h-8 px-2.5 flex items-center gap-1 text-sm rounded border border-gray-200 text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
1627
|
-
disabled: pdfGenerating.value,
|
|
1628
|
-
"data-testid": "mulmo-script-generate-pdf-button",
|
|
1629
|
-
onClick: generatePdf
|
|
1630
|
-
}, [pdfGenerating.value ? (openBlock(), createElementBlock("svg", _hoisted_19, [..._cache[13] || (_cache[13] = [createElementVNode("circle", {
|
|
1631
|
-
class: "opacity-25",
|
|
1632
|
-
cx: "12",
|
|
1633
|
-
cy: "12",
|
|
1634
|
-
r: "10",
|
|
1635
|
-
stroke: "currentColor",
|
|
1636
|
-
"stroke-width": "4"
|
|
1637
|
-
}, null, -1), createElementVNode("path", {
|
|
1638
|
-
class: "opacity-75",
|
|
1639
|
-
fill: "currentColor",
|
|
1640
|
-
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1641
|
-
}, null, -1)])])) : createCommentVNode("", true), pdfGenerating.value ? (openBlock(), createElementBlock("span", _hoisted_20, toDisplayString(unref(m).generatingPdf), 1)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [_cache[14] || (_cache[14] = createElementVNode("span", { class: "material-icons text-sm" }, "picture_as_pdf", -1)), createElementVNode("span", null, toDisplayString(unref(m).pdf), 1)], 64))], 8, _hoisted_18))
|
|
1949
|
+
]), createVNode(MulmoScriptToolbar_default, {
|
|
1950
|
+
"movie-path": unref(moviePath),
|
|
1951
|
+
"movie-generating": unref(movieGenerating),
|
|
1952
|
+
"movie-downloading": unref(movieDownloading),
|
|
1953
|
+
"is-play-ready": isPlayReady.value,
|
|
1954
|
+
"can-fetch-media": canFetchMedia.value,
|
|
1955
|
+
"pdf-path": unref(pdfPath),
|
|
1956
|
+
"pdf-generating": unref(pdfGenerating),
|
|
1957
|
+
"pdf-downloading": unref(pdfDownloading),
|
|
1958
|
+
onPlay: playPresentation,
|
|
1959
|
+
onGenerateMovie: unref(generateMovie),
|
|
1960
|
+
onDownloadMovie: unref(downloadMovie),
|
|
1961
|
+
onGeneratePdf: unref(generatePdf),
|
|
1962
|
+
onDownloadPdf: unref(downloadPdf)
|
|
1963
|
+
}, null, 8, [
|
|
1964
|
+
"movie-path",
|
|
1965
|
+
"movie-generating",
|
|
1966
|
+
"movie-downloading",
|
|
1967
|
+
"is-play-ready",
|
|
1968
|
+
"can-fetch-media",
|
|
1969
|
+
"pdf-path",
|
|
1970
|
+
"pdf-generating",
|
|
1971
|
+
"pdf-downloading",
|
|
1972
|
+
"onGenerateMovie",
|
|
1973
|
+
"onDownloadMovie",
|
|
1974
|
+
"onGeneratePdf",
|
|
1975
|
+
"onDownloadPdf"
|
|
1642
1976
|
])]),
|
|
1643
|
-
movieError
|
|
1644
|
-
_cache[
|
|
1645
|
-
createElementVNode("div",
|
|
1977
|
+
unref(movieError) ? (openBlock(), createElementBlock("div", _hoisted_9, [
|
|
1978
|
+
_cache[3] || (_cache[3] = createElementVNode("span", { class: "material-icons text-base shrink-0 mt-px" }, "error_outline", -1)),
|
|
1979
|
+
createElementVNode("div", _hoisted_10, [createElementVNode("div", _hoisted_11, toDisplayString(unref(m).movieGenerationFailed), 1), createElementVNode("div", _hoisted_12, toDisplayString(unref(movieError)), 1)]),
|
|
1646
1980
|
createElementVNode("button", {
|
|
1647
1981
|
class: "shrink-0 h-7 px-2 text-xs rounded border border-red-300 text-red-700 hover:bg-red-100 disabled:opacity-50",
|
|
1648
|
-
disabled: movieGenerating
|
|
1982
|
+
disabled: unref(movieGenerating),
|
|
1649
1983
|
"data-testid": "mulmo-script-movie-retry-button",
|
|
1650
|
-
onClick: generateMovie
|
|
1651
|
-
}, toDisplayString(unref(m).retry), 9,
|
|
1984
|
+
onClick: _cache[0] || (_cache[0] = (...args) => unref(generateMovie) && unref(generateMovie)(...args))
|
|
1985
|
+
}, toDisplayString(unref(m).retry), 9, _hoisted_13)
|
|
1652
1986
|
])) : createCommentVNode("", true),
|
|
1653
|
-
characterKeys.
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
charDragOver[key] ? (openBlock(), createElementBlock("div", _hoisted_37, [createElementVNode("span", _hoisted_38, toDisplayString(unref(m).drop), 1)])) : createCommentVNode("", true),
|
|
1687
|
-
charImages[key] && charRenderState[key] !== "rendering" ? (openBlock(), createElementBlock("button", {
|
|
1688
|
-
key: 6,
|
|
1689
|
-
class: normalizeClass(["absolute top-0.5 right-0.5 px-1 py-0.5 text-xs rounded border bg-white", movieGenerating.value || anyBeatRendering.value ? "border-yellow-400 text-yellow-500 cursor-not-allowed" : "border-gray-400 text-gray-600 hover:bg-gray-50"]),
|
|
1690
|
-
disabled: movieGenerating.value || anyBeatRendering.value,
|
|
1691
|
-
onClick: withModifiers(($event) => renderCharacter(key, true), ["stop"])
|
|
1692
|
-
}, [movieGenerating.value || anyBeatRendering.value ? (openBlock(), createElementBlock("span", _hoisted_40, "↺")) : (openBlock(), createElementBlock("span", _hoisted_41, "↺"))], 10, _hoisted_39)) : !charImages[key] && charRenderState[key] !== "rendering" ? (openBlock(), createElementBlock("button", {
|
|
1693
|
-
key: 7,
|
|
1694
|
-
class: normalizeClass(["absolute top-0.5 right-0.5 px-1 py-0.5 text-xs rounded border bg-white", movieGenerating.value || anyBeatRendering.value ? "border-yellow-400 text-yellow-500 cursor-not-allowed" : "border-blue-400 text-blue-600 hover:bg-blue-50"]),
|
|
1695
|
-
disabled: movieGenerating.value || anyBeatRendering.value,
|
|
1696
|
-
onClick: withModifiers(($event) => renderCharacter(key, false), ["stop"])
|
|
1697
|
-
}, [movieGenerating.value || anyBeatRendering.value ? (openBlock(), createElementBlock("svg", _hoisted_43, [..._cache[17] || (_cache[17] = [createElementVNode("circle", {
|
|
1698
|
-
class: "opacity-25",
|
|
1699
|
-
cx: "12",
|
|
1700
|
-
cy: "12",
|
|
1701
|
-
r: "10",
|
|
1702
|
-
stroke: "currentColor",
|
|
1703
|
-
"stroke-width": "4"
|
|
1704
|
-
}, null, -1), createElementVNode("path", {
|
|
1705
|
-
class: "opacity-75",
|
|
1706
|
-
fill: "currentColor",
|
|
1707
|
-
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1708
|
-
}, null, -1)])])) : (openBlock(), createElementBlock("span", _hoisted_44, toDisplayString(unref(m).gen), 1))], 10, _hoisted_42)) : createCommentVNode("", true)
|
|
1709
|
-
], 42, _hoisted_31), createElementVNode("span", _hoisted_45, toDisplayString(key), 1)]);
|
|
1710
|
-
}), 128))])])) : createCommentVNode("", true),
|
|
1711
|
-
isDeck.value ? (openBlock(), createElementBlock("div", _hoisted_46, [createVNode(unref(MulmoScriptDeckEditor), {
|
|
1712
|
-
script: deckScriptInput.value,
|
|
1987
|
+
unref(characterKeys).length > 0 ? (openBlock(), createBlock(CharacterStrip_default, {
|
|
1988
|
+
key: 1,
|
|
1989
|
+
"character-keys": unref(characterKeys),
|
|
1990
|
+
images: script.value.imageParams?.images,
|
|
1991
|
+
thumbnails: unref(charImages),
|
|
1992
|
+
"render-state": unref(charRenderState),
|
|
1993
|
+
errors: unref(charErrors),
|
|
1994
|
+
"drag-over": unref(charDragOver),
|
|
1995
|
+
"movie-generating": unref(movieGenerating),
|
|
1996
|
+
"any-beat-rendering": anyBeatRendering.value,
|
|
1997
|
+
onGenerateAll: unref(generateAllCharacters),
|
|
1998
|
+
onCharDragOver: unref(onCharDragOver),
|
|
1999
|
+
onCharDragLeave: unref(onCharDragLeave),
|
|
2000
|
+
onCharDrop: unref(onCharDrop),
|
|
2001
|
+
onOpenLightbox: openCharacterLightbox,
|
|
2002
|
+
onRenderCharacter: unref(renderCharacter)
|
|
2003
|
+
}, null, 8, [
|
|
2004
|
+
"character-keys",
|
|
2005
|
+
"images",
|
|
2006
|
+
"thumbnails",
|
|
2007
|
+
"render-state",
|
|
2008
|
+
"errors",
|
|
2009
|
+
"drag-over",
|
|
2010
|
+
"movie-generating",
|
|
2011
|
+
"any-beat-rendering",
|
|
2012
|
+
"onGenerateAll",
|
|
2013
|
+
"onCharDragOver",
|
|
2014
|
+
"onCharDragLeave",
|
|
2015
|
+
"onCharDrop",
|
|
2016
|
+
"onRenderCharacter"
|
|
2017
|
+
])) : createCommentVNode("", true),
|
|
2018
|
+
unref(isDeck) ? (openBlock(), createElementBlock("div", _hoisted_14, [createVNode(unref(MulmoScriptDeckEditor), {
|
|
2019
|
+
script: unref(deckScriptInput),
|
|
1713
2020
|
layout: "compact",
|
|
1714
|
-
"onUpdate:script": onDeckUpdate
|
|
1715
|
-
}, null, 8, ["script"])])) : (openBlock(), createElementBlock("div", {
|
|
2021
|
+
"onUpdate:script": unref(onDeckUpdate)
|
|
2022
|
+
}, null, 8, ["script", "onUpdate:script"])])) : (openBlock(), createElementBlock("div", {
|
|
1716
2023
|
key: 3,
|
|
1717
2024
|
ref_key: "beatListEl",
|
|
1718
2025
|
ref: beatListEl,
|
|
@@ -1721,40 +2028,44 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1721
2028
|
return openBlock(), createElementBlock("div", {
|
|
1722
2029
|
key: index,
|
|
1723
2030
|
class: "rounded-lg border border-gray-200 overflow-hidden"
|
|
1724
|
-
}, [createElementVNode("div",
|
|
2031
|
+
}, [createElementVNode("div", _hoisted_15, [createElementVNode("div", {
|
|
1725
2032
|
class: normalizeClass(["relative shrink-0 w-[45%] overflow-hidden bg-gray-50 transition-colors", beatDragOver[index] ? "bg-blue-50" : ""]),
|
|
1726
2033
|
onDragover: ($event) => onBeatDragOver($event, index),
|
|
1727
2034
|
onDragleave: ($event) => onBeatDragLeave(index),
|
|
1728
2035
|
onDrop: ($event) => onBeatDrop($event, index)
|
|
1729
2036
|
}, [
|
|
1730
|
-
|
|
1731
|
-
|
|
2037
|
+
createElementVNode("div", {
|
|
2038
|
+
class: "absolute top-1.5 left-1.5 z-10 px-1.5 py-0.5 rounded bg-black/55 text-white text-xs font-medium leading-none pointer-events-none",
|
|
2039
|
+
"data-testid": `mulmo-script-beat-number-${index}`
|
|
2040
|
+
}, toDisplayString(index + 1), 9, _hoisted_17),
|
|
2041
|
+
unref(beatMovieOpen)[index] && unref(beatMovieUrls)[index] ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("video", {
|
|
2042
|
+
src: unref(beatMovieUrls)[index],
|
|
1732
2043
|
class: "w-full object-contain",
|
|
1733
2044
|
controls: "",
|
|
1734
2045
|
autoplay: "",
|
|
1735
2046
|
"data-testid": `mulmo-script-beat-movie-player-${index}`
|
|
1736
|
-
}, null, 8,
|
|
2047
|
+
}, null, 8, _hoisted_18), createElementVNode("button", {
|
|
1737
2048
|
class: "absolute top-1.5 right-1.5 flex items-center justify-center w-6 h-6 rounded border border-gray-400 text-gray-600 bg-white hover:bg-gray-50",
|
|
1738
2049
|
title: unref(m).close,
|
|
1739
2050
|
"aria-label": unref(m).close,
|
|
1740
2051
|
"data-testid": `mulmo-script-beat-movie-close-${index}`,
|
|
1741
|
-
onClick: withModifiers(($event) => closeBeatMovie(index), ["stop"])
|
|
1742
|
-
}, [..._cache[
|
|
2052
|
+
onClick: withModifiers(($event) => unref(closeBeatMovie)(index), ["stop"])
|
|
2053
|
+
}, [..._cache[4] || (_cache[4] = [createElementVNode("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_19)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
1743
2054
|
renderedImages[index] ? (openBlock(), createElementBlock("img", {
|
|
1744
2055
|
key: 0,
|
|
1745
2056
|
src: renderedImages[index],
|
|
1746
2057
|
class: "w-full object-contain cursor-zoom-in",
|
|
1747
2058
|
alt: `Beat ${index + 1}`,
|
|
1748
2059
|
onClick: ($event) => openLightbox(index)
|
|
1749
|
-
}, null, 8,
|
|
1750
|
-
renderedImages[index] && beatMovies[index] && canFetchMedia.value ? (openBlock(), createElementBlock("button", {
|
|
2060
|
+
}, null, 8, _hoisted_20)) : createCommentVNode("", true),
|
|
2061
|
+
renderedImages[index] && unref(beatMovies)[index] && canFetchMedia.value ? (openBlock(), createElementBlock("button", {
|
|
1751
2062
|
key: 1,
|
|
1752
2063
|
class: "absolute inset-0 m-auto w-12 h-12 flex items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70",
|
|
1753
2064
|
title: unref(m).play,
|
|
1754
2065
|
"aria-label": unref(m).play,
|
|
1755
2066
|
"data-testid": `mulmo-script-beat-movie-play-${index}`,
|
|
1756
|
-
onClick: withModifiers(($event) => playBeatMovie(index), ["stop"])
|
|
1757
|
-
}, [beatMovieLoading[index] ? (openBlock(), createElementBlock("svg",
|
|
2067
|
+
onClick: withModifiers(($event) => unref(playBeatMovie)(index), ["stop"])
|
|
2068
|
+
}, [unref(beatMovieLoading)[index] ? (openBlock(), createElementBlock("svg", _hoisted_22, [..._cache[5] || (_cache[5] = [createElementVNode("circle", {
|
|
1758
2069
|
class: "opacity-25",
|
|
1759
2070
|
cx: "12",
|
|
1760
2071
|
cy: "12",
|
|
@@ -1765,13 +2076,13 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1765
2076
|
class: "opacity-75",
|
|
1766
2077
|
fill: "currentColor",
|
|
1767
2078
|
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1768
|
-
}, null, -1)])])) : (openBlock(), createElementBlock("span",
|
|
2079
|
+
}, null, -1)])])) : (openBlock(), createElementBlock("span", _hoisted_23, "play_arrow"))], 8, _hoisted_21)) : createCommentVNode("", true),
|
|
1769
2080
|
renderedImages[index] && renderState[index] !== "rendering" ? (openBlock(), createElementBlock("button", {
|
|
1770
2081
|
key: 2,
|
|
1771
2082
|
class: "absolute top-1.5 right-1.5 flex items-center gap-1 px-2 py-0.5 text-xs rounded border border-gray-400 text-gray-600 bg-white hover:bg-gray-50 disabled:opacity-60 disabled:cursor-not-allowed",
|
|
1772
|
-
disabled: movieGenerating
|
|
2083
|
+
disabled: unref(movieGenerating),
|
|
1773
2084
|
onClick: withModifiers(($event) => regenerateBeat(index), ["stop"])
|
|
1774
|
-
}, " ↺ ", 8,
|
|
2085
|
+
}, " ↺ ", 8, _hoisted_24)) : !renderedImages[index] ? (openBlock(), createElementBlock("div", _hoisted_25, [renderState[index] === "rendering" || unref(movieGenerating) && !renderedImages[index] && effectiveBeat$1(index).imagePrompt ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[6] || (_cache[6] = createElementVNode("svg", {
|
|
1775
2086
|
class: "animate-spin w-4 h-4 text-green-400",
|
|
1776
2087
|
viewBox: "0 0 24 24",
|
|
1777
2088
|
fill: "none"
|
|
@@ -1786,15 +2097,15 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1786
2097
|
class: "opacity-75",
|
|
1787
2098
|
fill: "currentColor",
|
|
1788
2099
|
d: "M4 12a8 8 0 018-8v8H4z"
|
|
1789
|
-
})], -1)), createElementVNode("span",
|
|
2100
|
+
})], -1)), createElementVNode("span", _hoisted_26, toDisplayString(unref(m).rendering), 1)], 64)) : renderState[index] === "error" ? (openBlock(), createElementBlock("span", _hoisted_27, toDisplayString(renderErrors[index]), 1)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [effectiveBeat$1(index).imagePrompt ? (openBlock(), createElementBlock("span", _hoisted_28, toDisplayString(effectiveBeat$1(index).imagePrompt), 1)) : (openBlock(), createElementBlock("span", _hoisted_29, toDisplayString(beat.image?.type ?? "—"), 1))], 64))])) : createCommentVNode("", true)
|
|
1790
2101
|
], 64)),
|
|
1791
|
-
beatDragOver[index] ? (openBlock(), createElementBlock("div",
|
|
1792
|
-
!renderedImages[index] && renderState[index] !== "rendering" && !movieGenerating
|
|
2102
|
+
beatDragOver[index] ? (openBlock(), createElementBlock("div", _hoisted_30, [createElementVNode("span", _hoisted_31, toDisplayString(unref(m).drop), 1)])) : !renderedImages[index] && renderState[index] !== "rendering" ? (openBlock(), createElementBlock("div", _hoisted_32, toDisplayString(unref(m).orDropImage), 1)) : createCommentVNode("", true),
|
|
2103
|
+
!renderedImages[index] && renderState[index] !== "rendering" && !unref(movieGenerating) && !unref(isBeatImageReference)(effectiveBeat$1(index)) ? (openBlock(), createElementBlock("button", {
|
|
1793
2104
|
key: 4,
|
|
1794
2105
|
class: "absolute top-1.5 right-1.5 flex items-center gap-1 px-2 py-0.5 text-xs rounded border border-blue-400 text-blue-600 bg-white hover:bg-blue-50",
|
|
1795
2106
|
onClick: ($event) => renderBeat(index)
|
|
1796
|
-
}, toDisplayString(unref(m).generate), 9,
|
|
1797
|
-
], 42,
|
|
2107
|
+
}, toDisplayString(unref(m).generate), 9, _hoisted_33)) : createCommentVNode("", true)
|
|
2108
|
+
], 42, _hoisted_16), createElementVNode("div", _hoisted_34, [createElementVNode("span", _hoisted_35, toDisplayString(effectiveBeat$1(index).text), 1), createElementVNode("div", _hoisted_36, [createElementVNode("div", _hoisted_37, [audioState[index] === "generating" || unref(movieGenerating) && !beatAudios[index] && effectiveBeat$1(index).text ? (openBlock(), createElementBlock("svg", _hoisted_38, [..._cache[7] || (_cache[7] = [createElementVNode("circle", {
|
|
1798
2109
|
class: "opacity-25",
|
|
1799
2110
|
cx: "12",
|
|
1800
2111
|
cy: "12",
|
|
@@ -1809,24 +2120,24 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1809
2120
|
key: 1,
|
|
1810
2121
|
class: normalizeClass(["text-xs px-2 py-0.5 rounded border", playingAudio.value?.index === index ? "border-red-400 text-red-600 hover:bg-red-50" : "border-green-400 text-green-600 hover:bg-green-50"]),
|
|
1811
2122
|
onClick: ($event) => playAudio(index)
|
|
1812
|
-
}, toDisplayString(playingAudio.value?.index === index ? unref(m).stop : unref(m).play), 11,
|
|
2123
|
+
}, toDisplayString(playingAudio.value?.index === index ? unref(m).stop : unref(m).play), 11, _hoisted_39)) : audioErrors[index] ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [createElementVNode("span", {
|
|
1813
2124
|
class: "text-xs text-red-400 truncate min-w-0 max-w-[20rem]",
|
|
1814
2125
|
title: audioErrors[index]
|
|
1815
|
-
}, toDisplayString(unref(m).errPrefix) + " " + toDisplayString(audioErrors[index]), 9,
|
|
2126
|
+
}, toDisplayString(unref(m).errPrefix) + " " + toDisplayString(audioErrors[index]), 9, _hoisted_40), effectiveBeat$1(index).text ? (openBlock(), createElementBlock("button", {
|
|
1816
2127
|
key: 0,
|
|
1817
2128
|
class: "text-xs px-2 py-0.5 rounded border border-gray-300 text-gray-500 hover:bg-gray-50 disabled:opacity-50",
|
|
1818
|
-
disabled: movieGenerating
|
|
2129
|
+
disabled: unref(movieGenerating),
|
|
1819
2130
|
onClick: ($event) => generateAudio(index)
|
|
1820
|
-
}, " ↺ ", 8,
|
|
2131
|
+
}, " ↺ ", 8, _hoisted_41)) : createCommentVNode("", true)], 64)) : effectiveBeat$1(index).text ? (openBlock(), createElementBlock("button", {
|
|
1821
2132
|
key: 3,
|
|
1822
2133
|
class: "text-xs px-2 py-0.5 rounded border border-gray-300 text-gray-500 hover:bg-gray-50",
|
|
1823
2134
|
onClick: ($event) => generateAudio(index)
|
|
1824
|
-
}, toDisplayString(unref(m).generateAudio), 9,
|
|
2135
|
+
}, toDisplayString(unref(m).generateAudio), 9, _hoisted_42)) : createCommentVNode("", true)]), createElementVNode("button", {
|
|
1825
2136
|
class: "text-gray-400 hover:text-gray-600",
|
|
1826
2137
|
title: sourceOpen[index] ? "Hide source" : "Show source",
|
|
1827
2138
|
"data-testid": `mulmo-script-beat-source-toggle-${index}`,
|
|
1828
2139
|
onClick: ($event) => toggleSource(index)
|
|
1829
|
-
}, [..._cache[
|
|
2140
|
+
}, [..._cache[8] || (_cache[8] = [createElementVNode("svg", {
|
|
1830
2141
|
xmlns: "http://www.w3.org/2000/svg",
|
|
1831
2142
|
class: "w-3.5 h-3.5",
|
|
1832
2143
|
viewBox: "0 0 24 24",
|
|
@@ -1835,36 +2146,36 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1835
2146
|
"stroke-width": "2",
|
|
1836
2147
|
"stroke-linecap": "round",
|
|
1837
2148
|
"stroke-linejoin": "round"
|
|
1838
|
-
}, [createElementVNode("polyline", { points: "16 18 22 12 16 6" }), createElementVNode("polyline", { points: "8 6 2 12 8 18" })], -1)])], 8,
|
|
2149
|
+
}, [createElementVNode("polyline", { points: "16 18 22 12 16 6" }), createElementVNode("polyline", { points: "8 6 2 12 8 18" })], -1)])], 8, _hoisted_43)])])]), sourceOpen[index] ? (openBlock(), createElementBlock("div", _hoisted_44, [withDirectives(createElementVNode("textarea", {
|
|
1839
2150
|
"onUpdate:modelValue": ($event) => sourceText[index] = $event,
|
|
1840
|
-
class: normalizeClass(["w-full text-xs text-gray-600 bg-gray-50 p-2 font-mono resize-none", isValidBeat(index) ? "outline-none" : "outline outline-2 outline-red-400"]),
|
|
2151
|
+
class: normalizeClass(["w-full text-xs text-gray-600 bg-gray-50 p-2 font-mono resize-none", isValidBeat$1(index) ? "outline-none" : "outline outline-2 outline-red-400"]),
|
|
1841
2152
|
rows: "8",
|
|
1842
2153
|
spellcheck: "false",
|
|
1843
2154
|
"data-testid": `mulmo-script-beat-source-textarea-${index}`
|
|
1844
|
-
}, null, 10,
|
|
1845
|
-
class: normalizeClass(["px-2 py-1 text-xs rounded border", isValidBeat(index) && !beatSaving[index] ? "border-blue-400 text-blue-600 hover:bg-blue-50 cursor-pointer" : "border-gray-200 text-gray-300 cursor-not-allowed"]),
|
|
1846
|
-
disabled: !isValidBeat(index) || !!beatSaving[index],
|
|
2155
|
+
}, null, 10, _hoisted_45), [[vModelText, sourceText[index]]]), createElementVNode("div", _hoisted_46, [beatSaveErrors[index] ? (openBlock(), createElementBlock("span", _hoisted_47, toDisplayString(beatSaveErrors[index].kind === "invalidJson" ? unref(m).saveErrorInvalidJson(beatSaveErrors[index].error) : unref(m).saveErrorSaveFailed(beatSaveErrors[index].error)), 1)) : createCommentVNode("", true), createElementVNode("button", {
|
|
2156
|
+
class: normalizeClass(["px-2 py-1 text-xs rounded border", isValidBeat$1(index) && !beatSaving[index] ? "border-blue-400 text-blue-600 hover:bg-blue-50 cursor-pointer" : "border-gray-200 text-gray-300 cursor-not-allowed"]),
|
|
2157
|
+
disabled: !isValidBeat$1(index) || !!beatSaving[index],
|
|
1847
2158
|
"data-testid": `mulmo-script-beat-update-button-${index}`,
|
|
1848
2159
|
onClick: ($event) => updateBeat(index)
|
|
1849
|
-
}, toDisplayString(beatSaving[index] ? unref(m).saving : unref(m).update), 11,
|
|
1850
|
-
}), 128)), beats.value.length === 0 ? (openBlock(), createElementBlock("div",
|
|
1851
|
-
createElementVNode("div",
|
|
2160
|
+
}, toDisplayString(beatSaving[index] ? unref(m).saving : unref(m).update), 11, _hoisted_48)])])) : createCommentVNode("", true)]);
|
|
2161
|
+
}), 128)), beats.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_49, toDisplayString(unref(m).noBeats), 1)) : createCommentVNode("", true)], 512)),
|
|
2162
|
+
createElementVNode("div", _hoisted_50, [createElementVNode("details", {
|
|
1852
2163
|
ref_key: "sourceDetails",
|
|
1853
2164
|
ref: sourceDetails,
|
|
1854
2165
|
class: "script-source",
|
|
1855
|
-
onToggle: _cache[
|
|
2166
|
+
onToggle: _cache[2] || (_cache[2] = ($event) => onSourceToggle($event.target.open))
|
|
1856
2167
|
}, [
|
|
1857
2168
|
createElementVNode("summary", null, toDisplayString(unref(m).editSource), 1),
|
|
1858
2169
|
withDirectives(createElementVNode("textarea", {
|
|
1859
|
-
"onUpdate:modelValue": _cache[
|
|
2170
|
+
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => editableSource.value = $event),
|
|
1860
2171
|
class: normalizeClass(["script-editor", { "script-editor-invalid": sourceChanged.value && !sourceValid.value }]),
|
|
1861
2172
|
spellcheck: "false"
|
|
1862
2173
|
}, null, 2), [[vModelText, editableSource.value]]),
|
|
1863
|
-
createElementVNode("div",
|
|
2174
|
+
createElementVNode("div", _hoisted_51, [createElementVNode("button", {
|
|
1864
2175
|
class: "apply-btn",
|
|
1865
2176
|
disabled: !sourceChanged.value || !sourceValid.value,
|
|
1866
2177
|
onClick: applySource
|
|
1867
|
-
}, toDisplayString(unref(m).applyChanges), 9,
|
|
2178
|
+
}, toDisplayString(unref(m).applyChanges), 9, _hoisted_52), createElementVNode("button", {
|
|
1868
2179
|
class: "cancel-btn",
|
|
1869
2180
|
onClick: cancelSourceEdit
|
|
1870
2181
|
}, toDisplayString(unref(m).cancel), 1)])
|
|
@@ -1872,50 +2183,31 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
1872
2183
|
class: "copy-btn",
|
|
1873
2184
|
title: unref(copied) ? "Copied!" : "Copy",
|
|
1874
2185
|
onClick: copyText
|
|
1875
|
-
}, [createElementVNode("span",
|
|
1876
|
-
lightbox.value ? (openBlock(),
|
|
2186
|
+
}, [createElementVNode("span", _hoisted_54, toDisplayString(unref(copied) ? "check" : "content_copy"), 1)], 8, _hoisted_53), [[vShow, !editing.value]])]),
|
|
2187
|
+
lightbox.value ? (openBlock(), createBlock(BeatLightbox_default, {
|
|
1877
2188
|
key: 4,
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
class: normalizeClass(["group flex-1 cursor-pointer relative transition-colors", i - 1 === lightbox.value.index ? "bg-white/80 hover:bg-white" : i - 1 < lightbox.value.index ? "bg-white/40 hover:bg-white/60" : "bg-white/20 hover:bg-white/40"]),
|
|
1901
|
-
onClick: ($event) => jumpToBeat(i - 1)
|
|
1902
|
-
}, [_cache[23] || (_cache[23] = createElementVNode("span", { class: "absolute -inset-y-3 inset-x-0" }, null, -1)), beatTooltip(i - 1) ? (openBlock(), createElementBlock("div", _hoisted_94, toDisplayString(beatTooltip(i - 1)), 1)) : createCommentVNode("", true)], 10, _hoisted_93);
|
|
1903
|
-
}), 128))]), playingAudio.value && playingAudio.value.index === lightbox.value.index ? (openBlock(), createElementBlock("div", {
|
|
1904
|
-
key: 0,
|
|
1905
|
-
class: "absolute top-1/2 w-3.5 h-3.5 rounded-full bg-white shadow ring-2 ring-black/30 -translate-y-1/2 -translate-x-1/2 pointer-events-none",
|
|
1906
|
-
style: normalizeStyle({ left: `${(lightbox.value.index + audioProgress.value) / beats.value.length * 100}%` })
|
|
1907
|
-
}, null, 4)) : createCommentVNode("", true)])) : createCommentVNode("", true)]),
|
|
1908
|
-
!lightbox.value.isCharacter ? (openBlock(), createElementBlock("button", {
|
|
1909
|
-
key: 1,
|
|
1910
|
-
class: "text-white/60 hover:text-white disabled:opacity-20 text-5xl leading-none",
|
|
1911
|
-
disabled: !hasNext.value,
|
|
1912
|
-
onClick: _cache[3] || (_cache[3] = ($event) => lightboxMove(1))
|
|
1913
|
-
}, " › ", 8, _hoisted_95)) : createCommentVNode("", true)
|
|
1914
|
-
]), lightbox.value.text || beatAudios[lightbox.value.index] ? (openBlock(), createElementBlock("div", _hoisted_96, [lightbox.value.text ? (openBlock(), createElementBlock("p", _hoisted_97, toDisplayString(lightbox.value.text), 1)) : createCommentVNode("", true), beatAudios[lightbox.value.index] ? (openBlock(), createElementBlock("button", {
|
|
1915
|
-
key: 1,
|
|
1916
|
-
class: "absolute top-0 right-4 text-sm px-3 py-1 rounded border border-white/60 text-white/60 hover:bg-white/20",
|
|
1917
|
-
onClick: _cache[4] || (_cache[4] = ($event) => playAudio(lightbox.value.index))
|
|
1918
|
-
}, toDisplayString(playingAudio.value?.index === lightbox.value.index ? unref(m).stop : unref(m).play), 1)) : createCommentVNode("", true)])) : createCommentVNode("", true)])])) : createCommentVNode("", true)
|
|
2189
|
+
lightbox: lightbox.value,
|
|
2190
|
+
"beat-count": beats.value.length,
|
|
2191
|
+
"beat-texts": beatTexts.value,
|
|
2192
|
+
"has-prev": hasPrev.value,
|
|
2193
|
+
"has-next": hasNext.value,
|
|
2194
|
+
"playing-audio-index": playingAudio.value?.index ?? null,
|
|
2195
|
+
"audio-progress": audioProgress.value,
|
|
2196
|
+
"has-current-audio": Boolean(beatAudios[lightbox.value.index]),
|
|
2197
|
+
onClose: closeLightbox,
|
|
2198
|
+
onMove: lightboxMove,
|
|
2199
|
+
onJump: jumpToBeat,
|
|
2200
|
+
onPlayAudio: playAudio
|
|
2201
|
+
}, null, 8, [
|
|
2202
|
+
"lightbox",
|
|
2203
|
+
"beat-count",
|
|
2204
|
+
"beat-texts",
|
|
2205
|
+
"has-prev",
|
|
2206
|
+
"has-next",
|
|
2207
|
+
"playing-audio-index",
|
|
2208
|
+
"audio-progress",
|
|
2209
|
+
"has-current-audio"
|
|
2210
|
+
])) : createCommentVNode("", true)
|
|
1919
2211
|
]);
|
|
1920
2212
|
};
|
|
1921
2213
|
}
|
|
@@ -1929,7 +2221,7 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
|
|
|
1929
2221
|
};
|
|
1930
2222
|
//#endregion
|
|
1931
2223
|
//#region src/vue/View.vue
|
|
1932
|
-
var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-
|
|
2224
|
+
var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-3776a73b"]]);
|
|
1933
2225
|
//#endregion
|
|
1934
2226
|
//#region src/vue/Preview.vue?vue&type=script&setup=true&lang.ts
|
|
1935
2227
|
var _hoisted_1 = {
|