mirror of
https://github.com/r2r90/canvas-label.git
synced 2025-12-17 21:19:39 +00:00
vercel json add
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"@trpc/next": "^10.37.1",
|
||||
"@trpc/react-query": "^10.37.1",
|
||||
"@trpc/server": "^10.37.1",
|
||||
"@types/react-color": "^3.0.10",
|
||||
"@types/uuid": "^9.0.3",
|
||||
"@uiw/react-color": "^2.0.3",
|
||||
"axios": "^1.5.1",
|
||||
@@ -55,6 +56,7 @@
|
||||
"next": "^13.4.19",
|
||||
"next-auth": "^4.23.0",
|
||||
"react": "18.2.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.49.0",
|
||||
"react-icons": "^4.11.0",
|
||||
|
||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -98,6 +98,9 @@ dependencies:
|
||||
'@trpc/server':
|
||||
specifier: ^10.37.1
|
||||
version: 10.42.0
|
||||
'@types/react-color':
|
||||
specifier: ^3.0.10
|
||||
version: 3.0.10
|
||||
'@types/uuid':
|
||||
specifier: ^9.0.3
|
||||
version: 9.0.6
|
||||
@@ -137,6 +140,9 @@ dependencies:
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
react-color:
|
||||
specifier: ^2.19.3
|
||||
version: 2.19.3(react@18.2.0)
|
||||
react-dom:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
@@ -386,6 +392,14 @@ packages:
|
||||
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -2083,6 +2097,13 @@ packages:
|
||||
/@types/prop-types@15.7.9:
|
||||
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:
|
||||
resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==}
|
||||
dependencies:
|
||||
@@ -2101,6 +2122,12 @@ packages:
|
||||
'@types/scheduler': 0.16.5
|
||||
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:
|
||||
resolution: {integrity: sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==}
|
||||
|
||||
@@ -4034,10 +4061,18 @@ packages:
|
||||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/lodash-es@4.17.21:
|
||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: false
|
||||
|
||||
/loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
@@ -4058,6 +4093,10 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/material-colors@1.2.6:
|
||||
resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==}
|
||||
dev: false
|
||||
|
||||
/merge2@1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4519,7 +4558,6 @@ packages:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
dev: true
|
||||
|
||||
/proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
@@ -4533,6 +4571,21 @@ packages:
|
||||
/queue-microtask@1.2.3:
|
||||
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):
|
||||
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
|
||||
peerDependencies:
|
||||
@@ -4728,6 +4781,15 @@ packages:
|
||||
loose-envify: 1.4.0
|
||||
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:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
dependencies:
|
||||
@@ -5089,6 +5151,10 @@ packages:
|
||||
dependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
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}
|
||||
ref={setNodeRef}
|
||||
{...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}>
|
||||
<GripVertical size={18} />
|
||||
|
||||
@@ -5,12 +5,9 @@ import {
|
||||
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<boolean>(false);
|
||||
|
||||
@@ -11,12 +11,15 @@ import * as process from "process";
|
||||
import Image from "next/image";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { addImage } from "@/store/app.slice";
|
||||
import { models } from "@/components/layout/sidebar/stage-settings/stage-size-selector/models";
|
||||
import { useAppDispatch } from "@/hooks";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
export const ImageGallery = ({ open }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
@@ -41,7 +44,6 @@ export const ImageGallery = ({ open }: Props) => {
|
||||
|
||||
if (query) {
|
||||
url = `${searchUrl}${clientID}${urlPage}${urlQuery}`;
|
||||
console.log(url);
|
||||
} else {
|
||||
url = `${mainUrl}${clientID}${urlPage}`;
|
||||
}
|
||||
@@ -55,7 +57,6 @@ export const ImageGallery = ({ open }: Props) => {
|
||||
} else if (query) {
|
||||
return [...oldPhotos, ...data.results];
|
||||
} else {
|
||||
console.log(data);
|
||||
return [...oldPhotos, ...data];
|
||||
}
|
||||
});
|
||||
@@ -70,10 +71,10 @@ export const ImageGallery = ({ open }: Props) => {
|
||||
const ref = scrollAreaRef.current;
|
||||
const handler = () => {
|
||||
if (scrollAreaRef.current) {
|
||||
console.log(
|
||||
/* console.log(
|
||||
scrollAreaRef.current.clientHeight + scrollAreaRef.current.scrollTop,
|
||||
scrollAreaRef.current.scrollHeight - 2,
|
||||
);
|
||||
);*/
|
||||
}
|
||||
if (
|
||||
isLoading ||
|
||||
@@ -104,16 +105,17 @@ export const ImageGallery = ({ open }: Props) => {
|
||||
return () => ref?.removeEventListener("scroll", handler);
|
||||
}, [isLoading, open]);
|
||||
|
||||
const handleImageAdd = (e) => {
|
||||
console.log(e.target);
|
||||
const handleImageAdd = (url: string, width: number, height: number) => {
|
||||
const aspectRatio = height / width;
|
||||
const finalWidth = 200;
|
||||
const finalHeight = finalWidth * aspectRatio;
|
||||
dispatch(
|
||||
addImage({ imageUrl: url, width: finalWidth, height: finalHeight }),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="min-h-full p-3">
|
||||
<CardHeader className="mb-2 p-2 text-center">
|
||||
<CardTitle>Template</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<Card className="h-full min-h-[600px] p-3">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Images"
|
||||
@@ -122,24 +124,27 @@ export const ImageGallery = ({ open }: Props) => {
|
||||
/>
|
||||
|
||||
<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}
|
||||
>
|
||||
{photos?.map((p, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick={(e) => handleImageAdd(e)}
|
||||
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"
|
||||
>
|
||||
<Image
|
||||
key={p.i}
|
||||
src={p.urls.small}
|
||||
alt={"image"}
|
||||
height={100}
|
||||
width={100}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{photos?.map((p, i) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
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}
|
||||
src={p.urls.small}
|
||||
alt={"image"}
|
||||
height={100}
|
||||
width={100}
|
||||
className="h-32 w-full"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -28,9 +28,6 @@ export const Layers = () => {
|
||||
|
||||
return (
|
||||
<Card className="p-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center">Layers</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid items-center gap-2 p-2 ">
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import React, { useState } from "react";
|
||||
import { LuLayoutTemplate } from "react-icons/lu";
|
||||
import {
|
||||
ColumnsIcon,
|
||||
GearIcon,
|
||||
ImageIcon,
|
||||
LayersIcon,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
} from "@radix-ui/react-icons";
|
||||
import { StageSize } from "@/components/layout/sidebar/stage-settings/stage-size";
|
||||
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() {
|
||||
const [openGallery, setOpenGallery] = useState<boolean>(false);
|
||||
@@ -20,7 +22,7 @@ export function Sidebar() {
|
||||
return (
|
||||
<div className="items-left flex w-full flex-col p-2 pt-4">
|
||||
<Tabs>
|
||||
<TabsList className="grid grid-cols-5">
|
||||
<TabsList className="grid grid-cols-6">
|
||||
<TabsTrigger value="stage">
|
||||
<GearIcon />
|
||||
</TabsTrigger>
|
||||
@@ -41,6 +43,9 @@ export function Sidebar() {
|
||||
<TabsTrigger value="layers">
|
||||
<LayersIcon />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="background">
|
||||
<ColumnsIcon />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="stage">
|
||||
<StageSize />
|
||||
@@ -58,6 +63,9 @@ export function Sidebar() {
|
||||
<TabsContent value="layers">
|
||||
<Layers />
|
||||
</TabsContent>
|
||||
<TabsContent value="background">
|
||||
<StageBackgroundChange />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</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 { useAppDispatch } from "@/hooks";
|
||||
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 = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -15,7 +17,7 @@ export const StageSize = () => {
|
||||
};
|
||||
return (
|
||||
<Card className="p-3">
|
||||
<CardHeader>
|
||||
{/*<CardHeader>
|
||||
<CardTitle>{`Sélectionnez la taille d'étiquette`}</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@@ -29,14 +31,9 @@ export const StageSize = () => {
|
||||
<Button onClick={() => handleStageSizeSelect(410, 289)}>
|
||||
Grand Format
|
||||
</Button>
|
||||
</div>
|
||||
</div>*/}
|
||||
|
||||
{/*<CardFooter className="mt-8 justify-between">*/}
|
||||
{/* <Button variant="ghost" onClick={() => setOpen(false)}>*/}
|
||||
{/* Cancel*/}
|
||||
{/* </Button>*/}
|
||||
{/* <Button onClick={handleSizeSelect}>Submit</Button>*/}
|
||||
{/*</CardFooter>*/}
|
||||
<ModelSelector types={types} models={models} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,9 +22,6 @@ export default function TextInput() {
|
||||
};
|
||||
return (
|
||||
<Card className="p-3">
|
||||
<CardHeader className="pt-2">
|
||||
<CardTitle className="text-center">Text</CardTitle>
|
||||
</CardHeader>
|
||||
<Textarea
|
||||
id="description"
|
||||
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 { v1 } from "uuid";
|
||||
import { type WritableDraft } from "immer/src/types/types-external";
|
||||
import { CANVAS_PADDING_X, CANVAS_PADDING_Y } from "@/consts/canvas-params";
|
||||
|
||||
export enum StageItemType {
|
||||
Text = "text",
|
||||
@@ -61,6 +62,16 @@ const defaultImageConfig = {
|
||||
scaleY: 1,
|
||||
};
|
||||
|
||||
const defaultBackgroundImageConfig = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
};
|
||||
|
||||
const addNewItemToHistory =
|
||||
(
|
||||
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(
|
||||
(state, action: PayloadAction<string>, currentHistoryEntry) => {
|
||||
@@ -332,4 +373,5 @@ export const {
|
||||
setBlockedItem,
|
||||
goBack,
|
||||
goForward,
|
||||
setBackgroundImage,
|
||||
} = 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