- master 는 계산 칸도 = 식으로 고침 · 지우면 열 식으로 돌아감 · project 손 열도 = 식 받음 - 너비 손잡이 반쪽이 옆 칸에 가려 안 잡히던 것 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tjoit7rxvpLMM7cafeVTo1
116 lines
4.7 KiB
TypeScript
116 lines
4.7 KiB
TypeScript
/* =============================================================================
|
|
* ui_template_sheet_ops.ts
|
|
* 표 문서 고치기 — 줄 · 열 더하기 · 지우기 · 칸 값. 문서를 그 자리에서 바꿈(부른 쪽이 다시 그림).
|
|
* 화면(DOM)을 모름 — 격자(`ui_template_sheet.ts`)가 부름.
|
|
* ========================================================================== */
|
|
|
|
import type { SheetCell, SheetColumn, SheetDoc, SheetRow } from "./ui_template_sheet_types";
|
|
|
|
export type SheetMode = "master" | "project";
|
|
|
|
export const ROW_FIXED_ALL = "전구간";
|
|
|
|
/** 겹치지 않는 새 id — `c1` · `c2` … */
|
|
function freshId(prefix: string, used: Iterable<string>): string {
|
|
const taken = new Set(used);
|
|
let n = 1;
|
|
while (taken.has(`${prefix}${n}`)) n += 1;
|
|
return `${prefix}${n}`;
|
|
}
|
|
|
|
/** 보이는 줄 차례 — 「전구간」 줄이 맨 위 · 나머지는 문서 차례. */
|
|
export function orderedRows(doc: SheetDoc): SheetRow[] {
|
|
const fixed = doc.줄.filter((r) => r.고정 === ROW_FIXED_ALL);
|
|
return [...fixed, ...doc.줄.filter((r) => r.고정 !== ROW_FIXED_ALL)];
|
|
}
|
|
|
|
export const isCalcColumn = (col: SheetColumn): boolean => !!col.식;
|
|
export const isBoundColumn = (col: SheetColumn): boolean => !!col.바인딩;
|
|
|
|
/** 본문 칸을 고칠 수 있나 — master 는 전부(계산 칸은 `=` 식으로) · project 는 손 열만(+ 사용자가 더한 줄의 글 열). */
|
|
export function cellEditable(mode: SheetMode, col: SheetColumn, row: SheetRow): boolean {
|
|
if (mode === "master") return true;
|
|
if (isBoundColumn(col) || isCalcColumn(col)) return false;
|
|
return !!col.손 || (!!row.손 && col.꼴 === "글");
|
|
}
|
|
|
|
export function canDeleteRow(mode: SheetMode, row: SheetRow): boolean {
|
|
if (mode === "master") return true;
|
|
return !!row.손 && row.고정 !== ROW_FIXED_ALL;
|
|
}
|
|
|
|
/** `after` 줄 뒤에 빈 줄(없으면 맨 끝) — 새 줄 id. project 에서 더한 줄은 `손`. */
|
|
export function addRow(doc: SheetDoc, mode: SheetMode, after: string | null): string {
|
|
const id = freshId(
|
|
"r",
|
|
doc.줄.map((r) => r.id),
|
|
);
|
|
const row: SheetRow = { id, 값: {}, ...(mode === "project" ? { 손: true } : {}) };
|
|
const at = doc.줄.findIndex((r) => r.id === after);
|
|
doc.줄.splice(at < 0 ? doc.줄.length : at + 1, 0, row);
|
|
return id;
|
|
}
|
|
|
|
export function deleteRow(doc: SheetDoc, id: string): void {
|
|
doc.줄 = doc.줄.filter((r) => r.id !== id);
|
|
}
|
|
|
|
/** `after` 열 오른쪽에 같은 묶음의 새 열 — 윗머리는 그대로 · 맨 아래 글만 「새 열」. */
|
|
export function addColumn(doc: SheetDoc, after: string | null, label: string): string {
|
|
const id = freshId(
|
|
"c",
|
|
doc.열.map((c) => c.id),
|
|
);
|
|
const at = doc.열.findIndex((c) => c.id === after);
|
|
const base = at < 0 ? null : doc.열[at];
|
|
const head = base ? [...base.머리] : [label];
|
|
if (base) {
|
|
let last = head.length - 1;
|
|
while (last > 0 && head[last] === null) last -= 1;
|
|
head[last] = label;
|
|
}
|
|
const col: SheetColumn = { id, 머리: head, 단위: base?.단위 ?? null, 꼴: "수" };
|
|
doc.열.splice(at < 0 ? doc.열.length : at + 1, 0, col);
|
|
return id;
|
|
}
|
|
|
|
/** 열을 지움 — 그 열을 쓰던 식은 남아 오류 칸으로 보임(조용히 고치지 않음). */
|
|
export function deleteColumn(doc: SheetDoc, id: string): void {
|
|
doc.열 = doc.열.filter((c) => c.id !== id);
|
|
for (const row of doc.줄) {
|
|
delete row.값[id];
|
|
if (row.식) delete row.식[id];
|
|
}
|
|
if (doc.보기?.열너비) delete doc.보기.열너비[id];
|
|
}
|
|
|
|
/** 칸에 친 글 → 저장 값. `=` 로 시작하면 그 칸만의 식(`줄.식`) · 수 열은 수 꼴이면 수 · 빈 글은 칸을 비움. */
|
|
export function setCell(doc: SheetDoc, rowId: string, col: SheetColumn, text: string): void {
|
|
const row = doc.줄.find((r) => r.id === rowId);
|
|
if (!row) return;
|
|
const trimmed = text.trim();
|
|
if (row.식) {
|
|
delete row.식[col.id];
|
|
if (!Object.keys(row.식).length) delete row.식;
|
|
}
|
|
if (trimmed.startsWith("=") && trimmed.slice(1).trim() !== (col.식 ?? "")) {
|
|
delete row.값[col.id];
|
|
(row.식 ??= {})[col.id] = trimmed.slice(1).trim();
|
|
return;
|
|
}
|
|
if (trimmed === "" || trimmed.startsWith("=")) {
|
|
delete row.값[col.id];
|
|
return;
|
|
}
|
|
const plain = trimmed.replace(/,/g, "");
|
|
const value: SheetCell =
|
|
col.꼴 !== "글" && /^-?(\d+(\.\d*)?|\.\d+)$/.test(plain) ? Number(plain) : trimmed;
|
|
// ⚠ 긴 소수는 Number 로 바꾸면 끝자리가 틀어짐 — 보이는 글 그대로 둠(풀이가 십진으로 읽음)
|
|
row.값[col.id] = typeof value === "number" && String(value) !== plain ? plain : value;
|
|
}
|
|
|
|
export function setColumnWidth(doc: SheetDoc, id: string, width: number): void {
|
|
doc.보기 ??= {};
|
|
(doc.보기.열너비 ??= {})[id] = Math.round(width);
|
|
}
|