add settings & about panes

This commit is contained in:
Emerald 2023-10-26 18:03:15 -04:00
parent 2a70d5300a
commit 70d1af07c7
Signed by: emerald
GPG Key ID: 13F7EFB915A0F623
15 changed files with 276 additions and 189 deletions

View File

@ -6,3 +6,7 @@
.dark body {
@apply bg-transparent;
}
.meta-button {
@apply btn btn-md variant-ringed-surface;
}

View File

@ -70,7 +70,8 @@ const webAudio = async () => {
// return setInterval(monitor);
} catch (e) {
console.error("Failed to get microphone: ", e);
// console.error("Failed to get microphone: ", e);
throw e;
//TODO: toast here
}
} else {

View File

@ -0,0 +1,33 @@
<script lang="ts">
import { version } from '$app/environment';
import { mode } from '$lib/store';
</script>
<section class="grid grid-rows-[auto_1fr_auto] gap-2 h-full">
<header>
<h1 class="h1"><strong>Cathode {mode === 'web' ? 'Web' : 'Desktop'}</strong></h1>
<p>Version v{version}</p>
</header>
<main>
<p>
Powered by <a href="https://kit.svelte.dev" class="anchor" target="_blank">SvelteKit</a>{mode === 'tauri'
? ', '
: ' and '}
<a href="https://skeleton.dev" class="anchor" target="_blank">Skeleton</a>
{#if mode === 'tauri'}
<span>, and <a href="https://tauri.app" class="anchor" target="_blank">Tauri</a></span>
{/if}
</p>
<p>
Source code is available <a href="https://codeberg.org/anactualemerald/cathode" class="anchor" target="_blank">here</a>
</p>
</main>
<footer>
<p><strong>Cathode</strong> ©2023 Emerald</p>
<p>This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; For details, see the <a href="https://codeberg.org/AnActualEmerald/cathode/src/branch/main/LICENSE" class="anchor">license</a>.</p>
</footer>
</section>

View File

@ -106,9 +106,15 @@
loading = false;
};
const handleCancel = () => {
if(loading && fileInput?.value.length === 0) loading = false;
};
let fileInput: HTMLInputElement;
</script>
<svelte:window on:focus={handleCancel} />
{#if mode === 'web'}
<input
bind:this={fileInput}
@ -124,7 +130,7 @@
<button
class="btn btn-xl p-4 w-24 md:w-32 variant-ghost-primary aspect-square cursor-pointer z-50"
aria-label="Preview of frame {index + 1}"
on:click={(e) => {
if (e.shiftKey) {
clearImage();

View File

@ -65,4 +65,4 @@
<input type="file" class="hidden" bind:this={fileLoader} bind:files on:change={handleWebLoad} />
{/if}
<button class="btn btn-md variant-ringed-surface" on:click={handleLoad}>Load</button>
<button class="meta-button" on:click={handleLoad}>Load</button>

View File

@ -44,4 +44,4 @@
};
</script>
<button class="btn btn-md variant-ringed-surface" on:click={saveCurrent}> Save </button>
<button class="meta-button" on:click={saveCurrent}> Save </button>

View File

@ -0,0 +1,20 @@
<script lang="ts">
import { getModalStore, type ModalSettings } from "@skeletonlabs/skeleton";
import SettingsModal from "./SettingsModal.svelte";
const modal = getModalStore();
const onClick = () => {
const settings: ModalSettings = {
type: "component",
component: {
ref: SettingsModal,
}
};
modal.trigger(settings);
}
</script>
<button class="meta-button" on:click={onClick}>Settings</button>

View File

@ -0,0 +1,23 @@
<script lang="ts">
import { Tab, TabGroup } from '@skeletonlabs/skeleton';
import SettingsPane from './SettingsPane.svelte';
import AboutPane from './AboutPane.svelte';
let currTab = 0;
</script>
<TabGroup class="h-[50vh] w-modal card p-4 flex flex-col" regionPanel="flex-1 flex-col">
<Tab bind:group={currTab} value={0} name="settings">
<strong>Settings</strong>
</Tab>
<Tab bind:group={currTab} value={1} name="about">
<strong>About</strong>
</Tab>
<svelte:fragment slot="panel">
{#if currTab === 0}
<SettingsPane />
{:else if currTab === 1}
<AboutPane />
{/if}
</svelte:fragment>
</TabGroup>

View File

@ -0,0 +1,64 @@
<script lang="ts">
import { config, mode, type BGColor } from '$lib/store';
import { slide } from 'svelte/transition';
</script>
<section id="settings" class="w-1/2 flex flex-col gap-4">
<label>
Mic Sens
<div class="grid grid-cols-[0.75fr_0.25fr] gap-2">
<input
type="range"
name="MicSens"
id="mic-sens"
bind:value={$config.mic_sens}
class="range"
max={5.0}
step={0.1}
/>
<input
type="number"
name="MicSens"
id="mic-sens"
bind:value={$config.mic_sens}
class="input"
max={5.0}
step={0.1}
/>
</div>
</label>
<label>
Minimum Blink Interval (milliseconds)
<input
type="number"
name="MicSens"
id="mic-sens"
bind:value={$config.blink_interval}
class="input"
/>
</label>
<label>
Background Color
<select class="select" bind:value={$config.background_color.color}>
<option value="bg-transparent" disabled={mode === 'web'}>Transparent</option>
<option value="bg-green-500">Green</option>
<option value="bg-blue-500">Blue</option>
<option value="bg-pink-500">Pink</option>
<option value="custom">Custom</option>
</select>
{#if $config.background_color.color === 'custom'}
<div class="grid grid-cols-[auto_1fr] gap-2 my-4" transition:slide>
<input class="input" type="color" bind:value={$config.background_color.custom} />
<input
class="input"
type="text"
bind:value={$config.background_color.custom}
readonly
tabindex="-1"
/>
</div>
{/if}
</label>
</section>

View File

@ -1,126 +0,0 @@
<script lang="ts">
import { config } from "../store";
import { createEventDispatcher } from "svelte";
import { invoke } from "@tauri-apps/api";
const dispatch = createEventDispatcher();
let org = $config;
$: {
invoke("set_mic_sens", { sens: $config.mic_sens }).catch(
async (e) => await invoke("log", { msg: JSON.stringify(e) })
);
}
const onClose = () => {
if (org != $config) {
invoke("set_blink_interval", { value: $config.blink_interval }).catch();
org = $config;
}
dispatch("close");
};
</script>
<div class="settings">
<button class="close" on:click={onClose}>X</button>
<div class="setting">
<label for="bg-color">Background color:</label>
<select bind:value={$config.background_color} id="bg-color">
<option value="transparent">Transparent</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="red">Red</option>
<option value="pink">Pink</option>
</select>
</div>
<div class="setting">
<label for="blink">Blink interval: </label>
<input
value={$config.blink_interval}
on:input={(e) => {
$config.blink_interval = e.currentTarget.valueAsNumber;
}}
id="blink"
type="number"
min="0"
/>
</div>
<div class="setting">
<label for="sens">Mic sensitivity: </label>
<input
type="range"
id="sens"
step="0.01"
min="0.01"
max="5.0"
bind:value={$config.mic_sens}
/>
<span>{Math.trunc($config.mic_sens * 100)}%</span>
</div>
<button
id="save"
on:click={async () => {
await invoke("save_current_config");
onClose();
}}>Save</button
>
</div>
<style lang="scss">
.settings {
position: absolute;
transform: translateX(-50%) translateY(-50%);
top: 50%;
left: 50%;
z-index: 1000;
width: 90%;
height: 90%;
display: flex;
flex-direction: column;
gap: 5vh;
background-color: rgba(63, 176, 252, 0.9);
border-radius: 15px;
border: solid black 2px;
padding: 20px;
label {
font-weight: bold;
}
.close {
&:focus {
border: solid white 2px;
}
position: absolute;
right: 15px;
top: 15px;
font-size: 20px;
cursor: pointer;
width: 2rem;
height: 2rem;
background-color: black;
color: white;
border: solid black 1px;
text-align: center;
vertical-align: center;
border-radius: 5px;
}
}
.setting {
display: flex;
align-content: center;
justify-content: space-between;
width: 50%;
}
#save {
position: absolute;
right: 0.25rem;
bottom: 0.25rem;
width: 25%;
height: 5rem;
}
</style>

View File

@ -1,56 +1,67 @@
import {writable} from "svelte/store";
import type { Image } from "image-js";
import type { Gif } from "$lib/gifler";
import { info } from "$lib/logging";
import { TAURI_MODE } from "$env/static/public";
export type BGColor ="transparent" | "blue" | "green" | "pink" | {custom: string} ;
import { writable, type Writable } from 'svelte/store';
import type { Image } from 'image-js';
import type { Gif } from '$lib/gifler';
import { info } from '$lib/logging';
import { TAURI_MODE } from '$env/static/public';
import { localStorageStore } from '@skeletonlabs/skeleton';
export type Meta = {
threshold: string | null;
closeThreshold: string | null;
};
export const mode = TAURI_MODE === 'tauri' ? 'tauri' : 'web';
export class WebRay {
meta: Meta;
frames: Array<Frame | null>;
public constructor(
frames: Array<Frame | null> = [null, null, null, null],
meta: Meta = { threshold: null, closeThreshold: null }
) {
this.frames = frames;
this.meta = meta;
}
export type BGColor = {
color: 'bg-transparent' | 'bg-blue-500' | 'bg-green-500' | 'bg-pink-500' | 'custom';
custom?: string;
};
export type Meta = {
threshold: string | null;
closeThreshold: string | null;
};
export class WebRay {
meta: Meta;
frames: Array<Frame | null>;
public constructor(
frames: Array<Frame | null> = [null, null, null, null],
meta: Meta = { threshold: null, closeThreshold: null }
) {
this.frames = frames;
this.meta = meta;
}
}
export type Frame = {
kind: "png" | "gif",
data: string
}
kind: 'png' | 'gif';
data: string;
};
export type FrameData = {
kind: "still",
value: Image,
} | {
kind: "GIF",
value: Gif
data: ArrayLike<number>
}
export type FrameData =
| {
kind: 'still';
value: Image;
}
| {
kind: 'GIF';
value: Gif;
data: ArrayLike<number>;
};
export class Config {
background_color: BGColor;
blink_interval: number;
mic_sens: number;
background_color: BGColor;
blink_interval: number;
mic_sens: number;
constructor(){
this.background_color = "transparent";
this.blink_interval = 1500;
this.mic_sens = 1.0;
}
constructor() {
this.background_color = { color: mode === 'tauri' ? 'bg-transparent' : 'bg-green-500' };
this.blink_interval = 1500;
this.mic_sens = 1.0;
}
}
export let frames = writable<Array<FrameData | null>>([null, null, null, null]);
export let config = writable(new Config());
export const mode = TAURI_MODE === "tauri" ? "tauri" : "web";
export let frames: Writable<Array<FrameData | null>> =
mode === 'web'
? localStorageStore('cathode-frames', [null, null, null, null])
: writable([null, null, null, null]);
export let config: Writable<Config> =
mode === 'web' ? localStorageStore('cathode-config', new Config()) : writable(new Config());
export let version = writable("");
info(`Running app in ${mode} mode`);

View File

@ -6,18 +6,32 @@
// Floating UI for Popups
import { computePosition, autoUpdate, flip, shift, offset, arrow } from '@floating-ui/dom';
import { Toast, storePopup } from '@skeletonlabs/skeleton';
import {
Modal,
Toast,
initializeStores,
storePopup,
type ModalSettings,
getToastStore,
getModalStore
} from '@skeletonlabs/skeleton';
import { fly } from 'svelte/transition';
import Bar from '$lib/components/bar.svelte';
import Bar from '$lib/components/Bar.svelte';
import { onDestroy, onMount } from 'svelte';
import { active, initState, closeThreshold, activationLevel, transparent } from '$lib/state';
import SaveButton from '$lib/components/SaveButton.svelte';
import LoadButton from '$lib/components/LoadButton.svelte';
import { frames } from "$lib/store"
import { initializeToastStore } from '@skeletonlabs/skeleton/dist/utilities/Toast/stores';
import { config, frames, version } from '$lib/store';
import SettingsButton from '$lib/components/SettingsButton.svelte';
import { debug } from '$lib/logging';
import type { LayoutData } from './$types';
storePopup.set({ computePosition, autoUpdate, flip, shift, offset, arrow });
initializeToastStore();
initializeStores();
const modal = getModalStore();
const toast = getToastStore();
// import { pwaInfo } from 'virtual:pwa-info';
@ -25,7 +39,11 @@
let deinitAudio: NodeJS.Timeout | undefined = undefined;
onMount(async () => {
deinitAudio = await initAudio();
try {
deinitAudio = await initAudio();
} catch (e) {
toast.trigger({ message: 'Error starting audio worker', background: 'variant-ghost-error' });
}
await initState();
});
@ -34,10 +52,25 @@
});
const clearFrames = () => {
frames.update(() => [null, null, null, null]);
}
const settings: ModalSettings = {
type: 'confirm',
title: 'Create new Ray?',
body: 'This will clear the currently loaded Ray',
response(ok) {
if (ok) frames.update(() => [null, null, null, null]);
}
};
$: bgColor = $transparent ? 'bg-green-800' : 'bg-surface-900';
if (!$frames.every((v) => !v)) modal.trigger(settings);
};
$: solidColor = 'bg-surface-900';
// $config.background_color.color === 'bg-transparent'
// ? 'bg-surface-900'
// : $config.background_color.color === "custom" ? undefined : $config.background_color.color;
$: transparentColor =
$config.background_color.color === 'custom' ? undefined : $config.background_color.color;
$: bgColor = $transparent ? transparentColor : solidColor;
</script>
<!-- <svelte:head>
@ -45,8 +78,15 @@
</svelte:head> -->
<Toast />
<Modal />
<div class="{bgColor} w-screen h-screen" id="container">
<div
class="{bgColor} w-screen h-screen"
style:background-color={$config.background_color.color === 'custom' && $transparent
? $config.background_color.custom
: undefined}
id="container"
>
<slot />
{#if !$transparent}
<div
@ -58,9 +98,10 @@
}}
class="flex w-full justify-center absolute top-4 gap-4"
>
<button class="btn btn-md variant-ringed-surface" on:click={clearFrames}>New</button>
<button class="meta-button" on:click={clearFrames}>New</button>
<SaveButton />
<LoadButton />
<SettingsButton />
</div>
<div
transition:fly={{

View File

@ -1,6 +1,11 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/kit/vite';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
const file = fileURLToPath(new URL('package.json', import.meta.url));
const json = readFileSync(file, 'utf8');
const pkg = JSON.parse(json);
/** @type {import('@sveltejs/kit').Config} */
const config = {
@ -19,6 +24,9 @@ const config = {
env: {
publicPrefix: 'TAURI_'
},
version: {
name: pkg.version
}
}
};

View File

@ -1,10 +1,12 @@
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
import { sveltekit } from '@sveltejs/kit/vite';
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
import { SvelteKitPWA } from "@vite-pwa/sveltekit";
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
import { SvelteKitPWA } from '@vite-pwa/sveltekit';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [wasm(), topLevelAwait(), sveltekit(), purgeCss()]
plugins: [wasm(), topLevelAwait(), sveltekit(), purgeCss()],
});