update zIndex changes

This commit is contained in:
Artur AGH
2023-11-05 11:02:32 +01:00
parent df33d4a3b8
commit 88feafddc3
11 changed files with 202 additions and 73 deletions

View File

@@ -1,24 +1,20 @@
import { TransformableImage } from "@/components/transformable-image"; import { TransformableImage } from "@/components/transformable-image";
import type { KonvaEventObject } from "konva/lib/Node"; import type { KonvaEventObject } from "konva/lib/Node";
import { Layer, Rect, Stage } from "react-konva";
import { Layer, Stage } from "react-konva";
import TransformableText from "./transformable-text"; import TransformableText from "./transformable-text";
import { useAppDispatch, useAppSelector } from "@/hooks"; import { useAppDispatch, useAppSelector } from "@/hooks";
import { appSlice, deselectItem } from "@/store/app.slice"; import { appSlice, deselectItem } from "@/store/app.slice";
import { TextToolbar } from "./text-toolbar";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { EditableText } from "@/components/editable-resizable-text";
import { ImageToolbar } from "@/components/image-toolbar";
import { Toolbar } from "@/components/toolbar"; import { Toolbar } from "@/components/toolbar";
const Canvas = () => { const Canvas = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const stage = useAppSelector((state) => state.app.stage);
const selectedItemId = useAppSelector((state) => state.app.selectedItemId); const selectedItemId = useAppSelector((state) => state.app.selectedItemId);
const selectItem = (id: string) => dispatch(appSlice.actions.selectItem(id)); const selectItem = (id: string) => dispatch(appSlice.actions.selectItem(id));
const texts = useAppSelector((state) => state.app.texts); const texts = useAppSelector((state) => state.app.texts);
const images = useAppSelector((state) => state.app.images); const images = useAppSelector((state) => state.app.images);
const backgroundRects = useAppSelector((state) => state.app.backgroundRects);
const deselectHandler = ( const deselectHandler = (
e: KonvaEventObject<MouseEvent> | KonvaEventObject<TouchEvent>, e: KonvaEventObject<MouseEvent> | KonvaEventObject<TouchEvent>,
@@ -32,9 +28,6 @@ const Canvas = () => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [isTransforming, setIsTransforming] = useState(false); const [isTransforming, setIsTransforming] = useState(false);
const [text, setText] = useState("Click to resize. Double click to edit.");
const [width, setWidth] = useState(200);
const [height, setHeight] = useState(200);
const [selected, setSelected] = useState(false); const [selected, setSelected] = useState(false);
useEffect(() => { useEffect(() => {
@@ -61,8 +54,8 @@ const Canvas = () => {
<Stage <Stage
className="m-[3rem] bg-white" className="m-[3rem] bg-white"
width={600} width={stage.width}
height={500} height={stage.height}
onTouchStart={deselectHandler} onTouchStart={deselectHandler}
onMouseDown={deselectHandler} onMouseDown={deselectHandler}
> >
@@ -94,23 +87,20 @@ const Canvas = () => {
); );
})} })}
{/*<EditableText*/} {backgroundRects.map((rect, i) => {
{/* x={20}*/} return (
{/* y={40}*/} <Rect
{/* text={text}*/} key={i}
{/* width={width}*/} width={rect.width}
{/* height={height}*/} height={rect.width}
{/* onResize={(newWidth, newHeight) => {*/} fill={rect.fill}
{/* setWidth(newWidth);*/} />
{/* setHeight(newHeight);*/} );
{/* }}*/} })}
{/* isEditing={isEditing}*/}
{/* isTransforming={isTransforming}*/}
{/* onToggleEdit={toggleEdit}*/}
{/* onToggleTransform={toggleTransforming}*/}
{/* onChange={setText}*/}
{/*/>*/}
</Layer> </Layer>
{/* <Layer>
<Text zIndex={10} x={30} y={200} fontSize={48} text="Hello" />
</Layer>*/}
</Stage> </Stage>
</div> </div>
); );

View File

@@ -9,7 +9,9 @@ import { Separator } from "@/components/ui/separator";
export function ImageToolbar({ selectedItemId, currentImage }) { export function ImageToolbar({ selectedItemId, currentImage }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const flipImageVerticaly = () => { const flipImageVerticaly = () => {
const editOffsetY = currentImage.height / 2; const currentScale = currentImage.scaleY;
let editOffsetY = currentImage.height;
if (currentScale < 0) editOffsetY = 0;
const editScaleY = -1 * currentImage.scaleY; const editScaleY = -1 * currentImage.scaleY;
dispatch( dispatch(
updateImage({ updateImage({
@@ -21,7 +23,9 @@ export function ImageToolbar({ selectedItemId, currentImage }) {
}; };
const flipImageHorizontaly = () => { const flipImageHorizontaly = () => {
const editOffsetX = currentImage.width / 2; const currentScale = currentImage.scaleX;
let editOffsetX = currentImage.width;
if (currentScale < 0) editOffsetX = 0;
const editScaleX = -1 * currentImage.scaleX; const editScaleX = -1 * currentImage.scaleX;
dispatch( dispatch(
updateImage({ updateImage({

View File

@@ -0,0 +1,74 @@
import React, { useState } from "react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { RxLayout } from "react-icons/rx";
import { Label } from "@/components/ui/label";
import { useAppDispatch } from "@/hooks";
import { selectBackground } from "@/store/app.slice";
export const BackgroundSelect = () => {
const dispatch = useAppDispatch();
const [open, setOpen] = useState(false);
const handleBackgroundSelect = (color: string) => {
dispatch(selectBackground(color));
};
const handleSizeSelect = () => {
setOpen(false);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
onClick={() => setOpen(true)}
variant="outline"
className="text-xl"
>
<RxLayout />
</Button>
</PopoverTrigger>
<PopoverContent side="right" className="mt-4">
<Card className="p-3">
<CardHeader className="mb-2 p-2 text-center">
<CardTitle>Fond</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-2">
<div>
<div
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-2 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
onClick={() => handleBackgroundSelect("red")}
>
Red
</div>
</div>
<div>
<Label
htmlFor="paypal"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
onClick={() => handleBackgroundSelect("blue")}
>
Blue
</Label>
</div>
<div>
<Label
htmlFor="apple"
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
onClick={() => handleBackgroundSelect("green")}
>
Green
</Label>
</div>
</CardContent>
</Card>
</PopoverContent>
</Popover>
);
};

View File

@@ -6,11 +6,11 @@ import {
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { PiImageDuotone } from "react-icons/pi";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { addImage } from "@/store/app.slice"; import { addImage } from "@/store/app.slice";
import { useAppDispatch } from "@/hooks"; import { useAppDispatch } from "@/hooks";
import { Card, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardHeader, CardTitle } from "@/components/ui/card";
import { RxImage } from "react-icons/rx";
function ImageInput() { function ImageInput() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -32,8 +32,8 @@ function ImageInput() {
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="secondary" className="text-xl"> <Button variant="outline" className="text-xl">
<PiImageDuotone /> <RxImage />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent side="right"> <PopoverContent side="right">

View File

@@ -0,0 +1,31 @@
import React from "react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { RxStack } from "react-icons/rx";
export const Layers = () => {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="text-xl">
<RxStack />
</Button>
</PopoverTrigger>
<PopoverContent side="right">
<Card className="p-2">
<CardHeader>
<CardTitle className="text-center">Layer</CardTitle>
</CardHeader>
<CardContent className="grid gap-2">
<div className="flex h-[2rem] flex-col items-center justify-between rounded-md border-2 border-muted bg-popover"></div>
</CardContent>
</Card>
</PopoverContent>
</Popover>
);
};

View File

@@ -1,12 +1,11 @@
import React, { ChangeEvent } from "react"; import React from "react";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { PiFrameCornersDuotone, PiImageDuotone } from "react-icons/pi"; import { PiFrameCornersDuotone } from "react-icons/pi";
import { Input } from "@/components/ui/input";
import { useAppDispatch } from "@/hooks"; import { useAppDispatch } from "@/hooks";
import { Card, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardHeader, CardTitle } from "@/components/ui/card";
@@ -25,7 +24,7 @@ export function SelectTemplate() {
<Card className="p-2"> <Card className="p-2">
<CardHeader> <CardHeader>
<CardTitle className="text-center">Choisissez un cadre</CardTitle> <CardTitle className="text-center">Cadre</CardTitle>
</CardHeader> </CardHeader>
</Card> </Card>
</PopoverContent> </PopoverContent>

View File

@@ -1,17 +1,18 @@
import { TextInput } from "@/components/layout/sidebar/text-input"; import { TextInput } from "@/components/layout/sidebar/text-input";
import ImageInput from "@/components/layout/sidebar/image-input"; import ImageInput from "@/components/layout/sidebar/image-input";
import { SelectTemplate } from "@/components/layout/sidebar/select-template";
import { Button } from "@/components/ui/button";
import { useAppDispatch } from "@/hooks";
import SizeSelect from "@/components/layout/sidebar/size-select"; import SizeSelect from "@/components/layout/sidebar/size-select";
import { BackgroundSelect } from "@/components/layout/sidebar/background-select";
import { Layers } from "@/components/layout/sidebar/layers";
export function Sidebar() { export function Sidebar() {
return ( return (
<div className="flex h-full w-20 flex-col gap-2 p-2"> <div className="inline-flex h-full w-20 flex-col items-center justify-center gap-2 p-2">
<SizeSelect /> <SizeSelect />
<TextInput /> <TextInput />
<ImageInput /> <ImageInput />
<SelectTemplate /> {/*<SelectTemplate />*/}
<BackgroundSelect />
<Layers />
</div> </div>
); );
} }

View File

@@ -5,15 +5,22 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { PiTextT } from "react-icons/pi";
import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Textarea } from "@/components/ui/textarea"; import { RxMove } from "react-icons/rx";
import { addText } from "@/store/app.slice"; import { useAppDispatch } from "@/hooks";
import { SlSizeFullscreen } from "react-icons/sl"; import { updateStage } from "@/store/app.slice";
const SizeSelect = () => { const SizeSelect = () => {
const dispatch = useAppDispatch();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const handleStageSizeSelect = (
width: number | undefined,
height: number | undefined,
) => {
dispatch(updateStage({ width, height }));
};
const handleSizeSelect = () => { const handleSizeSelect = () => {
setOpen(false); setOpen(false);
}; };
@@ -22,10 +29,10 @@ const SizeSelect = () => {
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
variant="secondary" variant="outline"
className="text-xl" className="text-xl"
> >
<SlSizeFullscreen /> <RxMove />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent side="right" className="mt-4"> <PopoverContent side="right" className="mt-4">
@@ -35,17 +42,23 @@ const SizeSelect = () => {
</CardHeader> </CardHeader>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Button>Petit Format</Button> <Button onClick={() => handleStageSizeSelect(350, 400)}>
<Button>Moyen Format</Button> Petit Format
<Button>Grand Format</Button> </Button>
<Button onClick={() => handleStageSizeSelect(500, 500)}>
Moyen Format
</Button>
<Button onClick={() => handleStageSizeSelect(800, 600)}>
Grand Format
</Button>
</div> </div>
<CardFooter className="mt-8 justify-between"> {/*<CardFooter className="mt-8 justify-between">*/}
<Button variant="ghost" onClick={() => setOpen(false)}> {/* <Button variant="ghost" onClick={() => setOpen(false)}>*/}
Cancel {/* Cancel*/}
</Button> {/* </Button>*/}
<Button onClick={handleSizeSelect}>Submit</Button> {/* <Button onClick={handleSizeSelect}>Submit</Button>*/}
</CardFooter> {/*</CardFooter>*/}
</Card> </Card>
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@@ -8,10 +8,8 @@ import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { addText } from "@/store/app.slice"; import { addText } from "@/store/app.slice";
import { useAppDispatch } from "@/hooks"; import { useAppDispatch } from "@/hooks";
import { Input } from "@/components/ui/input";
import { PiTextT } from "react-icons/pi";
import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label"; import { RxText } from "react-icons/rx";
export function TextInput() { export function TextInput() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -30,16 +28,16 @@ export function TextInput() {
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
variant="secondary" variant="outline"
className="text-xl" className="text-xl"
> >
<PiTextT /> <RxText />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent side="right" className="mt-4"> <PopoverContent side="right" className="mt-4">
<Card className="p-3"> <Card className="p-3">
<CardHeader> <CardHeader className="pt-2">
<CardTitle>Ajouter votre text</CardTitle> <CardTitle className="text-center">Text</CardTitle>
</CardHeader> </CardHeader>
<Textarea <Textarea
id="description" id="description"

View File

@@ -3,8 +3,6 @@ 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 { DeleteShapeButton } from "@/components/delete-shape-button";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";

View File

@@ -1,25 +1,23 @@
import type { TransformableImageProps } from "@/components/transformable-image"; import type { TransformableImageProps } from "@/components/transformable-image";
import type { TransformableTextProps } from "@/components/transformable-text"; 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 { 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 RectConfig } from "konva/lib/shapes/Rect";
import type { ImageConfig } from "konva/lib/shapes/Image";
const initialState = { const initialState = {
stage: { width: 500, height: 500 },
selectedItemId: null as string | null, selectedItemId: null as string | null,
images: [] as TransformableImageProps["imageProps"][], images: [] as TransformableImageProps["imageProps"][],
texts: [] as TransformableTextProps["textProps"][], texts: [] as TransformableTextProps["textProps"][],
backgroundRects: [] as RectConfig[],
}; };
type InitialState = typeof initialState;
const defaultTextConfig = { const defaultTextConfig = {
fontSize: 16, fontSize: 16,
align: "center", align: "center",
fontFamily: "Roboto", fontFamily: "Roboto",
zIndex: 3,
}; };
const defaultImageConfig = { const defaultImageConfig = {
@@ -30,6 +28,7 @@ const defaultImageConfig = {
offsetY: 0, offsetY: 0,
scaleX: 1, scaleX: 1,
scaleY: 1, scaleY: 1,
zIndex: 1,
}; };
export const appSlice = createSlice({ export const appSlice = createSlice({
@@ -45,6 +44,19 @@ export const appSlice = createSlice({
}); });
}, },
selectBackground: (state, action: PayloadAction<string>) => {
state.backgroundRects = [];
state.backgroundRects.push({
x: 0,
y: 0,
stroke: "black",
z_index: 0,
width: state.stage.width,
height: state.stage.height,
fill: action.payload,
});
},
addImage: ( addImage: (
state, state,
action: PayloadAction<{ action: PayloadAction<{
@@ -74,6 +86,13 @@ export const appSlice = createSlice({
state.selectedItemId = null; state.selectedItemId = null;
}, },
updateStage: (
state,
action: PayloadAction<{ width?: number; height?: number }>,
) => {
state.stage = { ...state.stage, ...action.payload };
},
updateText: ( updateText: (
state, state,
action: PayloadAction<{ id: string } & Partial<TransformableTextProps>>, action: PayloadAction<{ id: string } & Partial<TransformableTextProps>>,
@@ -126,4 +145,6 @@ export const {
updateText, updateText,
updateImage, updateImage,
deleteShape, deleteShape,
updateStage,
selectBackground,
} = appSlice.actions; } = appSlice.actions;