@kokoa/clotho-editor 0.4.0 → 0.4.1
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/clotho-editor.css
CHANGED
|
@@ -1229,6 +1229,13 @@ body.studio-player-popout #clotho-player-preview {
|
|
|
1229
1229
|
font-size: 0.66rem;
|
|
1230
1230
|
color: var(--color-fg-muted);
|
|
1231
1231
|
}
|
|
1232
|
+
/* A hint about something that will silently do nothing, rather than about taste. */
|
|
1233
|
+
.studio-props-warn {
|
|
1234
|
+
color: #b45309;
|
|
1235
|
+
background-color: color-mix(in srgb, #d97706 12%, transparent);
|
|
1236
|
+
border-radius: 4px;
|
|
1237
|
+
padding: 0.3rem 0.4rem;
|
|
1238
|
+
}
|
|
1232
1239
|
.studio-camera-chips {
|
|
1233
1240
|
display: flex;
|
|
1234
1241
|
flex-wrap: wrap;
|
|
@@ -1682,6 +1689,24 @@ body.studio-player-popout #clotho-player-preview {
|
|
|
1682
1689
|
font-size: 0.65rem;
|
|
1683
1690
|
margin-right: 0.2rem;
|
|
1684
1691
|
}
|
|
1692
|
+
/* One effect on the selected element, as a button that selects it. */
|
|
1693
|
+
.studio-effect-chip {
|
|
1694
|
+
display: block;
|
|
1695
|
+
width: 100%;
|
|
1696
|
+
text-align: left;
|
|
1697
|
+
margin-bottom: 0.25rem;
|
|
1698
|
+
padding: 0.28rem 0.4rem;
|
|
1699
|
+
border: 1px solid var(--color-border);
|
|
1700
|
+
border-radius: 4px;
|
|
1701
|
+
background-color: var(--color-surface);
|
|
1702
|
+
color: var(--color-fg);
|
|
1703
|
+
font-family: var(--font-mono);
|
|
1704
|
+
font-size: 0.66rem;
|
|
1705
|
+
cursor: pointer;
|
|
1706
|
+
}
|
|
1707
|
+
.studio-effect-chip:hover {
|
|
1708
|
+
border-color: var(--color-accent);
|
|
1709
|
+
}
|
|
1685
1710
|
.studio-add-effect-bar {
|
|
1686
1711
|
display: grid;
|
|
1687
1712
|
grid-template-columns: 1fr 1fr auto;
|
|
@@ -2237,7 +2262,8 @@ body.studio-player-popout #clotho-player-preview {
|
|
|
2237
2262
|
.studio-tl-element-track {
|
|
2238
2263
|
position: relative;
|
|
2239
2264
|
width: 100%;
|
|
2240
|
-
|
|
2265
|
+
/* Room for an effect strip under the appearance bars. */
|
|
2266
|
+
height: 42px;
|
|
2241
2267
|
user-select: none;
|
|
2242
2268
|
}
|
|
2243
2269
|
.studio-tl-appearance {
|
|
@@ -2283,6 +2309,64 @@ body.studio-player-popout #clotho-player-preview {
|
|
|
2283
2309
|
pointer-events: none;
|
|
2284
2310
|
white-space: nowrap;
|
|
2285
2311
|
}
|
|
2312
|
+
/*
|
|
2313
|
+
* Effects on the row of the element they decorate.
|
|
2314
|
+
*
|
|
2315
|
+
* They were nowhere on the timeline before, so an author scrubbing to find out
|
|
2316
|
+
* when something was emphasised had nothing to scrub against. A thin strip under
|
|
2317
|
+
* the appearance bars, because an effect is a note on an element's life rather
|
|
2318
|
+
* than a life of its own.
|
|
2319
|
+
*/
|
|
2320
|
+
.studio-tl-effect {
|
|
2321
|
+
position: absolute;
|
|
2322
|
+
top: 31px;
|
|
2323
|
+
height: 9px;
|
|
2324
|
+
border-radius: 3px;
|
|
2325
|
+
background-color: color-mix(in srgb, #d97706 45%, var(--color-surface));
|
|
2326
|
+
border: 1px solid #d97706;
|
|
2327
|
+
cursor: pointer;
|
|
2328
|
+
display: flex;
|
|
2329
|
+
align-items: center;
|
|
2330
|
+
overflow: visible;
|
|
2331
|
+
}
|
|
2332
|
+
.studio-tl-effect.is-spotlight {
|
|
2333
|
+
background-color: color-mix(in srgb, #7c3aed 45%, var(--color-surface));
|
|
2334
|
+
border-color: #7c3aed;
|
|
2335
|
+
}
|
|
2336
|
+
.studio-tl-effect.is-trail {
|
|
2337
|
+
background-color: color-mix(in srgb, #0891b2 45%, var(--color-surface));
|
|
2338
|
+
border-color: #0891b2;
|
|
2339
|
+
}
|
|
2340
|
+
.studio-tl-effect.is-selected {
|
|
2341
|
+
outline: 2px solid var(--color-accent);
|
|
2342
|
+
outline-offset: 1px;
|
|
2343
|
+
}
|
|
2344
|
+
.studio-tl-effect-label {
|
|
2345
|
+
padding: 0 4px;
|
|
2346
|
+
pointer-events: none;
|
|
2347
|
+
white-space: nowrap;
|
|
2348
|
+
font-family: var(--font-mono);
|
|
2349
|
+
font-size: 0.58rem;
|
|
2350
|
+
line-height: 1;
|
|
2351
|
+
color: var(--color-fg);
|
|
2352
|
+
opacity: 0.85;
|
|
2353
|
+
}
|
|
2354
|
+
/*
|
|
2355
|
+
* How far a trail reaches back before its own start. `window` is the field
|
|
2356
|
+
* authors get wrong, and drawing it makes it a length rather than a number.
|
|
2357
|
+
*/
|
|
2358
|
+
.studio-tl-effect-reach {
|
|
2359
|
+
position: absolute;
|
|
2360
|
+
top: 2px;
|
|
2361
|
+
bottom: 2px;
|
|
2362
|
+
border-radius: 3px 0 0 3px;
|
|
2363
|
+
background: repeating-linear-gradient(
|
|
2364
|
+
90deg,
|
|
2365
|
+
color-mix(in srgb, #0891b2 40%, transparent) 0 3px,
|
|
2366
|
+
transparent 3px 6px
|
|
2367
|
+
);
|
|
2368
|
+
pointer-events: none;
|
|
2369
|
+
}
|
|
2286
2370
|
.studio-tl-keyframe {
|
|
2287
2371
|
position: absolute;
|
|
2288
2372
|
top: 50%;
|
package/dist/index.js
CHANGED
|
@@ -703,7 +703,7 @@ function StudioMount({
|
|
|
703
703
|
if (repository) configureAnimationRepository(repository);
|
|
704
704
|
let disposed = false;
|
|
705
705
|
let cleanup;
|
|
706
|
-
void import('./main-
|
|
706
|
+
void import('./main-VRQTHHZE.js').then(({ initStudio }) => {
|
|
707
707
|
if (disposed) return;
|
|
708
708
|
cleanup = initStudio({
|
|
709
709
|
initialId,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { updateCanvas, getDef, subscribe, updateMeta, mountEditorPlugins, setSelection, getSelection, setDef, undo, redo, downloadAnimationJson, importAnimation, markClean, updateSettings, isDirty, isDraft, canUndo, canRedo, uniqueChapterId, getCurrentTime, addChapter, uniqueElementId, registerExternalAsset, addElement, registerDataUriAsset, listAnimations, setDraft, saveAnimation, deleteAnimation, duplicateAnimation, createAnimation, getSelectedElementIds, deleteElement, deleteChapter, deleteEffect, updateChapter, isGroup, ungroupElement, groupElements, moveElementToEnd, moveElementToFront, reorderElement, loadAnimation, getCurrentSnapshot, setElementValueAtTime, findContainingGroup, isElementSelected, updateElementBase, createLayout, detachFromLayout, addCheckpoint, uniqueCheckpointId, deleteCheckpoint, addCameraFocus, updateCameraFocus, deleteCameraFocus, setCameraKeyframe, removeCameraKeyframe, removeCameraTrack, setCurrentTime, clearCamera,
|
|
2
|
-
import { activeAppearance, animationDocumentSchema, bindablePropertiesFor, computeCamera, resolveAsset, compileDataBindings } from '@kokoa/clotho';
|
|
1
|
+
import { updateCanvas, getDef, subscribe, updateMeta, mountEditorPlugins, setSelection, getSelection, setDef, undo, redo, downloadAnimationJson, importAnimation, markClean, updateSettings, isDirty, isDraft, canUndo, canRedo, uniqueChapterId, getCurrentTime, addChapter, uniqueElementId, registerExternalAsset, addElement, registerDataUriAsset, listAnimations, setDraft, saveAnimation, deleteAnimation, duplicateAnimation, createAnimation, getSelectedElementIds, deleteElement, deleteChapter, deleteEffect, updateChapter, isGroup, ungroupElement, groupElements, moveElementToEnd, moveElementToFront, reorderElement, loadAnimation, getCurrentSnapshot, setElementValueAtTime, findContainingGroup, isElementSelected, updateElementBase, createLayout, detachFromLayout, addCheckpoint, uniqueCheckpointId, deleteCheckpoint, addCameraFocus, updateCameraFocus, deleteCameraFocus, setCameraKeyframe, removeCameraKeyframe, removeCameraTrack, setCurrentTime, clearCamera, removeAppearance, removeTrack, removeTrackKeyframe, setTrackKeyframe, addAppearance, beginTransient, endTransient, getCamera, jumpBack, jumpForward, getHistory, promoteDraftToSaved, toggleSelectionFor, cameraControlAt, moveGroupBy, placeholderImageUrl, groupBbox, hasCamera, layoutIdsFor, findLayoutCollisions, updateEffect, updateLocales, updateData, updateResponsive, updateDuration, updateCheckpoint, setCameraStrokeScaling, updateAppearance, uniqueEffectId, addEffect, moveCameraKeyframe, childIdsOf, replaceEffect } from './chunk-WOP52KFW.js';
|
|
2
|
+
import { activeAppearance, animationDocumentSchema, bindablePropertiesFor, computeCamera, resolveAsset, buildScene, compileDataBindings, resolveBlendMode } from '@kokoa/clotho';
|
|
3
3
|
|
|
4
4
|
// src/legacy/grid.ts
|
|
5
5
|
var STORAGE_KEY = "studio.grid";
|
|
@@ -2239,6 +2239,8 @@ function render2() {
|
|
|
2239
2239
|
if (cameraFrame) canvasEl.appendChild(cameraFrame);
|
|
2240
2240
|
const spotlightPreview = renderSpotlightPreview(def, snap2, elementsById);
|
|
2241
2241
|
if (spotlightPreview) canvasEl.appendChild(spotlightPreview);
|
|
2242
|
+
const trailPreview = renderTrailPreview(def);
|
|
2243
|
+
if (trailPreview) canvasEl.appendChild(trailPreview);
|
|
2242
2244
|
const selection = getSelection();
|
|
2243
2245
|
if (selection.kind === "element") {
|
|
2244
2246
|
const selEl = elementsById.get(selection.elementId);
|
|
@@ -2608,6 +2610,49 @@ function renderSpotlightPreview(def, snap2, elementsById) {
|
|
|
2608
2610
|
g.appendChild(label);
|
|
2609
2611
|
return g;
|
|
2610
2612
|
}
|
|
2613
|
+
function renderTrailPreview(def) {
|
|
2614
|
+
const selection = getSelection();
|
|
2615
|
+
if (selection.kind !== "effect") return null;
|
|
2616
|
+
const effect = def.effects.find(
|
|
2617
|
+
(candidate) => candidate.id === selection.effectId
|
|
2618
|
+
);
|
|
2619
|
+
if (!effect || effect.type !== "trail") return null;
|
|
2620
|
+
const time = getCurrentTime();
|
|
2621
|
+
const g = document.createElementNS(SVG_NS, "g");
|
|
2622
|
+
g.setAttribute("class", "studio-trail-preview");
|
|
2623
|
+
g.setAttribute("pointer-events", "none");
|
|
2624
|
+
const label = document.createElementNS(SVG_NS, "text");
|
|
2625
|
+
label.setAttribute("x", "8");
|
|
2626
|
+
label.setAttribute("y", "18");
|
|
2627
|
+
label.setAttribute("class", "studio-spotlight-preview-label");
|
|
2628
|
+
const span = `${effect.time}\u2013${effect.time + effect.duration}ms`;
|
|
2629
|
+
if (time < effect.time || time >= effect.time + effect.duration) {
|
|
2630
|
+
label.textContent = `\u2733 ${effect.id} \xB7 \uC7AC\uC0DD \uC704\uCE58\uAC00 ${span} \uBC16\uC785\uB2C8\uB2E4`;
|
|
2631
|
+
g.appendChild(label);
|
|
2632
|
+
return g;
|
|
2633
|
+
}
|
|
2634
|
+
const pieces = buildScene(def, time, {}).nodes.filter(
|
|
2635
|
+
(node) => typeof node.key === "string" && node.key.startsWith(`${effect.id}-`)
|
|
2636
|
+
);
|
|
2637
|
+
if (pieces.length === 0) {
|
|
2638
|
+
label.textContent = `\u2733 ${effect.id} \xB7 \uCD5C\uADFC ${effect.window}ms \uB3D9\uC548 ${effect.elementId}\uAC00 \uC6C0\uC9C1\uC774\uC9C0 \uC54A\uC544 \uAF2C\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4`;
|
|
2639
|
+
g.appendChild(label);
|
|
2640
|
+
return g;
|
|
2641
|
+
}
|
|
2642
|
+
for (const piece of pieces) {
|
|
2643
|
+
const node = document.createElementNS(SVG_NS, piece.kind);
|
|
2644
|
+
for (const [name, value] of Object.entries(piece.attrs)) {
|
|
2645
|
+
if (value !== void 0 && value !== null) {
|
|
2646
|
+
node.setAttribute(name, String(value));
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
g.appendChild(node);
|
|
2650
|
+
}
|
|
2651
|
+
const kind = pieces[0].kind === "circle" ? "\uC810" : "\uC120";
|
|
2652
|
+
label.textContent = `\u2733 ${effect.id} \xB7 window ${effect.window}ms \xB7 ${kind} ${pieces.length}\uAC1C (samples ${effect.samples})`;
|
|
2653
|
+
g.appendChild(label);
|
|
2654
|
+
return g;
|
|
2655
|
+
}
|
|
2611
2656
|
function cameraHandles(view, control) {
|
|
2612
2657
|
const g = document.createElementNS(SVG_NS, "g");
|
|
2613
2658
|
if (control.kind === "tracks") {
|
|
@@ -3253,6 +3298,7 @@ function renderInner() {
|
|
|
3253
3298
|
<option value="pulse">pulse</option>
|
|
3254
3299
|
<option value="flow">flow</option>
|
|
3255
3300
|
<option value="spotlight">spotlight</option>
|
|
3301
|
+
<option value="trail">trail</option>
|
|
3256
3302
|
</select>
|
|
3257
3303
|
<button type="button" class="studio-btn" data-add-effect>\uFF0B \uD6A8\uACFC</button>
|
|
3258
3304
|
</div>
|
|
@@ -3344,7 +3390,7 @@ function renderInner() {
|
|
|
3344
3390
|
setSelection({ kind: "none" });
|
|
3345
3391
|
return;
|
|
3346
3392
|
}
|
|
3347
|
-
const typeOpts = ["highlight", "pulse", "flow", "spotlight"].map(
|
|
3393
|
+
const typeOpts = ["highlight", "pulse", "flow", "spotlight", "trail"].map(
|
|
3348
3394
|
(t) => `<option value="${t}" ${t === eff.type ? "selected" : ""}>${t}</option>`
|
|
3349
3395
|
).join("");
|
|
3350
3396
|
if (eff.type === "spotlight") {
|
|
@@ -3375,6 +3421,36 @@ function renderInner() {
|
|
|
3375
3421
|
`;
|
|
3376
3422
|
return;
|
|
3377
3423
|
}
|
|
3424
|
+
if (eff.type === "trail") {
|
|
3425
|
+
const elemOptsTrail = def.elements.map(
|
|
3426
|
+
(e) => `<option value="${escapeHtml2(e.id)}" ${e.id === eff.elementId ? "selected" : ""}>${escapeHtml2(e.id)}</option>`
|
|
3427
|
+
).join("");
|
|
3428
|
+
const resolved = trailModeHint(def, eff);
|
|
3429
|
+
panelEl.innerHTML = `
|
|
3430
|
+
${timeHint}
|
|
3431
|
+
<div class="studio-props-header"><span class="studio-props-header-title">${escapeHtml2(eff.id)}</span><span class="studio-props-header-type">trail</span></div>
|
|
3432
|
+
<label class="studio-field"><span>type</span><select data-prop-key="effect.type">${typeOpts}</select></label>
|
|
3433
|
+
<p class="studio-props-empty" style="margin:0 0 0.4rem">\uC694\uC18C\uAC00 \uC9C0\uB098\uC628 \uC790\uB9AC\uB97C \uAF2C\uB9AC\uB85C \uB0A8\uAE41\uB2C8\uB2E4. \uC6C0\uC9C1\uC784 \uC790\uCCB4\uAC00 \uC815\uBCF4\uC778 \uBB38\uC11C\uC5D0\uC11C, \uC815\uC9C0 \uD504\uB808\uC784\uC774 \uC783\uC5B4\uBC84\uB9AC\uB294 \uAC83\uC744 \uB418\uB3CC\uB824 \uC90D\uB2C8\uB2E4.</p>
|
|
3434
|
+
<label class="studio-field"><span>elementId</span><select data-prop-key="effect.elementId">${elemOptsTrail}</select></label>
|
|
3435
|
+
${trailMotionWarning(def, eff)}
|
|
3436
|
+
${numberField("time (ms)", "effect.time", eff.time, 50)}
|
|
3437
|
+
${numberField("duration (ms)", "effect.duration", eff.duration, 50)}
|
|
3438
|
+
<div class="studio-props-header" style="margin-top:0.6rem"><span class="studio-props-header-title">\uAF2C\uB9AC</span></div>
|
|
3439
|
+
${rangeField("window (ms) \xB7 \uAF2C\uB9AC \uAE38\uC774", "effect.window", eff.window, 100, 5e3, 50, "\uC5BC\uB9C8\uB098 \uAC70\uC2AC\uB7EC \uC62C\uB77C\uAC08\uC9C0\uC785\uB2C8\uB2E4. \uD55C \uAC78\uC74C\uC758 \uBC15\uC790\uC5D0 \uB9DE\uCD94\uC138\uC694 \u2014 \uC774\uC0B0 \uCEE4\uC11C\uB294 \uD55C \uCE78, \uD6D1\uB294 \uC6C0\uC9C1\uC784\uC740 \uC870\uAE08 \uB354.")}
|
|
3440
|
+
${rangeField("samples \xB7 \uD574\uC0C1\uB3C4", "effect.samples", eff.samples, 2, 32, 1, "\uCC3D\uC744 \uBA87 \uAC1C\uB85C \uB098\uB20C\uC9C0\uC785\uB2C8\uB2E4. \uC62C\uB824\uB3C4 \uAF2C\uB9AC\uAC00 \uAE38\uC5B4\uC9C0\uC9C0\uB294 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uAE38\uC774\uB294 window\uAC00 \uC815\uD569\uB2C8\uB2E4.")}
|
|
3441
|
+
${selectField("mode", "effect.mode", eff.mode, [
|
|
3442
|
+
{ value: "auto", label: "auto (\uBCF4\uAC04\uC73C\uB85C \uACB0\uC815)" },
|
|
3443
|
+
{ value: "path", label: "path \u2014 \uC120" },
|
|
3444
|
+
{ value: "dots", label: "dots \u2014 \uC810" }
|
|
3445
|
+
])}
|
|
3446
|
+
<p class="studio-camera-hint">${resolved}</p>
|
|
3447
|
+
${checkboxField("fade \xB7 \uC624\uB798\uB41C \uCABD\uC744 \uD750\uB9AC\uAC8C", "effect.fade", eff.fade)}
|
|
3448
|
+
${colorField("color", "effect.color", eff.color)}
|
|
3449
|
+
${rangeField("width", "effect.width", eff.width, 0.5, 16, 0.5, "path\uB294 \uC120\uC758 \uAD75\uAE30, dots\uB294 \uC810\uC758 \uBC18\uC9C0\uB984\uC785\uB2C8\uB2E4.")}
|
|
3450
|
+
<button type="button" class="studio-btn studio-btn-danger" data-delete-effect style="margin-top:0.6rem">\u{1F5D1} \uD6A8\uACFC \uC0AD\uC81C</button>
|
|
3451
|
+
`;
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3378
3454
|
const elemOpts = def.elements.map(
|
|
3379
3455
|
(e) => `<option value="${escapeHtml2(e.id)}" ${e.id === eff.elementId ? "selected" : ""}>${escapeHtml2(e.id)}</option>`
|
|
3380
3456
|
).join("");
|
|
@@ -3530,11 +3606,13 @@ function renderElementForm(def, el) {
|
|
|
3530
3606
|
const baseHeader = `<span class="studio-props-header-title">${escapeHtml2(el.id)}</span><span class="studio-props-header-type">${escapeHtml2(el.type)}</span>`;
|
|
3531
3607
|
const apHeader = `<span class="studio-props-header-title">\uCD9C\uD604 (Appearances)</span><button type="button" class="studio-btn studio-btn-small" data-add-appearance>\uFF0B</button>`;
|
|
3532
3608
|
const tracksHeader = `<span class="studio-props-header-title">\uD0A4\uD504\uB808\uC784 \uD2B8\uB799 (${el.tracks.length})</span>`;
|
|
3609
|
+
const effects = renderElementEffects(def, el);
|
|
3533
3610
|
panelEl.innerHTML = `
|
|
3534
3611
|
${timeHint}
|
|
3535
3612
|
${section("el-base", baseHeader, baseFields)}
|
|
3536
3613
|
${section("el-appearances", apHeader, appearances)}
|
|
3537
3614
|
${section("el-tracks", tracksHeader, tracks)}
|
|
3615
|
+
${section("el-effects", `<span class="studio-props-header-title">\uD6A8\uACFC</span>`, effects)}
|
|
3538
3616
|
${section("el-bindings", `<span class="studio-props-header-title">\uB370\uC774\uD130 \uC5F0\uACB0 (${el.bindings.length})</span>`, bindings)}
|
|
3539
3617
|
<div class="studio-props-empty" style="font-size:0.72rem;margin-top:0.5rem">
|
|
3540
3618
|
base \uC18D\uC131\uC744 \uBCC0\uACBD\uD558\uBA74 \u2192 t=${getCurrentTime()} ms \uC5D0 keyframe \uCD94\uAC00<br/>
|
|
@@ -3542,6 +3620,25 @@ function renderElementForm(def, el) {
|
|
|
3542
3620
|
</div>
|
|
3543
3621
|
`;
|
|
3544
3622
|
}
|
|
3623
|
+
function renderElementEffects(def, el) {
|
|
3624
|
+
const mine = def.effects.filter(
|
|
3625
|
+
(effect) => effect.type === "spotlight" ? effect.elementIds.includes(el.id) : effect.elementId === el.id
|
|
3626
|
+
);
|
|
3627
|
+
const rows = mine.map(
|
|
3628
|
+
(effect) => `<button type="button" class="studio-effect-chip" data-select-effect="${escapeHtml2(effect.id)}">${escapeHtml2(effect.type)} \xB7 ${effect.time}\u2013${effect.time + effect.duration}ms</button>`
|
|
3629
|
+
).join("");
|
|
3630
|
+
return `${rows || '<p class="studio-props-empty" style="font-size:0.72rem">\uC774 \uC694\uC18C\uC5D0 \uAC78\uB9B0 \uD6A8\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.</p>'}
|
|
3631
|
+
<div class="studio-add-effect-bar">
|
|
3632
|
+
<select data-new-effect-for="${escapeHtml2(el.id)}">
|
|
3633
|
+
<option value="highlight">highlight</option>
|
|
3634
|
+
<option value="pulse">pulse</option>
|
|
3635
|
+
<option value="flow">flow</option>
|
|
3636
|
+
<option value="spotlight">spotlight</option>
|
|
3637
|
+
<option value="trail">trail</option>
|
|
3638
|
+
</select>
|
|
3639
|
+
<button type="button" class="studio-btn" data-add-effect-for="${escapeHtml2(el.id)}">\uFF0B \uD6A8\uACFC</button>
|
|
3640
|
+
</div>`;
|
|
3641
|
+
}
|
|
3545
3642
|
function renderBindings(el) {
|
|
3546
3643
|
const properties = bindablePropertiesFor(el);
|
|
3547
3644
|
const rows = el.bindings.map(
|
|
@@ -4045,7 +4142,7 @@ function apply(key, value) {
|
|
|
4045
4142
|
} else if (key.startsWith("effect.") && sel.kind === "effect") {
|
|
4046
4143
|
const prop = key.slice(7);
|
|
4047
4144
|
const patch = {};
|
|
4048
|
-
if (prop === "time" || prop === "duration" || prop === "scale" || prop === "particles" || prop === "radius" || prop === "dim" || prop === "lit" || prop === "padding" || prop === "fadeIn") {
|
|
4145
|
+
if (prop === "time" || prop === "duration" || prop === "scale" || prop === "particles" || prop === "radius" || prop === "dim" || prop === "lit" || prop === "padding" || prop === "fadeIn" || prop === "window" || prop === "samples" || prop === "width") {
|
|
4049
4146
|
patch[prop] = Number(value);
|
|
4050
4147
|
} else {
|
|
4051
4148
|
patch[prop] = value;
|
|
@@ -4053,6 +4150,94 @@ function apply(key, value) {
|
|
|
4053
4150
|
updateEffect(sel.effectId, patch);
|
|
4054
4151
|
}
|
|
4055
4152
|
}
|
|
4153
|
+
var TRAIL_POSITION_PROPERTIES = /* @__PURE__ */ new Set([
|
|
4154
|
+
"x",
|
|
4155
|
+
"y",
|
|
4156
|
+
"cx",
|
|
4157
|
+
"cy",
|
|
4158
|
+
"x1",
|
|
4159
|
+
"y1",
|
|
4160
|
+
"x2",
|
|
4161
|
+
"y2"
|
|
4162
|
+
]);
|
|
4163
|
+
function trailPositionTracks(def, elementId) {
|
|
4164
|
+
const el = def.elements.find((candidate) => candidate.id === elementId);
|
|
4165
|
+
return (el?.tracks ?? []).filter(
|
|
4166
|
+
(track) => TRAIL_POSITION_PROPERTIES.has(track.property)
|
|
4167
|
+
);
|
|
4168
|
+
}
|
|
4169
|
+
function trailModeHint(def, effect) {
|
|
4170
|
+
if (effect.mode === "path") return "\uC0D8\uD50C\uC744 \uC120\uC73C\uB85C \uC787\uC2B5\uB2C8\uB2E4.";
|
|
4171
|
+
if (effect.mode === "dots") return "\uC0D8\uD50C\uB9C8\uB2E4 \uC810\uC744 \uCC0D\uC2B5\uB2C8\uB2E4.";
|
|
4172
|
+
const tracks = trailPositionTracks(def, effect.elementId);
|
|
4173
|
+
if (tracks.length === 0) return "\uC704\uCE58 track\uC774 \uC5C6\uC73C\uBBC0\uB85C \uC120\uC73C\uB85C \uADF8\uB9BD\uB2C8\uB2E4.";
|
|
4174
|
+
const steps = tracks.every(
|
|
4175
|
+
(track) => resolveBlendMode(track.interpolate, track.property) === "discrete"
|
|
4176
|
+
);
|
|
4177
|
+
return steps ? "auto \u2192 \uC810. \uC704\uCE58 track\uC774 \uC774\uC0B0 \uBCF4\uAC04\uC774\uB77C \uC9C0\uB098\uC9C0 \uC54A\uC740 \uACF3\uC744 \uC787\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "auto \u2192 \uC120. \uC704\uCE58\uAC00 \uC5F0\uC18D\uC73C\uB85C \uBCC0\uD558\uBBC0\uB85C \uC0D8\uD50C\uC744 \uC774\uC5B4\uB3C4 \uAC70\uC9D3\uC774 \uC544\uB2D9\uB2C8\uB2E4.";
|
|
4178
|
+
}
|
|
4179
|
+
function trailMotionWarning(def, effect) {
|
|
4180
|
+
const el = def.elements.find(
|
|
4181
|
+
(candidate) => candidate.id === effect.elementId
|
|
4182
|
+
);
|
|
4183
|
+
if (!el) return "";
|
|
4184
|
+
if (trailPositionTracks(def, effect.elementId).length > 0) return "";
|
|
4185
|
+
const inGroup = el.parentId !== void 0;
|
|
4186
|
+
return `<p class="studio-camera-hint studio-props-warn">\u26A0 <code>${escapeHtml2(effect.elementId)}</code>\uC5D0 \uC704\uCE58 track\uC774 \uC5C6\uC2B5\uB2C8\uB2E4${inGroup ? " (\uBD80\uBAA8 group\uC774 \uC6C0\uC9C1\uC778\uB2E4\uBA74 \uAF2C\uB9AC\uB294 \uB0A8\uC2B5\uB2C8\uB2E4)" : ""}. \uC6C0\uC9C1\uC774\uC9C0 \uC54A\uB294 \uC694\uC18C\uB294 \uAF2C\uB9AC\uB97C \uB0A8\uAE30\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.</p>`;
|
|
4187
|
+
}
|
|
4188
|
+
function createEffect(def, type, seeds) {
|
|
4189
|
+
const firstEl = def.elements[0];
|
|
4190
|
+
if (!firstEl) return;
|
|
4191
|
+
const targets = seeds.length > 0 ? seeds : [firstEl.id];
|
|
4192
|
+
const id = uniqueEffectId();
|
|
4193
|
+
const time = getCurrentTime();
|
|
4194
|
+
const base = { id, elementId: targets[0], time, duration: 500 };
|
|
4195
|
+
let effect;
|
|
4196
|
+
if (type === "spotlight") {
|
|
4197
|
+
effect = {
|
|
4198
|
+
id,
|
|
4199
|
+
type: "spotlight",
|
|
4200
|
+
elementIds: [...targets],
|
|
4201
|
+
time,
|
|
4202
|
+
duration: 1200,
|
|
4203
|
+
dim: 0.7,
|
|
4204
|
+
lit: 0,
|
|
4205
|
+
litColor: "#fde68a",
|
|
4206
|
+
shape: "bbox",
|
|
4207
|
+
padding: 12,
|
|
4208
|
+
fadeIn: 200
|
|
4209
|
+
};
|
|
4210
|
+
} else if (type === "trail") {
|
|
4211
|
+
effect = {
|
|
4212
|
+
id,
|
|
4213
|
+
type: "trail",
|
|
4214
|
+
elementId: targets[0],
|
|
4215
|
+
time,
|
|
4216
|
+
duration: Math.max(1e3, def.duration - time),
|
|
4217
|
+
window: 1200,
|
|
4218
|
+
samples: 12,
|
|
4219
|
+
mode: "auto",
|
|
4220
|
+
fade: true,
|
|
4221
|
+
color: "#94a3b8",
|
|
4222
|
+
width: 2
|
|
4223
|
+
};
|
|
4224
|
+
} else if (type === "pulse") {
|
|
4225
|
+
effect = { ...base, type: "pulse", scale: 1.12 };
|
|
4226
|
+
} else if (type === "flow") {
|
|
4227
|
+
effect = {
|
|
4228
|
+
...base,
|
|
4229
|
+
type: "flow",
|
|
4230
|
+
color: "#facc15",
|
|
4231
|
+
particles: 3,
|
|
4232
|
+
radius: 4,
|
|
4233
|
+
duration: 800
|
|
4234
|
+
};
|
|
4235
|
+
} else {
|
|
4236
|
+
effect = { ...base, type: "highlight", color: "#facc15" };
|
|
4237
|
+
}
|
|
4238
|
+
addEffect(effect);
|
|
4239
|
+
setSelection({ kind: "effect", effectId: id });
|
|
4240
|
+
}
|
|
4056
4241
|
function changeEffectType(def, effectId, type) {
|
|
4057
4242
|
const current = def.effects.find((effect) => effect.id === effectId);
|
|
4058
4243
|
if (!current || current.type === type) return;
|
|
@@ -4072,6 +4257,21 @@ function changeEffectType(def, effectId, type) {
|
|
|
4072
4257
|
padding: 12,
|
|
4073
4258
|
fadeIn: 200
|
|
4074
4259
|
};
|
|
4260
|
+
} else if (type === "trail") {
|
|
4261
|
+
next = {
|
|
4262
|
+
...base,
|
|
4263
|
+
type: "trail",
|
|
4264
|
+
elementId: targets[0] ?? "",
|
|
4265
|
+
// A trail needs long enough to be seen moving; the emphasis effects default
|
|
4266
|
+
// to half a second, which is shorter than most single steps.
|
|
4267
|
+
duration: Math.max(current.duration, 3e3),
|
|
4268
|
+
window: 1200,
|
|
4269
|
+
samples: 12,
|
|
4270
|
+
mode: "auto",
|
|
4271
|
+
fade: true,
|
|
4272
|
+
color: "#94a3b8",
|
|
4273
|
+
width: 2
|
|
4274
|
+
};
|
|
4075
4275
|
} else {
|
|
4076
4276
|
const single = {
|
|
4077
4277
|
...base,
|
|
@@ -4278,45 +4478,25 @@ function onClick2(e) {
|
|
|
4278
4478
|
const typeSel = document.getElementById(
|
|
4279
4479
|
"studio-new-effect-type"
|
|
4280
4480
|
);
|
|
4281
|
-
const
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
dim: 0.7,
|
|
4301
|
-
lit: 0,
|
|
4302
|
-
litColor: "#fde68a",
|
|
4303
|
-
shape: "bbox",
|
|
4304
|
-
padding: 12,
|
|
4305
|
-
fadeIn: 200
|
|
4306
|
-
};
|
|
4307
|
-
} else if (type === "highlight")
|
|
4308
|
-
eff = { ...base, type: "highlight", color: "#facc15" };
|
|
4309
|
-
else if (type === "pulse") eff = { ...base, type: "pulse", scale: 1.12 };
|
|
4310
|
-
else
|
|
4311
|
-
eff = {
|
|
4312
|
-
...base,
|
|
4313
|
-
type: "flow",
|
|
4314
|
-
color: "#facc15",
|
|
4315
|
-
particles: 3,
|
|
4316
|
-
radius: 4,
|
|
4317
|
-
duration: 800
|
|
4318
|
-
};
|
|
4319
|
-
addEffect(eff);
|
|
4481
|
+
const seeds = sel.kind === "element" ? [sel.elementId] : sel.kind === "elements" ? [...sel.elementIds] : [];
|
|
4482
|
+
createEffect(def, typeSel?.value ?? "highlight", seeds);
|
|
4483
|
+
return;
|
|
4484
|
+
}
|
|
4485
|
+
const addFor = target.closest("[data-add-effect-for]");
|
|
4486
|
+
if (addFor) {
|
|
4487
|
+
const elementId = addFor.dataset.addEffectFor;
|
|
4488
|
+
const typeSel = panelEl?.querySelector(
|
|
4489
|
+
`[data-new-effect-for="${CSS.escape(elementId)}"]`
|
|
4490
|
+
);
|
|
4491
|
+
createEffect(def, typeSel?.value ?? "highlight", [elementId]);
|
|
4492
|
+
return;
|
|
4493
|
+
}
|
|
4494
|
+
const pickEffect = target.closest("[data-select-effect]");
|
|
4495
|
+
if (pickEffect) {
|
|
4496
|
+
setSelection({
|
|
4497
|
+
kind: "effect",
|
|
4498
|
+
effectId: pickEffect.dataset.selectEffect
|
|
4499
|
+
});
|
|
4320
4500
|
return;
|
|
4321
4501
|
}
|
|
4322
4502
|
if (target.closest("[data-delete-effect]") && sel.kind === "effect") {
|
|
@@ -4545,7 +4725,7 @@ function render4() {
|
|
|
4545
4725
|
</div>
|
|
4546
4726
|
<div class="studio-tl-total">\uC804\uCCB4 ${def.duration} ms \xB7 ${sortedChapters.length} chapters \xB7 ${def.elements.length} elements \xB7 \uD604\uC7AC ${currentTime} ms</div>
|
|
4547
4727
|
`;
|
|
4548
|
-
renderElementTracks(def
|
|
4728
|
+
renderElementTracks(def, currentTime, totalPx, sel);
|
|
4549
4729
|
}
|
|
4550
4730
|
function renderCameraRows(totalPx, sel) {
|
|
4551
4731
|
const camera = getCamera();
|
|
@@ -4585,8 +4765,18 @@ function renderCameraRows(totalPx, sel) {
|
|
|
4585
4765
|
function gutterLabel(el) {
|
|
4586
4766
|
return `${friendlyElementLabel(el)} \xB7 ${el.type}`;
|
|
4587
4767
|
}
|
|
4588
|
-
function renderElementTracks(
|
|
4768
|
+
function renderElementTracks(def, currentTime, totalPx, sel) {
|
|
4589
4769
|
if (!elTracksEl) return;
|
|
4770
|
+
const elements = def.elements;
|
|
4771
|
+
const effectsByElement = /* @__PURE__ */ new Map();
|
|
4772
|
+
for (const effect of def.effects) {
|
|
4773
|
+
const targets = effect.type === "spotlight" ? effect.elementIds : [effect.elementId];
|
|
4774
|
+
for (const target of targets) {
|
|
4775
|
+
const bucket = effectsByElement.get(target);
|
|
4776
|
+
if (bucket) bucket.push(effect);
|
|
4777
|
+
else effectsByElement.set(target, [effect]);
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4590
4780
|
if (elements.length === 0) {
|
|
4591
4781
|
elTracksEl.innerHTML = '<p class="studio-tl-empty">\uC694\uC18C \uC5C6\uC74C</p>';
|
|
4592
4782
|
return;
|
|
@@ -4620,12 +4810,24 @@ function renderElementTracks(elements, currentTime, totalPx, sel) {
|
|
|
4620
4810
|
).map(
|
|
4621
4811
|
(kf) => `<div class="studio-tl-keyframe" style="left:${timeToPx(kf.time)}px" data-kf-elem-id="${escapeHtml3(el.id)}" data-kf-prop="${escapeHtml3(kf.prop)}" data-kf-time="${kf.time}" title="${escapeHtml3(kf.prop)} @ ${kf.time}ms (\uB4DC\uB798\uADF8\uB85C \uC774\uB3D9)">\u25C6</div>`
|
|
4622
4812
|
).join("");
|
|
4813
|
+
const effectBars = (effectsByElement.get(el.id) ?? []).map((effect) => {
|
|
4814
|
+
const left = timeToPx(effect.time);
|
|
4815
|
+
const width = Math.max(6, timeToPx(effect.duration));
|
|
4816
|
+
const selected = sel.kind === "effect" && sel.effectId === effect.id;
|
|
4817
|
+
const trailWindow = effect.type === "trail" ? effect.window : 0;
|
|
4818
|
+
const lead = Math.min(timeToPx(trailWindow), left);
|
|
4819
|
+
const reach = lead > 0 ? `<span class="studio-tl-effect-reach" style="left:${-lead}px;width:${lead}px" title="window ${trailWindow}ms"></span>` : "";
|
|
4820
|
+
return `<div class="studio-tl-effect ${selected ? "is-selected" : ""} is-${escapeHtml3(effect.type)}" style="left:${left}px;width:${width}px" data-effect-id="${escapeHtml3(effect.id)}" title="${escapeHtml3(effect.type)} ${escapeHtml3(effect.id)} \xB7 ${effect.time}-${effect.time + effect.duration}ms">
|
|
4821
|
+
${reach}<span class="studio-tl-effect-label">${escapeHtml3(effect.type)}</span>
|
|
4822
|
+
</div>`;
|
|
4823
|
+
}).join("");
|
|
4623
4824
|
return `
|
|
4624
4825
|
<div class="studio-tl-row studio-tl-element-row ${isSel ? "is-selected" : ""} ${el.type === "group" ? "is-group" : ""}" data-elem-id="${escapeHtml3(el.id)}">
|
|
4625
4826
|
<div class="studio-tl-gutter studio-tl-element-label" style="--studio-tree-depth:${depth}" title="${escapeHtml3(el.id)}">${el.type === "group" ? "\u25BE " : depth > 0 ? "\u21B3 " : ""}${escapeHtml3(gutterLabel(el))}</div>
|
|
4626
4827
|
<div class="studio-tl-body" style="width:${totalPx}px">
|
|
4627
4828
|
<div class="studio-tl-element-track" data-tl-area="elements">
|
|
4628
4829
|
${appearanceBars}
|
|
4830
|
+
${effectBars}
|
|
4629
4831
|
${trackKfs}
|
|
4630
4832
|
</div>
|
|
4631
4833
|
</div>
|
|
@@ -4675,6 +4877,11 @@ function onElTracksClick(e) {
|
|
|
4675
4877
|
if (dragMode) return;
|
|
4676
4878
|
const target = e.target;
|
|
4677
4879
|
if (target.closest("[data-ap-idx]") || target.closest("[data-edge]")) return;
|
|
4880
|
+
const effectBar = target.closest("[data-effect-id]");
|
|
4881
|
+
if (effectBar) {
|
|
4882
|
+
setSelection({ kind: "effect", effectId: effectBar.dataset.effectId });
|
|
4883
|
+
return;
|
|
4884
|
+
}
|
|
4678
4885
|
const row = target.closest("[data-elem-id]");
|
|
4679
4886
|
if (row) {
|
|
4680
4887
|
setSelection({ kind: "element", elementId: row.dataset.elemId });
|
|
@@ -18096,5 +18303,5 @@ function reflectState(ui) {
|
|
|
18096
18303
|
}
|
|
18097
18304
|
|
|
18098
18305
|
export { getVisibleElementIds, initStudio };
|
|
18099
|
-
//# sourceMappingURL=main-
|
|
18100
|
-
//# sourceMappingURL=main-
|
|
18306
|
+
//# sourceMappingURL=main-VRQTHHZE.js.map
|
|
18307
|
+
//# sourceMappingURL=main-VRQTHHZE.js.map
|