feat(설정): 332-1 휠 확대 방향 읽기 층 — input_prefs · 모든 휠 화면 한 설정(기본 당김) · 핀치는 늘 벌리면 확대
- 새 A00_Common/input_prefs.ts(INPUT_PREFS_KEY · DEFAULT_INPUT_PREFS · inputPrefs · wheelIn · wheelEventIn · 의존 없음) - 등록표 input-prefs(pref · global) 한 줄 - touch_bridge wheelZoomsIn · b_svg_zoom_pan · B04 3D 카메라 · M02 DF 그림 · M02 노무 그림 · 돋보기 · 시트 배선 - M02 그림 · 시트 · 돋보기 기본 방향 = 당김(지금과 반대) - 시험: test_input_prefs 새로 · 돋보기 · 266 · 244-8 헬퍼가 읽기 층을 불러옴 · 돋보기 부호 당김 기본 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
This commit is contained in:
@@ -15,7 +15,9 @@ function load(file) {
|
||||
? {}
|
||||
: p.endsWith("key_shortcut") // 공용 키 판정(214-2)도 그 자리에서 트랜스파일
|
||||
? load(path.join(ROOT, "A00_Common", "key_shortcut.ts"))
|
||||
: require(p);
|
||||
: p.endsWith("input_prefs") // 휠 방향(332 · Node = 기본 당김)
|
||||
? load(path.join(ROOT, "A00_Common", "input_prefs.ts"))
|
||||
: require(p);
|
||||
new Function("exports", "module", "require", js)(box.exports, box, req);
|
||||
return box.exports;
|
||||
}
|
||||
@@ -100,30 +102,30 @@ r.consts = {
|
||||
r.listeners = win.calls.map((c) => [c.type, c.capture]);
|
||||
}
|
||||
{
|
||||
// 220-1 휠 — 켜 있는 동안만 배율 · 스크롤 막음 · 켤 때마다 2배
|
||||
// 220-1 휠 — 켜 있는 동안만 배율 · 스크롤 막음 · 켤 때마다 2배 · 확대 = 당김(deltaY > 0 · 332 기본)
|
||||
const { win, view } = setup();
|
||||
const wheel = (deltaY) => win.fire("wheel", { deltaY });
|
||||
const zoomLog = () => view.log.filter((l) => l[0] === "zoom").map((l) => l[1]);
|
||||
const offEv = wheel(-100);
|
||||
const offEv = wheel(100);
|
||||
const offState = { prevented: offEv.prevented, stopped: offEv.stopped, log: view.log.length };
|
||||
win.fire("keydown", { code: "KeyZ" });
|
||||
const up = wheel(-100);
|
||||
const up = wheel(100);
|
||||
const upState = { prevented: up.prevented, stopped: up.stopped, zoom: zoomLog().at(-1) };
|
||||
wheel(100);
|
||||
wheel(100);
|
||||
wheel(-100);
|
||||
wheel(-100);
|
||||
const down = zoomLog().at(-1);
|
||||
wheel(0); // 가로 굴림 — 배율 그대로 · 스크롤은 막음
|
||||
const flat = zoomLog().at(-1);
|
||||
for (let i = 0; i < 30; i++) wheel(-50);
|
||||
for (let i = 0; i < 30; i++) wheel(50);
|
||||
const max = zoomLog().at(-1);
|
||||
for (let i = 0; i < 30; i++) wheel(120);
|
||||
for (let i = 0; i < 30; i++) wheel(-120);
|
||||
const min = zoomLog().at(-1);
|
||||
win.fire("keyup", { code: "KeyZ" });
|
||||
const n = zoomLog().length;
|
||||
const afterOff = wheel(-100);
|
||||
const afterOff = wheel(100);
|
||||
const afterOffZoomed = zoomLog().length !== n;
|
||||
win.fire("keydown", { code: "KeyZ" });
|
||||
wheel(-100);
|
||||
wheel(100);
|
||||
r.wheel = {
|
||||
off: offState,
|
||||
up: upState,
|
||||
@@ -134,17 +136,17 @@ r.consts = {
|
||||
afterOff: { prevented: afterOff.prevented, stopped: afterOff.stopped, zoomed: afterOffZoomed },
|
||||
reopen: zoomLog().at(-1), // 다시 켜면 2배에서 한 칸
|
||||
pure: [
|
||||
m.nextLoupeZoom(2, -1),
|
||||
m.nextLoupeZoom(2, 1),
|
||||
m.nextLoupeZoom(6, -1),
|
||||
m.nextLoupeZoom(1.5, 1),
|
||||
m.nextLoupeZoom(2, -1),
|
||||
m.nextLoupeZoom(6, 1),
|
||||
m.nextLoupeZoom(1.5, -1),
|
||||
],
|
||||
};
|
||||
// 길게 누름으로 켠 동안도 같음
|
||||
const b = setup();
|
||||
b.win.fire("mousedown", { button: 2, buttons: 2 });
|
||||
b.win.tick(400);
|
||||
const press = b.win.fire("wheel", { deltaY: -1 });
|
||||
const press = b.win.fire("wheel", { deltaY: 1 });
|
||||
r.wheel.press = { prevented: press.prevented, zoom: b.view.log.at(-1) };
|
||||
}
|
||||
{
|
||||
@@ -298,7 +300,7 @@ r.origin = {
|
||||
// 켜진 채 CAD 위로(mouseover · mousemove 둘 다) — 숨김 · 휠 안 막음 · 나오면 배율 그대로 보임
|
||||
const { win, view } = setup();
|
||||
win.fire("keydown", { code: "KeyZ" });
|
||||
win.fire("wheel", { deltaY: -100 }); // 2.25
|
||||
win.fire("wheel", { deltaY: 100 }); // 2.25
|
||||
win.fire("mouseover", { target: cad });
|
||||
const hidden = last(view);
|
||||
const n = view.log.length;
|
||||
|
||||
@@ -28,6 +28,7 @@ function loadTs(full) {
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
STUBS["./input_prefs"] = loadTs(path.join(ROOT, "A00_Common", "input_prefs.ts")); // 휠 방향(332 · Node = 기본 당김)
|
||||
const metrics = loadTs(path.join(B06, "B06_Section_UI_Cross_View_Metrics.ts"));
|
||||
const { attachSvgZoomPan } = loadTs(path.join(ROOT, "A00_Common", "b_svg_zoom_pan.ts"));
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/* PLAN 332-1 단위검증 헬퍼 — 휠 방향 읽기 층(`A00_Common/input_prefs.ts`) · 손가락 다리(`touch_bridge.ts`)
|
||||
* · 등록표 키(`b_page_state.ts`)를 그 자리에서 트랜스파일해 Node 로 돌림. 가짜 localStorage 로 설정을 바꿔 봄.
|
||||
* 결과 = 표준출력 JSON 한 벌. 짝 = `test_input_prefs.py`. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const A00 = path.join(ROOT, "A00_Common");
|
||||
function load(file) {
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
const req = (p) => (p === "./input_prefs" ? prefs : {});
|
||||
new Function("exports", "module", "require", js)(box.exports, box, req);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const out = {};
|
||||
// ① Node(저장소 없음) — 불러올 때 window 를 안 만지고 기본값
|
||||
const prefs = load(path.join(A00, "input_prefs.ts"));
|
||||
const bridge = load(path.join(A00, "touch_bridge.ts"));
|
||||
const page = load(path.join(A00, "b_page_state.ts"));
|
||||
const sample = () => ({
|
||||
prefs: prefs.inputPrefs(),
|
||||
pull: prefs.wheelIn(100),
|
||||
push: prefs.wheelIn(-100),
|
||||
mouseCtrl: prefs.wheelEventIn({ ctrlKey: true, deltaY: 100 }),
|
||||
padSpread: prefs.wheelEventIn({ ctrlKey: true, deltaY: -3.5 }),
|
||||
padPinch: prefs.wheelEventIn({ ctrlKey: true, deltaY: 3.5 }),
|
||||
fingerSpread: prefs.wheelEventIn({ ctrlKey: true, deltaY: -9.5 }, true),
|
||||
bridgePull: bridge.wheelZoomsIn({ ctrlKey: false, deltaY: 100 }),
|
||||
});
|
||||
out.node = { key: prefs.INPUT_PREFS_KEY, default: prefs.DEFAULT_INPUT_PREFS, ...sample() };
|
||||
out.registryKey = page.stateKey("input-prefs");
|
||||
out.registryEntry = page.STATE_REGISTRY["input-prefs"];
|
||||
|
||||
// ② 브라우저 저장소 흉내 — 값을 바꾸면 다음 부름부터 바로 따름
|
||||
const store = new Map();
|
||||
globalThis.localStorage = {
|
||||
getItem: (k) => (store.has(k) ? store.get(k) : null),
|
||||
setItem: (k, v) => store.set(k, String(v)),
|
||||
removeItem: (k) => store.delete(k),
|
||||
};
|
||||
const set = (v) => store.set(prefs.INPUT_PREFS_KEY, v);
|
||||
set(JSON.stringify({ wheelZoom: "push", cadAliases: { line: ["ll", 3], ARC: [] } }));
|
||||
out.push = sample();
|
||||
set("{깨짐");
|
||||
out.broken = sample();
|
||||
set(JSON.stringify({ wheelZoom: "sideways" }));
|
||||
out.unknown = sample();
|
||||
store.delete(prefs.INPUT_PREFS_KEY);
|
||||
out.empty = sample();
|
||||
// ③ 불러오는 동안 저장소가 던지는 브라우저(사생활 보호) — 기본값
|
||||
globalThis.localStorage = {
|
||||
getItem: () => {
|
||||
throw new Error("blocked");
|
||||
},
|
||||
};
|
||||
out.blocked = sample();
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -34,10 +34,10 @@ def test_지름_360_배율_한도(r):
|
||||
assert (c["zoom"], c["zoomMin"], c["zoomMax"], c["zoomStep"]) == (2, 1.5, 6, 0.25)
|
||||
|
||||
|
||||
def test_휠_위_크게_아래_작게_한도(r):
|
||||
def test_휠_당김_크게_밀기_작게_한도(r): # 332 기본 = 당김 확대
|
||||
w = r["wheel"]
|
||||
assert w["up"]["zoom"] == 2.25 # 위로 한 칸
|
||||
assert w["down"] == 1.75 # 아래로 두 칸
|
||||
assert w["up"]["zoom"] == 2.25 # 당겨 한 칸
|
||||
assert w["down"] == 1.75 # 밀어 두 칸
|
||||
assert w["flat"] == 1.75 # 가로 굴림은 그대로
|
||||
assert w["max"] == 6 and w["min"] == 1.5
|
||||
assert w["pure"] == [2.25, 1.75, 6, 1.5]
|
||||
|
||||
@@ -20,7 +20,10 @@ def _read(rel):
|
||||
|
||||
|
||||
def _node(script):
|
||||
src = IMPORTS.sub("", _read("A00_Common/b_svg_zoom_pan.ts"))
|
||||
# 휠 방향 읽기 층(332) 을 앞에 붙임 — Node 엔 저장소가 없어 기본(당기면 확대)
|
||||
src = _read("A00_Common/input_prefs.ts") + IMPORTS.sub(
|
||||
"", _read("A00_Common/b_svg_zoom_pan.ts")
|
||||
)
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", src + "\n" + script],
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""PLAN 332-1 — 휠 확대 방향 한 설정(`A00_Common/input_prefs.ts`) · 모든 휠 화면이 그 판정을 거침.
|
||||
|
||||
· 기본 = 당김(휠을 몸 쪽으로 = 확대) · 밀기 저장 뒤 뒤집힘 · 깨짐 · 모르는 값 · 막힌 저장소 · Node = 기본
|
||||
· 핀치(트랙패드 · 손가락)는 설정과 상관없이 늘 벌리면 확대
|
||||
· 키 = 등록표 `input-prefs`(pref · global) 와 같은 글자
|
||||
짝: `helper_input_prefs.cjs`(실제 TS 를 Node 로 돌림).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
ROOT = os.path.join(HERE, "..", "..")
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not os.path.isdir(os.path.join(ROOT, "config", "node_modules")),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
|
||||
def _read(rel: str) -> str:
|
||||
with open(os.path.join(ROOT, rel), encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", os.path.join(HERE, "helper_input_prefs.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
PULL = {"pull": True, "push": False, "mouseCtrl": True, "bridgePull": True}
|
||||
PINCH = {"padSpread": True, "padPinch": False, "fingerSpread": True}
|
||||
|
||||
|
||||
def _pick(case: dict, keys: dict) -> dict:
|
||||
return {k: case[k] for k in keys}
|
||||
|
||||
|
||||
def test_Node_기본_당김(r):
|
||||
n = r["node"]
|
||||
assert n["default"] == {"wheelZoom": "pull", "cadAliases": {}}
|
||||
assert n["prefs"] == n["default"]
|
||||
assert _pick(n, PULL) == PULL and _pick(n, PINCH) == PINCH
|
||||
|
||||
|
||||
def test_밀기_저장_뒤_뒤집힘_핀치는_그대로(r):
|
||||
p = r["push"]
|
||||
assert p["prefs"]["wheelZoom"] == "push"
|
||||
assert _pick(p, PULL) == {"pull": False, "push": True, "mouseCtrl": False, "bridgePull": False}
|
||||
assert _pick(p, PINCH) == PINCH # 벌리면 확대는 설정과 상관없음
|
||||
# 별칭 = 대문자 · 글자 아닌 것 · 빈 목록 버림
|
||||
assert p["prefs"]["cadAliases"] == {"LINE": ["LL"]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", ["broken", "unknown", "empty", "blocked"])
|
||||
def test_깨짐_모르는_값_없음_막힘_기본(r, case):
|
||||
c = r[case]
|
||||
assert c["prefs"] == {"wheelZoom": "pull", "cadAliases": {}}
|
||||
assert _pick(c, PULL) == PULL and _pick(c, PINCH) == PINCH
|
||||
|
||||
|
||||
def test_등록표_키(r):
|
||||
assert r["node"]["key"] == r["registryKey"] == "aislo:pref:input-prefs"
|
||||
assert r["registryEntry"] == {"bucket": "pref", "scope": "global"}
|
||||
|
||||
|
||||
def test_읽기_층은_의존_없음_불러올_때_window_안_만짐():
|
||||
src = _read("A00_Common/input_prefs.ts")
|
||||
assert not re.search(r"^import ", src, re.M)
|
||||
assert "window." not in src
|
||||
|
||||
|
||||
# 휠 줌 자리 — 직접 박은 방향(`deltaY < 0 ?` · `deltaY > 0 ?` · `-ev.deltaY`)이 없고 공용 판정을 부름
|
||||
WHEEL_SITES = {
|
||||
"A00_Common/touch_bridge.ts": "wheelEventIn(e, isPinchWheel(e))",
|
||||
"A00_Common/b_svg_zoom_pan.ts": "wheelEventIn(event)",
|
||||
"B04_PreProcess/B04_PreProcess_UI_Camera.ts": "wheelEventIn(event)",
|
||||
"M02_MasterTemplete/DataFlow/M02_DF_Diagram.ts": "wheelZoomsIn(ev)",
|
||||
"M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts": "wheelZoomsIn(e)",
|
||||
"ui_template/ui_template_loupe.ts": "wheelIn(deltaY)",
|
||||
"A00_Common/spreadsheet/spreadsheet_grid.ts": "wheelZoomsIn(ev)",
|
||||
# wheelZoomsIn 을 거쳐 따라오는 셋
|
||||
"B04_PreProcess/B04_PreProcess_UI_MapViewer.ts": "wheelZoomsIn(event)",
|
||||
"B05_Profile/B05_Profile_UI_Drainage_Interact.ts": "wheelZoomsIn(event)",
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit_Input.ts": "wheelZoomsIn(event)",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rel", sorted(WHEEL_SITES))
|
||||
def test_휠_자리_공용_판정(rel):
|
||||
src = _read(rel)
|
||||
assert WHEEL_SITES[rel] in src
|
||||
assert not re.search(r"deltaY [<>] 0 \?", src), rel
|
||||
assert "-ev.deltaY" not in src and "Math.sign(-deltaY)" not in src
|
||||
|
||||
|
||||
def test_700줄_안():
|
||||
for rel in WHEEL_SITES:
|
||||
assert _read(rel).count("\n") <= 700, rel
|
||||
Reference in New Issue
Block a user