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

This commit is contained in:
2026-10-06 00:12:30 +09:00
8 changed files with 213 additions and 6 deletions
@@ -0,0 +1,158 @@
/* 52-15 — 같은 측점에 관 + 포장이 함께 있을 때 좌측 목록 · 알약 · 넘김값이 누른 것을 그대로 고르는가
* 시작 화면: B05 종단설계(#/b05-profile) · 검증 프로젝트(랩탑_보조) · 로그인 상태 · 목록에 「22+0 배관 3」
* 부르는 법: 단계마다 한 번씩 — 앞에 `window.__m5215 = "단계";` 를 붙여
* orca eval --expression "window.__m5215='setup'; $(cat 이 파일)" → sessionStorage 사진 · 22+0 에 콘크리트 포장 [추가](저장 안 함)
* orca eval --expression "window.__m5215='check'; $(cat 이 파일)" → 지금 화면(B05 · B06) 목록 고르기 네 길
* orca eval --expression "window.__m5215='handoff'; $(cat 이 파일)" → B05 에서 포장 고른 채 B06 으로(넘김값)
* orca eval --expression "window.__m5215='pill'; $(cat 이 파일)" → B06 종단 알약으로 포장 고르기
* orca eval --expression "window.__m5215='restore'; $(cat 이 파일)" → sessionStorage 를 사진 그대로 · 뒤에 orca reload
* 쓰는 것: sessionStorage 만(서버 저장 없음 · 공유 storage)
* 기대 수치: 네 길 모두 누른 종류(pavement_concrete · pipe) · 포장일 때 고름 칸 sid = 포장 id ·
* handoff = pavement_concrete · pill = pavement_concrete · restore 뒤 sessionStorage 같음
*/
(async () => {
const MODE = window.__m5215 || "check";
const PIPE = /^22\+0 배관/;
const PAVE = /^21\+15~22\+5 콘크리트 포장/; // 기준 22+0 · 길이 10 m
const OTHER = /^4\+5 배관/;
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const until = async (fn, ms = 15000) => {
for (let t = 0; t < ms; t += 200) {
if (fn()) return true;
await w(200);
}
return false;
};
const sec = () => document.querySelector(".b05-structure-section");
const spec = () => sec().querySelector(".b05-structure__spec");
const selects = () => sec().querySelectorAll(".b05-structure__grid select");
const fire = (el, value) => {
el.value = value;
el.dispatchEvent(new Event("change", { bubbles: true }));
};
const specCtl = (item) =>
spec()?.querySelector(`[data-spec-item="detail.${item}"]`)?.querySelector("select, input");
const listItems = () => [...document.querySelectorAll(".b05-route__irregular-list li")];
const text = (li) => li.innerText.replace(/\s+/g, " ").trim();
const row = (re) => listItems().find((li) => re.test(text(li)));
const click = async (re) => {
const li = row(re);
if (!li) return false;
(li.querySelector("button") ?? li).click();
await w(900);
return true;
};
const pickKey = () =>
Object.keys(sessionStorage).find((k) => k.includes("structure-pick") && k.includes(PROJECT));
const PROJECT = "936be972-11bc-46c2-8bf3-b15d8de7df0d";
const picked = () => {
const raw = pickKey() ? sessionStorage.getItem(pickKey()) : null;
let sid = null;
try {
const value = JSON.parse(raw);
sid = (value?.value ?? value)?.sid ?? null;
} catch {
/* 빈 값 */
}
return { type: selects()[1]?.value ?? null, sid };
};
/** 한 화면의 네 길 — 다른 측점 → 포장(첫 눌림) · 다른 측점 → 관 · 관 → 포장 · 포장 → 관. */
const fourWays = async () => {
const out = {};
const run = async (name, first, second) => {
await click(first);
await click(second);
out[name] = picked();
};
await run("otherToPave", OTHER, PAVE);
await run("otherToPipe", OTHER, PIPE);
await click(PIPE); // 재선택 = 해제 — 관이 골라진 채로 시작
await click(PIPE);
await click(PAVE);
out.pipeToPave = picked();
await click(PIPE);
out.paveToPipe = picked();
await click(PIPE); // 해제
return out;
};
const out = { mode: MODE };
await until(() => listItems().length > 0);
if (MODE === "setup") {
window.__m5215snap = JSON.stringify(Object.entries(sessionStorage));
const [g, t] = selects();
fire(g, "G");
await w(300);
fire(t, "pavement_concrete");
await w(1500);
const anchor = () =>
sec().querySelectorAll(".b05-structure__position-row > label")[2].querySelectorAll("input");
fire(anchor()[0], "22");
await w(300);
fire(anchor()[1], "0");
await w(500);
out.anchor = [...anchor()].map((i) => i.value);
fire(specCtl("spec"), "콘크리트");
await w(200);
fire(specCtl("height"), "0.35");
await w(200);
sec().querySelector(".is-primary").click(); // 추가
await w(1000);
out.rows = listItems()
.map(text)
.filter((t) => /22\+[05]/.test(t));
} else if (MODE === "check") {
out[location.hash.includes("b06") ? "b06" : "b05"] = await fourWays();
} else if (MODE === "handoff") {
await click(OTHER);
await click(PAVE); // B05 에서 포장을 고른 채 옮김
out.b05Picked = picked();
location.hash = "#/b06-section";
await until(() => listItems().length > 0 && selects()[1]?.value, 20000);
await w(2000);
out.handoff = picked();
out.handoffOk = out.handoff.type === "pavement_concrete";
} else if (MODE === "pill") {
// 알약 — B06 종단 알약 레인의 포장을 누른다(다른 측점을 고른 뒤 · 같은 측점 관이 골라진 뒤).
await click(OTHER);
await click(PAVE);
const sid = picked().sid;
await click(PAVE); // 해제
const pill = () =>
[...document.querySelectorAll("[data-structure-id]")].find(
(node) => node.dataset.structureId === sid,
);
out.pillFound = !!pill();
for (const [name, first] of [
["otherToPill", OTHER],
["pipeToPill", PIPE],
]) {
await click(first);
pill()?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
await w(900);
out[name] = picked();
}
await click(PIPE); // 관 고른 채로 끝났으면 해제
out.pillOk =
out.otherToPill?.type === "pavement_concrete" && out.pipeToPill?.type === "pavement_concrete";
} else if (MODE === "restore") {
const snap = window.__m5215snap;
if (!snap) {
out.error = "사진 없음";
} else {
sessionStorage.clear();
for (const [k, v] of JSON.parse(snap)) sessionStorage.setItem(k, v);
out.same = JSON.stringify(Object.entries(sessionStorage)) === snap;
}
}
const ways = (r) =>
!!r &&
r.otherToPave.type === "pavement_concrete" &&
!!r.otherToPave.sid &&
r.otherToPipe.type === "pipe" &&
r.pipeToPave.type === "pavement_concrete" &&
r.paveToPipe.type === "pipe";
if (out.b05) out.b05Ok = ways(out.b05);
if (out.b06) out.b06Ok = ways(out.b06);
return JSON.stringify(out);
})();