settings pane update with switch

This commit is contained in:
bflatastic committed 2025-05-08 12:33:08 +07:00
1 parent eec59c218d
commit bc2e44efdf
19 files changed
+403 -149

No files matched your search

@@ -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 }) => (
<>
@@ -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,22 @@ 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)
console.log("prefs", prefs);
setPreferences(prefs.preferences);
}
};
fetchData();
}, []);
const playSounds = preferences?.play_sounds || false;
const successSound = orNone(preferences?.generation_success_sound);
@@ -40,138 +45,129 @@ export const AudioSettingsPane = (args: AudioSettingsPaneProps) => {
const reloadPreferences = async () => {
const prefs = await GetAppPreferences();
console.log("prefs", prefs)
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>
</>)
}
);
};
@@ -54,13 +54,9 @@ 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">
@@ -170,7 +166,9 @@ export const SettingsModal = ({ isOpen, onClose }: SettingsModalProps) => {
case "image":
return (
<div>
<button className="text-blue-600">Various Image Settings (TODO)...</button>
<button className="text-blue-600">
Various Image Settings (TODO)...
</button>
</div>
);
}
@@ -208,7 +206,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,6 +5,9 @@
{
"path": "../modal"
},
{
"path": "../label"
},
{
"path": "../select"
},
@@ -46,6 +46,9 @@
{
"path": "../modal/tsconfig.lib.json"
},
{
"path": "../label/tsconfig.lib.json"
},
{
"path": "../select/tsconfig.lib.json"
},
+12
View File
@@ -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,
}
},
}));
+8
View File
@@ -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
+3
View File
@@ -68,6 +68,9 @@
},
{
"path": "./libs/components/login-modal"
},
{
"path": "./libs/components/switch"
}
]
}