/** * STMATE w 8.0 — 메인 메뉴·툴바·단축키·자동백업·최근 작업폴더·화면 크기 동작 (ui_app_menu.js) * 메뉴 자료 = ui_menu.js(원본 M_Menu1 체계) · 동작 우선순위 = 끼워 넣기(STMATE_PLUGINS.actions) → 여기 H 표 * 설정 저장 = 브라우저 localStorage("stmate_ui_prefs") — 원본 Start8x.Ini 의 창 위치·보기 설정 자리 */ (function (global) { "use strict"; const M = global.STMATE_UI_MENU; const PREF_KEY = "stmate_ui_prefs"; const DEF = { autoBackDaily: false, autoBack30: false, prnPdf: false, useName2: false }; Object.assign(DEF, { dflIndex: "C", dflWidth: 40, screenMode: 0, scale: 100, recent: [] }); Object.assign(DEF, { lastBackDay: "", lastBack30: 0 }); function loadPrefs() { try { return Object.assign({}, DEF, JSON.parse(global.localStorage.getItem(PREF_KEY) || "{}")); } catch (_e) { return Object.assign({}, DEF); } } const prefs = loadPrefs(); const savePrefs = () => { try { global.localStorage.setItem(PREF_KEY, JSON.stringify(prefs)); } catch (_e) { // 저장 공간 없음 — 이번 실행 동안만 유지 } }; const pad = (n) => String(n).padStart(2, "0"); const today = () => { const d = new Date(); return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}`; }; const nowTag = () => { const d = new Date(); return `${today()}_${pad(d.getHours())}${pad(d.getMinutes())}`; }; let API = null; const PLUG = () => global.STMATE_PLUGINS || { screens: {}, actions: {}, menus: [] }; function go(screen) { API.ctx.uiState.activeScreen = screen; API.renderWorkspace(); } // 백업 — 계산 결과 저장 묶음을 「프로젝트_백업_날짜」 폴더·STC 로(서버 work_dbf) async function backup(kind) { const { ctx } = API; if (!ctx.dbfData || !ctx.currentSourcePath) return alert("열린 프로젝트가 없음"); API.recompute(); const payload = global.STMATE_VIEW_PRJ_BOQ.buildSavePayload(ctx); API.decorateSavePayload(payload); const base = String(payload.target_folder_name || "stmate").slice(0, 20); payload.target_folder_name = `${base}_백업_${kind === "day" ? today() : nowTag()}`; try { const res = await API.postJson("/api/save", payload); if (kind === "day") prefs.lastBackDay = today(); if (kind === "30") prefs.lastBack30 = Date.now(); savePrefs(); return res; } catch (err) { alert("자동백업 실패: " + err.message); return null; } } // 재계산 뒤 30분 백업 async function afterCalc() { if (prefs.autoBack30 && Date.now() - (prefs.lastBack30 || 0) >= 30 * 60 * 1000) await backup("30"); } function applyView() { const shell = document.getElementById("stmate-shell"); if (shell) shell.setAttribute("data-screen-mode", String(prefs.screenMode)); const ws = document.getElementById("mdi-workspace"); if (ws) ws.style.zoom = prefs.scale === 100 ? "" : `${prefs.scale}%`; document.documentElement.style.setProperty("--stm-name-w", `${prefs.dflWidth * 7}px`); } function quit(withBackup) { const finish = () => { API.ctx.uiState.activeScreen = "wQuit"; API.renderWorkspace(); }; if (withBackup && prefs.autoBackDaily && prefs.lastBackDay !== today()) return backup("day").then(finish); finish(); } const toggle = (k) => () => ((prefs[k] = !prefs[k]), savePrefs(), render()); const radio = (k, v) => () => ( (prefs[k] = v), savePrefs(), applyView(), render(), API.renderWorkspace() ); const H = { Act_Maneger: () => go("wPrjMgr"), Act_Sys: () => go("wM_Set_Sys"), Act_Dan: () => go("wM_JabPum"), Act_iLWi_San: () => go("wM_Edit_iLWi"), Act_PrjLst: () => go("wPrjList"), Act_Prt: () => go("PM_Prt"), Act_Calc_Grass: async () => (await API.saveAll(), afterCalc()), Act_Calc_All: () => (API.cascadeAndRender(), afterCalc()), Act_Refresh: () => API.ctx.currentSourcePath && API.loadProjectFromServer(API.ctx.currentSourcePath), Act_Arc_Project: () => API.saveAll(), Act_Close_Table: () => { Object.assign(API.ctx, { state: API.Calc.createDefaultState(), dbfData: null, currentSourcePath: "", }); go("wPrjMgr"); }, mi_AutoBack: toggle("autoBackDaily"), mi_Auto_Time_Back: toggle("autoBack30"), Act_Arc_Now: async () => { const r = await backup("now"); if (r) alert(`현재상태 백업 완료\n• 폴더: ${r.saved_dir}\n• 압축: ${r.saved_stc}`); }, mi_Del_Recent: () => ((prefs.recent = []), savePrefs(), render()), Act_Open_Recent: (btn) => API.loadProjectFromServer(btn.getAttribute("data-path"), "wPrjList"), Act_Quit: () => quit(true), Act_Halt: () => quit(false), Act_UseName2: toggle("useName2"), N44: () => ((prefs.scale = 100), (prefs.screenMode = 0), savePrefs(), applyView(), render()), N55: () => { Object.assign(API.ctx.uiState, { boqLevelFilter: "ALL", boqSearch: "", boqLevelMin: 0, boqSel: [], }); API.renderWorkspace(); }, Act_information: () => { const c = API.ctx; const files = (c.dbfData && c.dbfData.file_list) || []; API.openModal( "제품 및 기타정보", `

STMATE w 8.0 재현판 — 원본 메뉴·화면 체계를 웹으로 다시 만든 시험판

열린 프로젝트: ${M.esc(c.currentSourcePath || "(없음)")}

자료 파일 ${files.length}개: ${M.esc(files.join(" · "))}

설정 저장: 브라우저(localStorage) · 자동백업 폴더: 서버 work_dbf

`, ); }, Act_SetScreen: () => radio("screenMode", (prefs.screenMode + 1) % 4)(), Act_Scale_Dfl: () => radio("scale", 100)(), Act_Scale_Up: () => radio("scale", Math.min(200, prefs.scale + 25))(), Act_Scale_Dn: () => radio("scale", Math.max(50, prefs.scale - 25))(), }; [0, 1, 2, 3].forEach((i) => (H[`Act_SetScreen_${i}`] = () => radio("screenMode", i)())); ["C", "N", "I", "E"].forEach((k) => (H[`Act_DFL_INDX_${k}`] = () => radio("dflIndex", k)())); [20, 30, 40, 50, 60].forEach((w) => (H[`Act_DFL_WIDTH_${w}`] = () => radio("dflWidth", w)())); const enabled = (act) => Boolean(PLUG().actions[act] || H[act]); function run(act, btn) { const p = PLUG().actions[act]; if (p) return p(API.ctx, API, btn); if (H[act]) return H[act](btn); } // 메뉴·툴바 다시 그림 — 끼워 넣은 메뉴(m.menu = m1~m5)는 해당 메뉴 끝에 function render() { const host = document.getElementById("menu-host"); if (host) host.innerHTML = M.renderMenuBar({ enabled, checked: (n) => (n.check ? Boolean(prefs[n.check]) : prefs[n.radio[0]] === n.radio[1]), recent: prefs.recent, }); PLUG().menus.forEach((m) => { const box = document.querySelector(`#main-menubar [data-menu="${m.menu}"] > .menu-dropdown`); if (box && !box.querySelector(`[data-act="${m.act}"]`)) box.insertAdjacentHTML("beforeend", ``); }); const tb = document.getElementById("main-toolbar"); if (tb) tb.innerHTML = M.renderToolbar(API.ctx.uiState.activeScreen); } function noteRecent(path) { const src = (API.ctx.sourcesData.projects || []).find((p) => p.path === path); const name = src ? src.name : path.split(/[\\/]/).pop(); prefs.recent = [{ path, name }].concat(prefs.recent.filter((r) => r.path !== path)).slice(0, 8); savePrefs(); render(); } const typing = (el) => el && /^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName); function install(api) { API = api; const scMap = M.shortcutMap(); render(); applyView(); document.getElementById("main-menubar").addEventListener("click", (e) => { const btn = e.target.closest("[data-act]"); if (!btn || btn.disabled) return; run(btn.getAttribute("data-act"), btn); }); document.getElementById("main-toolbar").addEventListener("click", (e) => { const btn = e.target.closest("[data-tb-act]"); if (btn) run(btn.getAttribute("data-tb-act"), btn); }); document.addEventListener("keydown", (e) => { const act = scMap[M.eventShortcut(e)]; if (!act || !enabled(act)) return; if (typing(document.activeElement) && !/^F\d+$/.test(e.key) && e.key !== "q") return; e.preventDefault(); run(act); }); Object.assign(api, { noteRecent, refreshMenus: render, uiPrefs: prefs, backup }); } global.STMATE_UI_APP_MENU = { install, prefs, H, enabled }; })(window);