diff --git a/package.json b/package.json index 3e252dd..bf41836 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7459649..353e694 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/public/bottle-images/bottle-schema.png b/public/bottle-images/bottle-schema.png new file mode 100644 index 0000000..97423e2 Binary files /dev/null and b/public/bottle-images/bottle-schema.png differ diff --git a/public/bottle-images/bouteille-demie.png b/public/bottle-images/bouteille-demie.png new file mode 100644 index 0000000..f39f4c8 Binary files /dev/null and b/public/bottle-images/bouteille-demie.png differ diff --git a/public/bottle-images/bouteille-magnum.png b/public/bottle-images/bouteille-magnum.png new file mode 100644 index 0000000..c22b1c8 Binary files /dev/null and b/public/bottle-images/bouteille-magnum.png differ diff --git a/public/bottle-images/bouteille-standard.png b/public/bottle-images/bouteille-standard.png new file mode 100644 index 0000000..ae00a97 Binary files /dev/null and b/public/bottle-images/bouteille-standard.png differ diff --git a/src/components/layer-item.tsx b/src/components/layer-item.tsx index d40510e..4180efa 100644 --- a/src/components/layer-item.tsx +++ b/src/components/layer-item.tsx @@ -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" > diff --git a/src/components/layout/sidebar/image-gallery.tsx b/src/components/layout/sidebar/image-gallery.tsx index 5cbcbfa..2a16aa4 100644 --- a/src/components/layout/sidebar/image-gallery.tsx +++ b/src/components/layout/sidebar/image-gallery.tsx @@ -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(false); diff --git a/src/components/layout/sidebar/image-gallery/image-gallery.tsx b/src/components/layout/sidebar/image-gallery/image-gallery.tsx index 0ca4a32..61ec4a8 100644 --- a/src/components/layout/sidebar/image-gallery/image-gallery.tsx +++ b/src/components/layout/sidebar/image-gallery/image-gallery.tsx @@ -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(""); const [page, setPage] = useState(1); const [isLoading, setIsLoading] = useState(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 ( - - - Template - - + { />
- {photos?.map((p, i) => ( -
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"} -
- ))} + {photos?.map((p, i) => { + return ( +
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"} +
+ ); + })}
); diff --git a/src/components/layout/sidebar/layers/layers.tsx b/src/components/layout/sidebar/layers/layers.tsx index 40177e2..3b83225 100644 --- a/src/components/layout/sidebar/layers/layers.tsx +++ b/src/components/layout/sidebar/layers/layers.tsx @@ -28,9 +28,6 @@ export const Layers = () => { return ( - - Layers - (false); @@ -20,7 +22,7 @@ export function Sidebar() { return (
- + @@ -41,6 +43,9 @@ export function Sidebar() { + + + @@ -58,6 +63,9 @@ export function Sidebar() { + + +
); diff --git a/src/components/layout/sidebar/stage-settings/stage-background-change/stage-background-change.tsx b/src/components/layout/sidebar/stage-settings/stage-background-change/stage-background-change.tsx new file mode 100644 index 0000000..5bdaa9c --- /dev/null +++ b/src/components/layout/sidebar/stage-settings/stage-background-change/stage-background-change.tsx @@ -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("gradient"); + 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}`; + 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 ( + + {/* setQuery(e.target.value)} + /> + + +*/} + +
+ {/* handleBackgroundSelect(e.target.value)} + /> + handleBackgroundSelect(e.target.value)} + />*/} + handleBackgroundSelect(e)} + /> +
+ +
+ {photos?.map((p, i) => { + return ( +
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"} +
+ ); + })} +
+
+ ); +}; diff --git a/src/components/layout/sidebar/stage-settings/stage-size-selector/model-selector.tsx b/src/components/layout/sidebar/stage-settings/stage-size-selector/model-selector.tsx new file mode 100644 index 0000000..3a20ded --- /dev/null +++ b/src/components/layout/sidebar/stage-settings/stage-size-selector/model-selector.tsx @@ -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(models[0]); + const [peekedModel, setPeekedModel] = React.useState(models[0]); + + return ( +
+ + + + + + 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. + + + + + + + + + +
+

{peekedModel.name}

+
+ {peekedModel.description} +
+ {peekedModel.image ? ( +
+ {peekedModel.name} +
+ ) : null} +
+
+ + + + {types.map((type) => ( + + {models + .filter((model) => model.type === type) + .map((model) => ( + setPeekedModel(model)} + onSelect={() => { + setSelectedModel(model); + setOpen(false); + }} + /> + ))} + + ))} + + +
+
+
+
+ ); +} + +interface ModelItemProps { + model: Model; + isSelected: boolean; + onSelect: () => void; + onPeek: (model: Model) => void; +} + +function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) { + const ref = React.useRef(null); + + useMutationObserver(ref, (mutations) => { + for (const mutation of mutations) { + if (mutation.type === "attributes") { + if (mutation.attributeName === "aria-selected") { + onPeek(model); + } + } + } + }); + + return ( + + {model.name} + + + + ); +} diff --git a/src/components/layout/sidebar/stage-settings/stage-size-selector/models.ts b/src/components/layout/sidebar/stage-settings/stage-size-selector/models.ts new file mode 100644 index 0000000..c89e59c --- /dev/null +++ b/src/components/layout/sidebar/stage-settings/stage-size-selector/models.ts @@ -0,0 +1,36 @@ +export const types = ["GPT-3", "Codex"] as const; + +export type ModelType = (typeof types)[number]; + +export interface Model { + id: string; + name: string; + description: string; + image: string; + type: Type; +} + +export const models: Model[] = [ + { + 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", + }, +]; diff --git a/src/components/layout/sidebar/stage-settings/stage-size.tsx b/src/components/layout/sidebar/stage-settings/stage-size.tsx index 0b82d90..7508482 100644 --- a/src/components/layout/sidebar/stage-settings/stage-size.tsx +++ b/src/components/layout/sidebar/stage-settings/stage-size.tsx @@ -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 ( - + {/* {`Sélectionnez la taille d'étiquette`} @@ -29,14 +31,9 @@ export const StageSize = () => { - + */} - {/**/} - {/* */} - {/* */} - {/**/} + ); }; diff --git a/src/components/layout/sidebar/text-input/text-input.tsx b/src/components/layout/sidebar/text-input/text-input.tsx index 4d62c51..1bd03bd 100644 --- a/src/components/layout/sidebar/text-input/text-input.tsx +++ b/src/components/layout/sidebar/text-input/text-input.tsx @@ -22,9 +22,6 @@ export default function TextInput() { }; return ( - - Text -