- Hopyo_Sheet.ts: [수정] = 시트 아래 줄 판(createJudgeRows · 그 호표 자리 뜀) · 오른쪽 판 아래 [옮기기](createJudgeMove) · 고름 캐시 한 벌 · 옮긴 뒤 목록 · 칠 · 단가산출 목록 다시 읽음 - Hopyo_SheetSk.ts: 단가산출 목록 = 새 블록 판정(hopyo-judge 목록 · 판정 · 고를 것 n · 표지 · 부름 · 만든 SK · 캐시 확인) · 줄 누름 = 그 자리 + 블록 판(createJudgeBlock) - Hopyo_JudgeApi.ts: 목록 블록 줄 판정 · 고를수 · Block.ts 타입 흠 둘 - 낡은 시험 고침(까닭 = 785-6 이 옛 마법사 단가산출 칸 · 확인 창을 새 판으로 갈음): test_627_sheet_sk(판 바꿈 · 강조 · 뛰기를 새 목록으로 · helper_778_5 sheetSk 대역 새 꼴) · test_785_4 줄 판 자리 손잡이 · test_m02_hopyo_screen [수정] = 줄 판 - test_642_6: 엑셀 보기 이음 · 가운데 페이지 없음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
478 lines
19 KiB
TypeScript
478 lines
19 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Hopyo_Sheet.ts
|
|
* 일위대가 실무자료 엑셀 보기 몸(PLAN 329 · 338-3 · 359-1 · 359-2 · 785-4) — 본 칸 불러오기 보기(`Hopyo_Import`)가 바로 담음 ·
|
|
* 머리 막대 = 시트 · 호표 수 · 검산 · 제안 n / 줄 m · 숫자 판정 셈 중 + 부르는 쪽 덧붙임(`headExtra` — 층 · 안 이은 줄 · [끝내기]) ·
|
|
* 왼쪽 = 올린 엑셀을 연 것처럼 읽기 전용 시트(A00 산출근거 스프레드시트 · 탭 셋 일위대가표 · 단가산출표 · 단가산출 상세 ·
|
|
* 칠 · 범례 · 뛰기 · 뒤로 = `Hopyo_SheetNav` · 779-4 · 같은 파일 몸은 다시 씀 = 그 자리로 뜀 · 779-6) ·
|
|
* 오른쪽 = 호표 목록(체크 · 제N호표 [수정] · 찾기 · 제안 n/m — 다 연결이면 녹색 · 뒤 셈 중이면 3 초마다 다시) + [저장] ·
|
|
* 단가산출표 · 상세 탭이면 단가산출 목록으로 바뀜(780-2 · `Hopyo_SheetSk`).
|
|
* · 목록 누름 → 시트에서 그 호표 영역(B열 ~ 끝 열) 고름 · 칸 누름 → 그 행 호표를 목록에서 강조 · 머리 행이면 영역 고름
|
|
* · [수정] = 시트 아래 줄 판(785-6 `Hopyo_Rows` · 줄 누름과 따로) · 단가산출 탭 블록 = 같은 자리 블록 판 ·
|
|
* 오른쪽 판 아래 [옮기기](확인한 줄 · 블록 · 고름 캐시 `createJudgeState` 한 벌)
|
|
* · [저장] · [모두 고정으로 들여오기] = `Hopyo_SheetMake`
|
|
* · 격자 = 서버 `/hopyo-grid/{키}` · 옛 파일(격자 없음)은 시트 자리에 까닭 글 · 목록 · 저장은 됨
|
|
* · `buildHopyoSheet` = 몸(머리 · 띠 · 시트 + 목록) · `openHopyoSheet` = 모달 껍데기([로직 추가] 「실무 호표」 길 — 785-7 이 지움)
|
|
* ⚠ 부르는 쪽은 `import()` 로 엶 · 스프레드시트도 열 때 불러옴(다른 화면 번들에 안 섞이게).
|
|
* ========================================================================== */
|
|
|
|
import { createButton, el } from "@ui/ui_template_elements";
|
|
import { createCheckbox } from "@ui/ui_template_checkbox";
|
|
import { createInputField } from "@ui/ui_template_input";
|
|
import { openModal } from "@ui/ui_template_modal";
|
|
import type { Sheet } from "../A00_Common/spreadsheet/spreadsheet_types";
|
|
import type { SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet";
|
|
import {
|
|
readHopyo,
|
|
type HopyoBrief,
|
|
type HopyoBuilt,
|
|
type HopyoGrid,
|
|
type HopyoSpan,
|
|
type HopyoView,
|
|
type HopyoScope,
|
|
} from "./M02_MasterTemplete_Hopyo_Api";
|
|
import { POLL_MS } from "./M02_MasterTemplete_Hopyo_Check";
|
|
import {
|
|
createJudgeMove,
|
|
createJudgeRows,
|
|
createJudgeState,
|
|
} from "./M02_MasterTemplete_Hopyo_Rows";
|
|
import * as Nav from "./M02_MasterTemplete_Hopyo_SheetNav";
|
|
import { createSheetSk, type SheetSk } from "./M02_MasterTemplete_Hopyo_SheetSk";
|
|
import { note, why, wireMake } from "./M02_MasterTemplete_Hopyo_SheetMake";
|
|
import "./M02_MasterTemplete_Hopyo.css";
|
|
|
|
export { buildOrder } from "./M02_MasterTemplete_Hopyo_SheetMake";
|
|
|
|
export interface HopyoSheetOptions {
|
|
/** 실무자료 파일 층 */
|
|
scope: HopyoScope;
|
|
key: string;
|
|
파일: string;
|
|
/** 만들 층 쓰기 권한 — 없으면 체크 · 만들기 없음(보기만) */
|
|
canWrite: boolean;
|
|
/** 사전조합을 만들 층(없으면 `scope` · [로직 추가] = 문서 층) */
|
|
target?: HopyoScope;
|
|
/** 처음 고를 호표 번(확인 창 머리 [엑셀 보기]) */
|
|
호표?: number;
|
|
/** 처음 보일 단가산출 행(330-3 근거 [엑셀 보기]) */
|
|
단산행?: number | [number, number];
|
|
/** 호표 하나를 만들었음 — [로직 추가] 참조 줄 */
|
|
onImport?: (got: HopyoBuilt, hopyo: HopyoBrief) => void;
|
|
/** 창을 닫았음(좌측 파일 줄 고름 표시) */
|
|
onClose?: () => void;
|
|
/** 머리 막대 끝에 덧붙일 것(본 칸 불러오기 보기 — 층 · 안 이은 줄 · [끝내기]) */
|
|
headExtra?: HTMLElement[];
|
|
/** 단가산출 판정 · 옮김 뒤 */
|
|
onSkChange?: () => void;
|
|
/** 엑셀 결과를 읽었음 · 다시 읽었음(뒤 셈 중 3 초 다시 읽기 포함) — 호표 번 목록을 바로 넘김 */
|
|
onRead?: (file: HopyoView) => void;
|
|
}
|
|
|
|
/** 몸 — 머리 막대 · 어긋남 글 · 시트 + 목록(세로로 쌓는 자리에 이어 붙임) */
|
|
export interface HopyoSheetBody {
|
|
parts: HTMLElement[];
|
|
/** 몸을 걷음(마지막 창 손잡이 · 격자 · 스프레드시트) */
|
|
destroy: () => void;
|
|
/** 모달에서 앞으로 꺼낸 몸을 제자리로 — 했으면 참(닫기 대신) */
|
|
lower: () => boolean;
|
|
/** 안 옮긴 단가산출 블록 수(판정 전 null) */
|
|
skCount: () => { 블록: number; 밖: number; 줄: number } | null;
|
|
/** 그 단가산출 블록으로 뜀 + 줄 판 엶 */
|
|
showSk: (번: number) => void;
|
|
}
|
|
|
|
const ILWI = "일위대가";
|
|
const DANSAN = ["단가산출", "단가산출근거"]; // 형식별 블록 시트(EST · STmate · 역할 id 없는 옛 격자)
|
|
/** 열린 창 손잡이(779-6) — 파일마다 · 다시 부르면 그 창이 앞으로 와 그 자리로 뜀 */
|
|
const opened = new Map<string, (o: HopyoSheetOptions) => void>();
|
|
const sheetId = (o: HopyoSheetOptions): string =>
|
|
`${o.scope.layer}:${o.scope.owner ?? ""}:${o.key}`;
|
|
|
|
/** 목록 숫자 「연결된 줄 / 연결할 줄」(358-3 두 칸 · 없으면 옛 맞춤 [제안, 줄]) — 다 연결(같고 0 넘음)이면 참 ·
|
|
* 0/0 은 색 없음 */
|
|
export function allLinked(h: {
|
|
맞춤: [number, number];
|
|
연결할?: number;
|
|
연결된?: number;
|
|
}): boolean {
|
|
const [done, all] = h.연결할 === undefined ? h.맞춤 : [h.연결된 ?? 0, h.연결할];
|
|
return all > 0 && done === all;
|
|
}
|
|
|
|
/** 목록 숫자 글 — 358-3 두 칸이 있으면 그것 */
|
|
const linkText = (h: { 맞춤: [number, number]; 연결할?: number; 연결된?: number }): string =>
|
|
h.연결할 === undefined ? `${h.맞춤[0]}/${h.맞춤[1]}` : `${h.연결된 ?? 0}/${h.연결할}`;
|
|
|
|
/** 엑셀 행(1부터)이 든 호표 번 · 없으면 null */
|
|
export const hopyoAtRow = (list: HopyoSpan[], row: number): number | null =>
|
|
list.find((h) => h.행[0] <= row && row <= h.행[1])?.번 ?? null;
|
|
|
|
/** 호표 영역 A1 — B열 ~ 시트 끝 열(북부청 = `B20:O24`) */
|
|
export const spanText = (행: [number, number], last: string): string => `B${행[0]}:${last}${행[1]}`;
|
|
|
|
/** 시트 끝 열 글자 — 칸 · 열 정보 중 가장 오른쪽 */
|
|
export function lastColumn(sheet: Pick<Sheet, "칸" | "열">): string {
|
|
let best = "A";
|
|
for (const k of [...Object.keys(sheet.칸), ...Object.keys(sheet.열 ?? {})]) {
|
|
const c = /^[A-Z]+/.exec(k)?.[0];
|
|
if (c && (c.length > best.length || (c.length === best.length && c > best))) best = c;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/** 단가산출 행 → B ~ F 열 범위 A1(`B4:F9`) */
|
|
export const rowsText = (행: number | [number, number]): string =>
|
|
typeof 행 === "number" ? `B${행}:F${행}` : `B${행[0]}:F${행[1]}`;
|
|
|
|
/** 모달 껍데기 — 같은 파일 몸이 이미 있으면(본 칸 · 모달) 그 몸을 그 자리로 뜀 */
|
|
export function openHopyoSheet(opt: HopyoSheetOptions): void {
|
|
const id = sheetId(opt);
|
|
if (opened.has(id)) return opened.get(id)!(opt);
|
|
const view = buildHopyoSheet(opt);
|
|
openModal({
|
|
title: `엑셀 보기 — ${opt.파일}`,
|
|
closeLabel: "닫기",
|
|
wide: true,
|
|
dialogClass: "m02-modal m02-hopyo-sheet__dialog",
|
|
beforeClose: async () => {
|
|
if (view.lower()) return false; // 꺼낸 창 = 닫기 대신 제자리로
|
|
view.destroy();
|
|
return true;
|
|
},
|
|
mount: (body) => body.append(...view.parts),
|
|
});
|
|
}
|
|
|
|
export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
|
const id = sheetId(opt);
|
|
let sheet: SpreadsheetHandle | null = null;
|
|
let nav: Nav.SheetNav | null = null;
|
|
let sk: SheetSk | null = null; // 단가산출 탭 오른쪽 판(780-2)
|
|
let detail = ""; // 단가산출 상세 시트 id
|
|
let above: Element[] = []; // 앞으로 꺼냈을 때 원래 위에 있던 창
|
|
let closed = false;
|
|
let briefs: HopyoBrief[] = [];
|
|
let spans: HopyoSpan[] = [];
|
|
let ilwi = ""; // 일위대가 시트 id
|
|
let last = "O";
|
|
let at = { 시트: "", 칸: "", 범위: "" }; // 시트 고름
|
|
let current: number | null = opt.호표 ?? null;
|
|
const checked = new Set<number>();
|
|
const items = new Map<number, HTMLElement>();
|
|
const target = opt.target ?? opt.scope;
|
|
const state = createJudgeState(opt.scope, opt.key); // 판정 고름 캐시(785-6)
|
|
let panel: { destroy: () => void } | null = null; // 시트 아래 줄 판 몸
|
|
|
|
const head = el("div", {
|
|
className: "ui-toolbar m02-hopyo__bar",
|
|
children: [note("엑셀을 읽는 중…")],
|
|
});
|
|
const warn = el("p", {
|
|
className: "ui-text ui-text--warning m02-hopyo__warn",
|
|
attrs: { "data-text": "14" },
|
|
});
|
|
warn.hidden = true;
|
|
const left = el("div", {
|
|
className: "m02-hopyo-sheet__grid",
|
|
children: [note("엑셀을 읽는 중…")],
|
|
});
|
|
const lines = el("div", {
|
|
className: "m02-hopyo-sheet__lines",
|
|
attrs: { "data-hopyo-lines": "" },
|
|
}); // 시트 아래 줄 판 자리
|
|
lines.hidden = true;
|
|
const closeLines = (): void => {
|
|
panel?.destroy();
|
|
panel = null;
|
|
lines.hidden = true;
|
|
};
|
|
const openLines = (make: (host: HTMLElement) => { destroy: () => void }): void => {
|
|
panel?.destroy();
|
|
lines.hidden = false;
|
|
panel = make(lines);
|
|
};
|
|
const col = el("div", { className: "m02-hopyo-sheet__col", children: [left, lines] });
|
|
const searchField = createInputField({
|
|
type: "search",
|
|
placeholder: "호표 · 품명 · 규격 찾기",
|
|
compact: true,
|
|
onInput: () => paintList(),
|
|
});
|
|
const search = searchField.input;
|
|
const list = el("div", { className: "m02-hopyo-sheet__list" });
|
|
const count = note("");
|
|
const go = createButton({ label: "저장", variant: "filled", size: "sm" });
|
|
go.disabled = true;
|
|
go.setAttribute("data-hopyo-save", "");
|
|
const fixAll = createButton({ label: "모두 고정으로 들여오기", size: "sm" });
|
|
fixAll.setAttribute("data-hopyo-all-fixed", "");
|
|
fixAll.disabled = true; // 엑셀을 다 읽은 뒤 켬
|
|
const result = el("div", { className: "m02-hopyo-sheet__result" });
|
|
const move = createJudgeMove({
|
|
file: opt.scope,
|
|
key: opt.key,
|
|
target,
|
|
state,
|
|
readOnly: !opt.canWrite,
|
|
onMoved: () => void refresh(),
|
|
});
|
|
const right = el("div", {
|
|
className: "m02-hopyo-sheet__side",
|
|
children: [
|
|
searchField.root,
|
|
el("div", {
|
|
className: "ui-toolbar m02-hopyo__bar",
|
|
children: [count, ...(opt.canWrite ? [fixAll, go] : [])],
|
|
}),
|
|
list,
|
|
result,
|
|
move.root,
|
|
],
|
|
});
|
|
|
|
const finish = (): void => {
|
|
if (closed) return;
|
|
closed = true;
|
|
opened.delete(id);
|
|
closeLines();
|
|
move.destroy();
|
|
sk?.destroy();
|
|
nav?.destroy();
|
|
sheet?.destroy();
|
|
opt.onClose?.();
|
|
};
|
|
|
|
const paintGo = (): void => {
|
|
go.querySelector(".ui-btn__label")!.textContent = checked.size
|
|
? `저장 (${checked.size})`
|
|
: "저장";
|
|
go.disabled = !checked.size;
|
|
};
|
|
|
|
/** 머리 막대 — 시트 · 호표 수 · 검산 · 제안 · 셈 중 + 덧붙임 */
|
|
function paintHead(file: HopyoView): void {
|
|
const wrong = file.검산.어긋남;
|
|
const c = file.셈 && !file.셈.끝 ? file.셈 : null;
|
|
head.replaceChildren(
|
|
note(`시트 ${file.시트}`),
|
|
note(`호표 ${file.호표수}`),
|
|
note(wrong.length ? `검산 어긋남 ${wrong.length}` : "검산 맞음"),
|
|
note(`제안 ${file.맞춤[0]} / 줄 ${file.맞춤[1]}`),
|
|
...(c
|
|
? [note(`숫자 판정 셈 중 ${c.한}/${c.모두 ?? "?"}${c.까닭 ? ` · ${c.까닭}` : ""}`)]
|
|
: []),
|
|
...(opt.headExtra ?? []),
|
|
);
|
|
opt.onRead?.(file);
|
|
warn.textContent = wrong.join("\n");
|
|
warn.hidden = !wrong.length;
|
|
}
|
|
|
|
/** 목록 강조 · 보이게(시트 고름은 그대로) */
|
|
function mark(no: number | null): void {
|
|
items.get(current ?? NaN)?.classList.remove("is-current");
|
|
current = no;
|
|
const row = items.get(no ?? NaN);
|
|
row?.classList.add("is-current");
|
|
row?.scrollIntoView({ block: "nearest" });
|
|
}
|
|
|
|
/** 호표 하나 강조 — 목록 · 시트 영역(같은 범위면 다시 안 부름) */
|
|
function focus(no: number): void {
|
|
mark(no);
|
|
const span = spans.find((h) => h.번 === no)?.행 ?? briefs.find((h) => h.번 === no)?.행;
|
|
if (!sheet || !span) return;
|
|
const text = spanText(span, last);
|
|
if (at.시트 !== ilwi || at.범위 !== text) sheet.select(text, ilwi);
|
|
}
|
|
opened.set(id, (o) => {
|
|
above = Nav.raise(left); // 모달이 아니면 [] — 그 자리로만 뜀
|
|
if (o.단산행 !== undefined && detail) nav?.jumpTo(detail, o.단산행);
|
|
else if (o.호표 !== undefined) focus(o.호표);
|
|
});
|
|
|
|
/** [수정] — 그 호표 자리 + 시트 아래 줄 판(줄 누름과 따로) */
|
|
const editButton = (h: HopyoBrief): HTMLElement => {
|
|
const b = createButton({
|
|
label: "수정",
|
|
variant: "ghost",
|
|
size: "sm",
|
|
onClick: (e) => {
|
|
e.stopPropagation();
|
|
focus(h.번);
|
|
openLines((host) =>
|
|
createJudgeRows({
|
|
host,
|
|
file: opt.scope,
|
|
key: opt.key,
|
|
target,
|
|
state,
|
|
no: h.번,
|
|
readOnly: !opt.canWrite,
|
|
onClose: closeLines,
|
|
onSheet: (r, role) => nav?.jumpTo(role === "일위대가표" ? ilwi : detail, r),
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
b.setAttribute("data-hopyo-edit", String(h.번));
|
|
return b;
|
|
};
|
|
|
|
function paintList(): void {
|
|
const q = search.value.trim().toLowerCase();
|
|
items.clear();
|
|
const shown = briefs.filter(
|
|
(h) => !q || `제${h.번}호표 ${h.이름} ${h.규격}`.toLowerCase().includes(q),
|
|
);
|
|
list.replaceChildren(
|
|
...shown.map((h) => {
|
|
const box = createCheckbox("", {
|
|
checked: checked.has(h.번),
|
|
onChange: (on) => {
|
|
if (on) checked.add(h.번);
|
|
else checked.delete(h.번);
|
|
paintGo();
|
|
},
|
|
});
|
|
box.root.hidden = !opt.canWrite;
|
|
box.root.addEventListener("click", (e) => e.stopPropagation());
|
|
const linked = note(linkText(h));
|
|
linked.classList.toggle("is-linked", allLinked(h));
|
|
const row = el("div", {
|
|
className: `m02-hopyo-sheet__item${h.번 === current ? " is-current" : ""}`,
|
|
attrs: { "data-hopyo-no": String(h.번), title: `${h.이름} ${h.규격}`.trim() },
|
|
children: [
|
|
box.root,
|
|
el("span", { className: "m02-hopyo-sheet__no", text: `제${h.번}호표` }),
|
|
editButton(h),
|
|
el("span", { className: "m02-hopyo-sheet__name", text: `${h.이름} ${h.규격}`.trim() }),
|
|
linked,
|
|
],
|
|
});
|
|
row.addEventListener("click", () => focus(h.번));
|
|
items.set(h.번, row);
|
|
return row;
|
|
}),
|
|
);
|
|
count.textContent = q ? `${shown.length} / ${briefs.length}` : `호표 ${briefs.length}`;
|
|
}
|
|
|
|
async function mountSheet(grid: HopyoGrid): Promise<void> {
|
|
const { createSpreadsheet } = await import("../A00_Common/spreadsheet/spreadsheet");
|
|
const { fillMissingWidths } = await import("../A00_Common/spreadsheet/spreadsheet_autofit");
|
|
if (closed) return;
|
|
const book = structuredClone(grid.책);
|
|
fillMissingWidths(book); // 엑셀 폭이 없는 열 = 글 길이로 처음 폭
|
|
const ids = Nav.nameTabs(book, grid); // 탭 글 통일(779-4)
|
|
const first = book.시트.find((s) => s.id === ids.일위대가표 || s.이름 === ILWI) ?? book.시트[0];
|
|
if (!first) return void left.replaceChildren(note("시트 없음"));
|
|
ilwi = first.id;
|
|
detail = ids.단가산출상세 ?? "";
|
|
last = lastColumn(first);
|
|
book.활성 = first.id;
|
|
left.replaceChildren();
|
|
const lastOf = (sid: string): string =>
|
|
lastColumn(book.시트.find((x) => x.id === sid) ?? first);
|
|
const more = { ...opt, target, grid, last: lastOf, sheet: () => sheet, host: left };
|
|
nav = ids.일위대가표 ? Nav.createSheetNav(more) : null;
|
|
if (nav) warn.after(nav.root);
|
|
sk = nav ? createSheetSk({ ...opt, target, nav, state, side: right, openLines, closeLines, ...(opt.onSkChange ? { onChange: opt.onSkChange } : {}) }) : null; // prettier-ignore
|
|
sheet = createSpreadsheet(left, book, {
|
|
readOnly: true,
|
|
칠: nav?.fill,
|
|
onSelect: (s) => {
|
|
at = s;
|
|
nav?.at(s);
|
|
sk?.at(s);
|
|
if (s.시트 !== ilwi) return;
|
|
const no = hopyoAtRow(spans, Number(/\d+/.exec(s.칸)?.[0]));
|
|
if (no !== null && no !== current) mark(no);
|
|
},
|
|
});
|
|
nav?.marks(briefs);
|
|
// 머리 행 칸을 눌러 뗌 → 그 호표 영역 고름(끌기 · 방향키 도중엔 안 바꿈 · 줄 행은 그 줄 그대로)
|
|
left.querySelector(".ss-grid-host")?.addEventListener("mouseup", (e) => {
|
|
if ((e as MouseEvent).button !== 0 || at.시트 !== ilwi) return;
|
|
const row = Number(/\d+/.exec(at.칸)?.[0]);
|
|
const no = hopyoAtRow(spans, row);
|
|
if (no !== null && spans.find((h) => h.번 === no)?.행[0] === row) focus(no);
|
|
});
|
|
}
|
|
|
|
/** 뒤 숫자 판정 셈 중이면 3 초 뒤 목록 「제안 n/m」 · 머리 다시 읽음 — 셈 끝 · 창 닫음 · 못 읽음이면 멈춤 */
|
|
function poll(file: HopyoView): void {
|
|
if (!file.셈 || file.셈.끝) return;
|
|
window.setTimeout(async () => {
|
|
const next = closed ? null : await readHopyo(opt.scope, opt.key).catch(() => null);
|
|
if (!next || closed) return;
|
|
briefs = next.호표;
|
|
paintHead(next);
|
|
paintList();
|
|
nav?.marks(briefs);
|
|
poll(next);
|
|
}, POLL_MS);
|
|
}
|
|
|
|
/** 옮긴 뒤 — 목록 · 칠 · 단가산출 목록 다시 읽음 */
|
|
async function refresh(): Promise<void> {
|
|
const next = await readHopyo(opt.scope, opt.key).catch(() => null);
|
|
if (!next || closed) return;
|
|
briefs = next.호표;
|
|
paintHead(next);
|
|
paintList();
|
|
nav?.marks(briefs);
|
|
sk?.reload();
|
|
opt.onSkChange?.();
|
|
}
|
|
|
|
async function load(): Promise<void> {
|
|
try {
|
|
const [file, grid] = await Promise.all([
|
|
readHopyo(opt.scope, opt.key),
|
|
Nav.gridOf(id, opt.scope, opt.key).catch((error: unknown) => new Error(why(error))),
|
|
]);
|
|
if (closed) return;
|
|
briefs = file.호표;
|
|
fixAll.disabled = !briefs.length;
|
|
spans = grid instanceof Error ? [] : grid.호표;
|
|
paintHead(file);
|
|
paintList();
|
|
poll(file);
|
|
if (grid instanceof Error) return void left.replaceChildren(note(grid.message));
|
|
await mountSheet(grid);
|
|
const rows = opt.단산행 === undefined ? null : rowsText(opt.단산행);
|
|
if (rows) [detail, ...DANSAN].some((name) => name && sheet?.select(rows, name));
|
|
else if (current !== null) focus(current);
|
|
} catch (error) {
|
|
if (!closed) {
|
|
head.replaceChildren(note(`못 읽음 — ${why(error)}`));
|
|
left.replaceChildren(note(`못 읽음 — ${why(error)}`));
|
|
}
|
|
}
|
|
}
|
|
|
|
wireMake({
|
|
scope: opt.scope,
|
|
key: opt.key,
|
|
파일: opt.파일,
|
|
target,
|
|
...(opt.onImport ? { onImport: opt.onImport } : {}),
|
|
checked,
|
|
briefs: () => briefs,
|
|
closed: () => closed,
|
|
go,
|
|
fixAll,
|
|
result,
|
|
paintGo,
|
|
});
|
|
void load();
|
|
return {
|
|
parts: [head, warn, el("div", { className: "m02-hopyo-sheet", children: [col, right] })],
|
|
destroy: finish,
|
|
lower: () => Nav.lower(left, above.splice(0)),
|
|
skCount: () => sk?.count() ?? null,
|
|
showSk: (번) => sk?.show(번),
|
|
};
|
|
}
|