fix(M02): 개인 층 지우기에 판을 보냄 · 저장 안 한 고침이 있으면 머리 메뉴·뒤로 가기로 떠날 때도 확인(취소하면 M02 에 남고 주소 되돌림) · 내 양식·회사 공식 저장 뒤 열던 양식 유지
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148XFUpPfpiuTjxF1EK9c95
This commit is contained in:
+21
-2
@@ -12,7 +12,13 @@ import "@ui/ui_template_theme.css";
|
||||
import { injectBaseStyles } from "@ui/ui_template_elements";
|
||||
import { ROUTES, type RoutePath } from "@config/config_frontend";
|
||||
import { initThemeAndLang, injectShellStyles, renderShell } from "./app_shell";
|
||||
import { currentRoute, renderCurrentRoute, ensureInitialHash } from "./router";
|
||||
import {
|
||||
currentRoute,
|
||||
renderCurrentRoute,
|
||||
ensureInitialHash,
|
||||
setLeaveGuard,
|
||||
takeLeaveGuard,
|
||||
} from "./router";
|
||||
|
||||
const FOOTERLESS_ROUTES: readonly RoutePath[] = [
|
||||
ROUTES.B01_ACCOUNT,
|
||||
@@ -44,14 +50,27 @@ function bootstrap(): void {
|
||||
initThemeAndLang();
|
||||
|
||||
// 3. 셸 렌더 + 현재 라우트 렌더를 함께 수행하는 헬퍼
|
||||
let shown = "";
|
||||
const renderAll = (): void => {
|
||||
shown = currentRoute();
|
||||
const showFooter = !FOOTERLESS_ROUTES.includes(currentRoute());
|
||||
const outlet = renderShell(appRoot, showFooter); // 헤더/푸터(로그인·언어 반영) 갱신
|
||||
void renderCurrentRoute(outlet); // 아울렛에 페이지 렌더
|
||||
};
|
||||
|
||||
// 4. hashchange 1회 구독 (리스너 누적 방지)
|
||||
window.addEventListener("hashchange", renderAll);
|
||||
window.addEventListener("hashchange", () => {
|
||||
const guard = takeLeaveGuard();
|
||||
if (!guard || currentRoute() === shown) return renderAll();
|
||||
void guard().then((ok) => {
|
||||
if (ok) {
|
||||
setLeaveGuard(null);
|
||||
renderAll();
|
||||
} else {
|
||||
history.replaceState(null, "", `#/${shown}`); // 취소 — 머문 채 주소만 되돌림
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 5. 최초 진입
|
||||
ensureInitialHash();
|
||||
|
||||
@@ -65,6 +65,13 @@ const routeTable: Partial<Record<RoutePath, () => Promise<PageRenderer>>> = {
|
||||
(await import("../M02_MasterTemplete/M02_MasterTemplete_UI_Page")).renderM02MasterTemplate,
|
||||
};
|
||||
|
||||
/** 페이지가 떠나기 전에 묻는 자리 — false 를 돌려주면 이동을 취소(주소도 되돌림). 이동이 끝나면 비워짐. */
|
||||
let leaveGuard: (() => Promise<boolean>) | null = null;
|
||||
export const setLeaveGuard = (guard: (() => Promise<boolean>) | null): void => {
|
||||
leaveGuard = guard;
|
||||
};
|
||||
export const takeLeaveGuard = (): (() => Promise<boolean>) | null => leaveGuard;
|
||||
|
||||
/** 로그인 여부 (토큰 존재 확인) */
|
||||
export async function isAuthenticated(): Promise<boolean> {
|
||||
const { fetchSession } = await import("../A06_Login/A06_Login_Api_Fetch");
|
||||
|
||||
@@ -83,13 +83,14 @@ export const saveTemplate = (
|
||||
export const deleteTemplate = (kind: Kind, name: string, 판: string): Promise<{ ok: boolean }> =>
|
||||
call(`/templates/${kind}/${enc(name)}?판=${enc(판)}`, { method: "DELETE" });
|
||||
|
||||
/** 개인·회사 층에서 지움 — 서버 길은 sub4 (없으면 404) */
|
||||
/** 개인·회사 층에서 지움 — 판이 맞을 때만(다르면 409) */
|
||||
export const deleteLayerTemplate = (
|
||||
layer: Layer,
|
||||
kind: Kind,
|
||||
name: string,
|
||||
판: string,
|
||||
): Promise<{ ok: boolean }> =>
|
||||
call(`/layers/${layer}/templates/${kind}/${enc(name)}`, { method: "DELETE" });
|
||||
call(`/layers/${layer}/templates/${kind}/${enc(name)}?판=${enc(판)}`, { method: "DELETE" });
|
||||
|
||||
/* --- 프로젝트 층 단추 다섯 --- */
|
||||
const project = (id: string, tail: string): string => `/projects/${enc(id)}/templates/${tail}`;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* 부품 파일이 없거나 못 읽어도 페이지는 뜸 — 빈 자리 글.
|
||||
* ========================================================================== */
|
||||
|
||||
import { setLeaveGuard } from "../A00_Common/router";
|
||||
import { createButton, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import {
|
||||
@@ -92,6 +93,7 @@ export function createMain(isAdmin: boolean, onSaved?: () => void): MainHandle {
|
||||
dirty = false;
|
||||
return true;
|
||||
};
|
||||
setLeaveGuard(confirmLeave); // 머리 메뉴 · 뒤로 가기로 떠날 때도 물음
|
||||
|
||||
const showNotice = (nodes: (HTMLElement | string)[]): void => {
|
||||
notice.replaceChildren(...nodes);
|
||||
|
||||
@@ -365,7 +365,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
if (!(await showConfirmDialog(L("M02_DeleteConfirm").replace("{value}", info.이름)))) return;
|
||||
try {
|
||||
if (layer === "system") await deleteTemplate(info.종류, info.이름, info.판);
|
||||
else await deleteLayerTemplate(layer, info.종류, info.이름);
|
||||
else await deleteLayerTemplate(layer, info.종류, info.이름, info.판);
|
||||
showToast(L("M02_Deleted"), "success");
|
||||
opt.onOpen(null);
|
||||
await refresh();
|
||||
@@ -406,7 +406,12 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
} catch (error) {
|
||||
return failed(error);
|
||||
}
|
||||
void act(() => saveProjectAs(projectId, to, cur.kind, cur.name));
|
||||
try {
|
||||
await saveProjectAs(projectId, to, cur.kind, cur.name);
|
||||
showToast(L("M02_Done"), "success"); // 열던 양식은 그대로 둠
|
||||
} catch (error) {
|
||||
failed(error);
|
||||
}
|
||||
};
|
||||
projectButton(L("M02_ProjectReset"), async () => {
|
||||
if (await showConfirmDialog(L("M02_ResetConfirm"))) void act(() => resetProject(projectId));
|
||||
|
||||
Reference in New Issue
Block a user