Updates and more
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {astroI18n, t} from "astro-i18n"
|
||||
import {l} from "../util/util.ts"
|
||||
import {fetchWithToken, tokenStore} from "./repo/repo.ts";
|
||||
import {get} from "svelte/store";
|
||||
import {SchematicListSchema} from "./types/schem.ts";
|
||||
import {PlayerSchema} from "./types/data.ts";
|
||||
import {dataRepo, schemRepo, tokenStore} from "./repo/repo.ts";
|
||||
import moment from "moment";
|
||||
import {
|
||||
CheckSolid,
|
||||
@@ -15,179 +12,38 @@
|
||||
XCircleOutline
|
||||
} from "flowbite-svelte-icons";
|
||||
import {Breadcrumb, BreadcrumbItem, Modal, Tooltip} from "flowbite-svelte";
|
||||
import SchematicInfo from "./SchematicInfo.svelte";
|
||||
import SchematicInfo from "./dashboard/SchematicInfo.svelte";
|
||||
import SchematicListTile from "./dashboard/SchematicListTile.svelte";
|
||||
import UploadModal from "./dashboard/UploadModal.svelte";
|
||||
import SchematicList from "./dashboard/SchematicList.svelte";
|
||||
import UserInfo from "./dashboard/UserInfo.svelte";
|
||||
|
||||
let userFetch = getUser();
|
||||
let schematicFetch = getSchematics();
|
||||
|
||||
let uploadOpen = false;
|
||||
let infoModalId: number | null = null;
|
||||
|
||||
function getUser() {
|
||||
return fetchWithToken(get(tokenStore), "/data/me").then(value => value.json()).then(value => PlayerSchema.parse(value))
|
||||
}
|
||||
|
||||
function logout() {
|
||||
tokenStore.set("")
|
||||
window.location.href = l("/login")
|
||||
return $dataRepo.getMe()
|
||||
}
|
||||
|
||||
function getSchematics() {
|
||||
return fetchWithToken(get(tokenStore), "/schem/list").then(value => value.json()).then(value => SchematicListSchema.parse(value))
|
||||
return $schemRepo.getRootSchematicList()
|
||||
}
|
||||
|
||||
function getSchematic(id: number) {
|
||||
return fetchWithToken(get(tokenStore), "/schem/list/" + id).then(value => value.json()).then(value => SchematicListSchema.parse(value))
|
||||
}
|
||||
|
||||
function schemListClick(isDir: boolean, id: number) {
|
||||
if (isDir) {
|
||||
return () => schematicFetch = getSchematic(id)
|
||||
} else {
|
||||
return () => infoModalId = id
|
||||
}
|
||||
return $schemRepo.getSchematicList(id)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await userFetch}
|
||||
<p>loading...</p>
|
||||
<p>{t("status.loading")}</p>
|
||||
{:then user}
|
||||
<div class="flex mb-4 flex-col md:flex-row">
|
||||
<div>
|
||||
<div class="bg-zinc-50 border-gray-100 py-24 px-12 border-2 m-2 transition duration-300 ease-in-out rounded-xl shadow-lg hidden md:block
|
||||
hover:scale-105 hover:shadow-2xl
|
||||
dark:bg-neutral-900 dark:border-gray-800 dark:text-white">
|
||||
<figure>
|
||||
<figcaption class="text-center mb-4 text-2xl">{user.name}</figcaption>
|
||||
<img src={`https://visage.surgeplay.com/bust/150/${user.uuid}`} class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl" alt={user.name + "s bust"} width="150" height="150" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<button class="btn mt-2" on:click={logout}>Logout</button>
|
||||
{#if user.perms.includes("MODERATION")}
|
||||
<a class="btn w-fit mt-2" href="/admin">Admin Panel</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold">Hello, {user.name}</h1>
|
||||
<p>Rang: {t("home.prefix." + user.prefix)}</p>
|
||||
<p>Permissions:</p>
|
||||
<ul>
|
||||
{#each user.perms as permission}
|
||||
<li class="list-disc ml-6">{permission}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<UserInfo {user} />
|
||||
<hr>
|
||||
<div>
|
||||
{#await schematicFetch}
|
||||
<p>Loading...</p>
|
||||
<p>{t("status.loading")}</p>
|
||||
{:then schematics}
|
||||
<div class="flex justify-between">
|
||||
<Breadcrumb navClass="py-4">
|
||||
<BreadcrumbItem home>
|
||||
<svelte:fragment slot="icon">
|
||||
<HomeOutline class="w-6 h-6 mx-2 dark:text-white" />
|
||||
</svelte:fragment>
|
||||
<span on:click={() => schematicFetch = getSchematics()} class="hover:underline hover:cursor-pointer text-2xl">Schematics</span>
|
||||
</BreadcrumbItem>
|
||||
{#each schematics.breadcrumbs as bread}
|
||||
<BreadcrumbItem>
|
||||
<svelte:fragment slot="icon">
|
||||
<ChevronDoubleRightOutline class="w-4 h-4 mx-2 dark:text-white" />
|
||||
</svelte:fragment>
|
||||
<span on:click={() => schematicFetch = getSchematic(bread.id)} class="hover:underline hover:cursor-pointer text-2xl">{bread.name}</span>
|
||||
</BreadcrumbItem>
|
||||
{/each}
|
||||
</Breadcrumb>
|
||||
<div class="flex flex-col justify-center">
|
||||
<button class="btn" on:click={() => uploadOpen = true}>
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr class="!cursor-auto">
|
||||
<th>Type</th>
|
||||
<th>Name</th>
|
||||
<th class="hidden sm:table-cell">Owner</th>
|
||||
<th class="hidden sm:table-cell"></th>
|
||||
<th class="hidden md:table-cell">Updated</th>
|
||||
<th>
|
||||
<InfoCircleOutline />
|
||||
<Tooltip>
|
||||
<span>Replace Color</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th>
|
||||
<InfoCircleOutline />
|
||||
<Tooltip>
|
||||
<span>Allow Replay</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
</tr>
|
||||
{#if schematics.breadcrumbs.length !== 0}
|
||||
<tr on:click|preventDefault={() => {
|
||||
if (schematics.breadcrumbs.length === 1) {
|
||||
schematicFetch = getSchematics()
|
||||
} else {
|
||||
schematicFetch = getSchematic(schematics.breadcrumbs[schematics.breadcrumbs.length - 2].id)
|
||||
}
|
||||
}}>
|
||||
<th>
|
||||
<FolderOutline />
|
||||
</th>
|
||||
<th>../</th>
|
||||
<th class="hidden sm:table-cell"></th>
|
||||
<th class="hidden sm:table-cell">Directory</th>
|
||||
<th class="hidden md:table-cell"></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{/if}
|
||||
{#each schematics.schematics as schem}
|
||||
<tr on:click|preventDefault={schemListClick(schem.type == null, schem.id)}>
|
||||
<th>
|
||||
{#if schem.type == null}
|
||||
<FolderOutline />
|
||||
{:else}
|
||||
<FileOutline />
|
||||
{/if}
|
||||
</th>
|
||||
<th>
|
||||
{schem.name}{#if schem.type == null}/{/if}
|
||||
</th>
|
||||
<th class="hidden sm:table-cell">{schematics.players[schem.owner].name}</th>
|
||||
<th class="hidden sm:table-cell">{schem.type ?? "Directory"}</th>
|
||||
<th class="hidden md:table-cell">{new Intl.DateTimeFormat(astroI18n.locale, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
}).format(moment(schem.lastUpdate).utc(false).toDate())}</th>
|
||||
<th>
|
||||
{#if schem.replaceColor}
|
||||
<CheckSolid class="text-green-500" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500" />
|
||||
{/if}
|
||||
</th>
|
||||
<th>
|
||||
{#if schem.allowReplay}
|
||||
<CheckSolid class="text-green-500" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500" />
|
||||
{/if}
|
||||
</th>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
<SchematicList {schematics} on:reset={() => schematicFetch = getSchematics()} on:to={(e) => schematicFetch = getSchematic(e.detail.id) } />
|
||||
{:catch error}
|
||||
<p>error: {error.message}</p>
|
||||
{/await}
|
||||
@@ -196,32 +52,3 @@
|
||||
<p>error: {error.message}</p>
|
||||
{/await}
|
||||
|
||||
<Modal title="Upload Schematic" bind:open={uploadOpen} autoclose outsideclose>
|
||||
<form>
|
||||
<input type="file" multiple />
|
||||
</form>
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="btn !ml-auto" on:click={() => uploadOpen = false}>Upload</button>
|
||||
<button class="btn btn-gray" on:click={() => uploadOpen = false}>Close</button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
{#if infoModalId !== null}
|
||||
<SchematicInfo schematicId={infoModalId} on:reset={() => infoModalId = null} />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
tr {
|
||||
@apply transition-colors cursor-pointer border-b
|
||||
dark:hover:bg-gray-800 hover:bg-gray-300;
|
||||
}
|
||||
|
||||
th {
|
||||
@apply text-left py-4 md:px-2;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
let {tokenStore} = await import("./repo/repo.ts");
|
||||
if (username === "" || token === "") {
|
||||
token = "";
|
||||
alert(t("login.error"));
|
||||
error = t("login.error");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -22,7 +22,7 @@
|
||||
}).then(res => res.json());
|
||||
|
||||
if (res.name !== username) {
|
||||
alert(t("login.error"));
|
||||
error = t("login.error");
|
||||
token = "";
|
||||
return;
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
tokenStore.set(token);
|
||||
window.location.href = l("/dashboard");
|
||||
} catch (e) {
|
||||
alert(t("login.error"));
|
||||
error = t("login.error");
|
||||
token = "";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import {fetchWithToken, tokenStore} from "./repo/repo.ts";
|
||||
import {get} from "svelte/store";
|
||||
import {Modal, Spinner} from "flowbite-svelte";
|
||||
import {SchematicInfoSchema} from "./types/schem.ts";
|
||||
import {astroI18n} from "astro-i18n";
|
||||
import moment from "moment/moment";
|
||||
import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let schematicId: number;
|
||||
|
||||
let schemInfo = getSchematicInfo(schematicId);
|
||||
|
||||
function getSchematicInfo(id: number) {
|
||||
return fetchWithToken(get(tokenStore), "/schem/info/" + id).then(r => r.json()).then(r => SchematicInfoSchema.parse(r))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await schemInfo}
|
||||
<Modal title="Loading" open>
|
||||
<Spinner />
|
||||
</Modal>
|
||||
{:then info}
|
||||
<Modal title={info.schem.name} autoclose open>
|
||||
<p>Path: {info.path}</p>
|
||||
<p class="flex !mt-0">
|
||||
Replace Color:
|
||||
{#if info.schem.replaceColor}
|
||||
<CheckSolid class="text-green-500 ml-2" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500 ml-2" />
|
||||
{/if}
|
||||
</p>
|
||||
<p class="flex !mt-0">
|
||||
Allow Replay: {#if info.schem.allowReplay}
|
||||
<CheckSolid class="text-green-500 ml-2" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500 ml-2" />
|
||||
{/if}
|
||||
</p>
|
||||
<p class="!mt-0">Type: {info.schem.type ?? "Directory"}</p>
|
||||
<p class="!mt-0">Updated: {new Intl.DateTimeFormat(astroI18n.locale, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
}).format(moment(info.schem.lastUpdate).utc(false).toDate())}</p>
|
||||
<p class="!mt-0">Item: {info.schem.item ?? (info.schem.type == null ? "CHEST" : "CAULDRON_ITEM")}</p>
|
||||
{#if info.members.length !== 0}
|
||||
<p class="!mt-0">Member: {info.members.join(", ")}</p>
|
||||
{/if}
|
||||
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
|
||||
</Modal>
|
||||
{:catch e}
|
||||
<Modal title="Error" open>
|
||||
<p>{e.message}</p>
|
||||
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
|
||||
</Modal>
|
||||
{/await}
|
||||
33
src/components/dashboard/SchematicInfo.svelte
Normal file
33
src/components/dashboard/SchematicInfo.svelte
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import {t} from "astro-i18n"
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import {schemRepo} from "../repo/repo.ts";
|
||||
import {Modal, Spinner} from "flowbite-svelte";
|
||||
import {astroI18n} from "astro-i18n";
|
||||
import moment from "moment/moment";
|
||||
import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons";
|
||||
import SchematicInfoModal from "./SchematicInfoModal.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let schematicId: number;
|
||||
|
||||
let schemInfo = getSchematicInfo(schematicId);
|
||||
|
||||
function getSchematicInfo(id: number) {
|
||||
return $schemRepo.getSchematicInfo(id);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await schemInfo}
|
||||
<Modal title="Loading" open on:close={() => dispatch("reset")}>
|
||||
<Spinner />
|
||||
</Modal>
|
||||
{:then info}
|
||||
<SchematicInfoModal {info} on:reset />
|
||||
{:catch e}
|
||||
<Modal title="Error" open on:close={() => dispatch("reset")}>
|
||||
<p>{e.message}</p>
|
||||
<button class="btn !ml-auto" slot="footer" on:click={() => dispatch("reset")}>Close</button>
|
||||
</Modal>
|
||||
{/await}
|
||||
56
src/components/dashboard/SchematicInfoModal.svelte
Normal file
56
src/components/dashboard/SchematicInfoModal.svelte
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import {astroI18n, t} from "astro-i18n";
|
||||
import moment from "moment";
|
||||
import {CheckSolid, XCircleOutline} from "flowbite-svelte-icons";
|
||||
import {Modal} from "flowbite-svelte";
|
||||
import type {SchematicInfo} from "../types/schem.ts";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import {schemRepo} from "../repo/repo.ts";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let info: SchematicInfo;
|
||||
|
||||
async function download() {
|
||||
const code = await $schemRepo.createDownload(info.schem.id);
|
||||
window.open(import.meta.env.PUBLIC_API_SERVER + "/download/" + code.code, "_blank")
|
||||
dispatch("reset")
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title={info.schem.name} autoclose open on:close={() => dispatch("reset")}>
|
||||
<p>{t("dashboard.schematic.info.path", {path: info.path})}</p>
|
||||
<p class="flex !mt-0">
|
||||
{t("dashboard.schematic.info.replaceColor")}
|
||||
{#if info.schem.replaceColor}
|
||||
<CheckSolid class="text-green-500 ml-2" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500 ml-2" />
|
||||
{/if}
|
||||
</p>
|
||||
<p class="flex !mt-0">
|
||||
{t("dashboard.schematic.info.allowReplay")}
|
||||
{#if info.schem.allowReplay}
|
||||
<CheckSolid class="text-green-500 ml-2" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500 ml-2" />
|
||||
{/if}
|
||||
</p>
|
||||
<p class="!mt-0">{t("dashboard.schematic.info.type", {type: info.schem.type ?? t("dashboard.schematic.dir")})}</p>
|
||||
<p class="!mt-0">{t("dashboard.schematic.info.updated", {updated: new Intl.DateTimeFormat(astroI18n.locale, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
}).format(moment(info.schem.lastUpdate).utc(false).toDate())})}</p>
|
||||
<p class="!mt-0">{t("dashboard.schematic.info.item", {item: info.schem.item ?? (info.schem.type == null ? "CHEST" : "CAULDRON_ITEM")})}</p>
|
||||
{#if info.members.length !== 0}
|
||||
<p class="!mt-0">{t("dashboard.schematic.info.members", {members: info.members.join(", ")})}</p>
|
||||
{/if}
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="btn !ml-auto" on:click={download}>{t("dashboard.schematic.info.btn.download")}</button>
|
||||
<button class="btn" on:click={() => dispatch("reset")}>{t("dashboard.schematic.info.btn.close")}</button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
115
src/components/dashboard/SchematicList.svelte
Normal file
115
src/components/dashboard/SchematicList.svelte
Normal file
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import {t} from "astro-i18n";
|
||||
import {ChevronDoubleRightOutline, FolderOutline, HomeOutline, InfoCircleOutline} from "flowbite-svelte-icons";
|
||||
import SchematicListTile from "./SchematicListTile.svelte";
|
||||
import {Breadcrumb, BreadcrumbItem, Tooltip} from "flowbite-svelte";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import type {SchematicList} from "../types/schem.ts";
|
||||
import SchematicInfo from "./SchematicInfo.svelte";
|
||||
import UploadModal from "./UploadModal.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let schematics: SchematicList;
|
||||
|
||||
let uploadOpen = false;
|
||||
let infoModalId: number | null = null;
|
||||
|
||||
function schemListClick(isDir: boolean, id: number) {
|
||||
if (isDir) {
|
||||
return () => dispatch("to", {id})
|
||||
} else {
|
||||
return () => infoModalId = id
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<Breadcrumb navClass="py-4">
|
||||
<BreadcrumbItem home>
|
||||
<svelte:fragment slot="icon">
|
||||
<HomeOutline class="w-6 h-6 mx-2 dark:text-white" />
|
||||
</svelte:fragment>
|
||||
<span on:click={() => dispatch("reset")} class="hover:underline hover:cursor-pointer text-2xl">{t("dashboard.schematic.home")}</span>
|
||||
</BreadcrumbItem>
|
||||
{#each schematics.breadcrumbs as bread}
|
||||
<BreadcrumbItem>
|
||||
<svelte:fragment slot="icon">
|
||||
<ChevronDoubleRightOutline class="w-4 h-4 mx-2 dark:text-white" />
|
||||
</svelte:fragment>
|
||||
<span on:click={() => dispatch("to", {id: bread.id})} class="hover:underline hover:cursor-pointer text-2xl">{bread.name}</span>
|
||||
</BreadcrumbItem>
|
||||
{/each}
|
||||
</Breadcrumb>
|
||||
<div class="flex flex-col justify-center">
|
||||
<button class="btn" on:click={() => uploadOpen = true}>
|
||||
{t("dashboard.schematic.upload")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr class="!cursor-auto">
|
||||
<th>{t("dashboard.schematic.head.type")}</th>
|
||||
<th>{t("dashboard.schematic.head.name")}</th>
|
||||
<th class="hidden sm:table-cell">{t("dashboard.schematic.head.owner")}</th>
|
||||
<th class="hidden sm:table-cell"></th>
|
||||
<th class="hidden md:table-cell">{t("dashboard.schematic.head.updated")}</th>
|
||||
<th>
|
||||
<InfoCircleOutline />
|
||||
<Tooltip>
|
||||
<span>{t("dashboard.schematic.head.replaceColor")}</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
<th>
|
||||
<InfoCircleOutline />
|
||||
<Tooltip>
|
||||
<span>{t("dashboard.schematic.head.allowReplay")}</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
</tr>
|
||||
{#if schematics.breadcrumbs.length !== 0}
|
||||
<tr on:click|preventDefault={() => {
|
||||
if (schematics.breadcrumbs.length === 1) {
|
||||
dispatch("reset")
|
||||
} else {
|
||||
dispatch("to", {id: schematics.breadcrumbs[schematics.breadcrumbs.length - 2].id})
|
||||
}
|
||||
}}>
|
||||
<th>
|
||||
<FolderOutline />
|
||||
</th>
|
||||
<th>../</th>
|
||||
<th class="hidden sm:table-cell"></th>
|
||||
<th class="hidden sm:table-cell">{t("dashboard.schematic.dir")}</th>
|
||||
<th class="hidden md:table-cell"></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{/if}
|
||||
{#each schematics.schematics as schem}
|
||||
<SchematicListTile schem={schem} players={schematics.players} on:click={schemListClick(schem.type == null, schem.id)} />
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<UploadModal bind:open={uploadOpen} on:refresh />
|
||||
|
||||
{#if infoModalId !== null}
|
||||
<SchematicInfo schematicId={infoModalId} on:reset={() => infoModalId = null} />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
tr {
|
||||
@apply transition-colors cursor-pointer border-b
|
||||
dark:hover:bg-gray-800 hover:bg-gray-300;
|
||||
}
|
||||
|
||||
th {
|
||||
@apply text-left py-4 md:px-2;
|
||||
}
|
||||
</style>
|
||||
58
src/components/dashboard/SchematicListTile.svelte
Normal file
58
src/components/dashboard/SchematicListTile.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import {astroI18n, t} from "astro-i18n";
|
||||
import moment from "moment/moment.js";
|
||||
import {CheckSolid, FileOutline, FolderOutline, XCircleOutline} from "flowbite-svelte-icons";
|
||||
import type {Schematic} from "../types/schem.ts";
|
||||
import type {Player} from "../types/data.ts";
|
||||
|
||||
export let schem: Schematic;
|
||||
export let players: Record<number, Player>;
|
||||
</script>
|
||||
|
||||
<tr on:click|preventDefault>
|
||||
<th>
|
||||
{#if schem.type == null}
|
||||
<FolderOutline />
|
||||
{:else}
|
||||
<FileOutline />
|
||||
{/if}
|
||||
</th>
|
||||
<th>
|
||||
{schem.name}{#if schem.type == null}/{/if}
|
||||
</th>
|
||||
<th class="hidden sm:table-cell">{players[schem.owner].name}</th>
|
||||
<th class="hidden sm:table-cell">{schem.type ?? t("dashboard.schematic.dir")}</th>
|
||||
<th class="hidden md:table-cell">{new Intl.DateTimeFormat(astroI18n.locale, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
}).format(moment(schem.lastUpdate).utc(false).toDate())}</th>
|
||||
<th>
|
||||
{#if schem.replaceColor}
|
||||
<CheckSolid class="text-green-500" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500" />
|
||||
{/if}
|
||||
</th>
|
||||
<th>
|
||||
{#if schem.allowReplay}
|
||||
<CheckSolid class="text-green-500" />
|
||||
{:else}
|
||||
<XCircleOutline class="text-red-500" />
|
||||
{/if}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<style lang="scss">
|
||||
tr {
|
||||
@apply transition-colors cursor-pointer border-b
|
||||
dark:hover:bg-gray-800 hover:bg-gray-300;
|
||||
}
|
||||
|
||||
th {
|
||||
@apply text-left py-4 md:px-2;
|
||||
}
|
||||
</style>
|
||||
47
src/components/dashboard/UploadModal.svelte
Normal file
47
src/components/dashboard/UploadModal.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import {Modal} from "flowbite-svelte";
|
||||
import {schemRepo} from "../repo/repo.js";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let open = false;
|
||||
|
||||
async function upload() {
|
||||
if (uploadFile == null) {
|
||||
return
|
||||
}
|
||||
let file = uploadFile[0];
|
||||
|
||||
let name = file.name;
|
||||
|
||||
let type = name.split(".").pop();
|
||||
|
||||
if (type !== "schem" && type !== "schematic") {
|
||||
return
|
||||
}
|
||||
|
||||
let content = await file.arrayBuffer();
|
||||
|
||||
// @ts-ignore
|
||||
let b64 = btoa(String.fromCharCode.apply(null, new Uint8Array(content)));
|
||||
|
||||
await $schemRepo.uploadSchematic(name, b64);
|
||||
|
||||
open = false;
|
||||
uploadFile = null;
|
||||
dispatch("reset")
|
||||
}
|
||||
|
||||
let uploadFile: File[] | null = null;
|
||||
</script>
|
||||
|
||||
<Modal title="Upload Schematic" bind:open autoclose outsideclose>
|
||||
<form>
|
||||
<input type="file" bind:files={uploadFile} />
|
||||
</form>
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="btn !ml-auto" on:click={upload}>Upload</button>
|
||||
<button class="btn btn-gray" on:click={() => open = false}>Close</button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
41
src/components/dashboard/UserInfo.svelte
Normal file
41
src/components/dashboard/UserInfo.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import {t} from "astro-i18n";
|
||||
import type {Player} from "../types/data.ts";
|
||||
import {tokenStore} from "../repo/repo.ts";
|
||||
import {l} from "../../util/util.ts";
|
||||
|
||||
export let user: Player;
|
||||
|
||||
function logout() {
|
||||
tokenStore.set("")
|
||||
window.location.href = l("/login")
|
||||
}
|
||||
</script>
|
||||
<div class="flex mb-4 flex-col md:flex-row">
|
||||
<div>
|
||||
<div class="bg-zinc-50 border-gray-100 py-24 px-12 border-2 m-2 transition duration-300 ease-in-out rounded-xl shadow-lg hidden md:block
|
||||
hover:scale-105 hover:shadow-2xl
|
||||
dark:bg-neutral-900 dark:border-gray-800 dark:text-white">
|
||||
<figure>
|
||||
<figcaption class="text-center mb-4 text-2xl">{user.name}</figcaption>
|
||||
<img src={`https://visage.surgeplay.com/bust/150/${user.uuid}`} class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl" alt={user.name + "s bust"} width="150" height="150" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<button class="btn mt-2" on:click={logout}>{t("dashboard.buttons.logout")}</button>
|
||||
{#if user.perms.includes("MODERATION")}
|
||||
<a class="btn w-fit mt-2" href="/admin">{t("dashboard.buttons.admin")}</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold">{t("dashboard.title", {name: user.name})}</h1>
|
||||
<p>{t("dashboard.rank", {rank: t("home.prefix." + user.prefix)})}</p>
|
||||
<p>{t("dashboard.permissions")}</p>
|
||||
<ul>
|
||||
{#each user.perms as permission}
|
||||
<li class="list-disc ml-6">{permission}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
import type {Server} from "../types/data.ts";
|
||||
import {ServerSchema} from "../types/data.ts";
|
||||
import type {Player, Server} from "../types/data.ts";
|
||||
import {PlayerSchema, ServerSchema} from "../types/data.ts";
|
||||
import {fetchWithToken} from "./repo.ts";
|
||||
|
||||
export class DataRepo {
|
||||
@@ -8,4 +8,8 @@ export class DataRepo {
|
||||
public async getServer(): Promise<Server> {
|
||||
return await fetchWithToken(this.token, "/data/server").then(value => value.json()).then(value => ServerSchema.parse(value));
|
||||
}
|
||||
|
||||
public async getMe(): Promise<Player> {
|
||||
return await fetchWithToken(this.token, "/data/me").then(value => value.json()).then(value => PlayerSchema.parse(value));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import type {Page, PageList} from "../types/page.ts";
|
||||
import {fetchWithToken} from "./repo.ts";
|
||||
import {PageListSchema, PageSchema} from "../types/page.ts";
|
||||
import {bytesToBase64} from "../admin/util.ts";
|
||||
import {branches} from "../stores/stores.ts";
|
||||
|
||||
export class PageRepo {
|
||||
constructor(private token: string) {}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {PageRepo} from "./page.ts";
|
||||
import {DataRepo} from "./data.ts";
|
||||
|
||||
import { AES, enc, format } from "crypto-js";
|
||||
import {SchematicRepo} from "./schem.ts";
|
||||
|
||||
export { EventRepo } from "./event.js"
|
||||
|
||||
@@ -29,3 +30,4 @@ export const fightRepo = derived(tokenStore, ($token) => new FightRepo($token))
|
||||
export const permsRepo = derived(tokenStore, ($token) => new PermsRepo($token))
|
||||
export const pageRepo = derived(tokenStore, ($token) => new PageRepo($token))
|
||||
export const dataRepo = derived(tokenStore, ($token) => new DataRepo($token))
|
||||
export const schemRepo = derived(tokenStore, ($token) => new SchematicRepo($token))
|
||||
|
||||
33
src/components/repo/schem.ts
Normal file
33
src/components/repo/schem.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {fetchWithToken} from "./repo.ts";
|
||||
import type {SchematicCode, SchematicInfo, SchematicList} from "../types/schem.ts";
|
||||
import {SchematicCodeSchema, SchematicInfoSchema, SchematicListSchema} from "../types/schem.ts";
|
||||
|
||||
export class SchematicRepo {
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async getRootSchematicList(): Promise<SchematicList> {
|
||||
return await fetchWithToken(this.token, "/schem").then(value => value.json()).then(value => SchematicListSchema.parse(value));
|
||||
}
|
||||
|
||||
public async getSchematicList(id: number): Promise<SchematicList> {
|
||||
return await fetchWithToken(this.token, `/schem/${id}/list`).then(value => value.json()).then(value => SchematicListSchema.parse(value));
|
||||
}
|
||||
|
||||
public async getSchematicInfo(id: number): Promise<SchematicInfo> {
|
||||
return await fetchWithToken(this.token, `/schem/${id}`).then(value => value.json()).then(value => SchematicInfoSchema.parse(value));
|
||||
}
|
||||
|
||||
public async createDownload(id: number): Promise<SchematicCode> {
|
||||
return await fetchWithToken(this.token, `/schem/${id}/download`).then(value => value.json()).then(value => SchematicCodeSchema.parse(value));
|
||||
}
|
||||
|
||||
public async uploadSchematic(name: string, content: string) {
|
||||
return await fetchWithToken(this.token, `/schem`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
content
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import type {Player, SchematicType, Server} from "../types/data.js";
|
||||
import {PlayerSchema, ServerSchema} from "../types/data.js";
|
||||
import type {Player, SchematicType} from "../types/data.js";
|
||||
import {PlayerSchema} from "../types/data.js";
|
||||
import {cached, cachedFamily} from "./cached.js";
|
||||
import type {Team} from "../types/team.js";
|
||||
import {TeamSchema} from "../types/team.js";
|
||||
import {derived, get, readable, writable} from "svelte/store";
|
||||
import {derived, get, writable} from "svelte/store";
|
||||
import {dataRepo, fetchWithToken, pageRepo, tokenStore} from "../repo/repo.js";
|
||||
import {z} from "zod";
|
||||
|
||||
@@ -47,7 +47,7 @@ export const branches = cached<string[]>([], async () => {
|
||||
return z.array(z.string()).parse(res);
|
||||
})
|
||||
|
||||
export const server = readable(new Promise((resolve) => {}), (set) => set(get(dataRepo).getServer()));
|
||||
export const server = derived(dataRepo, $dataRepo => $dataRepo.getServer());
|
||||
|
||||
export const isWide = writable(window.innerWidth >= 640);
|
||||
window.addEventListener("resize", () => isWide.set(window.innerWidth >= 640));
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type {Team} from "./team.js";
|
||||
import type {Player} from "./data.js";
|
||||
import {z} from "zod";
|
||||
import {TeamSchema} from "./team.js";
|
||||
import {PlayerSchema} from "./data.js";
|
||||
|
||||
@@ -32,4 +32,12 @@ export const SchematicInfoSchema = z.object({
|
||||
schem: SchematicSchema
|
||||
})
|
||||
|
||||
export type SchematicInfo = z.infer<typeof SchematicInfoSchema>
|
||||
export type SchematicInfo = z.infer<typeof SchematicInfoSchema>
|
||||
|
||||
export const SchematicCodeSchema = z.object({
|
||||
id: z.number().gte(0),
|
||||
code: z.string(),
|
||||
expires: z.number().positive()
|
||||
})
|
||||
|
||||
export type SchematicCode = z.infer<typeof SchematicCodeSchema>
|
||||
Reference in New Issue
Block a user