@leviosa-ai/detail-page-editor 0.14.0 → 0.14.2
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.
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
27
27
|
|
|
28
|
-
import {
|
|
28
|
+
import { movePageToFrame } from "@leviosa-ai/canvas/edit/commands";
|
|
29
29
|
import { frameInsertIndex, frameOf } from "@leviosa-ai/canvas/render/frames";
|
|
30
30
|
import type { CanvasStore } from "@leviosa-ai/canvas/store";
|
|
31
31
|
import { detailPageEditorProfile } from "../../lib/detail-page/editor-profile";
|
|
@@ -192,7 +192,9 @@ export function FrameDragLayer({
|
|
|
192
192
|
if (from !== to) page.setZIndex(to);
|
|
193
193
|
return;
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
// 다른 벌로 끌면 **옮긴다.** 요소와 같은 규칙이다 — 화면에서 같은 손짓인데
|
|
196
|
+
// 하나는 옮기고 하나는 두 개가 되면 어느 쪽이 어느 쪽인지 외워야 한다.
|
|
197
|
+
movePageToFrame(store, current.pageId, current.drop.frameKey, current.drop.at);
|
|
196
198
|
};
|
|
197
199
|
window.addEventListener("pointermove", onMove);
|
|
198
200
|
window.addEventListener("pointerup", onUp);
|
|
@@ -50,7 +50,11 @@ import { CanvasSectionHeightHandle } from "./section-height-handle";
|
|
|
50
50
|
import { loadEditorFont } from "../../lib/detail-page-canvas/editor-fonts";
|
|
51
51
|
import { ZoomButtons } from "@leviosa-ai/canvas";
|
|
52
52
|
import { CanvasView } from "@leviosa-ai/canvas/render/canvas-view";
|
|
53
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
frameOf,
|
|
55
|
+
frameVanished,
|
|
56
|
+
groupFrames,
|
|
57
|
+
} from "@leviosa-ai/canvas/render/frames";
|
|
54
58
|
import {
|
|
55
59
|
DetailPageFrameHeader,
|
|
56
60
|
FRAME_HEAD_HEIGHT,
|
|
@@ -344,7 +348,35 @@ export function LeviosaCanvasWorkspace({
|
|
|
344
348
|
};
|
|
345
349
|
}, [panelOpen, pageIds, store, thumbnailRevision]);
|
|
346
350
|
|
|
347
|
-
const
|
|
351
|
+
const frames = groupFrames(store.pages);
|
|
352
|
+
const frameCount = frames.length;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 벌 하나가 통째로 사라지면 **되돌릴 길을 띄운다.**
|
|
356
|
+
*
|
|
357
|
+
* 벌은 문서의 층이 아니라 판에 붙은 이름표라, 마지막 판을 옆 벌로 끌면 그 벌은 남을
|
|
358
|
+
* 자리가 없어 사라진다. 그게 틀린 결과는 아니다 — 남은 판이 없으니 열도 없다. 문제는
|
|
359
|
+
* 되돌릴 창이 짧다는 것이다: 자동저장은 이미 나갔고, 새로고침하면 되돌리기 기록이
|
|
360
|
+
* 없어져 실수로 끈 벌이 영영 안 돌아온다.
|
|
361
|
+
*
|
|
362
|
+
* 그래서 «막는» 대신 «돌아올 길»을 둔다. 끌기 층이 아니라 여기서 보는 이유는, 지우기와
|
|
363
|
+
* 끌기가 같은 자리에서 같은 결과를 내기 때문이다 — 벌이 몇 개 서 있는지는 이 화면이
|
|
364
|
+
* 이미 세고 있다.
|
|
365
|
+
*/
|
|
366
|
+
const [emptied, setEmptied] = useState(false);
|
|
367
|
+
const frameKeys = frames.map((one) => one.key).join("\u0000");
|
|
368
|
+
const seenFrames = useRef(frameKeys);
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
const before = seenFrames.current.split("\u0000").filter(Boolean);
|
|
371
|
+
const after = frameKeys.split("\u0000").filter(Boolean);
|
|
372
|
+
seenFrames.current = frameKeys;
|
|
373
|
+
if (frameVanished(before, after)) setEmptied(true);
|
|
374
|
+
}, [frameKeys]);
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (!emptied) return;
|
|
377
|
+
const timer = window.setTimeout(() => setEmptied(false), 10_000);
|
|
378
|
+
return () => window.clearTimeout(timer);
|
|
379
|
+
}, [emptied]);
|
|
348
380
|
// 끼어들 자리는 끌기 층이 정하고, 빈칸은 판을 그리는 쪽이 흐름 안에 넣는다.
|
|
349
381
|
const frameInsert = useSyncExternalStore(
|
|
350
382
|
subscribeFrameInsert,
|
|
@@ -473,6 +505,34 @@ export function LeviosaCanvasWorkspace({
|
|
|
473
505
|
것은 무대가 아니라 그 위에 뜬 이 층이 그린다. */}
|
|
474
506
|
<FrameDragLayer store={store} containerRef={outerRef} />
|
|
475
507
|
<GifAnimator store={store} />
|
|
508
|
+
|
|
509
|
+
{/* 벌 하나가 사라졌을 때의 «되돌리기». 아래 띠 위에 앉힌다 — 그 자리는 배율과
|
|
510
|
+
삽입이 이미 쓰고 있어 눈이 가 있다. 열 초 뒤에 스스로 사라진다. */}
|
|
511
|
+
{emptied ? (
|
|
512
|
+
<div
|
|
513
|
+
style={{
|
|
514
|
+
position: "absolute",
|
|
515
|
+
left: "50%",
|
|
516
|
+
bottom: 64,
|
|
517
|
+
transform: "translateX(-50%)",
|
|
518
|
+
zIndex: 45,
|
|
519
|
+
boxShadow: "0 6px 20px rgba(0, 0, 0, 0.25)",
|
|
520
|
+
}}
|
|
521
|
+
className="flex items-center gap-2 rounded-dpe-md bg-dpe-ink-900 px-3 py-2 text-[12px] text-dpe-on-accent"
|
|
522
|
+
>
|
|
523
|
+
<span>한 벌이 비어 사라졌습니다.</span>
|
|
524
|
+
<button
|
|
525
|
+
type="button"
|
|
526
|
+
className="font-dpe-semibold underline underline-offset-2"
|
|
527
|
+
onClick={() => {
|
|
528
|
+
store.history.undo();
|
|
529
|
+
setEmptied(false);
|
|
530
|
+
}}
|
|
531
|
+
>
|
|
532
|
+
되돌리기
|
|
533
|
+
</button>
|
|
534
|
+
</div>
|
|
535
|
+
) : null}
|
|
476
536
|
{/* 활성 화면의 아래 끝을 잡아 끌어 길이를 바꾸는 손잡이. 우측 패널의 숫자와
|
|
477
537
|
같은 함수를 거친다(`section-height.ts`) — 배경 요소까지 같이 늘리고 서버
|
|
478
538
|
굽기 상한 안에 가둔다. */}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leviosa-ai/detail-page-editor",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"description": "Leviosa detail-page editor shell. Source-only: consumers compile it through Next transpilePackages. Every app coupling (sourcing server, brand store, cache keys, toast) arrives through the DetailPageHost interface, and every URL it fetches comes from configureDetailPageEditor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"woff2-encoder": "2.0.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@leviosa-ai/canvas": "0.
|
|
70
|
+
"@leviosa-ai/canvas": "0.8.1",
|
|
71
71
|
"@tanstack/react-query": "^5.100.3",
|
|
72
72
|
"konva": "^10.0.0",
|
|
73
73
|
"lucide-react": "^0.563.0 || ^1.0.0",
|