diff --git a/resources/tester/helper_est_native.cjs b/resources/tester/helper_est_native.cjs
index a389e3db4..3d1339ebf 100644
--- a/resources/tester/helper_est_native.cjs
+++ b/resources/tester/helper_est_native.cjs
@@ -41,6 +41,7 @@ for (const file of [
"native-work-mode.js",
"native-dongwon-check.js",
"native-excel-files.js",
+ "native-option-all.js",
"native-business-actions.js",
"native-forms.js",
"native-excel-to-est.js",
diff --git a/resources/tester/test_est_option_all.py b/resources/tester/test_est_option_all.py
new file mode 100644
index 000000000..8468a7e2c
--- /dev/null
+++ b/resources/tester/test_est_option_all.py
@@ -0,0 +1,26 @@
+"""환경설정 전체: 읽기 기본값·바뀐 칸만 쓰기·옵션 자리(계산·표시·기성·Env)."""
+
+from test_est_rea_parity import run
+
+
+def test_write_only_changed_fields_to_their_places():
+ result = run(
+ "math",
+ """(()=>{
+ const A=EstOptionAll, item=(k)=>A.TABS.flatMap(t=>t[1]).find(i=>i.k===k);
+ const o={HeadDigits:2,VerFormat:true,CostValueTrunc:false,AutoCalc:true};
+ const r0={head:A.read(o,item('headDigits')),fmt:A.read(o,item('format')),code:A.read(o,item('code')),
+ total:A.read(o,item('totalName'))};
+ A.write(o,{headDigits:1,format:1,code:true,level:true,jabbi:true,costMethod:1,recalc:2,twoLine:1});
+ return {r0,o};
+ })()""",
+ )
+ assert result["r0"] == {"head": 1, "fmt": 0, "code": False, "total": "순공사비"}
+ o = result["o"]
+ assert o["HeadDigits"] == 2 # 1 → 1 그대로(바뀌지 않음)
+ assert o["VerFormat"] is False
+ assert o["CostValueTrunc"] is True
+ assert o["OldRecalcMethod"] == 2
+ assert o["Display"] == {"code": True, "twoLine": True}
+ assert o["AccomOption"] == {"level": True}
+ assert o["Env"] == {"jabbi": True}
diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md
index 974633e6a..95815d716 100644
--- a/tools/est-test/PLAN.md
+++ b/tools/est-test/PLAN.md
@@ -510,6 +510,8 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD
- 검증완료: 확인 시험 1개·동원 관련 시험 35개 통과. ORCA 브라우저에서 일위 8건 모두 일치·당초값=계산값, 항목 3개 표시.
- [x] 엑셀물량 파일목록(TfrmExcelMullang btnNewProject·btnDelete · spec_pa §7) — 공사에 딸린 엑셀 물량 파일을 작업에 담음(Index·파일명). 추가 = 파일 골라 목록에(같은 이름 거부 — 원본은 엑셀에 띄우기만, 웹은 엑셀이 없어 여기서 등록) · 삭제 = 확인 뒤 목록·담은 파일 지움(셀 연결은 남음) · 열기 = 내려받기. 셀 수량 연결 창에서 목록 파일을 골라 쓰고, 새 파일로 연결하면 목록에 들어감. ESTX 저장은 JSON 안내.
- 검증완료: 파일목록 시험 1개·수량산출 시험 6개 통과. ORCA 브라우저에서 목록 등록 → 목록 파일로 B2 셀 연결(수량 12.5)·연결 수 1 → 삭제 확인, 원복.
+ - [x] 환경설정 전체 창(TfrmOption btnLoad · spec_pb2 §9) — 탭 다섯(공통적용·내역서·일위단산기계·표준자재원가·설계변경수량심사) 92칸. 웹 계산·표시에 이어진 칸은 그 옵션에(일위 자리수·단산 금액란·중간 자리수·외화 절하·재료비 처리·이윤 사사오입·관급 조달수수료·원가 방향·자동계산·재계산 범위·정산금액·비목순서·정렬·두줄·Enter·코드 열·0 표시·바탕색·레벨·발췌근거·집계 진하게), 나머지는 값만(Env), PC 설정은 이 브라우저. 바꾼 칸만 씀. 적용 때 원본 알림(자동계산 끔/켬·표준단가비목·조달청 계산방식·산식 표시·공종+품명 합치기)과 순공사비 명칭 바꾸면 순공사비 행 이름도 바꿈. 기성 화면에서는 기성 환경설정. 순공사비 상단/하단 이동·제잡비 행 넣기는 값만(웹 계산이 순공사비 위치를 아래로 가정).
+ - 검증완료: 환경설정 시험 1개·표시·기성 시험 4개 통과. ORCA 브라우저에서 탭 5·칸 92, 코드표시 켬 → 자재 표에 코드 열, 순공사비 명칭 → 행 이름 바뀜, 원복.
- [x] 다른 공사 불러오기 창(원본 barbtnSelfLoad → TfrmOtherLoad, 찾기 popSerch) — 저장된 다른 작업을 골라 같은 표를 띄우고 찾기(품명·규격, A&B 둘 다)·고른 줄을 하위 자료와 함께 가져오기(내역서는 고른 줄 아래에 줄, 나머지는 자료·호표 합침, 같은 코드는 이 공사 것). 검증완료: 바인딩 통과, ORCA 브라우저에서 산불진화임도 작업의 일위대가 3개 → 「취수장」 1개 → 가져오기 호표 8→9·하위 41건, 샘플 복구.
- [x] 사용위치 검색 창(원본 TfrmUsesDataSerch btnSerch·btnAllDisplay) — 현재 표 항목을 품명·규격(빈칸·|·괄호 무시)으로 거르고 골라 사용위치 보기. 검증완료: 바인딩 통과, ORCA 브라우저에서 노임 9개 → 「보 통」 1개 → 보통인부 사용위치 11곳.
- [x] 인쇄양식 불러오기·저장하기(원본 mnuPrintFormatLoad·Save 0x10ed318·0x10ed000 — spec_pc §3) — .pfot(인쇄정보표 MIDAS 이진) 읽기·쓰기 `print-format.cjs`(설명 BLOB·빈 칸 표지까지 보존, 원본 파일 그대로 다시 쓰면 바이트 같음). 불러오면 공사 인쇄표를 그 값으로, 저장은 지금 인쇄표를 원본 설치 main.pfot 필드 설명자 위에(`EST_PFOT_TEMPLATE`). .gfot(cxGrid 열 배치)는 웹 격자와 호환되지 않아 하지 않음.
diff --git a/tools/est-test/index.html b/tools/est-test/index.html
index 40cd07137..828186127 100644
--- a/tools/est-test/index.html
+++ b/tools/est-test/index.html
@@ -29,6 +29,7 @@
+
diff --git a/tools/est-test/native-actions-more.js b/tools/est-test/native-actions-more.js
index 30ca6eaf2..d7e50de69 100644
--- a/tools/est-test/native-actions-more.js
+++ b/tools/est-test/native-actions-more.js
@@ -14,6 +14,7 @@ const EstActionsMore = (() => {
typeof EstWorkMode !== "undefined" && EstWorkMode,
typeof EstDongwonCheck !== "undefined" && EstDongwonCheck,
typeof EstExcelFiles !== "undefined" && EstExcelFiles,
+ typeof EstOptionAll !== "undefined" && EstOptionAll,
].filter(Boolean);
function handle(name, state, node) {
for (const module of modules()) if (module.handle(name, state, node)) return true;
diff --git a/tools/est-test/native-actions.js b/tools/est-test/native-actions.js
index 3dae41fab..0198a9554 100644
--- a/tools/est-test/native-actions.js
+++ b/tools/est-test/native-actions.js
@@ -334,7 +334,9 @@ const EstActions = (() => {
`
${result.checked}개 중 ${result.passed}개 일치 · 불일치 ${result.failed}개
보관된 EST 샘플의 저장값과 계산 결과를 비교했습니다. 원본 실행 비교는 포함하지 않습니다.
${result.errors.length ? `${esc(result.errors.join(" / "))}
` : ""}| 영역 | 검사 수 | 일치 |
${[...new Set(result.results.map((r) => r.group))].map((group) => `| ${esc(group)} | ${result.results.filter((r) => r.group === group).length} | ${result.results.filter((r) => r.group === group && r.pass).length} |
`).join("")}
`,
);
} else if (name === "settings")
- EstApp.state.view === "progress" ? EstProgressView.dialog(EstApp.state) : EstSettings.show();
+ EstApp.state.view === "progress"
+ ? EstProgressView.dialog(EstApp.state)
+ : EstOptionAll.dialog(EstApp.state);
else if (name === "raw-line") {
const row = activeRows()[Number(node.dataset.row)];
showDialog(
diff --git a/tools/est-test/native-display.js b/tools/est-test/native-display.js
index 916fb1a20..07103b5a7 100644
--- a/tools/est-test/native-display.js
+++ b/tools/est-test/native-display.js
@@ -22,6 +22,11 @@ const EstDisplay = (() => {
function columns(options, cols) {
const d = get(options);
let out = d.gubun ? cols : cols.filter((k) => k !== "ValueIndex");
+ // 코드표시(CommonCheckCode): 켜면 품명 앞에 코드 열, 끄면 뺌(웹 표는 처음에 코드 열이 없음)
+ if (d.code === false) out = out.filter((k) => k !== "Code");
+ else if (d.code === true && !out.includes("Code") && out.includes("Name"))
+ out = out.flatMap((k) => (k === "Name" ? ["Code", k] : [k]));
+ if (options?.AccomOption?.level === false) out = out.filter((k) => k !== "Level"); // 레벨 표시 끔
const order = ORDERS[d.order];
if (!order || d.order === 0) return out;
for (const suffix of ["C", "U"]) {
diff --git a/tools/est-test/native-option-all.js b/tools/est-test/native-option-all.js
new file mode 100644
index 000000000..ce2ca59c0
--- /dev/null
+++ b/tools/est-test/native-option-all.js
@@ -0,0 +1,282 @@
+// 원본 환경설정 TfrmOption 「적 용」(btnLoad 0xd034f8 → FormClose 후처리 d03d20 — spec_pb2 §9): 탭별 칸 전부.
+// 웹 계산·표시에 이어진 칸은 그 옵션(HeadDigits·CostValueTrunc·MidCostDiget·DollarCostCut·GCToWonga·EYunUp·
+// GangubExceptJodalRatio·VerFormat·AutoCalc·OldRecalcMethod·JungSangShow·Display·AccomOption)에 쓰고, 나머지는 계산 환경 Env 에
+// 값만 담는다. PC 설정(INI: 자동저장 분·백업·새이름 공사명·불러오기창·찾을문자크기·금액 변경확인·자재 틀고정)은 이 브라우저에.
+// 적용 때 원본 알림: 자동계산 끔/켬 · 표준단가비목 · 조달청 계산방식 · 산식 표시 · 공종+품명 합치기 · 순공사비 명칭(행 이름 바꿈).
+const EstOptionAll = (() => {
+ const num = (v) => Number(v) || 0;
+ const PC = "est-pc-options";
+ const yes = (k, label, at, def = false) => ({ k, label, kind: "check", at, def });
+ const pick = (k, label, choices, at, def = 0) => ({ k, label, kind: "pick", choices, at, def });
+ const int = (k, label, min, max, at, def = 0) => ({ k, label, kind: "int", min, max, at, def });
+ const text = (k, label, at, def = "") => ({ k, label, kind: "text", at, def });
+ const ORDER = [
+ "합/노/재/경",
+ "합/재/노/경",
+ "노/재/경/합",
+ "재/노/경/합",
+ "합/경/재/노",
+ "경/재/노/합",
+ ];
+ const GUNGU = [
+ "일위대가",
+ "단가산출",
+ "기계경비",
+ "표준단가",
+ "견적단가",
+ "자재단가",
+ "노임단가",
+ ];
+ // at: "opt:키"(계산 옵션) · "disp:키"(Display) · "accom:키"(AccomOption) · "pc"(이 브라우저) · 없음 = Env
+ const TABS = [
+ [
+ "공통적용",
+ [
+ pick("order", "비목순서", ORDER, "disp:order"),
+ pick("align", "품명정렬", ["좌정렬", "우정렬", "중앙정렬"], "disp:align"),
+ pick("twoLine", "품명표기", ["한줄로 표시", "두줄로 표시"], "disp:twoLine"),
+ pick("enter", "Enter 후 셀 이동", ["이동안함", "좌", "우", "위", "아래"], "disp:enter"),
+ yes("code", "코드표시", "disp:code"),
+ yes("gnZero", '수량 "0" 표시', "disp:gnZero", true),
+ yes("costZero", '금액 "0" 표시', "disp:costZero", true),
+ int("gnDigits", "수량 표시 자리수", 0, 6, null, 3),
+ int("nachalDigits", "낙찰율 표시 자리수", 0, 6, null, 2),
+ yes("nachalShow", "낙찰율 표시"),
+ yes("haljungShow", "할증율 표시"),
+ yes("siljukBimog", "표준단가비목표시"),
+ yes("autoSave", "데이타 자동저장", "disp:autoSave"),
+ int("saveTime", "자동저장 분", 5, 30, "pc", 10),
+ yes("siljukSearch", "표준단가 동시검색"),
+ yes("numSort", "공종일련번호 자동정렬"),
+ yes("jodalMethod", "조달청 계산방식적용"),
+ yes("nameMatch", "명칭 불일치 검색"),
+ yes("valueCompare", "신,구 단가비교 보기"),
+ int("searchLength", "찾을문자크기", 1, 20, "pc", 2),
+ text("color", "바탕색(#rrggbb)", "disp:color"),
+ yes("backup", "백업 저장", "pc"),
+ yes("projectAutoChange", "새이름 저장 시 공사명 변경", "pc"),
+ yes("searchFormAuto", "불러오기창 위치 기억", "pc"),
+ ],
+ ],
+ [
+ "내역서",
+ [
+ pick("nameShow", "공종 표시", ["공종=(공종+품명)", "별도"], null, 1),
+ yes("gungu", "비고란에 발췌근거 표시", "accom:gungu"),
+ yes("level", "레벨 표시", "accom:level"),
+ yes("projectTop", "공사명 설계서 첫줄에 표시"),
+ yes("titleTopPrint", "공사명 총괄표 첫줄에 인쇄"),
+ yes("jabbi", "내역서에 제잡비 표시"),
+ yes("sumBold", "집계공종 진하게", "accom:sumBold"),
+ yes("nachalGubun", "낙찰율 노,재,경 구분 표시"),
+ pick("totalPos", "순공사비 계산 위치", ["하단", "상단"]),
+ text("totalName", "순공사비 명칭", null, "순공사비"),
+ yes("firstTotal", "새공사 시작시 순공사비 자동생성", null, true),
+ pick("horCalc", "데이타(가로) 계산방법", ["절하", "절상"], "accom:horCalc"),
+ yes("calcCheck", "가로,세로 계산 확인", "accom:calcCheck"),
+ yes("calcAuto", "내역서 자동계산", "opt:AutoCalc", true),
+ yes("costCompareMsg", "재계산후 공사금액 변경확인", "pc"),
+ ...GUNGU.map((g, i) => yes(`gungu${i}`, `발췌근거 표시 — ${g}`)),
+ ],
+ ],
+ [
+ "일위·단산·기계",
+ [
+ pick("headDigits", "일위대가 금액 계산 자리수", ["원단위", "소수1위"], "opt:HeadDigits"),
+ pick("headHorCalc", "일위대가 데이타(가로) 계산방법", ["절하", "절상"]),
+ yes("headBigo", "일위대가 비고란 발췌근거"),
+ yes("headInfo", "일위대가 공종 정보창"),
+ pick("costMethod", "단가산출 금액란", ["소수1위", "절하"], "opt:CostValueTrunc"),
+ int("midCost", "단가산출 중간계산 결과값 자리수", 0, 6, "opt:MidCostDiget", 2),
+ yes("sansic", "수식을 산식으로 변환 표시"),
+ yes("susicCheck", "수식오류 검색"),
+ yes("linkValueEdit", "연결 항목 편집허용"),
+ yes("costShowToHead", "공종 라인 총단가"),
+ yes("costInfo", "단가산출 정보창"),
+ yes("dollarCut", "외화장비 손료 1,000단위 미만 절하", "opt:DollarCostCut"),
+ yes("machInfo", "기계경비 정보창"),
+ ],
+ ],
+ [
+ "표준·자재·원가",
+ [
+ pick("remainBimog", "노무비율계산후 잔여단가 적용비목", ["재료비", "경비"]),
+ yes("siljukCode", "표준단가 비고란 코드"),
+ yes("stuffcGungu", "자재 비고란 발췌근거"),
+ yes("stuffcFixed", "자재 품명,규격 틀고정", "pc"),
+ pick("gcTo", "순직접공사비의 재료비", ["간접재료비", "직접재료비"], "opt:GCToWonga"),
+ yes("eyun", "이윤 비율 사사오입", "opt:EYunUp"),
+ yes("gangub", "안전관리비 관급자재대 조달수수료 제외", "opt:GangubExceptJodalRatio"),
+ yes("digetOldCost", "자리수 조정후 수식에 원금액 표시"),
+ yes("jungsan", "정산금액 표시", "opt:JungSangShow"),
+ pick("format", "원가계산서 양식 방향", ["세로", "가로"], "opt:VerFormat"),
+ yes("selectPrint", "원가 선택항목만 인쇄"),
+ ],
+ ],
+ [
+ "설계변경·수량·심사",
+ [
+ pick("changeOrder", "상단·하단", ["상단=당초 · 하단=변경", "상단=변경 · 하단=당초"]),
+ text("upText", "상단 표시문자", null, "당초"),
+ text("downText", "하단 표시문자", null, "변경"),
+ yes("upBold", "상단 굵게 인쇄"),
+ yes("downBold", "하단 굵게 인쇄"),
+ pick("nameShowMethod", "공종,품명,규격", ["변경만", "당초,변경 모두", "다르면 둘다"]),
+ pick("gunguShowMethod", "근거자료", ["신규만", "전부"]),
+ yes("oldCostShow", "신규공종 당초단가 표시"),
+ yes("nachalAuto", "신규공종 낙찰율 자동계산"),
+ yes("nachalToBigo", "비고란에 낙찰율 표시"),
+ pick("recalc", "재계산", ["신규만", "변경단가만", "전체"], "opt:OldRecalcMethod"),
+ yes("changeZero", "변경금액만 0 표시"),
+ yes("cellMerge", "증감 셀 병합"),
+ yes("mnsShow", "감 부호(-) 표시"),
+ yes("changeBigo", "변경 비고란 표시"),
+ yes("changeCalcAuto", "변경 내역서 자동계산", null, true),
+ pick("mulCalc", "수량산출 계산결과값", ["사사오입", "절하", "절상"]),
+ int("mulDigits", "수량산출 소수이하 자리수", 0, 6, null, 3),
+ yes("mulSansic", "수량산출 산식 변환"),
+ yes("mulColor", "도면칼라 인쇄"),
+ pick("investPrint", "계약심사 인쇄방법", ["내역서 양식", "설계변경 양식"]),
+ pick("investItems", "계약심사 인쇄항목", ["전부", "변경만"]),
+ ],
+ ],
+ ];
+ const ALL = TABS.flatMap(([, items]) => items);
+
+ const pc = () => JSON.parse(localStorage.getItem(PC) || "{}");
+ // 지금 값 읽기
+ function read(options, item) {
+ const [where, key] = String(item.at || "").split(":");
+ let v;
+ if (where === "opt") v = options[key];
+ else if (where === "disp") v = options.Display?.[key];
+ else if (where === "accom") v = options.AccomOption?.[key];
+ else if (where === "pc") v = typeof localStorage !== "undefined" ? pc()[item.k] : undefined;
+ else v = options.Env?.[item.k];
+ if (item.k === "format") return v === false ? 1 : 0; // VerFormat true = 세로
+ if (item.k === "headDigits") return num(v) > 0 ? 1 : 0;
+ if (item.k === "twoLine") return v ? 1 : 0;
+ return v ?? item.def;
+ }
+ // 고친 값 → 옵션 쓰기(project.options 를 직접 고침). pcOut 은 이 브라우저 값
+ function write(options, values, pcOut = {}) {
+ for (const item of ALL) {
+ if (!(item.k in values)) continue;
+ let v = values[item.k];
+ // 바꾼 칸만 씀(웹 고유 값 — 예: 일위 자리수 2 — 을 그대로 두게)
+ const now = read(options, item);
+ if (
+ item.kind === "check"
+ ? Boolean(now) === Boolean(v)
+ : item.kind === "text"
+ ? String(now ?? "") === String(v)
+ : num(now) === num(v)
+ )
+ continue;
+ if (item.kind === "check") v = Boolean(v);
+ else if (item.kind !== "text") v = num(v);
+ if (item.k === "format") v = v === 0;
+ if (item.k === "twoLine") v = v === 1;
+ if (["costMethod", "gcTo"].includes(item.k)) v = v === 1;
+ const [where, key] = String(item.at || "").split(":");
+ if (where === "opt") options[key] = v;
+ else if (where === "disp") options.Display = { ...(options.Display || {}), [key]: v };
+ else if (where === "accom")
+ options.AccomOption = { ...(options.AccomOption || {}), [key]: v };
+ else if (where === "pc") pcOut[item.k] = v;
+ else options.Env = { ...(options.Env || {}), [item.k]: v };
+ }
+ return pcOut;
+ }
+
+ let tab = 0;
+ function dialog(state) {
+ const { esc } = EstApp,
+ o = state.project.options;
+ const field = (item) => {
+ const v = read(o, item);
+ if (item.kind === "check")
+ return ``;
+ if (item.kind === "pick")
+ return ``;
+ if (item.kind === "int")
+ return ``;
+ return ``;
+ };
+ EstApp.showDialog(
+ "환경설정",
+ `${TABS.map(([name], i) => ``).join("")}
${TABS.map(([, items], i) => `${items.map(field).join("")}
`).join("")}
`,
+ );
+ }
+ function collect() {
+ const values = {};
+ for (const node of document.querySelectorAll("#dialog-content [data-oa]"))
+ values[node.dataset.oa] = node.type === "checkbox" ? node.checked : node.value;
+ for (const item of ALL)
+ if (item.kind === "int") {
+ const v = Number(values[item.k]);
+ if (!Number.isInteger(v) || v < item.min || v > item.max)
+ throw Error(`${item.label}은(는) ${item.min}~${item.max} 정수입니다.`);
+ }
+ if (values.color && !/^#[0-9a-f]{6}$/i.test(values.color))
+ throw Error("바탕색은 #rrggbb 꼴입니다.");
+ return values;
+ }
+ // 적용(d03d20 후처리): 알림과 자료 반영
+ function apply(state) {
+ const values = collect(),
+ before = state.project.options,
+ notes = [];
+ const was = (k) =>
+ read(
+ before,
+ ALL.find((i) => i.k === k),
+ );
+ if (was("calcAuto") && !values.calcAuto && !confirm("내역서의 모든 계산기능이 해제됩니다."))
+ values.calcAuto = true;
+ if (!was("calcAuto") && values.calcAuto) notes.push("내역서를 재계산 시켜주세요.");
+ if (Boolean(was("siljukBimog")) !== values.siljukBimog)
+ notes.push("반드시 재계산을 시켜야 금액이 정상적으로 표시됩니다.");
+ if (Boolean(was("jodalMethod")) !== values.jodalMethod) notes.push("전체 재계산을 해주세요.");
+ if (Boolean(was("sansic")) !== values.sansic) notes.push("일건서류 전체를 재계산 합니다.");
+ if (
+ num(was("nameShow")) === 1 &&
+ num(values.nameShow) === 0 &&
+ !confirm("공종과 품명을 합치면 되돌릴수 없습니다.")
+ )
+ values.nameShow = 1;
+ const rename = String(values.totalName ?? "").trim() && values.totalName !== was("totalName");
+ let pcOut;
+ EstApp.change((project) => {
+ pcOut = write(project.options, values);
+ if (rename)
+ for (const row of project.tables.EstMainDat)
+ if (num(row.LinkIndex) === 54) row.Name = values.totalName.trim();
+ });
+ localStorage.setItem(PC, JSON.stringify({ ...pc(), ...pcOut }));
+ EstApp.byId("dialog").close();
+ state.notice = notes.join(" ") || "환경설정을 적용했습니다.";
+ EstApp.render();
+ }
+
+ function handle(name, state, node) {
+ if (name === "option-all") dialog(state);
+ else if (name === "option-all-tab") {
+ tab = Number(node.dataset.tab);
+ for (const box of document.querySelectorAll("#dialog-content [data-oa-tab]"))
+ box.hidden = Number(box.dataset.oaTab) !== tab;
+ for (const b of document.querySelectorAll('[data-action="option-all-tab"]'))
+ b.setAttribute("aria-pressed", String(Number(b.dataset.tab) === tab));
+ } else if (name === "option-all-apply")
+ try {
+ apply(state);
+ } catch (error) {
+ EstApp.byId("option-all-error").textContent = error.message;
+ }
+ else if (name === "option-all-cancel") EstApp.byId("dialog").close();
+ else if (name === "option-all-web") EstSettings.show();
+ else return false;
+ return true;
+ }
+ return { TABS, read, write, handle, dialog };
+})();
diff --git a/tools/est-test/server.cjs b/tools/est-test/server.cjs
index 80503723f..d0796aff7 100644
--- a/tools/est-test/server.cjs
+++ b/tools/est-test/server.cjs
@@ -113,6 +113,7 @@ const allowed = new Set([
"native-work-mode.js",
"native-dongwon-check.js",
"native-excel-files.js",
+ "native-option-all.js",
"native-actions-more.js",
"native-wonga-tools.js",
"native-estimate-print.js",