@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 { copyPageToFrame } from "@leviosa-ai/canvas/edit/commands";
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
- copyPageToFrame(store, current.pageId, current.drop.frameKey, current.drop.at);
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 { frameOf, groupFrames } from "@leviosa-ai/canvas/render/frames";
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 frameCount = groupFrames(store.pages).length;
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.0",
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.7.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",