mirror of
https://github.com/r2r90/canvas-label.git
synced 2025-12-17 12:35:48 +00:00
adding canvas size button to sidebar - without fonctionality
This commit is contained in:
25
src/components/delete-shape-button.tsx
Normal file
25
src/components/delete-shape-button.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { deleteShape } from "@/store/app.slice";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import { BsTrash3 } from "react-icons/bs";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
selectedItemId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DeleteShapeButton = ({ selectedItemId }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const deleteTextHandler = (selectedItemId: string) => {
|
||||||
|
dispatch(deleteShape(selectedItemId));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
color="cyan"
|
||||||
|
onClick={() => deleteTextHandler(selectedItemId)}
|
||||||
|
>
|
||||||
|
<BsTrash3 />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,32 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { DeleteShapeButton } from "@/components/delete-shape-button";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { TbFlipHorizontal, TbFlipVertical } from "react-icons/tb";
|
||||||
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import { updateImage } from "@/store/app.slice";
|
||||||
|
|
||||||
export function ImageToolbar() {
|
export function ImageToolbar({ selectedItemId, currentImage }) {
|
||||||
return <div>Image toolbar</div>;
|
const dispatch = useAppDispatch();
|
||||||
|
const flipImageVerticaly = (selectedItemId) => {
|
||||||
|
dispatch(
|
||||||
|
updateImage({
|
||||||
|
id: selectedItemId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const flipImageHorizontaly = () => {
|
||||||
|
console.log("Horizontal Flip");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Toggle onClick={flipImageVerticaly}>
|
||||||
|
<TbFlipVertical />
|
||||||
|
</Toggle>
|
||||||
|
<Toggle>
|
||||||
|
<TbFlipHorizontal onClick={flipImageHorizontaly} />
|
||||||
|
</Toggle>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import ImageInput from "@/components/layout/sidebar/image-input";
|
|||||||
import { SelectTemplate } from "@/components/layout/sidebar/select-template";
|
import { SelectTemplate } from "@/components/layout/sidebar/select-template";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useAppDispatch } from "@/hooks";
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import SizeSelect from "@/components/layout/sidebar/size-select";
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-20 flex-col gap-2 p-2">
|
<div className="flex h-full w-20 flex-col gap-2 p-2">
|
||||||
|
<SizeSelect />
|
||||||
<TextInput />
|
<TextInput />
|
||||||
<ImageInput />
|
<ImageInput />
|
||||||
<SelectTemplate />
|
<SelectTemplate />
|
||||||
|
|||||||
54
src/components/layout/sidebar/size-select.tsx
Normal file
54
src/components/layout/sidebar/size-select.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { PiTextT } from "react-icons/pi";
|
||||||
|
import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { addText } from "@/store/app.slice";
|
||||||
|
import { SlSizeFullscreen } from "react-icons/sl";
|
||||||
|
|
||||||
|
const SizeSelect = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleSizeSelect = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
variant="secondary"
|
||||||
|
className="text-xl"
|
||||||
|
>
|
||||||
|
<SlSizeFullscreen />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent side="right" className="mt-4">
|
||||||
|
<Card className="p-3">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Sélectionnez la taille d'étiquette</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Button>Petit Format</Button>
|
||||||
|
<Button>Moyen Format</Button>
|
||||||
|
<Button>Grand Format</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardFooter className="mt-8 justify-between">
|
||||||
|
<Button variant="ghost" onClick={() => setOpen(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSizeSelect}>Submit</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default SizeSelect;
|
||||||
@@ -9,6 +9,7 @@ import { type TransformableTextProps } from "@/components/transformable-text";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useAppDispatch } from "@/hooks";
|
import { useAppDispatch } from "@/hooks";
|
||||||
import { deleteShape } from "@/store/app.slice";
|
import { deleteShape } from "@/store/app.slice";
|
||||||
|
import { DeleteShapeButton } from "@/components/delete-shape-button";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedItemId: string;
|
selectedItemId: string;
|
||||||
@@ -22,9 +23,6 @@ export const TextToolbar = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
if (!currentText) return null;
|
if (!currentText) return null;
|
||||||
const deleteTextHandler = (selectedItemId) => {
|
|
||||||
dispatch(deleteShape(selectedItemId));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -53,9 +51,6 @@ export const TextToolbar = ({
|
|||||||
currentText={currentText}
|
currentText={currentText}
|
||||||
selectedItemId={selectedItemId}
|
selectedItemId={selectedItemId}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => deleteTextHandler(selectedItemId)}>
|
|
||||||
Delete Text
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { ImageToolbar } from "@/components/image-toolbar";
|
|||||||
import { TextToolbar } from "@/components/text-toolbar";
|
import { TextToolbar } from "@/components/text-toolbar";
|
||||||
import { useAppDispatch, useAppSelector } from "@/hooks";
|
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||||
import { updateText } from "@/store/app.slice";
|
import { updateText } from "@/store/app.slice";
|
||||||
|
import { BsTrash3 } from "react-icons/bs";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { DeleteShapeButton } from "@/components/delete-shape-button";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -27,7 +31,6 @@ export const Toolbar = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" flex h-[5rem] w-full gap-6 border border-t-0 bg-white p-[1rem]">
|
<div className=" flex h-[5rem] w-full gap-6 border border-t-0 bg-white p-[1rem]">
|
||||||
{currentImage && <ImageToolbar />}
|
|
||||||
{currentText && (
|
{currentText && (
|
||||||
<TextToolbar
|
<TextToolbar
|
||||||
currentText={currentText}
|
currentText={currentText}
|
||||||
@@ -35,6 +38,16 @@ export const Toolbar = () => {
|
|||||||
onTextColorChange={handleTextColorChange}
|
onTextColorChange={handleTextColorChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{currentImage && (
|
||||||
|
<ImageToolbar
|
||||||
|
currentImage={currentImage}
|
||||||
|
selectedItemId={selectedItemId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
|
||||||
|
<DeleteShapeButton selectedItemId={selectedItemId} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import type { TransformableTextProps } from "@/components/transformable-text";
|
|||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
import type { ChangeEvent } from "react";
|
import type { ChangeEvent } from "react";
|
||||||
|
|
||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice, current } from "@reduxjs/toolkit";
|
||||||
import { v1 } from "uuid";
|
import { v1 } from "uuid";
|
||||||
import type { TextConfig } from "konva/lib/shapes/Text";
|
import type { TextConfig } from "konva/lib/shapes/Text";
|
||||||
import { RootState } from "./store";
|
import type { ImageConfig } from "konva/lib/shapes/Image";
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
selectedItemId: null as string | null,
|
selectedItemId: null as string | null,
|
||||||
@@ -22,26 +22,30 @@ const defaultTextConfig = {
|
|||||||
fontFamily: "Roboto",
|
fontFamily: "Roboto",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultImageConfig = {};
|
||||||
|
|
||||||
export const appSlice = createSlice({
|
export const appSlice = createSlice({
|
||||||
name: "app",
|
name: "app",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
|
addText: (state, action: PayloadAction<{ initialValue: string }>) => {
|
||||||
|
const textId = v1();
|
||||||
|
state.texts.push({
|
||||||
|
text: action.payload.initialValue,
|
||||||
|
id: textId,
|
||||||
|
...defaultTextConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(current(state.texts));
|
||||||
|
},
|
||||||
|
|
||||||
addImage: (state, action: PayloadAction<ChangeEvent<HTMLInputElement>>) => {
|
addImage: (state, action: PayloadAction<ChangeEvent<HTMLInputElement>>) => {
|
||||||
const file = action.payload.target.files?.[0];
|
const file = action.payload.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
const imageId = v1();
|
const imageId = v1();
|
||||||
const imageUrl = URL.createObjectURL(file);
|
const imageUrl = URL.createObjectURL(file);
|
||||||
state.images.push({ imageUrl, id: imageId });
|
state.images.push({ imageUrl, id: imageId });
|
||||||
},
|
console.log(current(state.images));
|
||||||
|
|
||||||
addText: (state, action: PayloadAction<{ initialValue: string }>) => {
|
|
||||||
const textId = v1();
|
|
||||||
console.log(state);
|
|
||||||
state.texts.push({
|
|
||||||
text: action.payload.initialValue,
|
|
||||||
id: textId,
|
|
||||||
...defaultTextConfig,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
selectItem: (state, action: PayloadAction<string>) => {
|
selectItem: (state, action: PayloadAction<string>) => {
|
||||||
@@ -54,7 +58,7 @@ export const appSlice = createSlice({
|
|||||||
|
|
||||||
updateText: (
|
updateText: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ id: string } & Partial<TextConfig>>,
|
action: PayloadAction<{ id: string } & Partial<TransformableTextProps>>,
|
||||||
) => {
|
) => {
|
||||||
const textToUpdateIndex = state.texts.findIndex(
|
const textToUpdateIndex = state.texts.findIndex(
|
||||||
(t) => t.id === action.payload.id,
|
(t) => t.id === action.payload.id,
|
||||||
@@ -69,11 +73,28 @@ export const appSlice = createSlice({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateImage: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ id: string } & Partial<TransformableImageProps>>,
|
||||||
|
) => {
|
||||||
|
const imageToUpdateIndex = state.images.findIndex(
|
||||||
|
(img) => img.id === action.payload.id,
|
||||||
|
);
|
||||||
|
const imageToUpdate = state.images[imageToUpdateIndex];
|
||||||
|
|
||||||
|
if (!imageToUpdate) return;
|
||||||
|
|
||||||
|
state.images[imageToUpdateIndex] = {
|
||||||
|
...imageToUpdate,
|
||||||
|
...action.payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
deleteShape: (state, action: PayloadAction<string>) => {
|
deleteShape: (state, action: PayloadAction<string>) => {
|
||||||
console.log(state.texts);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
texts: state.texts.filter((shape) => shape.id !== action.payload),
|
texts: state.texts.filter((shape) => shape.id !== action.payload),
|
||||||
|
images: state.images.filter((shape) => shape.id !== action.payload),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -85,5 +106,6 @@ export const {
|
|||||||
selectItem,
|
selectItem,
|
||||||
deselectItem,
|
deselectItem,
|
||||||
updateText,
|
updateText,
|
||||||
|
updateImage,
|
||||||
deleteShape,
|
deleteShape,
|
||||||
} = appSlice.actions;
|
} = appSlice.actions;
|
||||||
|
|||||||
Reference in New Issue
Block a user