mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 01:38:44 +00:00
login modal and settings panel update (#590)
- login now flows automatically smoothly to the next step after login - settings modal: misc/audio panes styling and toggle switch - account settings disconnect sora account button remaining TODO: - link logout sora endpoint
This commit is contained in:
1 parent
eec59c218d
commit
d43ee0f6c9
31 files changed
+727
-318
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { LoadingDots, TopBar } from "~/components";
|
||||
import { Controls3D } from "./comps/Controls3D";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ErrorDialog, LoadingDots } from "~/components";
|
||||
import { GenerateModals } from "~/pages/PageEnigma/comps/GenerateModals/GenerateModals";
|
||||
import { Toaster } from "@storyteller/ui-toaster";
|
||||
import { EditorLoadingBar } from "./comps/EditorLoadingBar";
|
||||
import { Wizard } from "~/pages/PageEnigma/Wizard/Wizard";
|
||||
// import { Wizard } from "~/pages/PageEnigma/Wizard/Wizard";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { useEffect, useState } from "react";
|
||||
import * as gpu from "detect-gpu";
|
||||
@@ -91,7 +91,7 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log('installing event listeners')
|
||||
console.log("installing event listeners");
|
||||
InstallSounds();
|
||||
InstallImageGenerationSuccess();
|
||||
InstallImageGenerationFailure();
|
||||
@@ -117,7 +117,7 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
|
||||
/>
|
||||
<GenerateModals />
|
||||
<ErrorDialog />
|
||||
<Wizard />
|
||||
{/* <Wizard /> */}
|
||||
</EngineProvider>
|
||||
<EditorLoadingBar />
|
||||
<Toaster offsetTop={70} offsetRight={12} />
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
iconClassName?: string;
|
||||
iconFlip?: boolean;
|
||||
htmlFor?: string;
|
||||
variant?: "primary" | "secondary" | "action";
|
||||
variant?: "primary" | "secondary" | "action" | "destructive";
|
||||
loading?: boolean;
|
||||
as?: "button" | "link";
|
||||
href?: string;
|
||||
@@ -44,6 +44,9 @@ export const Button = ({
|
||||
case "action": {
|
||||
return "bg-action hover:bg-action-900 text-white focus-visible:outline-action";
|
||||
}
|
||||
case "destructive": {
|
||||
return "bg-red/70 hover:bg-red/50 text-white focus-visible:outline-red";
|
||||
}
|
||||
case "primary":
|
||||
default: {
|
||||
return "bg-primary hover:bg-primary-400 text-white focus-visible:outline-primary-600";
|
||||
|
||||
@@ -46,7 +46,8 @@ export const Input = React.forwardRef(
|
||||
id={id ? id : label ? label : undefined}
|
||||
className={twMerge(
|
||||
"h-10 w-full rounded-lg bg-[#242424] px-3 py-2.5 text-white placeholder-white/50 outline-none",
|
||||
"border border-[#363636] transition-all duration-150 ease-in-out hover:border-brand-primary/60 focus:border-brand-primary focus:!outline-none",
|
||||
"border border-[#363636] transition-all duration-150 ease-in-out hover:border-primary/60 focus:border-primary focus:!outline-none",
|
||||
"disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:border-[#363636]",
|
||||
icon && "pl-10",
|
||||
isError && "outline-red focus:outline-red",
|
||||
inputClassName
|
||||
|
||||
@@ -8,14 +8,18 @@ import {
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { IsDesktopApp } from "@storyteller/tauri-utils";
|
||||
import { CheckSoraSession, SoraSessionState } from "@storyteller/tauri-api";
|
||||
import {
|
||||
CheckSoraSession,
|
||||
SoraSessionState,
|
||||
useSoraLoginListener,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
interface LoginModalProps {
|
||||
onClose: () => void;
|
||||
videoSrc2D: string;
|
||||
videoSrc3D: string;
|
||||
openAiLogo: string;
|
||||
videoSrc2D?: string;
|
||||
videoSrc3D?: string;
|
||||
openAiLogo?: string;
|
||||
}
|
||||
|
||||
export function LoginModal({
|
||||
@@ -26,11 +30,15 @@ export function LoginModal({
|
||||
}: LoginModalProps) {
|
||||
const [step, setStep] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasSession, setHasSession] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoginClicked, setIsLoginClicked] = useState(false);
|
||||
|
||||
const totalSteps = 3;
|
||||
|
||||
useSoraLoginListener((payload) => {
|
||||
console.log("Login success!", payload);
|
||||
setStep(3);
|
||||
});
|
||||
|
||||
// Check session on component mount
|
||||
useEffect(() => {
|
||||
const initSession = async () => {
|
||||
@@ -52,14 +60,12 @@ export function LoginModal({
|
||||
const handleNext = async () => {
|
||||
if (step === 2) {
|
||||
setIsLoading(true);
|
||||
setIsLoginClicked(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 result = await CheckSoraSession();
|
||||
const sessionExists = result.state === SoraSessionState.Valid;
|
||||
if (sessionExists) {
|
||||
@@ -69,7 +75,6 @@ export function LoginModal({
|
||||
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) {
|
||||
@@ -169,9 +174,6 @@ export function LoginModal({
|
||||
alt="OpenAI Logo"
|
||||
className="w-72 h-72 mx-auto grow"
|
||||
/>
|
||||
{/*<div>
|
||||
<Button onClick={() => handleClose()}>Cancel (temporary)</Button>
|
||||
</div>*/}
|
||||
</div>
|
||||
);
|
||||
case 3:
|
||||
@@ -255,17 +257,6 @@ export function LoginModal({
|
||||
>
|
||||
{step === 2 ? "Login with OpenAI" : "Continue"}
|
||||
</Button>
|
||||
{step > 1 && step !== 3 ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setStep(step + 1);
|
||||
}}
|
||||
disabled={!isLoginClicked}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
|
||||
@@ -17,6 +17,7 @@ interface ListDropdownProps {
|
||||
placeholder?: string;
|
||||
value?: SelectValue;
|
||||
id?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Select = ({
|
||||
@@ -25,13 +26,14 @@ export const Select = ({
|
||||
placeholder,
|
||||
value,
|
||||
id,
|
||||
className,
|
||||
}: ListDropdownProps) => {
|
||||
const selectedOption = options.find(
|
||||
(option: SelectOption) => option.value === value
|
||||
) || { label: placeholder || "", value: "" };
|
||||
|
||||
return (
|
||||
<div className="relative mt-1">
|
||||
<div className={twMerge("relative", className)}>
|
||||
<Listbox value={value || ""} onChange={onChange}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
CheckSoraSession,
|
||||
SoraSessionState,
|
||||
LogoutSoraSession,
|
||||
useSoraLoginListener,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
interface SoraAccountButtonProps {
|
||||
cacheKey?: string;
|
||||
cacheTtlMs?: number;
|
||||
}
|
||||
|
||||
export const SoraAccountButton = ({
|
||||
cacheKey = "soraSettingsSessionCache",
|
||||
cacheTtlMs = 5 * 60 * 1000,
|
||||
}: SoraAccountButtonProps) => {
|
||||
const [soraSession, setSoraSession] = useState<SoraSessionState | null>(null);
|
||||
const [isCheckingSoraSession, setIsCheckingSoraSession] = useState(false);
|
||||
|
||||
useSoraLoginListener(() => {
|
||||
setIsCheckingSoraSession(true);
|
||||
CheckSoraSession()
|
||||
.then((result) => {
|
||||
setSoraSession(result.state);
|
||||
localStorage.setItem(
|
||||
cacheKey,
|
||||
JSON.stringify({ state: result.state, timestamp: Date.now() })
|
||||
);
|
||||
})
|
||||
.finally(() => setIsCheckingSoraSession(false));
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSession = async () => {
|
||||
setIsCheckingSoraSession(true);
|
||||
try {
|
||||
const cached = localStorage.getItem(cacheKey);
|
||||
if (cached) {
|
||||
const { state, timestamp } = JSON.parse(cached);
|
||||
if (Date.now() - timestamp < cacheTtlMs) {
|
||||
setSoraSession(state);
|
||||
setIsCheckingSoraSession(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const result = await CheckSoraSession();
|
||||
setSoraSession(result.state);
|
||||
localStorage.setItem(
|
||||
cacheKey,
|
||||
JSON.stringify({ state: result.state, timestamp: Date.now() })
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error fetching Sora session", e);
|
||||
setSoraSession(null);
|
||||
} finally {
|
||||
setIsCheckingSoraSession(false);
|
||||
}
|
||||
};
|
||||
fetchSession();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [cacheKey, cacheTtlMs]);
|
||||
|
||||
const handleSoraButton = async () => {
|
||||
if (isCheckingSoraSession) return;
|
||||
if (soraSession === SoraSessionState.Valid) {
|
||||
setIsCheckingSoraSession(true);
|
||||
await LogoutSoraSession();
|
||||
setSoraSession(SoraSessionState.NotSetUp);
|
||||
localStorage.removeItem(cacheKey);
|
||||
setIsCheckingSoraSession(false);
|
||||
} else {
|
||||
setIsCheckingSoraSession(true);
|
||||
try {
|
||||
await invoke("open_sora_login_command");
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setIsCheckingSoraSession(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={
|
||||
soraSession === SoraSessionState.Valid && !isCheckingSoraSession
|
||||
? "destructive"
|
||||
: soraSession === SoraSessionState.NotSetUp
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
className="h-[30px]"
|
||||
onClick={handleSoraButton}
|
||||
disabled={isCheckingSoraSession}
|
||||
>
|
||||
{isCheckingSoraSession ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-sm"
|
||||
/>
|
||||
) : soraSession === SoraSessionState.Valid ? (
|
||||
"Disconnect"
|
||||
) : (
|
||||
"Connect"
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { SoraAccountButton } from "../components/SoraAccountButton";
|
||||
|
||||
interface AccountInfo {
|
||||
username: string;
|
||||
subscription: string;
|
||||
credits: number;
|
||||
}
|
||||
|
||||
export const AccountSettingsPane = () => {
|
||||
const [accountInfo] = useState<AccountInfo>({
|
||||
username: "-",
|
||||
subscription: "-",
|
||||
credits: 0,
|
||||
});
|
||||
|
||||
const [palApiKey, setPalApiKey] = useState("");
|
||||
const [klingApiKey, setKlingApiKey] = useState("");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<span>ArtCraft Account:</span>
|
||||
<span className="text-white/80">{accountInfo.username}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>ArtCraft Subscription:</span>
|
||||
<span className="text-white/80">{accountInfo.subscription}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Credits Remaining:</span>
|
||||
<span className="text-white/80">{accountInfo.credits}</span>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md p-4 text-sm dark:bg-white/5">
|
||||
<p className="text-white/60">
|
||||
Note: You can optionally log into other accounts and use your
|
||||
credits at those providers. Some features are only available via 3rd
|
||||
party accounts, such as OpenAI / Sora GPT 4.0 Images. For other
|
||||
features, ArtCraft credits are consumed unless you log into your
|
||||
third party account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span>OpenAI / Sora Account:</span>
|
||||
<SoraAccountButton />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span>Google / Veo Account:</span>
|
||||
<Button variant="secondary" disabled className="h-[30px]">
|
||||
Connect
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="pal-api-key" className="mb-2 block">
|
||||
Pal API Key (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="pal-api-key"
|
||||
type="password"
|
||||
value={palApiKey}
|
||||
onChange={(e) => setPalApiKey((e.target as any).value)}
|
||||
placeholder="Enter API Key"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="kling-api-key" className="mb-2 block">
|
||||
Kling API Key (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="kling-api-key"
|
||||
type="password"
|
||||
value={klingApiKey}
|
||||
onChange={(e) => setKlingApiKey((e.target as any).value)}
|
||||
placeholder="Enter API Key"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,15 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { SoundRegistry } from "@storyteller/soundboard";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { AppPreferencesPayload, GetAppPreferences } from "@storyteller/tauri-api";
|
||||
import { faPlay } from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
AppPreferencesPayload,
|
||||
GetAppPreferences,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { PreferenceName, UpdateAppPreferences } from "@storyteller/tauri-api";
|
||||
import { Select, SelectValue } from "@storyteller/ui-select";
|
||||
import { Switch } from "@storyteller/ui-switch";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
|
||||
// TODO: This is maintained in two places. Here and InstallSounds.
|
||||
const SOUND_OPTIONS = [
|
||||
@@ -16,22 +21,21 @@ const SOUND_OPTIONS = [
|
||||
{ value: "crumble", label: "Crumble" },
|
||||
];
|
||||
|
||||
interface AudioSettingsPaneProps {
|
||||
}
|
||||
interface AudioSettingsPaneProps {}
|
||||
|
||||
export const AudioSettingsPane = (args: AudioSettingsPaneProps) => {
|
||||
const [preferences, setPreferences] = useState<AppPreferencesPayload|undefined>(undefined);
|
||||
const [preferences, setPreferences] = useState<
|
||||
AppPreferencesPayload | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const prefs = await GetAppPreferences();
|
||||
console.log("prefs", prefs)
|
||||
setPreferences(prefs.preferences);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
|
||||
const playSounds = preferences?.play_sounds || false;
|
||||
|
||||
const successSound = orNone(preferences?.generation_success_sound);
|
||||
@@ -40,138 +44,128 @@ export const AudioSettingsPane = (args: AudioSettingsPaneProps) => {
|
||||
|
||||
const reloadPreferences = async () => {
|
||||
const prefs = await GetAppPreferences();
|
||||
console.log("prefs", prefs)
|
||||
setPreferences(prefs.preferences);
|
||||
}
|
||||
};
|
||||
|
||||
const setPlaySounds = async (checked: boolean) => {
|
||||
//const value = checked ? "true" : "false";
|
||||
await UpdateAppPreferences({
|
||||
preference: PreferenceName.PlaySounds,
|
||||
preference: PreferenceName.PlaySounds,
|
||||
value: checked,
|
||||
});
|
||||
await reloadPreferences();
|
||||
}
|
||||
};
|
||||
|
||||
const setSuccessSound = async (val: string) => {
|
||||
let sendVal = val === "none" ? undefined : val;
|
||||
await UpdateAppPreferences({
|
||||
preference: PreferenceName.GenerationSuccessSound,
|
||||
preference: PreferenceName.GenerationSuccessSound,
|
||||
value: sendVal,
|
||||
});
|
||||
SoundRegistry.getInstance().playSound(val);
|
||||
await reloadPreferences();
|
||||
}
|
||||
};
|
||||
|
||||
const setFailureSound = async (val: string) => {
|
||||
let sendVal = val === "none" ? undefined : val;
|
||||
await UpdateAppPreferences({
|
||||
preference: PreferenceName.GenerationFailureSound,
|
||||
preference: PreferenceName.GenerationFailureSound,
|
||||
value: sendVal,
|
||||
});
|
||||
SoundRegistry.getInstance().playSound(val);
|
||||
await reloadPreferences();
|
||||
}
|
||||
};
|
||||
|
||||
const setEnqueueSound = async (val: string) => {
|
||||
let sendVal = val === "none" ? undefined : val;
|
||||
await UpdateAppPreferences({
|
||||
preference: PreferenceName.GenerationEnqueueSound,
|
||||
preference: PreferenceName.GenerationEnqueueSound,
|
||||
value: sendVal,
|
||||
});
|
||||
SoundRegistry.getInstance().playSound(val);
|
||||
await reloadPreferences();
|
||||
}
|
||||
};
|
||||
|
||||
const playSound = (val?: string) => {
|
||||
if (val !== undefined && val !== "none") {
|
||||
SoundRegistry.getInstance().playSound(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (<>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="play-sounds" className="mb-2 block">
|
||||
Play Notification Sounds for Events?
|
||||
</label>
|
||||
<Input
|
||||
id="play-sounds"
|
||||
type="checkbox"
|
||||
checked={playSounds}
|
||||
onChange={(e) => setPlaySounds((e.target as any).checked)}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col">
|
||||
<Label htmlFor="play-sounds">
|
||||
Play Notification Sounds for Events?
|
||||
</Label>
|
||||
<Switch enabled={playSounds} setEnabled={setPlaySounds} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="success-sound">Success Sound</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
id="success-sound"
|
||||
value={successSound}
|
||||
onChange={(val: SelectValue) => setSuccessSound(val as string)}
|
||||
options={SOUND_OPTIONS}
|
||||
className="grow"
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-[40px] h-[40px]"
|
||||
icon={faPlay}
|
||||
onClick={() => playSound(successSound)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="failure-sound">Failure Sound</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
id="failure-sound"
|
||||
value={failureSound}
|
||||
onChange={(val: SelectValue) => setFailureSound(val as string)}
|
||||
options={SOUND_OPTIONS}
|
||||
className="grow"
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-[40px] h-[40px]"
|
||||
icon={faPlay}
|
||||
onClick={() => playSound(failureSound)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="enqueue-sound">Enqueue Sound</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
id="enqueue-sound"
|
||||
value={enqueueSound}
|
||||
onChange={(val: SelectValue) => setEnqueueSound(val as string)}
|
||||
options={SOUND_OPTIONS}
|
||||
className="grow"
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-[40px] h-[40px]"
|
||||
icon={faPlay}
|
||||
onClick={() => playSound(enqueueSound)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
<div>
|
||||
<label htmlFor="success-sound" className="mb-2 block">
|
||||
Success Sound
|
||||
</label>
|
||||
<Select
|
||||
id="success-sound"
|
||||
value={successSound}
|
||||
onChange={(val: SelectValue) =>
|
||||
setSuccessSound(val as string)
|
||||
}
|
||||
options={SOUND_OPTIONS}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="py-1"
|
||||
onClick={() => playSound(successSound)}
|
||||
>
|
||||
Play
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="failure-sound" className="mb-2 block">
|
||||
Failure Sound
|
||||
</label>
|
||||
<Select
|
||||
id="failure-sound"
|
||||
value={failureSound}
|
||||
onChange={(val: SelectValue) =>
|
||||
setFailureSound(val as string)
|
||||
}
|
||||
options={SOUND_OPTIONS}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="py-1"
|
||||
onClick={() => playSound(failureSound)}
|
||||
>
|
||||
Play
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="enqueue-sound" className="mb-2 block">
|
||||
Enqueue Sound
|
||||
</label>
|
||||
<Select
|
||||
id="enqueue-sound"
|
||||
value={enqueueSound}
|
||||
onChange={(val: SelectValue) =>
|
||||
setEnqueueSound(val as string)
|
||||
}
|
||||
options={SOUND_OPTIONS}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="py-1"
|
||||
onClick={() => playSound(enqueueSound)}
|
||||
>
|
||||
Play
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>)
|
||||
}
|
||||
|
||||
const orNone = (val: string | undefined | null) : string => {
|
||||
const orNone = (val: string | undefined | null): string => {
|
||||
if (!!!val) {
|
||||
return "none";
|
||||
}
|
||||
return val;
|
||||
}
|
||||
};
|
||||
@@ -1,35 +1,47 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { AppPreferencesPayload, CustomDirectory, GetAppPreferences, SystemDirectory } from "@storyteller/tauri-api";
|
||||
import {
|
||||
AppPreferencesPayload,
|
||||
CustomDirectory,
|
||||
GetAppPreferences,
|
||||
SystemDirectory,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { PreferenceName, UpdateAppPreferences } from "@storyteller/tauri-api";
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
|
||||
interface MiscSettingsPaneProps {
|
||||
}
|
||||
interface MiscSettingsPaneProps {}
|
||||
|
||||
export const MiscSettingsPane = (args: MiscSettingsPaneProps) => {
|
||||
const [preferences, setPreferences] = useState<AppPreferencesPayload|undefined>(undefined);
|
||||
const [preferences, setPreferences] = useState<
|
||||
AppPreferencesPayload | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const prefs = await GetAppPreferences();
|
||||
console.log("prefs", prefs)
|
||||
console.log("prefs", prefs);
|
||||
setPreferences(prefs.preferences);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
|
||||
// NB: This might be a complex type.
|
||||
const outerDownloadObject = preferences?.preferred_download_directory || {};
|
||||
const downloadDirectory = ("custom" in outerDownloadObject) ? outerDownloadObject.custom as string : "";
|
||||
const currentDownloadLabel = ("system" in outerDownloadObject) ? "System Download Directory" : downloadDirectory;
|
||||
const downloadDirectory =
|
||||
"custom" in outerDownloadObject
|
||||
? (outerDownloadObject.custom as string)
|
||||
: "";
|
||||
const currentDownloadLabel =
|
||||
"system" in outerDownloadObject
|
||||
? "System Download Directory"
|
||||
: downloadDirectory;
|
||||
|
||||
const reloadPreferences = async () => {
|
||||
const prefs = await GetAppPreferences();
|
||||
console.log("prefs", prefs)
|
||||
console.log("prefs", prefs);
|
||||
setPreferences(prefs.preferences);
|
||||
}
|
||||
};
|
||||
|
||||
const openDirectoryPicker = async () => {
|
||||
let directory = await open({
|
||||
@@ -41,49 +53,44 @@ export const MiscSettingsPane = (args: MiscSettingsPaneProps) => {
|
||||
return; // User dismissed the dialog choice
|
||||
}
|
||||
await UpdateAppPreferences({
|
||||
preference: PreferenceName.PreferredDownloadDirectory,
|
||||
preference: PreferenceName.PreferredDownloadDirectory,
|
||||
value: {
|
||||
custom: directory
|
||||
custom: directory,
|
||||
} as CustomDirectory,
|
||||
});
|
||||
await reloadPreferences();
|
||||
}
|
||||
};
|
||||
|
||||
const clearDirectory = async () => {
|
||||
await UpdateAppPreferences({
|
||||
preference: PreferenceName.PreferredDownloadDirectory,
|
||||
preference: PreferenceName.PreferredDownloadDirectory,
|
||||
value: {
|
||||
system: "downloads"
|
||||
system: "downloads",
|
||||
} as SystemDirectory,
|
||||
});
|
||||
await reloadPreferences();
|
||||
}
|
||||
};
|
||||
|
||||
return (<>
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="download-path" className="mb-2 block">
|
||||
Default Download Directory
|
||||
</label>
|
||||
This is where downloads are placed after downloading.
|
||||
The current path is <pre>{currentDownloadLabel}</pre>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
className="py-1"
|
||||
onClick={openDirectoryPicker}
|
||||
>
|
||||
Choose Directory
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="download-path">Default Download Directory</Label>
|
||||
<p className="text-white/80">
|
||||
This is where downloads are placed after downloading. The current path
|
||||
is:
|
||||
</p>
|
||||
<div className="bg-black/20 py-1.5 px-2 rounded-md mt-1 text-white/90">
|
||||
<pre>{currentDownloadLabel}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="primary" onClick={openDirectoryPicker}>
|
||||
Choose Directory
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="py-1"
|
||||
onClick={clearDirectory}
|
||||
>
|
||||
Use Default
|
||||
<Button variant="secondary" onClick={clearDirectory}>
|
||||
Use Default
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>)
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Select, SelectValue } from "@storyteller/ui-select";
|
||||
import { useState } from "react";
|
||||
|
||||
export const VideoSettingsPane = () => {
|
||||
const [defaultVideoModel, setDefaultVideoModel] = useState("veo");
|
||||
const [humanVideoProvider, setHumanVideoProvider] = useState("artcraft");
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="default-video-model" className="mb-2 block">
|
||||
Default Video Model
|
||||
</label>
|
||||
<Select
|
||||
id="default-video-model"
|
||||
value={defaultVideoModel}
|
||||
onChange={(val: SelectValue) => setDefaultVideoModel(val as string)}
|
||||
options={[
|
||||
{ value: "veo", label: "Veo" },
|
||||
{ value: "kling", label: "Kling" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="human-video-provider" className="mb-2 block">
|
||||
Human Video Provider
|
||||
</label>
|
||||
<Select
|
||||
id="human-video-provider"
|
||||
value={humanVideoProvider}
|
||||
onChange={(val: SelectValue) => setHumanVideoProvider(val as string)}
|
||||
options={[
|
||||
{ value: "artcraft", label: "ArtCraft" },
|
||||
{ value: "pal", label: "Pal" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
@@ -10,39 +9,22 @@ import {
|
||||
faVolumeHigh,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Select, SelectValue } from "@storyteller/ui-select";
|
||||
import { MiscSettingsPane } from "./panes/MiscSettingsPane";
|
||||
import { AudioSettingsPane } from "./panes/AudioSettingsPane";
|
||||
import { AccountSettingsPane } from "./panes/AccountSettingsPane";
|
||||
import { VideoSettingsPane } from "./panes/VideoSettingsPane";
|
||||
|
||||
interface SettingsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface AccountInfo {
|
||||
username: string;
|
||||
subscription: string;
|
||||
credits: number;
|
||||
}
|
||||
|
||||
type SettingsSection = "misc" | "audio" | "accounts" | "video" | "image";
|
||||
|
||||
export const SettingsModal = ({ isOpen, onClose }: SettingsModalProps) => {
|
||||
const [accountInfo] = useState<AccountInfo>({
|
||||
username: "Username [logout]",
|
||||
subscription: "Pro-tier [change/upgrade]",
|
||||
credits: 10233,
|
||||
});
|
||||
|
||||
const [selectedSection, setSelectedSection] =
|
||||
useState<SettingsSection>("misc");
|
||||
|
||||
const [palApiKey, setPalApiKey] = useState("");
|
||||
const [klingApiKey, setKlingApiKey] = useState("");
|
||||
const [defaultVideoModel, setDefaultVideoModel] = useState("veo");
|
||||
const [humanVideoProvider, setHumanVideoProvider] = useState("artcraft");
|
||||
|
||||
const sections = [
|
||||
{ id: "audio" as const, label: "Audio", icon: faVolumeHigh },
|
||||
{ id: "misc" as const, label: "Misc", icon: faCog },
|
||||
@@ -54,123 +36,17 @@ export const SettingsModal = ({ isOpen, onClose }: SettingsModalProps) => {
|
||||
const renderContent = () => {
|
||||
switch (selectedSection) {
|
||||
case "audio":
|
||||
return (
|
||||
<AudioSettingsPane />
|
||||
);
|
||||
return <AudioSettingsPane />;
|
||||
case "misc":
|
||||
return (
|
||||
<MiscSettingsPane />
|
||||
);
|
||||
return <MiscSettingsPane />;
|
||||
case "accounts":
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<span>ArtCraft Account:</span>
|
||||
<span className="text-white/80">{accountInfo.username}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>ArtCraft Subscription:</span>
|
||||
<span className="text-white/80">{accountInfo.subscription}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Credits Remaining:</span>
|
||||
<span className="text-white/80">{accountInfo.credits}</span>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md p-4 text-sm dark:bg-white/5">
|
||||
<p className="text-white/60">
|
||||
Note: You can optionally log into other accounts and use your
|
||||
credits at those providers. Some features are only available via
|
||||
3rd party accounts, such as OpenAI / Sora GPT 4.0 Images. For
|
||||
other features, ArtCraft credits are consumed unless you log
|
||||
into your third party account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span>OpenAI / Sora Account:</span>
|
||||
<Button variant="secondary" className="py-1">
|
||||
Connect
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span>Google / Veo Account:</span>
|
||||
<Button variant="secondary" className="py-1">
|
||||
Connect
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="pal-api-key" className="mb-2 block">
|
||||
Pal API Key (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="pal-api-key"
|
||||
type="password"
|
||||
value={palApiKey}
|
||||
onChange={(e) => setPalApiKey((e.target as any).value)}
|
||||
placeholder="Enter API Key"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="kling-api-key" className="mb-2 block">
|
||||
Kling API Key (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="kling-api-key"
|
||||
type="password"
|
||||
value={klingApiKey}
|
||||
onChange={(e) => setKlingApiKey((e.target as any).value)}
|
||||
placeholder="Enter API Key"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <AccountSettingsPane />;
|
||||
case "video":
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="default-video-model" className="mb-2 block">
|
||||
Default Video Model
|
||||
</label>
|
||||
<Select
|
||||
id="default-video-model"
|
||||
value={defaultVideoModel}
|
||||
onChange={(val: SelectValue) =>
|
||||
setDefaultVideoModel(val as string)
|
||||
}
|
||||
options={[
|
||||
{ value: "veo", label: "Veo" },
|
||||
{ value: "kling", label: "Kling" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="human-video-provider" className="mb-2 block">
|
||||
Human Video Provider
|
||||
</label>
|
||||
<Select
|
||||
id="human-video-provider"
|
||||
value={humanVideoProvider}
|
||||
onChange={(val: SelectValue) =>
|
||||
setHumanVideoProvider(val as string)
|
||||
}
|
||||
options={[
|
||||
{ value: "artcraft", label: "ArtCraft" },
|
||||
{ value: "pal", label: "Pal" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <VideoSettingsPane />;
|
||||
case "image":
|
||||
return (
|
||||
<div>
|
||||
<button className="text-blue-600">Various Image Settings (TODO)...</button>
|
||||
<button className="text-blue-600">Various Image Settings...</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -208,7 +84,7 @@ export const SettingsModal = ({ isOpen, onClose }: SettingsModalProps) => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-8 flex h-full flex-col overflow-auto">
|
||||
<div className="col-span-8 flex h-full flex-col">
|
||||
<div className="w-full border-b border-white/10 py-2.5 ps-0">
|
||||
<h2 className="text-[18px] font-semibold">
|
||||
{sections.find((s) => s.id === selectedSection)?.label}
|
||||
|
||||
@@ -5,21 +5,27 @@
|
||||
{
|
||||
"path": "../modal"
|
||||
},
|
||||
{
|
||||
"path": "../label"
|
||||
},
|
||||
{
|
||||
"path": "../switch"
|
||||
},
|
||||
{
|
||||
"path": "../select"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-api"
|
||||
},
|
||||
{
|
||||
"path": "../../soundboard"
|
||||
},
|
||||
{
|
||||
"path": "../input"
|
||||
},
|
||||
{
|
||||
"path": "../button"
|
||||
},
|
||||
{
|
||||
"path": "../../soundboard"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
|
||||
@@ -46,20 +46,26 @@
|
||||
{
|
||||
"path": "../modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../label/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../switch/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../select/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../soundboard/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../input/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../soundboard/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# @storyteller/ui-switch
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test @storyteller/ui-switch` to execute the unit tests via [Vitest](https://vitest.dev/).
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@storyteller/ui-switch",
|
||||
"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/switch';
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Replace this with your own classes
|
||||
*
|
||||
* e.g.
|
||||
* .container {
|
||||
* }
|
||||
*/
|
||||
@@ -0,0 +1,12 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import Switch from './switch';
|
||||
|
||||
describe('Switch', () => {
|
||||
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Switch />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Switch as HeadlessSwitch } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { Fragment } from "react";
|
||||
|
||||
interface SwitchProps {
|
||||
enabled: boolean;
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Switch({ enabled, setEnabled, className }: SwitchProps) {
|
||||
return (
|
||||
<HeadlessSwitch checked={enabled} onChange={setEnabled} as={Fragment}>
|
||||
{({ checked, disabled }) => (
|
||||
<button
|
||||
className={clsx(
|
||||
"group inline-flex h-6 w-11 items-center rounded-full",
|
||||
checked ? "bg-primary" : "bg-action",
|
||||
disabled && "cursor-not-allowed opacity-50",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Enable notifications</span>
|
||||
<span
|
||||
className={clsx(
|
||||
"size-4 rounded-full bg-white transition",
|
||||
checked ? "translate-x-6" : "translate-x-1"
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</HeadlessSwitch>
|
||||
);
|
||||
}
|
||||
|
||||
export default Switch;
|
||||
@@ -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/switch',
|
||||
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-switch',
|
||||
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,
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
export * from './lib/settings/GetAppPreferences.js';
|
||||
export * from './lib/settings/UpdateAppPreference.js';
|
||||
export * from './lib/sora/CheckSoraSession.js';
|
||||
export * from "./lib/settings/GetAppPreferences.js";
|
||||
export * from "./lib/settings/UpdateAppPreference.js";
|
||||
export * from "./lib/sora/CheckSoraSession.js";
|
||||
export * from "./lib/sora/useSoraLoginListener.js";
|
||||
export * from "./lib/sora/LogoutSoraSession.js";
|
||||
@@ -1,8 +1,8 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export interface CheckSoraSessionResult {
|
||||
state: SoraSessionState,
|
||||
maybe_account_email?: string,
|
||||
state: SoraSessionState;
|
||||
maybe_account_email?: string;
|
||||
}
|
||||
|
||||
export enum SoraSessionState {
|
||||
@@ -11,8 +11,8 @@ export enum SoraSessionState {
|
||||
Valid = "valid",
|
||||
}
|
||||
|
||||
export const CheckSoraSession = async () : Promise<CheckSoraSessionResult> => {
|
||||
let result = await invoke("check_sora_session_command");
|
||||
export const CheckSoraSession = async (): Promise<CheckSoraSessionResult> => {
|
||||
const result = await invoke("check_sora_session_command");
|
||||
console.log(">>> sora result", result);
|
||||
return (result as CheckSoraSessionResult);
|
||||
}
|
||||
return result as CheckSoraSessionResult;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export const LogoutSoraSession = async (): Promise<void> => {
|
||||
// Replace with real Tauri command to logout Sora session - BFlat
|
||||
return new Promise((resolve) => setTimeout(resolve, 500));
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useEffect } from "react";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
type SoraLoginSuccessPayload = {
|
||||
// TODO
|
||||
};
|
||||
|
||||
export function useSoraLoginListener(
|
||||
onSuccess: (payload: SoraLoginSuccessPayload) => void
|
||||
) {
|
||||
useEffect(() => {
|
||||
let unlisten: () => void;
|
||||
|
||||
const setupListener = async () => {
|
||||
const stop = await listen<SoraLoginSuccessPayload>(
|
||||
"sora-login-success",
|
||||
(event) => {
|
||||
onSuccess(event.payload);
|
||||
}
|
||||
);
|
||||
unlisten = stop;
|
||||
};
|
||||
|
||||
setupListener();
|
||||
|
||||
return () => {
|
||||
if (unlisten) unlisten();
|
||||
};
|
||||
}, [onSuccess]);
|
||||
}
|
||||
Generated
+8
@@ -2234,6 +2234,10 @@
|
||||
"@tauri-apps/plugin-dialog": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"libs/components/switch": {
|
||||
"name": "@storyteller/ui-switch",
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"libs/components/tab-selector": {
|
||||
"name": "@storyteller/ui-tab-selector",
|
||||
"version": "0.0.1"
|
||||
@@ -10340,6 +10344,10 @@
|
||||
"resolved": "libs/components/settings-modal",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@storyteller/ui-switch": {
|
||||
"resolved": "libs/components/switch",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@storyteller/ui-tab-selector": {
|
||||
"resolved": "libs/components/tab-selector",
|
||||
"link": true
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./libs/components/login-modal"
|
||||
},
|
||||
{
|
||||
"path": "./libs/components/switch"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user