- [단위 ▾] 단추 → 자주 쓰는 기호 판(넓이·부피·길이 · 무게·부피(액체) · 개수 · 속도·각도·비율 · 연산·비교로 무리 지음) · 최근 쓴 것 앞(localStorage, 최대 8개) - 누르면 편집 중이면 커서 자리에 끼워 넣고(D 편집기 textarea 캐럿 위치) · 아니면 고른 칸 글 끝에 붙임(ctx.dispatch 라 되돌리기 짝 그대로) - 체크박스로 「숫자 형식으로 붙이기」 고르면 값은 그대로 두고 형식만 0"㎡" 처럼 단위를 붙임(기존 형식 끝 따옴표 단위는 새 것으로 교체) - e_harness.ts 에 편집 중 커서 갈래를 시험할 숨은 textarea + __setEditing 시험 전용 걸이 더함 - ORCA 실 엔진 확인 — 칸 글 끝 붙이기(터파기 계산서 → …㎡) · 형식 붙이기(12.5 값 그대로 · 형식 0"㎡") · 편집 중 커서 자리 끼우기(f|oo → fkgoo, 캐럿 이동) · 최근 목록 순서·중복 제거(개 본 개 식 → 식·개·본) 모두 확인 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeeQRLknEpP4SFjVkjDWDs
180 lines
6.0 KiB
TypeScript
180 lines
6.0 KiB
TypeScript
/* =============================================================================
|
|
* e_harness.ts — E(도구 모음 · 탭 · 메뉴 · 클립보드) 화면 시험 틀.
|
|
* A(주소 · 명령 · 엔진 · 참조 옮김) · B(숫자 형식) 가 이제 실제로 있어 진짜 모듈을 그대로 씀
|
|
* (`vite.harness.config.ts` 는 `@ui` 별칭만 · 가짜 없음). dispatch 는 진짜 `applyCommand` →
|
|
* 진짜 `createCalcEngine` → 화면 갱신 순서로 실제 앱과 같은 길을 탐.
|
|
* ========================================================================== */
|
|
|
|
import { mountToolbar } from "../../../A00_Common/spreadsheet/spreadsheet_toolbar";
|
|
import { mountTabs } from "../../../A00_Common/spreadsheet/spreadsheet_tabs";
|
|
import { attachMenu } from "../../../A00_Common/spreadsheet/spreadsheet_menu";
|
|
import {
|
|
attachClipboard,
|
|
parseClipboard,
|
|
} from "../../../A00_Common/spreadsheet/spreadsheet_clipboard";
|
|
import { applyCommand, emptyWorkbook } from "../../../A00_Common/spreadsheet/spreadsheet_commands";
|
|
import { createCalcEngine } from "../../../A00_Common/spreadsheet/spreadsheet_graph";
|
|
import { createHistory } from "../../../A00_Common/spreadsheet/spreadsheet_history";
|
|
import type { Workbook } from "../../../A00_Common/spreadsheet/spreadsheet_types";
|
|
import type {
|
|
GridHandle,
|
|
Selection,
|
|
SpreadsheetContext,
|
|
} from "../../../A00_Common/spreadsheet/spreadsheet_view_types";
|
|
|
|
const log: string[] = [];
|
|
const logEl = document.querySelector<HTMLPreElement>("#log")!;
|
|
function say(msg: string): void {
|
|
log.push(msg);
|
|
logEl.textContent = log.join("\n");
|
|
}
|
|
|
|
const book: Workbook = emptyWorkbook("test", "측구수로400");
|
|
book.서식 = [{}, { 굵게: true }];
|
|
book.시트[0].칸 = {
|
|
A1: { 값: "터파기 계산서", 서식: 1 },
|
|
B1: { 값: 12.5 },
|
|
C1: { 식: "B1*2" },
|
|
};
|
|
// 탭 이름 바꾸기가 다른 시트 참조 글도 고치는지 보는 둘째 시트.
|
|
book.시트.push({
|
|
id: "s2",
|
|
이름: "둘째",
|
|
칸: { A1: { 식: "측구수로400!B1+1" } },
|
|
});
|
|
|
|
const engine = createCalcEngine(book);
|
|
const history = createHistory();
|
|
|
|
const selection: Selection = {
|
|
시트: "s1",
|
|
범위: [{ r0: 0, c0: 0, r1: 0, c1: 0 }],
|
|
활성: { r: 0, c: 0 },
|
|
기준: { r: 0, c: 0 },
|
|
};
|
|
|
|
const root = document.querySelector<HTMLDivElement>("#ss-root")!;
|
|
|
|
const grid: GridHandle = {
|
|
root,
|
|
render() {},
|
|
invalidate() {},
|
|
renderSelection() {},
|
|
setRefHighlights() {},
|
|
cellBox: () => ({ x: 0, y: 0, w: 60, h: 20 }),
|
|
hitTest: (clientX) => {
|
|
const box = root.getBoundingClientRect();
|
|
const x = clientX - box.left;
|
|
if (x < 30) return { kind: "rowHead", r: 0, c: 0 };
|
|
if (x < 60) return { kind: "colHead", r: 0, c: 0 };
|
|
return { kind: "cell", r: selection.활성.r, c: selection.활성.c };
|
|
},
|
|
reveal() {},
|
|
visibleRange: () => ({ r0: 0, c0: 0, r1: 10, c1: 10 }),
|
|
editorSlot: () => ({ layer: root, box: { x: 0, y: 0, w: 60, h: 20 } }),
|
|
destroy() {},
|
|
};
|
|
|
|
function applyAndRefresh(
|
|
command: import("../../../A00_Common/spreadsheet/spreadsheet_types").Command,
|
|
): void {
|
|
const effect = applyCommand(book, command);
|
|
if (effect.rebuild) engine.rebuild(book);
|
|
else engine.update(effect.cells);
|
|
toolbar.refresh();
|
|
tabs.refresh();
|
|
}
|
|
|
|
const ctx: SpreadsheetContext = {
|
|
root,
|
|
book,
|
|
readOnly: false,
|
|
engine,
|
|
history,
|
|
selection,
|
|
sheet: () => book.시트.find((s) => s.id === selection.시트)!,
|
|
dispatch(command) {
|
|
say(`dispatch: ${JSON.stringify(command)}`);
|
|
const effect = applyCommand(book, command);
|
|
history.push(command, effect.undo);
|
|
if (effect.rebuild) engine.rebuild(book);
|
|
else engine.update(effect.cells);
|
|
toolbar.refresh();
|
|
tabs.refresh();
|
|
},
|
|
undo() {
|
|
const cmd = history.undo();
|
|
say(`undo: ${cmd ? JSON.stringify(cmd) : "(없음)"}`);
|
|
if (cmd) applyAndRefresh(cmd);
|
|
},
|
|
redo() {
|
|
const cmd = history.redo();
|
|
say(`redo: ${cmd ? JSON.stringify(cmd) : "(없음)"}`);
|
|
if (cmd) applyAndRefresh(cmd);
|
|
},
|
|
select(sel) {
|
|
Object.assign(selection, sel);
|
|
say(`select: ${JSON.stringify(sel)}`);
|
|
toolbar.refresh();
|
|
},
|
|
showSheet(sheetId) {
|
|
book.활성 = sheetId;
|
|
selection.시트 = sheetId;
|
|
selection.범위 = [{ r0: 0, c0: 0, r1: 0, c1: 0 }];
|
|
selection.활성 = { r: 0, c: 0 };
|
|
say(`showSheet: ${sheetId}`);
|
|
tabs.refresh();
|
|
toolbar.refresh();
|
|
},
|
|
grid,
|
|
editor: {
|
|
editing: () => editingFlag,
|
|
begin() {},
|
|
commit() {},
|
|
cancel() {},
|
|
sync() {},
|
|
},
|
|
};
|
|
|
|
// 단위 기호 [편집 중이면 커서 자리에] 갈래 시험용 — 진짜 D 편집기 대신 숨은 textarea 하나로 흉내.
|
|
let editingFlag = false;
|
|
const fakeEditorTa = document.createElement("textarea");
|
|
fakeEditorTa.id = "fake-editor-ta";
|
|
root.append(fakeEditorTa);
|
|
(window as unknown as { __setEditing: (on: boolean, value?: string) => void }).__setEditing = (
|
|
on,
|
|
value,
|
|
) => {
|
|
editingFlag = on;
|
|
if (on) {
|
|
if (value !== undefined) fakeEditorTa.value = value;
|
|
fakeEditorTa.focus();
|
|
} else {
|
|
fakeEditorTa.blur();
|
|
}
|
|
};
|
|
|
|
const toolbar = mountToolbar(ctx);
|
|
const tabs = mountTabs(ctx);
|
|
attachMenu(ctx);
|
|
attachClipboard(ctx);
|
|
|
|
document.querySelector("#toolbar-slot")!.append(toolbar.root!);
|
|
document.querySelector("#tabs-slot")!.append(tabs.root!);
|
|
|
|
say("mounted — 도구 모음 · 탭 · 메뉴 · 클립보드 붙음(진짜 A · B 엔진)");
|
|
|
|
(window as unknown as { __ctx: unknown; __book: unknown }).__ctx = ctx;
|
|
(window as unknown as { __ctx: unknown; __book: unknown }).__book = book;
|
|
|
|
// 클립보드 밖 → 안 시험 — 자리 표시자에 실제로 붙여넣기(Ctrl+V)를 받아 결과를 보임.
|
|
const pasteZone = document.querySelector<HTMLDivElement>("#paste-zone")!;
|
|
const pasteOut = document.querySelector<HTMLPreElement>("#paste-out")!;
|
|
pasteZone.addEventListener("paste", (ev) => {
|
|
ev.preventDefault();
|
|
const html = ev.clipboardData?.getData("text/html") ?? "";
|
|
const text = ev.clipboardData?.getData("text/plain") ?? "";
|
|
const block = parseClipboard(html, text, { r: 0, c: 0 });
|
|
pasteOut.textContent = `html:\n${html}\n\ntext:\n${text}\n\nblock:\n${JSON.stringify(block, null, 2)}`;
|
|
});
|