Skip to content

Commit

Permalink
Merge branch 'main' into zoom
Browse files Browse the repository at this point in the history
  • Loading branch information
KirillSerg committed Jun 13, 2024
2 parents 6395d9b + af35955 commit 827703f
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 23 deletions.
14 changes: 11 additions & 3 deletions src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ const Canvas = () => {
const keyPressed = useAtomValue(keyPressedAtom);
const canvasViewBox = useAtomValue(canvasViewBoxAtom);
const [, zoomCanvas] = useAtom(zoomCanvasAtom);
const [, grabCanvas] = useAtom(grabCanvasAtom);
const creationInitialElement = useAtomValue(creationInitialElementAtom);
const selectingArea = useAtomValue(selectingAreaAtom);
const [canasViewBox, grabCanvas] = useAtom(grabCanvasAtom);

const svgContainerRef = useRef<SVGSVGElement>(null);

Expand Down Expand Up @@ -70,7 +70,7 @@ const Canvas = () => {
zoomCanvas(ZoomCanvasFn.ZOOMDOWN);
}
if (!keyPressed.ctrlKey) {
grabCanvas({ ...canasViewBox, y: e.deltaY });
grabCanvas({ x: 0, y: e.deltaY });
}
};

Expand All @@ -85,7 +85,7 @@ const Canvas = () => {
containerElement.removeEventListener('wheel', handleOnWheel);
};
}
}, [keyPressed.ctrlKey, canasViewBox, zoomCanvas, grabCanvas]);
}, [keyPressed.ctrlKey, zoomCanvas, grabCanvas]);

return (
<svg
Expand Down Expand Up @@ -126,6 +126,14 @@ const Canvas = () => {
{!isDragging &&
!isDrawing &&
creationInitialElement.type_name !== 'grab' && <SelectingArea />}

{creationInitialElement.type_name === 'image' && (
<SingleElement
key={creationInitialElement.id}
element={creationInitialElement}
svgContainerRef={svgContainerRef.current}
/>
)}
</svg>
);
};
Expand Down
60 changes: 60 additions & 0 deletions src/components/ImageIconBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useRef } from 'react';
import { useAtom } from 'jotai';
import { ElementsTypeName } from '../types/CommonTypes';
import { creationInitialElementAtom } from '../store/store';

interface Props {
className: string;
handlerClick: (typeName: ElementsTypeName) => void;
}

const ImageIconBtn = ({ className, handlerClick }: Props) => {
const [, setCreationInitialElement] = useAtom(creationInitialElementAtom);

const fileInputRef = useRef<HTMLInputElement>(null);

const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const reader = new FileReader();
reader.onloadend = () => {
// if (reader.result !== null)
setCreationInitialElement((prev) => {
return { ...prev, href: reader.result };
});
// Reset the file input value
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
reader.readAsDataURL(e.target.files[0]);
}
};

return (
<button className={className} onClick={() => handlerClick('image')}>
<label
htmlFor="image"
className="absolute top-0 left-0 w-full h-full cursor-pointer"
></label>
<input
ref={fileInputRef}
onChange={(e) => handleImageChange(e)}
id="image"
type="file"
className="hidden"
/>
<svg
viewBox="0 0 24 24"
height="100%"
width="100%"
xmlns="http://www.w3.org/2000/svg"
fillRule="evenodd"
clipRule="evenodd"
>
<path d="M24 22h-24v-20h24v20zm-1-19h-22v18h22v-18zm-1 16h-19l4-7.492 3 3.048 5.013-7.556 6.987 12zm-11.848-2.865l-2.91-2.956-2.574 4.821h15.593l-5.303-9.108-4.806 7.243zm-4.652-11.135c1.38 0 2.5 1.12 2.5 2.5s-1.12 2.5-2.5 2.5-2.5-1.12-2.5-2.5 1.12-2.5 2.5-2.5zm0 1c.828 0 1.5.672 1.5 1.5s-.672 1.5-1.5 1.5-1.5-.672-1.5-1.5.672-1.5 1.5-1.5z" />
</svg>
</button>
);
};

export default ImageIconBtn;
2 changes: 2 additions & 0 deletions src/components/SingleElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const SingleElement = ({ element, svgContainerRef }: Props) => {
{element.type !== 'free' && element.type !== 'grab' && (
<element.type //flexible&dynemic rendering svg-elements
{...element}
// in order for the image to be stored and displayed between renderers, its type is an arrayBuffer, but the type of href of image element of svg is a string. That is why this transformation is necessary
href={element.type === 'image' ? element.href?.toString() : ''}
style={{ cursor: 'pointer' }}
onMouseDown={(e) => handleMouseDown(e)}
onMouseUp={onMouseUp}
Expand Down
38 changes: 22 additions & 16 deletions src/components/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { useAtom } from 'jotai';
import {
creationInitialElementAtom,
selectedElementAtom,
} from '../store/store';
import LineIconBtn from './LineIconBtn';
import LineArrowIconBtn from './LineArrowIconBtn';
import TriangleIconBtn from './TriangleIconBtn';
import EllipseIconBtn from './EllipseIconBtn';
import RectIconBtn from './RectIconBtn';
import FreeIconBtn from './FreeIconBtn';
import TextIconBtn from './TextIconBtn';
import PencilIconBtn from './PencilIconBtn';
import GrabIconBtn from './GrabIconBtn copy';
import ImageIconBtn from './ImageIconBtn';
import {
creationInitialElementAtom,
selectedElementAtom,
} from '../store/store';
import {
ELEMENT_TYPE_VARIANTS,
Element,
ElementsTypeName,
} from '../types/CommonTypes';
import TextIconBtn from './TextIconBtn';
import PencilIconBtn from './PencilIconBtn';
import GrabIconBtn from './GrabIconBtn copy';

const Toolbar = () => {
const [creationInitialElement, setCreationInitialElement] = useAtom(
Expand All @@ -40,46 +41,51 @@ const Toolbar = () => {
return (
<header className="w-fit h-[6%] fixed top-3 flex justify-center items-center gap-4 border-[1px] border-black">
<GrabIconBtn
className={`${creationInitialElement.type_name === 'grab' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'grab' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>
<FreeIconBtn
className={`${creationInitialElement.type_name === 'free' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'free' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<RectIconBtn
className={`${creationInitialElement.type_name === 'rect' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'rect' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<EllipseIconBtn
className={`${creationInitialElement.type_name === 'ellipse' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'ellipse' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<TriangleIconBtn
className={`${creationInitialElement.type_name === 'polygon' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'polygon' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<LineIconBtn
className={`${creationInitialElement.type_name === 'line' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'line' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<LineArrowIconBtn
className={`${creationInitialElement.type_name === 'arrow_line' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'arrow_line' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<TextIconBtn
className={`${creationInitialElement.type_name === 'text' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'text' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<PencilIconBtn
className={`${creationInitialElement.type_name === 'pencil' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px]`}
className={`${creationInitialElement.type_name === 'pencil' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none`}
handlerClick={handlerSelectElement}
/>

<ImageIconBtn
className={`${creationInitialElement.type_name === 'image' ? 'bg-orange-500' : 'bg-inherit'} h-[100%] w-8 p-[6px] focus-visible:outline-none relative`}
handlerClick={handlerSelectElement}
/>
</header>
Expand Down
19 changes: 16 additions & 3 deletions src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const initialElement: Element = {
id: "",
x: 0,
y: 0,
width: 100,
height: 50,
width: 150,
height: 150,
cx: 0,
cy: 0,
rx: 0.5,
Expand All @@ -22,6 +22,7 @@ const initialElement: Element = {
points: "",
textvalue: "",
d: "",
href: null,
markerEnd: "",
stroke: 'black',
strokeWidth: 4,
Expand Down Expand Up @@ -118,7 +119,7 @@ export const zoomCanvasAtom = atom(
)

export const grabCanvasAtom = atom(
(get) => get(canvasViewBoxAtom),
null,
(get, set, update: Coordinates) => {
const canvasViewBox = get(canvasViewBoxAtom)
set(canvasViewBoxAtom, {
Expand Down Expand Up @@ -186,6 +187,9 @@ export const onMouseDownAtom = atom(
}
set(elementsAtom, (prev) => [...prev, newEl])
set(selectedElementAtom, newEl)
set(creationInitialElementAtom, (prev) => {
return { ...prev, href: "" }
})
}

if (get(keyPressedAtom).ctrlKey) {
Expand Down Expand Up @@ -295,6 +299,15 @@ export const onMouseMoveAtom = atom(

set(selectingAreaAtom, { ...selectingArea, endX: update.x, endY: update.y })
}

// this is necessary to display the selected image and to move this image following the cursor
set(creationInitialElementAtom, (prev) => {
return {
...prev,
x: update.x,
y: update.y,
}
})
}
)

Expand Down
5 changes: 4 additions & 1 deletion src/types/CommonTypes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export interface Element {
id: string;
type_name: ElementsTypeName;
type: "grab" | "free" | "rect" | "ellipse" | "line" | "polygon" | "foreignObject" | "path";
type: "grab" | "free" | "rect" | "ellipse" | "line" | "polygon" | "foreignObject" | "path" | "image";
x: number;
y: number;
width: number;
Expand All @@ -17,6 +17,7 @@ export interface Element {
points: string //"x,y x,y x,y ..."
textvalue: string;
d: string;
href: string | ArrayBuffer | null;
markerEnd: string;
stroke: string;
strokeWidth: number;
Expand Down Expand Up @@ -45,6 +46,7 @@ export type ElemenEvent =
| React.MouseEvent<SVGTextElement, MouseEvent>
| React.MouseEvent<SVGForeignObjectElement, MouseEvent>
| React.MouseEvent<SVGPathElement, MouseEvent>
| React.MouseEvent<SVGImageElement, MouseEvent>

export const ELEMENT_TYPE_VARIANTS = {
grab: "grab",
Expand All @@ -56,6 +58,7 @@ export const ELEMENT_TYPE_VARIANTS = {
arrow_line: 'line',
text: "foreignObject",
pencil: "path",
image: "image",
};

export type ElementsTypeName = keyof typeof ELEMENT_TYPE_VARIANTS;
Expand Down

0 comments on commit 827703f

Please sign in to comment.