/* ============================================================================= * M02_MasterTemplete_TreePanel_Model.ts * 관리 화면 구조물 라이브러리의 자료 쪽(화면 없음) — 서버 나무(`폴더` + `items`)를 폴더 나무로 · 찾기 · * 폴더 목록. 항목 경로는 폴더 이름 배열 + 마지막 마디가 항목 이름(PLAN 26-1 · 깊이 자유). * ========================================================================== */ import type { TreeItem, TreeView } from "./M02_MasterTemplete_Api_Fetch"; export interface FolderNode { /** 이 폴더까지의 경로 — 맨 위 = 빈 배열 */ path: string[]; folders: FolderNode[]; items: TreeItem[]; } export const samePath = (a: string[], b: string[]): boolean => a.length === b.length && a.every((part, i) => part === b[i]); /** `path` 가 `folder` 와 같거나 그 안인가 */ export const isUnder = (path: string[], folder: string[]): boolean => folder.length <= path.length && folder.every((part, i) => part === path[i]); export const folderIdOf = (path: string[]): string => `f|${path.join("|")}`; export const itemIdOf = (item: TreeItem): string => `i|${item.열}`; export const leafName = (item: TreeItem): string => item.경로[item.경로.length - 1] ?? item.이름; export const folderOf = (item: TreeItem): string[] => item.경로.slice(0, -1); /** 찾기 — 경로 · 키 · 일위대가 키에 모든 낱말이 들어 있는 항목(빈 글이면 전부) */ export function matches(item: TreeItem, text: string): boolean { const words = text.trim().toLowerCase().split(/\s+/).filter(Boolean); const hay = [...item.경로, item.key, item.일위대가 ?? ""].join(" ").toLowerCase(); return words.every((word) => hay.includes(word)); } /** 폴더 나무 — 찾는 글이 있으면 맞는 항목과 그 위 폴더만 · 없으면 빈 폴더까지 전부 */ export function buildFolders(view: TreeView, text = ""): FolderNode { const searching = text.trim() !== ""; const root: FolderNode = { path: [], folders: [], items: [] }; const at = (path: string[]): FolderNode => path.reduce((node, name, i) => { let next = node.folders.find((f) => f.path[i] === name); if (!next) { next = { path: path.slice(0, i + 1), folders: [], items: [] }; node.folders.push(next); } return next; }, root); if (!searching) view.폴더.forEach(at); for (const item of view.items) if (matches(item, text)) at(folderOf(item)).items.push(item); return root; } export const countItems = (node: FolderNode): number => node.items.length + node.folders.reduce((sum, f) => sum + countItems(f), 0); /** 모든 폴더 경로(맨 위 제외) — 옮길 곳 고르기 */ export function listFolders(root: FolderNode): string[][] { return root.folders.flatMap((f) => [f.path, ...listFolders(f)]); } /** 그 열이 들어 있는 폴더 길 — 나무를 그 항목까지 펼칠 때 */ export function ancestorsOf(view: TreeView, colId: string): string[][] { const item = view.items.find((one) => one.열 === colId); if (!item) return []; const folder = folderOf(item); return folder.map((_, i) => folder.slice(0, i + 1)); }