Files
Aislo/B07_DesignDetail/openwebcad/src/integration/dxf-clip-paste.ts
T

107 lines
4.1 KiB
TypeScript

/**
* 오토캐드 도형 붙이기 — 두 길 모두 서버가 DXF 를 도형으로 바꿔 현재 도면에 원좌표 그대로 붙이고
* 고른 상태로 둔다(되돌리기 한 번에 빠짐).
* - 끌어다 놓기: 오토캐드 WBLOCK(DWG) · DXFOUT(DXF) 로 저장한 파일, 또는 도면 파일 통째.
* 끄는 동안 `body.cad-file-dragging` 으로 흐린 테두리.
* - Ctrl+V: 시스템 클립보드 글이 DXF 면 같은 길 · 아니면 앱 안 붙여넣기(PASTECLIP).
* 키 처리(`input-controller.ts`)는 Ctrl+V 의 기본 동작을 막지 않아 브라우저가 `paste` 를 쏜다 —
* `paste` 이벤트의 글은 권한 없이 읽힘(iframe 안에서도).
*/
import { toast } from 'react-toastify';
import { runCommandInput } from '../commands/run-command';
import { hasClipboardContent } from '../helpers/cad-clipboard';
import { getEntitiesAndLayersFromJsonObject } from '../helpers/import-export-handlers/import-entities-from-json';
import {
addEntities,
getLayers,
isDrawingReadOnly,
setLayers,
setSelectedEntityIds,
} from '../state';
import type { JsonEntity } from '../entities/Entity';
import { type ClipLayer, isDxfText, prepareClip } from './dxf-clip';
const DXF_CLIP_URL = '/api/projects/dxf-clip';
export async function pasteDxfText(text: string): Promise<number> {
return pasteServerClip(
await fetch(DXF_CLIP_URL, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
})
);
}
export async function pasteDrawingFile(file: File): Promise<number> {
const form = new FormData();
form.append('file', file);
return pasteServerClip(
await fetch(`${DXF_CLIP_URL}/file`, { method: 'POST', credentials: 'include', body: form })
);
}
async function pasteServerClip(response: Response): Promise<number> {
const payload = (await response.json().catch(() => ({}))) as {
entities?: JsonEntity[];
layers?: ClipLayer[];
message?: string;
};
if (!response.ok || !payload.entities) {
throw new Error(payload.message ?? `HTTP ${response.status}`);
}
const { entities, newLayers } = prepareClip(payload.entities, payload.layers ?? [], getLayers());
const drawing = await getEntitiesAndLayersFromJsonObject({ entities, layers: getLayers() });
if (newLayers.length) setLayers([...getLayers(), ...newLayers]);
addEntities(drawing.entities, true);
setSelectedEntityIds(drawing.entities.map((entity) => entity.id));
return drawing.entities.length;
}
function reportPaste(job: Promise<number>): void {
job
.then((count) => toast.success(`오토캐드 도형 ${count}개를 붙여넣었습니다.`))
.catch((error: unknown) =>
toast.error(error instanceof Error ? error.message : '도형을 붙여넣지 못했습니다.')
);
}
function refuseReadOnly(): boolean {
if (!isDrawingReadOnly()) return false;
toast.info('볼 수만 있는 도면입니다 — 붙여넣을 수 없습니다.');
return true;
}
export function registerDxfClipPaste(): void {
document.addEventListener('paste', (event: ClipboardEvent) => {
if ((event.target as HTMLElement | null)?.closest?.('input, textarea, select')) return;
const text = event.clipboardData?.getData('text/plain') ?? '';
event.preventDefault();
if (!isDxfText(text)) {
if (hasClipboardContent()) runCommandInput('PASTECLIP');
return;
}
if (!refuseReadOnly()) reportPaste(pasteDxfText(text));
});
// 파일을 끌 때만 — 앱 안 끌기(글 · 패널)는 건드리지 않음. 기본 동작(파일 열기로 화면 이동)은 막음.
const dragging = (event: DragEvent) => event.dataTransfer?.types.includes('Files') ?? false;
const setDragging = (on: boolean) => document.body.classList.toggle('cad-file-dragging', on);
document.addEventListener('dragover', (event) => {
if (!dragging(event)) return;
event.preventDefault();
setDragging(true);
});
document.addEventListener('dragleave', (event) => {
if (!event.relatedTarget) setDragging(false);
});
document.addEventListener('drop', (event) => {
if (!dragging(event)) return;
event.preventDefault();
setDragging(false);
const file = event.dataTransfer?.files[0];
if (file && !refuseReadOnly()) reportPaste(pasteDrawingFile(file));
});
}