Skip to content

Commit

Permalink
feat(web): update all stores to use runes
Browse files Browse the repository at this point in the history
Signed-off-by: Jordan Shatford <[email protected]>
  • Loading branch information
jordanshatford committed Jan 23, 2025
1 parent 274cab5 commit 0a46ee0
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 352 deletions.
3 changes: 1 addition & 2 deletions apps/web/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { browser } from '$app/environment';
import { env } from '$lib/config';
import { downloads } from '$lib/stores/downloads';
import { downloads } from '$lib/stores/downloads.svelte';

import { client, getSession, getSessionValidate } from '@yd/client';
import { toasts } from '@yd/ui';
Expand Down Expand Up @@ -45,7 +45,6 @@ export async function setupSession(): Promise<void> {
}
}
}
downloads.setupStatusListener();
await downloads.init();
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/components/DownloadActions.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { downloads } from '$lib/stores/downloads';
import { downloads } from '$lib/stores/downloads.svelte';
import type { Download } from '@yd/client';
import { ActionIcon, ArrowPathIcon, ButtonGroup, Confirm, DownloadIcon, TrashIcon } from '@yd/ui';
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/lib/components/ResultCard.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import StateIcon from '$lib/components/StateIcon.svelte';
import { downloads } from '$lib/stores/downloads';
import { downloads } from '$lib/stores/downloads.svelte';
import type { Video } from '@yd/client';
import { Card, IconButton, PlusIcon } from '@yd/ui';
Expand Down Expand Up @@ -45,14 +45,14 @@
{result.channel.name}
</p>
</a>
{#if !(result.id in $downloads)}
{#if !(result.id in downloads.downloads)}
<IconButton
onclick={() => downloads.add(result)}
src={PlusIcon}
class="hover:text-brand-600 dark:hover:text-brand-600 h-8 w-8 p-1 text-black hover:cursor-pointer dark:text-white"
/>
{:else}
<StateIcon state={$downloads[result.id].status.state} />
<StateIcon state={downloads.downloads[result.id].status.state} />
{/if}
</footer>
</Card>
127 changes: 127 additions & 0 deletions apps/web/src/lib/stores/downloads.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { SESSION_ID_KEY } from '$lib/api';
import { settings, userSettings } from '$lib/stores/settings.svelte';
import { saveAs } from '$lib/utils/files';

import type { Download, Video } from '@yd/client';
import {
deleteDownload,
getDownloadFile,
getDownloads,
getDownloadsStatus,
postDownloads,
putDownloads
} from '@yd/client';
import { toasts } from '@yd/ui';

class DownloadsStore {
public downloads = $state<Record<string, Download>>({});

public async init() {
// Setup listener for status updates of any downloads.
getDownloadsStatus(() => sessionStorage.getItem(SESSION_ID_KEY) ?? '', {
onMessage: (download) => {
this.updateDownload(download.video.id, download);
}
});
// Get all previous downloads still available.
const downloads = (await getDownloads()).data ?? [];
this.downloads = downloads.reduce((prev, curr) => ({ ...prev, [curr.video.id]: curr }), {});
}

public async add(video: Video) {
if (video.id in this.downloads) return;

const download: Download = {
video,
status: { state: 'WAITING', progress: null },
options: settings.settings
};

// Add initial value for the download
this.downloads[download.video.id] = download;

try {
const result = await postDownloads({
body: download
});
if (result.data) {
this.updateDownload(result.data.video.id, result.data);
}
} catch (err) {
this.handleError(download.video.id, `Failed to add '${video.title}' to downloads.`, err);
}
}

public async restart(id: string) {
if (!(id in this.downloads)) return;

const download = this.downloads[id];

try {
const result = await putDownloads({
body: download
});
if (result.data) {
this.updateDownload(result.data.video.id, result.data);
}
} catch (err) {
this.handleError(
download.video.id,
`Failed to restart '${download.video.title}' download.`,
err
);
}
}

public async remove(id: string) {
if (!(id in this.downloads)) return;

try {
await deleteDownload({ path: { download_id: id } });
toasts.success('Deleted', 'Download removed successfully.');
delete this.downloads[id];
} catch (err) {
this.handleError(id, 'Failed to remove download.', err);
}
}

public async getFile(id: string) {
if (!(id in this.downloads)) return;

try {
const response = await getDownloadFile({ path: { download_id: id } });
if (response.data) {
const download = this.downloads[id];
const filename = `${download.video.title}.${download.options.format}`;
saveAs(response.data as Blob, filename);
}
} catch (err) {
this.handleError(id, 'Failed to get file for download.', err);
}
}

private updateDownload(id: string, updatedValue: Partial<Download>) {
if (id in this.downloads) {
// Merge and update values.
const oldValue = this.downloads[id];
const value: Download = { ...oldValue, ...updatedValue };
this.downloads[id] = value;
// Automatically download file if enabled by the user.
if (userSettings.settings.autoDownloadOnComplete) {
if (updatedValue.status?.state === 'DONE') {
this.getFile(value.video.id);
}
}
} else {
console.error('Attempted to update download that does not exist.');
}
}

private handleError(downloadId: string, msg: string, error: unknown) {
toasts.error('Error', msg);
console.error(msg, error);
this.updateDownload(downloadId, { status: { state: 'ERROR', progress: null } });
}
}

export const downloads = new DownloadsStore();
147 changes: 0 additions & 147 deletions apps/web/src/lib/stores/downloads.ts

This file was deleted.

51 changes: 51 additions & 0 deletions apps/web/src/lib/stores/search.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Video } from '@yd/client';
import { getNextSearch, getSearch } from '@yd/client';
import { toasts } from '@yd/ui';

class SearchStore {
public query = $state<string>('');
public loading = $state<boolean>(false);
public results = $state<Video[]>([]);

public async get(q: string) {
if (this.query === q) {
return;
}
this.query = q;
this.loading = true;
try {
const response = await getSearch({ query: { query: q } });
if (response.data) {
this.results = response.data;
}
toasts.success('Success', `Found ${this.results.length} search results.`);
} catch (err) {
toasts.error('Error', 'Failed to get search results.');
console.error('Failed to search for videos ', err);
}
this.loading = false;
}

public async getMore() {
this.loading = true;
try {
const response = await getNextSearch();
if (response.data) {
this.results = [...this.results, ...response.data];
}
toasts.success('Success', `Found ${this.results.length} more search results.`);
} catch (err) {
toasts.error('Error', 'Failed to get more search results.');
console.error('Failed to get more search videos ', err);
}
this.loading = false;
}

public reset() {
this.query = '';
this.loading = false;
this.results = [];
}
}

export const search = new SearchStore();
Loading

0 comments on commit 0a46ee0

Please sign in to comment.