mirror of
https://github.com/r2r90/canvas-label.git
synced 2025-12-18 12:35:49 +00:00
vercel json add
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
"@trpc/next": "^10.37.1",
|
"@trpc/next": "^10.37.1",
|
||||||
"@trpc/react-query": "^10.37.1",
|
"@trpc/react-query": "^10.37.1",
|
||||||
"@trpc/server": "^10.37.1",
|
"@trpc/server": "^10.37.1",
|
||||||
|
"@types/react-color": "^3.0.10",
|
||||||
"@types/uuid": "^9.0.3",
|
"@types/uuid": "^9.0.3",
|
||||||
"@uiw/react-color": "^2.0.3",
|
"@uiw/react-color": "^2.0.3",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
"next": "^13.4.19",
|
"next": "^13.4.19",
|
||||||
"next-auth": "^4.23.0",
|
"next-auth": "^4.23.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.49.0",
|
"react-hook-form": "^7.49.0",
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
|
|||||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -98,6 +98,9 @@ dependencies:
|
|||||||
'@trpc/server':
|
'@trpc/server':
|
||||||
specifier: ^10.37.1
|
specifier: ^10.37.1
|
||||||
version: 10.42.0
|
version: 10.42.0
|
||||||
|
'@types/react-color':
|
||||||
|
specifier: ^3.0.10
|
||||||
|
version: 3.0.10
|
||||||
'@types/uuid':
|
'@types/uuid':
|
||||||
specifier: ^9.0.3
|
specifier: ^9.0.3
|
||||||
version: 9.0.6
|
version: 9.0.6
|
||||||
@@ -137,6 +140,9 @@ dependencies:
|
|||||||
react:
|
react:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
|
react-color:
|
||||||
|
specifier: ^2.19.3
|
||||||
|
version: 2.19.3(react@18.2.0)
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
@@ -386,6 +392,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
|
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@icons/material@0.2.4(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@jridgewell/gen-mapping@0.3.3:
|
/@jridgewell/gen-mapping@0.3.3:
|
||||||
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -2083,6 +2097,13 @@ packages:
|
|||||||
/@types/prop-types@15.7.9:
|
/@types/prop-types@15.7.9:
|
||||||
resolution: {integrity: sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==}
|
resolution: {integrity: sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==}
|
||||||
|
|
||||||
|
/@types/react-color@3.0.10:
|
||||||
|
resolution: {integrity: sha512-6K5BAn3zyd8lW8UbckIAVeXGxR82Za9jyGD2DBEynsa7fKaguLDVtjfypzs7fgEV7bULgs7uhds8A8v1wABTvQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.2.31
|
||||||
|
'@types/reactcss': 1.2.11
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/react-dom@18.2.14:
|
/@types/react-dom@18.2.14:
|
||||||
resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==}
|
resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2101,6 +2122,12 @@ packages:
|
|||||||
'@types/scheduler': 0.16.5
|
'@types/scheduler': 0.16.5
|
||||||
csstype: 3.1.2
|
csstype: 3.1.2
|
||||||
|
|
||||||
|
/@types/reactcss@1.2.11:
|
||||||
|
resolution: {integrity: sha512-0fFy0ubuPlhksId8r9V8nsLcxBAPQnn15g/ERAElgE9L6rOquMj2CapsxqfyBuHlkp0/ndEUVnkYI7MkTtkGpw==}
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.2.31
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/scheduler@0.16.5:
|
/@types/scheduler@0.16.5:
|
||||||
resolution: {integrity: sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==}
|
resolution: {integrity: sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==}
|
||||||
|
|
||||||
@@ -4034,10 +4061,18 @@ packages:
|
|||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lodash-es@4.17.21:
|
||||||
|
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.merge@4.6.2:
|
/lodash.merge@4.6.2:
|
||||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lodash@4.17.21:
|
||||||
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/loose-envify@1.4.0:
|
/loose-envify@1.4.0:
|
||||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -4058,6 +4093,10 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/material-colors@1.2.6:
|
||||||
|
resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/merge2@1.4.1:
|
/merge2@1.4.1:
|
||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -4519,7 +4558,6 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/proxy-from-env@1.1.0:
|
/proxy-from-env@1.1.0:
|
||||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
@@ -4533,6 +4571,21 @@ packages:
|
|||||||
/queue-microtask@1.2.3:
|
/queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
/react-color@2.19.3(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
dependencies:
|
||||||
|
'@icons/material': 0.2.4(react@18.2.0)
|
||||||
|
lodash: 4.17.21
|
||||||
|
lodash-es: 4.17.21
|
||||||
|
material-colors: 1.2.6
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 18.2.0
|
||||||
|
reactcss: 1.2.3(react@18.2.0)
|
||||||
|
tinycolor2: 1.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-dom@18.2.0(react@18.2.0):
|
/react-dom@18.2.0(react@18.2.0):
|
||||||
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
|
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4728,6 +4781,15 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/reactcss@1.2.3(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
dependencies:
|
||||||
|
lodash: 4.17.21
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/read-cache@1.0.0:
|
/read-cache@1.0.0:
|
||||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5089,6 +5151,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
any-promise: 1.3.0
|
any-promise: 1.3.0
|
||||||
|
|
||||||
|
/tinycolor2@1.6.0:
|
||||||
|
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/to-regex-range@5.0.1:
|
/to-regex-range@5.0.1:
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
engines: {node: '>=8.0'}
|
engines: {node: '>=8.0'}
|
||||||
|
|||||||
BIN
public/bottle-images/bottle-schema.png
Normal file
BIN
public/bottle-images/bottle-schema.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 320 KiB |
BIN
public/bottle-images/bouteille-demie.png
Normal file
BIN
public/bottle-images/bouteille-demie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
BIN
public/bottle-images/bouteille-magnum.png
Normal file
BIN
public/bottle-images/bouteille-magnum.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
BIN
public/bottle-images/bouteille-standard.png
Normal file
BIN
public/bottle-images/bouteille-standard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
@@ -34,7 +34,7 @@ export default function LayerItem({ item }: { item: StageItem }) {
|
|||||||
style={style}
|
style={style}
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
{...attributes}
|
{...attributes}
|
||||||
className="flex items-center justify-between rounded-md border bg-yellow-50 p-2 text-black"
|
className="flex items-center justify-between rounded-md border p-2 text-black"
|
||||||
>
|
>
|
||||||
<IconButton {...listeners}>
|
<IconButton {...listeners}>
|
||||||
<GripVertical size={18} />
|
<GripVertical size={18} />
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import * as process from "process";
|
import * as process from "process";
|
||||||
import { LuLayoutTemplate } from "react-icons/lu";
|
import { LuLayoutTemplate } from "react-icons/lu";
|
||||||
import Image from "next/image";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
|
|
||||||
export const ImageGallery = () => {
|
export const ImageGallery = () => {
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ import * as process from "process";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
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 { models } from "@/components/layout/sidebar/stage-settings/stage-size-selector/models";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImageGallery = ({ open }: Props) => {
|
export const ImageGallery = ({ open }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const [query, setQuery] = useState<string>("");
|
const [query, setQuery] = useState<string>("");
|
||||||
const [page, setPage] = useState<number>(1);
|
const [page, setPage] = useState<number>(1);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
@@ -41,7 +44,6 @@ export const ImageGallery = ({ open }: Props) => {
|
|||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
url = `${searchUrl}${clientID}${urlPage}${urlQuery}`;
|
url = `${searchUrl}${clientID}${urlPage}${urlQuery}`;
|
||||||
console.log(url);
|
|
||||||
} else {
|
} else {
|
||||||
url = `${mainUrl}${clientID}${urlPage}`;
|
url = `${mainUrl}${clientID}${urlPage}`;
|
||||||
}
|
}
|
||||||
@@ -55,7 +57,6 @@ export const ImageGallery = ({ open }: Props) => {
|
|||||||
} else if (query) {
|
} else if (query) {
|
||||||
return [...oldPhotos, ...data.results];
|
return [...oldPhotos, ...data.results];
|
||||||
} else {
|
} else {
|
||||||
console.log(data);
|
|
||||||
return [...oldPhotos, ...data];
|
return [...oldPhotos, ...data];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -70,10 +71,10 @@ export const ImageGallery = ({ open }: Props) => {
|
|||||||
const ref = scrollAreaRef.current;
|
const ref = scrollAreaRef.current;
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
if (scrollAreaRef.current) {
|
if (scrollAreaRef.current) {
|
||||||
console.log(
|
/* console.log(
|
||||||
scrollAreaRef.current.clientHeight + scrollAreaRef.current.scrollTop,
|
scrollAreaRef.current.clientHeight + scrollAreaRef.current.scrollTop,
|
||||||
scrollAreaRef.current.scrollHeight - 2,
|
scrollAreaRef.current.scrollHeight - 2,
|
||||||
);
|
);*/
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
isLoading ||
|
isLoading ||
|
||||||
@@ -104,16 +105,17 @@ export const ImageGallery = ({ open }: Props) => {
|
|||||||
return () => ref?.removeEventListener("scroll", handler);
|
return () => ref?.removeEventListener("scroll", handler);
|
||||||
}, [isLoading, open]);
|
}, [isLoading, open]);
|
||||||
|
|
||||||
const handleImageAdd = (e) => {
|
const handleImageAdd = (url: string, width: number, height: number) => {
|
||||||
console.log(e.target);
|
const aspectRatio = height / width;
|
||||||
|
const finalWidth = 200;
|
||||||
|
const finalHeight = finalWidth * aspectRatio;
|
||||||
|
dispatch(
|
||||||
|
addImage({ imageUrl: url, width: finalWidth, height: finalHeight }),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="min-h-full p-3">
|
<Card className="h-full min-h-[600px] p-3">
|
||||||
<CardHeader className="mb-2 p-2 text-center">
|
|
||||||
<CardTitle>Template</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search Images"
|
placeholder="Search Images"
|
||||||
@@ -122,24 +124,27 @@ export const ImageGallery = ({ open }: Props) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="mt-3 max-h-64 w-full grid-cols-2 justify-center overflow-auto"
|
className="mt-3 grid max-h-[550px] w-full grid-cols-2 justify-center gap-2 overflow-auto "
|
||||||
ref={scrollAreaRef}
|
ref={scrollAreaRef}
|
||||||
>
|
>
|
||||||
{photos?.map((p, i) => (
|
{photos?.map((p, i) => {
|
||||||
<div
|
return (
|
||||||
key={i}
|
<div
|
||||||
onClick={(e) => handleImageAdd(e)}
|
key={i}
|
||||||
className="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={() => handleImageAdd(p.urls.raw, p.width, p.height)}
|
||||||
>
|
className=" rounded-md 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"
|
||||||
<Image
|
>
|
||||||
key={p.i}
|
<Image
|
||||||
src={p.urls.small}
|
key={p.i}
|
||||||
alt={"image"}
|
src={p.urls.small}
|
||||||
height={100}
|
alt={"image"}
|
||||||
width={100}
|
height={100}
|
||||||
/>
|
width={100}
|
||||||
</div>
|
className="h-32 w-full"
|
||||||
))}
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ export const Layers = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-2">
|
<Card className="p-2">
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-center">Layers</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="grid items-center gap-2 p-2 ">
|
<CardContent className="grid items-center gap-2 p-2 ">
|
||||||
<DndContext
|
<DndContext
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { LuLayoutTemplate } from "react-icons/lu";
|
import { LuLayoutTemplate } from "react-icons/lu";
|
||||||
import {
|
import {
|
||||||
|
ColumnsIcon,
|
||||||
GearIcon,
|
GearIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
LayersIcon,
|
LayersIcon,
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import { StageSize } from "@/components/layout/sidebar/stage-settings/stage-size";
|
import { StageSize } from "@/components/layout/sidebar/stage-settings/stage-size";
|
||||||
import { StageBackground } from "@/components/layout/sidebar/stage-settings/stage-background";
|
import { StageBackground } from "@/components/layout/sidebar/stage-settings/stage-background";
|
||||||
|
import { StageBackgroundChange } from "@/components/layout/sidebar/stage-settings/stage-background-change/stage-background-change";
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const [openGallery, setOpenGallery] = useState<boolean>(false);
|
const [openGallery, setOpenGallery] = useState<boolean>(false);
|
||||||
@@ -20,7 +22,7 @@ export function Sidebar() {
|
|||||||
return (
|
return (
|
||||||
<div className="items-left flex w-full flex-col p-2 pt-4">
|
<div className="items-left flex w-full flex-col p-2 pt-4">
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabsList className="grid grid-cols-5">
|
<TabsList className="grid grid-cols-6">
|
||||||
<TabsTrigger value="stage">
|
<TabsTrigger value="stage">
|
||||||
<GearIcon />
|
<GearIcon />
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@@ -41,6 +43,9 @@ export function Sidebar() {
|
|||||||
<TabsTrigger value="layers">
|
<TabsTrigger value="layers">
|
||||||
<LayersIcon />
|
<LayersIcon />
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="background">
|
||||||
|
<ColumnsIcon />
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="stage">
|
<TabsContent value="stage">
|
||||||
<StageSize />
|
<StageSize />
|
||||||
@@ -58,6 +63,9 @@ export function Sidebar() {
|
|||||||
<TabsContent value="layers">
|
<TabsContent value="layers">
|
||||||
<Layers />
|
<Layers />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="background">
|
||||||
|
<StageBackgroundChange />
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import React, { type ElementRef, useEffect, useRef, useState } from "react";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import axios from "axios";
|
||||||
|
import * as process from "process";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { selectBackground, setBackgroundImage } from "@/store/app.slice";
|
||||||
|
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||||
|
import { SliderPicker } from "react-color";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StageBackgroundChange = ({ open }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { stage } = useAppSelector((state) => state.app.history[0]);
|
||||||
|
const [query, setQuery] = useState<string>("gradient");
|
||||||
|
const [page, setPage] = useState<number>(1);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [photos, setPhotos] = useState<any[]>([]);
|
||||||
|
const scrollAreaRef = useRef<ElementRef<"div">>(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}`;
|
||||||
|
const bgColor = useAppSelector((state) => state.app.backgroundColor);
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
} 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 {
|
||||||
|
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]);
|
||||||
|
|
||||||
|
const handleImageAdd = (url: string) => {
|
||||||
|
dispatch(
|
||||||
|
setBackgroundImage({
|
||||||
|
imageUrl: url,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackgroundSelect = (bgColor) => {
|
||||||
|
// dispatch(selectBackground(bgColor.hex));
|
||||||
|
|
||||||
|
console.log(bgColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="h-full min-h-[600px] p-3">
|
||||||
|
{/*<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Images"
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<div className=" mt-4">
|
||||||
|
{/* <input
|
||||||
|
className="h-[2rem] w-[2rem]"
|
||||||
|
type="color"
|
||||||
|
value={bgColor ?? undefined}
|
||||||
|
onChange={(e) => handleBackgroundSelect(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="h-[2rem] w-[2rem]"
|
||||||
|
type="color"
|
||||||
|
value={bgColor ?? undefined}
|
||||||
|
onChange={(e) => handleBackgroundSelect(e.target.value)}
|
||||||
|
/>*/}
|
||||||
|
<SliderPicker
|
||||||
|
color={bgColor}
|
||||||
|
onChangeComplete={(e) => handleBackgroundSelect(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="grid max-h-[550px] w-full grid-cols-2 justify-center gap-2 overflow-auto "
|
||||||
|
ref={scrollAreaRef}
|
||||||
|
>
|
||||||
|
{photos?.map((p, i) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={() => handleImageAdd(p.urls.raw)}
|
||||||
|
className="rounded-md 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"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
key={p.i}
|
||||||
|
src={p.urls.small}
|
||||||
|
alt={"image"}
|
||||||
|
height={100}
|
||||||
|
width={100}
|
||||||
|
className="h-32 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
|
import { type PopoverProps } from "@radix-ui/react-popover";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useMutationObserver } from "@/hooks/use-mutation-observer";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
HoverCard,
|
||||||
|
HoverCardContent,
|
||||||
|
HoverCardTrigger,
|
||||||
|
} from "@/components/ui/hover-card";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
|
import { type Model, type ModelType } from "./models";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
interface ModelSelectorProps extends PopoverProps {
|
||||||
|
types: readonly ModelType[];
|
||||||
|
models: Model[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [selectedModel, setSelectedModel] = React.useState<Model>(models[0]);
|
||||||
|
const [peekedModel, setPeekedModel] = React.useState<Model>(models[0]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<HoverCard openDelay={200}>
|
||||||
|
<HoverCardTrigger asChild>
|
||||||
|
<Label htmlFor="model">Selectionnez la taille d'etiquette</Label>
|
||||||
|
</HoverCardTrigger>
|
||||||
|
<HoverCardContent
|
||||||
|
align="start"
|
||||||
|
className="w-[260px] bg-yellow-400 text-sm"
|
||||||
|
side="left"
|
||||||
|
>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium
|
||||||
|
alias aut eaque eligendi fugiat harum, id ipsa ipsum maxime molestiae
|
||||||
|
nesciunt nobis odio quisquam quos reprehenderit sapiente suscipit, vel
|
||||||
|
voluptatibus.
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
|
<Popover open={open} onOpenChange={setOpen} {...props}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
aria-label="Select a model"
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
{selectedModel ? selectedModel.name : "Select a model..."}
|
||||||
|
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent align="end" className="w-[250px] p-0">
|
||||||
|
<HoverCard>
|
||||||
|
<HoverCardContent
|
||||||
|
side="left"
|
||||||
|
align="start"
|
||||||
|
forceMount
|
||||||
|
className="min-h-[500px] min-w-[200px] "
|
||||||
|
>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<h4 className="font-medium leading-none">{peekedModel.name}</h4>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{peekedModel.description}
|
||||||
|
</div>
|
||||||
|
{peekedModel.image ? (
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
src={peekedModel.image}
|
||||||
|
alt={peekedModel.name}
|
||||||
|
width={200}
|
||||||
|
height={500}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</HoverCardContent>
|
||||||
|
<Command loop>
|
||||||
|
<CommandList className="h-[var(--cmdk-list-height)] max-h-[400px]">
|
||||||
|
<HoverCardTrigger />
|
||||||
|
{types.map((type) => (
|
||||||
|
<CommandGroup key={type} heading={type}>
|
||||||
|
{models
|
||||||
|
.filter((model) => model.type === type)
|
||||||
|
.map((model) => (
|
||||||
|
<ModelItem
|
||||||
|
key={model.id}
|
||||||
|
model={model}
|
||||||
|
isSelected={selectedModel?.id === model.id}
|
||||||
|
onPeek={(model) => setPeekedModel(model)}
|
||||||
|
onSelect={() => {
|
||||||
|
setSelectedModel(model);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
))}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</HoverCard>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelItemProps {
|
||||||
|
model: Model;
|
||||||
|
isSelected: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
onPeek: (model: Model) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
|
||||||
|
const ref = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useMutationObserver(ref, (mutations) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type === "attributes") {
|
||||||
|
if (mutation.attributeName === "aria-selected") {
|
||||||
|
onPeek(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={model.id}
|
||||||
|
onSelect={onSelect}
|
||||||
|
ref={ref}
|
||||||
|
className="aria-selected:bg-primary aria-selected:text-primary-foreground"
|
||||||
|
>
|
||||||
|
{model.name}
|
||||||
|
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
isSelected ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export const types = ["GPT-3", "Codex"] as const;
|
||||||
|
|
||||||
|
export type ModelType = (typeof types)[number];
|
||||||
|
|
||||||
|
export interface Model<Type = string> {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
image: string;
|
||||||
|
type: Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const models: Model<ModelType>[] = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
name: "Demie (S)",
|
||||||
|
description:
|
||||||
|
"Most capable GPT-3 model. Can do any task the other models can do, often with higher quality, longer output and better instruction-following. Also supports inserting completions within text.",
|
||||||
|
type: "GPT-3",
|
||||||
|
image: "/bottle-images/bouteille-demie.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
name: "Bouteille (M)",
|
||||||
|
image: "/bottle-images/bouteille-standard.png",
|
||||||
|
description: "Very capable, but faster and lower cost than Davinci.",
|
||||||
|
type: "GPT-3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
name: "Magnum (L)",
|
||||||
|
image: "/bottle-images/bouteille-magnum.png",
|
||||||
|
description: "Capable of straightforward tasks, very fast, and lower cost.",
|
||||||
|
type: "GPT-3",
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -3,6 +3,8 @@ import { Card, CardHeader, CardTitle } from "@/components/ui/card";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useAppDispatch } from "@/hooks";
|
import { useAppDispatch } from "@/hooks";
|
||||||
import { updateStage } from "@/store/app.slice";
|
import { updateStage } from "@/store/app.slice";
|
||||||
|
import { ModelSelector } from "@/components/layout/sidebar/stage-settings/stage-size-selector/model-selector";
|
||||||
|
import { models, types } from "./stage-size-selector/models";
|
||||||
|
|
||||||
export const StageSize = () => {
|
export const StageSize = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -15,7 +17,7 @@ export const StageSize = () => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Card className="p-3">
|
<Card className="p-3">
|
||||||
<CardHeader>
|
{/*<CardHeader>
|
||||||
<CardTitle>{`Sélectionnez la taille d'étiquette`}</CardTitle>
|
<CardTitle>{`Sélectionnez la taille d'étiquette`}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
@@ -29,14 +31,9 @@ export const StageSize = () => {
|
|||||||
<Button onClick={() => handleStageSizeSelect(410, 289)}>
|
<Button onClick={() => handleStageSizeSelect(410, 289)}>
|
||||||
Grand Format
|
Grand Format
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>*/}
|
||||||
|
|
||||||
{/*<CardFooter className="mt-8 justify-between">*/}
|
<ModelSelector types={types} models={models} />
|
||||||
{/* <Button variant="ghost" onClick={() => setOpen(false)}>*/}
|
|
||||||
{/* Cancel*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* <Button onClick={handleSizeSelect}>Submit</Button>*/}
|
|
||||||
{/*</CardFooter>*/}
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ export default function TextInput() {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Card className="p-3">
|
<Card className="p-3">
|
||||||
<CardHeader className="pt-2">
|
|
||||||
<CardTitle className="text-center">Text</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
placeholder="Entrez votre text ..."
|
placeholder="Entrez votre text ..."
|
||||||
|
|||||||
20
src/hooks/use-mutation-observer.ts
Normal file
20
src/hooks/use-mutation-observer.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export const useMutationObserver = (
|
||||||
|
ref: React.MutableRefObject<HTMLElement | null>,
|
||||||
|
callback: MutationCallback,
|
||||||
|
options = {
|
||||||
|
attributes: true,
|
||||||
|
characterData: true,
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const observer = new MutationObserver(callback);
|
||||||
|
observer.observe(ref.current, options);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}
|
||||||
|
}, [ref, callback, options]);
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import type { AnyAction, PayloadAction } from "@reduxjs/toolkit";
|
|||||||
import { createSlice, current } from "@reduxjs/toolkit";
|
import { createSlice, current } from "@reduxjs/toolkit";
|
||||||
import { v1 } from "uuid";
|
import { v1 } from "uuid";
|
||||||
import { type WritableDraft } from "immer/src/types/types-external";
|
import { type WritableDraft } from "immer/src/types/types-external";
|
||||||
|
import { CANVAS_PADDING_X, CANVAS_PADDING_Y } from "@/consts/canvas-params";
|
||||||
|
|
||||||
export enum StageItemType {
|
export enum StageItemType {
|
||||||
Text = "text",
|
Text = "text",
|
||||||
@@ -61,6 +62,16 @@ const defaultImageConfig = {
|
|||||||
scaleY: 1,
|
scaleY: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultBackgroundImageConfig = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
opacity: 1,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
scaleX: 1,
|
||||||
|
scaleY: 1,
|
||||||
|
};
|
||||||
|
|
||||||
const addNewItemToHistory =
|
const addNewItemToHistory =
|
||||||
(
|
(
|
||||||
callback: (
|
callback: (
|
||||||
@@ -195,6 +206,36 @@ export const appSlice = createSlice({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
setBackgroundImage: addNewItemToHistory(
|
||||||
|
(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
imageUrl: string;
|
||||||
|
}>,
|
||||||
|
currentHistoryEntry,
|
||||||
|
) => {
|
||||||
|
const image = action.payload;
|
||||||
|
if (!image) return;
|
||||||
|
const imageId = v1();
|
||||||
|
const newImage = {
|
||||||
|
type: StageItemType.Image,
|
||||||
|
id: imageId,
|
||||||
|
isBlocked: false,
|
||||||
|
params: {
|
||||||
|
imageUrl: action.payload.imageUrl,
|
||||||
|
width: state.history[0]?.stage.width,
|
||||||
|
height: state.history[0]?.stage.height,
|
||||||
|
...defaultBackgroundImageConfig,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
const newHistoryEntry = {
|
||||||
|
...currentHistoryEntry,
|
||||||
|
items: [...currentHistoryEntry.items, newImage],
|
||||||
|
};
|
||||||
|
state.history.push(newHistoryEntry);
|
||||||
|
},
|
||||||
|
),
|
||||||
// -
|
// -
|
||||||
selectItem: addNewItemToHistory(
|
selectItem: addNewItemToHistory(
|
||||||
(state, action: PayloadAction<string>, currentHistoryEntry) => {
|
(state, action: PayloadAction<string>, currentHistoryEntry) => {
|
||||||
@@ -332,4 +373,5 @@ export const {
|
|||||||
setBlockedItem,
|
setBlockedItem,
|
||||||
goBack,
|
goBack,
|
||||||
goForward,
|
goForward,
|
||||||
|
setBackgroundImage,
|
||||||
} = appSlice.actions;
|
} = appSlice.actions;
|
||||||
|
|||||||
16
vercel.json
Normal file
16
vercel.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "labels-app",
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"src": "src/pages/index.ts",
|
||||||
|
"use": "@vercel/node"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"src": "/(.*)",
|
||||||
|
"dest": "src/pages/index.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user