From d43ee0f6c9fd7daca09f1eb5e4f758cff14d2cd9 Mon Sep 17 00:00:00 2001 From: BFlat Date: Thu, 8 May 2025 14:21:01 +0700 Subject: [PATCH] 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 --- .../app/src/pages/PageEnigma/PageEditor.tsx | 2 +- .../app/src/pages/PageEnigma/PageEnigma.tsx | 6 +- .../libs/components/button/src/lib/button.tsx | 5 +- .../libs/components/input/src/lib/input.tsx | 3 +- .../login-modal/src/lib/login-modal.tsx | 37 ++-- .../libs/components/select/src/lib/select.tsx | 4 +- .../src/lib/components/SoraAccountButton.tsx | 112 ++++++++++ .../src/lib/panes/AccountSettingsPane.tsx | 91 +++++++++ .../src/lib/panes/AudioSettingsPane.tsx | 192 +++++++++--------- .../src/lib/panes/MiscSettingsPane.tsx | 89 ++++---- .../src/lib/panes/VideoSettingsPane.tsx | 40 ++++ .../settings-modal/src/lib/settings-modal.tsx | 140 +------------ .../components/settings-modal/tsconfig.json | 12 +- .../settings-modal/tsconfig.lib.json | 12 +- frontend/libs/components/switch/.babelrc | 12 ++ frontend/libs/components/switch/README.md | 7 + frontend/libs/components/switch/package.json | 17 ++ frontend/libs/components/switch/src/index.ts | 2 + .../switch/src/lib/switch.module.css | 7 + .../components/switch/src/lib/switch.spec.tsx | 12 ++ .../libs/components/switch/src/lib/switch.tsx | 36 ++++ frontend/libs/components/switch/tsconfig.json | 13 ++ .../libs/components/switch/tsconfig.lib.json | 45 ++++ .../libs/components/switch/tsconfig.spec.json | 34 ++++ .../libs/components/switch/vite.config.ts | 49 +++++ frontend/libs/tauri-api/src/index.ts | 9 +- .../src/lib/sora/CheckSoraSession.ts | 12 +- .../src/lib/sora/LogoutSoraSession.ts | 4 + .../src/lib/sora/useSoraLoginListener.ts | 30 +++ frontend/package-lock.json | 8 + frontend/tsconfig.json | 3 + 31 files changed, 727 insertions(+), 318 deletions(-) create mode 100644 frontend/libs/components/settings-modal/src/lib/components/SoraAccountButton.tsx create mode 100644 frontend/libs/components/settings-modal/src/lib/panes/AccountSettingsPane.tsx create mode 100644 frontend/libs/components/settings-modal/src/lib/panes/VideoSettingsPane.tsx create mode 100644 frontend/libs/components/switch/.babelrc create mode 100644 frontend/libs/components/switch/README.md create mode 100644 frontend/libs/components/switch/package.json create mode 100644 frontend/libs/components/switch/src/index.ts create mode 100644 frontend/libs/components/switch/src/lib/switch.module.css create mode 100644 frontend/libs/components/switch/src/lib/switch.spec.tsx create mode 100644 frontend/libs/components/switch/src/lib/switch.tsx create mode 100644 frontend/libs/components/switch/tsconfig.json create mode 100644 frontend/libs/components/switch/tsconfig.lib.json create mode 100644 frontend/libs/components/switch/tsconfig.spec.json create mode 100644 frontend/libs/components/switch/vite.config.ts create mode 100644 frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts create mode 100644 frontend/libs/tauri-api/src/lib/sora/useSoraLoginListener.ts diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx index b0959b6c31..3b933889f1 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx @@ -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"; diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx index dc81ac2cc7..84ab4dfab5 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx @@ -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 }) => { /> - + {/* */} diff --git a/frontend/libs/components/button/src/lib/button.tsx b/frontend/libs/components/button/src/lib/button.tsx index 61dc6dc2c9..28e10d9321 100644 --- a/frontend/libs/components/button/src/lib/button.tsx +++ b/frontend/libs/components/button/src/lib/button.tsx @@ -14,7 +14,7 @@ export interface ButtonProps extends ButtonHTMLAttributes { 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"; diff --git a/frontend/libs/components/input/src/lib/input.tsx b/frontend/libs/components/input/src/lib/input.tsx index 95bebcbbbc..be46659168 100644 --- a/frontend/libs/components/input/src/lib/input.tsx +++ b/frontend/libs/components/input/src/lib/input.tsx @@ -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 diff --git a/frontend/libs/components/login-modal/src/lib/login-modal.tsx b/frontend/libs/components/login-modal/src/lib/login-modal.tsx index 951d89775d..559faf5a33 100644 --- a/frontend/libs/components/login-modal/src/lib/login-modal.tsx +++ b/frontend/libs/components/login-modal/src/lib/login-modal.tsx @@ -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" /> - {/*
- -
*/} ); case 3: @@ -255,17 +257,6 @@ export function LoginModal({ > {step === 2 ? "Login with OpenAI" : "Continue"} - {step > 1 && step !== 3 ? ( - - ) : null} ) : ( + ); +}; diff --git a/frontend/libs/components/settings-modal/src/lib/panes/AccountSettingsPane.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettingsPane.tsx new file mode 100644 index 0000000000..ff533d59d9 --- /dev/null +++ b/frontend/libs/components/settings-modal/src/lib/panes/AccountSettingsPane.tsx @@ -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({ + username: "-", + subscription: "-", + credits: 0, + }); + + const [palApiKey, setPalApiKey] = useState(""); + const [klingApiKey, setKlingApiKey] = useState(""); + + return ( + <> +
+
+ ArtCraft Account: + {accountInfo.username} +
+
+ ArtCraft Subscription: + {accountInfo.subscription} +
+
+ Credits Remaining: + {accountInfo.credits} +
+ +
+

+ 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. +

+
+ +
+ OpenAI / Sora Account: + +
+ +
+ Google / Veo Account: + +
+ +
+
+ + setPalApiKey((e.target as any).value)} + placeholder="Enter API Key" + disabled + /> +
+
+ + setKlingApiKey((e.target as any).value)} + placeholder="Enter API Key" + disabled + /> +
+
+
+ + ); +}; diff --git a/frontend/libs/components/settings-modal/src/lib/panes/AudioSettingsPane.tsx b/frontend/libs/components/settings-modal/src/lib/panes/AudioSettingsPane.tsx index d44f0529e5..100dbe999f 100644 --- a/frontend/libs/components/settings-modal/src/lib/panes/AudioSettingsPane.tsx +++ b/frontend/libs/components/settings-modal/src/lib/panes/AudioSettingsPane.tsx @@ -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(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 (<> -
-
- - setPlaySounds((e.target as any).checked)} - /> + return ( + <> +
+
+ + +
+ +
+ +
+ setFailureSound(val as string)} + options={SOUND_OPTIONS} + className="grow" + /> +
+
+ +
+ +
+ - setSuccessSound(val as string) - } - options={SOUND_OPTIONS} - /> - -
- -
- - - setEnqueueSound(val as string) - } - options={SOUND_OPTIONS} - /> - -
-
- ) -} - -const orNone = (val: string | undefined | null) : string => { +const orNone = (val: string | undefined | null): string => { if (!!!val) { return "none"; } return val; -} +}; diff --git a/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx b/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx index b9f54b5893..df9615f776 100644 --- a/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx +++ b/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx @@ -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(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 (
-
- - This is where downloads are placed after downloading. - The current path is
{currentDownloadLabel}
- - -
-
- ) -} + ); +}; diff --git a/frontend/libs/components/settings-modal/src/lib/panes/VideoSettingsPane.tsx b/frontend/libs/components/settings-modal/src/lib/panes/VideoSettingsPane.tsx new file mode 100644 index 0000000000..7148143036 --- /dev/null +++ b/frontend/libs/components/settings-modal/src/lib/panes/VideoSettingsPane.tsx @@ -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 ( +
+
+ + setHumanVideoProvider(val as string)} + options={[ + { value: "artcraft", label: "ArtCraft" }, + { value: "pal", label: "Pal" }, + ]} + /> +
+
+ ); +}; diff --git a/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx b/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx index 9c6e4a364b..39d5e764c7 100644 --- a/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx +++ b/frontend/libs/components/settings-modal/src/lib/settings-modal.tsx @@ -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({ - username: "Username [logout]", - subscription: "Pro-tier [change/upgrade]", - credits: 10233, - }); - const [selectedSection, setSelectedSection] = useState("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 ( - - ); + return ; case "misc": - return ( - - ); + return ; case "accounts": - return ( -
-
- ArtCraft Account: - {accountInfo.username} -
-
- ArtCraft Subscription: - {accountInfo.subscription} -
-
- Credits Remaining: - {accountInfo.credits} -
- -
-

- 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. -

-
- -
- OpenAI / Sora Account: - -
-
- Google / Veo Account: - -
- -
-
- - setPalApiKey((e.target as any).value)} - placeholder="Enter API Key" - /> -
-
- - setKlingApiKey((e.target as any).value)} - placeholder="Enter API Key" - /> -
-
-
- ); - + return ; case "video": - return ( -
-
- - - setHumanVideoProvider(val as string) - } - options={[ - { value: "artcraft", label: "ArtCraft" }, - { value: "pal", label: "Pal" }, - ]} - /> -
-
- ); - + return ; case "image": return (
- +
); } @@ -208,7 +84,7 @@ export const SettingsModal = ({ isOpen, onClose }: SettingsModalProps) => { ))}
-
+

{sections.find((s) => s.id === selectedSection)?.label} diff --git a/frontend/libs/components/settings-modal/tsconfig.json b/frontend/libs/components/settings-modal/tsconfig.json index a85c4f44ce..4f5c504705 100644 --- a/frontend/libs/components/settings-modal/tsconfig.json +++ b/frontend/libs/components/settings-modal/tsconfig.json @@ -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" }, diff --git a/frontend/libs/components/settings-modal/tsconfig.lib.json b/frontend/libs/components/settings-modal/tsconfig.lib.json index a0adb87070..62d1c85923 100644 --- a/frontend/libs/components/settings-modal/tsconfig.lib.json +++ b/frontend/libs/components/settings-modal/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" } ] } diff --git a/frontend/libs/components/switch/.babelrc b/frontend/libs/components/switch/.babelrc new file mode 100644 index 0000000000..1ea870ead4 --- /dev/null +++ b/frontend/libs/components/switch/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/frontend/libs/components/switch/README.md b/frontend/libs/components/switch/README.md new file mode 100644 index 0000000000..8260eb0135 --- /dev/null +++ b/frontend/libs/components/switch/README.md @@ -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/). diff --git a/frontend/libs/components/switch/package.json b/frontend/libs/components/switch/package.json new file mode 100644 index 0000000000..70e60b78ac --- /dev/null +++ b/frontend/libs/components/switch/package.json @@ -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" + } + } +} diff --git a/frontend/libs/components/switch/src/index.ts b/frontend/libs/components/switch/src/index.ts new file mode 100644 index 0000000000..3f2441cfda --- /dev/null +++ b/frontend/libs/components/switch/src/index.ts @@ -0,0 +1,2 @@ + +export * from './lib/switch'; diff --git a/frontend/libs/components/switch/src/lib/switch.module.css b/frontend/libs/components/switch/src/lib/switch.module.css new file mode 100644 index 0000000000..d0246c74b9 --- /dev/null +++ b/frontend/libs/components/switch/src/lib/switch.module.css @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ \ No newline at end of file diff --git a/frontend/libs/components/switch/src/lib/switch.spec.tsx b/frontend/libs/components/switch/src/lib/switch.spec.tsx new file mode 100644 index 0000000000..364763baec --- /dev/null +++ b/frontend/libs/components/switch/src/lib/switch.spec.tsx @@ -0,0 +1,12 @@ +import { render } from '@testing-library/react'; + +import Switch from './switch'; + +describe('Switch', () => { + + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); + +}); diff --git a/frontend/libs/components/switch/src/lib/switch.tsx b/frontend/libs/components/switch/src/lib/switch.tsx new file mode 100644 index 0000000000..5561b4eeda --- /dev/null +++ b/frontend/libs/components/switch/src/lib/switch.tsx @@ -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 ( + + {({ checked, disabled }) => ( + + )} + + ); +} + +export default Switch; diff --git a/frontend/libs/components/switch/tsconfig.json b/frontend/libs/components/switch/tsconfig.json new file mode 100644 index 0000000000..b3d19d8f5e --- /dev/null +++ b/frontend/libs/components/switch/tsconfig.json @@ -0,0 +1,13 @@ +{ + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json" +} diff --git a/frontend/libs/components/switch/tsconfig.lib.json b/frontend/libs/components/switch/tsconfig.lib.json new file mode 100644 index 0000000000..f0782e3dd8 --- /dev/null +++ b/frontend/libs/components/switch/tsconfig.lib.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" + ] +} diff --git a/frontend/libs/components/switch/tsconfig.spec.json b/frontend/libs/components/switch/tsconfig.spec.json new file mode 100644 index 0000000000..18b7de51b8 --- /dev/null +++ b/frontend/libs/components/switch/tsconfig.spec.json @@ -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" + } + ] +} diff --git a/frontend/libs/components/switch/vite.config.ts b/frontend/libs/components/switch/vite.config.ts new file mode 100644 index 0000000000..63d9fe0a36 --- /dev/null +++ b/frontend/libs/components/switch/vite.config.ts @@ -0,0 +1,49 @@ +/// +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, + } + }, +})); diff --git a/frontend/libs/tauri-api/src/index.ts b/frontend/libs/tauri-api/src/index.ts index e8f9c851bd..ff95a96ed2 100644 --- a/frontend/libs/tauri-api/src/index.ts +++ b/frontend/libs/tauri-api/src/index.ts @@ -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"; diff --git a/frontend/libs/tauri-api/src/lib/sora/CheckSoraSession.ts b/frontend/libs/tauri-api/src/lib/sora/CheckSoraSession.ts index 8d9b5831d8..69cc587692 100644 --- a/frontend/libs/tauri-api/src/lib/sora/CheckSoraSession.ts +++ b/frontend/libs/tauri-api/src/lib/sora/CheckSoraSession.ts @@ -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 => { - let result = await invoke("check_sora_session_command"); +export const CheckSoraSession = async (): Promise => { + const result = await invoke("check_sora_session_command"); console.log(">>> sora result", result); - return (result as CheckSoraSessionResult); -} + return result as CheckSoraSessionResult; +}; diff --git a/frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts b/frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts new file mode 100644 index 0000000000..c52f9ae5d0 --- /dev/null +++ b/frontend/libs/tauri-api/src/lib/sora/LogoutSoraSession.ts @@ -0,0 +1,4 @@ +export const LogoutSoraSession = async (): Promise => { + // Replace with real Tauri command to logout Sora session - BFlat + return new Promise((resolve) => setTimeout(resolve, 500)); +}; diff --git a/frontend/libs/tauri-api/src/lib/sora/useSoraLoginListener.ts b/frontend/libs/tauri-api/src/lib/sora/useSoraLoginListener.ts new file mode 100644 index 0000000000..40cb23a8d0 --- /dev/null +++ b/frontend/libs/tauri-api/src/lib/sora/useSoraLoginListener.ts @@ -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( + "sora-login-success", + (event) => { + onSuccess(event.payload); + } + ); + unlisten = stop; + }; + + setupListener(); + + return () => { + if (unlisten) unlisten(); + }; + }, [onSuccess]); +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b1ebb44995..5eab2998ff 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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 diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 32242d626e..8c9879f034 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -68,6 +68,9 @@ }, { "path": "./libs/components/login-modal" + }, + { + "path": "./libs/components/switch" } ] }