@kokoa/clotho-editor 0.3.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/{chunk-N47HF4VB.js → chunk-WOP52KFW.js} +22 -4
- package/dist/chunk-WOP52KFW.js.map +1 -0
- package/dist/clotho-editor.css +90 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +3 -3
- package/dist/{main-DC6D6RNQ.js → main-VRQTHHZE.js} +401 -37
- package/dist/main-VRQTHHZE.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-N47HF4VB.js.map +0 -1
- package/dist/main-DC6D6RNQ.js.map +0 -1
|
@@ -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";
|
|
@@ -2237,6 +2237,10 @@ function render2() {
|
|
|
2237
2237
|
}
|
|
2238
2238
|
const cameraFrame = renderCameraFrame(def);
|
|
2239
2239
|
if (cameraFrame) canvasEl.appendChild(cameraFrame);
|
|
2240
|
+
const spotlightPreview = renderSpotlightPreview(def, snap2, elementsById);
|
|
2241
|
+
if (spotlightPreview) canvasEl.appendChild(spotlightPreview);
|
|
2242
|
+
const trailPreview = renderTrailPreview(def);
|
|
2243
|
+
if (trailPreview) canvasEl.appendChild(trailPreview);
|
|
2240
2244
|
const selection = getSelection();
|
|
2241
2245
|
if (selection.kind === "element") {
|
|
2242
2246
|
const selEl = elementsById.get(selection.elementId);
|
|
@@ -2548,6 +2552,107 @@ function moveCameraDrag(e) {
|
|
|
2548
2552
|
updateCameraFocus(drag.focusIndex, { padding: next.padding });
|
|
2549
2553
|
}
|
|
2550
2554
|
}
|
|
2555
|
+
function renderSpotlightPreview(def, snap2, elementsById) {
|
|
2556
|
+
const selection = getSelection();
|
|
2557
|
+
if (selection.kind !== "effect") return null;
|
|
2558
|
+
const effect = def.effects.find(
|
|
2559
|
+
(candidate) => candidate.id === selection.effectId
|
|
2560
|
+
);
|
|
2561
|
+
if (!effect || effect.type !== "spotlight") return null;
|
|
2562
|
+
const boxes = effect.elementIds.map((id) => {
|
|
2563
|
+
const el = elementsById.get(id);
|
|
2564
|
+
const state = el ? snap2.get(id) : void 0;
|
|
2565
|
+
return el && state ? elementBBox(el, state) : null;
|
|
2566
|
+
}).filter(
|
|
2567
|
+
(box) => box !== null
|
|
2568
|
+
);
|
|
2569
|
+
if (boxes.length === 0) return null;
|
|
2570
|
+
const { width, height } = def.canvas;
|
|
2571
|
+
const pad = effect.padding;
|
|
2572
|
+
const g = document.createElementNS(SVG_NS, "g");
|
|
2573
|
+
g.setAttribute("class", "studio-spotlight-preview");
|
|
2574
|
+
g.setAttribute("pointer-events", "none");
|
|
2575
|
+
const holes = [];
|
|
2576
|
+
if (effect.shape === "elements") {
|
|
2577
|
+
for (const box of boxes) {
|
|
2578
|
+
holes.push(
|
|
2579
|
+
`M${box.x - pad} ${box.y - pad}H${box.x + box.w + pad}V${box.y + box.h + pad}H${box.x - pad}Z`
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
} else {
|
|
2583
|
+
const x = Math.min(...boxes.map((box) => box.x)) - pad;
|
|
2584
|
+
const y = Math.min(...boxes.map((box) => box.y)) - pad;
|
|
2585
|
+
const right = Math.max(...boxes.map((box) => box.x + box.w)) + pad;
|
|
2586
|
+
const bottom = Math.max(...boxes.map((box) => box.y + box.h)) + pad;
|
|
2587
|
+
if (effect.shape === "circle") {
|
|
2588
|
+
const cx = (x + right) / 2;
|
|
2589
|
+
const cy = (y + bottom) / 2;
|
|
2590
|
+
const r = Math.hypot(right - x, bottom - y) / 2;
|
|
2591
|
+
holes.push(
|
|
2592
|
+
`M${cx - r} ${cy}a${r} ${r} 0 1 0 ${r * 2} 0a${r} ${r} 0 1 0 ${-r * 2} 0Z`
|
|
2593
|
+
);
|
|
2594
|
+
} else {
|
|
2595
|
+
holes.push(`M${x} ${y}H${right}V${bottom}H${x}Z`);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
const scrim = document.createElementNS(SVG_NS, "path");
|
|
2599
|
+
scrim.setAttribute("d", `M0 0H${width}V${height}H0Z ${holes.join(" ")}`);
|
|
2600
|
+
scrim.setAttribute("fill-rule", "evenodd");
|
|
2601
|
+
scrim.setAttribute("fill", effect.dimColor ?? "#0b1120");
|
|
2602
|
+
scrim.setAttribute("opacity", String(Math.max(effect.dim, 0.15)));
|
|
2603
|
+
g.appendChild(scrim);
|
|
2604
|
+
const label = document.createElementNS(SVG_NS, "text");
|
|
2605
|
+
label.setAttribute("x", "8");
|
|
2606
|
+
label.setAttribute("y", "18");
|
|
2607
|
+
label.setAttribute("class", "studio-spotlight-preview-label");
|
|
2608
|
+
const approximated = effect.shape === "elements" ? " \xB7 \uC2E4\uB8E8\uC5E3\uC740 \uC0C1\uC790\uB85C \uADFC\uC0AC" : "";
|
|
2609
|
+
label.textContent = `\u{1F526} ${effect.id} \xB7 ${effect.time}\u2013${effect.time + effect.duration}ms${approximated}`;
|
|
2610
|
+
g.appendChild(label);
|
|
2611
|
+
return g;
|
|
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
|
+
}
|
|
2551
2656
|
function cameraHandles(view, control) {
|
|
2552
2657
|
const g = document.createElementNS(SVG_NS, "g");
|
|
2553
2658
|
if (control.kind === "tracks") {
|
|
@@ -3192,6 +3297,8 @@ function renderInner() {
|
|
|
3192
3297
|
<option value="highlight">highlight</option>
|
|
3193
3298
|
<option value="pulse">pulse</option>
|
|
3194
3299
|
<option value="flow">flow</option>
|
|
3300
|
+
<option value="spotlight">spotlight</option>
|
|
3301
|
+
<option value="trail">trail</option>
|
|
3195
3302
|
</select>
|
|
3196
3303
|
<button type="button" class="studio-btn" data-add-effect>\uFF0B \uD6A8\uACFC</button>
|
|
3197
3304
|
</div>
|
|
@@ -3283,12 +3390,70 @@ function renderInner() {
|
|
|
3283
3390
|
setSelection({ kind: "none" });
|
|
3284
3391
|
return;
|
|
3285
3392
|
}
|
|
3393
|
+
const typeOpts = ["highlight", "pulse", "flow", "spotlight", "trail"].map(
|
|
3394
|
+
(t) => `<option value="${t}" ${t === eff.type ? "selected" : ""}>${t}</option>`
|
|
3395
|
+
).join("");
|
|
3396
|
+
if (eff.type === "spotlight") {
|
|
3397
|
+
const shapeOpts = ["bbox", "circle", "elements"].map(
|
|
3398
|
+
(shape) => `<option value="${shape}" ${shape === eff.shape ? "selected" : ""}>${shape}${shape === "bbox" ? " (\uAE30\uBCF8)" : ""}</option>`
|
|
3399
|
+
).join("");
|
|
3400
|
+
panelEl.innerHTML = `
|
|
3401
|
+
${timeHint}
|
|
3402
|
+
<div class="studio-props-header"><span class="studio-props-header-title">${escapeHtml2(eff.id)}</span><span class="studio-props-header-type">spotlight</span></div>
|
|
3403
|
+
<label class="studio-field"><span>type</span><select data-prop-key="effect.type">${typeOpts}</select></label>
|
|
3404
|
+
<p class="studio-props-empty" style="margin:0 0 0.4rem">\uB300\uC0C1\uC740 \uADF8\uB300\uB85C \uB450\uACE0 \uB098\uBA38\uC9C0\uB97C \uC5B4\uB461\uAC8C \uD569\uB2C8\uB2E4. \uB300\uC0C1\uC758 \uC0C9\uC774 \uC815\uBCF4\uC778 \uBB38\uC11C\uC5D0\uC11C <code>highlight</code> \uB300\uC2E0 \uC501\uB2C8\uB2E4.</p>
|
|
3405
|
+
${numberField("time (ms)", "effect.time", eff.time, 50)}
|
|
3406
|
+
${numberField("duration (ms)", "effect.duration", eff.duration, 50)}
|
|
3407
|
+
${numberField("fadeIn (ms) \xB7 \uC591 \uB05D\uC5D0 \uC801\uC6A9", "effect.fadeIn", eff.fadeIn, 50)}
|
|
3408
|
+
<div class="studio-camera-field-label">\uB300\uC0C1 \uC694\uC18C (${eff.elementIds.length})</div>
|
|
3409
|
+
${elementCheckList(def, "data-spotlight-target", eff.id, eff.elementIds)}
|
|
3410
|
+
<label class="studio-field"><span>shape</span><select data-prop-key="effect.shape">${shapeOpts}</select></label>
|
|
3411
|
+
<p class="studio-camera-hint">\uB300\uC0C1\uC774 \uBB34\uB300 \uC5EC\uAE30\uC800\uAE30 \uD769\uC5B4\uC838 \uC788\uC73C\uBA74 <code>elements</code>\uB97C \uC4F0\uC138\uC694. <code>bbox</code>\uB294 \uC14B\uC744 \uB2F4\uB290\uB77C \uADF8 \uC0AC\uC774\uC758 \uAD00\uACC4\uC5C6\uB294 \uC694\uC18C\uAE4C\uC9C0 \uBC1D\uD799\uB2C8\uB2E4.</p>
|
|
3412
|
+
${rangeField("padding", "effect.padding", eff.padding, 0, 80, 1)}
|
|
3413
|
+
<div class="studio-props-header" style="margin-top:0.6rem"><span class="studio-props-header-title">\uAC00\uB824\uC9C0\uB294 \uCABD</span></div>
|
|
3414
|
+
${rangeField("dim", "effect.dim", eff.dim, 0, 1, 0.01, "0\uC774\uBA74 \uC5B4\uB461\uAC8C \uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.")}
|
|
3415
|
+
${colorField("dimColor", "effect.dimColor", eff.dimColor ?? "#0b1120")}
|
|
3416
|
+
<p class="studio-camera-hint">\uBE44\uC6CC \uB450\uBA74 \uD14C\uB9C8 \uD1A0\uD070\uC744 \uC501\uB2C8\uB2E4 \u2014 \uB77C\uC774\uD2B8\xB7\uB2E4\uD06C \uBAA8\uB450 near-black.</p>
|
|
3417
|
+
<div class="studio-props-header" style="margin-top:0.6rem"><span class="studio-props-header-title">\uBE44\uCD94\uB294 \uCABD</span></div>
|
|
3418
|
+
${rangeField("lit", "effect.lit", eff.lit, 0, 1, 0.01, "0\uC774\uBA74 \uB300\uC0C1\uC740 \uC790\uAE30 \uC0C9 \uADF8\uB300\uB85C\uC785\uB2C8\uB2E4. \uC870\uBA85\uC758 \uC824\uCC98\uB7FC \uC0C9\uC744 \uC5B9\uC73C\uB824\uBA74 \uC62C\uB9AC\uC138\uC694.")}
|
|
3419
|
+
${colorField("litColor", "effect.litColor", eff.litColor)}
|
|
3420
|
+
<button type="button" class="studio-btn studio-btn-danger" data-delete-effect style="margin-top:0.6rem">\u{1F5D1} \uD6A8\uACFC \uC0AD\uC81C</button>
|
|
3421
|
+
`;
|
|
3422
|
+
return;
|
|
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
|
+
}
|
|
3286
3454
|
const elemOpts = def.elements.map(
|
|
3287
3455
|
(e) => `<option value="${escapeHtml2(e.id)}" ${e.id === eff.elementId ? "selected" : ""}>${escapeHtml2(e.id)}</option>`
|
|
3288
3456
|
).join("");
|
|
3289
|
-
const typeOpts = ["highlight", "pulse", "flow"].map(
|
|
3290
|
-
(t) => `<option value="${t}" ${t === eff.type ? "selected" : ""}>${t}</option>`
|
|
3291
|
-
).join("");
|
|
3292
3457
|
const specific = eff.type === "highlight" ? colorField("color", "effect.color", eff.color) : eff.type === "pulse" ? numberField("scale", "effect.scale", eff.scale, 0.05) : colorField("color", "effect.color", eff.color) + numberField("particles", "effect.particles", eff.particles, 1) + numberField("radius", "effect.radius", eff.radius, 0.5);
|
|
3293
3458
|
panelEl.innerHTML = `
|
|
3294
3459
|
${timeHint}
|
|
@@ -3313,13 +3478,13 @@ function rangeField(label, key, value, min, max, step, hint) {
|
|
|
3313
3478
|
${hint ? `<p class="studio-camera-hint">${escapeHtml2(hint)}</p>` : ""}
|
|
3314
3479
|
</div>`;
|
|
3315
3480
|
}
|
|
3316
|
-
function
|
|
3481
|
+
function elementCheckList(def, attribute, key, selected) {
|
|
3317
3482
|
const chosen = new Set(selected);
|
|
3318
3483
|
const rows = def.elements.map((element) => {
|
|
3319
3484
|
const name = friendlyElementLabel(element);
|
|
3320
3485
|
const same = name === element.id;
|
|
3321
3486
|
return `<label class="studio-camera-target ${chosen.has(element.id) ? "is-on" : ""}">
|
|
3322
|
-
<input type="checkbox"
|
|
3487
|
+
<input type="checkbox" ${attribute}="${escapeHtml2(key)}" value="${escapeHtml2(element.id)}" ${chosen.has(element.id) ? "checked" : ""} />
|
|
3323
3488
|
<span class="studio-camera-target-name">${escapeHtml2(name)}</span>
|
|
3324
3489
|
${same ? "" : `<span class="studio-camera-target-id">${escapeHtml2(element.id)}</span>`}
|
|
3325
3490
|
<span class="studio-camera-target-type">${element.type}</span>
|
|
@@ -3366,7 +3531,7 @@ function renderCameraForm(def, focusIndex) {
|
|
|
3366
3531
|
</div>
|
|
3367
3532
|
</div>
|
|
3368
3533
|
<div class="studio-camera-field-label">\uB300\uC0C1 \uC694\uC18C (${entry.elementIds.length})</div>
|
|
3369
|
-
${
|
|
3534
|
+
${elementCheckList(def, "data-camera-target", String(index), entry.elementIds)}
|
|
3370
3535
|
${rangeField("padding", `camera.focus.${index}.padding`, entry.padding, 0, 200, 2, "\uB300\uC0C1 \uC8FC\uBCC0 \uC5EC\uBC31. \uD074\uC218\uB85D \uB113\uAC8C \uC7A1\uC2B5\uB2C8\uB2E4.")}
|
|
3371
3536
|
${rangeField("maxZoom", `camera.focus.${index}.maxZoom`, entry.maxZoom, 1, 8, 0.1, "\uD655\uB300 \uC0C1\uD55C. \uC791\uC740 \uC694\uC18C \uD558\uB098\uAC00 \uD654\uBA74\uC744 \uCC44\uC6B0\uB294 \uAC83\uC744 \uB9C9\uC2B5\uB2C8\uB2E4.")}
|
|
3372
3537
|
<button type="button" class="studio-btn studio-btn-danger" data-delete-camera-focus="${index}">\u{1F5D1} focus \uC0AD\uC81C</button>
|
|
@@ -3441,11 +3606,13 @@ function renderElementForm(def, el) {
|
|
|
3441
3606
|
const baseHeader = `<span class="studio-props-header-title">${escapeHtml2(el.id)}</span><span class="studio-props-header-type">${escapeHtml2(el.type)}</span>`;
|
|
3442
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>`;
|
|
3443
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);
|
|
3444
3610
|
panelEl.innerHTML = `
|
|
3445
3611
|
${timeHint}
|
|
3446
3612
|
${section("el-base", baseHeader, baseFields)}
|
|
3447
3613
|
${section("el-appearances", apHeader, appearances)}
|
|
3448
3614
|
${section("el-tracks", tracksHeader, tracks)}
|
|
3615
|
+
${section("el-effects", `<span class="studio-props-header-title">\uD6A8\uACFC</span>`, effects)}
|
|
3449
3616
|
${section("el-bindings", `<span class="studio-props-header-title">\uB370\uC774\uD130 \uC5F0\uACB0 (${el.bindings.length})</span>`, bindings)}
|
|
3450
3617
|
<div class="studio-props-empty" style="font-size:0.72rem;margin-top:0.5rem">
|
|
3451
3618
|
base \uC18D\uC131\uC744 \uBCC0\uACBD\uD558\uBA74 \u2192 t=${getCurrentTime()} ms \uC5D0 keyframe \uCD94\uAC00<br/>
|
|
@@ -3453,6 +3620,25 @@ function renderElementForm(def, el) {
|
|
|
3453
3620
|
</div>
|
|
3454
3621
|
`;
|
|
3455
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
|
+
}
|
|
3456
3642
|
function renderBindings(el) {
|
|
3457
3643
|
const properties = bindablePropertiesFor(el);
|
|
3458
3644
|
const rows = el.bindings.map(
|
|
@@ -3697,6 +3883,11 @@ function onInput(e) {
|
|
|
3697
3883
|
toggleFocusTarget(Number(focusTarget), target.value, target.checked);
|
|
3698
3884
|
return;
|
|
3699
3885
|
}
|
|
3886
|
+
const spotTarget = target.dataset.spotlightTarget;
|
|
3887
|
+
if (spotTarget !== void 0 && target instanceof HTMLInputElement) {
|
|
3888
|
+
toggleSpotlightTarget(spotTarget, target.value, target.checked);
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3700
3891
|
const key = target.dataset.propKey;
|
|
3701
3892
|
if (!key) return;
|
|
3702
3893
|
if (target.type === "text" && e.isComposing) return;
|
|
@@ -3946,10 +4137,12 @@ function apply(key, value) {
|
|
|
3946
4137
|
if (targets) references[token] = targets;
|
|
3947
4138
|
else delete references[token];
|
|
3948
4139
|
updateChapter(sel.chapterId, { references });
|
|
4140
|
+
} else if (key === "effect.type" && sel.kind === "effect") {
|
|
4141
|
+
changeEffectType(def, sel.effectId, String(value));
|
|
3949
4142
|
} else if (key.startsWith("effect.") && sel.kind === "effect") {
|
|
3950
4143
|
const prop = key.slice(7);
|
|
3951
4144
|
const patch = {};
|
|
3952
|
-
if (prop === "time" || prop === "duration" || prop === "scale" || prop === "particles" || prop === "radius") {
|
|
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") {
|
|
3953
4146
|
patch[prop] = Number(value);
|
|
3954
4147
|
} else {
|
|
3955
4148
|
patch[prop] = value;
|
|
@@ -3957,6 +4150,155 @@ function apply(key, value) {
|
|
|
3957
4150
|
updateEffect(sel.effectId, patch);
|
|
3958
4151
|
}
|
|
3959
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
|
+
}
|
|
4241
|
+
function changeEffectType(def, effectId, type) {
|
|
4242
|
+
const current = def.effects.find((effect) => effect.id === effectId);
|
|
4243
|
+
if (!current || current.type === type) return;
|
|
4244
|
+
const targets = current.type === "spotlight" ? current.elementIds : [current.elementId];
|
|
4245
|
+
const base = { id: current.id, time: current.time };
|
|
4246
|
+
let next;
|
|
4247
|
+
if (type === "spotlight") {
|
|
4248
|
+
next = {
|
|
4249
|
+
...base,
|
|
4250
|
+
type: "spotlight",
|
|
4251
|
+
elementIds: targets,
|
|
4252
|
+
duration: Math.max(current.duration, 800),
|
|
4253
|
+
dim: 0.7,
|
|
4254
|
+
lit: 0,
|
|
4255
|
+
litColor: "#fde68a",
|
|
4256
|
+
shape: "bbox",
|
|
4257
|
+
padding: 12,
|
|
4258
|
+
fadeIn: 200
|
|
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
|
+
};
|
|
4275
|
+
} else {
|
|
4276
|
+
const single = {
|
|
4277
|
+
...base,
|
|
4278
|
+
elementId: targets[0] ?? "",
|
|
4279
|
+
duration: current.duration
|
|
4280
|
+
};
|
|
4281
|
+
next = type === "pulse" ? { ...single, type: "pulse", scale: 1.12 } : type === "flow" ? {
|
|
4282
|
+
...single,
|
|
4283
|
+
type: "flow",
|
|
4284
|
+
color: "#facc15",
|
|
4285
|
+
particles: 3,
|
|
4286
|
+
radius: 4
|
|
4287
|
+
} : { ...single, type: "highlight", color: "#facc15" };
|
|
4288
|
+
}
|
|
4289
|
+
replaceEffect(effectId, next);
|
|
4290
|
+
}
|
|
4291
|
+
function toggleSpotlightTarget(effectId, id, on) {
|
|
4292
|
+
const def = getDef();
|
|
4293
|
+
const effect = def?.effects.find((candidate) => candidate.id === effectId);
|
|
4294
|
+
if (!effect || effect.type !== "spotlight") return;
|
|
4295
|
+
const next = on ? [...effect.elementIds, id] : effect.elementIds.filter((target) => target !== id);
|
|
4296
|
+
if (next.length === 0) {
|
|
4297
|
+
render3();
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
updateEffect(effectId, { elementIds: next });
|
|
4301
|
+
}
|
|
3960
4302
|
function parseBindingFallback(value) {
|
|
3961
4303
|
if (value === "") return void 0;
|
|
3962
4304
|
if (value === "true") return true;
|
|
@@ -4136,30 +4478,25 @@ function onClick2(e) {
|
|
|
4136
4478
|
const typeSel = document.getElementById(
|
|
4137
4479
|
"studio-new-effect-type"
|
|
4138
4480
|
);
|
|
4139
|
-
const
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
particles: 3,
|
|
4159
|
-
radius: 4,
|
|
4160
|
-
duration: 800
|
|
4161
|
-
};
|
|
4162
|
-
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
|
+
});
|
|
4163
4500
|
return;
|
|
4164
4501
|
}
|
|
4165
4502
|
if (target.closest("[data-delete-effect]") && sel.kind === "effect") {
|
|
@@ -4388,7 +4725,7 @@ function render4() {
|
|
|
4388
4725
|
</div>
|
|
4389
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>
|
|
4390
4727
|
`;
|
|
4391
|
-
renderElementTracks(def
|
|
4728
|
+
renderElementTracks(def, currentTime, totalPx, sel);
|
|
4392
4729
|
}
|
|
4393
4730
|
function renderCameraRows(totalPx, sel) {
|
|
4394
4731
|
const camera = getCamera();
|
|
@@ -4428,8 +4765,18 @@ function renderCameraRows(totalPx, sel) {
|
|
|
4428
4765
|
function gutterLabel(el) {
|
|
4429
4766
|
return `${friendlyElementLabel(el)} \xB7 ${el.type}`;
|
|
4430
4767
|
}
|
|
4431
|
-
function renderElementTracks(
|
|
4768
|
+
function renderElementTracks(def, currentTime, totalPx, sel) {
|
|
4432
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
|
+
}
|
|
4433
4780
|
if (elements.length === 0) {
|
|
4434
4781
|
elTracksEl.innerHTML = '<p class="studio-tl-empty">\uC694\uC18C \uC5C6\uC74C</p>';
|
|
4435
4782
|
return;
|
|
@@ -4463,12 +4810,24 @@ function renderElementTracks(elements, currentTime, totalPx, sel) {
|
|
|
4463
4810
|
).map(
|
|
4464
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>`
|
|
4465
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("");
|
|
4466
4824
|
return `
|
|
4467
4825
|
<div class="studio-tl-row studio-tl-element-row ${isSel ? "is-selected" : ""} ${el.type === "group" ? "is-group" : ""}" data-elem-id="${escapeHtml3(el.id)}">
|
|
4468
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>
|
|
4469
4827
|
<div class="studio-tl-body" style="width:${totalPx}px">
|
|
4470
4828
|
<div class="studio-tl-element-track" data-tl-area="elements">
|
|
4471
4829
|
${appearanceBars}
|
|
4830
|
+
${effectBars}
|
|
4472
4831
|
${trackKfs}
|
|
4473
4832
|
</div>
|
|
4474
4833
|
</div>
|
|
@@ -4518,6 +4877,11 @@ function onElTracksClick(e) {
|
|
|
4518
4877
|
if (dragMode) return;
|
|
4519
4878
|
const target = e.target;
|
|
4520
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
|
+
}
|
|
4521
4885
|
const row = target.closest("[data-elem-id]");
|
|
4522
4886
|
if (row) {
|
|
4523
4887
|
setSelection({ kind: "element", elementId: row.dataset.elemId });
|
|
@@ -17939,5 +18303,5 @@ function reflectState(ui) {
|
|
|
17939
18303
|
}
|
|
17940
18304
|
|
|
17941
18305
|
export { getVisibleElementIds, initStudio };
|
|
17942
|
-
//# sourceMappingURL=main-
|
|
17943
|
-
//# sourceMappingURL=main-
|
|
18306
|
+
//# sourceMappingURL=main-VRQTHHZE.js.map
|
|
18307
|
+
//# sourceMappingURL=main-VRQTHHZE.js.map
|