- 나무 부품 — Ctrl · Shift 클릭 고름(TreePanel_Pick) · 고른 줄 배경 · 층 단추 줄 「N개 고름」 · 오른쪽 클릭 메뉴가 고른 것 전부(복사 · 옮기기 · 지우기) · Ctrl+C · Ctrl+V · Delete · 복사는 읽기 층에서도 · 붙여넣기는 쓸 수 있는 층만 · 콜백 api.many · api.paste · clipKind(일위대가 쪽이 붙일 자리)
- 서버 — POST /layers/{층}/items/paste(여러 항목 · 폴더 · 새 SC/SP 키 · 본뜬 = 원본 · 같은 이름 「(2)」 · 세 파일 · 입력 연결 · 자재 조합 · 빈 폴더까지 · M02_Layer_Access 쓰기 층만) · 나무 고침 대상 여러 개(옮기기 · 지우기 한 번 저장 · 막히면 안 씀)
- 층 단추 줄 「N개 고름」 · 「+」 오른쪽 끝
- 시험 test_m02_tree_paste 새로 · 고르기 규칙 Node 시험
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
4.4 KiB
Python
106 lines
4.4 KiB
Python
"""여러 항목 · 폴더를 층 사이 붙여넣기(PLAN 30-2) — 구조물 라이브러리 나무의 [복사] → [붙여넣기].
|
|
|
|
붙일 폴더(`dest`) 아래 새 항목 — 대상 층 새 키(SC · SP) · 열 `본뜬` = 원본(층 · 주인 · 키) ·
|
|
같은 이름은 「이름 (2)」 · 폴더는 안의 항목 · 빈 폴더까지(폴더 이름이 겹치면 「폴더 (2)」).
|
|
파일(도면 · 면적 · 재료 · 입력 연결 · 자재 조합)은 `Items.copy_item` 이 옮김. 권한은 부르는 쪽 몫.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from M02_MasterTemplete import M02_MasterTemplete_Items as items_store
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
|
|
|
|
|
|
def _under(path: list[str], folder: list[str]) -> bool:
|
|
return path[: len(folder)] == folder
|
|
|
|
|
|
def outer(targets: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
"""고른 것 가운데 다른 고른 폴더 안에 든 것은 뺌(폴더가 안의 것까지 다룸) —
|
|
옮기기 · 지우기 · 붙여넣기."""
|
|
folders = [list(t.get("경로") or []) for t in targets if not t.get("키")]
|
|
|
|
def inside(t: dict[str, Any]) -> bool:
|
|
path = list(t.get("경로") or [])
|
|
return any(_under(path, f) and (t.get("키") or path != f) for f in folders if f)
|
|
|
|
return [t for t in targets if not inside(t)]
|
|
|
|
|
|
def _table(folder: Path) -> dict[str, Any]:
|
|
with store.at(folder):
|
|
return tree.normalize_table(store.read("table", store.STRUCTURE_TABLE)["문서"])
|
|
|
|
|
|
def _items(doc: dict[str, Any]) -> list[dict[str, Any]]:
|
|
return [c for c in doc.get("열") or [] if tree.is_item(c)]
|
|
|
|
|
|
def _unique(path: list[str], taken: list[list[str]]) -> list[str]:
|
|
n, out = 2, path
|
|
while out in taken:
|
|
out, n = [*path[:-1], f"{path[-1]} ({n})"], n + 1
|
|
return out
|
|
|
|
|
|
def paste(
|
|
source: Path,
|
|
target: Path,
|
|
targets: list[dict[str, Any]],
|
|
dest: list[str],
|
|
origin: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
"""`targets` = `[{경로, 키?}]`(키 = 항목 · 없으면 폴더 경로) → `target` 층 `dest` 아래 ·
|
|
돌려줌 알림 `{op: "paste", 경로, 열: [새 열 id]}`. `origin` = `{layer, owner}`(본뜬)."""
|
|
src = _table(source)
|
|
with store.at(source):
|
|
keys = {str(r.get("열")): str(r.get("키")) for r in store.read_combos().get("줄", [])}
|
|
src_items = [(tree.path_of(c), keys.get(c["id"])) for c in _items(src)]
|
|
src_folders = tree.folders_of(src)
|
|
items_store.ensure(target)
|
|
dst = _table(target)
|
|
taken = [tree.path_of(c) for c in _items(dst)]
|
|
taken_folders = tree.folders_of(dst)
|
|
plan: list[tuple[str, list[str]]] = [] # (원본 키, 새 경로)
|
|
new_folders: list[list[str]] = []
|
|
|
|
def busy(path: list[str]) -> bool:
|
|
paths = [*taken, *(p for _, p in plan)]
|
|
return path in [*taken_folders, *new_folders] or any(_under(p, path) for p in paths)
|
|
|
|
for one in outer(targets):
|
|
if one.get("키"):
|
|
key = str(one["키"])
|
|
found = next((p for p, k in src_items if k == key), None)
|
|
if found is None:
|
|
raise store.StoreError(404, f"없는 항목 「{key}」")
|
|
plan.append((key, _unique([*dest, found[-1]], [*taken, *(p for _, p in plan)])))
|
|
continue
|
|
folder = list(one.get("경로") or [])
|
|
if not folder or (
|
|
folder not in src_folders and not any(_under(p, folder) for p, _ in src_items)
|
|
):
|
|
raise store.StoreError(404, f"없는 폴더 「{' '.join(folder)}」")
|
|
top, n = [*dest, folder[-1]], 2
|
|
while busy(top):
|
|
top, n = [*dest, f"{folder[-1]} ({n})"], n + 1
|
|
new_folders.append(top)
|
|
new_folders += [[*top, *f[len(folder) :]] for f in src_folders if _under(f, folder)]
|
|
plan += [(k, [*top, *p[len(folder) :]]) for p, k in src_items if _under(p, folder) and k]
|
|
|
|
made = [
|
|
items_store.copy_item(source, target, key, {**origin, "key": key}, path)["열"]
|
|
for key, path in plan
|
|
]
|
|
if new_folders: # 빈 폴더도 남게 — 집계표 `폴더`
|
|
with store.at(target):
|
|
got = store.read("table", store.STRUCTURE_TABLE)
|
|
doc = got["문서"]
|
|
doc["폴더"] = [*(doc.get("폴더") or []), *new_folders]
|
|
store.write("table", store.STRUCTURE_TABLE, got["판"], doc)
|
|
return {"op": "paste", "경로": dest, "열": made}
|