refactor(cad): CAD 앱 겉 자체 변종을 부품 상자 부품으로 — 상태막대 켜기 칩 · 패널 탭 · 떠 있는 판 · 메뉴 · 아이콘 단추 · 확인창 (PLAN 553-6)
- 부품 상자 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>
This commit is contained in:
@@ -27,7 +27,8 @@
|
||||
--cad-text-dim: var(--color-text-secondary);
|
||||
--cad-text-muted: var(--color-text-muted);
|
||||
--cad-accent: var(--color-accent);
|
||||
--cad-hover: var(--color-mist-violet);
|
||||
/* 마우스 바탕 = 부품 상자와 같은 값 · 켜짐 바탕은 --color-accent-soft */
|
||||
--cad-hover: var(--color-hover);
|
||||
--cad-canvas: var(--draw-canvas);
|
||||
}
|
||||
|
||||
@@ -111,21 +112,12 @@ body > canvas[data-id="canvas"] {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
/* 접기 단추 = 아이콘 단추 부품(단 20) · 여기는 자리만 */
|
||||
.cad-ribbon-tabs__collapse {
|
||||
width: 24px;
|
||||
height: 20px;
|
||||
align-self: center;
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 11px;
|
||||
}
|
||||
.cad-ribbon-tabs__collapse:hover {
|
||||
background: var(--cad-hover);
|
||||
}
|
||||
.cad-ribbon-tabs button {
|
||||
/* 리본 탭 = CAD 고유 꼴 그대로(토큰만) */
|
||||
.cad-ribbon-tabs > button:not(.ui-icon-btn) {
|
||||
padding: 0 14px;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 0;
|
||||
@@ -134,10 +126,10 @@ body > canvas[data-id="canvas"] {
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 12px;
|
||||
}
|
||||
.cad-ribbon-tabs button:hover {
|
||||
.cad-ribbon-tabs > button:not(.ui-icon-btn):hover {
|
||||
background: var(--cad-hover);
|
||||
}
|
||||
.cad-ribbon-tabs button[data-active="true"] {
|
||||
.cad-ribbon-tabs > button[data-active="true"] {
|
||||
background: var(--cad-chrome);
|
||||
border-color: var(--cad-line);
|
||||
color: var(--color-text);
|
||||
@@ -185,53 +177,25 @@ body > canvas[data-id="canvas"] {
|
||||
.cad-ribbon-group__label[data-expandable="true"]:hover {
|
||||
color: var(--cad-accent);
|
||||
}
|
||||
/* 꼴 = 메뉴 부품(`ui-ctx-menu`) · 여기는 자리만 */
|
||||
.cad-ribbon-overflow {
|
||||
/* 리본 패널 줄이 가로 스크롤 상자라 그 안에서는 잘린다 — 화면 좌표로 띄운다 */
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 170px;
|
||||
max-height: 72vh;
|
||||
padding: 4px;
|
||||
overflow-y: auto;
|
||||
background: var(--cad-chrome-raised);
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.cad-ribbon-overflow button {
|
||||
.cad-ribbon-overflow > .ui-ctx-menu__item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 5px 8px;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
background: transparent;
|
||||
font-size: 11px;
|
||||
text-align: left;
|
||||
}
|
||||
.cad-ribbon-overflow button:hover {
|
||||
background: var(--cad-hover);
|
||||
}
|
||||
/* ↶ ↷ = 아이콘 단추 부품(단 20) · 여기는 자리만 */
|
||||
.cad-quick-access {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-self: center;
|
||||
}
|
||||
.cad-ribbon-tabs .cad-quick-access button {
|
||||
width: 24px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
line-height: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
.cad-quick-access button:hover {
|
||||
border-color: var(--cad-line);
|
||||
background: var(--cad-hover);
|
||||
}
|
||||
.cad-ribbon-props {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -252,15 +216,16 @@ body > canvas[data-id="canvas"] {
|
||||
width: 52px;
|
||||
text-align: left;
|
||||
}
|
||||
/* 리본 안 칸 = CAD 고유 꼴(토큰만) · 높이 = 크기 단 18 · 둥글기 4 */
|
||||
.cad-prop select,
|
||||
.cad-prop input[type="number"] {
|
||||
height: 19px;
|
||||
height: 18px;
|
||||
min-width: 78px;
|
||||
padding: 0 3px;
|
||||
color: var(--cad-text);
|
||||
background: var(--cad-chrome-sunken);
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.cad-prop input[type="number"] {
|
||||
@@ -268,13 +233,13 @@ body > canvas[data-id="canvas"] {
|
||||
width: 52px;
|
||||
}
|
||||
.cad-prop input[type="color"] {
|
||||
height: 19px;
|
||||
height: 18px;
|
||||
width: 44px;
|
||||
padding: 1px;
|
||||
background: var(--cad-chrome-sunken);
|
||||
/* 흰색을 골라도 스와치 자리가 보이게 테두리를 진하게 준다 (2026-09-01) */
|
||||
border: 1px solid var(--cad-text-muted);
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* 고른 것들 색이 다름(488) — 빈 견본(투명 · 점선 테두리) */
|
||||
@@ -328,7 +293,7 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans
|
||||
}
|
||||
.cad-tool[data-active="true"] {
|
||||
border-color: var(--cad-accent);
|
||||
background: var(--cad-hover);
|
||||
background: var(--color-accent-soft);
|
||||
box-shadow: inset 0 -2px var(--cad-accent);
|
||||
}
|
||||
.cad-tool:disabled {
|
||||
@@ -373,23 +338,14 @@ input[type="color"][data-mixed="true"]::-webkit-color-swatch { background: trans
|
||||
background: var(--cad-chrome-raised);
|
||||
color: var(--cad-text-dim);
|
||||
}
|
||||
/* 꼴 = 탭 부품(`ui-tabs`) · 여기는 칸 둘이 폭을 나누는 배치만 */
|
||||
.cad-inspector-tabs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
height: 38px;
|
||||
border-bottom: 1px solid var(--cad-line);
|
||||
gap: 0;
|
||||
}
|
||||
.cad-inspector-tabs button {
|
||||
border: 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
background: var(--cad-chrome-sunken);
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 12px;
|
||||
}
|
||||
.cad-inspector-tabs button[data-active="true"] {
|
||||
border-bottom-color: var(--cad-accent);
|
||||
background: var(--cad-chrome-raised);
|
||||
color: var(--color-text);
|
||||
.cad-inspector-tabs > .ui-tabs__tab {
|
||||
justify-content: center;
|
||||
}
|
||||
.cad-properties {
|
||||
padding: 14px 12px;
|
||||
|
||||
@@ -74,7 +74,6 @@ export interface Layer {
|
||||
|
||||
export enum LOCAL_STORAGE_KEY {
|
||||
DRAWING = 'OPEN_WEB_CAD__DRAWING',
|
||||
DROPDOWN = 'OPEN_WEB_CAD__DROPDOWN',
|
||||
}
|
||||
|
||||
export interface StartAndEndpointEntity {
|
||||
|
||||
@@ -14,22 +14,22 @@
|
||||
padding-right: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
/* 도면층 버튼 — 색은 토큰으로만, 상태는 Button이 붙이는 data-* 속성으로 가른다. */
|
||||
/* 도면층 버튼 — 목록 줄 부품(`ui-sidebar-row`) 꼴 · 상태는 Button이 붙이는 data-* 속성으로 가른다. */
|
||||
.cad-layer-manager button[data-type="regular"] {
|
||||
background: var(--cad-chrome-sunken);
|
||||
color: var(--cad-accent);
|
||||
background: transparent;
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
.cad-layer-manager button[data-type="transparent"] {
|
||||
background: transparent;
|
||||
color: var(--cad-accent);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.cad-layer-manager button:hover {
|
||||
background: var(--cad-hover);
|
||||
background: var(--color-hover);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.cad-layer-manager button[data-active="true"] {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
background: var(--color-accent-soft);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.cad-view-controls {
|
||||
@@ -41,7 +41,7 @@
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-buttons);
|
||||
background: var(--cad-chrome-raised);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
@@ -87,21 +87,7 @@ body.cad-file-dragging::after {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cad-view-controls button {
|
||||
width: 34px;
|
||||
height: 32px;
|
||||
border: 0;
|
||||
border-right: 1px solid var(--cad-line);
|
||||
background: transparent;
|
||||
font-size: 17px;
|
||||
}
|
||||
.cad-view-controls button:hover {
|
||||
background: var(--cad-hover);
|
||||
}
|
||||
.cad-view-controls__fit {
|
||||
color: var(--cad-accent);
|
||||
font-size: 15px;
|
||||
}
|
||||
/* 단추 = 아이콘 단추 부품(단 32) · 여기는 배율 글만 */
|
||||
.cad-view-controls span {
|
||||
min-width: 48px;
|
||||
color: var(--cad-text-dim);
|
||||
@@ -133,30 +119,16 @@ body.cad-file-dragging::after {
|
||||
color: var(--cad-text-muted);
|
||||
font: 11px Consolas, monospace;
|
||||
}
|
||||
/* 꼴 = 메뉴 부품(`ui-ctx-menu`) · 여기는 자리와 명령 글꼴만 */
|
||||
.cad-command-suggestions {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
bottom: 100%;
|
||||
left: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 220px;
|
||||
padding: 4px;
|
||||
background: var(--cad-chrome-raised);
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.cad-command-suggestions button {
|
||||
padding: 4px 8px;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
background: transparent;
|
||||
font: 11px Consolas, monospace;
|
||||
text-align: left;
|
||||
}
|
||||
.cad-command-suggestions button:hover {
|
||||
background: var(--cad-hover);
|
||||
.cad-command-suggestions > .ui-ctx-menu__item {
|
||||
font: 12px Consolas, monospace;
|
||||
}
|
||||
.cad-prop--wide select {
|
||||
min-width: 132px;
|
||||
@@ -180,12 +152,13 @@ body.cad-file-dragging::after {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
/* 명령 칸 = CAD 고유 꼴(토큰만) · 높이 = 크기 단 28 */
|
||||
.cad-command-area input {
|
||||
flex: 1;
|
||||
height: 26px;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--radius-buttons);
|
||||
outline: none;
|
||||
background: var(--cad-chrome-sunken);
|
||||
color: var(--cad-text);
|
||||
@@ -210,23 +183,7 @@ body.cad-file-dragging::after {
|
||||
background: var(--cad-chrome);
|
||||
border-top: 1px solid var(--cad-line);
|
||||
}
|
||||
.cad-statusbar button {
|
||||
height: 21px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
background: transparent;
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 10px;
|
||||
}
|
||||
.cad-statusbar button:hover {
|
||||
background: var(--cad-hover);
|
||||
}
|
||||
.cad-statusbar button[data-active="true"] {
|
||||
border-color: var(--cad-accent);
|
||||
background: var(--color-primary);
|
||||
color: var(--color-primary-text);
|
||||
}
|
||||
/* 켜기 = 켜기 칩 부품(`ui-toggle--chip` · 단 22) */
|
||||
.cad-statusbar__hint {
|
||||
margin-left: auto;
|
||||
color: var(--cad-text-muted);
|
||||
@@ -261,13 +218,14 @@ body.cad-file-dragging::after {
|
||||
font-size: 11px;
|
||||
color: var(--cad-text-dim);
|
||||
}
|
||||
/* 칸 = 크기 단 24(둥글기 6) · 토큰만 */
|
||||
.cad-properties-editor select,
|
||||
.cad-properties-editor input {
|
||||
height: 24px;
|
||||
min-width: 0;
|
||||
padding: 0 4px;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 3px;
|
||||
border-radius: 6px;
|
||||
background: var(--cad-chrome-sunken);
|
||||
color: var(--cad-text);
|
||||
font-size: 11px;
|
||||
@@ -292,59 +250,19 @@ body.cad-file-dragging::after {
|
||||
color: var(--cad-text);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.cad-properties__empty {
|
||||
margin: 0;
|
||||
color: var(--cad-text-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 빠른 특성 — 화면 위에 뜨는 작은 상자 */
|
||||
.cad-quick-properties {
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
top: calc(var(--cad-title-height) + var(--cad-ribbon-height) + 12px);
|
||||
right: 14px;
|
||||
width: 240px;
|
||||
padding: 8px 10px 10px;
|
||||
background: var(--cad-chrome-raised);
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 5px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.cad-quick-properties__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 11px;
|
||||
}
|
||||
.cad-quick-properties__header button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--cad-text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 블록 라이브러리 팔레트 (INSERT/BLOCK) */
|
||||
/* 빠른 특성 · 블록 라이브러리 — 꼴 = 떠 있는 판 부품(`ui-float-panel`) · 여기는 자리 · 폭만 */
|
||||
.cad-quick-properties,
|
||||
.cad-block-library {
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
top: calc(var(--cad-title-height) + var(--cad-ribbon-height) + 12px);
|
||||
right: 14px;
|
||||
width: 240px;
|
||||
padding: 8px 10px 10px;
|
||||
background: var(--cad-chrome-raised);
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 5px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.cad-block-library__empty {
|
||||
margin: 0;
|
||||
color: var(--cad-text-muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 블록 라이브러리 팔레트 (INSERT/BLOCK) */
|
||||
.cad-block-library__list {
|
||||
max-height: 220px;
|
||||
margin: 0;
|
||||
@@ -357,40 +275,26 @@ body.cad-file-dragging::after {
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.cad-block-library__list li button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--cad-text);
|
||||
font-size: 12px;
|
||||
}
|
||||
/* 블록 줄 = 목록 줄 부품(`ui-sidebar-row`) · 지우기 = 아이콘 단추 부품(단 20) · 여기는 줄 폭만 */
|
||||
.cad-block-library__name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding: 3px 4px;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cad-block-library__name:hover {
|
||||
background: var(--cad-chrome-sunken);
|
||||
}
|
||||
/* 단추 = 단추 부품(보조 · 단 24) · 여기는 둘이 폭을 나눔만 */
|
||||
.cad-block-library__actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.cad-block-library__actions button {
|
||||
.cad-block-library__actions > .ui-btn {
|
||||
flex: 1;
|
||||
padding: 4px 0;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 3px;
|
||||
background: transparent;
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 도각 자리표 패널 — 도각 편집으로 도면을 실었을 때만 뜬다 (2026-09-06 사용자 지시로
|
||||
부모 사이드바에서 캐드 안으로 옮김). 도면 오른쪽 위, 리본 아래에 붙는다. */
|
||||
부모 사이드바에서 캐드 안으로 옮김). 꼴 = 떠 있는 판 부품(`ui-float-panel`) · 여기는 자리 · 폭 · 줄 배치만. */
|
||||
.cad-frame-tokens {
|
||||
position: fixed;
|
||||
/* 오른쪽 위는 진행단계 패널이 쓴다 — 아래쪽(상태막대·명령행 위)에 붙인다. */
|
||||
@@ -398,36 +302,13 @@ body.cad-file-dragging::after {
|
||||
bottom: calc(var(--cad-status-height) + var(--cad-command-height) + 8px);
|
||||
z-index: 3;
|
||||
max-height: 46vh;
|
||||
overflow-y: auto;
|
||||
width: 236px;
|
||||
}
|
||||
|
||||
.cad-frame-tokens__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 236px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 6px;
|
||||
background: var(--cad-chrome-raised);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: var(--cad-text);
|
||||
}
|
||||
|
||||
.cad-frame-tokens__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.cad-frame-tokens__fold {
|
||||
flex: none;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--cad-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cad-frame-tokens__group {
|
||||
@@ -439,39 +320,9 @@ body.cad-file-dragging::after {
|
||||
|
||||
.cad-frame-tokens__title {
|
||||
width: 100%;
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.cad-frame-tokens__button {
|
||||
padding: 3px 7px;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 4px;
|
||||
background: var(--cad-chrome);
|
||||
color: var(--cad-text);
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cad-frame-tokens__button:hover {
|
||||
background: var(--cad-accent-soft, var(--cad-chrome-raised));
|
||||
}
|
||||
|
||||
/* 자리표 칸 크기 입력 (2026-09-06) */
|
||||
.cad-frame-tokens__size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--cad-text-dim);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.cad-frame-tokens__size input {
|
||||
/* 자리표 칸 크기 입력 (2026-09-06) — 칸 = 입력 부품(단 24) */
|
||||
.cad-frame-tokens__size > .ui-input {
|
||||
width: 64px;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid var(--cad-line);
|
||||
border-radius: 4px;
|
||||
background: var(--cad-chrome);
|
||||
color: var(--cad-text);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { setBlockLibraryVisible } from '../components/ui-state';
|
||||
import { importImageFromFile } from '../helpers/import-export-handlers/import-image-from-file';
|
||||
import { pickFile } from '../helpers/pick-file';
|
||||
import { importDrawingFile } from '../integration/dxf-clip-paste';
|
||||
import { askText } from '../integration/ui-kit';
|
||||
import { getActiveToolActor, getSelectedEntities } from '../state';
|
||||
import { Tool } from '../tools';
|
||||
import { imageImportToolStateMachine } from '../tools/image-import-tool';
|
||||
@@ -39,13 +40,15 @@ export const INSERT_COMMANDS: CadCommand[] = [
|
||||
needsSelection: true,
|
||||
run: () => {
|
||||
const entities = getSelectedEntities();
|
||||
const name = window.prompt('블록 이름', '')?.trim();
|
||||
if (!name) return '블록 작성 취소';
|
||||
void saveBlockFromEntities(name, entities).then(() => {
|
||||
// 글 묻기 = 확인창 부품(window.prompt 대신 · 553-6)
|
||||
void askText({ message: '블록 이름', okLabel: '저장' }).then(async (text) => {
|
||||
const name = text?.trim();
|
||||
if (!name) return;
|
||||
await saveBlockFromEntities(name, entities);
|
||||
toast.success(`블록 '${name}' 저장 (객체 ${entities.length}개)`);
|
||||
setBlockLibraryVisible(true);
|
||||
});
|
||||
return `블록 '${name}' 저장`;
|
||||
return '블록 이름 입력';
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 블록 라이브러리 팔레트 — 저장한 블록을 고르면 삽입 도구가 켜진다 (INSERT/BLOCK) */
|
||||
/** 블록 라이브러리 팔레트 — 저장한 블록을 고르면 삽입 도구가 켜진다 (INSERT/BLOCK) · 떠 있는 판 부품 */
|
||||
import type { FC } from 'react';
|
||||
import {
|
||||
deleteBlock,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
importBlocksFromFile,
|
||||
} from '../blocks/block-library';
|
||||
import { pickFile } from '../helpers/pick-file';
|
||||
import { askConfirm } from '../integration/ui-kit';
|
||||
import { startBlockInsert } from '../tools/insert-block-tool';
|
||||
import { isBlockLibraryVisible, setBlockLibraryVisible } from './ui-state';
|
||||
|
||||
@@ -16,57 +17,83 @@ export const BlockLibraryPanel: FC = () => {
|
||||
const blocks = getBlocks();
|
||||
|
||||
return (
|
||||
<div className="cad-block-library controls">
|
||||
<div className="cad-quick-properties__header">
|
||||
<span>블록 라이브러리</span>
|
||||
<button type="button" onClick={() => setBlockLibraryVisible(false)} title="닫기">
|
||||
<div className="ui-float-panel cad-block-library controls">
|
||||
<div className="ui-float-panel__head">
|
||||
<span className="ui-float-panel__title">블록 라이브러리</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => setBlockLibraryVisible(false)}
|
||||
title="닫기"
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="ui-float-panel__body">
|
||||
{blocks.length === 0 ? (
|
||||
<div className="ui-empty ui-empty--small">
|
||||
<p className="ui-empty__text">객체를 선택하고 BLOCK 명령으로 저장하십시오.</p>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="cad-block-library__list">
|
||||
{blocks.map((block) => (
|
||||
<li key={block.name}>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-sidebar-row cad-block-library__name"
|
||||
title={`객체 ${block.entities.length}개 — 클릭하면 삽입`}
|
||||
onClick={() => {
|
||||
setBlockLibraryVisible(false);
|
||||
void startBlockInsert(block);
|
||||
}}
|
||||
>
|
||||
{block.name}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="20"
|
||||
title="삭제"
|
||||
aria-label="삭제"
|
||||
onClick={async () => {
|
||||
if (
|
||||
await askConfirm({
|
||||
message: `블록 '${block.name}'을 삭제하시겠습니까?`,
|
||||
okLabel: '삭제',
|
||||
danger: true,
|
||||
})
|
||||
) {
|
||||
deleteBlock(block.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{blocks.length === 0 ? (
|
||||
<p className="cad-block-library__empty">객체를 선택하고 BLOCK 명령으로 저장하십시오.</p>
|
||||
) : (
|
||||
<ul className="cad-block-library__list">
|
||||
{blocks.map((block) => (
|
||||
<li key={block.name}>
|
||||
<button
|
||||
type="button"
|
||||
className="cad-block-library__name"
|
||||
title={`객체 ${block.entities.length}개 — 클릭하면 삽입`}
|
||||
onClick={() => {
|
||||
setBlockLibraryVisible(false);
|
||||
void startBlockInsert(block);
|
||||
}}
|
||||
>
|
||||
{block.name}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
title="삭제"
|
||||
onClick={() => {
|
||||
if (window.confirm(`블록 '${block.name}'을 삭제하시겠습니까?`)) {
|
||||
deleteBlock(block.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<div className="cad-block-library__actions">
|
||||
<button type="button" onClick={() => exportBlocksToFile()}>
|
||||
파일로 내보내기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => pickFile('application/json', (file) => importBlocksFromFile(file))}
|
||||
>
|
||||
파일에서 가져오기
|
||||
</button>
|
||||
<div className="cad-block-library__actions">
|
||||
<button
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => exportBlocksToFile()}
|
||||
>
|
||||
파일로 내보내기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => pickFile('application/json', (file) => importBlocksFromFile(file))}
|
||||
>
|
||||
파일에서 가져오기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -37,9 +37,9 @@ export const Button: FC<ButtonProps> = ({
|
||||
left = null,
|
||||
right = null,
|
||||
}) => {
|
||||
// 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만.
|
||||
// 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만 · 높이 = 크기 단 42(글 14 · 둥글기 8)
|
||||
const classParts = [
|
||||
'font-semibold py-4 h-10 flex flex-row justify-start w-full items-center',
|
||||
'font-semibold h-[42px] text-sm rounded-lg flex flex-row justify-start w-full items-center',
|
||||
size === 'regular' ? 'pl-2 pr-2 gap-2' : '',
|
||||
size === 'small' ? 'pl-2 pr-2 gap-0' : '',
|
||||
className || '',
|
||||
|
||||
@@ -16,7 +16,10 @@ export const CommandLine: FC = () => {
|
||||
const history = getCommandHistory();
|
||||
const instruction = currentPrompt().line || '명령을 입력하거나 도구를 선택하십시오.';
|
||||
|
||||
const suggestions = useMemo(() => matchCommandPrefixes(pick?.base ?? text).slice(0, 8), [text, pick]);
|
||||
const suggestions = useMemo(
|
||||
() => matchCommandPrefixes(pick?.base ?? text).slice(0, 8),
|
||||
[text, pick]
|
||||
);
|
||||
|
||||
const submit = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
@@ -102,10 +105,11 @@ export const CommandLine: FC = () => {
|
||||
<span className="cad-command-prompt">{instruction}</span>
|
||||
</form>
|
||||
{text.trim() && suggestions.length > 0 && (
|
||||
<div className="cad-command-suggestions">
|
||||
<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);
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { CSSProperties, FC, ReactNode } from 'react';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { LOCAL_STORAGE_KEY } from '../App.types.ts';
|
||||
import { keyboardHandler } from '../helpers/keyboard-handler.ts';
|
||||
import { Button } from './Button.tsx';
|
||||
import { Icon, IconName } from './Icon/Icon.tsx';
|
||||
|
||||
interface DropdownButtonProps {
|
||||
label?: string;
|
||||
title?: string;
|
||||
iconName?: IconName;
|
||||
iconComponent?: ReactNode;
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
buttonStyle?: CSSProperties;
|
||||
dataId: string;
|
||||
children?: ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
}
|
||||
|
||||
export const DropdownButton: FC<DropdownButtonProps> = ({
|
||||
label,
|
||||
title,
|
||||
iconName,
|
||||
iconComponent,
|
||||
className,
|
||||
style,
|
||||
buttonStyle,
|
||||
dataId,
|
||||
children,
|
||||
defaultOpen = false,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useLocalStorageState<boolean>(
|
||||
`${LOCAL_STORAGE_KEY.DROPDOWN}___${dataId}`,
|
||||
{ defaultValue: defaultOpen }
|
||||
);
|
||||
|
||||
const classParts = [
|
||||
'flex flex-col gap-2 relative',
|
||||
className || '',
|
||||
isOpen ? ' bg-slate-900' : '',
|
||||
];
|
||||
return (
|
||||
<div className={classParts.join(' ')} style={style} data-id={dataId}>
|
||||
<Button
|
||||
iconName={iconName}
|
||||
iconComponent={iconComponent}
|
||||
label={label}
|
||||
title={title}
|
||||
active={isOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
style={buttonStyle}
|
||||
className={'w-full data-[active=true]:bg-blue-950 data-[active=true]:text-white'}
|
||||
/>
|
||||
<Icon name={IconName.SolidDownSmall} className={'absolute top-2.5 right-1 text-blue-700'} />
|
||||
{isOpen && (
|
||||
<div
|
||||
className="flex flex-row flex-wrap max-w-72 gap-1 pl-1 pb-6"
|
||||
onClick={() => setIsOpen(false)}
|
||||
onKeyUp={keyboardHandler(() => setIsOpen(false))}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -139,15 +139,15 @@ export const FramePlaceholderPanel: FC = () => {
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<section className="cad-frame-tokens controls">
|
||||
<header className="cad-frame-tokens__header">
|
||||
<span>
|
||||
{folded ? '자리표 놓기' : '자리표 놓기 — 누르면 화면 가운데에 서고, 끌어서 자리를 잡습니다'}
|
||||
</span>
|
||||
<section className="ui-float-panel cad-frame-tokens controls">
|
||||
<header className="ui-float-panel__head cad-frame-tokens__header">
|
||||
<span className="ui-float-panel__title">자리표 놓기</span>
|
||||
<button
|
||||
type="button"
|
||||
className="cad-frame-tokens__fold"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="24"
|
||||
aria-expanded={!folded}
|
||||
aria-label={folded ? '자리표 패널 펴기' : '자리표 패널 접기'}
|
||||
title={folded ? '자리표 패널 펴기' : '자리표 패널 접기'}
|
||||
onClick={() => setFolded(!folded)}
|
||||
>
|
||||
@@ -155,58 +155,71 @@ export const FramePlaceholderPanel: FC = () => {
|
||||
</button>
|
||||
</header>
|
||||
{!folded && (
|
||||
<>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">글자</span>
|
||||
{TEXT_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="cad-frame-tokens__button"
|
||||
onClick={() => addTextPlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">그림 (칸에 비율 그대로 들어감)</span>
|
||||
{IMAGE_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="cad-frame-tokens__button"
|
||||
onClick={() => void addImagePlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{picked && (
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">고른 자리표 칸 크기 (mm)</span>
|
||||
<label className="cad-frame-tokens__size">
|
||||
가로
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={size.width}
|
||||
onChange={(event) => applySize(Number(event.target.value), size.height)}
|
||||
/>
|
||||
</label>
|
||||
<label className="cad-frame-tokens__size">
|
||||
세로
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={size.height}
|
||||
onChange={(event) => applySize(size.width, Number(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<div className="ui-float-panel__body cad-frame-tokens__body">
|
||||
<p className="ui-text ui-text--muted">
|
||||
누르면 화면 가운데에 서고, 끌어서 자리를 잡습니다
|
||||
</p>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="ui-text ui-text--muted cad-frame-tokens__title">글자</span>
|
||||
{TEXT_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="22"
|
||||
onClick={() => addTextPlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="ui-text ui-text--muted cad-frame-tokens__title">
|
||||
그림 (칸에 비율 그대로 들어감)
|
||||
</span>
|
||||
{IMAGE_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="22"
|
||||
onClick={() => void addImagePlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{picked && (
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="ui-text ui-text--muted cad-frame-tokens__title">
|
||||
고른 자리표 칸 크기 (mm)
|
||||
</span>
|
||||
<label className="ui-text ui-text--muted cad-frame-tokens__size">
|
||||
가로
|
||||
<input
|
||||
type="number"
|
||||
className="ui-input"
|
||||
data-size="24"
|
||||
min={1}
|
||||
value={size.width}
|
||||
onChange={(event) => applySize(Number(event.target.value), size.height)}
|
||||
/>
|
||||
</label>
|
||||
<label className="ui-text ui-text--muted cad-frame-tokens__size">
|
||||
세로
|
||||
<input
|
||||
type="number"
|
||||
className="ui-input"
|
||||
data-size="24"
|
||||
min={1}
|
||||
value={size.height}
|
||||
onChange={(event) => applySize(size.width, Number(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,17 +27,24 @@ export const InspectorPanel: FC<InspectorPanelProps> = ({ collapsed, onToggleCol
|
||||
</button>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className="cad-inspector-tabs">
|
||||
{/* 탭 부품(밑줄 탭 · 단 36) — 칸 둘이 폭을 나눠 가짐 */}
|
||||
<div className="ui-tabs cad-inspector-tabs" role="tablist">
|
||||
<button
|
||||
type="button"
|
||||
data-active={tab === 'properties'}
|
||||
role="tab"
|
||||
className={`ui-tabs__tab${tab === 'properties' ? ' is-active' : ''}`}
|
||||
data-size="36"
|
||||
aria-selected={tab === 'properties'}
|
||||
onClick={() => openInspector('properties')}
|
||||
>
|
||||
특성
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={tab === 'layers'}
|
||||
role="tab"
|
||||
className={`ui-tabs__tab${tab === 'layers' ? ' is-active' : ''}`}
|
||||
data-size="36"
|
||||
aria-selected={tab === 'layers'}
|
||||
onClick={() => openInspector('layers')}
|
||||
>
|
||||
레이어
|
||||
|
||||
@@ -18,7 +18,8 @@ interface PropertiesEditorProps {
|
||||
}
|
||||
|
||||
/** 고른 것을 사본으로 바꿈 — 되돌리기 한 칸(488) · 선 특성은 글 뺌 */
|
||||
const applyToSelection = (mutate: (entity: Entity) => void) => restyle(getSelectedEntities(), mutate);
|
||||
const applyToSelection = (mutate: (entity: Entity) => void) =>
|
||||
restyle(getSelectedEntities(), mutate);
|
||||
const applyToLines = (mutate: (entity: Entity) => void) =>
|
||||
restyle(styleTargets(getSelectedEntities()), mutate);
|
||||
|
||||
@@ -30,7 +31,11 @@ export const PropertiesEditor: FC<PropertiesEditorProps> = ({ compact = false })
|
||||
const view = selectionLineStyle();
|
||||
|
||||
if (!first) {
|
||||
return <p className="cad-properties__empty">선택된 객체가 없습니다.</p>;
|
||||
return (
|
||||
<div className="ui-empty ui-empty--small">
|
||||
<p className="ui-empty__text">선택된 객체가 없습니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const points = sampleEntityPoints(first);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 빠른 실행 단추(↶ ↷) — 리본 탭 줄 오른쪽(접기 단추 앞)에 그린다 · 숨김 목록은 DOM 만 둔다 */
|
||||
/** 빠른 실행 단추(↶ ↷) — 리본 탭 줄 오른쪽(접기 단추 앞)에 그린다 · 아이콘 단추 부품(단 20) · 숨김 목록은 DOM 만 둔다 */
|
||||
import type { FC } from 'react';
|
||||
import { getCommandById } from '../commands/registry';
|
||||
import { runCommand } from '../commands/run-command';
|
||||
@@ -16,6 +16,9 @@ export const QuickAccessBar: FC = () => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="20"
|
||||
aria-label={command.label}
|
||||
key={command.id}
|
||||
hidden={QUICK_ACCESS_HIDDEN.includes(id)}
|
||||
title={`${command.label} (${command.id})`}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 빠른 특성 — 선택이 있을 때만 화면 오른쪽 위에 뜨는 간이 특성 상자 (QUICKPROPERTIES) */
|
||||
/** 빠른 특성 — 선택이 있을 때만 화면 오른쪽 위에 뜨는 간이 특성 상자 (QUICKPROPERTIES) · 떠 있는 판 부품 */
|
||||
import type { FC } from 'react';
|
||||
import { PropertiesEditor } from './PropertiesEditor';
|
||||
import { getSelectedEntities } from '../state';
|
||||
@@ -8,14 +8,23 @@ export const QuickProperties: FC = () => {
|
||||
if (!isQuickPropertiesVisible() || getSelectedEntities().length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="cad-quick-properties controls">
|
||||
<div className="cad-quick-properties__header">
|
||||
<span>빠른 특성</span>
|
||||
<button type="button" onClick={() => setQuickPropertiesVisible(false)} title="닫기">
|
||||
<div className="ui-float-panel cad-quick-properties controls">
|
||||
<div className="ui-float-panel__head">
|
||||
<span className="ui-float-panel__title">빠른 특성</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => setQuickPropertiesVisible(false)}
|
||||
title="닫기"
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<PropertiesEditor compact />
|
||||
<div className="ui-float-panel__body">
|
||||
<PropertiesEditor compact />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 상태막대 — AutoCAD 하단 제도 보조 토글 */
|
||||
/** 상태막대 — AutoCAD 하단 제도 보조 토글 · 켜기 칩 부품(`ui-toggle--chip` · 단 22) */
|
||||
import type { FC } from 'react';
|
||||
import {
|
||||
getAngleStep,
|
||||
@@ -27,7 +27,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
<footer className="cad-statusbar controls">
|
||||
<button
|
||||
type="button"
|
||||
data-active={snap}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={snap}
|
||||
title="객체 스냅 (OSNAP)"
|
||||
onClick={() => setSnapEnabled(!snap)}
|
||||
>
|
||||
@@ -35,7 +37,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={ortho}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={ortho}
|
||||
title="직교 모드 (F8) — 90° 간격 각도 가이드"
|
||||
onClick={() => setAngleStep(ortho ? 0 : 90)}
|
||||
>
|
||||
@@ -43,7 +47,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={polar}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={polar}
|
||||
title="극좌표 추적 (F10) — 45° 간격 각도 가이드"
|
||||
onClick={() => setAngleStep(polar ? 0 : 45)}
|
||||
>
|
||||
@@ -51,7 +57,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={snapTracking}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={snapTracking}
|
||||
title="객체 스냅 추적 — 스냅점에 잠시 머물면 그 점에서 정렬선이 뻗는다"
|
||||
onClick={() => setSnapTrackingEnabled(!snapTracking)}
|
||||
>
|
||||
@@ -59,7 +67,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={grid}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={grid}
|
||||
title="그리드 표시"
|
||||
onClick={() => setGridEnabled(!grid)}
|
||||
>
|
||||
@@ -67,7 +77,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={commandLineVisible}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={commandLineVisible}
|
||||
title="명령행 표시/숨기기"
|
||||
onClick={onToggleCommandLine}
|
||||
>
|
||||
|
||||
@@ -3,12 +3,10 @@ import type { FC } from 'react';
|
||||
import { runCommandInput } from '../commands/run-command';
|
||||
import { redoCommand, undoCommand } from '../inputController/prompt-input';
|
||||
import { zoomExtents } from '../inputController/zoom-prompt';
|
||||
import {
|
||||
getScreenCanvasDrawController,
|
||||
isDrawingReadOnly,
|
||||
isHostSave,
|
||||
isViewOnly,
|
||||
} from '../state';
|
||||
import { getScreenCanvasDrawController, isDrawingReadOnly, isHostSave, isViewOnly } from '../state';
|
||||
|
||||
/** 아이콘 단추 부품(단 32) */
|
||||
const ICON_BUTTON = { className: 'ui-icon-btn ui-icon-btn--ghost', 'data-size': '32' } as const;
|
||||
|
||||
export const ViewControls: FC = () => {
|
||||
const zoom = getScreenCanvasDrawController()?.getScreenScale() ?? 1;
|
||||
@@ -19,27 +17,52 @@ export const ViewControls: FC = () => {
|
||||
<div className="cad-view-controls controls">
|
||||
{showUndo && (
|
||||
<>
|
||||
<button type="button" onClick={undoCommand} title="실행 취소 (Ctrl+Z)">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={undoCommand}
|
||||
title="실행 취소 (Ctrl+Z)"
|
||||
aria-label="실행 취소"
|
||||
>
|
||||
↶
|
||||
</button>
|
||||
<button type="button" onClick={redoCommand} title="다시 실행 (Ctrl+Y)">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={redoCommand}
|
||||
title="다시 실행 (Ctrl+Y)"
|
||||
aria-label="다시 실행"
|
||||
>
|
||||
↷
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="cad-view-controls__fit"
|
||||
{...ICON_BUTTON}
|
||||
onClick={() => zoomExtents()}
|
||||
title="범위 줌 (Z) — 도면 전체를 화면에 맞춘다"
|
||||
aria-label="범위 줌"
|
||||
>
|
||||
⛶
|
||||
</button>
|
||||
<button type="button" onClick={() => runCommandInput('ZOOMIN')} title="확대">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={() => runCommandInput('ZOOMIN')}
|
||||
title="확대"
|
||||
aria-label="확대"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<span>{Math.round(zoom * 100)}%</span>
|
||||
<button type="button" onClick={() => runCommandInput('ZOOMOUT')} title="축소">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={() => runCommandInput('ZOOMOUT')}
|
||||
title="축소"
|
||||
aria-label="축소"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,29 +1,5 @@
|
||||
/* Shift+우클릭 한 번 스냅 메뉴(`snap-menu.ts`) — 커서 자리에 세로 목록 */
|
||||
/* Shift+우클릭 한 번 스냅 메뉴(`snap-menu.ts`) — 꼴 = 메뉴 부품(`ui-ctx-menu`) · 여기는 커서 자리 · 위 겹침만 */
|
||||
.cad-snap-menu {
|
||||
position: fixed;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 160px;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
background: var(--color-surface-raised);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.cad-snap-menu > button {
|
||||
padding: 4px 10px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cad-snap-menu > button:hover,
|
||||
.cad-snap-menu > button:focus-visible {
|
||||
background: var(--color-surface-hover, rgba(127, 127, 127, 0.15));
|
||||
}
|
||||
|
||||
@@ -55,11 +55,12 @@ export function openSnapMenu(x: number, y: number, onPick: (name: string) => voi
|
||||
current = { onPick, element: null };
|
||||
if (typeof document === 'undefined') return;
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'cad-snap-menu controls';
|
||||
menu.className = 'ui-ctx-menu cad-snap-menu controls'; // 꼴 = 메뉴 부품
|
||||
menu.setAttribute('role', 'menu');
|
||||
for (const [name, label] of SNAP_MENU) {
|
||||
const item = document.createElement('button');
|
||||
item.type = 'button';
|
||||
item.className = 'ui-ctx-menu__item';
|
||||
item.setAttribute('role', 'menuitem');
|
||||
item.textContent = `${label} (${name})`;
|
||||
item.addEventListener('click', () => pickSnapMenu(name));
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 부품 상자(`ui_template/`) CAD 쪽 한 벌(553-6) — CAD 겉도 앱 화면과 같은 부품 꼴을 씀.
|
||||
* 부품 CSS 를 싣고 단추 · 확인창 기본 꼴(`injectBaseStyles`)을 한 번 넣음 · 확인창 · 글 묻기를 다시 냄.
|
||||
* React 화면은 부품 클래스(`ui-btn` · `ui-icon-btn` · `ui-toggle--chip` · `ui-tabs` · `ui-float-panel` · `ui-ctx-menu` · `ui-empty` · `ui-text` · `ui-input`)와
|
||||
* `data-size` 단을 직접 붙임 · 자리 · 폭은 App.css 의 `cad-*` 가 맡음 · 도면 그림 색(`--draw-*`)은 안 건드림.
|
||||
*/
|
||||
import '../../../../ui_template/ui_template_tabs.css';
|
||||
import '../../../../ui_template/ui_template_toggle.css';
|
||||
import '../../../../ui_template/ui_template_float_panel.css';
|
||||
import '../../../../ui_template/ui_template_context_menu.css';
|
||||
import '../../../../ui_template/ui_template_empty.css';
|
||||
import '../../../../ui_template/ui_template_input.css';
|
||||
import '../../../../ui_template/ui_template_text.css';
|
||||
import { injectBaseStyles } from '../../../../ui_template/ui_template_elements_styles.ts';
|
||||
|
||||
export { askConfirm, askText } from '../../../../ui_template/ui_template_modal.ts';
|
||||
|
||||
injectBaseStyles();
|
||||
@@ -3,6 +3,8 @@ import { Point } from '@flatten-js/core';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { Actor, type MachineSnapshot } from 'xstate';
|
||||
// 부품 상자 CSS 를 앱 CSS 보다 먼저 실음 — 자리 · 폭은 뒤에 오는 App.css 의 `cad-*` 가 덮음
|
||||
import './integration/ui-kit.ts';
|
||||
import { SNAP_POINT_DISTANCE } from './App.consts';
|
||||
import App from './App.tsx';
|
||||
import { TOOL_STATE_MACHINES } from './commands/registry';
|
||||
|
||||
@@ -129,10 +129,14 @@ const Panel: FC<{
|
||||
{overflowCommands.length ? ' ▾' : ''}
|
||||
</button>
|
||||
{overflowAt && overflowCommands.length > 0 && (
|
||||
<div className="cad-ribbon-overflow" style={{ top: overflowAt.top, left: overflowAt.left }}>
|
||||
<div
|
||||
className="ui-ctx-menu cad-ribbon-overflow"
|
||||
style={{ top: overflowAt.top, left: overflowAt.left }}
|
||||
>
|
||||
{overflowCommands.map((command) => (
|
||||
<button
|
||||
type="button"
|
||||
className="ui-ctx-menu__item"
|
||||
key={command.id}
|
||||
title={command.hint}
|
||||
onClick={() => {
|
||||
@@ -228,7 +232,9 @@ export const Ribbon: FC<RibbonProps> = ({ activeTool, widgets, onCommandRun }) =
|
||||
<QuickAccessBar />
|
||||
<button
|
||||
type="button"
|
||||
className="cad-ribbon-tabs__collapse"
|
||||
className="ui-icon-btn ui-icon-btn--ghost cad-ribbon-tabs__collapse"
|
||||
data-size="20"
|
||||
aria-label={collapsed ? '리본 펼치기' : '리본 접기'}
|
||||
onClick={() => {
|
||||
setPeek(false);
|
||||
setRibbonCollapsed(!collapsed);
|
||||
|
||||
@@ -25,6 +25,15 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src", "../../A00_Common/key_shortcut.ts", "../../A00_Common/input_prefs.ts"],
|
||||
"include": [
|
||||
"src",
|
||||
"../../A00_Common/key_shortcut.ts",
|
||||
"../../A00_Common/input_prefs.ts",
|
||||
"../../ui_template/ui_template_modal.ts",
|
||||
"../../ui_template/ui_template_button.ts",
|
||||
"../../ui_template/ui_template_icons.ts",
|
||||
"../../ui_template/ui_template_elements_base.ts",
|
||||
"../../ui_template/ui_template_elements_styles.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
padding: 10px 18px;
|
||||
border-radius: var(--radius-cards);
|
||||
background-color: var(--color-surface);
|
||||
box-shadow: 0 2px 10px rgb(0 0 0 / 18%);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: var(--color-text);
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -38,10 +38,10 @@
|
||||
transform: translateX(-50%);
|
||||
max-width: 70%;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--color-danger, #d33);
|
||||
border: 1px solid var(--color-danger);
|
||||
border-radius: var(--radius-cards);
|
||||
background-color: var(--color-surface);
|
||||
color: var(--color-danger, #d33);
|
||||
color: var(--color-danger);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@@ -96,11 +96,10 @@
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 단추 = 단추 부품(단 24) · 여기는 넷이 폭을 나눔만 */
|
||||
.cad-frame-edit__buttons > button {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
/* 작도 영역 숫자 칸 넷(169-1 · `cad_host_area.ts`) — 두 줄 두 칸 */
|
||||
|
||||
@@ -86,6 +86,7 @@ export function createFrameTemplateEditor<D extends CadHostDrawing>(
|
||||
const finishButton = createButton({
|
||||
label: "완료",
|
||||
variant: "filled",
|
||||
size: 24,
|
||||
onClick: () => void finish(),
|
||||
});
|
||||
/**
|
||||
@@ -95,6 +96,7 @@ export function createFrameTemplateEditor<D extends CadHostDrawing>(
|
||||
const resetButton = createButton({
|
||||
label: "기본 도각 복원",
|
||||
variant: "ghost",
|
||||
size: 24,
|
||||
onClick: () => void resetToDefault(),
|
||||
});
|
||||
/**
|
||||
@@ -111,12 +113,14 @@ export function createFrameTemplateEditor<D extends CadHostDrawing>(
|
||||
const importButton = createButton({
|
||||
label: "파일 불러오기",
|
||||
variant: "ghost",
|
||||
size: 24,
|
||||
onClick: () => fileInput.click(),
|
||||
});
|
||||
|
||||
const cancelButton = createButton({
|
||||
label: "취소",
|
||||
variant: "ghost",
|
||||
size: 24,
|
||||
onClick: () => leave(),
|
||||
});
|
||||
const bannerButtons = document.createElement("div");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* ui_template/cad_host/cad_host_tabs — CAD 를 감싸는 공용 탭 줄(82-1 · M02 구조물 도면 · B07). */
|
||||
/* ui_template/cad_host/cad_host_tabs — CAD 를 감싸는 공용 탭 줄(82-1 · M02 구조물 도면 · B07).
|
||||
문서 탭 = 꼴 그대로 · 공통 값 · 크기 단만(탭 24 · × 18 · + 20 · 553-6). */
|
||||
|
||||
.cad-tabs {
|
||||
display: flex;
|
||||
@@ -18,8 +19,10 @@
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
max-width: 220px;
|
||||
padding: 4px 8px;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-bottom: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
@@ -68,7 +71,12 @@
|
||||
|
||||
.cad-tabs__x,
|
||||
.cad-tabs__add {
|
||||
padding: 0 4px;
|
||||
display: inline-grid;
|
||||
flex: none;
|
||||
place-items: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
@@ -80,12 +88,14 @@
|
||||
|
||||
.cad-tabs__x:hover,
|
||||
.cad-tabs__add:hover {
|
||||
background: var(--color-surface-raised);
|
||||
background: var(--color-hover);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.cad-tabs__add {
|
||||
align-self: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 2px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user