Skip to content

Commit dd61e06

Browse files
committed
feat: add layers to save to / load from local storage
1 parent 80ffcb2 commit dd61e06

File tree

9 files changed

+114
-79
lines changed

9 files changed

+114
-79
lines changed

src/App.types.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,52 @@ import type {Arc, Circle, Point, Polygon, Segment} from '@flatten-js/core';
33
export type Shape = Polygon | Segment | Point | Circle | Arc;
44

55
export enum SnapPointType {
6-
AngleGuide = 'AngleGuide',
7-
LineEndPoint = 'LineEndPoint',
8-
Intersection = 'Intersection',
9-
CircleCenter = 'CircleCenter',
10-
CircleCardinal = 'CircleCardinal',
11-
CircleTangent = 'CircleTangent',
12-
LineMidPoint = 'LineMidPoint',
13-
Point = 'Point',
6+
AngleGuide = 'AngleGuide',
7+
LineEndPoint = 'LineEndPoint',
8+
Intersection = 'Intersection',
9+
CircleCenter = 'CircleCenter',
10+
CircleCardinal = 'CircleCardinal',
11+
CircleTangent = 'CircleTangent',
12+
LineMidPoint = 'LineMidPoint',
13+
Point = 'Point',
1414
}
1515

1616
export interface SnapPoint {
17-
point: Point;
18-
type: SnapPointType;
17+
point: Point;
18+
type: SnapPointType;
1919
}
2020

2121
export type SnapPointConfig = Record<SnapPointType, boolean>;
2222

2323
export interface HoverPoint {
24-
snapPoint: SnapPoint;
25-
milliSecondsHovered: number;
24+
snapPoint: SnapPoint;
25+
milliSecondsHovered: number;
2626
}
2727

2828
export enum MouseButton {
29-
Left = 0, // Main button pressed, usually the left button or the un-initialized state
30-
Middle = 1, // Auxiliary button pressed, usually the wheel button or the middle button (if present)
31-
Right = 2, // Secondary button pressed, usually the right button
32-
Back = 3, // Fourth button, typically the Browser Back button
33-
Forward = 4, // Fifth button, typically the Browser Forward button
29+
Left = 0, // Main button pressed, usually the left button or the un-initialized state
30+
Middle = 1, // Auxiliary button pressed, usually the wheel button or the middle button (if present)
31+
Right = 2, // Secondary button pressed, usually the right button
32+
Back = 3, // Fourth button, typically the Browser Back button
33+
Forward = 4, // Fifth button, typically the Browser Forward button
3434
}
3535

3636
export enum HtmlEvent {
37-
UPDATE_STATE = 'UPDATE_STATE',
37+
UPDATE_STATE = 'UPDATE_STATE',
3838
}
3939

4040
export interface StateMetaData {
41-
instructions: string;
41+
instructions: string;
4242
}
4343

4444
export interface Layer {
45-
id: string;
46-
name: string;
47-
isVisible: boolean;
48-
isLocked: boolean;
45+
id: string;
46+
name: string;
47+
isVisible: boolean;
48+
isLocked: boolean;
49+
}
50+
51+
export enum LOCAL_STORAGE_KEY {
52+
DRAWING = 'OPEN_WEB_CAD__DRAWING',
53+
DROPDOWN = 'OPEN_WEB_CAD__DROPDOWN',
4954
}

src/components/DropdownButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {CSSProperties, FC, ReactNode} from 'react';
22
import useLocalStorageState from 'use-local-storage-state';
3-
import {LOCAL_STORAGE_KEY} from '../helpers/import-export-handlers/export-entities-to-local-storage.ts';
4-
import {keyboardHandler} from "../helpers/keyboard-handler.ts";
3+
import {LOCAL_STORAGE_KEY} from '../App.types.ts';
4+
import {keyboardHandler} from '../helpers/keyboard-handler.ts';
55
import {Button} from './Button.tsx';
66
import {Icon, IconName} from './Icon/Icon.tsx';
77

src/components/LayerManager.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {FC, MouseEvent} from 'react';
22
import type {Layer} from '../App.types.ts';
3+
import {getNewLayer} from '../helpers/get-new-layer.ts';
34
import {getEntities, getLayers, getSelectedEntities, setEntities, setSelectedEntityIds,} from '../state.ts';
45
import {Button} from './Button';
56
import {IconName} from './Icon/Icon.tsx';
@@ -68,12 +69,7 @@ export const LayerManager: FC<LayerManagerProps> = ({
6869

6970
const handleCreateNewLayer = (evt: MouseEvent): void => {
7071
evt.stopPropagation();
71-
const newLayer: Layer = {
72-
id: crypto.randomUUID(),
73-
isLocked: false,
74-
isVisible: true,
75-
name: `New layer ${getLayers().length}${1}`,
76-
};
72+
const newLayer: Layer = getNewLayer();
7773
setLayers([...getLayers(), newLayer]);
7874
};
7975

src/helpers/get-new-layer.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type {Layer} from '../App.types.ts';
2+
import {getLayers} from '../state.ts';
3+
4+
export function getNewLayer(): Layer {
5+
return {
6+
id: crypto.randomUUID(),
7+
isLocked: false,
8+
isVisible: true,
9+
name: `New layer ${getLayers().length}${1}`,
10+
};
11+
}
Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
1-
import {saveAs} from 'file-saver';
21
import {compact} from 'es-toolkit';
3-
import {getEntities} from '../../state';
4-
import type {JsonEntity} from '../../entities/Entity';
2+
import {saveAs} from 'file-saver';
3+
import type {Layer} from '../../App.types.ts';
4+
import type {Entity, JsonEntity} from '../../entities/Entity';
5+
import {getEntities, getLayers} from '../../state';
56

67
export async function exportEntitiesToJsonFile() {
7-
const json = await exportEntitiesToJsonString();
8+
const json = await exportEntitiesAndLayersToJsonString();
89

9-
const blob = new Blob([json], { type: 'text/json;charset=utf-8' });
10-
saveAs(blob, 'open-web-cad--drawing.json');
10+
const blob = new Blob([json], { type: 'text/json;charset=utf-8' });
11+
saveAs(blob, 'open-web-cad--drawing.json');
1112
}
1213

13-
export async function exportEntitiesToJsonString() {
14-
const entities = getEntities();
14+
export async function exportEntitiesAndLayersToJsonString() {
15+
const entities = getEntities();
16+
17+
const jsonEntities = entities.map((entity) => entity.toJson());
18+
const jsonDrawingFile: JsonDrawingFileSerialized = {
19+
entities: compact(await Promise.all(jsonEntities)), // TODO use a mapLimit to avoid overloading the event loop
20+
layers: getLayers(),
21+
};
22+
return JSON.stringify(jsonDrawingFile, null, 2);
23+
}
1524

16-
const jsonEntities = entities.map(entity => entity.toJson());
17-
const jsonDrawingFile: JsonDrawingFile = {
18-
entities: compact(await Promise.all(jsonEntities)), // TODO use a mapLimit to avoid overloading the event loop
19-
};
20-
return JSON.stringify(jsonDrawingFile, null, 2);
25+
export interface JsonDrawingFileSerialized {
26+
entities: JsonEntity[];
27+
layers: Layer[];
2128
}
2229

23-
export interface JsonDrawingFile {
24-
entities: JsonEntity[];
30+
export interface JsonDrawingFileDeserialized {
31+
entities: Entity[];
32+
layers: Layer[];
2533
}
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import {exportEntitiesToJsonString} from "./export-entities-to-json.ts";
2-
3-
export enum LOCAL_STORAGE_KEY {
4-
DRAWING = 'OPEN_WEB_CAD__DRAWING',
5-
DROPDOWN = 'OPEN_WEB_CAD__DROPDOWN',
6-
};
1+
import {LOCAL_STORAGE_KEY} from '../../App.types.ts';
2+
import {exportEntitiesAndLayersToJsonString} from './export-entities-to-json.ts';
73

84
export async function exportEntitiesToLocalStorage() {
9-
const json = await exportEntitiesToJsonString();
5+
const json = await exportEntitiesAndLayersToJsonString();
106

11-
localStorage.setItem(LOCAL_STORAGE_KEY.DRAWING, json);
7+
localStorage.setItem(LOCAL_STORAGE_KEY.DRAWING, json);
128
}

src/helpers/import-export-handlers/import-entities-from-json.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type {JsonDrawingFile} from './export-entities-to-json';
21
import {compact} from 'es-toolkit';
3-
import {type Entity, EntityName, type JsonEntity} from '../../entities/Entity';
42
import {ArcEntity, type ArcJsonData} from '../../entities/ArcEntity';
53
import {CircleEntity, type CircleJsonData} from '../../entities/CircleEntity';
4+
import {type Entity, EntityName, type JsonEntity} from '../../entities/Entity';
65
import {LineEntity, type LineJsonData} from '../../entities/LineEntity';
76
import {PointEntity, type PointJsonData} from '../../entities/PointEntity';
8-
import {RectangleEntity, type RectangleJsonData,} from '../../entities/RectangleEntity';
9-
import {setEntities} from '../../state';
7+
import {RectangleEntity, type RectangleJsonData} from '../../entities/RectangleEntity';
8+
import {setEntities, setLayers} from '../../state';
9+
import type {JsonDrawingFileDeserialized, JsonDrawingFileSerialized} from './export-entities-to-json';
1010

1111
/**
1212
* Open a file selection dialog to select *.json files
@@ -21,16 +21,19 @@ export function importEntitiesFromJsonFile(file: File | null | undefined) {
2121
const reader = new FileReader();
2222
reader.addEventListener('load', async () => {
2323
const json = reader.result as string;
24-
const entities = await getEntitiesFromJsonString(json);
25-
setEntities(entities);
24+
const file = await getEntitiesAndLayersFromJsonString(json);
25+
setEntities(file.entities);
26+
setLayers(file.layers);
2627
resolve();
2728
});
2829
reader.readAsText(file, 'utf-8');
2930
});
3031
}
3132

32-
export async function getEntitiesFromJsonString(json: string): Promise<Entity[]> {
33-
const data = JSON.parse(json) as JsonDrawingFile;
33+
export async function getEntitiesAndLayersFromJsonString(
34+
json: string
35+
): Promise<JsonDrawingFileDeserialized> {
36+
const data = JSON.parse(json) as JsonDrawingFileSerialized;
3437

3538
if (!data.entities) {
3639
throw new Error('Invalid JSON file');
@@ -57,5 +60,9 @@ export async function getEntitiesFromJsonString(json: string): Promise<Entity[]>
5760
})
5861
);
5962

60-
return compact(await Promise.all(entityPromises));
63+
const entities = compact(await Promise.all(entityPromises));
64+
return {
65+
entities,
66+
layers: data.layers,
67+
};
6168
}
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
import {getEntitiesFromJsonString} from "./import-entities-from-json.ts";
2-
import {setEntities} from "../../state.ts";
3-
import type {Entity} from "../../entities/Entity.ts";
1+
import {LOCAL_STORAGE_KEY} from '../../App.types.ts';
2+
import {setEntities, setLayers} from '../../state.ts';
3+
import {getNewLayer} from '../get-new-layer.ts';
4+
import {getEntitiesAndLayersFromJsonString} from './import-entities-from-json.ts';
5+
import type {JsonDrawingFileDeserialized} from "./export-entities-to-json.ts";
46

5-
export enum LOCAL_STORAGE_KEY {
6-
DRAWING = 'OPEN_WEB_CAD__DRAWING',
7-
};
8-
9-
export async function importEntitiesFromLocalStorage(): Promise<void> {
10-
const entities = await getEntitiesFromLocalStorage();
11-
setEntities(entities);
7+
export async function importEntitiesAndLayersFromLocalStorage(): Promise<void> {
8+
const file = await getEntitiesAndLayersFromLocalStorage();
9+
setEntities(file.entities);
10+
setLayers(file.layers);
1211
}
1312

14-
export async function getEntitiesFromLocalStorage(): Promise<Entity[]> {
13+
export async function getEntitiesAndLayersFromLocalStorage(): Promise<JsonDrawingFileDeserialized> {
1514
const json = localStorage.getItem(LOCAL_STORAGE_KEY.DRAWING);
1615
if (!json) {
17-
return [];
16+
return {
17+
entities: [],
18+
layers: [getNewLayer()],
19+
};
1820
}
19-
return (await getEntitiesFromJsonString(json)) || [];
21+
22+
const file = (await getEntitiesAndLayersFromJsonString(json)) || [];
23+
return file;
2024
}

src/main.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import App from './App.tsx';
77
import {ScreenCanvasDrawController} from './drawControllers/screenCanvas.drawController';
88
import {draw} from './helpers/draw';
99
import {findClosestEntity} from './helpers/find-closest-entity';
10-
import {getEntitiesFromLocalStorage,} from "./helpers/import-export-handlers/import-entities-from-local-storage.ts";
10+
import {getNewLayer} from './helpers/get-new-layer.ts';
11+
import type {JsonDrawingFileDeserialized} from './helpers/import-export-handlers/export-entities-to-json.ts';
12+
import {getEntitiesAndLayersFromLocalStorage} from './helpers/import-export-handlers/import-entities-from-local-storage.ts';
1113
import {trackHoveredSnapPoint} from './helpers/track-hovered-snap-points';
1214
import {InputController} from './inputController/input-controller.ts';
1315
import {
@@ -25,6 +27,7 @@ import {
2527
setHoveredSnapPoints,
2628
setInputController,
2729
setLastDrawTimestamp,
30+
setLayers,
2831
setScreenCanvasDrawController,
2932
} from './state';
3033
import {Tool} from './tools';
@@ -112,8 +115,13 @@ function initApplication() {
112115
setEntities([], true); // Creates the first undo entry
113116

114117
// Load the last drawing from local storage
115-
getEntitiesFromLocalStorage().then((entities) => {
116-
setEntities(entities, true);
118+
getEntitiesAndLayersFromLocalStorage().then((file: JsonDrawingFileDeserialized) => {
119+
setEntities(file.entities, true);
120+
let layers = file.layers;
121+
if (layers.length === 0) {
122+
layers = [getNewLayer()];
123+
}
124+
setLayers(layers);
117125
});
118126
const screenCanvasDrawController = new ScreenCanvasDrawController(context);
119127
setScreenCanvasDrawController(screenCanvasDrawController);

0 commit comments

Comments
 (0)