diff --git a/next.config.mjs b/next.config.mjs
index 35d3fc6..e0eb093 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -23,6 +23,18 @@ const config = {
locales: ["en"],
defaultLocale: "en",
},
+
+ images: {
+ remotePatterns: [
+ {
+ protocol: 'https',
+ hostname: 'images.unsplash.com',
+ },
+ ],
+ },
+
+
+
};
export default config;
diff --git a/package.json b/package.json
index b147bf9..3e252dd 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@headlessui/react": "^1.7.17",
+ "@hookform/resolvers": "^3.3.2",
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/client": "^5.1.1",
"@radix-ui/react-avatar": "^1.0.4",
@@ -27,6 +28,7 @@
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
+ "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4",
@@ -54,6 +56,7 @@
"next-auth": "^4.23.0",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-hook-form": "^7.49.0",
"react-icons": "^4.11.0",
"react-konva": "^18.2.10",
"react-konva-utils": "^1.0.5",
@@ -63,7 +66,7 @@
"tailwindcss-animate": "^1.0.7",
"use-image": "^1.1.1",
"uuid": "^9.0.1",
- "zod": "^3.21.4"
+ "zod": "^3.22.4"
},
"devDependencies": {
"@types/eslint": "^8.44.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f45da7b..7459649 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@ dependencies:
'@headlessui/react':
specifier: ^1.7.17
version: 1.7.17(react-dom@18.2.0)(react@18.2.0)
+ '@hookform/resolvers':
+ specifier: ^3.3.2
+ version: 3.3.2(react-hook-form@7.49.0)
'@next-auth/prisma-adapter':
specifier: ^1.0.7
version: 1.0.7(@prisma/client@5.5.0)(next-auth@4.24.3)
@@ -53,6 +56,9 @@ dependencies:
'@radix-ui/react-slider':
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-slot':
+ specifier: ^1.0.2
+ version: 1.0.2(@types/react@18.2.31)(react@18.2.0)
'@radix-ui/react-tabs':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
@@ -134,6 +140,9 @@ dependencies:
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
+ react-hook-form:
+ specifier: ^7.49.0
+ version: 7.49.0(react@18.2.0)
react-icons:
specifier: ^4.11.0
version: 4.11.0(react@18.2.0)
@@ -162,7 +171,7 @@ dependencies:
specifier: ^9.0.1
version: 9.0.1
zod:
- specifier: ^3.21.4
+ specifier: ^3.22.4
version: 3.22.4
devDependencies:
@@ -349,6 +358,14 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@hookform/resolvers@3.3.2(react-hook-form@7.49.0):
+ resolution: {integrity: sha512-Tw+GGPnBp+5DOsSg4ek3LCPgkBOuOgS5DsDV7qsWNH9LZc433kgsWICjlsh2J9p04H2K66hsXPPb9qn9ILdUtA==}
+ peerDependencies:
+ react-hook-form: ^7.0.0
+ dependencies:
+ react-hook-form: 7.49.0(react@18.2.0)
+ dev: false
+
/@humanwhocodes/config-array@0.11.13:
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
engines: {node: '>=10.10.0'}
@@ -4526,6 +4543,15 @@ packages:
scheduler: 0.23.0
dev: false
+ /react-hook-form@7.49.0(react@18.2.0):
+ resolution: {integrity: sha512-gf4qyY4WiqK2hP/E45UUT6wt3Khl49pleEVcIzxhLBrD6m+GMWtLRk0vMrRv45D1ZH8PnpXFwRPv0Pewske2jw==}
+ engines: {node: '>=18', pnpm: '8'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/react-icons@4.11.0(react@18.2.0):
resolution: {integrity: sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==}
peerDependencies:
diff --git a/src/components/Test/content/edit-utils/sidebar.tsx b/src/components/Test/content/edit-utils/sidebar.tsx
new file mode 100644
index 0000000..68c086f
--- /dev/null
+++ b/src/components/Test/content/edit-utils/sidebar.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+import StageMode from "@/components/Test/content/edit-utils/stage-mode";
+
+export default function Sidebar() {
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/components/Test/content/edit-utils/stage-mode.tsx b/src/components/Test/content/edit-utils/stage-mode.tsx
new file mode 100644
index 0000000..522f474
--- /dev/null
+++ b/src/components/Test/content/edit-utils/stage-mode.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+import {
+ HoverCard,
+ HoverCardContent,
+ HoverCardTrigger,
+} from "@/components/ui/hover-card";
+import { TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { PiBeerBottle } from "react-icons/pi";
+
+export default function StageMode() {
+ return (
+
+
+
+
+ Taille de l'etquieutte
+
+
+
+ Sélectionnez la taille de votre raquette parmi les trois tailles
+ disponibles : S, M, L.
+
+
+
+
+ Complete
+
+
+
+ Insert
+
+
+
+ Edit
+
+
+
+
+ );
+}
diff --git a/src/components/Test/content/playground-area/stage-area.tsx b/src/components/Test/content/playground-area/stage-area.tsx
new file mode 100644
index 0000000..361fd94
--- /dev/null
+++ b/src/components/Test/content/playground-area/stage-area.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { TabsContent } from "@/components/ui/tabs";
+import { Button } from "@/components/ui/button";
+import { CounterClockwiseClockIcon } from "@radix-ui/react-icons";
+import Canvas from "@/components/canvas";
+
+export default function StageArea() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Test/content/playground.tsx b/src/components/Test/content/playground.tsx
new file mode 100644
index 0000000..4ee528c
--- /dev/null
+++ b/src/components/Test/content/playground.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+import { Tabs } from "@/components/ui/tabs";
+import Sidebar from "@/components/Test/content/edit-utils/sidebar";
+import StageArea from "@/components/Test/content/playground-area/stage-area";
+
+export default function Playground() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/Test/navbar/Navbar.tsx b/src/components/Test/navbar/Navbar.tsx
new file mode 100644
index 0000000..c9eb8de
--- /dev/null
+++ b/src/components/Test/navbar/Navbar.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import { Save } from "@/components/Test/navbar/Save";
+import { Preview } from "@/components/Test/navbar/Preview";
+import { SelectVigneron } from "@/components/Test/navbar/select-vigneron";
+import { presets } from "@/components/Test/navbar/list-de-vignerons";
+import { UserNav } from "@/components/layout/user-nav";
+
+export default function Navbar() {
+ return (
+
+ );
+}
diff --git a/src/components/Test/navbar/Preview.tsx b/src/components/Test/navbar/Preview.tsx
new file mode 100644
index 0000000..885d8bf
--- /dev/null
+++ b/src/components/Test/navbar/Preview.tsx
@@ -0,0 +1,89 @@
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+
+export function Preview() {
+ return (
+
+ );
+}
diff --git a/src/components/Test/navbar/Save.tsx b/src/components/Test/navbar/Save.tsx
new file mode 100644
index 0000000..c571d38
--- /dev/null
+++ b/src/components/Test/navbar/Save.tsx
@@ -0,0 +1,44 @@
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Label } from "@/components/ui/label";
+import { Input } from "@/components/ui/input";
+
+export function Save() {
+ return (
+
+ );
+}
diff --git a/src/components/Test/navbar/list-de-vignerons.ts b/src/components/Test/navbar/list-de-vignerons.ts
new file mode 100644
index 0000000..1cd0057
--- /dev/null
+++ b/src/components/Test/navbar/list-de-vignerons.ts
@@ -0,0 +1,47 @@
+export interface Preset {
+ id: string;
+ name: string;
+}
+
+export const presets: Preset[] = [
+ {
+ id: "9cb0e66a-9937-465d-a188-2c4c4ae2401f",
+ name: "Grammatical Standard English",
+ },
+ {
+ id: "61eb0e32-2391-4cd3-adc3-66efe09bc0b7",
+ name: "Summarize for a 2nd grader",
+ },
+ {
+ id: "a4e1fa51-f4ce-4e45-892c-224030a00bdd",
+ name: "Text to command",
+ },
+ {
+ id: "cc198b13-4933-43aa-977e-dcd95fa30770",
+ name: "Q&A",
+ },
+ {
+ id: "adfa95be-a575-45fd-a9ef-ea45386c64de",
+ name: "English to other languages",
+ },
+ {
+ id: "c569a06a-0bd6-43a7-adf9-bf68c09e7a79",
+ name: "Parse unstructured data",
+ },
+ {
+ id: "15ccc0d7-f37a-4f0a-8163-a37e162877dc",
+ name: "Classification",
+ },
+ {
+ id: "4641ef41-1c0f-421d-b4b2-70fe431081f3",
+ name: "Natural language to Python",
+ },
+ {
+ id: "48d34082-72f3-4a1b-a14d-f15aca4f57a0",
+ name: "Explain code",
+ },
+ {
+ id: "dfd42fd5-0394-4810-92c6-cc907d3bfd1a",
+ name: "Chat",
+ },
+];
diff --git a/src/components/Test/navbar/select-vigneron.tsx b/src/components/Test/navbar/select-vigneron.tsx
new file mode 100644
index 0000000..e07b158
--- /dev/null
+++ b/src/components/Test/navbar/select-vigneron.tsx
@@ -0,0 +1,82 @@
+"use client";
+
+import * as React from "react";
+import { useRouter } from "next/navigation";
+import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
+import { type PopoverProps } from "@radix-ui/react-popover";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+
+import { type Preset } from "./list-de-vignerons";
+
+interface PresetSelectorProps extends PopoverProps {
+ presets: Preset[];
+}
+
+export function SelectVigneron({ presets, ...props }: PresetSelectorProps) {
+ const [open, setOpen] = React.useState(false);
+ const [selectedPreset, setSelectedPreset] = React.useState();
+ const router = useRouter();
+
+ return (
+
+
+
+
+
+
+
+ No presets found.
+
+ {presets.map((preset) => (
+ {
+ setSelectedPreset(preset);
+ setOpen(false);
+ }}
+ >
+ {preset.name}
+
+
+ ))}
+
+
+ router.push("/examples")}>
+ More examples
+
+
+
+
+
+ );
+}
diff --git a/src/components/canvas.tsx b/src/components/canvas.tsx
index db2443c..1956631 100644
--- a/src/components/canvas.tsx
+++ b/src/components/canvas.tsx
@@ -9,16 +9,36 @@ import {
updateImage,
updateText,
} from "@/store/app.slice";
-import { useEffect, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { Toolbar } from "@/components/toolbar";
import TransformableText from "@/components/transformable-text";
import LayerBorder from "@/components/layer-border";
import { CANVAS_PADDING_X, CANVAS_PADDING_Y } from "@/consts/canvas-params";
import Legacy from "@/components/layout/sidebar/legacy";
+import { type Shape } from "konva/lib/Shape";
+import Konva from "konva";
+
+type Snap = "start" | "center" | "end";
+type SnappingEdges = {
+ vertical: Array<{
+ guide: number;
+ offset: number;
+ snap: Snap;
+ }>;
+ horizontal: Array<{
+ guide: number;
+ offset: number;
+ snap: Snap;
+ }>;
+};
+
+const GUIDELINE_OFFSET = 5;
const Canvas = () => {
const dispatch = useAppDispatch();
const stage = useAppSelector((state) => state.app.stage);
+ const stageRef = useRef(null);
+ const layerRef = useRef(null);
const selectedItemId = useAppSelector((state) => state.app.selectedItemId);
const selectItem = (id: string) => dispatch(appSlice.actions.selectItem(id));
const items = useAppSelector((state) => state.app.items);
@@ -56,6 +76,267 @@ const Canvas = () => {
setSelected(!isEditing);
}
+ /* Drag Drop logic */
+
+ const getObjectSnappingEdges = useCallback((node: Shape): SnappingEdges => {
+ const box = node.getClientRect();
+ const absPos = node.absolutePosition();
+
+ return {
+ vertical: [
+ {
+ guide: Math.round(box.x),
+ offset: Math.round(absPos.x - box.x),
+ snap: "start",
+ },
+ {
+ guide: Math.round(box.x + box.width / 2),
+ offset: Math.round(absPos.x - box.x - box.width / 2),
+ snap: "center",
+ },
+ {
+ guide: Math.round(box.x + box.width),
+ offset: Math.round(absPos.x - box.x - box.width),
+ snap: "end",
+ },
+ ],
+ horizontal: [
+ {
+ guide: Math.round(box.y),
+ offset: Math.round(absPos.y - box.y),
+ snap: "start",
+ },
+ {
+ guide: Math.round(box.y + box.height / 2),
+ offset: Math.round(absPos.y - box.y - box.height / 2),
+ snap: "center",
+ },
+ {
+ guide: Math.round(box.y + box.height),
+ offset: Math.round(absPos.y - box.y - box.height),
+ snap: "end",
+ },
+ ],
+ };
+ }, []);
+
+ const getLineGuideStops = (skipShape: Konva.Shape) => {
+ const stage = skipShape.getStage();
+ if (!stage) return { vertical: [], horizontal: [] };
+
+ // we can snap to playground-area borders and the center of the playground-area
+ const vertical = [0, stage.width() / 2, stage.width()];
+ const horizontal = [0, stage.height() / 2, stage.height()];
+
+ // and we snap over edges and center of each object on the canvas
+ stage.find(".object").forEach((guideItem) => {
+ if (guideItem === skipShape) {
+ return;
+ }
+ const box = guideItem.getClientRect();
+ // and we can snap to all edges of shapes
+ vertical.push(box.x, box.x + box.width, box.x + box.width / 2);
+ horizontal.push(box.y, box.y + box.height, box.y + box.height / 2);
+ });
+ return {
+ vertical,
+ horizontal,
+ };
+ };
+
+ const getGuides = useCallback(
+ (
+ lineGuideStops: ReturnType,
+ itemBounds: ReturnType,
+ ) => {
+ const resultV: Array<{
+ lineGuide: number;
+ diff: number;
+ snap: Snap;
+ offset: number;
+ }> = [];
+
+ const resultH: Array<{
+ lineGuide: number;
+ diff: number;
+ snap: Snap;
+ offset: number;
+ }> = [];
+
+ lineGuideStops.vertical.forEach((lineGuide) => {
+ itemBounds.vertical.forEach((itemBound) => {
+ const diff = Math.abs(lineGuide - itemBound.guide);
+ if (diff < GUIDELINE_OFFSET) {
+ resultV.push({
+ lineGuide: lineGuide,
+ diff: diff,
+ snap: itemBound.snap,
+ offset: itemBound.offset,
+ });
+ }
+ });
+ });
+
+ lineGuideStops.horizontal.forEach((lineGuide) => {
+ itemBounds.horizontal.forEach((itemBound) => {
+ const diff = Math.abs(lineGuide - itemBound.guide);
+ if (diff < GUIDELINE_OFFSET) {
+ resultH.push({
+ lineGuide: lineGuide,
+ diff: diff,
+ snap: itemBound.snap,
+ offset: itemBound.offset,
+ });
+ }
+ });
+ });
+
+ const guides: Array<{
+ lineGuide: number;
+ offset: number;
+ orientation: "V" | "H";
+ snap: "start" | "center" | "end";
+ }> = [];
+
+ const minV = resultV.sort((a, b) => a.diff - b.diff)[0];
+ const minH = resultH.sort((a, b) => a.diff - b.diff)[0];
+
+ if (minV) {
+ guides.push({
+ lineGuide: minV.lineGuide,
+ offset: minV.offset,
+ orientation: "V",
+ snap: minV.snap,
+ });
+ }
+
+ if (minH) {
+ guides.push({
+ lineGuide: minH.lineGuide,
+ offset: minH.offset,
+ orientation: "H",
+ snap: minH.snap,
+ });
+ }
+
+ return guides;
+ },
+ [],
+ );
+ const drawGuides = useCallback(
+ (guides: ReturnType, layer: Konva.Layer) => {
+ guides.forEach((lg) => {
+ if (lg.orientation === "H") {
+ const line = new Konva.Line({
+ points: [-6000, 0, 6000, 0],
+ stroke: "rgb(0, 161, 255)",
+ strokeWidth: 1,
+ name: "guid-line",
+ dash: [4, 6],
+ });
+ layer.add(line);
+ line.absolutePosition({
+ x: 0,
+ y: lg.lineGuide,
+ });
+ } else if (lg.orientation === "V") {
+ const line = new Konva.Line({
+ points: [0, -6000, 0, 6000],
+ stroke: "rgb(0, 161, 255)",
+ strokeWidth: 1,
+ name: "guid-line",
+ dash: [4, 6],
+ });
+ layer.add(line);
+ line.absolutePosition({
+ x: lg.lineGuide,
+ y: 0,
+ });
+ }
+ });
+ },
+ [],
+ );
+ const onDragMove = useCallback(
+ (e: Konva.KonvaEventObject) => {
+ const layer = e.target.getLayer();
+ if (!layer) {
+ return;
+ }
+ // clear all previous lines on the screen
+ layer.find(".guid-line").forEach((l) => l.destroy());
+
+ // find possible snapping lines
+ const lineGuideStops = getLineGuideStops(e.target as Konva.Shape);
+ // find snapping points of current object
+ const itemBounds = getObjectSnappingEdges(e.target as Konva.Shape);
+
+ // now find where can we snap current object
+ const guides = getGuides(lineGuideStops, itemBounds);
+
+ // do nothing if no snapping
+ if (!guides.length) {
+ return;
+ }
+
+ drawGuides(guides, layer);
+
+ const absPos = e.target.absolutePosition();
+ // now force object position
+ guides.forEach((lg) => {
+ switch (lg.snap) {
+ case "start": {
+ switch (lg.orientation) {
+ case "V": {
+ absPos.x = lg.lineGuide + lg.offset;
+ break;
+ }
+ case "H": {
+ absPos.y = lg.lineGuide + lg.offset;
+ break;
+ }
+ }
+ break;
+ }
+ case "center": {
+ switch (lg.orientation) {
+ case "V": {
+ absPos.x = lg.lineGuide + lg.offset;
+ break;
+ }
+ case "H": {
+ absPos.y = lg.lineGuide + lg.offset;
+ break;
+ }
+ }
+ break;
+ }
+ case "end": {
+ switch (lg.orientation) {
+ case "V": {
+ absPos.x = lg.lineGuide + lg.offset;
+ break;
+ }
+ case "H": {
+ absPos.y = lg.lineGuide + lg.offset;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ });
+ e.target.absolutePosition(absPos);
+ },
+ [drawGuides, getGuides, getObjectSnappingEdges],
+ );
+
+ const onDragEnd = (e: Konva.KonvaEventObject) => {
+ const layer = e.target.getLayer();
+ // clear all previous lines on the screen
+ layer?.find(".guid-line").forEach((l) => l.destroy());
+ };
+
return (
@@ -72,6 +353,7 @@ const Canvas = () => {
>
{
clipY={CANVAS_PADDING_Y}
clipWidth={stage.width - 2 * CANVAS_PADDING_X}
clipHeight={stage.height - 2 * CANVAS_PADDING_Y}
+ ref={layerRef}
>
{items.toReversed().map((item) => {
if (item.type === StageItemType.Image) {
@@ -109,7 +392,12 @@ const Canvas = () => {
onChange={(newAttrs) => {
dispatch(updateImage({ id: item.id, ...newAttrs }));
}}
- imageProps={item.params}
+ imageProps={{
+ ...item.params,
+ onDragMove,
+ onDragEnd,
+ name: "object",
+ }}
key={item.id}
isBlocked={item.isBlocked}
/>
@@ -126,7 +414,12 @@ const Canvas = () => {
onChange={(newAttrs) => {
dispatch(updateText({ id: item.id, ...newAttrs }));
}}
- textProps={item.params}
+ textProps={{
+ ...item.params,
+ onDragMove,
+ onDragEnd,
+ name: "object",
+ }}
key={item.id}
id={item.id}
/>
diff --git a/src/components/layout/layout.tsx b/src/components/layout/layout.tsx
index 699610e..d869839 100644
--- a/src/components/layout/layout.tsx
+++ b/src/components/layout/layout.tsx
@@ -1,6 +1,9 @@
import type { ReactNode } from "react";
-import { Sidebar } from "./sidebar/sidebar";
-import { Navbar } from "./navbar";
+import React from "react";
+import { Separator } from "@/components/ui/separator";
+import { Tabs } from "@/components/ui/tabs";
+import { Sidebar } from "@/components/layout/sidebar/sidebar";
+import { Navbar } from "@/components/layout/navbar";
type Props = {
children: ReactNode;
@@ -14,6 +17,23 @@ export const Layout = ({ children }: Props) => {
{children}
+
+ {/*Test Update Version */}
+
+ {/* */}
);
};
diff --git a/src/components/layout/sidebar/image-gallery.tsx b/src/components/layout/sidebar/image-gallery.tsx
new file mode 100644
index 0000000..c340900
--- /dev/null
+++ b/src/components/layout/sidebar/image-gallery.tsx
@@ -0,0 +1,140 @@
+import React, { type ElementRef, useEffect, useRef, useState } from "react";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Button } from "@/components/ui/button";
+import { Card, CardHeader, CardTitle } from "@/components/ui/card";
+import axios from "axios";
+import * as process from "process";
+import { LuLayoutTemplate } from "react-icons/lu";
+import Image from "next/image";
+import { Input } from "@/components/ui/input";
+
+export const ImageGallery = () => {
+ const [open, setOpen] = useState(false);
+ const [query, setQuery] = useState("");
+ const [page, setPage] = useState(1);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const [photos, setPhotos] = useState([]);
+
+ const scrollAreaRef = useRef>(null);
+
+ const mainUrl = "https://api.unsplash.com/photos";
+ const searchUrl = "https://api.unsplash.com/search/photos";
+ const clientID = `?client_id=${process.env.NEXT_PUBLIC_ACCESS_KEY}`;
+
+ useEffect(() => {
+ fetchImages();
+ }, [query, page]);
+
+ const fetchImages = async () => {
+ setIsLoading(true);
+ let url;
+ const urlPage = `&page=${page}`;
+ const urlQuery = `&query=${query}`;
+
+ if (query) {
+ url = `${searchUrl}${clientID}${urlPage}${urlQuery}`;
+ console.log(url);
+ } else {
+ url = `${mainUrl}${clientID}${urlPage}`;
+ }
+
+ try {
+ const { data } = await axios.get(url);
+
+ setPhotos((oldPhotos) => {
+ if (query && page === 1) {
+ return data.results;
+ } else if (query) {
+ return [...oldPhotos, ...data.results];
+ } else {
+ console.log(data);
+ return [...oldPhotos, ...data];
+ }
+ });
+ setIsLoading(false);
+ } catch (error) {
+ console.log(error);
+ setIsLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ const ref = scrollAreaRef.current;
+ const handler = () => {
+ if (scrollAreaRef.current) {
+ console.log(
+ scrollAreaRef.current.clientHeight + scrollAreaRef.current.scrollTop,
+ scrollAreaRef.current.scrollHeight - 2,
+ );
+ }
+ if (
+ isLoading ||
+ !scrollAreaRef.current ||
+ scrollAreaRef.current.clientHeight + scrollAreaRef.current.scrollTop <
+ scrollAreaRef.current.scrollHeight - 2
+ ) {
+ return;
+ }
+
+ setPage((oldPage) => {
+ return oldPage + 1;
+ });
+ };
+
+ scrollAreaRef.current?.addEventListener("scroll", handler);
+ return () => ref?.removeEventListener("scroll", handler);
+ }, [isLoading, open]);
+
+ return (
+
+
+
+
+
+
+
+ Template
+
+
+ setQuery(e.target.value)}
+ />
+
+
+ {photos?.map((p, i) => (
+
+
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/src/components/layout/sidebar/sidebar.tsx b/src/components/layout/sidebar/sidebar.tsx
index c0ea5d0..d3e3c69 100644
--- a/src/components/layout/sidebar/sidebar.tsx
+++ b/src/components/layout/sidebar/sidebar.tsx
@@ -4,7 +4,7 @@ import SizeSelect from "@/components/layout/sidebar/size-select";
import { BackgroundSelect } from "@/components/layout/sidebar/background-select";
import { Layers } from "@/components/layout/sidebar/layers";
import { SelectTemplate } from "@/components/layout/sidebar/select-template";
-import { Switch } from "@/components/ui/switch";
+import { ImageGallery } from "@/components/layout/sidebar/image-gallery";
export function Sidebar() {
return (
@@ -14,6 +14,7 @@ export function Sidebar() {
+
);
diff --git a/src/components/transformable-image.tsx b/src/components/transformable-image.tsx
index 9082988..40d1e05 100644
--- a/src/components/transformable-image.tsx
+++ b/src/components/transformable-image.tsx
@@ -41,21 +41,11 @@ export const TransformableImage = ({
<>
{
- console.log(e);
- const positionX = e.target.x() + e.target.width() / 2;
- const positionY = e.target.y() + e.target.height() / 2;
- const canvasXCenter = (e.target.getStage()?.width() ?? 0) / 2;
- const canvasYCenter = (e.target.getStage()?.height() ?? 0) / 2;
- const isInTheXCenter =
- Math.abs(Math.round(canvasXCenter) - Math.round(positionX)) <= 5;
- const isInTheYCenter =
- Math.abs(Math.round(canvasYCenter) - Math.round(positionY)) <= 5;
+ if (isBlocked) {
+ return;
+ }
- document.body.classList.toggle("show-vertical-line", isInTheXCenter);
- document.body.classList.toggle(
- "show-horizontal-line",
- isInTheYCenter,
- );
+ imageProps.onDragMove(e);
}}
id={id}
alt={"canvas image"}
@@ -69,18 +59,7 @@ export const TransformableImage = ({
if (isBlocked) {
return;
}
- const positionX = e.target.x() + e.target.width() / 2;
- const canvasXCenter = (e.target.getStage()?.width() ?? 0) / 2;
- const isInTheCenter =
- Math.abs(Math.round(canvasXCenter) - Math.round(positionX)) <= 5;
- onChange({
- ...imageProps,
- image,
- x: isInTheCenter
- ? canvasXCenter - e.target.width() / 2
- : e.target.x(),
- y: e.target.y(),
- });
+ imageProps.onDragEnd?.(e);
}}
onTransformEnd={() => {
const node = imageRef.current;
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
new file mode 100644
index 0000000..80acc66
--- /dev/null
+++ b/src/components/ui/form.tsx
@@ -0,0 +1,171 @@
+import * as React from "react";
+import type * as LabelPrimitive from "@radix-ui/react-label";
+import { Slot } from "@radix-ui/react-slot";
+import { Controller, FormProvider, useFormContext } from "react-hook-form";
+import type { ControllerProps, FieldPath, FieldValues } from "react-hook-form";
+
+import { cn } from "@/lib/utils";
+import { Label } from "@/components/ui/label";
+
+const Form = FormProvider;
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+> = {
+ name: TName;
+};
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue,
+);
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ );
+};
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext);
+ const itemContext = React.useContext(FormItemContext);
+ const { getFieldState, formState } = useFormContext();
+
+ const fieldState = getFieldState(fieldContext.name, formState);
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ");
+ }
+
+ const { id } = itemContext;
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ };
+};
+
+type FormItemContextValue = {
+ id: string;
+};
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue,
+);
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId();
+
+ return (
+
+
+
+ );
+});
+FormItem.displayName = "FormItem";
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField();
+
+ return (
+
+ );
+});
+FormLabel.displayName = "FormLabel";
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField();
+
+ return (
+
+ );
+});
+FormControl.displayName = "FormControl";
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField();
+
+ return (
+
+ );
+});
+FormDescription.displayName = "FormDescription";
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField();
+ const body = error ? String(error?.message) : children;
+
+ if (!body) {
+ return null;
+ }
+
+ return (
+
+ {body}
+
+ );
+});
+FormMessage.displayName = "FormMessage";
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+};
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index bbba7e0..59dd525 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -1,11 +1,11 @@
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
+import * as React from "react";
+import * as PopoverPrimitive from "@radix-ui/react-popover";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const Popover = PopoverPrimitive.Root
+const Popover = PopoverPrimitive.Root;
-const PopoverTrigger = PopoverPrimitive.Trigger
+const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef,
@@ -18,12 +18,12 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
+ className,
)}
{...props}
/>
-))
-PopoverContent.displayName = PopoverPrimitive.Content.displayName
+));
+PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-export { Popover, PopoverTrigger, PopoverContent }
+export { Popover, PopoverTrigger, PopoverContent };
diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx
index ffc5a82..570a076 100644
--- a/src/components/ui/scroll-area.tsx
+++ b/src/components/ui/scroll-area.tsx
@@ -8,9 +8,9 @@ const ScrollArea = React.forwardRef<
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
{children}
diff --git a/src/consts/sources-api.ts b/src/consts/sources-api.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 2c97f0f..d1c07bc 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,6 +1,10 @@
import dynamic from "next/dynamic";
import Head from "next/head";
const Canvas = dynamic(() => import("../components/canvas"), { ssr: false });
+/*const Canvas = dynamic(
+ () => import("../components/Test/content/playground-area/stage-area"),
+ { ssr: false },
+);*/
export default function Home() {
return (