feat(534-1): 내장 엑셀 그림 겹판 · 쪽 나눔 점선 — 늦게 불러옴 · 행열 넣기 · 스크롤 따라감
- spreadsheet_figures: 시트 그림을 닻 범위에 얹음(figureSrc 없으면 이름 상자) · 쪽 나눔 행 아래 점선 · overlay 맨 아래 층 - spreadsheet: figureSrc 선택 · 그림 · 쪽 나눔 있는 문서일 때만 import() Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ import { createGrid } from "./spreadsheet_grid";
|
||||
import { createHistory } from "./spreadsheet_history";
|
||||
import { attachKeys } from "./spreadsheet_keys";
|
||||
import type { ConditionOptions } from "./spreadsheet_condition";
|
||||
import type { FigureSrc } from "./spreadsheet_figures";
|
||||
import type { LinkOptions, Links } from "./spreadsheet_links";
|
||||
import { linkCommand, type LinkInfo } from "./spreadsheet_link_set";
|
||||
import { attachMenu } from "./spreadsheet_menu";
|
||||
@@ -69,6 +70,8 @@ export interface SpreadsheetOptions {
|
||||
preview?: boolean;
|
||||
/** 칸 우클릭 「조건 만들기」 — 이것을 줄 때만 붙음(늦게 불러옴) · 안 주면 화면 그대로 */
|
||||
conditions?: ConditionOptions;
|
||||
/** 그림 열쇠 → 주소(요구 347-5 · 시트 `그림`) — 없으면 이름 적힌 빈 상자 */
|
||||
figureSrc?: FigureSrc;
|
||||
}
|
||||
|
||||
export interface SpreadsheetHandle {
|
||||
@@ -185,9 +188,21 @@ export function createSpreadsheet(
|
||||
});
|
||||
}
|
||||
|
||||
/** 그림 겹판 · 쪽 나눔 점선 — 문서에 그림 · 쪽 나눔이 있을 때만 불러옴(크기 예산) */
|
||||
let figuring = false;
|
||||
function loadFigures(): void {
|
||||
const any = ctx.book.시트.some((s) => s.그림?.length || s.인쇄?.쪽나눔?.length);
|
||||
if (figuring || !any) return;
|
||||
figuring = true;
|
||||
void import("./spreadsheet_figures").then((m) => {
|
||||
if (root.isConnected) others.push(m.attachFigures(ctx, opts.figureSrc));
|
||||
});
|
||||
}
|
||||
|
||||
grid.render();
|
||||
select(ctx.selection, false, true);
|
||||
loadLinks();
|
||||
loadFigures();
|
||||
if (opts.conditions && !readOnly)
|
||||
void import("./spreadsheet_condition").then((m) => {
|
||||
if (root.isConnected) others.push(m.attachCondition(ctx, opts.conditions!));
|
||||
@@ -292,6 +307,7 @@ export function createSpreadsheet(
|
||||
grid.render();
|
||||
select(ctx.selection, false, true);
|
||||
loadLinks();
|
||||
loadFigures();
|
||||
},
|
||||
recalc() {
|
||||
ctx.engine.rebuild(ctx.book);
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_figures.ts (주인 P · 534 sub6)
|
||||
* 그림 겹판 · 쪽 나눔 점선(요구 347-5) — 시트 `그림`(범위 닻)을 칸 위에 얹고 `인쇄.쪽나눔` 행 아래에 점선.
|
||||
* 격자 overlay 맨 아래 층(고름 테두리가 위) · 누름은 칸으로 통과 · 스크롤 · 확대 · 다시 그림마다 자리를 다시 셈.
|
||||
* 그림 주소 = 부른 쪽 `figureSrc(열쇠)` · 없으면 이름 적힌 빈 상자. 그림 옮기기 · 나눔 고치기 화면은 없음(1차).
|
||||
* `createSpreadsheet` 가 그림 · 쪽 나눔이 있는 문서일 때만 불러옴(크기 예산).
|
||||
* ========================================================================== */
|
||||
|
||||
import { parseRange } from "./spreadsheet_address";
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
export type FigureSrc = (열쇠: string) => string | undefined;
|
||||
|
||||
const css = (node: HTMLElement, style: Partial<CSSStyleDeclaration>) =>
|
||||
Object.assign(node.style, style);
|
||||
|
||||
export function attachFigures(ctx: SpreadsheetContext, figureSrc?: FigureSrc): PartHandle {
|
||||
const layer = document.createElement("div");
|
||||
layer.className = "ss-figures";
|
||||
css(layer, { position: "absolute", inset: "0", overflow: "hidden", pointerEvents: "none" });
|
||||
const imgs = new Map<string, HTMLImageElement>(); // 열쇠 → 그림(다시 그려도 다시 안 받음)
|
||||
|
||||
function draw(): void {
|
||||
const overlay = ctx.grid.root.querySelector<HTMLElement>(".aislo-grid__overlay");
|
||||
if (!overlay) return;
|
||||
if (overlay.firstChild !== layer) overlay.prepend(layer);
|
||||
// editorSlot 상자 = 격자 뿌리 기준 → overlay 기준(머리 칸만큼 밀림)
|
||||
const box = (r: number, c: number) => {
|
||||
const b = ctx.grid.editorSlot(r, c).box;
|
||||
return { ...b, x: b.x - overlay.offsetLeft, y: b.y - overlay.offsetTop };
|
||||
};
|
||||
const sheet = ctx.sheet();
|
||||
const parts: HTMLElement[] = [];
|
||||
for (const f of sheet.그림 ?? []) {
|
||||
const rg = parseRange(f.닻);
|
||||
if (!rg) continue;
|
||||
const a = box(rg.r0, rg.c0);
|
||||
const b = box(rg.r1, rg.c1);
|
||||
const frame = document.createElement("div");
|
||||
frame.className = "ss-figure";
|
||||
frame.dataset.key = f.열쇠;
|
||||
css(frame, {
|
||||
position: "absolute",
|
||||
left: `${a.x}px`,
|
||||
top: `${a.y}px`,
|
||||
width: `${Math.max(0, b.x + b.w - a.x)}px`,
|
||||
height: `${Math.max(0, b.y + b.h - a.y)}px`,
|
||||
boxSizing: "border-box",
|
||||
background: "#fff",
|
||||
});
|
||||
const src = figureSrc?.(f.열쇠);
|
||||
if (src) {
|
||||
let img = imgs.get(f.열쇠);
|
||||
if (!img || img.getAttribute("src") !== src) {
|
||||
img = document.createElement("img");
|
||||
img.src = src;
|
||||
img.alt = f.이름 ?? f.열쇠;
|
||||
css(img, { width: "100%", height: "100%", objectFit: "contain", display: "block" });
|
||||
imgs.set(f.열쇠, img);
|
||||
}
|
||||
frame.append(img);
|
||||
} else {
|
||||
css(frame, {
|
||||
border: "1px dashed #888",
|
||||
color: "#666",
|
||||
font: "12px sans-serif",
|
||||
padding: "4px",
|
||||
});
|
||||
frame.textContent = f.이름 ?? f.열쇠;
|
||||
}
|
||||
parts.push(frame);
|
||||
}
|
||||
const view = ctx.grid.visibleRange();
|
||||
for (const k of sheet.인쇄?.쪽나눔 ?? []) {
|
||||
const at = box(k - 1, view.c0);
|
||||
const line = document.createElement("div");
|
||||
line.className = "ss-pagebreak";
|
||||
line.dataset.row = String(k);
|
||||
css(line, {
|
||||
position: "absolute",
|
||||
left: "0",
|
||||
right: "0",
|
||||
top: `${at.y + at.h - 1}px`,
|
||||
borderTop: "2px dashed #3b6fd8",
|
||||
});
|
||||
parts.push(line);
|
||||
}
|
||||
layer.replaceChildren(...parts);
|
||||
}
|
||||
|
||||
const onScroll = () => draw();
|
||||
ctx.grid.root.addEventListener("scroll", onScroll, true);
|
||||
ctx.grid.root.addEventListener("aislo-grid-zoom", onScroll);
|
||||
draw();
|
||||
|
||||
return {
|
||||
root: null,
|
||||
refresh: draw,
|
||||
destroy() {
|
||||
ctx.grid.root.removeEventListener("scroll", onScroll, true);
|
||||
ctx.grid.root.removeEventListener("aislo-grid-zoom", onScroll);
|
||||
layer.remove();
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user