Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1

This commit is contained in:
2026-10-04 21:46:45 +09:00
7 changed files with 103 additions and 14 deletions
@@ -54,7 +54,8 @@ function reason(detail: unknown, status: number): string {
return lines.filter(Boolean).join(" · ") || `HTTP ${status}`;
}
/** 지금 화면의 층 — 시스템 = 옛 길 그대로 · 회사 · 개인 = `/layers/{층}` 아래(구조물집계표 · 조합 길 모두) ·
/** 지금 화면의 층 — 시스템 · 회사 · 개인 모두 `/layers/{층}` 아래(구조물집계표 · 조합 길 모두 · 읽기는
* 로그인 누구나 · 쓰기는 서버 층 권한 · 옛 `/api/m02/templates/*` 는 시스템 관리자 전용이라 안 씀) ·
* `owner` = 남의 층을 볼 때 주인(회사 `{C}` · 개인 `{C}/{U}` · 자기 층 = null · PLAN 27-1c) —
* 지금 층 길에 `?owner=` 로 붙음(M02 화면을 떠나면 페이지가 시스템 · null 로 되돌림) */
let activeLayer: Layer = "system";
@@ -73,24 +74,20 @@ const owned = (layer: Layer, path: string): string =>
? `${path}${path.includes("?") ? "&" : "?"}owner=${enc(activeOwner)}`
: path;
const scoped = <T>(path: string, init: RequestInit = {}): Promise<T> =>
call<T>(
owned(activeLayer, `${activeLayer === "system" ? "" : `/layers/${activeLayer}`}${path}`),
init,
);
call<T>(owned(activeLayer, `/layers/${activeLayer}${path}`), init);
const q = (projectId: string | null): string => (projectId ? `?project_id=${enc(projectId)}` : "");
const head = (layer: Layer): string =>
layer === "system" ? "/templates" : `/layers/${layer}/templates`;
const head = (layer: Layer): string => `/layers/${layer}/templates`;
const item = (layer: Layer, kind: Kind, name: string, projectId: string | null): string =>
owned(layer, `${head(layer)}/${kind}/${enc(name)}${layer === "system" ? "" : q(projectId)}`);
/** 시스템 길은 배열 · 층 길은 `{층, 양식: [...]}` — 둘 다 목록으로 */
/** 층 길은 `{층, 양식: [...]}` · 옛 시스템 길은 배열 — 둘 다 목록으로 */
export const listTemplates = async (
layer: Layer,
projectId: string | null,
): Promise<TemplateInfo[]> => {
const got = await call<TemplateInfo[] | { 양식: TemplateInfo[] }>(
layer === "system" ? head(layer) : owned(layer, `${head(layer)}${q(projectId)}`),
owned(layer, `${head(layer)}${layer === "system" ? "" : q(projectId)}`),
);
return Array.isArray(got) ? got : got.양식;
};
@@ -114,7 +111,7 @@ export const saveTemplate = (
/** 시스템 층만 지움 */
export const deleteTemplate = (kind: Kind, name: string, 판: string): Promise<{ ok: boolean }> =>
call(`/templates/${kind}/${enc(name)}?판=${enc(판)}`, { method: "DELETE" });
call(`${head("system")}/${kind}/${enc(name)}?판=${enc(판)}`, { method: "DELETE" });
/** 개인·회사 층에서 지움 — 판이 맞을 때만(다르면 409) */
export const deleteLayerTemplate = (
@@ -204,7 +201,8 @@ export interface OptionsDoc {
종류: OptionKind[];
}
export const fetchStructureOptions = (): Promise<OptionsDoc> => call("/structure-options");
export const fetchStructureOptions = (): Promise<OptionsDoc> =>
call("/layers/system/structure-options"); // 읽기 = 로그인 누구나 · 고쳐 씀은 옛 길(시스템 관리자)
/** 고쳐 씀 — 숨긴 것만 보냄 · 낡은 판이면 StaleError · 돌려받는 것은 새 문서 */
export const saveStructureOptions = (판: string, 표시: OptionsDoc["표시"]): Promise<OptionsDoc> =>
@@ -288,6 +288,14 @@ def get_numbers(layer: Layer, owner: str | None = None, session: Session = Depen
return _call(_root(session, layer, owner), store.structure_numbers)
@router.get("/layers/system/structure-options")
def get_structure_options(session: Session = Depends(verify_session)) -> dict:
"""구조물 옵션 보이기 읽기 — 로그인 누구나(마스터 한 벌 · 고쳐 씀은 옛 길 시스템 관리자만)."""
from M02_MasterTemplete import M02_MasterTemplete_Options as options
return _call(_root(session, "system"), options.listing)
@router.get("/layers/{layer}/structure-combos")
def get_combos(layer: Layer, owner: str | None = None, session: Session = Depends(verify_session)):
return _call(_root(session, layer, owner), store.read_combos)
@@ -276,6 +276,8 @@ async def list_layer(
manifest = await asyncio.to_thread(layers.read_manifest, folder)
for row in rows:
row["출처"] = manifest.get(f"{row['종류']}/{row['이름']}")
if (row["종류"], row["이름"]) in store.IN_USE: # 화면이 지우기 확인 창 전에 막음
row["쓰는곳"] = store.IN_USE[(row["종류"], row["이름"])]
return {"층": layer, "양식": rows}
@@ -4,6 +4,7 @@
* 아직 만든 것이 없는 남의 층(404)은 맨 위만 — 첫 쓰기 때 서버가 소속을 보고 뼈대를 세움.
* ========================================================================== */
import { t as L } from "@ui/ui_template_locale";
import type { Layer, TreeView } from "./M02_MasterTemplete_Api_Fetch";
import { askFolder } from "./M02_MasterTemplete_TreePanel_Dialogs";
import { buildFolders, listFolders } from "./M02_MasterTemplete_TreePanel_Model";
@@ -22,5 +23,6 @@ export async function askCopyFolder(
): Promise<string[] | null> {
const view = await fetchTree(to.layer, to.owner).catch(() => null);
const folders = view ? listFolders(buildFolders(view)) : [];
return askFolder(to.label.replace("{n}", String(n)), folders, () => false, []);
const title = to.label.replace("{n}", String(n));
return askFolder(title, folders, () => false, [], L("M02_TreeCopyFolder"));
}
@@ -142,12 +142,14 @@ export function askNewItem(opt: {
});
}
/** 옮길 폴더 고르기 — `folders` 에서 `blocked` 인 것은 뺌 · 맨 위(폴더 밖)는 늘 있음 */
/** 옮길 폴더 고르기 — `folders` 에서 `blocked` 인 것은 뺌 · 맨 위(폴더 밖)는 늘 있음 ·
* `fieldLabel` = 칸 글(복사는 「복사할 폴더」) */
export function askFolder(
title: string,
folders: string[][],
blocked: (path: string[]) => boolean,
current: string[],
fieldLabel: string = L("M02_TreeMoveTo"),
): Promise<string[] | null> {
return new Promise((resolve) => {
let done = false;
@@ -164,7 +166,7 @@ export function askFolder(
beforeClose: () => (finish(null), true),
mount: (body, close) => {
const field = createSelectField({
label: L("M02_TreeMoveTo"),
label: fieldLabel,
options: choices.map((path, i) => ({
value: String(i),
text: path.length ? path.join(" / ") : L("M02_TreeTop"),