cathode/src/views/main.svelte

249 lines
5.5 KiB
Svelte

<script lang="ts" context="module">
import { writable, type Writable } from "svelte/store";
const transparent = writable(false);
const threshold: Writable<number> = writable(0.5);
const level = writable(0);
type WebRay = {
frames: Array<string>;
meta: {
threshold: string | undefined;
closeThreshold: string | undefined;
};
};
</script>
<script lang="ts">
import { appWindow, PhysicalSize } from "@tauri-apps/api/window";
import { onMount, onDestroy } from "svelte";
import { fly } from "svelte/transition";
import FramePreview from "../components/FramePreview.svelte";
import Tuber from "../components/tube.svelte";
import { invoke } from "@tauri-apps/api/tauri";
import Bar from "../components/bar.svelte";
import { tick } from "svelte";
import { frames } from "../store";
import { quintInOut } from "svelte/easing";
let monitorTimer: NodeJS.Timer;
let active = false;
let activation = 0;
let closeThreshold = 75;
$: {
invoke("set_mic_threshold", { threshold: $threshold as number })
.then()
.catch(async (e) => {
await invoke("log", { msg: `Error setting mic threshold: ${e}` });
});
}
const openRay = (ray: WebRay) => {
for (let i = 0; i < 4; i++) {
$frames[i] = ray.frames[i];
}
if (ray.meta.threshold) {
$threshold = parseFloat(ray.meta.threshold);
}
if (ray.meta.closeThreshold) {
closeThreshold = parseFloat(ray.meta.closeThreshold);
}
};
onMount(async () => {
await appWindow.setMinSize(new PhysicalSize(720, 600));
await appWindow.onFocusChanged(({ payload: focused }) => {
$transparent = !focused;
});
await appWindow.listen("load-ray", (e) => {
openRay(e.payload as WebRay);
});
monitorTimer = setInterval(async () => {
if (!$transparent) {
$level = await invoke("get_audio_level");
await tick();
}
}, 40);
$threshold = await invoke("get_mic_threshold");
});
onDestroy(() => {
if (monitorTimer) {
clearInterval(monitorTimer);
monitorTimer = undefined;
}
});
const saveRay = async () => {
let fr = new Array<string>(4);
for (let i = 0; i < 4; i++) {
$frames[i] ? (fr[i] = $frames[i]) : (fr[i] = "");
}
fr.map((e) => (e ? e : ""));
const ray = {
frames: [fr[0], fr[1], fr[2], fr[3]],
meta: {
threshold: $threshold.toString(),
closeThreshold: closeThreshold.toString(),
},
};
// await invoke("log", { msg: `Saving ray: ${JSON.stringify(ray)}` });
invoke("save_ray", { ray })
.then()
.catch((e) => invoke("log", { msg: e }).then());
};
const loadRay = async () => {
const ray: WebRay = await invoke("open_ray");
openRay(ray);
};
</script>
<div class="container" class:transparent={$transparent}>
<Tuber
bind:open={active}
bind:buf={activation}
bind:threshold={closeThreshold}
/>
{#if !$transparent}
<div
transition:fly={{
duration: 200,
x: -200,
opacity: 100,
easing: quintInOut,
}}
class="frames"
>
{#each [0, 1, 2, 3] as i}
<FramePreview index={i} />
{/each}
</div>
<div
transition:fly={{
duration: 200,
x: 200,
opacity: 100,
easing: quintInOut,
}}
class="audio"
>
{#key $threshold}
<Bar
progress={$level * 100}
setpoint={$threshold * 100}
withSetpoint
onSetpointChange={async (e) => {
$threshold = e;
}}
--bar-color={active ? "lightgreen" : "blue"}
><img src="mic.svg" alt="microphone" /></Bar
>
{/key}
{#key closeThreshold}
<Bar
withSetpoint
progress={activation}
setpoint={closeThreshold}
onSetpointChange={(y) => (closeThreshold = y * 100)}
><img src="mouth.svg" alt="mouth" /></Bar
>
{/key}
</div>
<div
transition:fly={{
duration: 200,
y: -100,
opacity: 100,
easing: quintInOut,
}}
class="buttons"
>
<div on:click={saveRay}>Save</div>
<div on:click={loadRay}>Load</div>
</div>
{/if}
</div>
<svelte:body on:contextmenu|preventDefault />
<style lang="scss">
.buttons {
position: absolute;
top: 2vh;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-items: center;
gap: 5vh;
div {
color: white;
padding: 10px;
border-radius: 5px;
background-color: rgba(0.5, 0.5, 0.5, 0.5);
border: solid black 2px;
user-select: none;
cursor: pointer;
&:hover {
background-color: rgba(0.9, 0.9, 0.9, 0.9);
}
}
}
.frames {
align-items: left;
position: absolute;
top: 5vh;
left: 20px;
bottom: 5vh;
display: flex;
justify-content: space-between;
flex-direction: column;
}
.audio {
position: absolute;
display: flex;
gap: 2.5vh;
bottom: 5vh;
top: 5vh;
right: 20px;
}
@keyframes fade-out {
0% {
background-color: inherit;
}
100% {
background-color: transparent;
}
}
@keyframes fade-in {
0% {
background-color: transparent;
}
100% {
background-color: inherit;
}
}
.container {
background-color: inherit;
animation-name: fade-in;
animation-duration: 0.2s;
}
.container.transparent {
animation-name: fade-out;
animation-duration: 0.2s;
background-color: transparent;
}
</style>