feat(M02): 좌측 새로 · 본떠 만들기 · 지우기를 표 양식 · 도면 양식 컨테이너 안으로 · 본떠 만들기 흠 고침
- 맨 아래 단추 줄 없앰 · 표 양식 · 도면 양식 컨테이너마다 [새로] [본떠 만들기] [지우기] · 새로 = 그 종류로 이름만 · 본떠 · 지우기 = 그 컨테이너에서 고른 양식이 있을 때만 켜짐 · 구조물 도면 · 면적산출 · 재료산출 컨테이너는 단추 없음 · 시스템 관리자만 - 본떠 만들기 흠: 표 복사본의 머리 이름(양식)이 원본 이름으로 남던 것을 새 이름으로 - 이름 규칙: 80자 넘는 이름이 서버 500 이던 것 · 앞 밑줄 이름이 새 프로젝트 복사를 깨던 것을 서버 · 화면 둘 다 막음 - 시험: 복사 독립 · 새 프로젝트 복사 · 이름 규칙 · 컨테이너 안 단추 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011vX53nJGC8ucBRFJns2n7B
This commit is contained in:
@@ -9,7 +9,6 @@
|
||||
import {
|
||||
createButton,
|
||||
createInputField,
|
||||
createSelectField,
|
||||
el,
|
||||
showConfirmDialog,
|
||||
showToast,
|
||||
@@ -87,7 +86,10 @@ export interface SideHandle {
|
||||
|
||||
/** 서버 이름 규칙과 같게 — 화면에서 먼저 막음 */
|
||||
const badName = (name: string): boolean =>
|
||||
/[\\/:*?"<>|\x00-\x1f]|\.\./.test(name) || name.startsWith(".");
|
||||
/[\\/:*?"<>|\x00-\x1f]|\.\./.test(name) ||
|
||||
name.startsWith(".") ||
|
||||
name.startsWith("_") ||
|
||||
name.length > 80;
|
||||
|
||||
const why = (error: unknown): string => (error instanceof Error ? error.message : "");
|
||||
const failed = (error: unknown): void =>
|
||||
@@ -113,25 +115,30 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
const columnBodies = COLUMN_KINDS.map(() =>
|
||||
el("div", { className: "m02-side__items ui-sidebar-list" }),
|
||||
);
|
||||
/** 새로 · 본떠 만들기 · 지우기 — 표 · 도면 컨테이너 안에 하나씩(그 종류로 동작) · 관리자만 */
|
||||
const editButtons = new Map<Kind, { copy: HTMLButtonElement; del: HTMLButtonElement }>();
|
||||
const actionRow = (kind: Kind): HTMLElement[] => {
|
||||
if (!canEdit(layer, opt.isAdmin)) return [];
|
||||
const btnNew = createButton({ label: L("M02_New"), variant: "filled" });
|
||||
const copy = createButton({ label: L("M02_Copy"), variant: "ghost" });
|
||||
const del = createButton({ label: L("M02_Delete"), variant: "danger" });
|
||||
editButtons.set(kind, { copy, del });
|
||||
btnNew.addEventListener("click", () => newTemplate(kind));
|
||||
copy.addEventListener("click", copyTemplate);
|
||||
del.addEventListener("click", () => void deleteCurrent());
|
||||
return [el("div", { className: "m02-side__row", children: [btnNew, copy, del] })];
|
||||
};
|
||||
const listHost = el("div", {
|
||||
className: "m02-side__lists ui-sidebar-panel",
|
||||
children: [
|
||||
section(L("M02_KindTable"), bodies.get("table")!),
|
||||
section(L("M02_KindDrawing"), bodies.get("drawing")!),
|
||||
section(L("M02_KindTable"), bodies.get("table")!, ...actionRow("table")),
|
||||
section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing")),
|
||||
...COLUMN_KINDS.map((k, i) => section(k.label(), columnBodies[i])),
|
||||
],
|
||||
});
|
||||
attachCollapsible(listHost);
|
||||
|
||||
const btnNew = createButton({ label: L("M02_New"), variant: "filled" });
|
||||
const btnCopy = createButton({ label: L("M02_Copy"), variant: "ghost" });
|
||||
const btnDel = createButton({ label: L("M02_Delete"), variant: "danger" });
|
||||
const editRow = el("div", {
|
||||
className: "m02-side__row ui-sidebar-card",
|
||||
children: [btnNew, btnCopy, btnDel],
|
||||
});
|
||||
|
||||
const root = el("div", { className: "m02-side ui-sidebar-panel", children: [listHost, editRow] });
|
||||
const root = el("div", { className: "m02-side ui-sidebar-panel", children: [listHost] });
|
||||
|
||||
/** 고름 표시 — `picked`(방금 누름) 이 있으면 그것 · 없으면 지금 열린 것과 비교 */
|
||||
const isPicked = (kind: Kind, name: string): boolean => {
|
||||
@@ -186,11 +193,10 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
const paintColumns = (): void =>
|
||||
COLUMN_KINDS.forEach((k, i) => paintRows(columnBodies[i], k.kind, k.key));
|
||||
|
||||
/** 본떠 만들기 · 지우기 — 그 컨테이너에서 고른(열린) 양식이 있을 때만 켜짐 */
|
||||
function paintButtons(): void {
|
||||
const editable = canEdit(layer, opt.isAdmin);
|
||||
btnNew.disabled = !editable;
|
||||
btnCopy.disabled = !editable || !currentInfo();
|
||||
btnDel.disabled = !editable || !currentInfo();
|
||||
const cur = currentInfo();
|
||||
for (const [kind, b] of editButtons) b.copy.disabled = b.del.disabled = cur?.종류 !== kind;
|
||||
}
|
||||
|
||||
async function select(info: TemplateInfo): Promise<void> {
|
||||
@@ -243,16 +249,8 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
}
|
||||
|
||||
/* --- 시스템 층 새로 · 본떠 · 지우기(표 · 도면만 — 구조물 도면은 집계표 열이 정함) --- */
|
||||
const nameDialog = (
|
||||
title: string,
|
||||
withKind: boolean,
|
||||
onOk: (kind: Kind, name: string) => Promise<void>,
|
||||
): void => {
|
||||
const nameDialog = (title: string, onOk: (name: string) => Promise<void>): void => {
|
||||
const name = createInputField({ label: L("M02_Name"), placeholder: L("M02_Name") });
|
||||
const kind = createSelectField({
|
||||
label: L("M02_Kind"),
|
||||
options: KINDS.map((k) => ({ value: k.kind, text: k.label() })),
|
||||
});
|
||||
openModal({
|
||||
title,
|
||||
closeLabel: L("M02_Close"),
|
||||
@@ -265,7 +263,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
if (badName(text)) return showToast(L("M02_BadName"), "error");
|
||||
ok.disabled = true; // 연달아 눌러도 한 번만
|
||||
try {
|
||||
await onOk(kind.select.value as Kind, text);
|
||||
await onOk(text);
|
||||
close();
|
||||
} catch (error) {
|
||||
if (error instanceof StaleError) showToast(L("M02_NameExists"), "error");
|
||||
@@ -274,7 +272,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
ok.disabled = false;
|
||||
}
|
||||
});
|
||||
body.append(...(withKind ? [kind.root] : []), name.root, ok);
|
||||
body.append(name.root, ok);
|
||||
name.input.focus();
|
||||
},
|
||||
});
|
||||
@@ -287,22 +285,25 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
if (info) await select(info);
|
||||
};
|
||||
|
||||
btnNew.addEventListener("click", () =>
|
||||
nameDialog(L("M02_NewTitle"), true, async (kind, name) => {
|
||||
/** 이 컨테이너 종류의 새 양식 — 종류 고르기 없이 이름만 */
|
||||
function newTemplate(kind: Kind): void {
|
||||
nameDialog(L("M02_NewTitle"), async (name) => {
|
||||
await saveTemplate("system", kind, name, null, "", stubDoc(kind, name));
|
||||
await openCreated(kind, name);
|
||||
}),
|
||||
);
|
||||
btnCopy.addEventListener("click", () => {
|
||||
});
|
||||
}
|
||||
function copyTemplate(): void {
|
||||
const from = currentInfo();
|
||||
if (!from) return;
|
||||
nameDialog(L("M02_CopyTitle"), false, async (_kind, name) => {
|
||||
nameDialog(L("M02_CopyTitle"), async (name) => {
|
||||
const got = await readTemplate("system", from.종류, from.이름, null);
|
||||
await saveTemplate("system", from.종류, name, null, "", got.문서);
|
||||
// 표 문서 머리 이름(`양식`)도 새 이름으로 — 안 바꾸면 복사본이 원본 이름을 달고 있음
|
||||
const doc = from.종류 === "table" ? { ...(got.문서 as object), 양식: name } : got.문서;
|
||||
await saveTemplate("system", from.종류, name, null, "", doc);
|
||||
await openCreated(from.종류, name);
|
||||
});
|
||||
});
|
||||
btnDel.addEventListener("click", async () => {
|
||||
}
|
||||
async function deleteCurrent(): Promise<void> {
|
||||
await refresh(); // 저장 직후여도 최신 판으로
|
||||
const info = currentInfo();
|
||||
if (!info) return;
|
||||
@@ -315,7 +316,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
} catch (error) {
|
||||
failed(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void refresh();
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user