@lingjingai/script-editor 0.1.0 → 0.1.13
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/INTEGRATION.md +2 -7
- package/dist/index.d.ts +116 -15
- package/dist/index.js +447 -127
- package/dist/index.js.map +1 -1
- package/dist/style.css +25 -2
- package/package.json +2 -6
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createContext, memo, useState, useMemo, useCallback, useRef, useContext, useEffect, useLayoutEffect,
|
|
2
|
-
import { Plus, Trash2, Check, Eraser, ArrowUp, ArrowDown, GripVertical, MessageCircle, Film, Brain, BookOpen, Scissors, ArrowUpToLine, Globe, BookText, ChevronRight, FileText, Users, MapPin, Package, Undo2, Redo2, MessageSquareQuote, ArrowLeft, ChevronLeft, Loader2, AlertCircle } from 'lucide-react';
|
|
3
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
1
|
+
import { createContext, memo, useState, useMemo, useCallback, Fragment, useRef, useContext, useEffect, useLayoutEffect, useDeferredValue } from 'react';
|
|
2
|
+
import { Plus, Trash2, X, Check, Eraser, ArrowUp, ArrowDown, GripVertical, MessageCircle, Film, Brain, BookOpen, Scissors, ArrowUpToLine, Globe, BookText, ChevronRight, FileText, Users, MapPin, Package, Undo2, Redo2, MessageSquareQuote, ArrowLeft, ChevronLeft, Loader2, AlertCircle } from 'lucide-react';
|
|
3
|
+
import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
4
4
|
import { Virtuoso } from 'react-virtuoso';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
|
|
@@ -286,10 +286,10 @@ function getSceneContext(scene) {
|
|
|
286
286
|
props: (scene.context?.props?.length ? scene.context.props : scene.props) ?? []
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
|
-
function findState(
|
|
289
|
+
function findState(states2, stateId) {
|
|
290
290
|
const id = stateId?.trim();
|
|
291
|
-
if (!id || !
|
|
292
|
-
return
|
|
291
|
+
if (!id || !states2) return void 0;
|
|
292
|
+
return states2.find((state) => (state.state_id || "").trim() === id);
|
|
293
293
|
}
|
|
294
294
|
var ZH_DIGIT_MAP = {
|
|
295
295
|
"\u96F6": 0,
|
|
@@ -443,7 +443,7 @@ function GroupHeader({
|
|
|
443
443
|
highlighted,
|
|
444
444
|
onToggle
|
|
445
445
|
}) {
|
|
446
|
-
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
446
|
+
const inner = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
447
447
|
onToggle ? /* @__PURE__ */ jsx(
|
|
448
448
|
ChevronRight,
|
|
449
449
|
{
|
|
@@ -591,7 +591,7 @@ function EntityGroup({
|
|
|
591
591
|
}) {
|
|
592
592
|
const [open, setOpen] = useState(true);
|
|
593
593
|
const valid = items.filter((i) => i.id);
|
|
594
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
594
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
595
595
|
/* @__PURE__ */ jsx(GroupHeader, { icon: Icon, label, count: valid.length, open, highlighted, onToggle: () => setOpen((v) => !v) }),
|
|
596
596
|
open ? valid.length === 0 ? /* @__PURE__ */ jsx("div", { className: "lj-se-toc-empty", children: emptyText }) : valid.map((i) => /* @__PURE__ */ jsxs(
|
|
597
597
|
"button",
|
|
@@ -931,7 +931,11 @@ function updateSceneItemActor(data, episodeIndex, sceneIndex, itemIndex, actorId
|
|
|
931
931
|
const actions = [...scene.actions || []];
|
|
932
932
|
const item = actions[itemIndex];
|
|
933
933
|
if (!item) return scene;
|
|
934
|
-
|
|
934
|
+
const next = { ...item, actor_id: actorId.trim() };
|
|
935
|
+
delete next.speaker_id;
|
|
936
|
+
delete next.speakers;
|
|
937
|
+
delete next.lines;
|
|
938
|
+
actions[itemIndex] = next;
|
|
935
939
|
return { ...scene, actions };
|
|
936
940
|
});
|
|
937
941
|
}
|
|
@@ -1267,6 +1271,21 @@ function buildDistributionIndex(data, prev) {
|
|
|
1267
1271
|
}
|
|
1268
1272
|
return index;
|
|
1269
1273
|
}
|
|
1274
|
+
var states = /* @__PURE__ */ new Map();
|
|
1275
|
+
function readScrollState(scope) {
|
|
1276
|
+
return scope ? states.get(scope) : void 0;
|
|
1277
|
+
}
|
|
1278
|
+
function useScrollStateMemo(scope, virtuosoRef) {
|
|
1279
|
+
useEffect(() => {
|
|
1280
|
+
if (!scope) return;
|
|
1281
|
+
const ref = virtuosoRef;
|
|
1282
|
+
return () => {
|
|
1283
|
+
ref.current?.getState((snapshot) => {
|
|
1284
|
+
states.set(scope, snapshot);
|
|
1285
|
+
});
|
|
1286
|
+
};
|
|
1287
|
+
}, [scope, virtuosoRef]);
|
|
1288
|
+
}
|
|
1270
1289
|
function Popover({
|
|
1271
1290
|
trigger,
|
|
1272
1291
|
children,
|
|
@@ -1383,7 +1402,7 @@ function RowMenuPanel({
|
|
|
1383
1402
|
sections
|
|
1384
1403
|
}) {
|
|
1385
1404
|
return /* @__PURE__ */ jsxs("div", { className: "lj-se-rowmenu-inner", children: [
|
|
1386
|
-
typeSeg ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1405
|
+
typeSeg ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1387
1406
|
/* @__PURE__ */ jsx("div", { className: "lj-se-rowmenu-cap", children: "\u6539\u4E3A" }),
|
|
1388
1407
|
/* @__PURE__ */ jsx("div", { className: "lj-se-typeseg", children: TYPES.map((t) => {
|
|
1389
1408
|
const Icon = t.icon;
|
|
@@ -1406,7 +1425,7 @@ function RowMenuPanel({
|
|
|
1406
1425
|
);
|
|
1407
1426
|
}) })
|
|
1408
1427
|
] }) : null,
|
|
1409
|
-
sections.map((items, si) => /* @__PURE__ */ jsxs(Fragment
|
|
1428
|
+
sections.map((items, si) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1410
1429
|
si > 0 || typeSeg ? /* @__PURE__ */ jsx("div", { className: "lj-se-rowmenu-div" }) : null,
|
|
1411
1430
|
items.map((item) => {
|
|
1412
1431
|
const Icon = item.icon;
|
|
@@ -1568,20 +1587,20 @@ function SelectField({
|
|
|
1568
1587
|
}
|
|
1569
1588
|
);
|
|
1570
1589
|
}
|
|
1571
|
-
function stateOptions(
|
|
1572
|
-
return (
|
|
1590
|
+
function stateOptions(states2) {
|
|
1591
|
+
return (states2 ?? []).map((s) => ({ value: (s.state_id ?? "").trim(), label: getStateDisplayName(s) })).filter((o) => o.value);
|
|
1573
1592
|
}
|
|
1574
1593
|
function AssetStateChip({
|
|
1575
1594
|
name,
|
|
1576
1595
|
orphan,
|
|
1577
|
-
states,
|
|
1596
|
+
states: states2,
|
|
1578
1597
|
currentStateId,
|
|
1579
1598
|
disabled,
|
|
1580
1599
|
onChangeState,
|
|
1581
1600
|
onDelete,
|
|
1582
1601
|
onNavigate
|
|
1583
1602
|
}) {
|
|
1584
|
-
const opts = stateOptions(
|
|
1603
|
+
const opts = stateOptions(states2);
|
|
1585
1604
|
const trimmed = (currentStateId ?? "").trim();
|
|
1586
1605
|
return /* @__PURE__ */ jsxs("span", { className: cn("lj-se-chip", orphan && "is-orphan"), title: orphan ? `${name} \u4E0D\u5B58\u5728` : void 0, children: [
|
|
1587
1606
|
onNavigate ? /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-chip-name lj-se-chip-name--link", title: `\u8DF3\u5230 ${name}`, onClick: onNavigate, children: name }) : /* @__PURE__ */ jsx("span", { className: "lj-se-chip-name", children: name }),
|
|
@@ -2118,10 +2137,10 @@ function formatStateChangeLabels(item, actorMap, locationMap, propMap) {
|
|
|
2118
2137
|
const kind = change.target_kind?.trim() || "actor";
|
|
2119
2138
|
const id = change.target_id?.trim() || "";
|
|
2120
2139
|
if (!id) return "";
|
|
2121
|
-
const
|
|
2140
|
+
const states2 = kind === "location" ? locationMap.get(id)?.states : kind === "prop" ? propMap.get(id)?.states : actorMap.get(id)?.states;
|
|
2122
2141
|
const name = kind === "location" ? getLocationDisplayName(locationMap.get(id)) || id : kind === "prop" ? getPropDisplayName(propMap.get(id)) || id : getActorDisplayName(actorMap.get(id)) || id;
|
|
2123
|
-
const from = getStateDisplayName(findState(
|
|
2124
|
-
const to = getStateDisplayName(findState(
|
|
2142
|
+
const from = getStateDisplayName(findState(states2, change.from_state_id)) || change.from_state_id?.trim() || "\u672A\u8BBE";
|
|
2143
|
+
const to = getStateDisplayName(findState(states2, change.to_state_id)) || change.to_state_id?.trim() || "\u672A\u8BBE";
|
|
2125
2144
|
return `${name}\uFF1A${from} \u2192 ${to}`;
|
|
2126
2145
|
}).filter(Boolean);
|
|
2127
2146
|
if (item.transition_prompt?.process || item.transition_prompt?.contrast) {
|
|
@@ -2158,12 +2177,19 @@ function SceneItemRowImpl({
|
|
|
2158
2177
|
const type = item.type ?? "dialogue" /* Dialogue */;
|
|
2159
2178
|
const content = item.content ?? "";
|
|
2160
2179
|
const actorId = item.actor_id?.trim() || "";
|
|
2161
|
-
const actor = actorMap.get(actorId);
|
|
2162
|
-
const actorName = actor ? getActorDisplayName(actor) : actorId || "\u672A\u77E5\u89D2\u8272";
|
|
2163
|
-
const hasOrphanActor = !!actorId && !actor;
|
|
2164
2180
|
const emotion = translateEmotionLabel(item.emotion);
|
|
2165
2181
|
const stateLabels = formatStateChangeLabels(item, actorMap, locationMap, propMap);
|
|
2166
|
-
const
|
|
2182
|
+
const multiSpeaker = type === "dialogue" /* Dialogue */ && (item.delivery === "overlap" || (item.lines?.length ?? 0) > 0 || (item.speakers?.length ?? 0) > 1);
|
|
2183
|
+
const resolvedActorId = actorId || (() => {
|
|
2184
|
+
const spkId = item.speaker_id?.trim() || item.speakers?.[0]?.speaker_id?.trim() || "";
|
|
2185
|
+
if (!spkId) return "";
|
|
2186
|
+
const spk = speakerMap.get(spkId);
|
|
2187
|
+
const kind = spk?.source_kind?.trim();
|
|
2188
|
+
if ((kind === "actor" || kind === "prop") && spk?.source_id?.trim()) return spk.source_id.trim();
|
|
2189
|
+
if (spkId.startsWith("spk_") && actorMap.has(spkId.slice(4))) return spkId.slice(4);
|
|
2190
|
+
return "";
|
|
2191
|
+
})();
|
|
2192
|
+
const resolvedActor = actorMap.get(resolvedActorId) ?? propMap.get(resolvedActorId);
|
|
2167
2193
|
const handle = /* @__PURE__ */ jsx(
|
|
2168
2194
|
RowControlHandle,
|
|
2169
2195
|
{
|
|
@@ -2178,11 +2204,11 @@ function SceneItemRowImpl({
|
|
|
2178
2204
|
onDelete
|
|
2179
2205
|
}
|
|
2180
2206
|
);
|
|
2181
|
-
const actorCue = (tone) =>
|
|
2207
|
+
const actorCue = (tone) => multiSpeaker ? /* @__PURE__ */ jsxs(
|
|
2182
2208
|
"span",
|
|
2183
2209
|
{
|
|
2184
2210
|
className: "lj-se-cue lj-se-cue--structured",
|
|
2185
|
-
style:
|
|
2211
|
+
style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0,
|
|
2186
2212
|
children: [
|
|
2187
2213
|
getDialogueSpeakerLabel(item, actorMap, speakerMap) || "\u672A\u77E5\u53D1\u58F0\u6E90",
|
|
2188
2214
|
getDialogueDeliveryLabel(item.delivery) ? /* @__PURE__ */ jsxs("span", { className: "lj-se-cue-delivery", children: [
|
|
@@ -2195,13 +2221,13 @@ function SceneItemRowImpl({
|
|
|
2195
2221
|
) : /* @__PURE__ */ jsx(
|
|
2196
2222
|
SelectField,
|
|
2197
2223
|
{
|
|
2198
|
-
value:
|
|
2224
|
+
value: resolvedActorId,
|
|
2199
2225
|
options: actorOptions,
|
|
2200
2226
|
disabled,
|
|
2201
2227
|
placeholder: "\u672A\u77E5\u89D2\u8272",
|
|
2202
|
-
title:
|
|
2203
|
-
className: cn("lj-se-cue lj-se-cue--select",
|
|
2204
|
-
style:
|
|
2228
|
+
title: "\u5207\u6362\u89D2\u8272",
|
|
2229
|
+
className: cn("lj-se-cue lj-se-cue--select", resolvedActorId && !resolvedActor && "is-orphan"),
|
|
2230
|
+
style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0,
|
|
2205
2231
|
onChange: onSaveActor
|
|
2206
2232
|
}
|
|
2207
2233
|
);
|
|
@@ -2210,7 +2236,7 @@ function SceneItemRowImpl({
|
|
|
2210
2236
|
if (type === "dialogue" /* Dialogue */) {
|
|
2211
2237
|
body2 = /* @__PURE__ */ jsxs("div", { className: "lj-se-std-dialogue", children: [
|
|
2212
2238
|
actorCue("dialogue"),
|
|
2213
|
-
|
|
2239
|
+
/* @__PURE__ */ jsx("div", { className: "lj-se-std-emotion", children: /* @__PURE__ */ jsx(EditableText, { value: item.emotion ?? "", disabled, placeholder: "\u60C5\u7EEA\u2026", onSave: onSaveEmotion }) }),
|
|
2214
2240
|
/* @__PURE__ */ jsx(
|
|
2215
2241
|
EditableText,
|
|
2216
2242
|
{
|
|
@@ -2276,19 +2302,19 @@ function SceneItemRowImpl({
|
|
|
2276
2302
|
const tone = isInner ? "inner" : "dialogue";
|
|
2277
2303
|
const tag = isInner ? emotion ? `OS\xB7${emotion}` : "OS" : emotion;
|
|
2278
2304
|
body = /* @__PURE__ */ jsxs("p", { className: cn("lj-se-asian-cueline", isInner && "is-inner"), children: [
|
|
2279
|
-
|
|
2305
|
+
multiSpeaker ? /* @__PURE__ */ jsx("strong", { className: "lj-se-asian-speaker", style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0, children: getDialogueSpeakerLabel(item, actorMap, speakerMap) }) : /* @__PURE__ */ jsx(
|
|
2280
2306
|
SelectField,
|
|
2281
2307
|
{
|
|
2282
|
-
value:
|
|
2308
|
+
value: resolvedActorId,
|
|
2283
2309
|
options: actorOptions,
|
|
2284
2310
|
disabled,
|
|
2285
2311
|
placeholder: "\u672A\u77E5\u89D2\u8272",
|
|
2286
|
-
className: cn("lj-se-asian-speaker lj-se-asian-speaker--select",
|
|
2287
|
-
style:
|
|
2312
|
+
className: cn("lj-se-asian-speaker lj-se-asian-speaker--select", resolvedActorId && !resolvedActor && "is-orphan"),
|
|
2313
|
+
style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0,
|
|
2288
2314
|
onChange: onSaveActor
|
|
2289
2315
|
}
|
|
2290
2316
|
),
|
|
2291
|
-
!
|
|
2317
|
+
!multiSpeaker ? /* @__PURE__ */ jsxs("span", { className: "lj-se-asian-tag", children: [
|
|
2292
2318
|
"\uFF08",
|
|
2293
2319
|
/* @__PURE__ */ jsx(EditableText, { value: item.emotion ?? "", disabled, placeholder: isInner ? "OS" : "\u60C5\u7EEA", onSave: onSaveEmotion }),
|
|
2294
2320
|
"\uFF09"
|
|
@@ -2308,6 +2334,12 @@ function SceneItemRowImpl({
|
|
|
2308
2334
|
] });
|
|
2309
2335
|
}
|
|
2310
2336
|
var SceneItemRow = memo(SceneItemRowImpl);
|
|
2337
|
+
function DiffActionControls({ onAccept, onReject }) {
|
|
2338
|
+
return /* @__PURE__ */ jsxs("span", { className: "lj-se-actiondiff-ctrl", children: [
|
|
2339
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-actiondiff-reject", title: "\u62D2\u7EDD\u8FD9\u884C", onClick: onReject, children: /* @__PURE__ */ jsx(X, { className: "lj-se-ic-3", strokeWidth: 2.75 }) }),
|
|
2340
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-actiondiff-accept", title: "\u63A5\u53D7\u8FD9\u884C", onClick: onAccept, children: /* @__PURE__ */ jsx(Check, { className: "lj-se-ic-3", strokeWidth: 2.75 }) })
|
|
2341
|
+
] });
|
|
2342
|
+
}
|
|
2311
2343
|
function SceneEditorImpl({
|
|
2312
2344
|
episodeIndex,
|
|
2313
2345
|
sceneIndex,
|
|
@@ -2316,6 +2348,7 @@ function SceneEditorImpl({
|
|
|
2316
2348
|
format,
|
|
2317
2349
|
disabled,
|
|
2318
2350
|
focused = false,
|
|
2351
|
+
actionDiff,
|
|
2319
2352
|
onEdit,
|
|
2320
2353
|
onNavigateAsset,
|
|
2321
2354
|
onRequestAssetDelete
|
|
@@ -2323,10 +2356,18 @@ function SceneEditorImpl({
|
|
|
2323
2356
|
const [pendingEdit, setPendingEdit] = useState(null);
|
|
2324
2357
|
const { actorMap, speakerMap, locationMap, propMap } = maps;
|
|
2325
2358
|
const actions = scene.actions ?? [];
|
|
2326
|
-
const actorOptions = useMemo(
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2359
|
+
const actorOptions = useMemo(() => {
|
|
2360
|
+
const out = [];
|
|
2361
|
+
for (const a of actorMap.values()) {
|
|
2362
|
+
const v = (a.actor_id ?? "").trim();
|
|
2363
|
+
if (v) out.push({ value: v, label: getActorDisplayName(a) });
|
|
2364
|
+
}
|
|
2365
|
+
for (const p of propMap.values()) {
|
|
2366
|
+
const v = (p.prop_id ?? "").trim();
|
|
2367
|
+
if (v) out.push({ value: v, label: `${getPropDisplayName(p)}\uFF08\u9053\u5177\uFF09` });
|
|
2368
|
+
}
|
|
2369
|
+
return out;
|
|
2370
|
+
}, [actorMap, propMap]);
|
|
2330
2371
|
const insertAt = useCallback(
|
|
2331
2372
|
(index, type) => {
|
|
2332
2373
|
const inherited = pickInheritedActorId(scene, index);
|
|
@@ -2362,6 +2403,32 @@ function SceneEditorImpl({
|
|
|
2362
2403
|
e.preventDefault();
|
|
2363
2404
|
dnd?.drop();
|
|
2364
2405
|
};
|
|
2406
|
+
const noop = () => {
|
|
2407
|
+
};
|
|
2408
|
+
const renderOldRow = (item) => /* @__PURE__ */ jsx(
|
|
2409
|
+
SceneItemRow,
|
|
2410
|
+
{
|
|
2411
|
+
item,
|
|
2412
|
+
actorMap,
|
|
2413
|
+
speakerMap,
|
|
2414
|
+
locationMap,
|
|
2415
|
+
propMap,
|
|
2416
|
+
actorOptions,
|
|
2417
|
+
format,
|
|
2418
|
+
disabled: true,
|
|
2419
|
+
onSave: noop,
|
|
2420
|
+
onSaveActor: noop,
|
|
2421
|
+
onSaveEmotion: noop,
|
|
2422
|
+
onChangeType: noop,
|
|
2423
|
+
onInsertAbove: noop,
|
|
2424
|
+
onInsertBelow: noop,
|
|
2425
|
+
onDelete: noop
|
|
2426
|
+
}
|
|
2427
|
+
);
|
|
2428
|
+
const renderGhosts = (beforeIdx) => actionDiff?.removedBefore.get(beforeIdx)?.map((m, gi) => /* @__PURE__ */ jsxs("div", { className: "lj-se-action-slot lj-se-diff-old lj-se-ghost-row", children: [
|
|
2429
|
+
/* @__PURE__ */ jsx(DiffActionControls, { onAccept: () => actionDiff.onAccept(m), onReject: () => actionDiff.onReject(m) }),
|
|
2430
|
+
m.item ? renderOldRow(m.item) : /* @__PURE__ */ jsx("p", { className: "lj-se-ghost-empty", children: "\uFF08\u5DF2\u5220\u9664\u7A7A\u884C\uFF09" })
|
|
2431
|
+
] }, `ghost-${beforeIdx}-${gi}`)) ?? null;
|
|
2365
2432
|
return /* @__PURE__ */ jsxs("div", { className: "lj-se-scene", children: [
|
|
2366
2433
|
/* @__PURE__ */ jsx(
|
|
2367
2434
|
SceneHeader,
|
|
@@ -2381,52 +2448,73 @@ function SceneEditorImpl({
|
|
|
2381
2448
|
}
|
|
2382
2449
|
),
|
|
2383
2450
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-actions", children: [
|
|
2384
|
-
actions.map((item, idx) =>
|
|
2385
|
-
|
|
2386
|
-
{
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
children: /* @__PURE__ */ jsx(
|
|
2391
|
-
SceneItemRow,
|
|
2451
|
+
actions.map((item, idx) => {
|
|
2452
|
+
const mark = actionDiff?.byIndex.get(idx);
|
|
2453
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2454
|
+
renderGhosts(idx),
|
|
2455
|
+
/* @__PURE__ */ jsxs(
|
|
2456
|
+
"div",
|
|
2392
2457
|
{
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2458
|
+
className: cn(
|
|
2459
|
+
"lj-se-action-slot",
|
|
2460
|
+
dragIndex === idx && "is-dragging",
|
|
2461
|
+
dropSlot === idx && "drop-before",
|
|
2462
|
+
mark && (mark.kind === "added" ? "is-diff-added" : "is-diff-modified")
|
|
2463
|
+
),
|
|
2464
|
+
onDragOver: (e) => onSlotDragOver(e, idx),
|
|
2465
|
+
onDrop: onSlotDrop,
|
|
2466
|
+
children: [
|
|
2467
|
+
mark ? /* @__PURE__ */ jsx(
|
|
2468
|
+
DiffActionControls,
|
|
2469
|
+
{
|
|
2470
|
+
onAccept: () => actionDiff.onAccept(mark),
|
|
2471
|
+
onReject: () => actionDiff.onReject(mark)
|
|
2472
|
+
}
|
|
2473
|
+
) : null,
|
|
2474
|
+
mark?.kind === "modified" && mark.item ? /* @__PURE__ */ jsx("div", { className: "lj-se-diff-old", children: renderOldRow(mark.item) }) : null,
|
|
2475
|
+
/* @__PURE__ */ jsx(
|
|
2476
|
+
SceneItemRow,
|
|
2477
|
+
{
|
|
2478
|
+
item,
|
|
2479
|
+
actorMap,
|
|
2480
|
+
speakerMap,
|
|
2481
|
+
locationMap,
|
|
2482
|
+
propMap,
|
|
2483
|
+
actorOptions,
|
|
2484
|
+
format,
|
|
2485
|
+
disabled,
|
|
2486
|
+
autoEdit: pendingEdit === idx,
|
|
2487
|
+
onEditEnd: pendingEdit === idx ? clearPending : void 0,
|
|
2488
|
+
canSplit: idx > 0,
|
|
2489
|
+
dragHandleProps: disabled ? void 0 : { draggable: true, onDragStart: (e) => onDragStart(e, idx), onDragEnd: () => dnd?.end() },
|
|
2490
|
+
onSave: (content) => {
|
|
2491
|
+
clearPending();
|
|
2492
|
+
onEdit((cur) => updateSceneItemContent(cur, episodeIndex, sceneIndex, idx, content));
|
|
2493
|
+
},
|
|
2494
|
+
onSaveActor: (actorId) => onEdit((cur) => updateSceneItemActor(cur, episodeIndex, sceneIndex, idx, actorId)),
|
|
2495
|
+
onSaveEmotion: (emotion) => onEdit((cur) => updateSceneItemEmotion(cur, episodeIndex, sceneIndex, idx, emotion)),
|
|
2496
|
+
onChangeType: (type) => {
|
|
2497
|
+
clearPending();
|
|
2498
|
+
onEdit((cur) => updateSceneItemType(cur, episodeIndex, sceneIndex, idx, type));
|
|
2499
|
+
},
|
|
2500
|
+
onInsertAbove: () => insertSame(idx, idx),
|
|
2501
|
+
onInsertBelow: () => insertSame(idx + 1, idx),
|
|
2502
|
+
onSplitHere: idx > 0 ? () => {
|
|
2503
|
+
clearPending();
|
|
2504
|
+
onEdit((cur) => splitScene(cur, episodeIndex, sceneIndex, idx));
|
|
2505
|
+
} : void 0,
|
|
2506
|
+
onDelete: () => {
|
|
2507
|
+
clearPending();
|
|
2508
|
+
onEdit((cur) => deleteSceneItem(cur, episodeIndex, sceneIndex, idx));
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
)
|
|
2512
|
+
]
|
|
2425
2513
|
}
|
|
2426
2514
|
)
|
|
2427
|
-
},
|
|
2428
|
-
|
|
2429
|
-
)
|
|
2515
|
+
] }, `item-${idx}`);
|
|
2516
|
+
}),
|
|
2517
|
+
renderGhosts(actions.length),
|
|
2430
2518
|
dropSlot === actions.length && actions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "lj-se-dropline-end" }) : null,
|
|
2431
2519
|
actions.length === 0 && !disabled ? /* @__PURE__ */ jsxs(
|
|
2432
2520
|
"button",
|
|
@@ -2804,7 +2892,7 @@ function AssetCardImpl({
|
|
|
2804
2892
|
const name = group === "actors" ? getActorDisplayName(entity) : group === "locations" ? getLocationDisplayName(entity) : getPropDisplayName(entity);
|
|
2805
2893
|
const rawDescription = entity.description ?? "";
|
|
2806
2894
|
const description = rawDescription.trim();
|
|
2807
|
-
const
|
|
2895
|
+
const states2 = entity.states ?? [];
|
|
2808
2896
|
const fromRef = buildSelectionFrom(kind, assetId);
|
|
2809
2897
|
return /* @__PURE__ */ jsx("div", { className: "lj-se-assetcard-row", children: /* @__PURE__ */ jsxs("article", { className: "lj-se-assetcard", "data-no-quote-selection": true, children: [
|
|
2810
2898
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-head", children: [
|
|
@@ -2852,7 +2940,7 @@ function AssetCardImpl({
|
|
|
2852
2940
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-states-head", children: [
|
|
2853
2941
|
/* @__PURE__ */ jsxs("span", { className: "lj-se-assetcard-states-label", children: [
|
|
2854
2942
|
"\u72B6\u6001 ",
|
|
2855
|
-
/* @__PURE__ */ jsx("span", { className: "lj-se-field-hint", children:
|
|
2943
|
+
/* @__PURE__ */ jsx("span", { className: "lj-se-field-hint", children: states2.length })
|
|
2856
2944
|
] }),
|
|
2857
2945
|
!disabled ? /* @__PURE__ */ jsxs(
|
|
2858
2946
|
"button",
|
|
@@ -2867,7 +2955,7 @@ function AssetCardImpl({
|
|
|
2867
2955
|
}
|
|
2868
2956
|
) : null
|
|
2869
2957
|
] }),
|
|
2870
|
-
|
|
2958
|
+
states2.length > 0 ? /* @__PURE__ */ jsx("div", { className: "lj-se-assetcard-state-cards", children: states2.map((state, idx) => {
|
|
2871
2959
|
const sid = (state.state_id ?? "").trim() || `idx-${idx}`;
|
|
2872
2960
|
return /* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-state", children: [
|
|
2873
2961
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-state-top", children: [
|
|
@@ -2922,6 +3010,10 @@ function ScriptStream({
|
|
|
2922
3010
|
selection,
|
|
2923
3011
|
changedSceneKeys,
|
|
2924
3012
|
sceneDiff,
|
|
3013
|
+
actionDiffByScene,
|
|
3014
|
+
onAcceptAction,
|
|
3015
|
+
onRejectAction,
|
|
3016
|
+
scrollScopeKey,
|
|
2925
3017
|
onEdit,
|
|
2926
3018
|
onSelectionChange,
|
|
2927
3019
|
onRequestAssetDelete
|
|
@@ -2944,6 +3036,7 @@ function ScriptStream({
|
|
|
2944
3036
|
return next;
|
|
2945
3037
|
}, [deferredValue]);
|
|
2946
3038
|
const handleRef = useRef(null);
|
|
3039
|
+
useScrollStateMemo(scrollScopeKey, handleRef);
|
|
2947
3040
|
const programmaticUntil = useRef(0);
|
|
2948
3041
|
const lastEmitted = useRef(-1);
|
|
2949
3042
|
const lastScrolledKey = useRef("");
|
|
@@ -3003,8 +3096,8 @@ function ScriptStream({
|
|
|
3003
3096
|
const idx = selectionToFlatIndex(stream, selection);
|
|
3004
3097
|
if (idx == null) return;
|
|
3005
3098
|
lastScrolledKey.current = key;
|
|
3006
|
-
programmaticUntil.current = Date.now() +
|
|
3007
|
-
handleRef.current?.scrollToIndex({ index: idx, align: "start", behavior: "
|
|
3099
|
+
programmaticUntil.current = Date.now() + 200;
|
|
3100
|
+
handleRef.current?.scrollToIndex({ index: idx, align: "start", behavior: "auto" });
|
|
3008
3101
|
}, [selection, stream]);
|
|
3009
3102
|
const emitScrollSelection = (topIndex) => {
|
|
3010
3103
|
if (Date.now() < programmaticUntil.current) return;
|
|
@@ -3021,6 +3114,7 @@ function ScriptStream({
|
|
|
3021
3114
|
ref: handleRef,
|
|
3022
3115
|
className: "lj-se-stream lj-se-scroll",
|
|
3023
3116
|
data: stream.items,
|
|
3117
|
+
restoreStateFrom: readScrollState(scrollScopeKey),
|
|
3024
3118
|
increaseViewportBy: 1500,
|
|
3025
3119
|
rangeChanged: (range) => emitScrollSelection(range.startIndex),
|
|
3026
3120
|
itemContent: (_index, item) => {
|
|
@@ -3064,19 +3158,26 @@ function ScriptStream({
|
|
|
3064
3158
|
if (!scene) return null;
|
|
3065
3159
|
const focused = selection.kind === "scene" && selection.episodeIdx === item.episodeIdx && selection.sceneIdx === item.sceneIdx;
|
|
3066
3160
|
const diffKey = `${item.episodeIdx}:${item.sceneId}`;
|
|
3067
|
-
const
|
|
3068
|
-
const
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3161
|
+
const inDiff = !disabled && (changedSceneKeys?.has(diffKey) ?? false);
|
|
3162
|
+
const wholeKind = !disabled ? sceneDiff?.kindByKey.get(diffKey) : void 0;
|
|
3163
|
+
const sceneActions = !disabled ? actionDiffByScene?.get(diffKey) : void 0;
|
|
3164
|
+
const actionDiff = sceneActions && onAcceptAction && onRejectAction ? {
|
|
3165
|
+
byIndex: sceneActions.byIndex,
|
|
3166
|
+
removedBefore: sceneActions.removedBefore,
|
|
3167
|
+
onAccept: onAcceptAction,
|
|
3168
|
+
onReject: onRejectAction
|
|
3169
|
+
} : void 0;
|
|
3170
|
+
return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", "data-quote-source": true, children: /* @__PURE__ */ jsxs("div", { className: cn("lj-se-paper-seg", paperEdge(item.key), wholeKind && "is-changed"), children: [
|
|
3171
|
+
sceneDiff && inDiff ? /* @__PURE__ */ jsxs("div", { className: "lj-se-scenediff", children: [
|
|
3172
|
+
/* @__PURE__ */ jsx("span", { className: cn("lj-se-scenediff-tag", wholeKind === "added" && "is-added"), children: wholeKind === "added" ? "AI \u65B0\u589E" : "AI \u6539\u52A8" }),
|
|
3072
3173
|
/* @__PURE__ */ jsx("span", { className: "lj-se-scenediff-spacer" }),
|
|
3073
3174
|
/* @__PURE__ */ jsx(
|
|
3074
3175
|
"button",
|
|
3075
3176
|
{
|
|
3076
3177
|
type: "button",
|
|
3077
3178
|
className: "lj-se-scenediff-reject",
|
|
3078
|
-
onClick: () => sceneDiff.onReject(item.episodeIdx, item.
|
|
3079
|
-
children: "\u62D2\u7EDD"
|
|
3179
|
+
onClick: () => sceneDiff.onReject(item.episodeIdx, item.sceneId),
|
|
3180
|
+
children: "\u6574\u573A\u62D2\u7EDD"
|
|
3080
3181
|
}
|
|
3081
3182
|
),
|
|
3082
3183
|
/* @__PURE__ */ jsx(
|
|
@@ -3085,13 +3186,14 @@ function ScriptStream({
|
|
|
3085
3186
|
type: "button",
|
|
3086
3187
|
className: "lj-se-scenediff-accept",
|
|
3087
3188
|
onClick: () => sceneDiff.onAccept(item.episodeIdx, item.sceneId),
|
|
3088
|
-
children: "\u63A5\u53D7"
|
|
3189
|
+
children: "\u6574\u573A\u63A5\u53D7"
|
|
3089
3190
|
}
|
|
3090
3191
|
)
|
|
3091
3192
|
] }) : null,
|
|
3092
3193
|
/* @__PURE__ */ jsx(
|
|
3093
3194
|
SceneEditor,
|
|
3094
3195
|
{
|
|
3196
|
+
actionDiff,
|
|
3095
3197
|
episodeIndex: item.episodeIdx,
|
|
3096
3198
|
sceneIndex: item.sceneIdx,
|
|
3097
3199
|
scene,
|
|
@@ -3168,7 +3270,7 @@ function EpisodeBanner({
|
|
|
3168
3270
|
onEdit
|
|
3169
3271
|
}) {
|
|
3170
3272
|
return /* @__PURE__ */ jsxs("div", { className: "lj-se-epbanner", children: [
|
|
3171
|
-
projectTitle !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3273
|
+
projectTitle !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3172
3274
|
/* @__PURE__ */ jsx("div", { className: "lj-se-epbanner-kicker", children: "\u5267\u672C" }),
|
|
3173
3275
|
/* @__PURE__ */ jsx("h1", { className: "lj-se-epbanner-script", children: /* @__PURE__ */ jsx(
|
|
3174
3276
|
EditableText,
|
|
@@ -3267,6 +3369,21 @@ function computeChangeSet(baseline, current) {
|
|
|
3267
3369
|
const count = (title ? 1 : 0) + (worldview ? 1 : 0) + (style ? 1 : 0) + changedScenes.size + removedCount;
|
|
3268
3370
|
return { title, worldview, style, changedScenes, removedCount, count };
|
|
3269
3371
|
}
|
|
3372
|
+
function changeSetToDiffScenes(changeSet) {
|
|
3373
|
+
const out = [];
|
|
3374
|
+
for (const [key, kind] of changeSet.changedScenes) {
|
|
3375
|
+
const i = key.indexOf(":");
|
|
3376
|
+
if (i < 0) continue;
|
|
3377
|
+
const episodeIdx = Number(key.slice(0, i));
|
|
3378
|
+
const sceneId = key.slice(i + 1);
|
|
3379
|
+
if (Number.isFinite(episodeIdx)) out.push({ episodeIdx, sceneId, kind });
|
|
3380
|
+
}
|
|
3381
|
+
return out;
|
|
3382
|
+
}
|
|
3383
|
+
function buildDiffFromBaseline(baseline, current) {
|
|
3384
|
+
const cs = computeChangeSet(baseline, current);
|
|
3385
|
+
return { scenes: changeSetToDiffScenes(cs), removedCount: cs.removedCount, count: cs.count };
|
|
3386
|
+
}
|
|
3270
3387
|
function acceptSceneIntoBaseline(baseline, episodeId, episodeIdx, scene) {
|
|
3271
3388
|
const episodes = (baseline.episodes ?? []).slice();
|
|
3272
3389
|
const id = episodeId?.trim();
|
|
@@ -3304,6 +3421,170 @@ function findBaselineScene(baseline, episodeId, episodeIdx, sceneId) {
|
|
|
3304
3421
|
if (!sid) return null;
|
|
3305
3422
|
return (ep.scenes ?? []).find((s) => (s.scene_id ?? "").trim() === sid) ?? null;
|
|
3306
3423
|
}
|
|
3424
|
+
function actionSig(a) {
|
|
3425
|
+
return [a.type ?? "", (a.content ?? "").trim(), a.actor_id ?? "", a.emotion ?? "", a.delivery ?? ""].join("\0");
|
|
3426
|
+
}
|
|
3427
|
+
function computeSceneActionDiff(baseline, current) {
|
|
3428
|
+
const m = baseline.length;
|
|
3429
|
+
const n = current.length;
|
|
3430
|
+
const bs = baseline.map(actionSig);
|
|
3431
|
+
const cs = current.map(actionSig);
|
|
3432
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
3433
|
+
for (let i2 = m - 1; i2 >= 0; i2--) {
|
|
3434
|
+
for (let j2 = n - 1; j2 >= 0; j2--) {
|
|
3435
|
+
dp[i2][j2] = bs[i2] === cs[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
const ops = [];
|
|
3439
|
+
let i = 0;
|
|
3440
|
+
let j = 0;
|
|
3441
|
+
while (i < m && j < n) {
|
|
3442
|
+
if (bs[i] === cs[j]) {
|
|
3443
|
+
ops.push({ t: "match", j });
|
|
3444
|
+
i++;
|
|
3445
|
+
j++;
|
|
3446
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
3447
|
+
ops.push({ t: "del", i });
|
|
3448
|
+
i++;
|
|
3449
|
+
} else {
|
|
3450
|
+
ops.push({ t: "ins", j });
|
|
3451
|
+
j++;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
while (i < m) {
|
|
3455
|
+
ops.push({ t: "del", i });
|
|
3456
|
+
i++;
|
|
3457
|
+
}
|
|
3458
|
+
while (j < n) {
|
|
3459
|
+
ops.push({ t: "ins", j });
|
|
3460
|
+
j++;
|
|
3461
|
+
}
|
|
3462
|
+
const changed = [];
|
|
3463
|
+
const removed = [];
|
|
3464
|
+
let curJ = 0;
|
|
3465
|
+
let p = 0;
|
|
3466
|
+
while (p < ops.length) {
|
|
3467
|
+
const op = ops[p];
|
|
3468
|
+
if (op.t === "match") {
|
|
3469
|
+
curJ = op.j + 1;
|
|
3470
|
+
p++;
|
|
3471
|
+
continue;
|
|
3472
|
+
}
|
|
3473
|
+
if (op.t === "ins") {
|
|
3474
|
+
changed.push({ itemIdx: op.j, kind: "added" });
|
|
3475
|
+
curJ = op.j + 1;
|
|
3476
|
+
p++;
|
|
3477
|
+
continue;
|
|
3478
|
+
}
|
|
3479
|
+
const dels = [];
|
|
3480
|
+
while (p < ops.length && ops[p].t === "del") {
|
|
3481
|
+
dels.push(ops[p].i);
|
|
3482
|
+
p++;
|
|
3483
|
+
}
|
|
3484
|
+
const inss = [];
|
|
3485
|
+
while (p < ops.length && ops[p].t === "ins") {
|
|
3486
|
+
inss.push(ops[p].j);
|
|
3487
|
+
p++;
|
|
3488
|
+
}
|
|
3489
|
+
const pair = Math.min(dels.length, inss.length);
|
|
3490
|
+
for (let q = 0; q < pair; q++) changed.push({ itemIdx: inss[q], kind: "modified", baselineItemIdx: dels[q] });
|
|
3491
|
+
for (let q = pair; q < inss.length; q++) changed.push({ itemIdx: inss[q], kind: "added" });
|
|
3492
|
+
const ghostBefore = inss.length ? inss[inss.length - 1] + 1 : curJ;
|
|
3493
|
+
for (let q = pair; q < dels.length; q++) removed.push({ beforeItemIdx: ghostBefore, baselineItemIdx: dels[q] });
|
|
3494
|
+
if (inss.length) curJ = inss[inss.length - 1] + 1;
|
|
3495
|
+
}
|
|
3496
|
+
return { changed, removed };
|
|
3497
|
+
}
|
|
3498
|
+
function applyActionAccept(baselineScene, currentScene, mark) {
|
|
3499
|
+
const bActions = (baselineScene.actions ?? []).slice();
|
|
3500
|
+
const cActions = currentScene.actions ?? [];
|
|
3501
|
+
if (mark.kind === "modified" && mark.baselineItemIdx != null) {
|
|
3502
|
+
const item = cActions[mark.itemIdx];
|
|
3503
|
+
if (item && mark.baselineItemIdx < bActions.length) bActions[mark.baselineItemIdx] = item;
|
|
3504
|
+
} else if (mark.kind === "added") {
|
|
3505
|
+
const item = cActions[mark.itemIdx];
|
|
3506
|
+
if (item) bActions.splice(Math.min(mark.itemIdx, bActions.length), 0, item);
|
|
3507
|
+
} else if (mark.kind === "removed" && mark.baselineItemIdx != null) {
|
|
3508
|
+
if (mark.baselineItemIdx < bActions.length) bActions.splice(mark.baselineItemIdx, 1);
|
|
3509
|
+
}
|
|
3510
|
+
return { ...baselineScene, actions: bActions };
|
|
3511
|
+
}
|
|
3512
|
+
function applyActionReject(currentScene, baselineScene, mark) {
|
|
3513
|
+
const cActions = (currentScene.actions ?? []).slice();
|
|
3514
|
+
const bActions = baselineScene?.actions ?? [];
|
|
3515
|
+
if (mark.kind === "modified" && mark.baselineItemIdx != null) {
|
|
3516
|
+
const item = bActions[mark.baselineItemIdx];
|
|
3517
|
+
if (item && mark.itemIdx < cActions.length) cActions[mark.itemIdx] = item;
|
|
3518
|
+
} else if (mark.kind === "added") {
|
|
3519
|
+
if (mark.itemIdx < cActions.length) cActions.splice(mark.itemIdx, 1);
|
|
3520
|
+
} else if (mark.kind === "removed") {
|
|
3521
|
+
const item = mark.item ?? (mark.baselineItemIdx != null ? bActions[mark.baselineItemIdx] : void 0);
|
|
3522
|
+
if (item) cActions.splice(Math.min(mark.itemIdx, cActions.length), 0, item);
|
|
3523
|
+
}
|
|
3524
|
+
return { ...currentScene, actions: cActions };
|
|
3525
|
+
}
|
|
3526
|
+
function acceptActionIntoBaseline(baseline, current, mark) {
|
|
3527
|
+
const cEp = current.episodes?.[mark.episodeIdx];
|
|
3528
|
+
const curScene = cEp?.scenes?.find((s) => (s.scene_id ?? "").trim() === mark.sceneId.trim());
|
|
3529
|
+
const baseScene = findBaselineScene(baseline, cEp?.episode_id, mark.episodeIdx, mark.sceneId);
|
|
3530
|
+
if (!baseScene) {
|
|
3531
|
+
return curScene ? acceptSceneIntoBaseline(baseline, cEp?.episode_id, mark.episodeIdx, curScene) : baseline;
|
|
3532
|
+
}
|
|
3533
|
+
const next = applyActionAccept(baseScene, curScene ?? baseScene, mark);
|
|
3534
|
+
return acceptSceneIntoBaseline(baseline, cEp?.episode_id, mark.episodeIdx, next);
|
|
3535
|
+
}
|
|
3536
|
+
function rejectActionToBaseline(current, baseline, mark) {
|
|
3537
|
+
const cEp = current.episodes?.[mark.episodeIdx];
|
|
3538
|
+
const sceneIdx = cEp?.scenes?.findIndex((s) => (s.scene_id ?? "").trim() === mark.sceneId.trim()) ?? -1;
|
|
3539
|
+
if (sceneIdx < 0 || !cEp?.scenes) return current;
|
|
3540
|
+
const curScene = cEp.scenes[sceneIdx];
|
|
3541
|
+
const baseScene = findBaselineScene(baseline, cEp.episode_id, mark.episodeIdx, mark.sceneId);
|
|
3542
|
+
const next = applyActionReject(curScene, baseScene, mark);
|
|
3543
|
+
return rejectSceneToBaseline(current, mark.episodeIdx, sceneIdx, next);
|
|
3544
|
+
}
|
|
3545
|
+
function buildActionDiff(baseline, current) {
|
|
3546
|
+
const cs = computeChangeSet(baseline, current);
|
|
3547
|
+
const scenes = [];
|
|
3548
|
+
const actions = [];
|
|
3549
|
+
for (const [key, kind] of cs.changedScenes) {
|
|
3550
|
+
const ci = key.indexOf(":");
|
|
3551
|
+
const episodeIdx = Number(key.slice(0, ci));
|
|
3552
|
+
const sceneId = key.slice(ci + 1);
|
|
3553
|
+
if (!Number.isFinite(episodeIdx)) continue;
|
|
3554
|
+
if (kind === "added") {
|
|
3555
|
+
scenes.push({ episodeIdx, sceneId, kind: "added" });
|
|
3556
|
+
continue;
|
|
3557
|
+
}
|
|
3558
|
+
const ep = current.episodes?.[episodeIdx];
|
|
3559
|
+
const curScene = ep?.scenes?.find((s) => (s.scene_id ?? "").trim() === sceneId);
|
|
3560
|
+
const baseScene = findBaselineScene(baseline, ep?.episode_id, episodeIdx, sceneId);
|
|
3561
|
+
if (!curScene) continue;
|
|
3562
|
+
const sd = computeSceneActionDiff(baseScene?.actions ?? [], curScene.actions ?? []);
|
|
3563
|
+
for (const c of sd.changed) {
|
|
3564
|
+
actions.push({
|
|
3565
|
+
episodeIdx,
|
|
3566
|
+
sceneId,
|
|
3567
|
+
kind: c.kind,
|
|
3568
|
+
itemIdx: c.itemIdx,
|
|
3569
|
+
baselineItemIdx: c.baselineItemIdx,
|
|
3570
|
+
// modified carries the OLD baseline action so the editor can render the
|
|
3571
|
+
// deleted line above the new one (unified-diff view)
|
|
3572
|
+
item: c.kind === "modified" && c.baselineItemIdx != null ? baseScene?.actions?.[c.baselineItemIdx] : void 0
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
for (const r of sd.removed) {
|
|
3576
|
+
actions.push({
|
|
3577
|
+
episodeIdx,
|
|
3578
|
+
sceneId,
|
|
3579
|
+
kind: "removed",
|
|
3580
|
+
itemIdx: r.beforeItemIdx,
|
|
3581
|
+
baselineItemIdx: r.baselineItemIdx,
|
|
3582
|
+
item: baseScene?.actions?.[r.baselineItemIdx]
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
return { scenes, actions, removedCount: cs.removedCount, count: scenes.length + actions.length + cs.removedCount };
|
|
3587
|
+
}
|
|
3307
3588
|
|
|
3308
3589
|
// src/reference.ts
|
|
3309
3590
|
function asNumber(value) {
|
|
@@ -3433,6 +3714,7 @@ function ScriptEditor({
|
|
|
3433
3714
|
onRedo,
|
|
3434
3715
|
onEditingChange,
|
|
3435
3716
|
referenceFocusRequest,
|
|
3717
|
+
scrollScopeKey,
|
|
3436
3718
|
diff,
|
|
3437
3719
|
asset,
|
|
3438
3720
|
onAddChatReferences,
|
|
@@ -3453,6 +3735,31 @@ function ScriptEditor({
|
|
|
3453
3735
|
}),
|
|
3454
3736
|
[]
|
|
3455
3737
|
);
|
|
3738
|
+
const undoKeysRef = useRef({ onUndo, onRedo, canUndo, canRedo });
|
|
3739
|
+
undoKeysRef.current = { onUndo, onRedo, canUndo, canRedo };
|
|
3740
|
+
useEffect(() => {
|
|
3741
|
+
if (disabled) return;
|
|
3742
|
+
const onKeyDown = (e) => {
|
|
3743
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
3744
|
+
const t = e.target;
|
|
3745
|
+
if (t instanceof HTMLElement && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable)) {
|
|
3746
|
+
return;
|
|
3747
|
+
}
|
|
3748
|
+
const key = e.key.toLowerCase();
|
|
3749
|
+
const { onUndo: u, onRedo: r, canUndo: cu, canRedo: cr } = undoKeysRef.current;
|
|
3750
|
+
if (key === "z" && !e.shiftKey) {
|
|
3751
|
+
if (!u || !cu) return;
|
|
3752
|
+
e.preventDefault();
|
|
3753
|
+
u();
|
|
3754
|
+
} else if (key === "z" && e.shiftKey || key === "y") {
|
|
3755
|
+
if (!r || !cr) return;
|
|
3756
|
+
e.preventDefault();
|
|
3757
|
+
r();
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
document.addEventListener("keydown", onKeyDown);
|
|
3761
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
3762
|
+
}, [disabled]);
|
|
3456
3763
|
const [innerSelection, setInnerSelection] = useState(EMPTY_SELECTION);
|
|
3457
3764
|
const selection = controlledSelection ?? innerSelection;
|
|
3458
3765
|
const setSelection = useCallback(
|
|
@@ -3471,14 +3778,36 @@ function ScriptEditor({
|
|
|
3471
3778
|
},
|
|
3472
3779
|
[onFormatChange]
|
|
3473
3780
|
);
|
|
3474
|
-
const
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3781
|
+
const sceneKindByKey = useMemo(() => {
|
|
3782
|
+
const m = /* @__PURE__ */ new Map();
|
|
3783
|
+
for (const s of diff?.scenes ?? []) m.set(sceneChangeKey(s.episodeIdx, s.sceneId), s.kind);
|
|
3784
|
+
return m;
|
|
3785
|
+
}, [diff?.scenes]);
|
|
3786
|
+
const actionDiffByScene = useMemo(() => {
|
|
3787
|
+
const map = /* @__PURE__ */ new Map();
|
|
3788
|
+
for (const m of diff?.actions ?? []) {
|
|
3789
|
+
const key = sceneChangeKey(m.episodeIdx, m.sceneId);
|
|
3790
|
+
let e = map.get(key);
|
|
3791
|
+
if (!e) {
|
|
3792
|
+
e = { byIndex: /* @__PURE__ */ new Map(), removedBefore: /* @__PURE__ */ new Map() };
|
|
3793
|
+
map.set(key, e);
|
|
3794
|
+
}
|
|
3795
|
+
if (m.kind === "removed") {
|
|
3796
|
+
const arr = e.removedBefore.get(m.itemIdx) ?? [];
|
|
3797
|
+
arr.push(m);
|
|
3798
|
+
e.removedBefore.set(m.itemIdx, arr);
|
|
3799
|
+
} else {
|
|
3800
|
+
e.byIndex.set(m.itemIdx, m);
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
return map;
|
|
3804
|
+
}, [diff?.actions]);
|
|
3805
|
+
const changedSceneKeys = useMemo(() => {
|
|
3806
|
+
const s = new Set(sceneKindByKey.keys());
|
|
3807
|
+
for (const k of actionDiffByScene.keys()) s.add(k);
|
|
3808
|
+
return s;
|
|
3809
|
+
}, [sceneKindByKey, actionDiffByScene]);
|
|
3810
|
+
const diffCount = diff ? diff.count ?? diff.scenes.length + (diff.actions?.length ?? 0) + (diff.removedCount ?? 0) : 0;
|
|
3482
3811
|
const changedSelections = useMemo(() => {
|
|
3483
3812
|
if (changedSceneKeys.size === 0) return [];
|
|
3484
3813
|
const out = [];
|
|
@@ -3503,28 +3832,15 @@ function ScriptEditor({
|
|
|
3503
3832
|
},
|
|
3504
3833
|
[changedSelections, diffCursor, setSelection]
|
|
3505
3834
|
);
|
|
3506
|
-
const
|
|
3507
|
-
(
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
}
|
|
3514
|
-
|
|
3515
|
-
);
|
|
3516
|
-
const onRejectScene = useCallback(
|
|
3517
|
-
(episodeIdx, sceneIdx, sceneId) => {
|
|
3518
|
-
const ep = value.episodes?.[episodeIdx];
|
|
3519
|
-
const base = findBaselineScene(diff?.baseline ?? null, ep?.episode_id, episodeIdx, sceneId);
|
|
3520
|
-
onEdit((cur) => rejectSceneToBaseline(cur, episodeIdx, sceneIdx, base));
|
|
3521
|
-
},
|
|
3522
|
-
[diff, value, onEdit]
|
|
3523
|
-
);
|
|
3524
|
-
const sceneDiff = useMemo(
|
|
3525
|
-
() => diff?.onPatchBaseline && changeSet.changedScenes.size > 0 ? { kindByKey: changeSet.changedScenes, onAccept: onAcceptScene, onReject: onRejectScene } : void 0,
|
|
3526
|
-
[diff?.onPatchBaseline, changeSet, onAcceptScene, onRejectScene]
|
|
3527
|
-
);
|
|
3835
|
+
const sceneDiff = useMemo(() => {
|
|
3836
|
+
if (!diff || !diff.onAcceptScene && !diff.onRejectScene || changedSceneKeys.size === 0) return void 0;
|
|
3837
|
+
const kindAt = (episodeIdx, sceneId) => sceneKindByKey.get(sceneChangeKey(episodeIdx, sceneId)) ?? "modified";
|
|
3838
|
+
return {
|
|
3839
|
+
kindByKey: sceneKindByKey,
|
|
3840
|
+
onAccept: (episodeIdx, sceneId) => diff.onAcceptScene?.({ episodeIdx, sceneId, kind: kindAt(episodeIdx, sceneId) }),
|
|
3841
|
+
onReject: (episodeIdx, sceneId) => diff.onRejectScene?.({ episodeIdx, sceneId, kind: kindAt(episodeIdx, sceneId) })
|
|
3842
|
+
};
|
|
3843
|
+
}, [diff, sceneKindByKey, changedSceneKeys]);
|
|
3528
3844
|
useEffect(() => {
|
|
3529
3845
|
if (!referenceFocusRequest) return;
|
|
3530
3846
|
const sel = selectionFromScriptReference(referenceFocusRequest.reference);
|
|
@@ -3542,6 +3858,10 @@ function ScriptEditor({
|
|
|
3542
3858
|
selection,
|
|
3543
3859
|
changedSceneKeys,
|
|
3544
3860
|
sceneDiff,
|
|
3861
|
+
actionDiffByScene,
|
|
3862
|
+
onAcceptAction: diff?.onAcceptAction,
|
|
3863
|
+
onRejectAction: diff?.onRejectAction,
|
|
3864
|
+
scrollScopeKey,
|
|
3545
3865
|
onEdit,
|
|
3546
3866
|
onSelectionChange: setSelection,
|
|
3547
3867
|
onRequestAssetDelete
|
|
@@ -3577,7 +3897,7 @@ function ScriptEditor({
|
|
|
3577
3897
|
SaveCapsule,
|
|
3578
3898
|
{
|
|
3579
3899
|
saveStatus,
|
|
3580
|
-
diffCount
|
|
3900
|
+
diffCount,
|
|
3581
3901
|
baselineStatus: diff?.baselineStatus,
|
|
3582
3902
|
canApplyDiff: !!(diff?.onAcceptAll || diff?.onRejectAll),
|
|
3583
3903
|
onDiffPrev: changedSelections.length > 0 ? () => navDiff(-1) : void 0,
|
|
@@ -3691,6 +4011,6 @@ __export(mutations_exports, {
|
|
|
3691
4011
|
updateScenePropState: () => updateScenePropState
|
|
3692
4012
|
});
|
|
3693
4013
|
|
|
3694
|
-
export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, buildSelectionFrom, computeChangeSet, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
|
|
4014
|
+
export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, rejectActionToBaseline, rejectSceneToBaseline, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
|
|
3695
4015
|
//# sourceMappingURL=index.js.map
|
|
3696
4016
|
//# sourceMappingURL=index.js.map
|