core functionalities
This commit is contained in:
@@ -1,13 +1,53 @@
|
||||
<script lang="ts">
|
||||
import * as O from 'fp-ts/Option';
|
||||
import { pipe } from 'fp-ts/lib/function';
|
||||
import {
|
||||
downloads,
|
||||
httpPostRpcEndpoint,
|
||||
rpcClient,
|
||||
serverApiEndpoint,
|
||||
websocketRpcEndpoint,
|
||||
} from './lib/store';
|
||||
import { datetimeCompareFunc, isRPCResponse } from './lib/utils';
|
||||
|
||||
rpcClient.subscribe(($client) => {
|
||||
setInterval(() => $client.running(), 750);
|
||||
|
||||
$client.socket.onmessage = (ev: any) => {
|
||||
const event = JSON.parse(ev.data);
|
||||
// guards
|
||||
if (!isRPCResponse(event)) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(event.result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.result) {
|
||||
return downloads.set(
|
||||
O.of(
|
||||
event.result
|
||||
.filter((f) => !!f.info.url)
|
||||
.sort((a, b) =>
|
||||
datetimeCompareFunc(b.info.created_at, a.info.created_at),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
downloads.set(O.none);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
{$serverApiEndpoint}
|
||||
{$httpPostRpcEndpoint}
|
||||
{$websocketRpcEndpoint}
|
||||
<div class="flex flex-col gap-2 p-8">
|
||||
{#each pipe( $downloads, O.getOrElseW(() => []), ) as download}
|
||||
<div class="bg-neutral-100 p-4 rounded-lg shadow-lg">
|
||||
<div>{download.id}</div>
|
||||
<div>{JSON.stringify(download.info)}</div>
|
||||
<div>{JSON.stringify(download.progress)}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
@tailwind utilities;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { derived, readable, writable } from 'svelte/store'
|
||||
import { RPCClient } from './RPCClient'
|
||||
import type { RPCResponse, RPCResult } from './types'
|
||||
import * as O from 'fp-ts/lib/Option'
|
||||
|
||||
export const rpcHost = writable<string>(localStorage.getItem('rpcHost') ?? 'localhost')
|
||||
export const rpcPort = writable<number>(Number(localStorage.getItem('rpcPort')) || 3033)
|
||||
@@ -31,8 +32,4 @@ export const rpcClient = derived(
|
||||
([$http, $ws, $token]) => new RPCClient($http, $ws, $token)
|
||||
)
|
||||
|
||||
export const downloads = readable<RPCResponse<RPCResult[]>>({
|
||||
id: '',
|
||||
error: null,
|
||||
result: [],
|
||||
})
|
||||
export const downloads = writable<O.Option<RPCResult[]>>(O.none)
|
||||
84
ui/src/lib/utils.ts
Normal file
84
ui/src/lib/utils.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { pipe } from 'fp-ts/lib/function'
|
||||
import type { RPCResponse } from "./types"
|
||||
|
||||
/**
|
||||
* Validate an ip v4 via regex
|
||||
* @param {string} ipAddr
|
||||
* @returns ip validity test
|
||||
*/
|
||||
export function validateIP(ipAddr: string): boolean {
|
||||
let ipRegex = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/gm
|
||||
return ipRegex.test(ipAddr)
|
||||
}
|
||||
|
||||
export function validateDomain(url: string): boolean {
|
||||
const urlRegex = /(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
|
||||
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
||||
|
||||
const [name, slug] = url.split('/')
|
||||
|
||||
return urlRegex.test(url) || name === 'localhost' && slugRegex.test(slug)
|
||||
}
|
||||
|
||||
export function isValidURL(url: string): boolean {
|
||||
let urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/
|
||||
return urlRegex.test(url)
|
||||
}
|
||||
|
||||
export function ellipsis(str: string, lim: number): string {
|
||||
if (str) {
|
||||
return str.length > lim ? `${str.substring(0, lim)}...` : str
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export function toFormatArgs(codes: string[]): string {
|
||||
if (codes.length > 1) {
|
||||
return codes.reduce((v, a) => ` -f ${v}+${a}`)
|
||||
}
|
||||
if (codes.length === 1) {
|
||||
return ` -f ${codes[0]}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export const formatGiB = (bytes: number) =>
|
||||
`${(bytes / 1_000_000_000).toFixed(0)}GiB`
|
||||
|
||||
export const roundMiB = (bytes: number) =>
|
||||
`${(bytes / 1_000_000).toFixed(2)} MiB`
|
||||
|
||||
export const formatSpeedMiB = (val: number) =>
|
||||
`${roundMiB(val)}/s`
|
||||
|
||||
export const datetimeCompareFunc = (a: string, b: string) =>
|
||||
new Date(a).getTime() - new Date(b).getTime()
|
||||
|
||||
export function isRPCResponse(object: any): object is RPCResponse<any> {
|
||||
return 'result' in object && 'id' in object
|
||||
}
|
||||
|
||||
export function mapProcessStatus(status: number) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 'Pending'
|
||||
case 1:
|
||||
return 'Downloading'
|
||||
case 2:
|
||||
return 'Completed'
|
||||
case 3:
|
||||
return 'Error'
|
||||
default:
|
||||
return 'Pending'
|
||||
}
|
||||
}
|
||||
|
||||
export const prefersDarkMode = () =>
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
|
||||
export const base64URLEncode = (s: string) => pipe(
|
||||
s,
|
||||
s => String.fromCodePoint(...new TextEncoder().encode(s)),
|
||||
btoa,
|
||||
encodeURIComponent
|
||||
)
|
||||
@@ -2,7 +2,7 @@
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
"./src/**/*.{svelte,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
|
||||
Reference in New Issue
Block a user