@lingjingai/script-editor 0.1.1 → 0.1.14
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 +1 -1
- package/dist/index.d.ts +116 -15
- package/dist/index.js +493 -128
- package/dist/index.js.map +1 -1
- package/dist/style.css +25 -2
- package/package.json +1 -1
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 }),
|
|
@@ -1996,6 +2015,7 @@ function getActorCueVar(actorId, tone = "dialogue") {
|
|
|
1996
2015
|
return `var(--lj-se-actor-cue-${idx})`;
|
|
1997
2016
|
}
|
|
1998
2017
|
var EditingSignalContext = createContext(null);
|
|
2018
|
+
var LIVE_COMMIT_MS = 400;
|
|
1999
2019
|
function caretOffsetFromPoint(x, y) {
|
|
2000
2020
|
const d = document;
|
|
2001
2021
|
if (typeof d.caretRangeFromPoint === "function") {
|
|
@@ -2025,6 +2045,34 @@ function EditableText({
|
|
|
2025
2045
|
const inputRef = useRef(null);
|
|
2026
2046
|
const committedRef = useRef(value);
|
|
2027
2047
|
const editingSignal = useContext(EditingSignalContext);
|
|
2048
|
+
const liveTimerRef = useRef(null);
|
|
2049
|
+
const composingRef = useRef(false);
|
|
2050
|
+
const draftRef = useRef(value);
|
|
2051
|
+
const onSaveRef = useRef(onSave);
|
|
2052
|
+
onSaveRef.current = onSave;
|
|
2053
|
+
const clearLiveTimer = () => {
|
|
2054
|
+
if (liveTimerRef.current) {
|
|
2055
|
+
clearTimeout(liveTimerRef.current);
|
|
2056
|
+
liveTimerRef.current = null;
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
const flushLive = () => {
|
|
2060
|
+
clearLiveTimer();
|
|
2061
|
+
const next = draftRef.current;
|
|
2062
|
+
if (next !== committedRef.current) {
|
|
2063
|
+
committedRef.current = next;
|
|
2064
|
+
onSaveRef.current(next);
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
const scheduleLive = () => {
|
|
2068
|
+
clearLiveTimer();
|
|
2069
|
+
liveTimerRef.current = setTimeout(() => {
|
|
2070
|
+
liveTimerRef.current = null;
|
|
2071
|
+
if (composingRef.current) return;
|
|
2072
|
+
flushLive();
|
|
2073
|
+
}, LIVE_COMMIT_MS);
|
|
2074
|
+
};
|
|
2075
|
+
useEffect(() => () => flushLive(), []);
|
|
2028
2076
|
useEffect(() => {
|
|
2029
2077
|
if (!editing || !editingSignal) return;
|
|
2030
2078
|
editingSignal.inc();
|
|
@@ -2060,9 +2108,11 @@ function EditableText({
|
|
|
2060
2108
|
if (sel && !sel.isCollapsed) return;
|
|
2061
2109
|
caretRef.current = caretOffsetFromPoint(e.clientX, e.clientY);
|
|
2062
2110
|
setDraft(committedRef.current);
|
|
2111
|
+
draftRef.current = committedRef.current;
|
|
2063
2112
|
setEditing(true);
|
|
2064
2113
|
}
|
|
2065
2114
|
function commit() {
|
|
2115
|
+
clearLiveTimer();
|
|
2066
2116
|
setEditing(false);
|
|
2067
2117
|
onEditEnd?.();
|
|
2068
2118
|
if (draft !== committedRef.current) {
|
|
@@ -2071,7 +2121,9 @@ function EditableText({
|
|
|
2071
2121
|
}
|
|
2072
2122
|
}
|
|
2073
2123
|
function cancel() {
|
|
2124
|
+
clearLiveTimer();
|
|
2074
2125
|
setDraft(committedRef.current);
|
|
2126
|
+
draftRef.current = committedRef.current;
|
|
2075
2127
|
setEditing(false);
|
|
2076
2128
|
onEditEnd?.();
|
|
2077
2129
|
}
|
|
@@ -2091,8 +2143,20 @@ function EditableText({
|
|
|
2091
2143
|
value: draft,
|
|
2092
2144
|
className: "lj-se-editing" + (className ? " " + className : ""),
|
|
2093
2145
|
onChange: (e) => {
|
|
2094
|
-
|
|
2146
|
+
const v = e.target.value;
|
|
2147
|
+
setDraft(v);
|
|
2148
|
+
draftRef.current = v;
|
|
2095
2149
|
if (multiline) autoSize(e.target);
|
|
2150
|
+
if (!composingRef.current) scheduleLive();
|
|
2151
|
+
},
|
|
2152
|
+
onCompositionStart: () => {
|
|
2153
|
+
composingRef.current = true;
|
|
2154
|
+
clearLiveTimer();
|
|
2155
|
+
},
|
|
2156
|
+
onCompositionEnd: (e) => {
|
|
2157
|
+
composingRef.current = false;
|
|
2158
|
+
draftRef.current = e.currentTarget.value;
|
|
2159
|
+
scheduleLive();
|
|
2096
2160
|
},
|
|
2097
2161
|
onBlur: commit,
|
|
2098
2162
|
onKeyDown: (e) => {
|
|
@@ -2118,10 +2182,10 @@ function formatStateChangeLabels(item, actorMap, locationMap, propMap) {
|
|
|
2118
2182
|
const kind = change.target_kind?.trim() || "actor";
|
|
2119
2183
|
const id = change.target_id?.trim() || "";
|
|
2120
2184
|
if (!id) return "";
|
|
2121
|
-
const
|
|
2185
|
+
const states2 = kind === "location" ? locationMap.get(id)?.states : kind === "prop" ? propMap.get(id)?.states : actorMap.get(id)?.states;
|
|
2122
2186
|
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(
|
|
2187
|
+
const from = getStateDisplayName(findState(states2, change.from_state_id)) || change.from_state_id?.trim() || "\u672A\u8BBE";
|
|
2188
|
+
const to = getStateDisplayName(findState(states2, change.to_state_id)) || change.to_state_id?.trim() || "\u672A\u8BBE";
|
|
2125
2189
|
return `${name}\uFF1A${from} \u2192 ${to}`;
|
|
2126
2190
|
}).filter(Boolean);
|
|
2127
2191
|
if (item.transition_prompt?.process || item.transition_prompt?.contrast) {
|
|
@@ -2158,12 +2222,19 @@ function SceneItemRowImpl({
|
|
|
2158
2222
|
const type = item.type ?? "dialogue" /* Dialogue */;
|
|
2159
2223
|
const content = item.content ?? "";
|
|
2160
2224
|
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
2225
|
const emotion = translateEmotionLabel(item.emotion);
|
|
2165
2226
|
const stateLabels = formatStateChangeLabels(item, actorMap, locationMap, propMap);
|
|
2166
|
-
const
|
|
2227
|
+
const multiSpeaker = type === "dialogue" /* Dialogue */ && (item.delivery === "overlap" || (item.lines?.length ?? 0) > 0 || (item.speakers?.length ?? 0) > 1);
|
|
2228
|
+
const resolvedActorId = actorId || (() => {
|
|
2229
|
+
const spkId = item.speaker_id?.trim() || item.speakers?.[0]?.speaker_id?.trim() || "";
|
|
2230
|
+
if (!spkId) return "";
|
|
2231
|
+
const spk = speakerMap.get(spkId);
|
|
2232
|
+
const kind = spk?.source_kind?.trim();
|
|
2233
|
+
if ((kind === "actor" || kind === "prop") && spk?.source_id?.trim()) return spk.source_id.trim();
|
|
2234
|
+
if (spkId.startsWith("spk_") && actorMap.has(spkId.slice(4))) return spkId.slice(4);
|
|
2235
|
+
return "";
|
|
2236
|
+
})();
|
|
2237
|
+
const resolvedActor = actorMap.get(resolvedActorId) ?? propMap.get(resolvedActorId);
|
|
2167
2238
|
const handle = /* @__PURE__ */ jsx(
|
|
2168
2239
|
RowControlHandle,
|
|
2169
2240
|
{
|
|
@@ -2178,11 +2249,11 @@ function SceneItemRowImpl({
|
|
|
2178
2249
|
onDelete
|
|
2179
2250
|
}
|
|
2180
2251
|
);
|
|
2181
|
-
const actorCue = (tone) =>
|
|
2252
|
+
const actorCue = (tone) => multiSpeaker ? /* @__PURE__ */ jsxs(
|
|
2182
2253
|
"span",
|
|
2183
2254
|
{
|
|
2184
2255
|
className: "lj-se-cue lj-se-cue--structured",
|
|
2185
|
-
style:
|
|
2256
|
+
style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0,
|
|
2186
2257
|
children: [
|
|
2187
2258
|
getDialogueSpeakerLabel(item, actorMap, speakerMap) || "\u672A\u77E5\u53D1\u58F0\u6E90",
|
|
2188
2259
|
getDialogueDeliveryLabel(item.delivery) ? /* @__PURE__ */ jsxs("span", { className: "lj-se-cue-delivery", children: [
|
|
@@ -2195,13 +2266,13 @@ function SceneItemRowImpl({
|
|
|
2195
2266
|
) : /* @__PURE__ */ jsx(
|
|
2196
2267
|
SelectField,
|
|
2197
2268
|
{
|
|
2198
|
-
value:
|
|
2269
|
+
value: resolvedActorId,
|
|
2199
2270
|
options: actorOptions,
|
|
2200
2271
|
disabled,
|
|
2201
2272
|
placeholder: "\u672A\u77E5\u89D2\u8272",
|
|
2202
|
-
title:
|
|
2203
|
-
className: cn("lj-se-cue lj-se-cue--select",
|
|
2204
|
-
style:
|
|
2273
|
+
title: "\u5207\u6362\u89D2\u8272",
|
|
2274
|
+
className: cn("lj-se-cue lj-se-cue--select", resolvedActorId && !resolvedActor && "is-orphan"),
|
|
2275
|
+
style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0,
|
|
2205
2276
|
onChange: onSaveActor
|
|
2206
2277
|
}
|
|
2207
2278
|
);
|
|
@@ -2210,7 +2281,7 @@ function SceneItemRowImpl({
|
|
|
2210
2281
|
if (type === "dialogue" /* Dialogue */) {
|
|
2211
2282
|
body2 = /* @__PURE__ */ jsxs("div", { className: "lj-se-std-dialogue", children: [
|
|
2212
2283
|
actorCue("dialogue"),
|
|
2213
|
-
|
|
2284
|
+
/* @__PURE__ */ jsx("div", { className: "lj-se-std-emotion", children: /* @__PURE__ */ jsx(EditableText, { value: item.emotion ?? "", disabled, placeholder: "\u60C5\u7EEA\u2026", onSave: onSaveEmotion }) }),
|
|
2214
2285
|
/* @__PURE__ */ jsx(
|
|
2215
2286
|
EditableText,
|
|
2216
2287
|
{
|
|
@@ -2276,19 +2347,19 @@ function SceneItemRowImpl({
|
|
|
2276
2347
|
const tone = isInner ? "inner" : "dialogue";
|
|
2277
2348
|
const tag = isInner ? emotion ? `OS\xB7${emotion}` : "OS" : emotion;
|
|
2278
2349
|
body = /* @__PURE__ */ jsxs("p", { className: cn("lj-se-asian-cueline", isInner && "is-inner"), children: [
|
|
2279
|
-
|
|
2350
|
+
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
2351
|
SelectField,
|
|
2281
2352
|
{
|
|
2282
|
-
value:
|
|
2353
|
+
value: resolvedActorId,
|
|
2283
2354
|
options: actorOptions,
|
|
2284
2355
|
disabled,
|
|
2285
2356
|
placeholder: "\u672A\u77E5\u89D2\u8272",
|
|
2286
|
-
className: cn("lj-se-asian-speaker lj-se-asian-speaker--select",
|
|
2287
|
-
style:
|
|
2357
|
+
className: cn("lj-se-asian-speaker lj-se-asian-speaker--select", resolvedActorId && !resolvedActor && "is-orphan"),
|
|
2358
|
+
style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0,
|
|
2288
2359
|
onChange: onSaveActor
|
|
2289
2360
|
}
|
|
2290
2361
|
),
|
|
2291
|
-
!
|
|
2362
|
+
!multiSpeaker ? /* @__PURE__ */ jsxs("span", { className: "lj-se-asian-tag", children: [
|
|
2292
2363
|
"\uFF08",
|
|
2293
2364
|
/* @__PURE__ */ jsx(EditableText, { value: item.emotion ?? "", disabled, placeholder: isInner ? "OS" : "\u60C5\u7EEA", onSave: onSaveEmotion }),
|
|
2294
2365
|
"\uFF09"
|
|
@@ -2308,6 +2379,12 @@ function SceneItemRowImpl({
|
|
|
2308
2379
|
] });
|
|
2309
2380
|
}
|
|
2310
2381
|
var SceneItemRow = memo(SceneItemRowImpl);
|
|
2382
|
+
function DiffActionControls({ onAccept, onReject }) {
|
|
2383
|
+
return /* @__PURE__ */ jsxs("span", { className: "lj-se-actiondiff-ctrl", children: [
|
|
2384
|
+
/* @__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 }) }),
|
|
2385
|
+
/* @__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 }) })
|
|
2386
|
+
] });
|
|
2387
|
+
}
|
|
2311
2388
|
function SceneEditorImpl({
|
|
2312
2389
|
episodeIndex,
|
|
2313
2390
|
sceneIndex,
|
|
@@ -2316,6 +2393,7 @@ function SceneEditorImpl({
|
|
|
2316
2393
|
format,
|
|
2317
2394
|
disabled,
|
|
2318
2395
|
focused = false,
|
|
2396
|
+
actionDiff,
|
|
2319
2397
|
onEdit,
|
|
2320
2398
|
onNavigateAsset,
|
|
2321
2399
|
onRequestAssetDelete
|
|
@@ -2323,10 +2401,18 @@ function SceneEditorImpl({
|
|
|
2323
2401
|
const [pendingEdit, setPendingEdit] = useState(null);
|
|
2324
2402
|
const { actorMap, speakerMap, locationMap, propMap } = maps;
|
|
2325
2403
|
const actions = scene.actions ?? [];
|
|
2326
|
-
const actorOptions = useMemo(
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2404
|
+
const actorOptions = useMemo(() => {
|
|
2405
|
+
const out = [];
|
|
2406
|
+
for (const a of actorMap.values()) {
|
|
2407
|
+
const v = (a.actor_id ?? "").trim();
|
|
2408
|
+
if (v) out.push({ value: v, label: getActorDisplayName(a) });
|
|
2409
|
+
}
|
|
2410
|
+
for (const p of propMap.values()) {
|
|
2411
|
+
const v = (p.prop_id ?? "").trim();
|
|
2412
|
+
if (v) out.push({ value: v, label: `${getPropDisplayName(p)}\uFF08\u9053\u5177\uFF09` });
|
|
2413
|
+
}
|
|
2414
|
+
return out;
|
|
2415
|
+
}, [actorMap, propMap]);
|
|
2330
2416
|
const insertAt = useCallback(
|
|
2331
2417
|
(index, type) => {
|
|
2332
2418
|
const inherited = pickInheritedActorId(scene, index);
|
|
@@ -2362,6 +2448,32 @@ function SceneEditorImpl({
|
|
|
2362
2448
|
e.preventDefault();
|
|
2363
2449
|
dnd?.drop();
|
|
2364
2450
|
};
|
|
2451
|
+
const noop = () => {
|
|
2452
|
+
};
|
|
2453
|
+
const renderOldRow = (item) => /* @__PURE__ */ jsx(
|
|
2454
|
+
SceneItemRow,
|
|
2455
|
+
{
|
|
2456
|
+
item,
|
|
2457
|
+
actorMap,
|
|
2458
|
+
speakerMap,
|
|
2459
|
+
locationMap,
|
|
2460
|
+
propMap,
|
|
2461
|
+
actorOptions,
|
|
2462
|
+
format,
|
|
2463
|
+
disabled: true,
|
|
2464
|
+
onSave: noop,
|
|
2465
|
+
onSaveActor: noop,
|
|
2466
|
+
onSaveEmotion: noop,
|
|
2467
|
+
onChangeType: noop,
|
|
2468
|
+
onInsertAbove: noop,
|
|
2469
|
+
onInsertBelow: noop,
|
|
2470
|
+
onDelete: noop
|
|
2471
|
+
}
|
|
2472
|
+
);
|
|
2473
|
+
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: [
|
|
2474
|
+
/* @__PURE__ */ jsx(DiffActionControls, { onAccept: () => actionDiff.onAccept(m), onReject: () => actionDiff.onReject(m) }),
|
|
2475
|
+
m.item ? renderOldRow(m.item) : /* @__PURE__ */ jsx("p", { className: "lj-se-ghost-empty", children: "\uFF08\u5DF2\u5220\u9664\u7A7A\u884C\uFF09" })
|
|
2476
|
+
] }, `ghost-${beforeIdx}-${gi}`)) ?? null;
|
|
2365
2477
|
return /* @__PURE__ */ jsxs("div", { className: "lj-se-scene", children: [
|
|
2366
2478
|
/* @__PURE__ */ jsx(
|
|
2367
2479
|
SceneHeader,
|
|
@@ -2381,52 +2493,73 @@ function SceneEditorImpl({
|
|
|
2381
2493
|
}
|
|
2382
2494
|
),
|
|
2383
2495
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-actions", children: [
|
|
2384
|
-
actions.map((item, idx) =>
|
|
2385
|
-
|
|
2386
|
-
{
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
children: /* @__PURE__ */ jsx(
|
|
2391
|
-
SceneItemRow,
|
|
2496
|
+
actions.map((item, idx) => {
|
|
2497
|
+
const mark = actionDiff?.byIndex.get(idx);
|
|
2498
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2499
|
+
renderGhosts(idx),
|
|
2500
|
+
/* @__PURE__ */ jsxs(
|
|
2501
|
+
"div",
|
|
2392
2502
|
{
|
|
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
|
-
|
|
2503
|
+
className: cn(
|
|
2504
|
+
"lj-se-action-slot",
|
|
2505
|
+
dragIndex === idx && "is-dragging",
|
|
2506
|
+
dropSlot === idx && "drop-before",
|
|
2507
|
+
mark && (mark.kind === "added" ? "is-diff-added" : "is-diff-modified")
|
|
2508
|
+
),
|
|
2509
|
+
onDragOver: (e) => onSlotDragOver(e, idx),
|
|
2510
|
+
onDrop: onSlotDrop,
|
|
2511
|
+
children: [
|
|
2512
|
+
mark ? /* @__PURE__ */ jsx(
|
|
2513
|
+
DiffActionControls,
|
|
2514
|
+
{
|
|
2515
|
+
onAccept: () => actionDiff.onAccept(mark),
|
|
2516
|
+
onReject: () => actionDiff.onReject(mark)
|
|
2517
|
+
}
|
|
2518
|
+
) : null,
|
|
2519
|
+
mark?.kind === "modified" && mark.item ? /* @__PURE__ */ jsx("div", { className: "lj-se-diff-old", children: renderOldRow(mark.item) }) : null,
|
|
2520
|
+
/* @__PURE__ */ jsx(
|
|
2521
|
+
SceneItemRow,
|
|
2522
|
+
{
|
|
2523
|
+
item,
|
|
2524
|
+
actorMap,
|
|
2525
|
+
speakerMap,
|
|
2526
|
+
locationMap,
|
|
2527
|
+
propMap,
|
|
2528
|
+
actorOptions,
|
|
2529
|
+
format,
|
|
2530
|
+
disabled,
|
|
2531
|
+
autoEdit: pendingEdit === idx,
|
|
2532
|
+
onEditEnd: pendingEdit === idx ? clearPending : void 0,
|
|
2533
|
+
canSplit: idx > 0,
|
|
2534
|
+
dragHandleProps: disabled ? void 0 : { draggable: true, onDragStart: (e) => onDragStart(e, idx), onDragEnd: () => dnd?.end() },
|
|
2535
|
+
onSave: (content) => {
|
|
2536
|
+
clearPending();
|
|
2537
|
+
onEdit((cur) => updateSceneItemContent(cur, episodeIndex, sceneIndex, idx, content));
|
|
2538
|
+
},
|
|
2539
|
+
onSaveActor: (actorId) => onEdit((cur) => updateSceneItemActor(cur, episodeIndex, sceneIndex, idx, actorId)),
|
|
2540
|
+
onSaveEmotion: (emotion) => onEdit((cur) => updateSceneItemEmotion(cur, episodeIndex, sceneIndex, idx, emotion)),
|
|
2541
|
+
onChangeType: (type) => {
|
|
2542
|
+
clearPending();
|
|
2543
|
+
onEdit((cur) => updateSceneItemType(cur, episodeIndex, sceneIndex, idx, type));
|
|
2544
|
+
},
|
|
2545
|
+
onInsertAbove: () => insertSame(idx, idx),
|
|
2546
|
+
onInsertBelow: () => insertSame(idx + 1, idx),
|
|
2547
|
+
onSplitHere: idx > 0 ? () => {
|
|
2548
|
+
clearPending();
|
|
2549
|
+
onEdit((cur) => splitScene(cur, episodeIndex, sceneIndex, idx));
|
|
2550
|
+
} : void 0,
|
|
2551
|
+
onDelete: () => {
|
|
2552
|
+
clearPending();
|
|
2553
|
+
onEdit((cur) => deleteSceneItem(cur, episodeIndex, sceneIndex, idx));
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
)
|
|
2557
|
+
]
|
|
2425
2558
|
}
|
|
2426
2559
|
)
|
|
2427
|
-
},
|
|
2428
|
-
|
|
2429
|
-
)
|
|
2560
|
+
] }, `item-${idx}`);
|
|
2561
|
+
}),
|
|
2562
|
+
renderGhosts(actions.length),
|
|
2430
2563
|
dropSlot === actions.length && actions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "lj-se-dropline-end" }) : null,
|
|
2431
2564
|
actions.length === 0 && !disabled ? /* @__PURE__ */ jsxs(
|
|
2432
2565
|
"button",
|
|
@@ -2804,7 +2937,7 @@ function AssetCardImpl({
|
|
|
2804
2937
|
const name = group === "actors" ? getActorDisplayName(entity) : group === "locations" ? getLocationDisplayName(entity) : getPropDisplayName(entity);
|
|
2805
2938
|
const rawDescription = entity.description ?? "";
|
|
2806
2939
|
const description = rawDescription.trim();
|
|
2807
|
-
const
|
|
2940
|
+
const states2 = entity.states ?? [];
|
|
2808
2941
|
const fromRef = buildSelectionFrom(kind, assetId);
|
|
2809
2942
|
return /* @__PURE__ */ jsx("div", { className: "lj-se-assetcard-row", children: /* @__PURE__ */ jsxs("article", { className: "lj-se-assetcard", "data-no-quote-selection": true, children: [
|
|
2810
2943
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-head", children: [
|
|
@@ -2852,7 +2985,7 @@ function AssetCardImpl({
|
|
|
2852
2985
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-states-head", children: [
|
|
2853
2986
|
/* @__PURE__ */ jsxs("span", { className: "lj-se-assetcard-states-label", children: [
|
|
2854
2987
|
"\u72B6\u6001 ",
|
|
2855
|
-
/* @__PURE__ */ jsx("span", { className: "lj-se-field-hint", children:
|
|
2988
|
+
/* @__PURE__ */ jsx("span", { className: "lj-se-field-hint", children: states2.length })
|
|
2856
2989
|
] }),
|
|
2857
2990
|
!disabled ? /* @__PURE__ */ jsxs(
|
|
2858
2991
|
"button",
|
|
@@ -2867,7 +3000,7 @@ function AssetCardImpl({
|
|
|
2867
3000
|
}
|
|
2868
3001
|
) : null
|
|
2869
3002
|
] }),
|
|
2870
|
-
|
|
3003
|
+
states2.length > 0 ? /* @__PURE__ */ jsx("div", { className: "lj-se-assetcard-state-cards", children: states2.map((state, idx) => {
|
|
2871
3004
|
const sid = (state.state_id ?? "").trim() || `idx-${idx}`;
|
|
2872
3005
|
return /* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-state", children: [
|
|
2873
3006
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-state-top", children: [
|
|
@@ -2922,6 +3055,10 @@ function ScriptStream({
|
|
|
2922
3055
|
selection,
|
|
2923
3056
|
changedSceneKeys,
|
|
2924
3057
|
sceneDiff,
|
|
3058
|
+
actionDiffByScene,
|
|
3059
|
+
onAcceptAction,
|
|
3060
|
+
onRejectAction,
|
|
3061
|
+
scrollScopeKey,
|
|
2925
3062
|
onEdit,
|
|
2926
3063
|
onSelectionChange,
|
|
2927
3064
|
onRequestAssetDelete
|
|
@@ -2944,6 +3081,7 @@ function ScriptStream({
|
|
|
2944
3081
|
return next;
|
|
2945
3082
|
}, [deferredValue]);
|
|
2946
3083
|
const handleRef = useRef(null);
|
|
3084
|
+
useScrollStateMemo(scrollScopeKey, handleRef);
|
|
2947
3085
|
const programmaticUntil = useRef(0);
|
|
2948
3086
|
const lastEmitted = useRef(-1);
|
|
2949
3087
|
const lastScrolledKey = useRef("");
|
|
@@ -3003,8 +3141,8 @@ function ScriptStream({
|
|
|
3003
3141
|
const idx = selectionToFlatIndex(stream, selection);
|
|
3004
3142
|
if (idx == null) return;
|
|
3005
3143
|
lastScrolledKey.current = key;
|
|
3006
|
-
programmaticUntil.current = Date.now() +
|
|
3007
|
-
handleRef.current?.scrollToIndex({ index: idx, align: "start", behavior: "
|
|
3144
|
+
programmaticUntil.current = Date.now() + 200;
|
|
3145
|
+
handleRef.current?.scrollToIndex({ index: idx, align: "start", behavior: "auto" });
|
|
3008
3146
|
}, [selection, stream]);
|
|
3009
3147
|
const emitScrollSelection = (topIndex) => {
|
|
3010
3148
|
if (Date.now() < programmaticUntil.current) return;
|
|
@@ -3021,6 +3159,7 @@ function ScriptStream({
|
|
|
3021
3159
|
ref: handleRef,
|
|
3022
3160
|
className: "lj-se-stream lj-se-scroll",
|
|
3023
3161
|
data: stream.items,
|
|
3162
|
+
restoreStateFrom: readScrollState(scrollScopeKey),
|
|
3024
3163
|
increaseViewportBy: 1500,
|
|
3025
3164
|
rangeChanged: (range) => emitScrollSelection(range.startIndex),
|
|
3026
3165
|
itemContent: (_index, item) => {
|
|
@@ -3064,19 +3203,26 @@ function ScriptStream({
|
|
|
3064
3203
|
if (!scene) return null;
|
|
3065
3204
|
const focused = selection.kind === "scene" && selection.episodeIdx === item.episodeIdx && selection.sceneIdx === item.sceneIdx;
|
|
3066
3205
|
const diffKey = `${item.episodeIdx}:${item.sceneId}`;
|
|
3067
|
-
const
|
|
3068
|
-
const
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3206
|
+
const inDiff = !disabled && (changedSceneKeys?.has(diffKey) ?? false);
|
|
3207
|
+
const wholeKind = !disabled ? sceneDiff?.kindByKey.get(diffKey) : void 0;
|
|
3208
|
+
const sceneActions = !disabled ? actionDiffByScene?.get(diffKey) : void 0;
|
|
3209
|
+
const actionDiff = sceneActions && onAcceptAction && onRejectAction ? {
|
|
3210
|
+
byIndex: sceneActions.byIndex,
|
|
3211
|
+
removedBefore: sceneActions.removedBefore,
|
|
3212
|
+
onAccept: onAcceptAction,
|
|
3213
|
+
onReject: onRejectAction
|
|
3214
|
+
} : void 0;
|
|
3215
|
+
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: [
|
|
3216
|
+
sceneDiff && inDiff ? /* @__PURE__ */ jsxs("div", { className: "lj-se-scenediff", children: [
|
|
3217
|
+
/* @__PURE__ */ jsx("span", { className: cn("lj-se-scenediff-tag", wholeKind === "added" && "is-added"), children: wholeKind === "added" ? "AI \u65B0\u589E" : "AI \u6539\u52A8" }),
|
|
3072
3218
|
/* @__PURE__ */ jsx("span", { className: "lj-se-scenediff-spacer" }),
|
|
3073
3219
|
/* @__PURE__ */ jsx(
|
|
3074
3220
|
"button",
|
|
3075
3221
|
{
|
|
3076
3222
|
type: "button",
|
|
3077
3223
|
className: "lj-se-scenediff-reject",
|
|
3078
|
-
onClick: () => sceneDiff.onReject(item.episodeIdx, item.
|
|
3079
|
-
children: "\u62D2\u7EDD"
|
|
3224
|
+
onClick: () => sceneDiff.onReject(item.episodeIdx, item.sceneId),
|
|
3225
|
+
children: "\u6574\u573A\u62D2\u7EDD"
|
|
3080
3226
|
}
|
|
3081
3227
|
),
|
|
3082
3228
|
/* @__PURE__ */ jsx(
|
|
@@ -3085,13 +3231,14 @@ function ScriptStream({
|
|
|
3085
3231
|
type: "button",
|
|
3086
3232
|
className: "lj-se-scenediff-accept",
|
|
3087
3233
|
onClick: () => sceneDiff.onAccept(item.episodeIdx, item.sceneId),
|
|
3088
|
-
children: "\u63A5\u53D7"
|
|
3234
|
+
children: "\u6574\u573A\u63A5\u53D7"
|
|
3089
3235
|
}
|
|
3090
3236
|
)
|
|
3091
3237
|
] }) : null,
|
|
3092
3238
|
/* @__PURE__ */ jsx(
|
|
3093
3239
|
SceneEditor,
|
|
3094
3240
|
{
|
|
3241
|
+
actionDiff,
|
|
3095
3242
|
episodeIndex: item.episodeIdx,
|
|
3096
3243
|
sceneIndex: item.sceneIdx,
|
|
3097
3244
|
scene,
|
|
@@ -3168,7 +3315,7 @@ function EpisodeBanner({
|
|
|
3168
3315
|
onEdit
|
|
3169
3316
|
}) {
|
|
3170
3317
|
return /* @__PURE__ */ jsxs("div", { className: "lj-se-epbanner", children: [
|
|
3171
|
-
projectTitle !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3318
|
+
projectTitle !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3172
3319
|
/* @__PURE__ */ jsx("div", { className: "lj-se-epbanner-kicker", children: "\u5267\u672C" }),
|
|
3173
3320
|
/* @__PURE__ */ jsx("h1", { className: "lj-se-epbanner-script", children: /* @__PURE__ */ jsx(
|
|
3174
3321
|
EditableText,
|
|
@@ -3267,6 +3414,21 @@ function computeChangeSet(baseline, current) {
|
|
|
3267
3414
|
const count = (title ? 1 : 0) + (worldview ? 1 : 0) + (style ? 1 : 0) + changedScenes.size + removedCount;
|
|
3268
3415
|
return { title, worldview, style, changedScenes, removedCount, count };
|
|
3269
3416
|
}
|
|
3417
|
+
function changeSetToDiffScenes(changeSet) {
|
|
3418
|
+
const out = [];
|
|
3419
|
+
for (const [key, kind] of changeSet.changedScenes) {
|
|
3420
|
+
const i = key.indexOf(":");
|
|
3421
|
+
if (i < 0) continue;
|
|
3422
|
+
const episodeIdx = Number(key.slice(0, i));
|
|
3423
|
+
const sceneId = key.slice(i + 1);
|
|
3424
|
+
if (Number.isFinite(episodeIdx)) out.push({ episodeIdx, sceneId, kind });
|
|
3425
|
+
}
|
|
3426
|
+
return out;
|
|
3427
|
+
}
|
|
3428
|
+
function buildDiffFromBaseline(baseline, current) {
|
|
3429
|
+
const cs = computeChangeSet(baseline, current);
|
|
3430
|
+
return { scenes: changeSetToDiffScenes(cs), removedCount: cs.removedCount, count: cs.count };
|
|
3431
|
+
}
|
|
3270
3432
|
function acceptSceneIntoBaseline(baseline, episodeId, episodeIdx, scene) {
|
|
3271
3433
|
const episodes = (baseline.episodes ?? []).slice();
|
|
3272
3434
|
const id = episodeId?.trim();
|
|
@@ -3304,6 +3466,170 @@ function findBaselineScene(baseline, episodeId, episodeIdx, sceneId) {
|
|
|
3304
3466
|
if (!sid) return null;
|
|
3305
3467
|
return (ep.scenes ?? []).find((s) => (s.scene_id ?? "").trim() === sid) ?? null;
|
|
3306
3468
|
}
|
|
3469
|
+
function actionSig(a) {
|
|
3470
|
+
return [a.type ?? "", (a.content ?? "").trim(), a.actor_id ?? "", a.emotion ?? "", a.delivery ?? ""].join("\0");
|
|
3471
|
+
}
|
|
3472
|
+
function computeSceneActionDiff(baseline, current) {
|
|
3473
|
+
const m = baseline.length;
|
|
3474
|
+
const n = current.length;
|
|
3475
|
+
const bs = baseline.map(actionSig);
|
|
3476
|
+
const cs = current.map(actionSig);
|
|
3477
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
3478
|
+
for (let i2 = m - 1; i2 >= 0; i2--) {
|
|
3479
|
+
for (let j2 = n - 1; j2 >= 0; j2--) {
|
|
3480
|
+
dp[i2][j2] = bs[i2] === cs[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
const ops = [];
|
|
3484
|
+
let i = 0;
|
|
3485
|
+
let j = 0;
|
|
3486
|
+
while (i < m && j < n) {
|
|
3487
|
+
if (bs[i] === cs[j]) {
|
|
3488
|
+
ops.push({ t: "match", j });
|
|
3489
|
+
i++;
|
|
3490
|
+
j++;
|
|
3491
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
3492
|
+
ops.push({ t: "del", i });
|
|
3493
|
+
i++;
|
|
3494
|
+
} else {
|
|
3495
|
+
ops.push({ t: "ins", j });
|
|
3496
|
+
j++;
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
while (i < m) {
|
|
3500
|
+
ops.push({ t: "del", i });
|
|
3501
|
+
i++;
|
|
3502
|
+
}
|
|
3503
|
+
while (j < n) {
|
|
3504
|
+
ops.push({ t: "ins", j });
|
|
3505
|
+
j++;
|
|
3506
|
+
}
|
|
3507
|
+
const changed = [];
|
|
3508
|
+
const removed = [];
|
|
3509
|
+
let curJ = 0;
|
|
3510
|
+
let p = 0;
|
|
3511
|
+
while (p < ops.length) {
|
|
3512
|
+
const op = ops[p];
|
|
3513
|
+
if (op.t === "match") {
|
|
3514
|
+
curJ = op.j + 1;
|
|
3515
|
+
p++;
|
|
3516
|
+
continue;
|
|
3517
|
+
}
|
|
3518
|
+
if (op.t === "ins") {
|
|
3519
|
+
changed.push({ itemIdx: op.j, kind: "added" });
|
|
3520
|
+
curJ = op.j + 1;
|
|
3521
|
+
p++;
|
|
3522
|
+
continue;
|
|
3523
|
+
}
|
|
3524
|
+
const dels = [];
|
|
3525
|
+
while (p < ops.length && ops[p].t === "del") {
|
|
3526
|
+
dels.push(ops[p].i);
|
|
3527
|
+
p++;
|
|
3528
|
+
}
|
|
3529
|
+
const inss = [];
|
|
3530
|
+
while (p < ops.length && ops[p].t === "ins") {
|
|
3531
|
+
inss.push(ops[p].j);
|
|
3532
|
+
p++;
|
|
3533
|
+
}
|
|
3534
|
+
const pair = Math.min(dels.length, inss.length);
|
|
3535
|
+
for (let q = 0; q < pair; q++) changed.push({ itemIdx: inss[q], kind: "modified", baselineItemIdx: dels[q] });
|
|
3536
|
+
for (let q = pair; q < inss.length; q++) changed.push({ itemIdx: inss[q], kind: "added" });
|
|
3537
|
+
const ghostBefore = inss.length ? inss[inss.length - 1] + 1 : curJ;
|
|
3538
|
+
for (let q = pair; q < dels.length; q++) removed.push({ beforeItemIdx: ghostBefore, baselineItemIdx: dels[q] });
|
|
3539
|
+
if (inss.length) curJ = inss[inss.length - 1] + 1;
|
|
3540
|
+
}
|
|
3541
|
+
return { changed, removed };
|
|
3542
|
+
}
|
|
3543
|
+
function applyActionAccept(baselineScene, currentScene, mark) {
|
|
3544
|
+
const bActions = (baselineScene.actions ?? []).slice();
|
|
3545
|
+
const cActions = currentScene.actions ?? [];
|
|
3546
|
+
if (mark.kind === "modified" && mark.baselineItemIdx != null) {
|
|
3547
|
+
const item = cActions[mark.itemIdx];
|
|
3548
|
+
if (item && mark.baselineItemIdx < bActions.length) bActions[mark.baselineItemIdx] = item;
|
|
3549
|
+
} else if (mark.kind === "added") {
|
|
3550
|
+
const item = cActions[mark.itemIdx];
|
|
3551
|
+
if (item) bActions.splice(Math.min(mark.itemIdx, bActions.length), 0, item);
|
|
3552
|
+
} else if (mark.kind === "removed" && mark.baselineItemIdx != null) {
|
|
3553
|
+
if (mark.baselineItemIdx < bActions.length) bActions.splice(mark.baselineItemIdx, 1);
|
|
3554
|
+
}
|
|
3555
|
+
return { ...baselineScene, actions: bActions };
|
|
3556
|
+
}
|
|
3557
|
+
function applyActionReject(currentScene, baselineScene, mark) {
|
|
3558
|
+
const cActions = (currentScene.actions ?? []).slice();
|
|
3559
|
+
const bActions = baselineScene?.actions ?? [];
|
|
3560
|
+
if (mark.kind === "modified" && mark.baselineItemIdx != null) {
|
|
3561
|
+
const item = bActions[mark.baselineItemIdx];
|
|
3562
|
+
if (item && mark.itemIdx < cActions.length) cActions[mark.itemIdx] = item;
|
|
3563
|
+
} else if (mark.kind === "added") {
|
|
3564
|
+
if (mark.itemIdx < cActions.length) cActions.splice(mark.itemIdx, 1);
|
|
3565
|
+
} else if (mark.kind === "removed") {
|
|
3566
|
+
const item = mark.item ?? (mark.baselineItemIdx != null ? bActions[mark.baselineItemIdx] : void 0);
|
|
3567
|
+
if (item) cActions.splice(Math.min(mark.itemIdx, cActions.length), 0, item);
|
|
3568
|
+
}
|
|
3569
|
+
return { ...currentScene, actions: cActions };
|
|
3570
|
+
}
|
|
3571
|
+
function acceptActionIntoBaseline(baseline, current, mark) {
|
|
3572
|
+
const cEp = current.episodes?.[mark.episodeIdx];
|
|
3573
|
+
const curScene = cEp?.scenes?.find((s) => (s.scene_id ?? "").trim() === mark.sceneId.trim());
|
|
3574
|
+
const baseScene = findBaselineScene(baseline, cEp?.episode_id, mark.episodeIdx, mark.sceneId);
|
|
3575
|
+
if (!baseScene) {
|
|
3576
|
+
return curScene ? acceptSceneIntoBaseline(baseline, cEp?.episode_id, mark.episodeIdx, curScene) : baseline;
|
|
3577
|
+
}
|
|
3578
|
+
const next = applyActionAccept(baseScene, curScene ?? baseScene, mark);
|
|
3579
|
+
return acceptSceneIntoBaseline(baseline, cEp?.episode_id, mark.episodeIdx, next);
|
|
3580
|
+
}
|
|
3581
|
+
function rejectActionToBaseline(current, baseline, mark) {
|
|
3582
|
+
const cEp = current.episodes?.[mark.episodeIdx];
|
|
3583
|
+
const sceneIdx = cEp?.scenes?.findIndex((s) => (s.scene_id ?? "").trim() === mark.sceneId.trim()) ?? -1;
|
|
3584
|
+
if (sceneIdx < 0 || !cEp?.scenes) return current;
|
|
3585
|
+
const curScene = cEp.scenes[sceneIdx];
|
|
3586
|
+
const baseScene = findBaselineScene(baseline, cEp.episode_id, mark.episodeIdx, mark.sceneId);
|
|
3587
|
+
const next = applyActionReject(curScene, baseScene, mark);
|
|
3588
|
+
return rejectSceneToBaseline(current, mark.episodeIdx, sceneIdx, next);
|
|
3589
|
+
}
|
|
3590
|
+
function buildActionDiff(baseline, current) {
|
|
3591
|
+
const cs = computeChangeSet(baseline, current);
|
|
3592
|
+
const scenes = [];
|
|
3593
|
+
const actions = [];
|
|
3594
|
+
for (const [key, kind] of cs.changedScenes) {
|
|
3595
|
+
const ci = key.indexOf(":");
|
|
3596
|
+
const episodeIdx = Number(key.slice(0, ci));
|
|
3597
|
+
const sceneId = key.slice(ci + 1);
|
|
3598
|
+
if (!Number.isFinite(episodeIdx)) continue;
|
|
3599
|
+
if (kind === "added") {
|
|
3600
|
+
scenes.push({ episodeIdx, sceneId, kind: "added" });
|
|
3601
|
+
continue;
|
|
3602
|
+
}
|
|
3603
|
+
const ep = current.episodes?.[episodeIdx];
|
|
3604
|
+
const curScene = ep?.scenes?.find((s) => (s.scene_id ?? "").trim() === sceneId);
|
|
3605
|
+
const baseScene = findBaselineScene(baseline, ep?.episode_id, episodeIdx, sceneId);
|
|
3606
|
+
if (!curScene) continue;
|
|
3607
|
+
const sd = computeSceneActionDiff(baseScene?.actions ?? [], curScene.actions ?? []);
|
|
3608
|
+
for (const c of sd.changed) {
|
|
3609
|
+
actions.push({
|
|
3610
|
+
episodeIdx,
|
|
3611
|
+
sceneId,
|
|
3612
|
+
kind: c.kind,
|
|
3613
|
+
itemIdx: c.itemIdx,
|
|
3614
|
+
baselineItemIdx: c.baselineItemIdx,
|
|
3615
|
+
// modified carries the OLD baseline action so the editor can render the
|
|
3616
|
+
// deleted line above the new one (unified-diff view)
|
|
3617
|
+
item: c.kind === "modified" && c.baselineItemIdx != null ? baseScene?.actions?.[c.baselineItemIdx] : void 0
|
|
3618
|
+
});
|
|
3619
|
+
}
|
|
3620
|
+
for (const r of sd.removed) {
|
|
3621
|
+
actions.push({
|
|
3622
|
+
episodeIdx,
|
|
3623
|
+
sceneId,
|
|
3624
|
+
kind: "removed",
|
|
3625
|
+
itemIdx: r.beforeItemIdx,
|
|
3626
|
+
baselineItemIdx: r.baselineItemIdx,
|
|
3627
|
+
item: baseScene?.actions?.[r.baselineItemIdx]
|
|
3628
|
+
});
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
return { scenes, actions, removedCount: cs.removedCount, count: scenes.length + actions.length + cs.removedCount };
|
|
3632
|
+
}
|
|
3307
3633
|
|
|
3308
3634
|
// src/reference.ts
|
|
3309
3635
|
function asNumber(value) {
|
|
@@ -3433,6 +3759,7 @@ function ScriptEditor({
|
|
|
3433
3759
|
onRedo,
|
|
3434
3760
|
onEditingChange,
|
|
3435
3761
|
referenceFocusRequest,
|
|
3762
|
+
scrollScopeKey,
|
|
3436
3763
|
diff,
|
|
3437
3764
|
asset,
|
|
3438
3765
|
onAddChatReferences,
|
|
@@ -3453,6 +3780,31 @@ function ScriptEditor({
|
|
|
3453
3780
|
}),
|
|
3454
3781
|
[]
|
|
3455
3782
|
);
|
|
3783
|
+
const undoKeysRef = useRef({ onUndo, onRedo, canUndo, canRedo });
|
|
3784
|
+
undoKeysRef.current = { onUndo, onRedo, canUndo, canRedo };
|
|
3785
|
+
useEffect(() => {
|
|
3786
|
+
if (disabled) return;
|
|
3787
|
+
const onKeyDown = (e) => {
|
|
3788
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
3789
|
+
const t = e.target;
|
|
3790
|
+
if (t instanceof HTMLElement && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable)) {
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
const key = e.key.toLowerCase();
|
|
3794
|
+
const { onUndo: u, onRedo: r, canUndo: cu, canRedo: cr } = undoKeysRef.current;
|
|
3795
|
+
if (key === "z" && !e.shiftKey) {
|
|
3796
|
+
if (!u || !cu) return;
|
|
3797
|
+
e.preventDefault();
|
|
3798
|
+
u();
|
|
3799
|
+
} else if (key === "z" && e.shiftKey || key === "y") {
|
|
3800
|
+
if (!r || !cr) return;
|
|
3801
|
+
e.preventDefault();
|
|
3802
|
+
r();
|
|
3803
|
+
}
|
|
3804
|
+
};
|
|
3805
|
+
document.addEventListener("keydown", onKeyDown);
|
|
3806
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
3807
|
+
}, [disabled]);
|
|
3456
3808
|
const [innerSelection, setInnerSelection] = useState(EMPTY_SELECTION);
|
|
3457
3809
|
const selection = controlledSelection ?? innerSelection;
|
|
3458
3810
|
const setSelection = useCallback(
|
|
@@ -3471,14 +3823,36 @@ function ScriptEditor({
|
|
|
3471
3823
|
},
|
|
3472
3824
|
[onFormatChange]
|
|
3473
3825
|
);
|
|
3474
|
-
const
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3826
|
+
const sceneKindByKey = useMemo(() => {
|
|
3827
|
+
const m = /* @__PURE__ */ new Map();
|
|
3828
|
+
for (const s of diff?.scenes ?? []) m.set(sceneChangeKey(s.episodeIdx, s.sceneId), s.kind);
|
|
3829
|
+
return m;
|
|
3830
|
+
}, [diff?.scenes]);
|
|
3831
|
+
const actionDiffByScene = useMemo(() => {
|
|
3832
|
+
const map = /* @__PURE__ */ new Map();
|
|
3833
|
+
for (const m of diff?.actions ?? []) {
|
|
3834
|
+
const key = sceneChangeKey(m.episodeIdx, m.sceneId);
|
|
3835
|
+
let e = map.get(key);
|
|
3836
|
+
if (!e) {
|
|
3837
|
+
e = { byIndex: /* @__PURE__ */ new Map(), removedBefore: /* @__PURE__ */ new Map() };
|
|
3838
|
+
map.set(key, e);
|
|
3839
|
+
}
|
|
3840
|
+
if (m.kind === "removed") {
|
|
3841
|
+
const arr = e.removedBefore.get(m.itemIdx) ?? [];
|
|
3842
|
+
arr.push(m);
|
|
3843
|
+
e.removedBefore.set(m.itemIdx, arr);
|
|
3844
|
+
} else {
|
|
3845
|
+
e.byIndex.set(m.itemIdx, m);
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
return map;
|
|
3849
|
+
}, [diff?.actions]);
|
|
3850
|
+
const changedSceneKeys = useMemo(() => {
|
|
3851
|
+
const s = new Set(sceneKindByKey.keys());
|
|
3852
|
+
for (const k of actionDiffByScene.keys()) s.add(k);
|
|
3853
|
+
return s;
|
|
3854
|
+
}, [sceneKindByKey, actionDiffByScene]);
|
|
3855
|
+
const diffCount = diff ? diff.count ?? diff.scenes.length + (diff.actions?.length ?? 0) + (diff.removedCount ?? 0) : 0;
|
|
3482
3856
|
const changedSelections = useMemo(() => {
|
|
3483
3857
|
if (changedSceneKeys.size === 0) return [];
|
|
3484
3858
|
const out = [];
|
|
@@ -3503,28 +3877,15 @@ function ScriptEditor({
|
|
|
3503
3877
|
},
|
|
3504
3878
|
[changedSelections, diffCursor, setSelection]
|
|
3505
3879
|
);
|
|
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
|
-
);
|
|
3880
|
+
const sceneDiff = useMemo(() => {
|
|
3881
|
+
if (!diff || !diff.onAcceptScene && !diff.onRejectScene || changedSceneKeys.size === 0) return void 0;
|
|
3882
|
+
const kindAt = (episodeIdx, sceneId) => sceneKindByKey.get(sceneChangeKey(episodeIdx, sceneId)) ?? "modified";
|
|
3883
|
+
return {
|
|
3884
|
+
kindByKey: sceneKindByKey,
|
|
3885
|
+
onAccept: (episodeIdx, sceneId) => diff.onAcceptScene?.({ episodeIdx, sceneId, kind: kindAt(episodeIdx, sceneId) }),
|
|
3886
|
+
onReject: (episodeIdx, sceneId) => diff.onRejectScene?.({ episodeIdx, sceneId, kind: kindAt(episodeIdx, sceneId) })
|
|
3887
|
+
};
|
|
3888
|
+
}, [diff, sceneKindByKey, changedSceneKeys]);
|
|
3528
3889
|
useEffect(() => {
|
|
3529
3890
|
if (!referenceFocusRequest) return;
|
|
3530
3891
|
const sel = selectionFromScriptReference(referenceFocusRequest.reference);
|
|
@@ -3542,6 +3903,10 @@ function ScriptEditor({
|
|
|
3542
3903
|
selection,
|
|
3543
3904
|
changedSceneKeys,
|
|
3544
3905
|
sceneDiff,
|
|
3906
|
+
actionDiffByScene,
|
|
3907
|
+
onAcceptAction: diff?.onAcceptAction,
|
|
3908
|
+
onRejectAction: diff?.onRejectAction,
|
|
3909
|
+
scrollScopeKey,
|
|
3545
3910
|
onEdit,
|
|
3546
3911
|
onSelectionChange: setSelection,
|
|
3547
3912
|
onRequestAssetDelete
|
|
@@ -3577,7 +3942,7 @@ function ScriptEditor({
|
|
|
3577
3942
|
SaveCapsule,
|
|
3578
3943
|
{
|
|
3579
3944
|
saveStatus,
|
|
3580
|
-
diffCount
|
|
3945
|
+
diffCount,
|
|
3581
3946
|
baselineStatus: diff?.baselineStatus,
|
|
3582
3947
|
canApplyDiff: !!(diff?.onAcceptAll || diff?.onRejectAll),
|
|
3583
3948
|
onDiffPrev: changedSelections.length > 0 ? () => navDiff(-1) : void 0,
|
|
@@ -3691,6 +4056,6 @@ __export(mutations_exports, {
|
|
|
3691
4056
|
updateScenePropState: () => updateScenePropState
|
|
3692
4057
|
});
|
|
3693
4058
|
|
|
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 };
|
|
4059
|
+
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
4060
|
//# sourceMappingURL=index.js.map
|
|
3696
4061
|
//# sourceMappingURL=index.js.map
|