feat(B07): GROUP 묶음 JSON 저장·복원, 줌 로그 제거

- `groupId` 를 도면 JSON 에 실어 [저장] 뒤 다시 연 도면에서도 묶음 유지.
  엔티티 13종의 `toJson` 대신 직렬화 한 곳(export/import)에서 붙이고,
  복원은 순번이 아니라 id 로 — `compact` 가 null 을 걷어 index 가 어긋남.
- `setScreenScale` 의 매 호출 `console.log` 제거 — 줌·팬마다 콘솔을 덮었음.
- 검증: `group-id-roundtrip.test.ts` 신규, `npx vitest run` 87건 중 81 passed,
  실패 6건은 작업 전과 동일한 기존 결함. `npm run build` 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-02 06:33:19 +09:00
co-authored by Claude Opus 5
parent 019a742948
commit 946d61f8df
5 changed files with 43 additions and 2 deletions
@@ -69,7 +69,6 @@ export class ScreenCanvasDrawController implements DrawController {
}
public setScreenScale(newScreenScale: number) {
console.log(`set screen scale: ${newScreenScale}`);
this.screenScale = newScreenScale;
triggerReactUpdate(StateVariable.screenZoom);
}
@@ -86,6 +86,8 @@ export interface JsonEntity<TShapeJsonData = ShapeJsonData> {
lineWidth: number;
lineDash?: number[];
layerId: string;
/** GROUP 묶음 식별자 — 저장·복원에서 그대로 실어 나른다 */
groupId?: string;
shapeData: TShapeJsonData | null;
children?: JsonEntity<ShapeJsonData>[];
}
@@ -14,7 +14,11 @@ export async function exportEntitiesToJsonFile() {
export async function exportEntitiesAndLayersToJsonString() {
const entities = getEntities();
const jsonEntities = entities.map((entity) => entity.toJson());
// groupId는 엔티티 13종의 toJson을 다 고치는 대신 여기 한 곳에서 붙인다.
const jsonEntities = entities.map(async (entity) => {
const json = await entity.toJson();
return json && entity.groupId ? { ...json, groupId: entity.groupId } : json;
});
const jsonDrawingFile: JsonDrawingFileSerialized = {
entities: compact(await Promise.all(jsonEntities)), // TODO use a mapLimit to avoid overloading the event loop
layers: getLayers(),
@@ -0,0 +1,30 @@
import { Point } from '@flatten-js/core';
import { describe, expect, it } from 'vitest';
import { LineEntity } from '../../entities/LineEntity.ts';
import { setEntities, setLayers } from '../../state.ts';
import { exportEntitiesAndLayersToJsonString } from './export-entities-to-json.ts';
import { getEntitiesAndLayersFromJsonString } from './import-entities-from-json.ts';
describe('groupId 저장·복원', () => {
it('GROUP 묶음이 JSON 왕복 뒤에도 한 덩어리로 남는다', async () => {
const layer = { id: 'layer-1', isLocked: false, isVisible: true, name: '작업' };
const groupId = 'group-1';
const first = new LineEntity(layer.id, new Point(0, 0), new Point(1, 0));
const second = new LineEntity(layer.id, new Point(1, 0), new Point(1, 1));
const loner = new LineEntity(layer.id, new Point(2, 2), new Point(3, 3));
first.groupId = groupId;
second.groupId = groupId;
setLayers([layer]);
setEntities([first, second, loner]);
const restored = await getEntitiesAndLayersFromJsonString(
await exportEntitiesAndLayersToJsonString()
);
const byId = new Map(restored.entities.map((entity) => [entity.id, entity]));
expect(byId.get(first.id)?.groupId).toBe(groupId);
expect(byId.get(second.id)?.groupId).toBe(groupId);
expect(byId.get(loner.id)?.groupId).toBeUndefined();
});
});
@@ -77,6 +77,12 @@ export async function getEntitiesAndLayersFromJsonObject(
);
const entities = compact(await Promise.all(entityPromises));
// 순번이 아니라 id로 되돌린다 — compact가 null을 걷어 index가 어긋날 수 있다.
const groupIdById = new Map(data.entities.map((entity) => [entity.id, entity.groupId]));
for (const entity of entities) {
const groupId = groupIdById.get(entity.id);
if (groupId) entity.groupId = groupId;
}
let layers = data.layers;
if (data.layers.length === 0) {
layers = [getNewLayer()];