Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17f6d002a0 | |||
| 1de7e5ccce | |||
| 2af92f8e16 | |||
| d878ee5737 | |||
| df35337e89 |
9 changed files with 269 additions and 186 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -3,13 +3,13 @@
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[svelte]": {
|
"[svelte]": {
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"svelte.enable-ts-plugin": true,
|
"svelte.enable-ts-plugin": true,
|
||||||
"eslint.validate": [
|
"eslint.validate": [
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -2,9 +2,15 @@
|
||||||
|
|
||||||
A soundboard with direct access to [myinstants](https://www.myinstants.com)' library.
|
A soundboard with direct access to [myinstants](https://www.myinstants.com)' library.
|
||||||
|
|
||||||
## Recommended IDE Setup
|
## Installation
|
||||||
|
|
||||||
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode)
|
To use throughmic, you need to install [vbcable](https://vb-audio.com/Cable/), then download the latest version of throughmic in the releases tabs.
|
||||||
|
Once done you need to select in the app the vbcable device. You should pick `CABLE Input (VB-Audio Virtual Cable)` with audiooutput selected. If you can't find it, check that you installed correctly VBCable.
|
||||||
|
Then you need to go to the app you want to use this with, for example discord, and you need to change the micro to `CABLE Output (VB-Audio Virtual Cable)`.
|
||||||
|
Finally to be able to speak and use the soundboard you need to go to windows sound settings and enable "listen to this device" to send your mic's output to `CABLE Input (VB-Audio Virtual Cable)`.
|
||||||
|
You should now be able to play sounds.
|
||||||
|
|
||||||
|
You don't need to use the myinstants windows, it's for developpement purposes and will probably be removed later.
|
||||||
|
|
||||||
## Project Setup
|
## Project Setup
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,186 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { _Audio } from '$lib/audio'
|
import Header from '$lib/components/Header.svelte'
|
||||||
import { Button } from '$lib/components/ui/button'
|
import Footer from '$lib/components/Footer.svelte'
|
||||||
import { Input } from '$lib/components/ui/input/'
|
import Results from '$lib/components/Results.svelte'
|
||||||
import * as Table from '$lib/components/ui/table/'
|
|
||||||
import * as Dialog from '$lib/components/ui/dialog/'
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import * as Select from '$lib/components/ui/select'
|
|
||||||
import * as RadioGroup from '$lib/components/ui/radio-group/'
|
|
||||||
import { Label } from '$lib/components/ui/label/'
|
|
||||||
|
|
||||||
type Sound = {
|
|
||||||
name: string
|
|
||||||
// author: string
|
|
||||||
// favorited: number
|
|
||||||
// viewed: number
|
|
||||||
link: string
|
|
||||||
src: string
|
|
||||||
}
|
|
||||||
let sounds: Sound[] = $state([])
|
|
||||||
|
|
||||||
let searchValue = $state('')
|
|
||||||
|
|
||||||
function debounce(func, delay) {
|
|
||||||
let timeout
|
|
||||||
return function (...args) {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
func.apply(this, args)
|
|
||||||
}, delay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function search(): Promise<void> {
|
|
||||||
sounds = await window.api.search(searchValue)
|
|
||||||
console.log(sounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dSearch = debounce(search, 500)
|
|
||||||
|
|
||||||
let sound: _Audio
|
|
||||||
function play(src: string): void {
|
|
||||||
if (!localStorage.getItem('output_device_id')) {
|
|
||||||
return alert('Please select an audio device to ouput sounds.')
|
|
||||||
}
|
|
||||||
if (sound) {
|
|
||||||
sound.stop()
|
|
||||||
}
|
|
||||||
sound = new _Audio(src)
|
|
||||||
sound.setOuputDevice(localStorage.getItem('output_device_id'))
|
|
||||||
sound.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
let showSelectDeviceDialog = $state(false)
|
|
||||||
|
|
||||||
let devices: MediaDeviceInfo[] = $state([])
|
|
||||||
|
|
||||||
let device = $state(localStorage.getItem('output_device_id'))
|
|
||||||
|
|
||||||
const triggerContent: string = $derived(
|
|
||||||
devices.find((d) => d.deviceId === device)?.label ?? 'Select a device'
|
|
||||||
)
|
|
||||||
|
|
||||||
let kinds = $state([])
|
|
||||||
|
|
||||||
let kind = $state('audiooutput')
|
|
||||||
|
|
||||||
async function updateDevices(): Promise<void> {
|
|
||||||
devices = await navigator.mediaDevices.enumerateDevices()
|
|
||||||
kinds = []
|
|
||||||
devices.forEach((d) => {
|
|
||||||
if (!kinds.includes(d.kind)) {
|
|
||||||
kinds.push(d.kind)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
devices = devices.filter((d) => d.kind === kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
sounds = await window.api.search()
|
|
||||||
updateDevices()
|
|
||||||
console.log(devices)
|
|
||||||
|
|
||||||
if (!localStorage.getItem('output_device_id')) {
|
|
||||||
showSelectDeviceDialog = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="absolute top-0 left-0 h-16 w-full">
|
<Header></Header>
|
||||||
<Input
|
|
||||||
placeholder="Search a sound"
|
|
||||||
class="absolute top-[50%] left-[50%] w-[calc(100%-64px)] translate-[-50%]"
|
|
||||||
bind:value={searchValue}
|
|
||||||
onkeyup={dSearch} />
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main
|
<Results></Results>
|
||||||
class="absolute top-16 left-[50%] h-[calc(100%-128px)] w-[calc(100%-64px)] translate-x-[-50%] overflow-y-scroll">
|
|
||||||
<Table.Root>
|
|
||||||
<!-- <Table.Header>
|
|
||||||
<Table.Row>
|
|
||||||
<Table.Head class="w-[100px]">Sound</Table.Head>
|
|
||||||
<Table.Head>Author</Table.Head>
|
|
||||||
<Table.Head>Favorited</Table.Head>
|
|
||||||
<Table.Head>Viewed</Table.Head>
|
|
||||||
<Table.Head class="text-right"></Table.Head>
|
|
||||||
<Table.Head class="text-right"></Table.Head>
|
|
||||||
</Table.Row>
|
|
||||||
</Table.Header> -->
|
|
||||||
<Table.Body>
|
|
||||||
{#each sounds as sound (sound)}
|
|
||||||
<Table.Row>
|
|
||||||
<Table.Cell class="font-medium"
|
|
||||||
><Button variant="link" href={sound.link} target="_blank">{sound.name}</Button
|
|
||||||
></Table.Cell>
|
|
||||||
<!-- <Table.Cell>{sound.author}</Table.Cell>
|
|
||||||
<Table.Cell>{sound.favorited}</Table.Cell>
|
|
||||||
<Table.Cell>{sound.viewed}</Table.Cell> -->
|
|
||||||
<Table.Cell class="text-right">
|
|
||||||
<Button variant="link">Add to favorites</Button></Table.Cell>
|
|
||||||
<Table.Cell class="text-right">
|
|
||||||
<Button
|
|
||||||
class="mr-4"
|
|
||||||
onclick={() => {
|
|
||||||
play(sound.src)
|
|
||||||
}}>Play</Button>
|
|
||||||
</Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
{/each}
|
|
||||||
</Table.Body>
|
|
||||||
</Table.Root>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="absolute bottom-0 left-0 h-16 w-full">
|
<Footer></Footer>
|
||||||
<Dialog.Root open={showSelectDeviceDialog}>
|
|
||||||
<Dialog.Trigger
|
|
||||||
><Button
|
|
||||||
class="absolute top-[50%] right-8 w-fit translate-y-[-50%]"
|
|
||||||
onclick={() => {
|
|
||||||
showSelectDeviceDialog = true
|
|
||||||
}}>Select audio device</Button
|
|
||||||
></Dialog.Trigger>
|
|
||||||
<Dialog.Content class="max-w-[425px]">
|
|
||||||
<Dialog.Header>
|
|
||||||
<Dialog.Title>Select audio device</Dialog.Title>
|
|
||||||
<Dialog.Description>Usually you should select "CABLE Input"</Dialog.Description>
|
|
||||||
</Dialog.Header>
|
|
||||||
<RadioGroup.Root bind:value={kind} onValueChange={updateDevices}>
|
|
||||||
{#each kinds as kind (kind)}
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<RadioGroup.Item value={kind} />
|
|
||||||
<Label for="r1">{kind}</Label>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</RadioGroup.Root>
|
|
||||||
<Select.Root type="single" name="device" bind:value={device}>
|
|
||||||
<Select.Trigger class="w-full">
|
|
||||||
{triggerContent}
|
|
||||||
</Select.Trigger>
|
|
||||||
<Select.Content>
|
|
||||||
<Select.Group>
|
|
||||||
<Select.Label>Devices</Select.Label>
|
|
||||||
{#each devices as device (`${device.deviceId}`)}
|
|
||||||
<Select.Item value={device.deviceId} label={device.label}>
|
|
||||||
{device.label}
|
|
||||||
</Select.Item>
|
|
||||||
{/each}
|
|
||||||
</Select.Group>
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root>
|
|
||||||
<Dialog.Footer>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
class="w-full"
|
|
||||||
onclick={() => {
|
|
||||||
console.log('save')
|
|
||||||
if (device !== '') {
|
|
||||||
localStorage.setItem('output_device_id', device)
|
|
||||||
} else localStorage.removeItem('output_device_id')
|
|
||||||
showSelectDeviceDialog = false
|
|
||||||
}}>Save</Button>
|
|
||||||
</Dialog.Footer>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Root>
|
|
||||||
</footer>
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,29 @@
|
||||||
export class _Audio {
|
export class _Audio {
|
||||||
src: string
|
src: string
|
||||||
audio: HTMLAudioElement
|
audio: HTMLAudioElement
|
||||||
|
secondaryAudio: HTMLAudioElement
|
||||||
constructor(src: string) {
|
constructor(src: string) {
|
||||||
this.src = src
|
this.src = src
|
||||||
this.audio = new Audio(src)
|
this.audio = new Audio(src)
|
||||||
|
this.secondaryAudio = new Audio(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
play() {
|
play() {
|
||||||
this.audio.play()
|
this.audio.play()
|
||||||
|
this.secondaryAudio.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.audio.pause()
|
this.audio.pause()
|
||||||
|
this.secondaryAudio.pause()
|
||||||
this.audio.remove()
|
this.audio.remove()
|
||||||
|
this.secondaryAudio.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
setOuputDevice(id: string) {
|
setMainOuputDevice(id: string) {
|
||||||
this.audio.setSinkId(id)
|
this.audio.setSinkId(id)
|
||||||
}
|
}
|
||||||
|
setSecondaryOuputDevice(id: string) {
|
||||||
|
this.secondaryAudio.setSinkId(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
130
src/renderer/src/lib/components/DevicesSelector.svelte
Normal file
130
src/renderer/src/lib/components/DevicesSelector.svelte
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as Dialog from '$lib/components/ui/dialog/'
|
||||||
|
import { Button } from '$lib/components/ui/button'
|
||||||
|
import * as Select from '$lib/components/ui/select'
|
||||||
|
import * as RadioGroup from '$lib/components/ui/radio-group/'
|
||||||
|
import { Label } from '$lib/components/ui/label/'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
let showSelectDeviceDialog = $state(false)
|
||||||
|
|
||||||
|
let devices: MediaDeviceInfo[] = $state([])
|
||||||
|
|
||||||
|
let mainDevice = $state(localStorage.getItem('output_main_device_id'))
|
||||||
|
let secondaryDevice = $state(localStorage.getItem('output_secondary_device_id'))
|
||||||
|
|
||||||
|
const mainTriggerContent: string = $derived(
|
||||||
|
devices.find((d) => d.deviceId === mainDevice)?.label ?? 'Select a device'
|
||||||
|
)
|
||||||
|
|
||||||
|
const secondaryTriggerContent: string = $derived(
|
||||||
|
devices.find((d) => d.deviceId === secondaryDevice)?.label ?? 'Select a device'
|
||||||
|
)
|
||||||
|
|
||||||
|
let kinds = $state([])
|
||||||
|
|
||||||
|
let mainKind = $state('audiooutput')
|
||||||
|
let secondaryKind = $state('audiooutput')
|
||||||
|
|
||||||
|
async function updateDevices(): Promise<void> {
|
||||||
|
devices = await navigator.mediaDevices.enumerateDevices()
|
||||||
|
kinds = []
|
||||||
|
devices.forEach((d) => {
|
||||||
|
if (!kinds.includes(d.kind)) {
|
||||||
|
kinds.push(d.kind)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
devices = devices.filter((d) => d.kind === mainKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
updateDevices()
|
||||||
|
console.log(devices)
|
||||||
|
|
||||||
|
if (!localStorage.getItem('output_main_device_id')) {
|
||||||
|
showSelectDeviceDialog = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root open={showSelectDeviceDialog}>
|
||||||
|
<Dialog.Trigger
|
||||||
|
><Button
|
||||||
|
class="absolute top-[50%] right-8 w-fit translate-y-[-50%]"
|
||||||
|
onclick={() => {
|
||||||
|
showSelectDeviceDialog = true
|
||||||
|
}}>Select audio devices</Button
|
||||||
|
></Dialog.Trigger>
|
||||||
|
<Dialog.Content class="max-w-[425px]">
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>Select main audio device</Dialog.Title>
|
||||||
|
<Dialog.Description>Usually you should select "CABLE Input"</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
<RadioGroup.Root bind:value={mainKind} onValueChange={updateDevices}>
|
||||||
|
{#each kinds as kind (kind)}
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroup.Item value={kind} />
|
||||||
|
<Label for="r1">{kind}</Label>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</RadioGroup.Root>
|
||||||
|
<Select.Root type="single" name="device" bind:value={mainDevice}>
|
||||||
|
<Select.Trigger class="w-full">
|
||||||
|
{mainTriggerContent}
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content>
|
||||||
|
<Select.Group>
|
||||||
|
<Select.Label>Devices</Select.Label>
|
||||||
|
{#each devices as device (`${device.deviceId}`)}
|
||||||
|
<Select.Item value={device.deviceId} label={device.label}>
|
||||||
|
{device.label}
|
||||||
|
</Select.Item>
|
||||||
|
{/each}
|
||||||
|
</Select.Group>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
<br />
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>Select secondary audio device</Dialog.Title>
|
||||||
|
<Dialog.Description>Usually you should your headset</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
<RadioGroup.Root bind:value={secondaryKind} onValueChange={updateDevices}>
|
||||||
|
{#each kinds as secondaryKind (secondaryKind)}
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroup.Item value={secondaryKind} />
|
||||||
|
<Label for="r1">{secondaryKind}</Label>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</RadioGroup.Root>
|
||||||
|
<Select.Root type="single" name="device" bind:value={secondaryDevice}>
|
||||||
|
<Select.Trigger class="w-full">
|
||||||
|
{secondaryTriggerContent}
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content>
|
||||||
|
<Select.Group>
|
||||||
|
<Select.Label>Devices</Select.Label>
|
||||||
|
{#each devices as device (`${device.deviceId}`)}
|
||||||
|
<Select.Item value={device.deviceId} label={device.label}>
|
||||||
|
{device.label}
|
||||||
|
</Select.Item>
|
||||||
|
{/each}
|
||||||
|
</Select.Group>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
class="w-full"
|
||||||
|
onclick={() => {
|
||||||
|
console.log('save')
|
||||||
|
if (mainDevice !== '') {
|
||||||
|
localStorage.setItem('output_main_device_id', mainDevice)
|
||||||
|
} else localStorage.removeItem('output_main_device_id')
|
||||||
|
if (secondaryDevice !== '') {
|
||||||
|
localStorage.setItem('output_secondary_device_id', secondaryDevice)
|
||||||
|
} else localStorage.removeItem('output_secondary_device_id')
|
||||||
|
showSelectDeviceDialog = false
|
||||||
|
}}>Save</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
7
src/renderer/src/lib/components/Footer.svelte
Normal file
7
src/renderer/src/lib/components/Footer.svelte
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
import DevicesSelector from './DevicesSelector.svelte'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<footer class="absolute bottom-0 left-0 h-16 w-full">
|
||||||
|
<DevicesSelector></DevicesSelector>
|
||||||
|
</footer>
|
||||||
30
src/renderer/src/lib/components/Header.svelte
Normal file
30
src/renderer/src/lib/components/Header.svelte
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Input } from '$lib/components/ui/input/'
|
||||||
|
import { states } from '$lib/states.svelte'
|
||||||
|
|
||||||
|
let searchValue = $state('')
|
||||||
|
|
||||||
|
function debounce(func, delay) {
|
||||||
|
let timeout
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
func.apply(this, args)
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function search(): Promise<void> {
|
||||||
|
states.sounds = await window.api.search(searchValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dSearch = debounce(search, 500)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header class="absolute top-0 left-0 h-16 w-full">
|
||||||
|
<Input
|
||||||
|
placeholder="Search a sound"
|
||||||
|
class="absolute top-[50%] left-[50%] w-[calc(100%-64px)] translate-[-50%]"
|
||||||
|
bind:value={searchValue}
|
||||||
|
onkeyup={dSearch} />
|
||||||
|
</header>
|
||||||
63
src/renderer/src/lib/components/Results.svelte
Normal file
63
src/renderer/src/lib/components/Results.svelte
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Button } from '$lib/components/ui/button'
|
||||||
|
import * as Table from '$lib/components/ui/table/'
|
||||||
|
import { _Audio } from '$lib/audio'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import { states } from '$lib/states.svelte'
|
||||||
|
|
||||||
|
let sound: _Audio
|
||||||
|
function play(src: string): void {
|
||||||
|
if (!localStorage.getItem('output_main_device_id')) {
|
||||||
|
return alert('Please select an audio device to ouput sounds.')
|
||||||
|
}
|
||||||
|
if (sound) {
|
||||||
|
sound.stop()
|
||||||
|
}
|
||||||
|
sound = new _Audio(src)
|
||||||
|
sound.setMainOuputDevice(localStorage.getItem('output_main_device_id'))
|
||||||
|
if (localStorage.getItem('output_secondary_device_id')) {
|
||||||
|
sound.setSecondaryOuputDevice(localStorage.getItem('output_secondary_device_id'))
|
||||||
|
}
|
||||||
|
sound.play()
|
||||||
|
}
|
||||||
|
onMount(async () => {
|
||||||
|
states.sounds = await window.api.search()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main
|
||||||
|
class="absolute top-16 left-[50%] h-[calc(100%-128px)] w-[calc(100%-64px)] translate-x-[-50%] overflow-y-scroll">
|
||||||
|
<Table.Root>
|
||||||
|
<!-- <Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Head class="w-[100px]">Sound</Table.Head>
|
||||||
|
<Table.Head>Author</Table.Head>
|
||||||
|
<Table.Head>Favorited</Table.Head>
|
||||||
|
<Table.Head>Viewed</Table.Head>
|
||||||
|
<Table.Head class="text-right"></Table.Head>
|
||||||
|
<Table.Head class="text-right"></Table.Head>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header> -->
|
||||||
|
<Table.Body>
|
||||||
|
{#each states.sounds as sound (sound)}
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell class="font-medium"
|
||||||
|
><Button variant="link" href={sound.link} target="_blank">{sound.name}</Button
|
||||||
|
></Table.Cell>
|
||||||
|
<!-- <Table.Cell>{sound.author}</Table.Cell>
|
||||||
|
<Table.Cell>{sound.favorited}</Table.Cell>
|
||||||
|
<Table.Cell>{sound.viewed}</Table.Cell> -->
|
||||||
|
<Table.Cell class="text-right">
|
||||||
|
<Button variant="link">Add to favorites</Button></Table.Cell>
|
||||||
|
<Table.Cell class="text-right">
|
||||||
|
<Button
|
||||||
|
class="mr-4"
|
||||||
|
onclick={() => {
|
||||||
|
play(sound.src)
|
||||||
|
}}>Play</Button>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</main>
|
||||||
14
src/renderer/src/lib/states.svelte.ts
Normal file
14
src/renderer/src/lib/states.svelte.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
type Sound = {
|
||||||
|
name: string
|
||||||
|
// author: string
|
||||||
|
// favorited: number
|
||||||
|
// viewed: number
|
||||||
|
link: string
|
||||||
|
src: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type States = { sounds: Sound[] }
|
||||||
|
|
||||||
|
export const states: States = $state({
|
||||||
|
sounds: []
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue