Merge branch 'sub_laptop_3' of https://gitea.chemifactory.com/eomsangdon/Aislo into main_laptop_1
This commit is contained in:
@@ -116,14 +116,22 @@ function buildScaleFields(
|
||||
void save({ x: scaleToSend(x, fields[0].fallback), y: scaleToSend(y, fields[1].fallback) });
|
||||
},
|
||||
});
|
||||
// 칸 밑 오류 글은 숨기고(틀린 칸만 길어짐) 줄 아래 한 줄로 알림
|
||||
const alertLine = document.createElement("p");
|
||||
alertLine.className = "b07-scale__error";
|
||||
alertLine.setAttribute("role", "alert");
|
||||
alertLine.hidden = true;
|
||||
const check = (): void => {
|
||||
let bad = false;
|
||||
const errors = new Map<string, string[]>();
|
||||
for (const f of fields) {
|
||||
const result = f.read();
|
||||
f.field.setError(typeof result === "string" ? result : undefined);
|
||||
bad ||= typeof result === "string";
|
||||
const message = typeof result === "string" ? result : undefined;
|
||||
f.field.setError(message);
|
||||
if (message) errors.set(message, [...(errors.get(message) ?? []), f.axis.toUpperCase()]);
|
||||
}
|
||||
saveButton.disabled = bad;
|
||||
alertLine.textContent = [...errors].map(([m, axes]) => `${axes.join(" · ")}: ${m}`).join(" / ");
|
||||
alertLine.hidden = errors.size === 0;
|
||||
saveButton.disabled = errors.size > 0;
|
||||
};
|
||||
for (const f of fields) {
|
||||
f.field.input.addEventListener("input", check);
|
||||
@@ -153,5 +161,5 @@ function buildScaleFields(
|
||||
const buttons = document.createElement("div");
|
||||
buttons.className = "b07-scale__actions";
|
||||
buttons.append(reset, saveButton);
|
||||
section.append(title, row, buttons);
|
||||
section.append(title, row, alertLine, buttons);
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ html.is-bm-route .b07-cad-stack > .cad-tabs {
|
||||
top: 0;
|
||||
right: var(--cad-tab-slot-right, 0px);
|
||||
left: var(--cad-tab-slot-left, 0px);
|
||||
height: 26px;
|
||||
height: 25px;
|
||||
padding: 2px 4px 0;
|
||||
border-bottom: none;
|
||||
background: transparent;
|
||||
@@ -522,6 +522,16 @@ html.is-bm-route .b07-cad-stack > .cad-tabs {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.b07-scale .ui-field__error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.b07-scale__error {
|
||||
margin: 0;
|
||||
color: var(--color-danger);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.b07-scale__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -105,7 +105,7 @@ body > canvas[data-id="canvas"] {
|
||||
gap: 2px;
|
||||
height: 26px;
|
||||
padding: 0 8px;
|
||||
border-bottom: 1px solid var(--cad-line);
|
||||
border-bottom: 1px solid var(--color-smoke);
|
||||
}
|
||||
.cad-ribbon-tabs__slot {
|
||||
flex: 1;
|
||||
|
||||
@@ -19,10 +19,7 @@ export const QuickAccessBar: FC = () => {
|
||||
key={command.id}
|
||||
hidden={QUICK_ACCESS_HIDDEN.includes(id)}
|
||||
title={`${command.label} (${command.id})`}
|
||||
onClick={(event) => {
|
||||
event.currentTarget.blur();
|
||||
runCommand(command);
|
||||
}}
|
||||
onClick={() => runCommand(command)}
|
||||
>
|
||||
<span aria-hidden>{command.glyph}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/* 손가락 기기 명령 단추(기기대응 4-1 · `input-touch.ts`) — 자판 없이 Esc · Enter · 지움 · 되돌리기 · 다시 · 스냅.
|
||||
자판 없는 기기(손가락이 주 입력 · 마우스 스침 없음)만 보임 — 터치 노트북엔 안 보임(요구 280-6). 그림 면 오른쪽 가운데에 세로로. */
|
||||
.cad-touch-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.cad-touch-bar {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 8px;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.cad-touch-bar > button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--color-surface-raised);
|
||||
color: var(--color-text);
|
||||
font-size: 18px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { Point } from '@flatten-js/core';
|
||||
import { setTouchPickPx } from '../helpers/pick-radius.ts';
|
||||
import { getCanvas, getScreenCanvasDrawController } from '../state.ts';
|
||||
import type { InputController } from './input-controller.ts';
|
||||
import './input-touch.css';
|
||||
|
||||
/**
|
||||
* 손가락 CAD(기기대응 4-1) — 마우스 코드는 그대로 두고 손가락만 여기서 번역한다.
|
||||
@@ -11,7 +10,6 @@ import './input-touch.css';
|
||||
* · 한 손가락 끌기 = 화면 옮기기 · 두 손가락 = 벌려 확대 + 같이 끌어 옮기기
|
||||
* · 길게 누름(450ms) = 정밀 고르기 — 손가락 위 70px 에 십자선 · 끌어 맞추고 떼면 그 자리 클릭
|
||||
* · 고름 반경 = 접촉 크기(최소 24px) · 마우스로 누르면 원래 반경
|
||||
* · 자판 없이 쓰는 명령 단추(Esc · Enter · 지움 · 되돌리기 · 다시 · 스냅) — 손가락 기기에서만 보임(App.css)
|
||||
*/
|
||||
const LONG_MS = 450;
|
||||
const SLOP_PX = 10;
|
||||
@@ -115,30 +113,4 @@ export function attachTouch(ctl: InputController): void {
|
||||
};
|
||||
canvas.addEventListener('pointerup', (e) => lift(e, false));
|
||||
canvas.addEventListener('pointercancel', (e) => lift(e, true));
|
||||
|
||||
mountTouchBar(ctl);
|
||||
}
|
||||
|
||||
/** 자판 없는 명령 단추 — 단축키와 같은 길(`handleKeyStroke`)로 보낸다 */
|
||||
function mountTouchBar(ctl: InputController): void {
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'cad-touch-bar';
|
||||
const keys: [string, string, KeyboardEventInit][] = [
|
||||
['Esc', '취소(Esc)', { key: 'Escape' }],
|
||||
['↵', '확정(Enter)', { key: 'Enter' }],
|
||||
['🗑', '지움(Delete)', { key: 'Delete' }],
|
||||
['↶', '되돌리기(Ctrl+Z)', { key: 'z', code: 'KeyZ', ctrlKey: true }],
|
||||
['↷', '다시(Ctrl+Y)', { key: 'y', code: 'KeyY', ctrlKey: true }],
|
||||
['⌖', '스냅 켬/끔(F3)', { key: 'F3' }],
|
||||
];
|
||||
for (const [label, title, init] of keys) {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.textContent = label;
|
||||
b.title = title;
|
||||
b.setAttribute('aria-label', title);
|
||||
b.addEventListener('click', () => ctl.handleKeyStroke(new KeyboardEvent('keydown', init)));
|
||||
bar.append(b);
|
||||
}
|
||||
document.body.append(bar);
|
||||
}
|
||||
|
||||
@@ -35,11 +35,7 @@ const CommandButton: FC<CommandButtonProps> = ({ command, size, activeTool, onRu
|
||||
title={`${command.label}${command.aliases?.length ? ` (${command.aliases[0]})` : ''}${
|
||||
command.hint ? ` — ${command.hint}` : ''
|
||||
}`}
|
||||
onClick={(event) => {
|
||||
// 초점 테두리가 남아 「선택」과 둘이 켜진 듯 보이지 않게(487)
|
||||
event.currentTarget.blur();
|
||||
onRun(command);
|
||||
}}
|
||||
onClick={() => onRun(command)}
|
||||
>
|
||||
<span className="cad-tool__glyph">{command.glyph}</span>
|
||||
<span className="cad-tool__label">{command.label}</span>
|
||||
@@ -139,8 +135,7 @@ const Panel: FC<{
|
||||
type="button"
|
||||
key={command.id}
|
||||
title={command.hint}
|
||||
onClick={(event) => {
|
||||
event.currentTarget.blur();
|
||||
onClick={() => {
|
||||
setOverflowAt(null);
|
||||
onRun(command);
|
||||
}}
|
||||
@@ -205,7 +200,16 @@ export const Ribbon: FC<RibbonProps> = ({ activeTool, widgets, onCommandRun }) =
|
||||
};
|
||||
|
||||
return (
|
||||
<nav ref={navRef} className="cad-ribbon controls" aria-label="CAD 리본" data-peek={peeking}>
|
||||
<nav
|
||||
ref={navRef}
|
||||
className="cad-ribbon controls"
|
||||
aria-label="CAD 리본"
|
||||
data-peek={peeking}
|
||||
onClick={(event) => {
|
||||
// 마우스로 누른 단추만 초점을 놓음 — 뒤이은 Esc 에 초점 테두리가 안 뜸(키보드 Enter · Space 는 detail 0 이라 남음)
|
||||
if (event.detail > 0) (event.target as HTMLElement).closest('button')?.blur();
|
||||
}}
|
||||
>
|
||||
<div className="cad-ribbon-tabs">
|
||||
{RIBBON_TABS.map((tab) => (
|
||||
<button
|
||||
|
||||
@@ -163,7 +163,7 @@ def test_탭_줄_겹판_약속():
|
||||
block = block[: block.index("}")]
|
||||
assert "right: var(--cad-tab-slot-right, 0px);" in block
|
||||
assert "left: var(--cad-tab-slot-left, 0px);" in block
|
||||
assert re.search(r"height:\s*26px", block)
|
||||
assert re.search(r"height:\s*25px", block)
|
||||
|
||||
|
||||
class _Cursor:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""B07 CAD 손가락(PLAN_기기대응 4-1 · `openwebcad/src/inputController/input-touch.ts`) — 빌드된 CAD 화면.
|
||||
|
||||
① 자판 없는 기기(hover:none · pointer:coarse) = 명령 단추 6개(44px) · 마우스 · 터치 노트북 = 안 보임(요구 280-6)
|
||||
① 손가락 명령 단추(.cad-touch-bar)는 없음(요구 298-1) — 마우스 · 손가락 화면 모두 DOM 0
|
||||
② 확대 막기(user-scalable=no) 없음
|
||||
③ 캔버스 touch-action none · 두 손가락 벌림 · 한 손가락 끌기가 페이지를 확대 · 밀지 않음(페이지 오류 0)
|
||||
"""
|
||||
@@ -32,24 +32,15 @@ def _touch(cdp, kind, points):
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _up(), reason="sub1 화면 서버(5174) 꺼짐")
|
||||
def test_cad_touch_bar_and_gestures():
|
||||
def test_cad_no_touch_bar_and_gestures():
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(channel="msedge", headless=True)
|
||||
mouse = browser.new_page(viewport={"width": 1440, "height": 900})
|
||||
mouse.goto(URL)
|
||||
mouse.wait_for_selector(".cad-touch-bar", state="attached", timeout=20000)
|
||||
assert (
|
||||
mouse.evaluate("getComputedStyle(document.querySelector('.cad-touch-bar')).display")
|
||||
== "none"
|
||||
)
|
||||
# 터치 노트북(any-pointer: coarse · 주 입력 마우스) — 단추는 hover:none 일 때만(에뮬은 터치 노트북을 못 꾸밈)
|
||||
media = mouse.evaluate(
|
||||
"[...document.styleSheets].flatMap(s => [...s.cssRules]).filter(r => r.media"
|
||||
" && [...r.cssRules].some(c => c.selectorText === '.cad-touch-bar')).map(r => r.media.mediaText)"
|
||||
)
|
||||
assert media == ["(hover: none) and (pointer: coarse)"], media
|
||||
mouse.wait_for_selector("canvas[data-id=canvas]", timeout=20000)
|
||||
assert mouse.evaluate("document.querySelector('.cad-touch-bar')") is None
|
||||
ctx = browser.new_context(
|
||||
viewport={"width": 1180, "height": 820}, has_touch=True, is_mobile=True
|
||||
)
|
||||
@@ -57,11 +48,8 @@ def test_cad_touch_bar_and_gestures():
|
||||
errors = []
|
||||
page.on("pageerror", lambda e: errors.append(str(e)))
|
||||
page.goto(URL)
|
||||
page.wait_for_selector(".cad-touch-bar", timeout=20000)
|
||||
sizes = page.evaluate(
|
||||
"[...document.querySelectorAll('.cad-touch-bar > button')].map(b => [b.offsetWidth, b.offsetHeight])"
|
||||
)
|
||||
assert len(sizes) == 6 and all(w >= 44 and h >= 44 for w, h in sizes), sizes
|
||||
page.wait_for_selector("canvas[data-id=canvas]", timeout=20000)
|
||||
assert page.evaluate("document.querySelector('.cad-touch-bar')") is None
|
||||
assert "user-scalable" not in page.evaluate(
|
||||
"document.querySelector('meta[name=viewport]').content"
|
||||
)
|
||||
@@ -86,7 +74,6 @@ def test_cad_touch_bar_and_gestures():
|
||||
for i in range(1, 8):
|
||||
_touch(cdp, "touchMove", [(cx - 30 - i * 10, cy), (cx + 30 + i * 10, cy)])
|
||||
_touch(cdp, "touchEnd", [])
|
||||
page.locator(".cad-touch-bar > button").first.tap() # Esc
|
||||
page.wait_for_timeout(300)
|
||||
assert page.evaluate("visualViewport.scale") == 1
|
||||
assert page.evaluate("[scrollX, scrollY]") == [0, 0]
|
||||
|
||||
Reference in New Issue
Block a user