core functionalities
This commit is contained in:
@@ -1,13 +1,53 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import * as O from 'fp-ts/Option';
|
||||||
|
import { pipe } from 'fp-ts/lib/function';
|
||||||
import {
|
import {
|
||||||
|
downloads,
|
||||||
httpPostRpcEndpoint,
|
httpPostRpcEndpoint,
|
||||||
|
rpcClient,
|
||||||
serverApiEndpoint,
|
serverApiEndpoint,
|
||||||
websocketRpcEndpoint,
|
websocketRpcEndpoint,
|
||||||
} from './lib/store';
|
} 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>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{$serverApiEndpoint}
|
<div class="flex flex-col gap-2 p-8">
|
||||||
{$httpPostRpcEndpoint}
|
{#each pipe( $downloads, O.getOrElseW(() => []), ) as download}
|
||||||
{$websocketRpcEndpoint}
|
<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>
|
</main>
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { derived, readable, writable } from 'svelte/store'
|
import { derived, readable, writable } from 'svelte/store'
|
||||||
import { RPCClient } from './RPCClient'
|
import { RPCClient } from './RPCClient'
|
||||||
import type { RPCResponse, RPCResult } from './types'
|
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 rpcHost = writable<string>(localStorage.getItem('rpcHost') ?? 'localhost')
|
||||||
export const rpcPort = writable<number>(Number(localStorage.getItem('rpcPort')) || 3033)
|
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)
|
([$http, $ws, $token]) => new RPCClient($http, $ws, $token)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const downloads = readable<RPCResponse<RPCResult[]>>({
|
export const downloads = writable<O.Option<RPCResult[]>>(O.none)
|
||||||
id: '',
|
|
||||||
error: null,
|
|
||||||
result: [],
|
|
||||||
})
|
|
||||||
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 {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
"./src/**/*.{svelte,js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
|
|||||||
Reference in New Issue
Block a user