add settings & about panes
This commit is contained in:
parent
2a70d5300a
commit
70d1af07c7
|
@ -6,3 +6,7 @@
|
|||
.dark body {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
.meta-button {
|
||||
@apply btn btn-md variant-ringed-surface;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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`);
|
||||
|
|
|
@ -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={{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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()],
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue