From 72881c20e7f3bc9d42c8aa40d7ad0fad25186403 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 16:38:40 +0900 Subject: [PATCH] =?UTF-8?q?feat(M02):=2049-8=20=EA=B5=AC=EC=A1=B0=EB=AC=BC?= =?UTF-8?q?=20=EC=A1=B0=ED=95=A9=20=EC=B0=BE=EA=B8=B0=20=EC=B9=B8=20?= =?UTF-8?q?=EC=97=86=EC=95=B0=20=E2=80=94=20=EB=B6=84=EB=A5=98=20=ED=91=9C?= =?UTF-8?q?=20=C2=B7=20=EC=B9=B4=EB=93=9C=EA=B0=80=20=EC=9C=84=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=99=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7 --- .../M02_MasterTemplete_TreePanel.css | 5 --- .../M02_MasterTemplete_TreePanel.ts | 44 +++++-------------- .../M02_MasterTemplete_UI_Main.ts | 8 +--- .../M02_MasterTemplete_UI_Page.ts | 3 +- .../M02_MasterTemplete_UI_Style.css | 4 -- resources/tester/test_m02_combo_screen.py | 12 ++--- .../tester/ui_checks/49-8_찾기칸_없앰.js | 24 ++++++++++ ui_template/ui_template_locale_m2.ts | 5 --- 8 files changed, 41 insertions(+), 64 deletions(-) create mode 100644 resources/tester/ui_checks/49-8_찾기칸_없앰.js diff --git a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css index 760776347..a5a67be4d 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css +++ b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.css @@ -22,11 +22,6 @@ overflow-wrap: anywhere; } -/* 찾기 칸 — 상세 위쪽(PLAN 29-2) */ -.m02-tree__search { - max-width: 24rem; -} - /* 층 단추 셋 · 사람 고르기 — 좌측 패널 줄 아래 한 줄(PLAN 29-2) */ .m02-layers { display: flex; diff --git a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts index 3fa73d61b..f8c2ed41f 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_TreePanel.ts @@ -1,7 +1,7 @@ /* ============================================================================= * M02_MasterTemplete_TreePanel.ts - * 관리 화면(마스터 · 회사 · 개인 공용) 「구조물 라이브러리」 한 층의 나무 — 폴더 → … → 항목(깊이 자유) · 찾기. - * 층 + `owner`(남의 층 · PLAN 27-1c) 한 벌을 그림 — 층 단추는 `_Roots` · 찾기 칸은 부르는 쪽이 상세 위쪽에 · 자료 길은 `api`. + * 관리 화면(마스터 · 회사 · 개인 공용) 「구조물 라이브러리」 한 층의 나무 — 폴더 → … → 항목(깊이 자유). + * 층 + `owner`(남의 층 · PLAN 27-1c) 한 벌을 그림 — 층 단추는 `_Roots` · 자료 길은 `api`. * 항목을 누르면 표에서 그 열이 골라짐(카드 넷은 표가 엶) · 표에서 열을 고르면 `select` 로 나무도 그 항목. * 오른쪽 클릭 · 키보드(F2 · Delete)로 폴더 · 항목 더하기 · 이름 바꾸기 · 옮기기 · 지우기 — * `POST /api/m02/layers/{층}/tree`(PLAN 26-1). 읽기만 층은 [복사]만. 나무 자료는 `_Model`. @@ -10,13 +10,7 @@ * ========================================================================== */ import "./M02_MasterTemplete_TreePanel.css"; -import { - createButton, - createInputField, - el, - showConfirmDialog, - showToast, -} from "@ui/ui_template_elements"; +import { createButton, el, showConfirmDialog, showToast } from "@ui/ui_template_elements"; import { loadingError, showLoading } from "@ui/ui_template_loading"; import { t as L } from "@ui/ui_template_locale"; import { @@ -103,8 +97,6 @@ export interface TreePanelOptions { export interface TreePanelHandle { /** 나무(좌측 패널에 붙임) */ root: HTMLElement; - /** 찾기 칸 — 부르는 쪽이 상세 위쪽에 붙임(PLAN 29-2) */ - search: HTMLElement; /** 층 단추 줄 오른쪽 도구 — 「N개 고름」(30-2) · 「+」(맨 위 폴더 · 항목 더하기 · 읽기만 층 = 숨김 · 29-6) */ add: HTMLElement; refresh: () => Promise; @@ -132,7 +124,6 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { const api = opt.api ?? { fetch: fetchTree, edit: editTree, many: editTreeMany, paste: pasteTree }; const clipKind = "구조물"; // 복사해 둔 것의 나무 종류 — 같은 종류 나무끼리만 붙임 let view: TreeView | null = null; - let text = ""; let activeCol: string | null = null; let seq = 0; let busy = false; @@ -142,10 +133,8 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { let picked = new Set(); let anchor: string | null = null; - const search = createInputField({ placeholder: L("M02_TreeSearch"), compact: true }); const body = el("div", { className: "m02-tree__body", attrs: { tabindex: "-1" } }); const root = el("div", { className: "m02-tree", children: [body] }); - search.root.classList.add("m02-tree__search"); // 층 단추 옆 「+」 — 맨 위에 폴더 · 항목 더하기(뿌리 줄 오른쪽 클릭을 갈음) const add = createButton({ label: "+", @@ -241,10 +230,10 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { return { ...base, id, ...(opt.canEdit || canCopy ? { menu: () => menuFor(target, id) } : {}) }; } - function folderNode(folder: FolderNode, searching: boolean): TreeNode { + function folderNode(folder: FolderNode): TreeNode { const id = folderIdOf(folder.path); const kids = [ - ...folder.folders.map((one) => folderNode(one, searching)), + ...folder.folders.map((one) => folderNode(one)), ...folder.items.map((item) => node( { kind: "item", item }, @@ -265,7 +254,7 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { count: countItems(folder), folder: true, children: kids, - open: searching || open.has(id), + open: open.has(id), run: () => void (open.has(id) ? open.delete(id) : open.add(id)), }, id, @@ -348,19 +337,12 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { const focused = (document.activeElement as HTMLElement | null)?.dataset?.node; targets.clear(); if (!view) return body.replaceChildren(hint(L("M02_PickerLoading"))); - const searching = text.trim() !== ""; - const tree = buildFolders(view, text); - const none = countItems(tree) === 0 && (searching || !tree.folders.length); + const tree = buildFolders(view); + const none = countItems(tree) === 0 && !tree.folders.length; body.replaceChildren( // 맨 위 뿌리 줄 없음 — 폴더 · 항목부터(PLAN 29-6 · 뿌리 메뉴 = 빈 자리 오른쪽 클릭 · 「+」) - ...renderTree(folderNode(tree, searching).children ?? [], make), - ...(none - ? [ - hint( - L(searching ? "M02_TreeNoHit" : opt.canEdit ? "M02_TreeEmptyEdit" : "M02_TreeEmpty"), - ), - ] - : []), + ...renderTree(folderNode(tree).children ?? [], make), + ...(none ? [hint(L(opt.canEdit ? "M02_TreeEmptyEdit" : "M02_TreeEmpty"))] : []), ); if (focused) rowOf(focused)?.focus({ preventScroll: true }); setPicked(picked, anchor); // 없어진 줄은 고름에서 뺌 @@ -599,11 +581,6 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { : from.folders.map((one) => findFolder(one, path)).find(Boolean); /* --- 이음 --- */ - search.input.addEventListener("input", () => { - text = search.input.value; - paint(); - }); - // 빈 자리 오른쪽 클릭 = 맨 위 메뉴(폴더 · 항목 더하기 · 붙여넣기) body.addEventListener("contextmenu", (event) => { if ((event.target as HTMLElement).closest("[data-node]")) return; @@ -676,7 +653,6 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle { }; return { root, - search: search.root, add: tools, refresh, select, diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index 77d6b3146..4007cab7b 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -101,11 +101,9 @@ export function canEdit(layer: Layer, isAdmin: boolean): boolean { } /** 페이지가 끼우는 것 — `top` = 편집 칸 맨 위 막대(회사용 읽기만 안내) · `onColumn` = 집계표에서 고른 열 · - * `search` = 「구조물 조합」을 열 때만 상세 위쪽에 보이는 나무 찾기 칸(나무는 좌측 패널 · PLAN 29-2) · * `newItem` = 「구조물 조합」 제목 줄 오른쪽 끝 「새 항목」(쓸 수 있는 층만 · PLAN 30-5 · 공용 `m02-main__new`) */ export interface MainExtras { top?: HTMLElement; - search?: HTMLElement; newItem?: HTMLElement; onColumn?: (column: string | null) => void; /** 구조물 조합 표 머리 이름 칸 고침 → 나무 이름 바꾸기(쓸 수 있는 층만 · PLAN 31-1) */ @@ -145,7 +143,6 @@ export function createMain( // M01 머리 꼴(39-5) — ← 는 제목 앞 · 제목 18px 왼쪽 · 단추는 오른쪽(slot 이 늘어나 밂) children: [back, title, slot, reload, save, ...(extras.newItem ? [extras.newItem] : [])], }), - ...(extras.search ? [extras.search] : []), notice, host, extra, @@ -154,10 +151,9 @@ export function createMain( const root = column; const isCombo = (at: Selection | null): boolean => !!at && at.kind === "table" && at.name === STRUCTURE_TABLE && at.layer !== "project"; - // 찾기 칸 · 「새 항목」 · 표 접기(제목)는 「구조물 조합」일 때만 + // 「새 항목」 · 표 접기(제목)는 「구조물 조합」일 때만 const showAside = (): void => { const combo = isCombo(sel); - if (extras.search) extras.search.hidden = !combo; if (extras.newItem) extras.newItem.hidden = !combo; title.classList.toggle("m02-main__title--fold", combo); title.title = combo ? L("M02_TableFold") : ""; @@ -166,7 +162,7 @@ export function createMain( title.classList.remove("is-folded"); } }; - // 「구조물 조합」 표 접기(PLAN 31-2) — 제목을 누르면 표만 접힘 · 펴짐(카드 · 찾기는 그대로) + // 「구조물 조합」 표 접기(PLAN 31-2) — 제목을 누르면 표만 접힘 · 펴짐(카드는 그대로) title.addEventListener("click", () => { if (!isCombo(sel)) return; host.hidden = !host.hidden; diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts index e18034463..f3a44130f 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts @@ -1,7 +1,7 @@ /* ============================================================================= * M02_MasterTemplete_UI_Page.ts * 마스터 템플릿 · 구조물 관리 화면 — 좌측 도킹 패널(양식 목록 · 층 단추 · 고른 층의 나무) / - * 우측 편집 칸(표·도면 부품 · 「구조물 조합」이면 위쪽에 나무 찾기 칸 · PLAN 29-2) + * 우측 편집 칸(표·도면 부품 · 「구조물 조합」이면 나무는 좌측 패널 · PLAN 29-2) * * 한 화면에 층 셋(마스터 · 회사 · 개인 · PLAN 27-1) — 고른 층 = 진입 해시 `?open=&root=&owner=&company=` * (`M02_MasterTemplete_Layer.ts`) · 층 단추 · 「사람 고르기」를 누르면 해시를 바꿔 이 화면을 다시 세움. @@ -130,7 +130,6 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise side.paint(), { top: readOnlyBar?.root, - search: tree.search, // 「새 항목」(30-5) — 제목 줄 오른쪽 끝 · 쓸 수 있는 층만 · 상세 칸에 [새로 만들기] [복사 생성](39-5) ...(canEdit(layer, isAdmin) ? { newItem: newItemButton(() => void openNew()) } : {}), onColumn: (column) => { diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css b/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css index 730b3775d..889860da2 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css @@ -165,10 +165,6 @@ color: var(--color-text); } -.m02-main__head + .m02-tree__search { - margin-bottom: var(--spacing-8); -} - /* 상세 제목 줄 오른쪽 끝 「새 항목」 · 「새 조합」(PLAN 30-5 · 공용) — 늘어나는 칸이 없어도 끝으로 */ .m02-main__new { margin-left: auto; diff --git a/resources/tester/test_m02_combo_screen.py b/resources/tester/test_m02_combo_screen.py index 929c47ea0..dcf3d396d 100644 --- a/resources/tester/test_m02_combo_screen.py +++ b/resources/tester/test_m02_combo_screen.py @@ -5,8 +5,7 @@ 빼고(30-7), [저장] 때 저장본 것을 다시 끼움(열 차례 = 저장본 · 보인 고침은 받음). ② 표 부품 `lockHead` — 열 더하기 · 지우기 · 머리 · 층 이름 고치기 없음(단위 · 조회 후 선택은 그대로). -③ 나무는 좌측 패널 「구조물 조합」 줄 아래(층 단추 + 나무 · PLAN 29-2) · 상세 위쪽 찾기 칸은 - 「구조물 조합」일 때만. +③ 나무는 좌측 패널 「구조물 조합」 줄 아래(층 단추 + 나무 · PLAN 29-2) · 찾기 칸 없음(49-8). """ from __future__ import annotations @@ -101,21 +100,18 @@ def test_참조표는_열_머리를_못_고침(): assert "getDoc: () => comboMerge(loaded, shown.getDoc())" in main -def test_나무는_좌측_패널_찾기_칸은_상세_위쪽(): +def test_나무는_좌측_패널_찾기_칸_없음(): side = _text(M02 / "M02_MasterTemplete_UI_Side.ts") assert "tree?: HTMLElement[];" in side and "children: opt.tree ?? []" in side assert 'info.이름 === STRUCTURE_TABLE ? L("M02_KindTable")' in side # 좌측 줄 이름 page = _text(M02 / "M02_MasterTemplete_UI_Page.ts") assert "createTreePanel({" in page and "tree.select(column)" in page - assert ( - 'layerBar("구조물조합", tree.add, () => void main.openTable(layer)),' in page - and "search: tree.search," in page - ) + assert 'layerBar("구조물조합", tree.add, () => void main.openTable(layer)),' in page main = _text(M02 / "M02_MasterTemplete_UI_Main.ts") assert 'at.kind === "table" && at.name === STRUCTURE_TABLE && at.layer !== "project"' in main assert 'isCombo(next) ? L("M02_KindTable")' in main # 상세 제목 assert main.count("showAside();") == 3 # 열 때 · 양식 밖 화면 · 처음 - assert "if (extras.search) extras.search.hidden = !combo;" in main + assert "extras.search" not in main and "tree.search" not in page # 찾기 칸 없앰(49-8) locale = _text(ROOT / "ui_template" / "ui_template_locale_m2.ts") assert 'M02_KindTable: ["구조물 조합"' in locale assert ".m02-shell" not in _text( diff --git a/resources/tester/ui_checks/49-8_찾기칸_없앰.js b/resources/tester/ui_checks/49-8_찾기칸_없앰.js new file mode 100644 index 000000000..6ae4f3e93 --- /dev/null +++ b/resources/tester/ui_checks/49-8_찾기칸_없앰.js @@ -0,0 +1,24 @@ +/* 49-8 — M02 구조물 조합 상세: 위쪽 찾기 칸 없앰 · 제목 줄 ~ 분류 표 간격 + * 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 + * 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌) + * 기대 수치: + * - search.count = 0 (상세 안 찾기 칸 없음) + * - gap.px = 제목 줄 아래 ~ 분류 표(첫 표 칸) 위 간격 — 전 대비 줄어듦 + */ +(async () => { + const main = document.querySelector(".m02-main"); + const head = main?.querySelector(".m02-main__head"); + const table = main?.querySelector(".m02-struct__sheet, .ss-root, table"); + const searchCount = main + ? main.querySelectorAll(".m02-tree__search, input[type=search]").length + : -1; + const headBottom = head?.getBoundingClientRect().bottom ?? null; + const tableTop = table?.getBoundingClientRect().top ?? null; + return JSON.stringify({ + search: { count: searchCount }, + gap: { + px: headBottom !== null && tableTop !== null ? +(tableTop - headBottom).toFixed(1) : null, + table: table?.className || table?.tagName || null, + }, + }); +})(); diff --git a/ui_template/ui_template_locale_m2.ts b/ui_template/ui_template_locale_m2.ts index 15a1000a0..e7da5b753 100644 --- a/ui_template/ui_template_locale_m2.ts +++ b/ui_template/ui_template_locale_m2.ts @@ -99,17 +99,12 @@ export const ui_locales_m2 = { M02_SourcePeople: ["다른 사람", "Others"], M02_SourcePractice: ["실무문서", "Practice documents"], M02_PeopleEmpty: ["비어 있음", "empty"], - M02_TreeSearch: [ - "찾기 — 이름 · 키 · 폴더 · 단위 (낱말 여럿 = 모두 담은 것)", - "Find — name, key, folder, unit (all words)", - ], M02_TreeEmptyEdit: [ "항목이 없음 — 빈 자리 오른쪽 클릭 또는 층 단추 옆 「+」 로 폴더 · 항목을 더함", "No items — right-click an empty area or use + beside the layer buttons to add", ], M02_TreeAddTop: ["맨 위에 폴더 · 항목 추가", "Add folder or item at top"], M02_TreeEmpty: ["항목이 없음", "No items"], - M02_TreeNoHit: ["찾은 항목이 없음", "No matching item"], M02_TreeAddFolder: ["폴더 추가", "Add folder"], M02_TreeAddItem: ["항목 추가", "Add item"], M02_TreeRename: ["이름 바꾸기", "Rename"],