mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-30 15:25:57 -04:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b176d023e | ||
|
|
eef929c065 | ||
|
|
91ea263b0c | ||
|
|
196e1c4615 | ||
|
|
8f321f06d0 | ||
|
|
0b338f3886 | ||
|
|
de76fc6c36 | ||
|
|
9fb0ca2dab |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
Binary file not shown.
+64
-62
@@ -1,12 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
TransitionDialogue,
|
||||
LoadingDots,
|
||||
Label,
|
||||
CloseButton,
|
||||
} from "~/components";
|
||||
|
||||
import { LoadingDots } from "~/components";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import { UploadAssetError } from "../UploadModal/UploadAssetError";
|
||||
import { UploadSuccess } from "../UploadModal/UploadSuccess";
|
||||
import { UploadFiles3D } from "./UploadFiles3D";
|
||||
@@ -19,18 +14,21 @@ import {
|
||||
CHARACTER_MIXAMO_FILE_TYPE,
|
||||
IMAGEPLANE_FILE_TYPE,
|
||||
} from "~/enums";
|
||||
import { assetModalVisible } from "~/pages/PageEnigma/signals";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
onSuccess: (category: FilterEngineCategories) => void;
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
titleIcon: IconDefinition;
|
||||
options?: {
|
||||
fileSubtypes?: { [key: string]: string }[];
|
||||
hasLength?: boolean;
|
||||
hasThumbnailUpload?: boolean;
|
||||
};
|
||||
preselectedCategory?: FilterEngineCategories;
|
||||
isSelectVisible: boolean;
|
||||
}
|
||||
|
||||
// Map categories to their allowed file types
|
||||
@@ -43,16 +41,6 @@ const categoryFileTypes: Partial<Record<FilterEngineCategories, string[]>> = {
|
||||
[FilterEngineCategories.LOCATION]: Object.values(OBJECT_FILE_TYPE),
|
||||
[FilterEngineCategories.CREATURE]: Object.values(OBJECT_FILE_TYPE),
|
||||
};
|
||||
|
||||
// Map engine categories to asset modal tabs
|
||||
const categoryToAssetTab: Partial<Record<FilterEngineCategories, string>> = {
|
||||
[FilterEngineCategories.OBJECT]: "objects",
|
||||
[FilterEngineCategories.CHARACTER]: "character",
|
||||
[FilterEngineCategories.IMAGE_PLANE]: "image-planes",
|
||||
[FilterEngineCategories.LOCATION]: "sets",
|
||||
[FilterEngineCategories.CREATURE]: "creatures",
|
||||
};
|
||||
|
||||
// Excluded category options for the upload modal
|
||||
const categoryOptions = Object.entries(FilterEngineCategories)
|
||||
.filter(
|
||||
@@ -82,12 +70,19 @@ export function UploadModal3D({
|
||||
onClose,
|
||||
onSuccess,
|
||||
title,
|
||||
titleIcon,
|
||||
options,
|
||||
}: Props) {
|
||||
preselectedCategory,
|
||||
isSelectVisible,
|
||||
}: Props & { preselectedCategory?: FilterEngineCategories }) {
|
||||
const [uploaderState, setUploaderState] =
|
||||
useState<UploaderState>(initialUploaderState);
|
||||
const [selectedCategory, setSelectedCategory] =
|
||||
useState<FilterEngineCategories>(FilterEngineCategories.OBJECT);
|
||||
useState<FilterEngineCategories>(
|
||||
preselectedCategory !== undefined
|
||||
? preselectedCategory
|
||||
: FilterEngineCategories.OBJECT,
|
||||
);
|
||||
|
||||
const updateUploaderState = (newState: UploaderState) => {
|
||||
setUploaderState(newState);
|
||||
@@ -103,30 +98,51 @@ export function UploadModal3D({
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (preselectedCategory !== undefined) {
|
||||
setSelectedCategory(preselectedCategory);
|
||||
}
|
||||
}, [preselectedCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Preselected category:", preselectedCategory);
|
||||
console.log("Initial selected category:", selectedCategory);
|
||||
}, [preselectedCategory, selectedCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uploaderState.status === UploaderStates.success) {
|
||||
onSuccess(selectedCategory);
|
||||
onClose();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [uploaderState.status]);
|
||||
|
||||
const UploaderModalContent = () => {
|
||||
switch (uploaderState.status) {
|
||||
case UploaderStates.ready:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex w-full flex-col">
|
||||
<Label>Category</Label>
|
||||
<Select
|
||||
id="category-select"
|
||||
options={categoryOptions}
|
||||
value={selectedCategory}
|
||||
onChange={(value) => {
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
Object.values(FilterEngineCategories).includes(
|
||||
value as FilterEngineCategories,
|
||||
)
|
||||
) {
|
||||
setSelectedCategory(value as FilterEngineCategories);
|
||||
}
|
||||
}}
|
||||
placeholder="Select a category"
|
||||
/>
|
||||
</div>
|
||||
{isSelectVisible && (
|
||||
<div className="flex w-full flex-col">
|
||||
<Label className="mb-1">Category</Label>
|
||||
<Select
|
||||
id="category-select"
|
||||
options={categoryOptions}
|
||||
value={selectedCategory}
|
||||
onChange={(value) => {
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
Object.values(FilterEngineCategories).includes(
|
||||
value as FilterEngineCategories,
|
||||
)
|
||||
) {
|
||||
setSelectedCategory(value as FilterEngineCategories);
|
||||
}
|
||||
}}
|
||||
placeholder="Select a category"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<UploadFiles3D
|
||||
title={title}
|
||||
engineCategory={selectedCategory}
|
||||
@@ -146,31 +162,17 @@ export function UploadModal3D({
|
||||
<div className="w-100 text-center opacity-50">Uploading...</div>
|
||||
</>
|
||||
);
|
||||
case UploaderStates.success:
|
||||
case UploaderStates.success: {
|
||||
return (
|
||||
<UploadSuccess
|
||||
title="3D model"
|
||||
onOk={() => {
|
||||
onSuccess();
|
||||
onClose();
|
||||
// Store the selected category and tab in sessionStorage
|
||||
const assetTab = categoryToAssetTab[selectedCategory];
|
||||
if (assetTab) {
|
||||
sessionStorage.setItem(
|
||||
"lastUploadedCategory",
|
||||
selectedCategory,
|
||||
);
|
||||
sessionStorage.setItem("lastUploadedTab", assetTab);
|
||||
// Reopen the asset modal
|
||||
assetModalVisible.value = true;
|
||||
} else {
|
||||
// Clear any existing stored values if we don't have a valid tab
|
||||
sessionStorage.removeItem("lastUploadedCategory");
|
||||
sessionStorage.removeItem("lastUploadedTab");
|
||||
}
|
||||
onSuccess(selectedCategory);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case UploaderStates.assetError:
|
||||
return (
|
||||
<UploadAssetError
|
||||
@@ -199,15 +201,15 @@ export function UploadModal3D({
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionDialogue
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
titleIcon={titleIcon}
|
||||
title={title}
|
||||
className="max-w-xl"
|
||||
showClose={false}
|
||||
showClose={true}
|
||||
>
|
||||
<CloseButton className="absolute right-4 top-4" onClick={onClose} />
|
||||
<UploaderModalContent />
|
||||
</TransitionDialogue>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faGear,
|
||||
faImages,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
@@ -30,8 +29,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const appTabs: TabItem[] = [
|
||||
{ id: "2D", label: "2D" },
|
||||
{ id: "3D", label: "3D" },
|
||||
{ id: "2D", label: "2D" },
|
||||
];
|
||||
|
||||
export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => {
|
||||
@@ -45,10 +44,6 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => {
|
||||
const [activeLibraryTab, setActiveLibraryTab] = useState("my-media");
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
window.location.href = "https://storyteller-2d.netlify.app/";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="fixed left-0 top-0 z-[60] w-full border-b border-white/5 bg-ui-background">
|
||||
@@ -74,7 +69,8 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => {
|
||||
tabs={appTabs}
|
||||
activeTab={appTabIdSignal.value}
|
||||
disabled={false}
|
||||
onTabChange={(tabId) => setAppTabId(tabId)}
|
||||
onTabChange={(tabId: string) => setAppTabId(tabId)}
|
||||
className="w-fit"
|
||||
/>
|
||||
{/* <Button
|
||||
variant="secondary"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { LoadingDots, TopBar } from "~/components";
|
||||
import { Controls3D } from "./comps/Controls3D";
|
||||
@@ -23,10 +22,9 @@ import { Outliner } from "./comps/Outliner";
|
||||
import { CameraAspectRatio } from "./enums";
|
||||
import { PromptBox } from "./comps/PromptBox";
|
||||
import { OnboardingHelper } from "./comps/OnboardingHelper";
|
||||
import { IsDesktopApp } from "@storyteller/tauri-utils";
|
||||
import { FocalLengthDisplay } from "./comps/FocalLengthDisplay/FocalLengthDisplay";
|
||||
import { DemoModal } from "@storyteller/ui-demo-modal";
|
||||
import { appTabId, setAppTabId } from "~/signals/appTab";
|
||||
import { LoginModal } from "@storyteller/ui-login-modal";
|
||||
|
||||
export const PageEditor = () => {
|
||||
useSignals();
|
||||
@@ -158,7 +156,7 @@ export const PageEditor = () => {
|
||||
<Timeline />
|
||||
</div> */}
|
||||
|
||||
<DemoModal
|
||||
{/* <DemoModal
|
||||
title="Welcome to ArtCraft 3D"
|
||||
subTitle="Your 3D editor for digital art and design"
|
||||
description="Set up your scene by adding objects and start bringing your ideas to life!"
|
||||
@@ -171,7 +169,14 @@ export const PageEditor = () => {
|
||||
console.error("This is not the Desktop app.");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{/* <LoginModal
|
||||
onClose={() => {}}
|
||||
videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4"
|
||||
videoSrc3D="/resources/videos/artcraft-3d-demo.mp4"
|
||||
openAiLogo="/resources/images/openai-logo.png"
|
||||
/> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { UploadModal3D } from "~/components/reusable/UploadModal3D";
|
||||
import {
|
||||
faPlus,
|
||||
faSearch,
|
||||
faChevronLeft,
|
||||
faLayerGroup,
|
||||
@@ -12,13 +11,13 @@ import {
|
||||
faMountainCity,
|
||||
faDog,
|
||||
faImage,
|
||||
faUpFromLine,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { CloseButton } from "@storyteller/ui-close-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { TabSelector } from "~/components/reusable/TabSelector";
|
||||
import { TabSelector } from "@storyteller/ui-tab-selector";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
@@ -45,6 +44,7 @@ import { isAnyStatusFetching } from "../SidePanelTabs/utilities";
|
||||
type AssetTab = {
|
||||
id: string;
|
||||
label: string;
|
||||
labelSingle?: string;
|
||||
icon: typeof faLayerGroup;
|
||||
engineCategory?: FilterEngineCategories;
|
||||
items: MediaItem[];
|
||||
@@ -81,6 +81,21 @@ const AllTabSection = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const categoryToTabIdMap: Record<FilterEngineCategories, string> = {
|
||||
[FilterEngineCategories.CHARACTER]: "characters",
|
||||
[FilterEngineCategories.CREATURE]: "creatures",
|
||||
[FilterEngineCategories.IMAGE_PLANE]: "image-planes",
|
||||
[FilterEngineCategories.LOCATION]: "sets",
|
||||
[FilterEngineCategories.OBJECT]: "objects",
|
||||
[FilterEngineCategories.SKYBOX]: "skybox",
|
||||
[FilterEngineCategories.ANIMATION]: "all",
|
||||
[FilterEngineCategories.AUDIO]: "all",
|
||||
[FilterEngineCategories.EXPRESSION]: "all",
|
||||
[FilterEngineCategories.SCENE]: "all",
|
||||
[FilterEngineCategories.SET_DRESSING]: "all",
|
||||
[FilterEngineCategories.VIDEO_PLANE]: "all",
|
||||
};
|
||||
|
||||
export const AssetModal = () => {
|
||||
useSignals();
|
||||
const [activeLibraryTab, setActiveLibraryTab] = useState("library");
|
||||
@@ -88,6 +103,9 @@ export const AssetModal = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedCategory, setSelectedCategory] =
|
||||
useState<FilterEngineCategories | null>(null);
|
||||
const [isSelectVisible, setIsSelectVisible] = useState(true);
|
||||
|
||||
const handleReopenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.checked;
|
||||
@@ -101,6 +119,10 @@ export const AssetModal = () => {
|
||||
assetModalVisible.value = false;
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
assetModalVisible.value = true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (assetModalVisible.value) {
|
||||
// Check for stored category and tab from upload
|
||||
@@ -116,7 +138,10 @@ export const AssetModal = () => {
|
||||
const timer = setTimeout(() => {
|
||||
searchInputRef.current?.focus();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
// Ensure no side effects remain
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -232,7 +257,8 @@ export const AssetModal = () => {
|
||||
{ id: "all", label: "All", icon: faLayerGroup, items: [] },
|
||||
{
|
||||
id: "character",
|
||||
label: "Character",
|
||||
label: "Characters",
|
||||
labelSingle: "Character",
|
||||
icon: faUser,
|
||||
engineCategory: FilterEngineCategories.CHARACTER,
|
||||
items:
|
||||
@@ -243,6 +269,7 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "objects",
|
||||
label: "Objects",
|
||||
labelSingle: "Object",
|
||||
icon: faCube,
|
||||
engineCategory: FilterEngineCategories.OBJECT,
|
||||
items:
|
||||
@@ -253,6 +280,7 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "sets",
|
||||
label: "Sets",
|
||||
labelSingle: "Set",
|
||||
icon: faMountainCity,
|
||||
engineCategory: FilterEngineCategories.LOCATION,
|
||||
items:
|
||||
@@ -263,6 +291,7 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "creatures",
|
||||
label: "Creatures",
|
||||
labelSingle: "Creature",
|
||||
icon: faDog,
|
||||
engineCategory: FilterEngineCategories.CREATURE,
|
||||
items:
|
||||
@@ -273,12 +302,14 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "skybox",
|
||||
label: "Skybox",
|
||||
labelSingle: "Skybox",
|
||||
icon: faSun,
|
||||
items: activeLibraryTab === "library" ? demoSkyboxItems.value : [],
|
||||
},
|
||||
{
|
||||
id: "image-planes",
|
||||
label: "Image Planes",
|
||||
labelSingle: "Image Plane",
|
||||
icon: faImage,
|
||||
engineCategory: FilterEngineCategories.IMAGE_PLANE,
|
||||
items:
|
||||
@@ -397,11 +428,20 @@ export const AssetModal = () => {
|
||||
};
|
||||
|
||||
const handleAddAsset = () => {
|
||||
assetModalVisible.value = false;
|
||||
handleClose();
|
||||
setIsUploadModalOpen(true);
|
||||
setSelectedCategory(null);
|
||||
setIsSelectVisible(true);
|
||||
};
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
const handleAddSpecificAsset = (category: FilterEngineCategories) => {
|
||||
handleClose();
|
||||
setIsUploadModalOpen(true);
|
||||
setSelectedCategory(category);
|
||||
setIsSelectVisible(false);
|
||||
};
|
||||
|
||||
const handleUploadSuccess = (category: FilterEngineCategories) => {
|
||||
if (reopenAfterDragSignal.value) {
|
||||
// Small delay to allow the modal to close and reopen
|
||||
setTimeout(() => {
|
||||
@@ -410,10 +450,32 @@ export const AssetModal = () => {
|
||||
} else {
|
||||
handleClose();
|
||||
}
|
||||
|
||||
// Use the provided category to find the AssetTab ID
|
||||
const lastUploadedTabId = categoryToTabIdMap[category] || "all";
|
||||
|
||||
// Close the upload modal
|
||||
setTimeout(() => {
|
||||
sessionStorage.setItem("lastUploadedTab", lastUploadedTabId);
|
||||
console.log("AssetModal: lastUploadedTabId set to:", lastUploadedTabId);
|
||||
setIsUploadModalOpen(false);
|
||||
handleOpen();
|
||||
}, 300);
|
||||
|
||||
fetchUserCharacters();
|
||||
fetchUserObjects();
|
||||
fetchUserSets();
|
||||
fetchUserCreatures();
|
||||
fetchUserImagePlanes();
|
||||
};
|
||||
|
||||
const clearSearch = () => setSearchTerm("");
|
||||
|
||||
const getArticle = (word: string | undefined) => {
|
||||
if (!word) return "a";
|
||||
return /^[aeiou]/i.test(word) ? "an" : "a";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@@ -429,17 +491,31 @@ export const AssetModal = () => {
|
||||
<h2 className="text-[18px] font-semibold opacity-80">
|
||||
3D Assets
|
||||
</h2>
|
||||
<Tooltip content="Upload model" position="top" delay={200}>
|
||||
{/* <Tooltip content="Upload model" position="top" delay={200}>
|
||||
<Button
|
||||
className="h-6 w-6 rounded-full border-none bg-transparent text-white/70 transition-colors hover:bg-transparent hover:text-white/100"
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md border-none bg-primary/50 text-white/70 transition-colors hover:bg-primary/70 hover:text-white/100"
|
||||
onClick={handleAddAsset}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="text-xl" />
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Tooltip> */}
|
||||
</div>
|
||||
<hr className="my-2 w-full border-white/10" />
|
||||
<div className="space-y-2">
|
||||
<div className="flex h-full flex-col space-y-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
className={twMerge(
|
||||
"w-full justify-start rounded-xl border border-white/[2%] bg-white/[4%] px-3.5 py-2.5 text-left hover:bg-white/15",
|
||||
"border-primary bg-primary/70 hover:bg-primary",
|
||||
)}
|
||||
onClick={handleAddAsset}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faUpFromLine}
|
||||
className="mr-2 text-lg opacity-70"
|
||||
/>
|
||||
Upload an asset
|
||||
</Button>
|
||||
{assetTabs.map((tab) => (
|
||||
<Button
|
||||
key={tab.id}
|
||||
@@ -447,7 +523,7 @@ export const AssetModal = () => {
|
||||
className={twMerge(
|
||||
"w-full justify-start rounded-xl border border-white/[2%] bg-white/[4%] px-3.5 py-2.5 text-left hover:bg-white/15",
|
||||
activeAssetTab === tab.id &&
|
||||
"border-brand-primary bg-brand-primary/10 hover:bg-brand-primary/10",
|
||||
"border-primary bg-primary/10 hover:bg-primary/10",
|
||||
)}
|
||||
onClick={() => setActiveAssetTab(tab.id)}
|
||||
>
|
||||
@@ -479,7 +555,7 @@ export const AssetModal = () => {
|
||||
id="reopen-after-add"
|
||||
checked={reopenAfterDragSignal.value}
|
||||
onChange={handleReopenChange}
|
||||
className="h-4 w-4 cursor-pointer rounded-lg border-gray-300 bg-gray-700 text-brand-primary focus:ring-brand-primary"
|
||||
className="h-4 w-4 cursor-pointer rounded-lg border-gray-300 bg-gray-700 text-primary focus:ring-primary"
|
||||
/>
|
||||
<label
|
||||
htmlFor="reopen-after-add"
|
||||
@@ -529,18 +605,36 @@ export const AssetModal = () => {
|
||||
)}
|
||||
>
|
||||
{activeAssetTab !== "all" && !searchTerm && (
|
||||
<div className="mb-2 flex items-center font-semibold">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center gap-2 border-none bg-transparent px-3 py-1.5 text-sm text-white/70 hover:bg-transparent hover:text-white/100"
|
||||
onClick={() => setActiveAssetTab("all")}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronLeft}
|
||||
className="text-sm font-semibold opacity-70"
|
||||
/>
|
||||
</Button>
|
||||
{currentTab.label}
|
||||
<div className="mb-2 flex items-center justify-between font-semibold">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center gap-2 border-none bg-transparent px-3 py-1.5 text-sm text-white/70 hover:bg-transparent hover:text-white/100"
|
||||
onClick={() => setActiveAssetTab("all")}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronLeft}
|
||||
className="text-sm font-semibold opacity-70"
|
||||
/>
|
||||
</Button>
|
||||
{currentTab.label}
|
||||
</div>
|
||||
{activeAssetTab !== "skybox" &&
|
||||
activeAssetTab !== "all" && (
|
||||
<Button
|
||||
icon={faUpFromLine}
|
||||
onClick={() =>
|
||||
handleAddSpecificAsset(
|
||||
currentTab.engineCategory ||
|
||||
FilterEngineCategories.OBJECT,
|
||||
)
|
||||
}
|
||||
className="border-primary bg-primary/70 px-2 py-1 text-xs transition-colors hover:bg-primary"
|
||||
>
|
||||
Upload {getArticle(currentTab.labelSingle)}{" "}
|
||||
{currentTab.labelSingle}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{renderContent()}
|
||||
@@ -551,10 +645,15 @@ export const AssetModal = () => {
|
||||
</div>
|
||||
</Modal>
|
||||
<UploadModal3D
|
||||
onClose={() => setIsUploadModalOpen(false)}
|
||||
onSuccess={handleUploadSuccess}
|
||||
onClose={() => {
|
||||
setIsUploadModalOpen(false);
|
||||
}}
|
||||
onSuccess={(category) => handleUploadSuccess(category)}
|
||||
isOpen={isUploadModalOpen}
|
||||
title="Upload 3D Asset"
|
||||
title={`Upload ${selectedCategory ? (selectedCategory.toLowerCase().replace(/_/g, " ") === "location" ? "sets" : selectedCategory.toLowerCase().replace(/_/g, " ")) : "3D Asset"}`}
|
||||
titleIcon={!isSelectVisible ? currentTab.icon : faUpFromLine}
|
||||
preselectedCategory={selectedCategory || FilterEngineCategories.OBJECT}
|
||||
isSelectVisible={isSelectVisible}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,8 @@ export const Controls3D = () => {
|
||||
useEffect(() => {
|
||||
// Check if scene is empty and onboarding helper is not visible
|
||||
const checkSceneEmpty = () => {
|
||||
const isSceneEmpty = outlinerState.items.value.length === 0;
|
||||
const isSceneEmpty =
|
||||
outlinerState.items.value.length === 0 && !assetModalVisible.value;
|
||||
|
||||
setShowEmptySceneTooltip(isSceneEmpty);
|
||||
};
|
||||
@@ -37,7 +38,8 @@ export const Controls3D = () => {
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assetModalVisible.value]);
|
||||
|
||||
const handleModeChange = (value: string) => {
|
||||
selectedMode.value = value;
|
||||
@@ -119,7 +121,7 @@ export const Controls3D = () => {
|
||||
<Tooltip
|
||||
content="Add 3D asset to scene (B)"
|
||||
position="bottom"
|
||||
delay={100}
|
||||
delay={300}
|
||||
closeOnClick
|
||||
className={twMerge(
|
||||
showEmptySceneTooltip ? "hidden" : "block",
|
||||
|
||||
+9
-12
@@ -3,7 +3,7 @@ import { useSignals } from "@preact/signals-react/runtime";
|
||||
import DndAsset from "~/pages/PageEnigma/DragAndDrop/DndAsset";
|
||||
import { Badge } from "~/components";
|
||||
import { AssetType } from "~/enums";
|
||||
import { useState } from "react";
|
||||
// import { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faUpDownLeftRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
@@ -37,7 +37,7 @@ const patchExpressionObjectType = (mediaType: string) => {
|
||||
|
||||
export const ItemElement = ({ item }: Props) => {
|
||||
useSignals();
|
||||
const [imageError, setImageError] = useState(false);
|
||||
// const [imageError, setImageError] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="group relative w-full select-none overflow-hidden transition-all duration-200">
|
||||
@@ -59,16 +59,13 @@ export const ItemElement = ({ item }: Props) => {
|
||||
onPointerDown={(event) => DndAsset.onPointerDown(event, item)}
|
||||
style={{ cursor: "grab", pointerEvents: "auto" }}
|
||||
>
|
||||
{item.thumbnail && !imageError && (
|
||||
<img
|
||||
crossOrigin="anonymous"
|
||||
referrerPolicy="no-referrer"
|
||||
src={item.thumbnail}
|
||||
alt={item.name}
|
||||
className="h-full w-full object-cover object-center"
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
crossOrigin="anonymous"
|
||||
referrerPolicy="no-referrer"
|
||||
src={item.thumbnail}
|
||||
alt={item.name}
|
||||
className="h-full w-full object-cover object-center"
|
||||
/>
|
||||
|
||||
<div className="text-shadow-md absolute inset-0 flex items-center justify-center bg-brand-primary-950/50 text-[13px] font-medium text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faUpDownLeftRight} className="mr-1.5" />
|
||||
|
||||
@@ -54,6 +54,15 @@
|
||||
{
|
||||
"path": "../../libs/components/demo-modal"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/soundboard"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/tab-selector"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/api"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/components/tooltip"
|
||||
},
|
||||
@@ -65,12 +74,6 @@
|
||||
},
|
||||
{
|
||||
"path": "../../libs/tauri-utils"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/api"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/soundboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ export function DemoModal({
|
||||
<video autoPlay muted loop controls={false}>
|
||||
<source src={videoSrc} type="video/mp4" />
|
||||
</video>
|
||||
W
|
||||
</div>
|
||||
<Button
|
||||
className="font-semibold"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# @storyteller/ui-login-modal
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test @storyteller/ui-login-modal` to execute the unit tests via [Vitest](https://vitest.dev/).
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@storyteller/ui-login-modal",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"development": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
export * from './lib/login-modal';
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Replace this with your own classes
|
||||
*
|
||||
* e.g.
|
||||
* .container {
|
||||
* }
|
||||
*/
|
||||
@@ -0,0 +1,12 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import LoginModal from './login-modal';
|
||||
|
||||
describe('LoginModal', () => {
|
||||
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<LoginModal />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,273 @@
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Transition, TransitionChild } from "@headlessui/react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
faArrowRight,
|
||||
faRightToBracket,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { IsDesktopApp } from "@storyteller/tauri-utils";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
interface LoginModalProps {
|
||||
onClose: () => void;
|
||||
videoSrc2D: string;
|
||||
videoSrc3D: string;
|
||||
openAiLogo: string;
|
||||
}
|
||||
|
||||
export function LoginModal({
|
||||
onClose,
|
||||
videoSrc2D,
|
||||
videoSrc3D,
|
||||
openAiLogo,
|
||||
}: LoginModalProps) {
|
||||
const [step, setStep] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasSession, setHasSession] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const totalSteps = 3;
|
||||
|
||||
const checkSession = async () => {
|
||||
// Return the current session state
|
||||
return hasSession;
|
||||
};
|
||||
|
||||
// Check session on component mount
|
||||
useEffect(() => {
|
||||
const initSession = async () => {
|
||||
const sessionExists = await checkSession();
|
||||
if (!sessionExists) {
|
||||
setIsOpen(true);
|
||||
setStep(1);
|
||||
}
|
||||
};
|
||||
initSession();
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
if (step === 2) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (IsDesktopApp()) {
|
||||
await invoke("open_sora_login_command");
|
||||
// Add 3 second delay before setting session to true
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
setHasSession(true);
|
||||
const sessionExists = await checkSession();
|
||||
if (sessionExists) {
|
||||
setStep(step + 1);
|
||||
}
|
||||
} else {
|
||||
alert("Please open the desktop app to login");
|
||||
// Add 3 second delay before setting session to true
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
setHasSession(true);
|
||||
const sessionExists = true;
|
||||
|
||||
if (sessionExists) {
|
||||
setStep(step + 1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login failed:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else if (step < totalSteps) {
|
||||
setStep(step + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (step > 1) setStep(step - 1);
|
||||
};
|
||||
|
||||
// Progress bar rendering
|
||||
const renderProgress = () => (
|
||||
<div className="flex items-center justify-center gap-2 mb-2">
|
||||
{[...Array(totalSteps)].map((_, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`h-1.5 rounded transition-all duration-300 w-16 ${
|
||||
idx < step ? "bg-primary" : "bg-white/30"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
// Step content rendering
|
||||
const renderStepContent = () => {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-2 text-center">
|
||||
Welcome to ArtCraft
|
||||
</h2>
|
||||
<p className="text-white/70 mb-6 text-center">
|
||||
Here's what you can do...
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-6 h-full grow">
|
||||
<div className="h-full">
|
||||
<div className="aspect-[4/3] w-full overflow-hidden bg-black/20 rounded-t-lg">
|
||||
<video
|
||||
className="object-cover w-full h-full"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
controls={false}
|
||||
>
|
||||
<source src={videoSrc2D} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
<p className="text-center px-1.5 py-2 text-white/90 bg-black/20 rounded-b-lg font-medium text-sm">
|
||||
2D Canvas
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="aspect-[4/3] w-full overflow-hidden bg-black/20 rounded-t-lg">
|
||||
<video
|
||||
className="object-cover w-full h-full"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
controls={false}
|
||||
>
|
||||
<source src={videoSrc3D} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
<p className="text-center px-1.5 py-2 text-white/90 bg-black/20 rounded-b-lg font-medium text-sm">
|
||||
3D Scene Editor
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-2 text-center">
|
||||
Login with your OpenAI account
|
||||
</h2>
|
||||
<p className="text-white/70 mb-6 text-center px-16">
|
||||
We're the application layer for images and video. To provide you
|
||||
with <b>100% free service</b>, we use your existing AI accounts.
|
||||
To start off, you'll need to add your OpenAI Sora account. If you
|
||||
have a ChatGPT subscription, you can log in with Sora.
|
||||
</p>
|
||||
<img
|
||||
src={openAiLogo}
|
||||
alt="OpenAI Logo"
|
||||
className="w-72 h-72 mx-auto grow"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-2 text-center">
|
||||
Thank you for signing in!
|
||||
</h2>
|
||||
<p className="text-white/70 mb-6 text-center px-24">
|
||||
You're all set to start creating amazing content. Join our Discord
|
||||
community to connect with other creators, share your work, and get
|
||||
the latest updates.
|
||||
</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
window.open("https://discord.gg/75svZP2Vje", "_blank")
|
||||
}
|
||||
icon={faDiscord}
|
||||
className="text-md bg-[#5865F2] hover:bg-[#6a76ff] rounded-xl"
|
||||
>
|
||||
Join our Discord
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<Transition appear show={isOpen}>
|
||||
<div className="fixed inset-0 z-[100]">
|
||||
<TransitionChild
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 cursor-pointer bg-black/80" />
|
||||
</TransitionChild>
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<TransitionChild
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<div
|
||||
className="relative h-[660px] max-w-4xl w-full rounded-xl bg-[#2C2C2C] text-white shadow-lg border border-white/5"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex flex-col gap-4 p-8 h-full">
|
||||
<span className="text-sm text-center opacity-60 font-medium">
|
||||
Step {step} of {totalSteps}
|
||||
</span>
|
||||
{renderProgress()}
|
||||
{renderStepContent()}
|
||||
<div className="flex items-end justify-center gap-2.5 mt-10 grow">
|
||||
{step > 1 && step !== 3 ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleBack}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
) : null}
|
||||
{step < totalSteps ? (
|
||||
<Button
|
||||
icon={step === 2 ? faRightToBracket : faArrowRight}
|
||||
iconFlip={step !== 2}
|
||||
onClick={handleNext}
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{step === 2 ? "Login with OpenAI" : "Continue"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Start Creating Now
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
export default LoginModal;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts",
|
||||
"vite/client"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"jsx": "react-jsx",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
|
||||
},
|
||||
"exclude": [
|
||||
"out-tsc",
|
||||
"dist",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/vitest",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
],
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import dts from 'vite-plugin-dts';
|
||||
import * as path from 'path';
|
||||
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../node_modules/.vite/libs/components/login-modal',
|
||||
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
// Configuration for building your library.
|
||||
// See: https://vitejs.dev/guide/build.html#library-mode
|
||||
build: {
|
||||
outDir: './dist',
|
||||
emptyOutDir: true,
|
||||
reportCompressedSize: true,
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points.
|
||||
entry: 'src/index.ts',
|
||||
name: '@storyteller/ui-login-modal',
|
||||
fileName: 'index',
|
||||
// Change this to the formats you want to support.
|
||||
// Don't forget to update your package.json as well.
|
||||
formats: ['es' as const]
|
||||
},
|
||||
rollupOptions: {
|
||||
// External packages that should not be bundled into your library.
|
||||
external: ['react','react-dom','react/jsx-runtime']
|
||||
},
|
||||
},
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory: './test-output/vitest/coverage',
|
||||
provider: 'v8' as const,
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -2,27 +2,21 @@
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../modal"
|
||||
},
|
||||
{
|
||||
"path": "../select"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-api"
|
||||
},
|
||||
{
|
||||
"path": "../input"
|
||||
},
|
||||
{
|
||||
"path": "../button"
|
||||
},
|
||||
{
|
||||
"path": "../modal"
|
||||
},
|
||||
{
|
||||
"path": "../../api"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-api"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-utils"
|
||||
},
|
||||
{
|
||||
"path": "../../soundboard"
|
||||
},
|
||||
|
||||
@@ -43,29 +43,23 @@
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../select/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../input/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-utils/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../soundboard/tsconfig.lib.json"
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export const TabSelector: React.FC<TabSelectorProps> = ({
|
||||
className={({ selected }) =>
|
||||
twMerge(
|
||||
"relative z-20 mx-0.5 min-w-max rounded-md border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
|
||||
"focus-visible:outline-none focus-visible:ring-0",
|
||||
selected ? "text-white" : "text-gray-300 hover:text-white",
|
||||
disabled ? "cursor-not-allowed opacity-60" : ""
|
||||
)
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.5.0"
|
||||
"@tauri-apps/api": "^2.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+12
-4
@@ -2215,6 +2215,10 @@
|
||||
"name": "@storyteller/ui-loading-spinner",
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"libs/components/login-modal": {
|
||||
"name": "@storyteller/ui-login-modal",
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"libs/components/modal": {
|
||||
"name": "@storyteller/ui-modal",
|
||||
"version": "0.0.1"
|
||||
@@ -2260,7 +2264,7 @@
|
||||
"name": "@storyteller/tauri-api",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.5.0"
|
||||
"@tauri-apps/api": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"libs/tauri-utils": {
|
||||
@@ -10320,6 +10324,10 @@
|
||||
"resolved": "libs/components/loading-spinner",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@storyteller/ui-login-modal": {
|
||||
"resolved": "libs/components/login-modal",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@storyteller/ui-modal": {
|
||||
"resolved": "libs/components/modal",
|
||||
"link": true
|
||||
@@ -10941,9 +10949,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.5.0.tgz",
|
||||
"integrity": "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.4.1.tgz",
|
||||
"integrity": "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./libs/tauri-api"
|
||||
},
|
||||
{
|
||||
"path": "./libs/components/login-modal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user