- 부품 상자 CSS · 단추 기본 꼴을 CAD iframe 에 실음(integration/ui-kit.ts) · 자리 · 폭은 cad-* 그대로 - 상태막대 = ui-toggle--chip 22 · 특성/레이어 = ui-tabs 36 · ↶↷ · 리본 접기 · 탐색 막대 = ui-icon-btn 20 · 32 - 빠른 특성 · 블록 라이브러리 · 자리표 = ui-float-panel · 리본 넘침 · 스냅 · 명령 자동완성 = ui-ctx-menu - window.confirm · window.prompt → askConfirm · askText · 빈 안내 = ui-empty 작은 꼴 - 높이 단: 리본 칸 19→18 · 명령 칸 26→28 · 레이어 줄 40→42 · 문서 탭 25→24 · 도각 편집 단추 24 - 마우스 바탕 = --color-hover · 켜짐 = --color-accent-soft · 도면 그림 색(--draw-*) 안 건드림 - 안 쓰는 DropdownButton 지움(쓰는 곳 0 · grep) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
/** 명령행 — AutoCAD 명령행처럼 기록·입력·자동완성을 담당한다. */
|
|
import { type FC, type FormEvent, useMemo, useRef, useState } from 'react';
|
|
import { describeCommand, matchCommandPrefixes } from '../commands/registry';
|
|
import { getCommandHistory, runCommandInput } from '../commands/run-command';
|
|
import { activeToolWantsFreeText, isPlainSpace } from '../inputController/ime-sink';
|
|
import { commandCandidate, recallInput } from '../inputController/input-history';
|
|
import { currentPrompt } from '../inputController/prompt-input';
|
|
import { getInputController } from '../state';
|
|
|
|
export const CommandLine: FC = () => {
|
|
const [text, setText] = useState('');
|
|
/** Tab · ↑↓ 후보 돌림 — 처음 친 글 · 몇째(null = 안 돌림) */
|
|
const [pick, setPick] = useState<{ base: string; index: number } | null>(null);
|
|
const [fromHistory, setFromHistory] = useState(false);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const history = getCommandHistory();
|
|
const instruction = currentPrompt().line || '명령을 입력하거나 도구를 선택하십시오.';
|
|
|
|
const suggestions = useMemo(
|
|
() => matchCommandPrefixes(pick?.base ?? text).slice(0, 8),
|
|
[text, pick]
|
|
);
|
|
|
|
const submit = (event: FormEvent) => {
|
|
event.preventDefault();
|
|
const value = text.trim();
|
|
if (!value) {
|
|
// 빈 ENTER는 활성 명령의 확정 신호다 (선택 끝내기·직전 명령 반복)
|
|
getInputController().handleEnterKey(true);
|
|
return;
|
|
}
|
|
// 진행 중인 명령이 문자·숫자를 기다리면 그쪽으로, 아니면 명령으로 해석된다
|
|
getInputController().submitText(value);
|
|
setText('');
|
|
setPick(null);
|
|
setFromHistory(false);
|
|
};
|
|
|
|
/** 후보 보이면 후보 돌림 · 아니면 친 명령 · 최근 값 기록(캔버스와 같은 함수 · 알림 줄 안 섞음) */
|
|
const recall = (direction: -1 | 1) => {
|
|
if (pick || (!fromHistory && text.trim() && suggestions.length)) {
|
|
cycle(direction);
|
|
return;
|
|
}
|
|
const value = recallInput(direction, text);
|
|
if (value === null) return;
|
|
setText(value);
|
|
setFromHistory(true);
|
|
};
|
|
const cycle = (direction: -1 | 1) => {
|
|
const base = pick?.base ?? text;
|
|
const index = pick ? pick.index + direction : direction > 0 ? 0 : -1;
|
|
const name = commandCandidate(base, index);
|
|
if (!name) return;
|
|
setPick({ base, index });
|
|
setText(name);
|
|
};
|
|
|
|
return (
|
|
<section className="cad-command-area controls">
|
|
<div className="cad-command-history">
|
|
{history.slice(-3).map((line, index) => (
|
|
// biome-ignore lint/suspicious/noArrayIndexKey: 기록은 순서 자체가 식별자다
|
|
<span key={`${line}-${index}`}>{line}</span>
|
|
))}
|
|
</div>
|
|
<form onSubmit={submit}>
|
|
<label htmlFor="cad-command">명령:</label>
|
|
<input
|
|
id="cad-command"
|
|
ref={inputRef}
|
|
value={text}
|
|
onChange={(event) => {
|
|
setText(event.target.value);
|
|
setPick(null);
|
|
setFromHistory(false);
|
|
}}
|
|
onKeyDown={(event) => {
|
|
if (event.nativeEvent.isComposing) return; // 한글 조합 중 키는 IME 몫
|
|
if (event.key === ' ' && !activeToolWantsFreeText()) {
|
|
// Space = Enter(오토캐드) · 글 단계는 띄어쓰기 · 수정키 Space 는 그대로(입력칸 몫)
|
|
if (isPlainSpace(event)) {
|
|
event.preventDefault();
|
|
event.currentTarget.form?.requestSubmit();
|
|
} else if (event.repeat) event.preventDefault();
|
|
return;
|
|
}
|
|
if (event.key === 'Escape') {
|
|
event.preventDefault();
|
|
getInputController().handleEscapeKey();
|
|
}
|
|
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
|
|
event.preventDefault();
|
|
recall(event.key === 'ArrowUp' ? -1 : 1);
|
|
}
|
|
if (event.key === 'Tab' && suggestions.length) {
|
|
event.preventDefault();
|
|
cycle(1);
|
|
}
|
|
}}
|
|
placeholder="명령 입력 (예: L, LINE, REC, MOVE)"
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
/>
|
|
<span className="cad-command-prompt">{instruction}</span>
|
|
</form>
|
|
{text.trim() && suggestions.length > 0 && (
|
|
<div className="ui-ctx-menu cad-command-suggestions">
|
|
{suggestions.map((command) => (
|
|
<button
|
|
type="button"
|
|
className="ui-ctx-menu__item"
|
|
key={command.id}
|
|
onClick={() => {
|
|
runCommandInput(command.id);
|
|
setText('');
|
|
inputRef.current?.focus();
|
|
}}
|
|
>
|
|
{describeCommand(command)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
};
|