Skip to content

Commit

Permalink
refactor: import/export scene and union config
Browse files Browse the repository at this point in the history
  • Loading branch information
nagy-nabil committed Sep 11, 2023
1 parent 726c130 commit e21870e
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 371 deletions.
72 changes: 9 additions & 63 deletions src/actions/ExportScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,76 +33,22 @@ export class ActionExportScene extends Command {
super();
}

/**
* elements in the store contains props that is needed when exporting them, or when the user import them again so we clean those props up to propably make the exported size small
*/
private static cleanupItem<T extends ZagyCanvasElement>(el: T): CleanedElement<T> {
const baseTemp: CleanedElement<ZagyCanvasElement> = {
id: el.id,
shape: el.shape,
x: el.x,
y: el.y,
endX: el.endX,
endY: el.endY,
options: el.options,
};
if (isImage(el)) {
return {
...baseTemp,
shape: el.shape,
image: el.image,
} satisfies CleanedElement<ZagyCanvasImageElement> as unknown as CleanedElement<T>;
} else if (isRect(el)) {
return {
...baseTemp,
shape: el.shape,
options: el.options,
} satisfies CleanedElement<ZagyCanvasRectElement> as unknown as CleanedElement<T>;
} else if (isLine(el)) {
return {
...baseTemp,
shape: el.shape,
point1: el.point1,
point2: el.point2,
options: el.options,
} satisfies CleanedElement<ZagyCanvasLineElement> as unknown as CleanedElement<T>;
} else if (isText(el)) {
return {
...baseTemp,
shape: el.shape,
text: el.text,
options: el.options,
} satisfies CleanedElement<ZagyCanvasTextElement> as unknown as CleanedElement<T>;
} else if (isHanddrawn(el)) {
return {
...baseTemp,
shape: el.shape,
paths: el.paths,
options: el.options,
} satisfies CleanedElement<ZagyCanvasHandDrawnElement> as unknown as CleanedElement<T>;
} else {
throw new Error("EXPORT SCENE: cannot export unknown item");
}
}

public async execute() {
const { selectedElements, elements } = useStore.getState();
try {
// clean up the items
const portable: ZagyPortableT = {
// don't copy dump text into the user clipboard if there's no data to copy
if (elements.length === 0 && selectedElements.length === 0) return;
// cleaned up the items
const portable: ZagyPortableT<unknown> = {
type: "ZagyPortableContent",
version: 1,
elements: [],
};

// choose which items to clean up
if (this.onlySelected) {
selectedElements.forEach((el) =>
portable.elements.push(ActionExportScene.cleanupItem(el)),
);
} else {
elements.forEach((el) => portable.elements.push(ActionExportScene.cleanupItem(el)));
}
// don't copy dump text into the user clipboard if there's no data to copy
if (portable.elements.length === 0) return;
(this.onlySelected ? selectedElements : elements).forEach((el) =>
portable.elements.push(el.copy()),
);

// choose export mechanism
if (this.dest === DestOpts.CLIPBOARD) {
Expand Down
88 changes: 24 additions & 64 deletions src/actions/importElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import {
generateImageElement,
generateTextElement,
} from "@/utils/canvas/generateElement";
import { Point, getBoundingRect, normalizeToGrid } from "@/utils";
import { Point, getBoundingRect, normalizePos, normalizeToGrid } from "@/utils";
import {
ZagyCanvasElement,
ZagyPortableT,
ZagyShape,
isHanddrawn,
isImage,
isLine,
isRect,
isText,
isZagyPortable,
} from "@/types/general";
import { HandDrawn, Line, Rectangle, Text, ZagyImage } from "@/utils/canvas/shapes";

export class ActionImportElements extends UndoableCommand {
#importedIds: Set<string>;
Expand All @@ -35,46 +37,28 @@ export class ActionImportElements extends UndoableCommand {
* to do so i will create bounding rect between pasted elements to know each point difference from the original bounding rect
* create new bounding rect on the current mouse position, then calc each element new position, element x = newBounding.x + (oldBounding.x - el.x);
*/
private updateElementsCoords(els: ZagyPortableT["elements"]): void {
private updateElementsCoords(els: ZagyShape[]): void {
const { getPosition } = useStore.getState();
const newBoundingStart = normalizeToGrid(getPosition(), this.mouseCoords);
const oldBounding = getBoundingRect(...els);
for (const el of els) {
const xDiff = el.endX - el.x;
const yDiff = el.endY - el.y;
el.x = newBoundingStart[0] + (el.x - oldBounding[0][0]);
el.y = newBoundingStart[1] + (el.y - oldBounding[0][1]);
el.endX = el.x + xDiff;
el.endY = el.y + yDiff;
if (isLine(el)) {
el.point1 = [
newBoundingStart[0] + (el.point1[0] - oldBounding[0][0]),
newBoundingStart[1] + (el.point1[1] - oldBounding[0][1]),
];
el.point2 = [
newBoundingStart[0] + (el.point2[0] - oldBounding[0][0]),
newBoundingStart[1] + (el.point2[1] - oldBounding[0][1]),
];
} else if (isHanddrawn(el)) {
el.paths = el.paths.map((path) => [
newBoundingStart[0] + (path[0] - oldBounding[0][0]),
newBoundingStart[1] + (path[1] - oldBounding[0][1]),
]);
}
const bounding = el.getBoundingRect();
const xOffset = bounding[0][0] - oldBounding[0][0];
const yOffset = bounding[0][1] - oldBounding[0][1];
el.moveTo([newBoundingStart[0] + xOffset, newBoundingStart[1] + yOffset]);
}
}

public execute() {
const { setElements, getPosition, zoomLevel } = useStore.getState();
const { setElements, getPosition } = useStore.getState();
for (const item of this.dataTransfer.items) {
// if the content is text then we need to check to its structure if we can create ZagyElement from it or not, if not fallback to normal text
// ignore any files that is not image
// TODO branch to every possible item we can create from clipboard
if (item.kind === "file" && item.type.indexOf("image") !== -1) {
const blob = item.getAsFile();
if (!blob) return;
const norm = normalizeToGrid(getPosition(), this.mouseCoords);
const el = generateImageElement(blob, norm);
const el = new ZagyImage({ image: blob, point1: norm });
this.#importedIds.add(el.id);
setElements((prev) => [...prev, el]);
} else {
Expand All @@ -84,57 +68,33 @@ export class ActionImportElements extends UndoableCommand {
try {
const items = JSON.parse(pasted);
isZagyPortable(items);
const roughGenerator = new RoughGenerator();
// we need to keep same structure and order of the copied elements relative to the bounding rect that they were copied from
// to do so i will create bounding rect between pasted elements to know each point difference from the original bounding rect
// create new bounding rect on the current mouse position, then calc each element new position, element x = newBounding.x + (oldBounding.x - el.x);
this.updateElementsCoords(items.elements);
const elsToPush: ZagyCanvasElement[] = [];
const elsToPush: ZagyShape[] = [];
for (const el of items.elements) {
if (isRect(el)) {
elsToPush.push(
generateCacheRectElement(
roughGenerator,
[el.x, el.y],
[el.endX, el.endY],
zoomLevel,
el.options,
),
);
elsToPush.push(new Rectangle(el.options));
} else if (isLine(el)) {
elsToPush.push(
generateCacheLineElement(
roughGenerator,
el.point1,
el.point2,
zoomLevel,
el.options,
),
);
elsToPush.push(new Line(el.options));
} else if (isText(el)) {
elsToPush.push(
generateTextElement(
el.text.join("\n"),
[el.x, el.y],
el.options,
),
);
elsToPush.push(new Text(el.options));
} else if (isHanddrawn(el)) {
elsToPush.push(
generateCachedHandDrawnElement(el.paths, zoomLevel, el.options),
);
elsToPush.push(new HandDrawn(el.options));
} else if (isImage(el) && el.image !== null) {
elsToPush.push(
generateImageElement(el.image, [el.x, el.y], el.options),
);
elsToPush.push(new ZagyImage(el.options));
}
}
// we need to keep same structure and order of the copied elements relative to the bounding rect that they were copied from
// to do so i will create bounding rect between pasted elements to know each point difference from the original bounding rect
// create new bounding rect on the current mouse position, then calc each element new position, element x = newBounding.x + (oldBounding.x - el.x);
this.updateElementsCoords(elsToPush);

// append elements to be deleted from the history stack
elsToPush.forEach((el) => this.#importedIds.add(el.id));
setElements((prev) => [...prev, ...elsToPush]);
} catch {
const textEl = generateTextElement(pasted, this.mouseCoords);
const textEl = new Text({
text: pasted,
point1: normalizePos(getPosition(), this.mouseCoords),
});
setElements((prev) => [...prev, textEl]);
}
});
Expand Down
44 changes: 8 additions & 36 deletions src/actions/openScene.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { RoughGenerator } from "roughjs/bin/generator";
import { fileOpen } from "browser-fs-access";
import { Command } from "./types";
import {
Expand All @@ -9,15 +8,10 @@ import {
isText,
isImage,
isHanddrawn,
ZagyShape,
} from "@/types/general";
import { useStore } from "@/store/index";
import {
generateCacheLineElement,
generateCacheRectElement,
generateCachedHandDrawnElement,
generateImageElement,
generateTextElement,
} from "@/utils/canvas/generateElement";
import { Line, Rectangle, Text, ZagyImage, HandDrawn } from "@/utils/canvas/shapes";

/**
* future version should support loading scene from Z+ as well
Expand All @@ -38,41 +32,19 @@ export class ActionOpenScene extends Command {
if (text) {
const portable = JSON.parse(text);
isZagyPortable(portable);
const roughGenerator = new RoughGenerator();
const elsToPush: ZagyCanvasElement[] = [];
const elsToPush: ZagyShape[] = [];
for (const el of portable.elements) {
if (isRect(el)) {
elsToPush.push(
generateCacheRectElement(
roughGenerator,
[el.x, el.y],
[el.endX, el.endY],
zoomLevel,
el.options,
),
);
elsToPush.push(new Rectangle(el.options));
} else if (isLine(el)) {
elsToPush.push(
generateCacheLineElement(
roughGenerator,
el.point1,
el.point2,
zoomLevel,
el.options,
),
);
elsToPush.push(new Line(el.options));
} else if (isText(el)) {
elsToPush.push(
generateTextElement(el.text.join("\n"), [el.x, el.y], el.options),
);
elsToPush.push(new Text(el.options));
} else if (isHanddrawn(el)) {
elsToPush.push(
generateCachedHandDrawnElement(el.paths, zoomLevel, el.options),
);
elsToPush.push(new HandDrawn(el.options));
} else if (isImage(el) && el.image !== null) {
elsToPush.push(generateImageElement(el.image, [el.x, el.y], el.options));
elsToPush.push(new ZagyImage(el.options));
}

setPosition({ x: 0, y: 0 });
setSelectedElements(() => []);
setElements(() => [...elsToPush]);
Expand Down
42 changes: 12 additions & 30 deletions src/components/ToolbarElementConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
GlobalElementOptions,
StrokeWidth,
ZagyCanvasElement,
ZagyShape,
isHanddrawn,
isLine,
isRect,
Expand Down Expand Up @@ -47,6 +48,7 @@ const isValidColor = (color: string) => {
s.color = color;
return s.color !== "";
};

const InputWithIcon: React.FC<Props> = (props) => {
const inputRef = React.useRef<HTMLInputElement>(null);
const lastValue = React.useRef<string>("");
Expand Down Expand Up @@ -102,6 +104,7 @@ const InputWithIcon: React.FC<Props> = (props) => {
</label>
);
};

const RadioButton: React.FC<{
children: ReactNode;
value: string | number;
Expand Down Expand Up @@ -135,6 +138,7 @@ const RadioButton: React.FC<{
</div>
);
};

const {
setElements,
setSelectedElements,
Expand All @@ -147,6 +151,7 @@ const {
setStrokeLineDash,
setStrokeWidth,
} = useStore.getState();

export default function ToolbarLeft() {
const [font, fontSize] = useStore((state) => [state.font, state.fontSize]);
const selectedElements = useStore((state) => state.selectedElements);
Expand All @@ -173,7 +178,7 @@ export default function ToolbarLeft() {
{
onShortcut: () => commandManager.executeCommand(new ActionDeleteSelected()),
},
"Delete",
...SHORTCUTS["editor"]["delete"]["keys"],
);
useKeyboardShortcut(
{
Expand Down Expand Up @@ -218,37 +223,14 @@ export default function ToolbarLeft() {
for (const itm of selectedElements) {
ids.add(itm.id);
}
const els: ZagyCanvasElement[] = [];
const els: ZagyShape[] = [];
//todo maybe optimize so that if the element's own config isn't changed no need to re-generate
selectedElements.forEach((el) => {
if (isRect(el)) {
els.push(
generateCacheRectElement(gen, [el.x, el.y], [el.endX, el.endY], zoom, {
...el.options,
[k]: value,
id: el.id,
}),
);
} else if (isLine(el)) {
els.push(
generateCacheLineElement(gen, [el.x, el.y], [el.endX, el.endY], zoom, {
...el.options,
id: el.id,
[k]: value,
}),
);
} else if (isText(el)) {
els.push(
generateTextElement(el.text.join("\n"), [el.x, el.y], {
...el.options,
[k]: value,
}),
);
} else if (isHanddrawn(el)) {
els.push(
generateCachedHandDrawnElement(el.paths, zoom, { ...el.options, [k]: value }),
);
}
els.push(
el.regenerate({
[k]: value,
}),
);
});
// change global config to new options

Expand Down
Loading

0 comments on commit e21870e

Please sign in to comment.