add: text fields and button in signup page
This commit is contained in:
parent
4addadb035
commit
f003e77d52
@ -8,8 +8,10 @@ import { logger } from "./logger.ts";
|
|||||||
import { timing } from "hono/timing";
|
import { timing } from "hono/timing";
|
||||||
import { contentType } from "./contentType.ts";
|
import { contentType } from "./contentType.ts";
|
||||||
import { captchaMiddleware } from "./captcha.ts";
|
import { captchaMiddleware } from "./captcha.ts";
|
||||||
|
import { cors } from 'hono/cors';
|
||||||
|
|
||||||
export function configureMiddleWares(app: Hono<{ Variables: Variables }>) {
|
export function configureMiddleWares(app: Hono<{ Variables: Variables }>) {
|
||||||
|
app.all("*", cors());
|
||||||
app.use("*", contentType);
|
app.use("*", contentType);
|
||||||
app.use(timing());
|
app.use(timing());
|
||||||
app.use("*", preetifyResponse);
|
app.use("*", preetifyResponse);
|
||||||
|
3
packages/frontend/src/components/Dialog.svelte
Normal file
3
packages/frontend/src/components/Dialog.svelte
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="absolute bg-surface-container-high dark:bg-dark-surface-container-high z-50">
|
||||||
|
|
||||||
|
</div>
|
@ -6,7 +6,6 @@
|
|||||||
export let show: boolean = false;
|
export let show: boolean = false;
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
|
||||||
let drawer: HTMLDivElement;
|
|
||||||
let cover: HTMLDivElement;
|
let cover: HTMLDivElement;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@ -27,19 +26,17 @@
|
|||||||
<div class="absolute z-50 ">
|
<div class="absolute z-50 ">
|
||||||
{#if show}
|
{#if show}
|
||||||
<div
|
<div
|
||||||
bind:this={cover}
|
bind:this={cover}
|
||||||
transition:fade="{{ duration: 300 }}"
|
transition:fade="{{ duration: 300 }}"
|
||||||
class="fixed top-0 left-0 w-full h-full z-40 bg-[#00000020]"
|
class="fixed top-0 left-0 w-full h-full z-40 bg-[#00000020]"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={drawer}
|
transition:fly="{{ x: -500, duration: 300 }}" class="fixed top-0 left-0 h-full
|
||||||
transition:fly="{{ x: -500, duration: 300 }}" class="fixed top-0 left-0 h-full
|
|
||||||
bg-[#fff0ee] dark:bg-[#231918] z-50"
|
bg-[#fff0ee] dark:bg-[#231918] z-50"
|
||||||
style="width: min(22.5rem, 70vw);"
|
style="width: min(22.5rem, 70vw);"
|
||||||
role="dialog" aria-modal="true">
|
role="dialog" aria-modal="true">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
8
packages/frontend/src/components/Portal.svelte
Normal file
8
packages/frontend/src/components/Portal.svelte
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
let ref;
|
||||||
|
$: ref && document.body.appendChild(ref);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={ref}>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import TextField from "@components/TextField.svelte";
|
||||||
|
import LoadingSpinner from "@components/icon/LoadingSpinner.svelte";
|
||||||
|
import { computeVdfInWorker } from "@lib/vdf.js";
|
||||||
|
|
||||||
|
export let backendURL: string;
|
||||||
|
|
||||||
|
let password = '';
|
||||||
|
let username = '';
|
||||||
|
let nickname = '';
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
|
|
||||||
|
async function createCaptchaSession() {
|
||||||
|
const url = new URL(backendURL);
|
||||||
|
url.pathname = '/captcha/session';
|
||||||
|
const res = await fetch(url.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
"route": "POST-/user"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (res.status !== 201) {
|
||||||
|
throw new Error("Failed to create captcha session");
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCaptchaResult(id: string, ans: string) {
|
||||||
|
const url = new URL(backendURL);
|
||||||
|
url.pathname = `/captcha/${id}/result`;
|
||||||
|
url.searchParams.set("ans", ans);
|
||||||
|
const res = await fetch(url.toString());
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error("Failed to verify captcha answer");
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function register() {
|
||||||
|
const { g, n, t, id } = await createCaptchaSession();
|
||||||
|
const ans = await computeVdfInWorker(BigInt(g), BigInt(n), BigInt(t));
|
||||||
|
const res = await getCaptchaResult(id, ans.result.toString());
|
||||||
|
console.log(res)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="w-full flex flex-col gap-6">
|
||||||
|
<TextField labelText="用户名" bind:inputText={username} maxChar={50}
|
||||||
|
supportingText="*必填。用户名是唯一的,不区分大小写。"
|
||||||
|
/>
|
||||||
|
<TextField labelText="密码" type="password" bind:inputText={password}
|
||||||
|
supportingText="*必填。密码至少为 4 个字符。" maxChar={120}
|
||||||
|
/>
|
||||||
|
<TextField labelText="昵称" bind:inputText={nickname}
|
||||||
|
supportingText="昵称可以重复。" maxChar={30}
|
||||||
|
/>
|
||||||
|
<button class="bg-primary dark:bg-dark-primary text-on-primary dark:text-dark-on-primary duration-150
|
||||||
|
rounded-full hover:bg-on-primary-container hover:dark:bg-dark-on-primary-container mt-2
|
||||||
|
flex items-center text-sm leading-5 justify-center h-10 w-full"
|
||||||
|
onclick={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
loading = true;
|
||||||
|
try {
|
||||||
|
await register();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if !loading}
|
||||||
|
<span>注册</span>
|
||||||
|
{:else}
|
||||||
|
<LoadingSpinner/>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
72
packages/frontend/src/components/TextField.svelte
Normal file
72
packages/frontend/src/components/TextField.svelte
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let focus = $state(false);
|
||||||
|
let {
|
||||||
|
labelText = "",
|
||||||
|
type = "text",
|
||||||
|
inputText = $bindable(),
|
||||||
|
maxChar = undefined,
|
||||||
|
supportingText = undefined,
|
||||||
|
...rest
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
function onValueChange(event: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
||||||
|
if (!event.target) return;
|
||||||
|
const { value } = event.target as HTMLInputElement;
|
||||||
|
inputText = value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div {...rest}>
|
||||||
|
<div class="relative h-14 px-4">
|
||||||
|
<div class="absolute flex top-0 left-0 h-full w-full">
|
||||||
|
<div class={`w-3 rounded-l-sm border-outline dark:border-dark-outline
|
||||||
|
${(focus) ?
|
||||||
|
"border-primary dark:border-dark-primary border-l-2 border-y-2" :
|
||||||
|
"border-l-[1px] border-y-[1px] "}
|
||||||
|
`}></div>
|
||||||
|
|
||||||
|
<div class={`px-1 border-outline dark:border-dark-outline transition-none
|
||||||
|
${(!focus && !inputText) && "border-y-[1px]"}
|
||||||
|
${(!focus && inputText) && "border-y-[1px] border-t-0"}
|
||||||
|
${focus && "border-primary dark:border-dark-primary border-y-2 border-t-0"}
|
||||||
|
`}>
|
||||||
|
<span class={`
|
||||||
|
relative leading-6 text-base text-on-surface-variant dark:text-dark-on-surface-variant duration-150
|
||||||
|
${(focus || inputText) ? "-top-3 text-xs leading-4" : "top-4"}
|
||||||
|
${focus && "text-primary dark:text-dark-primary"}
|
||||||
|
`}>
|
||||||
|
{labelText}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={`flex-grow rounded-r-sm border-outline dark:border-dark-outline
|
||||||
|
${(focus) ?
|
||||||
|
"border-primary dark:border-dark-primary border-r-2 border-y-2" :
|
||||||
|
"border-r-[1px] border-y-[1px] "}
|
||||||
|
`}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="relative focus:outline-none h-full w-full"
|
||||||
|
onfocus={() => focus = true}
|
||||||
|
onblur={() => focus = false}
|
||||||
|
oninput={onValueChange}
|
||||||
|
type={type}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if supportingText || maxChar}
|
||||||
|
<div class="w-full relative mt-1 text-on-surface-variant dark:text-dark-on-surface-variant
|
||||||
|
text-xs leading-4 h-4">
|
||||||
|
{#if supportingText}
|
||||||
|
<span class="absolute left-4">
|
||||||
|
{supportingText}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{#if maxChar}
|
||||||
|
<span class="absolute right-4">
|
||||||
|
{inputText.length}/{maxChar}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
@ -9,6 +9,7 @@
|
|||||||
import HomeIcon from "@components/icon/HomeIcon.svelte";
|
import HomeIcon from "@components/icon/HomeIcon.svelte";
|
||||||
import InfoIcon from "@components/icon/InfoIcon.svelte";
|
import InfoIcon from "@components/icon/InfoIcon.svelte";
|
||||||
import RegisterIcon from "@components/icon/RegisterIcon.svelte";
|
import RegisterIcon from "@components/icon/RegisterIcon.svelte";
|
||||||
|
import Portal from "@components/Portal.svelte";
|
||||||
|
|
||||||
let searchBox: SearchBox | null = null;
|
let searchBox: SearchBox | null = null;
|
||||||
let showSearchBox = false;
|
let showSearchBox = false;
|
||||||
@ -19,22 +20,24 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NavigationDrawer show={showDrawer} onClose={() => showDrawer = false}>
|
<Portal>
|
||||||
<div class="flex flex-col w-full">
|
<NavigationDrawer show={showDrawer} onClose={() => showDrawer = false}>
|
||||||
<div class="w-full h-14 flex items-center px-4">
|
<div class="flex flex-col w-full">
|
||||||
<HomeIcon className="text-2xl pr-4"/>
|
<div class="w-full h-14 flex items-center px-4">
|
||||||
<a href="/">首页</a>
|
<HomeIcon className="text-2xl pr-4"/>
|
||||||
|
<a href="/">首页</a>
|
||||||
|
</div>
|
||||||
|
<div class="w-full h-14 flex items-center px-4">
|
||||||
|
<InfoIcon className="text-2xl pr-4"/>
|
||||||
|
<a href="/about">关于</a>
|
||||||
|
</div>
|
||||||
|
<div class="w-full h-14 flex items-center px-4">
|
||||||
|
<RegisterIcon className="text-2xl pr-4"/>
|
||||||
|
<a href="/register">注册</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-14 flex items-center px-4">
|
</NavigationDrawer>
|
||||||
<InfoIcon className="text-2xl pr-4"/>
|
</Portal>
|
||||||
<a href="/about">关于</a>
|
|
||||||
</div>
|
|
||||||
<div class="w-full h-14 flex items-center px-4">
|
|
||||||
<RegisterIcon className="text-2xl pr-4"/>
|
|
||||||
<a href="/register">注册</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NavigationDrawer>
|
|
||||||
|
|
||||||
<div class="md:hidden relative top-0 left-0 w-full h-16 z-20">
|
<div class="md:hidden relative top-0 left-0 w-full h-16 z-20">
|
||||||
{#if !showSearchBox}
|
{#if !showSearchBox}
|
||||||
|
8
packages/frontend/src/components/icon/LeftArrow.astro
Normal file
8
packages/frontend/src/components/icon/LeftArrow.astro
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
const { ...props } = Astro.props;
|
||||||
|
---
|
||||||
|
<svg {...props} width="1em" height="1em" viewBox="0 0 10.72 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="path"
|
||||||
|
d="M10.64 4.34C10.69 4.23 10.72 4.11 10.72 3.97C10.72 3.79 10.67 3.65 10.59 3.55C10.55 3.51 10.5 3.46 10.46 3.44C10.39 3.4 10.31 3.38 10.22 3.38L2.16 3.38L4.57 0.97C4.61 0.9 4.7 0.73 4.7 0.63C4.7 0.6 4.7 0.57 4.7 0.52C4.67 0.4 4.6 0.28 4.5 0.16C4.39 0.06 4.29 0 4.17 -0.02C4.13 -0.04 4.09 -0.05 4.04 -0.05C3.95 -0.05 3.87 -0.02 3.81 0.01C3.76 0.04 3.73 0.06 3.7 0.09L0.22 3.58C0.07 3.72 0 3.85 0 3.97C0 4.09 0.07 4.23 0.22 4.38L3.7 7.87C3.82 7.95 3.93 8 4.04 8C4.18 7.98 4.37 7.91 4.5 7.79C4.6 7.68 4.67 7.56 4.7 7.44C4.7 7.4 4.7 7.36 4.7 7.32C4.7 7.26 4.7 7.21 4.67 7.16C4.66 7.1 4.62 7.04 4.57 7L2.16 4.59L10.22 4.59C10.3 4.59 10.37 4.57 10.43 4.54C10.49 4.51 10.59 4.4 10.64 4.34Z"
|
||||||
|
fill="currentColor" fill-opacity="1.000000" fill-rule="evenodd" />
|
||||||
|
</svg>
|
15
packages/frontend/src/components/icon/LoadingSpinner.svelte
Normal file
15
packages/frontend/src/components/icon/LoadingSpinner.svelte
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let className = '';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={className}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||||
|
<g stroke="currentColor" stroke-width="1">
|
||||||
|
<circle cx="12" cy="12" r="9.5" fill="none" stroke-linecap="round" stroke-width="3">
|
||||||
|
<animate attributeName="stroke-dasharray" calcMode="spline" dur="1.5s" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" keyTimes="0;0.475;0.95;1" repeatCount="indefinite" values="0 150;42 150;42 150;42 150" />
|
||||||
|
<animate attributeName="stroke-dashoffset" calcMode="spline" dur="1.5s" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" keyTimes="0;0.475;0.95;1" repeatCount="indefinite" values="0;-16;-59;-59" />
|
||||||
|
</circle>
|
||||||
|
<animateTransform attributeName="transform" dur="2s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
@ -3,18 +3,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={className}>
|
<div class={className}>
|
||||||
<svg width="28.000000" height="28.000000" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="28.000000" height="28.000000" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<desc>
|
|
||||||
Created with Pixso.
|
|
||||||
</desc>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip97_210">
|
|
||||||
<rect id="菜单按钮" width="28.000000" height="28.000000" fill="white" fill-opacity="0"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g clip-path="url(#clip97_210)">
|
|
||||||
<path id="path" d="M4.66 21C4.33 21 4.05 20.88 3.83 20.66C3.61 20.44 3.5 20.16 3.5 19.83C3.49 19.5 3.61 19.22 3.83 19C4.06 18.77 4.33 18.66 4.66 18.66L23.33 18.66C23.66 18.66 23.94 18.77 24.16 19C24.38 19.22 24.5 19.5 24.5 19.83C24.49 20.16 24.38 20.44 24.16 20.66C23.94 20.88 23.66 21 23.33 21L4.66 21ZM4.66 15.16C4.33 15.16 4.05 15.05 3.83 14.83C3.61 14.6 3.5 14.32 3.5 14C3.49 13.67 3.61 13.39 3.83 13.16C4.06 12.94 4.33 12.83 4.66 12.83L23.33 12.83C23.66 12.83 23.94 12.94 24.16 13.16C24.38 13.39 24.5 13.67 24.5 14C24.49 14.32 24.38 14.6 24.16 14.83C23.94 15.05 23.66 15.16 23.33 15.16L4.66 15.16ZM4.66 9.33C4.33 9.33 4.05 9.22 3.83 8.99C3.61 8.77 3.5 8.49 3.5 8.16C3.49 7.83 3.61 7.56 3.83 7.33C4.06 7.11 4.33 7 4.66 7L23.33 7C23.66 7 23.94 7.11 24.16 7.33C24.38 7.56 24.5 7.83 24.5 8.16C24.49 8.49 24.38 8.77 24.16 8.99C23.94 9.22 23.66 9.33 23.33 9.33L4.66 9.33Z" fill="currentColor" fill-opacity="1.000000" fill-rule="nonzero"/>
|
<path id="path" d="M4.66 21C4.33 21 4.05 20.88 3.83 20.66C3.61 20.44 3.5 20.16 3.5 19.83C3.49 19.5 3.61 19.22 3.83 19C4.06 18.77 4.33 18.66 4.66 18.66L23.33 18.66C23.66 18.66 23.94 18.77 24.16 19C24.38 19.22 24.5 19.5 24.5 19.83C24.49 20.16 24.38 20.44 24.16 20.66C23.94 20.88 23.66 21 23.33 21L4.66 21ZM4.66 15.16C4.33 15.16 4.05 15.05 3.83 14.83C3.61 14.6 3.5 14.32 3.5 14C3.49 13.67 3.61 13.39 3.83 13.16C4.06 12.94 4.33 12.83 4.66 12.83L23.33 12.83C23.66 12.83 23.94 12.94 24.16 13.16C24.38 13.39 24.5 13.67 24.5 14C24.49 14.32 24.38 14.6 24.16 14.83C23.94 15.05 23.66 15.16 23.33 15.16L4.66 15.16ZM4.66 9.33C4.33 9.33 4.05 9.22 3.83 8.99C3.61 8.77 3.5 8.49 3.5 8.16C3.49 7.83 3.61 7.56 3.83 7.33C4.06 7.11 4.33 7 4.66 7L23.33 7C23.66 7 23.94 7.11 24.16 7.33C24.38 7.56 24.5 7.83 24.5 8.16C24.49 8.49 24.38 8.77 24.16 8.99C23.94 9.22 23.66 9.33 23.33 9.33L4.66 9.33Z" fill="currentColor" fill-opacity="1.000000" fill-rule="nonzero"/>
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
const {...props} = Astro.props;
|
const {...props} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<svg {...props} width="10.72" height="8" viewBox="0 0 10.72 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg {...props} width="1em" height="1em" viewBox="0 0 10.72 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M0.08 3.66Q0 3.82 0 4.03Q0 4.29 0.13 4.45Q0.13 4.46 0.13 4.46Q0.19 4.52 0.26 4.56Q0.36 4.62 0.49 4.62L8.56 4.62L6.15 7.03Q6.1 7.07 6.08 7.12Q6.01 7.22 6.01 7.36Q6.01 7.41 6.02 7.47Q6.06 7.66 6.22 7.83Q6.37 7.98 6.54 8.02Q6.61 8.04 6.67 8.04Q6.81 8.04 6.91 7.98Q6.97 7.95 7.01 7.9L10.5 4.42Q10.72 4.2 10.72 4.03Q10.72 3.84 10.5 3.62L7.01 0.13Q6.84 0 6.67 0Q6.64 0 6.61 0Q6.41 0.02 6.22 0.21Q6.06 0.37 6.02 0.56Q6.01 0.62 6.01 0.68Q6.01 0.76 6.04 0.84Q6.07 0.93 6.15 1L8.56 3.41L0.49 3.41Q0.38 3.41 0.29 3.46Q0.2 3.5 0.13 3.59Q0.1 3.63 0.08 3.66Z"
|
<path d="M0.08 3.66Q0 3.82 0 4.03Q0 4.29 0.13 4.45Q0.13 4.46 0.13 4.46Q0.19 4.52 0.26 4.56Q0.36 4.62 0.49 4.62L8.56 4.62L6.15 7.03Q6.1 7.07 6.08 7.12Q6.01 7.22 6.01 7.36Q6.01 7.41 6.02 7.47Q6.06 7.66 6.22 7.83Q6.37 7.98 6.54 8.02Q6.61 8.04 6.67 8.04Q6.81 8.04 6.91 7.98Q6.97 7.95 7.01 7.9L10.5 4.42Q10.72 4.2 10.72 4.03Q10.72 3.84 10.5 3.62L7.01 0.13Q6.84 0 6.67 0Q6.64 0 6.61 0Q6.41 0.02 6.22 0.21Q6.06 0.37 6.02 0.56Q6.01 0.62 6.01 0.68Q6.01 0.76 6.04 0.84Q6.07 0.93 6.15 1L8.56 3.41L0.49 3.41Q0.38 3.41 0.29 3.46Q0.2 3.5 0.13 3.59Q0.1 3.63 0.08 3.66Z"
|
||||||
fill="currentColor" fill-opacity="1.000000" fill-rule="evenodd"/>
|
fill="currentColor" fill-opacity="1.000000" fill-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
113
packages/frontend/src/lib/vdf.ts
Normal file
113
packages/frontend/src/lib/vdf.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
// Define interfaces for input and output
|
||||||
|
interface VdfProgressCallback {
|
||||||
|
(progress: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VdfResult {
|
||||||
|
result: bigint;
|
||||||
|
time: number; // Time taken in milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// The content of the Web Worker script
|
||||||
|
const workerContent = `addEventListener("message", async (event) => {
|
||||||
|
const { g, N, difficulty } = event.data;
|
||||||
|
|
||||||
|
// Although pow is not used in the iterative VDF, it's good to keep the original worker code structure.
|
||||||
|
// The iterative computeVDFWithProgress is better for progress reporting.
|
||||||
|
function pow(base, exponent, mod) {
|
||||||
|
let result = 1n;
|
||||||
|
base = base % mod;
|
||||||
|
while (exponent > 0n) {
|
||||||
|
if (exponent % 2n === 1n) {
|
||||||
|
result = (result * base) % mod;
|
||||||
|
}
|
||||||
|
base = (base * base) % mod;
|
||||||
|
exponent = exponent / 2n;
|
||||||
|
// Using BigInt division (/) which performs integer division
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute VDF iteratively to report progress
|
||||||
|
function computeVDFWithProgress(g, N, T, postProgress) {
|
||||||
|
let result = g;
|
||||||
|
let latestTime = performance.now();
|
||||||
|
const totalSteps = T; // T is the difficulty, representing 2^T squaring steps
|
||||||
|
|
||||||
|
for (let i = 0n; i < totalSteps; i++) {
|
||||||
|
result = (result * result) % N;
|
||||||
|
// Report progress periodically (approx. every 16ms to match typical frame rate)
|
||||||
|
if (performance.now() - latestTime > 16) {
|
||||||
|
// Calculate progress as a percentage
|
||||||
|
const progress = Number((i + 1n) * 10000n / totalSteps) / 100; // Using 10000 for better precision before dividing by 100
|
||||||
|
postProgress(progress);
|
||||||
|
latestTime = performance.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ensure final progress is reported
|
||||||
|
postProgress(100);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
// The worker computes g^(2^difficulty) mod N. The loop runs 'difficulty' times, performing squaring.
|
||||||
|
const result = computeVDFWithProgress(g, N, difficulty, (progress) => {
|
||||||
|
// Post progress back to the main thread
|
||||||
|
postMessage({ type: "progress", progress: progress });
|
||||||
|
});
|
||||||
|
const endTime = performance.now();
|
||||||
|
const timeTaken = endTime - startTime;
|
||||||
|
|
||||||
|
// Post the final result and time taken back to the main thread
|
||||||
|
postMessage({ type: "result", result: result.toString(), time: timeTaken });
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the Verifiable Delay Function (VDF) result g^(2^difficulty) mod N
|
||||||
|
* in a Web Worker and reports progress.
|
||||||
|
* @param g - The base (bigint).
|
||||||
|
* @param N - The modulus (bigint).
|
||||||
|
* @param difficulty - The number of squaring steps (T) (bigint).
|
||||||
|
* @param onProgress - Optional callback function to receive progress updates (0-100).
|
||||||
|
* @returns A Promise that resolves with the VDF result and time taken.
|
||||||
|
*/
|
||||||
|
export function computeVdfInWorker(g: bigint, N: bigint, difficulty: bigint, onProgress?: VdfProgressCallback): Promise<VdfResult> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Create a Blob containing the worker script
|
||||||
|
const blob = new Blob([workerContent], { type: "text/javascript" });
|
||||||
|
// Create a URL for the Blob
|
||||||
|
const workerUrl = URL.createObjectURL(blob);
|
||||||
|
// Create a new Web Worker
|
||||||
|
const worker = new Worker(workerUrl);
|
||||||
|
|
||||||
|
// Handle messages from the worker
|
||||||
|
worker.onmessage = (event) => {
|
||||||
|
const { type, progress, result, time } = event.data;
|
||||||
|
|
||||||
|
if (type === "progress") {
|
||||||
|
if (onProgress) {
|
||||||
|
onProgress(progress);
|
||||||
|
}
|
||||||
|
} else if (type === "result") {
|
||||||
|
// Resolve the promise with the result and time
|
||||||
|
resolve({ result: BigInt(result), time });
|
||||||
|
// Terminate the worker and revoke the URL
|
||||||
|
worker.terminate();
|
||||||
|
URL.revokeObjectURL(workerUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle potential errors in the worker
|
||||||
|
worker.onerror = (error) => {
|
||||||
|
reject(error);
|
||||||
|
// Terminate the worker and revoke the URL in case of error
|
||||||
|
worker.terminate();
|
||||||
|
URL.revokeObjectURL(workerUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Post the data to the worker to start the computation
|
||||||
|
worker.postMessage({ g, N, difficulty });
|
||||||
|
});
|
||||||
|
}
|
@ -1,13 +1,24 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import RightArrow from "@components/icon/RightArrow.astro";
|
import RightArrow from "@components/icon/RightArrow.astro";
|
||||||
|
import RegisterForm from "@components/RegisterPage/RegisterForm.svelte";
|
||||||
|
import LeftArrow from "@components/icon/LeftArrow.astro";
|
||||||
|
|
||||||
|
const backendURL = import.meta.env.BACKEND_URL;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="注册">
|
<Layout title="注册">
|
||||||
<main class="relative flex-grow pt-36 px-4 md:w-full md:flex md:items-center md:flex-col">
|
<main class="relative flex-grow pt-8 md:pt-0 px-4 md:w-full md:h-full md:flex md:items-center md:justify-center">
|
||||||
<div class="md:w-[40rem] rounded-md md:p-8 md:bg-surface-container md:dark:bg-dark-container">
|
<div class="md:w-[40rem] rounded-md md:p-8 md:-translate-y-6
|
||||||
|
md:bg-surface-container md:dark:bg-dark-surface-container">
|
||||||
|
<p class="mb-2">
|
||||||
|
<a href="/">
|
||||||
|
<LeftArrow class="inline -translate-y-[0.1rem] scale-90" aria-hidden="true"/>
|
||||||
|
首页
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
<h1 class="text-5xl leading-[4rem] font-extralight">欢迎</h1>
|
<h1 class="text-5xl leading-[4rem] font-extralight">欢迎</h1>
|
||||||
<p class="mt-2.5 md:mt-4">
|
<p class="mt-2 md:mt-3">
|
||||||
欢迎来到中 V 档案馆。<br/>
|
欢迎来到中 V 档案馆。<br/>
|
||||||
这里是中文虚拟歌手相关信息的收集站与档案馆。
|
这里是中文虚拟歌手相关信息的收集站与档案馆。
|
||||||
</p>
|
</p>
|
||||||
@ -15,16 +26,14 @@ import RightArrow from "@components/icon/RightArrow.astro";
|
|||||||
注册一个账号,<br/>
|
注册一个账号,<br/>
|
||||||
让我们一起见证中 V 的历史,现在,与未来。
|
让我们一起见证中 V 的历史,现在,与未来。
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-4">
|
<p class="mt-4 mb-7">
|
||||||
已有账户?
|
已有账户?
|
||||||
<a href="/login">
|
<a href="/login">
|
||||||
<span>登录</span>
|
<span>登录</span>
|
||||||
<RightArrow class="inline -translate-y-0.5" aria-hidden="true"/>
|
<RightArrow class="text-xs inline -translate-y-0.5" aria-hidden="true"/>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-4 leading-8 font-medium">很抱歉,但您现在无法注册。</p>
|
<RegisterForm backendURL={backendURL} client:load />
|
||||||
<p class="text-sm text-on-surface-variant dark:text-dark-on-surface-variant">因为目前还没有写好啦~</p>
|
|
||||||
<p class="mt-4"><a href="/">返回首页</a></p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@components/*": ["src/components/*"],
|
"@components/*": ["src/components/*"],
|
||||||
"@layouts/*": ["src/layouts/*"],
|
"@layouts/*": ["src/layouts/*"],
|
||||||
"@utils/*": ["src/utils/*"],
|
"@lib/*": ["src/lib/*"],
|
||||||
"@assets/*": ["src/assets/*"],
|
"@assets/*": ["src/assets/*"],
|
||||||
"@styles": ["src/styles/*"],
|
"@styles": ["src/styles/*"],
|
||||||
"@core/*": ["../core/*"]
|
"@core/*": ["../core/*"],
|
||||||
},
|
},
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": true
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user