Merge remote-tracking branch 'origin/dev' into sub_laptop_8

This commit is contained in:
2026-10-05 00:33:56 +09:00
4 changed files with 109 additions and 2 deletions
@@ -130,8 +130,8 @@ export function createMain(
const title = el("h2", { className: "m02-main__title", text: L("M02_PickTemplate") });
const reload = createButton({ label: L("M02_Reload"), variant: "ghost" });
const save = createButton({ label: L("M02_Save"), variant: "filled" });
// 구조물 도면 · 면적산출 상세 화면 → 구조물집계표(그 열 고른 채)
const back = createButton({ label: L("M02_Back"), variant: "ghost", size: "sm" });
// 구조물 도면 · 일위대가 · 면적산출 상세 화면 → 구조물집계표(그 열 고른 채) · 일반 단추(43-5 · 알약 · ghost 아님)
const back = createButton({ label: L("M02_Back") });
const slot = el("div", { className: "m02-main__slot" }); // 도면 양식의 작도 영역 칸이 들어옴
const notice = el("div", { className: "m02-main__notice", attrs: { hidden: "" } });
const host = el("div", { className: "m02-main__host" });
@@ -8,3 +8,7 @@
4. 구조물조합에서 일위대가 미리보기에서 일위대가 상세화면의 단가산출 로직(순서와 로직추가는 제외)과 자재목록을 그대로 보여주게 하되 기능은 유지(수량산출 연결창, 옵션지정 모달창)하고 수평스크롤을 가능하게 하기
5. 일위대가의 재료도 2열구조여야 할것같은데....
6. 도면정보, 일위대가, 면적산출을 접근 후 제목의 돌아가기 버튼은 알약이 아니라 일반 버튼으로
## 추가 (10-5 새벽 · 원문 그대로)
추가 항목이 하나더 있어. 구조물 조합에서의 미리보고 3개 컨테이너에 새로만들기와 복사 생성로직이 필요없겠네. 구조물 조합의 새항목 생성을 하면 당연히 새로운 항목들이 자동으로 생성되잖아. 그후 가져오기를 하면 당연히 값을 가져와서 덮어쓰면 되겠어. 그리고 일위대가와 면적산출에서 가져오기를 하면 도면은 개별로 작성되는게 맞지만 일위대가와 면적산출은 연결되어 있는정보니 같이 가져오는게 맞겠어. 알약연결 포함해서. 이 내용도 추가해줘.
@@ -238,3 +238,10 @@ def test_상세_화면_돌아가기는_집계표로_그_열_고른_채():
body = main.split('back.addEventListener("click"')[1]
assert "confirmLeave()" in body and "STRUCTURE_TABLE" in body
assert "sheet?.selectColumn(column)" in body
def test_상세_머리_돌아가기는_일반_단추():
"""43-5 — 도면 · 일위대가 · 면적산출 상세 머리 [돌아가기] = 공용 기본 단추(filled · 같은 높이 ·
알약 · ghost · sm 아님) · 대비는 ui_checks/43-5 시나리오(어두움 8.01)."""
main = _text(MAIN)
assert 'const back = createButton({ label: L("M02_Back") });' in main
@@ -0,0 +1,96 @@
/* 43-5 · E1 — 도면 · 일위대가 · 면적산출 상세 머리 [돌아가기] 꼴 · 머리 단추 글자 대비(밝음 · 어두움)
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌)
* 기대 수치:
* - 세 상세 모두 back.cls = "ui-btn ui-btn--filled"(ghost · sm 없음) · back.h = 머리 다른 단추 높이(±1px)
* - 밝음 · 어두움(앱 ☾ 단추) 모두 머리 보이는 단추 대비 ≥ 4.5
* - 끝나면 처음 테마로 되돌림(theme.end = theme.start)
* ⚠ 단추 color 전환(120ms)이 ORCA 탭에선 0ms 에 멈춰 옛 색이 읽힘(1.11 착오 · E1) — 재는 동안 전환을 끔
*/
(async () => {
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const rgb = (s) => (s.match(/[\d.]+/g) || []).map(Number);
const lum = ([r, g, b]) => {
const f = (v) => ((v /= 255) <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
};
/** 실제로 보이는 바탕 — 투명이면 조상으로 */
const bgOf = (el) => {
for (let e = el; e; e = e.parentElement) {
const c = rgb(getComputedStyle(e).backgroundColor);
if (c.length >= 3 && (c.length < 4 || c[3] > 0.5)) return c.slice(0, 3);
}
return [255, 255, 255];
};
const ratio = (el) => {
const a = lum(rgb(getComputedStyle(el).color).slice(0, 3));
const b = lum(bgOf(el));
return +((Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05)).toFixed(2);
};
const theme = () => document.documentElement.getAttribute("data-theme") || "auto";
const moon = () =>
[...document.querySelectorAll("button")].find((b) => /^[☾☀]$/.test(b.textContent.trim()));
const headButtons = () =>
[...document.querySelectorAll(".m02-main__head button")].filter(
(b) => !b.hidden && b.offsetParent,
);
const openRow = async (sectionTitle, prefix) => {
const sec = [...document.querySelectorAll(".m02-side__summary > section")].find(
(s) => s.querySelector("h3").textContent.trim() === sectionTitle,
);
sec.classList.remove("is-collapsed");
[...sec.querySelectorAll("button")].find((b) => b.textContent.startsWith(prefix)).click();
await w(5000);
};
const measure = () => {
const back = [...document.querySelectorAll(".m02-main__head button")].find(
(b) => b.textContent.trim() === "돌아가기",
);
const others = headButtons().filter((b) => b !== back);
return {
title: document.querySelector(".m02-main__title")?.textContent,
back: back && {
cls: back.className,
h: Math.round(back.getBoundingClientRect().height),
radius: getComputedStyle(back).borderRadius,
},
otherH: others.map((b) => Math.round(b.getBoundingClientRect().height)),
contrast: Object.fromEntries(
headButtons().map((b) => [b.textContent.trim() || b.title, ratio(b)]),
),
};
};
const still = document.createElement("style");
still.textContent = "*,*::before,*::after{transition:none!important}";
document.head.append(still);
const start = theme();
const out = { theme: { start }, pages: {} };
for (const [name, sec, prefix] of [
["도면", "구조물 도면", "SD-000001"],
["일위대가", "일위대가", "SU-000001"],
["면적산출", "면적산출", "SA-000001"],
]) {
await openRow(sec, prefix);
const light = measure();
moon()?.click();
await w(400);
const dark = { theme: theme(), ...measure() };
moon()?.click();
await w(400);
out.pages[name] = { light, dark };
}
if (theme() !== start) moon()?.click();
out.theme.end = theme();
still.remove();
const all = Object.values(out.pages).flatMap((p) => [
...Object.values(p.light.contrast),
...Object.values(p.dark.contrast),
]);
out.minContrast = Math.min(...all);
out.backOk = Object.values(out.pages).every(
(p) =>
p.light.back?.cls === "ui-btn ui-btn--filled" &&
p.light.otherH.every((h) => Math.abs(h - p.light.back.h) <= 1),
);
return JSON.stringify(out);
})();