mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 01:38:44 +00:00
api fixes and sort only user uploads on my uploads tab, shared gallery button for both 2d and 3d
This commit is contained in:
1 parent
e5627fd225
commit
1573c927bf
17 files changed
+356
-275
No files matched your search
@@ -22,7 +22,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devnomic/marquee": "^1.0.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.7.2",
|
||||
@@ -52,8 +51,8 @@
|
||||
"@preact/signals-react-transform": "^0.4.0",
|
||||
"@tauri-apps/cli": "^2.4.0",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Marquee } from "@devnomic/marquee";
|
||||
// import { Marquee } from "@devnomic/marquee";
|
||||
import "@devnomic/marquee/dist/index.css";
|
||||
|
||||
function getRandomImages(count: number) {
|
||||
@@ -21,16 +21,18 @@ interface ImageColumnProps {
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
function ImageColumn({
|
||||
images,
|
||||
delay,
|
||||
reverse = false,
|
||||
duration = 100,
|
||||
isLoaded,
|
||||
}: ImageColumnProps) {
|
||||
function ImageColumn(
|
||||
{
|
||||
// images,
|
||||
// delay,
|
||||
// reverse = false,
|
||||
// duration = 100,
|
||||
// isLoaded,
|
||||
}: ImageColumnProps,
|
||||
) {
|
||||
return (
|
||||
<div className="flex h-screen flex-col gap-6">
|
||||
<Marquee
|
||||
{/* <Marquee
|
||||
fade={false}
|
||||
direction="up"
|
||||
reverse={reverse}
|
||||
@@ -59,7 +61,7 @@ function ImageColumn({
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Marquee>
|
||||
</Marquee> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import { PopoverItem } from "~/components/reusable/Popover";
|
||||
import { Api, ApiManager } from "~/KonvaApp/Api";
|
||||
import { useJobContext } from "~/components/JobContext";
|
||||
import { IsDesktopApp } from "@storyteller/tauri-utils";
|
||||
import { GalleryItem, GalleryModal } from "@storyteller/ui-gallery-modal";
|
||||
|
||||
interface ReferenceImage {
|
||||
id: string;
|
||||
@@ -56,6 +57,11 @@ export const PromptBox = () => {
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [isEnqueueing, setisEnqueueing] = useState(false);
|
||||
const [useSystemPrompt, setUseSystemPrompt] = useState(true);
|
||||
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
||||
const [selectedGalleryImages, setSelectedGalleryImages] = useState<string[]>(
|
||||
[],
|
||||
);
|
||||
const [activeGalleryTab, setActiveGalleryTab] = useState("my-media");
|
||||
const [referenceImages, setReferenceImages] = useState<ReferenceImage[]>([]);
|
||||
const [uploadingImages, setUploadingImages] = useState<
|
||||
{ id: string; file: File }[]
|
||||
@@ -153,8 +159,40 @@ export const PromptBox = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleLibrarySelect = () => {
|
||||
console.log("Opening library selector");
|
||||
const handleGallerySelect = () => {
|
||||
setIsGalleryModalOpen(true);
|
||||
};
|
||||
|
||||
const handleGalleryClose = () => {
|
||||
setIsGalleryModalOpen(false);
|
||||
setSelectedGalleryImages([]);
|
||||
};
|
||||
|
||||
const handleImageSelect = (id: string) => {
|
||||
setSelectedGalleryImages((prev) => {
|
||||
if (prev.includes(id)) {
|
||||
return prev.filter((imageId) => imageId !== id);
|
||||
}
|
||||
if (prev.length >= 4) {
|
||||
return prev;
|
||||
}
|
||||
return [...prev, id];
|
||||
});
|
||||
};
|
||||
|
||||
const handleGalleryImages = (selectedItems: GalleryItem[]) => {
|
||||
selectedItems.forEach((item) => {
|
||||
if (!item.fullImage) return;
|
||||
const referenceImage: ReferenceImage = {
|
||||
id: Math.random().toString(36).substring(7),
|
||||
url: item.fullImage,
|
||||
file: new File([], "library-image"),
|
||||
mediaToken: item.id,
|
||||
};
|
||||
setReferenceImages((prev) => [...prev, referenceImage]);
|
||||
});
|
||||
setIsGalleryModalOpen(false);
|
||||
setSelectedGalleryImages([]);
|
||||
};
|
||||
|
||||
const handleAction = (action: string) => {
|
||||
@@ -162,8 +200,8 @@ export const PromptBox = () => {
|
||||
case "upload":
|
||||
handleUploadClick();
|
||||
break;
|
||||
case "library":
|
||||
handleLibrarySelect();
|
||||
case "gallery":
|
||||
handleGallerySelect();
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown action:", action);
|
||||
@@ -292,7 +330,7 @@ export const PromptBox = () => {
|
||||
);
|
||||
|
||||
const isDesktop = IsDesktopApp();
|
||||
console.log('Is this a desktop app?', isDesktop);
|
||||
console.log("Is this a desktop app?", isDesktop);
|
||||
|
||||
if (isDesktop) {
|
||||
handleTauriEnqueue();
|
||||
@@ -324,158 +362,176 @@ export const PromptBox = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 flex-col gap-3">
|
||||
{(referenceImages.length > 0 || uploadingImages.length > 0) && (
|
||||
<div className="flex w-full gap-2">
|
||||
{referenceImages.map((image) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className="glass relative aspect-square w-20 rounded-lg"
|
||||
>
|
||||
<img
|
||||
src={image.url}
|
||||
alt="Reference"
|
||||
className="h-full w-full rounded-lg object-cover"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemoveReference(image.id)}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/40 text-white backdrop-blur-md transition-colors hover:bg-black"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTimes} className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{uploadingImages.map(({ id, file }) => {
|
||||
const previewUrl = URL.createObjectURL(file);
|
||||
return (
|
||||
<>
|
||||
<div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 flex-col gap-3">
|
||||
{(referenceImages.length > 0 || uploadingImages.length > 0) && (
|
||||
<div className="flex w-full gap-2">
|
||||
{referenceImages.map((image) => (
|
||||
<div
|
||||
key={id}
|
||||
className="glass relative aspect-square w-20 overflow-hidden rounded-lg"
|
||||
key={image.id}
|
||||
className="glass relative aspect-square w-20 rounded-lg"
|
||||
>
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt="Uploading preview"
|
||||
className="h-full w-full object-cover blur-sm"
|
||||
/>
|
||||
<img
|
||||
src={image.url}
|
||||
alt="Reference"
|
||||
className="h-full w-full rounded-lg object-cover"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemoveReference(image.id)}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/40 text-white backdrop-blur-md transition-colors hover:bg-black"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTimes} className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{uploadingImages.map(({ id, file }) => {
|
||||
const previewUrl = URL.createObjectURL(file);
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className="glass relative aspect-square w-20 overflow-hidden rounded-lg"
|
||||
>
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src={previewUrl}
|
||||
alt="Uploading preview"
|
||||
className="h-full w-full object-cover blur-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="h-6 w-6 animate-spin text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="glass w-[730px] rounded-xl p-4">
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
accept="image/*"
|
||||
onChange={handleFileUpload}
|
||||
multiple
|
||||
/>
|
||||
<div className="flex justify-center gap-2">
|
||||
<PopoverMenu
|
||||
mode="button"
|
||||
panelTitle="Add Image"
|
||||
items={[
|
||||
{
|
||||
label: "Upload from device",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faUpload} className="h-4 w-4" />,
|
||||
action: "upload",
|
||||
},
|
||||
{
|
||||
label: "Choose from library",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faImages} className="h-4 w-4" />,
|
||||
action: "gallery",
|
||||
},
|
||||
]}
|
||||
onPanelAction={handleAction}
|
||||
showIconsInList
|
||||
buttonClassName="backdrop-blur-none bg-transparent hover:bg-transparent py-1.5 px-0 pr-1 m-0 hover:opacity-50 transition-opacity duration-100 ring-0 border-none focus:ring-0 outline-none"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faPlus} className="text-xl" />
|
||||
}
|
||||
/>
|
||||
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe your image..."
|
||||
className="text-md mb-2 max-h-[5.5em] flex-1 resize-none overflow-y-auto rounded bg-transparent pb-2 pr-2 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => {}}
|
||||
onBlur={() => {}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip
|
||||
content="Aspect ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
useSystemPrompt
|
||||
? "Use system prompt: ON"
|
||||
: "Use system prompt: OFF"
|
||||
}
|
||||
position="top"
|
||||
className="z-50"
|
||||
delay={200}
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={useSystemPrompt}
|
||||
icon={faMessageXmark}
|
||||
activeIcon={faMessageCheck}
|
||||
onClick={() => setUseSystemPrompt(!useSystemPrompt)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
className="flex items-center border-none bg-primary px-3 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
icon={!isEnqueueing ? faSparkles : undefined}
|
||||
onClick={handleEnqueue}
|
||||
disabled={isEnqueueing || !prompt.trim()}
|
||||
>
|
||||
{isEnqueueing ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="h-6 w-6 animate-spin text-white"
|
||||
className="animate-spin text-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="glass w-[730px] rounded-xl p-4">
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
accept="image/*"
|
||||
onChange={handleFileUpload}
|
||||
multiple
|
||||
/>
|
||||
<div className="flex justify-center gap-2">
|
||||
<PopoverMenu
|
||||
mode="button"
|
||||
panelTitle="Add Image"
|
||||
items={[
|
||||
{
|
||||
label: "Upload from device",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faUpload} className="h-4 w-4" />,
|
||||
action: "upload",
|
||||
},
|
||||
{
|
||||
label: "Choose from library",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faImages} className="h-4 w-4" />,
|
||||
action: "library",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
onPanelAction={handleAction}
|
||||
showIconsInList
|
||||
buttonClassName="backdrop-blur-none bg-transparent hover:bg-transparent py-1.5 px-0 pr-1 m-0 hover:opacity-50 transition-opacity duration-100 ring-0 border-none focus:ring-0 outline-none"
|
||||
triggerIcon={<FontAwesomeIcon icon={faPlus} className="text-xl" />}
|
||||
/>
|
||||
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe your image..."
|
||||
className="text-md mb-2 max-h-[5.5em] flex-1 resize-none overflow-y-auto rounded bg-transparent pb-2 pr-2 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => {}}
|
||||
onBlur={() => {}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip
|
||||
content="Aspect ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
useSystemPrompt
|
||||
? "Use system prompt: ON"
|
||||
: "Use system prompt: OFF"
|
||||
}
|
||||
position="top"
|
||||
className="z-50"
|
||||
delay={200}
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={useSystemPrompt}
|
||||
icon={faMessageXmark}
|
||||
activeIcon={faMessageCheck}
|
||||
onClick={() => setUseSystemPrompt(!useSystemPrompt)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
className="flex items-center border-none bg-primary px-3 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
icon={!isEnqueueing ? faSparkles : undefined}
|
||||
onClick={handleEnqueue}
|
||||
disabled={isEnqueueing || !prompt.trim()}
|
||||
>
|
||||
{isEnqueueing ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-lg"
|
||||
/>
|
||||
) : (
|
||||
"Generate"
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
"Generate"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<GalleryModal
|
||||
isOpen={isGalleryModalOpen}
|
||||
onClose={handleGalleryClose}
|
||||
mode="select"
|
||||
selectedItemIds={selectedGalleryImages}
|
||||
onSelectItem={handleImageSelect}
|
||||
maxSelections={4}
|
||||
onUseSelected={handleGalleryImages}
|
||||
tabs={[
|
||||
{ id: "my-media", label: "My generations" },
|
||||
{ id: "uploads", label: "My uploads" },
|
||||
]}
|
||||
activeTab={activeGalleryTab}
|
||||
onTabChange={setActiveGalleryTab}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +1,16 @@
|
||||
import { faGear } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faGear, faImages } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Activity } from "~/components/ui/Activity/Activity";
|
||||
// import { paperWrapperStyles } from "~/components/styles";
|
||||
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
|
||||
// import { ToolbarButton } from "~/components/features/ToolbarButton";
|
||||
import { GalleryModal } from "@storyteller/ui-gallery-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { SettingsModal } from "@storyteller/ui-settings-modal";
|
||||
import { useState } from "react";
|
||||
|
||||
export const ToolbarTopRight = () => {
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
||||
const [activeGalleryTab, setActiveGalleryTab] = useState("my-media");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -20,6 +21,13 @@ export const ToolbarTopRight = () => {
|
||||
className="h-[42px] w-[42px] bg-[#5F5F68]/60 backdrop-blur-lg hover:bg-[#5F5F68]/90"
|
||||
onClick={() => setIsSettingsModalOpen(true)}
|
||||
/>
|
||||
<Button
|
||||
icon={faImages}
|
||||
className="h-[42px] bg-[#5F5F68]/60 backdrop-blur-lg hover:bg-[#5F5F68]/90"
|
||||
onClick={() => setIsGalleryModalOpen(true)}
|
||||
>
|
||||
My Gallery
|
||||
</Button>
|
||||
<Activity />
|
||||
</div>
|
||||
|
||||
@@ -27,6 +35,18 @@ export const ToolbarTopRight = () => {
|
||||
isOpen={isSettingsModalOpen}
|
||||
onClose={() => setIsSettingsModalOpen(false)}
|
||||
/>
|
||||
|
||||
<GalleryModal
|
||||
isOpen={isGalleryModalOpen}
|
||||
onClose={() => setIsGalleryModalOpen(false)}
|
||||
mode="view"
|
||||
tabs={[
|
||||
{ id: "my-media", label: "My generations" },
|
||||
{ id: "uploads", label: "My uploads" },
|
||||
]}
|
||||
activeTab={activeGalleryTab}
|
||||
onTabChange={setActiveGalleryTab}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -34,6 +34,9 @@
|
||||
{
|
||||
"path": "../../libs/components/button/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/gallery-modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/tauri-utils/tsconfig.lib.json"
|
||||
},
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
{
|
||||
"path": "../../libs/components/button"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/gallery-modal"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/tauri-utils"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AuthButtons } from "./AuthButtons";
|
||||
import { SceneTitleInput } from "./SceneTitleInput";
|
||||
import { getCurrentLocationWithoutParams } from "~/utilities";
|
||||
import { Activity } from "~/pages/PageEnigma/comps/GenerateModals/Activity";
|
||||
import { LibraryModal } from "~/pages/PageEnigma/comps/LibraryModal/LibraryModal";
|
||||
import { GalleryModal } from "@storyteller/ui-gallery-modal";
|
||||
import { SettingsModal } from "@storyteller/ui-settings-modal";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
@@ -106,13 +106,13 @@ export const TopBar = ({ pageName }: Props) => {
|
||||
onClose={() => setIsSettingsModalOpen(false)}
|
||||
/>
|
||||
|
||||
<LibraryModal
|
||||
<GalleryModal
|
||||
isOpen={isLibraryModalOpen}
|
||||
onClose={() => setIsLibraryModalOpen(false)}
|
||||
mode="view"
|
||||
tabs={[
|
||||
{ id: "my-media", label: "My media" },
|
||||
{ id: "uploads", label: "Uploads" },
|
||||
{ id: "my-media", label: "My generations" },
|
||||
{ id: "uploads", label: "My uploads" },
|
||||
]}
|
||||
activeTab={activeLibraryTab}
|
||||
onTabChange={setActiveLibraryTab}
|
||||
|
||||
@@ -38,8 +38,6 @@ import {
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { LibraryModal } from "../LibraryModal/LibraryModal";
|
||||
import type { LibraryItem } from "../LibraryModal/LibraryModal";
|
||||
import {
|
||||
cameras,
|
||||
selectedCameraId,
|
||||
@@ -61,6 +59,7 @@ import { EngineApi } from "~/Classes/ApiManager/EngineApi";
|
||||
import { UploaderStates } from "~/enums/UploaderStates";
|
||||
import { CameraSettingsModal } from "../CameraSettingsModal";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { GalleryModal, GalleryItem } from "@storyteller/ui-gallery-modal";
|
||||
|
||||
export const PromptBox = () => {
|
||||
useSignals();
|
||||
@@ -92,11 +91,11 @@ export const PromptBox = () => {
|
||||
},
|
||||
]);
|
||||
const [isCameraSettingsOpen, setIsCameraSettingsOpen] = useState(false);
|
||||
const [isLibraryModalOpen, setIsLibraryModalOpen] = useState(false);
|
||||
const [selectedLibraryImages, setSelectedLibraryImages] = useState<string[]>(
|
||||
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
||||
const [selectedGalleryImages, setSelectedGalleryImages] = useState<string[]>(
|
||||
[],
|
||||
);
|
||||
const [activeLibraryTab, setActiveLibraryTab] = useState("my-media");
|
||||
const [activeGalleryTab, setActiveGalleryTab] = useState("my-media");
|
||||
|
||||
useEffect(() => {
|
||||
if (textareaRef.current) {
|
||||
@@ -359,17 +358,17 @@ export const PromptBox = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleLibrarySelect = () => {
|
||||
setIsLibraryModalOpen(true);
|
||||
const handleGallerySelect = () => {
|
||||
setIsGalleryModalOpen(true);
|
||||
};
|
||||
|
||||
const handleLibraryClose = () => {
|
||||
setIsLibraryModalOpen(false);
|
||||
setSelectedLibraryImages([]);
|
||||
const handleGalleryClose = () => {
|
||||
setIsGalleryModalOpen(false);
|
||||
setSelectedGalleryImages([]);
|
||||
};
|
||||
|
||||
const handleImageSelect = (id: string) => {
|
||||
setSelectedLibraryImages((prev) => {
|
||||
setSelectedGalleryImages((prev) => {
|
||||
if (prev.includes(id)) {
|
||||
return prev.filter((imageId) => imageId !== id);
|
||||
}
|
||||
@@ -380,19 +379,19 @@ export const PromptBox = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleLibraryImages = (selectedItems: LibraryItem[]) => {
|
||||
const handleGalleryImages = (selectedItems: GalleryItem[]) => {
|
||||
selectedItems.forEach((item) => {
|
||||
if (!item.fullImage) return;
|
||||
const referenceImage: ReferenceImage = {
|
||||
id: Math.random().toString(36).substring(7),
|
||||
url: item.fullImage,
|
||||
file: new File([], "library-image"),
|
||||
file: new File([], "gallery-image"),
|
||||
mediaToken: item.id,
|
||||
};
|
||||
setReferenceImages((prev) => [...prev, referenceImage]);
|
||||
});
|
||||
setIsLibraryModalOpen(false);
|
||||
setSelectedLibraryImages([]);
|
||||
setIsGalleryModalOpen(false);
|
||||
setSelectedGalleryImages([]);
|
||||
};
|
||||
|
||||
const handleAction = (action: string) => {
|
||||
@@ -400,8 +399,8 @@ export const PromptBox = () => {
|
||||
case "upload":
|
||||
handleUploadClick();
|
||||
break;
|
||||
case "library":
|
||||
handleLibrarySelect();
|
||||
case "gallery":
|
||||
handleGallerySelect();
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown action:", action);
|
||||
@@ -581,7 +580,7 @@ export const PromptBox = () => {
|
||||
label: "Choose from library",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faImages} className="h-4 w-4" />,
|
||||
action: "library",
|
||||
action: "gallery",
|
||||
},
|
||||
]}
|
||||
onPanelAction={handleAction}
|
||||
@@ -762,20 +761,20 @@ export const PromptBox = () => {
|
||||
onDeleteCamera={deleteCamera}
|
||||
/>
|
||||
</div>
|
||||
<LibraryModal
|
||||
isOpen={isLibraryModalOpen}
|
||||
onClose={handleLibraryClose}
|
||||
<GalleryModal
|
||||
isOpen={isGalleryModalOpen}
|
||||
onClose={handleGalleryClose}
|
||||
mode="select"
|
||||
selectedItemIds={selectedLibraryImages}
|
||||
selectedItemIds={selectedGalleryImages}
|
||||
onSelectItem={handleImageSelect}
|
||||
maxSelections={4}
|
||||
onUseSelected={handleLibraryImages}
|
||||
onUseSelected={handleGalleryImages}
|
||||
tabs={[
|
||||
{ id: "my-media", label: "My media" },
|
||||
{ id: "uploads", label: "Uploads" },
|
||||
{ id: "my-media", label: "My generations" },
|
||||
{ id: "uploads", label: "My uploads" },
|
||||
]}
|
||||
activeTab={activeLibraryTab}
|
||||
onTabChange={setActiveLibraryTab}
|
||||
activeTab={activeGalleryTab}
|
||||
onTabChange={setActiveGalleryTab}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/settings-modal"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/gallery-modal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
} from "lib/enums/QueryFilters.js";
|
||||
|
||||
interface ListUserMediaQuery {
|
||||
include_user_uploads?: boolean;
|
||||
username: string;
|
||||
filter_media_classes?: FilterMediaClasses[];
|
||||
filter_media_type?: FilterMediaType[];
|
||||
filter_engine_categories?: FilterEngineCategories[];
|
||||
page?: number;
|
||||
limit?: number;
|
||||
user_uploads_only?: boolean;
|
||||
include_user_uploads?: boolean;
|
||||
}
|
||||
|
||||
export class LibraryModalApi extends MediaFilesApi {
|
||||
@@ -25,6 +25,9 @@ export class LibraryModalApi extends MediaFilesApi {
|
||||
filter_media_classes: query.filter_media_classes,
|
||||
filter_media_type: query.filter_media_type,
|
||||
filter_engine_categories: query.filter_engine_categories,
|
||||
username: query.username,
|
||||
user_uploads_only: query.user_uploads_only,
|
||||
include_user_uploads: query.include_user_uploads,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ interface ListUserMediaQuery {
|
||||
filter_media_classes?: FilterMediaClasses[];
|
||||
filter_media_type?: FilterMediaType[];
|
||||
filter_engine_categories?: FilterEngineCategories[];
|
||||
user_uploads_only?: boolean;
|
||||
include_user_uploads?: boolean;
|
||||
}
|
||||
|
||||
interface SearchFeaturedMediaQuery {
|
||||
@@ -115,7 +117,7 @@ export class MediaFilesApi extends ApiManager {
|
||||
}
|
||||
|
||||
public async ListMediaFiles(
|
||||
query: ListMediaQuery,
|
||||
query: ListMediaQuery
|
||||
): Promise<ApiResponse<MediaFile[], PaginationInfinite>> {
|
||||
const endpoint = `${this.ApiTargets.BaseApi}/v1/media_files/list`;
|
||||
const queryWithStrings = {
|
||||
@@ -149,7 +151,7 @@ export class MediaFilesApi extends ApiManager {
|
||||
}
|
||||
|
||||
public async ListFeaturedMediaFiles(
|
||||
query: ListMediaQuery,
|
||||
query: ListMediaQuery
|
||||
): Promise<ApiResponse<MediaFile[], PaginationInfinite>> {
|
||||
const endpoint = `${this.ApiTargets.BaseApi}/v1/media_files/list_featured`;
|
||||
const queryWithStrings = {
|
||||
@@ -183,13 +185,13 @@ export class MediaFilesApi extends ApiManager {
|
||||
}
|
||||
|
||||
public async ListUserMediaFiles(
|
||||
query: ListUserMediaQuery,
|
||||
query: ListUserMediaQuery
|
||||
): Promise<ApiResponse<MediaFile[], Pagination>> {
|
||||
const userName = query.username;
|
||||
const endpoint = `${this.ApiTargets.BaseApi}/v1/media_files/list/user/${userName}`;
|
||||
const queryWithStrings = {
|
||||
...query,
|
||||
include_user_uploads: true,
|
||||
include_user_uploads: query.include_user_uploads,
|
||||
filter_media_classes: query.filter_media_classes
|
||||
? query.filter_media_classes.join(",")
|
||||
: undefined,
|
||||
@@ -205,11 +207,17 @@ export class MediaFilesApi extends ApiManager {
|
||||
results: MediaFile[];
|
||||
pagination?: Pagination;
|
||||
}>({ endpoint, query: queryWithStrings })
|
||||
.then((response) => ({
|
||||
success: response.success,
|
||||
data: response.results ?? [],
|
||||
pagination: response.pagination,
|
||||
}))
|
||||
.then((response) => {
|
||||
let results = response.results ?? [];
|
||||
if (query.user_uploads_only) {
|
||||
results = results.filter((file) => file.origin_category === "upload");
|
||||
}
|
||||
return {
|
||||
success: response.success,
|
||||
data: results,
|
||||
pagination: response.pagination,
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
success: false,
|
||||
@@ -219,7 +227,7 @@ export class MediaFilesApi extends ApiManager {
|
||||
}
|
||||
|
||||
public async SearchFeaturedMediaFiles(
|
||||
query: SearchFeaturedMediaQuery,
|
||||
query: SearchFeaturedMediaQuery
|
||||
): Promise<ApiResponse<MediaFile[], Pagination>> {
|
||||
const endpoint = `${this.ApiTargets.BaseApi}/v1/media_files/search_featured`;
|
||||
const queryWithStrings = {
|
||||
@@ -251,7 +259,7 @@ export class MediaFilesApi extends ApiManager {
|
||||
}
|
||||
|
||||
public async SearchUserMediaFiles(
|
||||
query: SearchFeaturedMediaQuery,
|
||||
query: SearchFeaturedMediaQuery
|
||||
): Promise<ApiResponse<MediaFile[], Pagination>> {
|
||||
const endpoint = `${this.ApiTargets.BaseApi}/v1/media_files/search_session`;
|
||||
const queryWithStrings = {
|
||||
|
||||
@@ -11,10 +11,11 @@ import {
|
||||
GetCdnOrigin,
|
||||
FilterMediaClasses,
|
||||
LibraryModalApi,
|
||||
UsersApi,
|
||||
} from "@storyteller/api";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export interface LibraryItem {
|
||||
export interface GalleryItem {
|
||||
id: string;
|
||||
label: string;
|
||||
thumbnail: string | null;
|
||||
@@ -23,7 +24,7 @@ export interface LibraryItem {
|
||||
}
|
||||
|
||||
interface GroupedItems {
|
||||
[date: string]: LibraryItem[];
|
||||
[date: string]: GalleryItem[];
|
||||
}
|
||||
|
||||
type ModalMode = "select" | "view";
|
||||
@@ -35,7 +36,7 @@ interface LibraryModalProps {
|
||||
selectedItemIds?: string[];
|
||||
onSelectItem?: (id: string) => void;
|
||||
maxSelections?: number;
|
||||
onUseSelected?: (selectedItems: LibraryItem[]) => void;
|
||||
onUseSelected?: (selectedItems: GalleryItem[]) => void;
|
||||
tabs: { id: string; label: string }[];
|
||||
activeTab: string;
|
||||
onTabChange: (tabId: string) => void;
|
||||
@@ -56,14 +57,16 @@ export const GalleryModal = React.memo(
|
||||
}: LibraryModalProps) => {
|
||||
const [groupedItems, setGroupedItems] = useState<GroupedItems>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lightboxImage, setLightboxImage] = useState<LibraryItem | null>(
|
||||
const [lightboxImage, setLightboxImage] = useState<GalleryItem | null>(
|
||||
null
|
||||
);
|
||||
const [isLightboxVisible, setIsLightboxVisible] = useState(false);
|
||||
const [failedImageUrls] = useState<Set<string>>(new Set());
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const cdnOrigin = GetCdnOrigin();
|
||||
const imageUrl = lightboxImage?.fullImage || "";
|
||||
const api = useMemo(() => new LibraryModalApi(), []);
|
||||
const usersApi = useMemo(() => new UsersApi(), []);
|
||||
|
||||
const formatDate = useCallback((date: string) => {
|
||||
const d = new Date(date);
|
||||
@@ -75,7 +78,7 @@ export const GalleryModal = React.memo(
|
||||
}, []);
|
||||
|
||||
const groupItemsByDate = useCallback(
|
||||
(items: LibraryItem[]) => {
|
||||
(items: GalleryItem[]) => {
|
||||
const grouped = items.reduce((acc: GroupedItems, item) => {
|
||||
const dateKey = formatDate(item.createdAt);
|
||||
if (!acc[dateKey]) {
|
||||
@@ -114,15 +117,29 @@ export const GalleryModal = React.memo(
|
||||
[cdnOrigin, failedImageUrls]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const getUsername = async () => {
|
||||
const session = await usersApi.GetSession();
|
||||
if (session.success && session.data?.user) {
|
||||
setUsername(session.data.user.username);
|
||||
}
|
||||
};
|
||||
getUsername();
|
||||
}, [usersApi]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadItems = async () => {
|
||||
if (!username) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await api.listUserMediaFiles({
|
||||
filter_media_classes: [FilterMediaClasses.IMAGE],
|
||||
username: username,
|
||||
include_user_uploads: activeTab === "uploads",
|
||||
user_uploads_only: activeTab === "uploads",
|
||||
});
|
||||
if (response.success && response.data) {
|
||||
const newItems = response.data.map((item) => ({
|
||||
const newItems = response.data.map((item: any) => ({
|
||||
id: item.token,
|
||||
label: item.maybe_title || "Image Generation",
|
||||
thumbnail: getImageUrl(item.public_bucket_path),
|
||||
@@ -141,7 +158,7 @@ export const GalleryModal = React.memo(
|
||||
loadItems();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab, isOpen]);
|
||||
}, [activeTab, isOpen, username]);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("LibraryModal render caused by:", {
|
||||
@@ -152,7 +169,7 @@ export const GalleryModal = React.memo(
|
||||
// }, [isOpen, activeTab, selectedItemIds]);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(item: LibraryItem) => {
|
||||
(item: GalleryItem) => {
|
||||
if (mode === "select" && onSelectItem) {
|
||||
onSelectItem(item.id);
|
||||
} else {
|
||||
@@ -190,12 +207,13 @@ export const GalleryModal = React.memo(
|
||||
mode === "view" && "h-[80vh] max-h-[870px] max-w-7xl"
|
||||
)}
|
||||
childPadding={false}
|
||||
showClose={false}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b border-white/10 p-4 py-3">
|
||||
<div className="grid grid-cols-3 items-center">
|
||||
<h2 className="text-xl font-semibold">
|
||||
{mode === "select" ? "Select Images" : "My Gallery"}
|
||||
{mode === "select" ? "Select Images" : "Gallery"}
|
||||
</h2>
|
||||
<div className="flex items-center justify-center">
|
||||
<TabSelector
|
||||
@@ -211,7 +229,7 @@ export const GalleryModal = React.memo(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-white/60">
|
||||
@@ -219,7 +237,7 @@ export const GalleryModal = React.memo(
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6 p-4">
|
||||
{Object.entries(groupedItems).map(([date, dateItems]) => (
|
||||
<div key={date}>
|
||||
<h3 className="text-md mb-2 font-medium text-white/60">
|
||||
@@ -238,7 +256,7 @@ export const GalleryModal = React.memo(
|
||||
"group relative aspect-square overflow-hidden rounded-md border-[3px] transition-all",
|
||||
mode === "select" &&
|
||||
selectedItemIds.includes(item.id)
|
||||
? "border-brand-primary"
|
||||
? "border-primary"
|
||||
: "border-transparent hover:border-white"
|
||||
)}
|
||||
onClick={() => handleItemClick(item)}
|
||||
@@ -269,7 +287,7 @@ export const GalleryModal = React.memo(
|
||||
</div>
|
||||
{mode === "select" &&
|
||||
selectedItemIds.includes(item.id) && (
|
||||
<div className="absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-brand-primary">
|
||||
<div className="absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-primary">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-sm"
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../api"
|
||||
},
|
||||
{
|
||||
"path": "../loading-spinner"
|
||||
},
|
||||
{
|
||||
"path": "../close-button"
|
||||
},
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../loading-spinner/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../close-button/tsconfig.lib.json"
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@ export const Modal = ({
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-[70]" onClose={onClose}>
|
||||
<DialogBackdrop className={backdropClassName} />
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="fixed inset-0">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<TransitionChild
|
||||
as="div"
|
||||
@@ -83,7 +83,7 @@ export const Modal = ({
|
||||
)}
|
||||
>
|
||||
<DialogPanel
|
||||
className="w-full relative"
|
||||
className="w-full h-full"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -60,10 +60,10 @@ export const TabSelector: React.FC<TabSelectorProps> = ({
|
||||
)}
|
||||
>
|
||||
<TabGroup selectedIndex={selectedIndex} onChange={handleTabChange}>
|
||||
<TabList className="glass relative inline-flex min-w-fit overflow-x-auto rounded-lg p-0.5 py-1 !shadow-none">
|
||||
<TabList className="glass glass-no-hover relative inline-flex min-w-fit overflow-x-auto rounded-lg p-0.5 py-1 !shadow-none">
|
||||
{/* Animated indicator */}
|
||||
<div
|
||||
className="absolute top-1 z-10 h-[calc(100%-8px)] rounded-md bg-brand-primary/30 transition-all duration-200 ease-in-out"
|
||||
className="absolute top-1 z-10 h-[calc(100%-8px)] rounded-md bg-primary/30 transition-all duration-200 ease-in-out"
|
||||
style={{
|
||||
left: indicatorStyle.left,
|
||||
width: indicatorStyle.width,
|
||||
|
||||
Generated
+2
-47
@@ -82,7 +82,6 @@
|
||||
"apps/editor2d": {
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@devnomic/marquee": "^1.0.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.7.2",
|
||||
@@ -112,8 +111,8 @@
|
||||
"@preact/signals-react-transform": "^0.4.0",
|
||||
"@tauri-apps/cli": "^2.4.0",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
@@ -136,22 +135,6 @@
|
||||
"vite-tsconfig-paths": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"apps/editor2d/node_modules/@devnomic/marquee": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@devnomic/marquee/-/marquee-1.0.3.tgz",
|
||||
"integrity": "sha512-q8QUtNO8ff+UvfrKJsHBtxh4fbOfhGk6SSdK0Uu/XZFdEzrmiGJ/l0FuVHRYsPAmzgnhVVnz1dGNB5CWGLMVvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.0",
|
||||
"tailwind-merge": "^2.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"apps/editor2d/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
@@ -607,25 +590,6 @@
|
||||
"react": "^16.14.0 || 17.x || 18.x || 19.x"
|
||||
}
|
||||
},
|
||||
"apps/editor2d/node_modules/@types/react": {
|
||||
"version": "18.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz",
|
||||
"integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"apps/editor2d/node_modules/@types/react-dom": {
|
||||
"version": "18.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.6.tgz",
|
||||
"integrity": "sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"apps/editor2d/node_modules/@types/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
|
||||
@@ -699,15 +663,6 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"apps/editor2d/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"apps/editor2d/node_modules/deep-equal": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
|
||||
|
||||
Reference in new issue
Block a user