feat(M02): 49-8 구조물 조합 찾기 칸 없앰 — 분류 표 · 카드가 위로 붙음
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -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<void>;
|
||||
@@ -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<string>();
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user