feat: flag management
This commit is contained in:
parent
8bd4449804
commit
d9d48c3cbf
24 changed files with 615 additions and 235 deletions
|
@ -1,7 +1,8 @@
|
|||
import { apiRequest } from "$api";
|
||||
import ApiError, { ErrorCode } from "$api/error.js";
|
||||
import type { PartialMember, User, UserWithMembers } from "$api/models";
|
||||
import type { UserWithMembers } from "$api/models";
|
||||
import log from "$lib/log.js";
|
||||
import paginate from "$lib/paginate";
|
||||
import { error } from "@sveltejs/kit";
|
||||
|
||||
const MEMBERS_PER_PAGE = 20;
|
||||
|
@ -20,22 +21,11 @@ export const load = async ({ params, fetch, cookies, url }) => {
|
|||
throw e;
|
||||
}
|
||||
|
||||
// Paginate members on the server side
|
||||
let currentPage = 0;
|
||||
let pageCount = 0;
|
||||
let members: PartialMember[] = [];
|
||||
if (user.members) {
|
||||
currentPage = Number(url.searchParams.get("page") || "0");
|
||||
pageCount = Math.ceil(user.members.length / MEMBERS_PER_PAGE);
|
||||
members = user.members.slice(
|
||||
currentPage * MEMBERS_PER_PAGE,
|
||||
(currentPage + 1) * MEMBERS_PER_PAGE,
|
||||
);
|
||||
if (members.length === 0) {
|
||||
members = user.members.slice(0, MEMBERS_PER_PAGE);
|
||||
currentPage = 0;
|
||||
}
|
||||
}
|
||||
const { data, currentPage, pageCount } = paginate(
|
||||
user.members,
|
||||
url.searchParams.get("page"),
|
||||
MEMBERS_PER_PAGE,
|
||||
);
|
||||
|
||||
return { user, members, currentPage, pageCount };
|
||||
return { user, members: data, currentPage, pageCount };
|
||||
};
|
||||
|
|
42
Foxnouns.Frontend/src/routes/settings/flags/+page.server.ts
Normal file
42
Foxnouns.Frontend/src/routes/settings/flags/+page.server.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { apiRequest, fastRequest } from "$api";
|
||||
import ApiError from "$api/error";
|
||||
import type { PrideFlag } from "$api/models/user";
|
||||
import log from "$lib/log";
|
||||
import { encode } from "base64-arraybuffer";
|
||||
|
||||
export const load = async ({ url, fetch, cookies }) => {
|
||||
const resp = await apiRequest<PrideFlag[]>("GET", "/users/@me/flags", { fetch, cookies });
|
||||
|
||||
return {
|
||||
flags: resp,
|
||||
};
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
upload: async ({ request, fetch, cookies }) => {
|
||||
const body = await request.formData();
|
||||
const name = body.get("name") as string;
|
||||
const description = body.get("desc") as string;
|
||||
const image = body.get("image") as File;
|
||||
|
||||
const buffer = await image.arrayBuffer();
|
||||
const base64 = encode(buffer);
|
||||
|
||||
try {
|
||||
await fastRequest("POST", "/users/@me/flags", {
|
||||
body: {
|
||||
name,
|
||||
description: description ? description : null,
|
||||
image: `data:${image.type};base64,${base64}`,
|
||||
},
|
||||
fetch,
|
||||
cookies,
|
||||
});
|
||||
return { ok: true, error: null };
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) return { ok: false, error: e.obj };
|
||||
log.error("error uploading flag:", e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
};
|
121
Foxnouns.Frontend/src/routes/settings/flags/+page.svelte
Normal file
121
Foxnouns.Frontend/src/routes/settings/flags/+page.svelte
Normal file
|
@ -0,0 +1,121 @@
|
|||
<script lang="ts">
|
||||
import { Accordion, AccordionItem } from "@sveltestrap/sveltestrap";
|
||||
import type { ActionData, PageData } from "./$types";
|
||||
import EditorFlagImage from "$components/editor/EditorFlagImage.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
import type { PrideFlag } from "$api/models";
|
||||
import paginate from "$lib/paginate";
|
||||
import ClientPaginator from "$components/ClientPaginator.svelte";
|
||||
import FlagEditor from "$components/editor/FlagEditor.svelte";
|
||||
import NoscriptWarning from "$components/editor/NoscriptWarning.svelte";
|
||||
import { fastRequest } from "$api";
|
||||
import type { RawApiError } from "$api/error";
|
||||
import ApiError from "$api/error";
|
||||
import log from "$lib/log";
|
||||
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
|
||||
|
||||
type Props = { data: PageData; form: ActionData };
|
||||
let { data, form }: Props = $props();
|
||||
|
||||
let flags = $state(data.flags);
|
||||
|
||||
let arr: PrideFlag[] = $state([]);
|
||||
let currentPage = $state(0);
|
||||
let pageCount = $state(0);
|
||||
|
||||
const FLAGS_PER_PAGE = 50;
|
||||
|
||||
$effect(() => {
|
||||
const pages = paginate(flags, currentPage, FLAGS_PER_PAGE);
|
||||
arr = pages.data;
|
||||
pageCount = pages.pageCount;
|
||||
});
|
||||
|
||||
let lastEditedFlag: string | null = $state(null);
|
||||
let ok: { ok: boolean; error: RawApiError | null } | null = $state(null);
|
||||
|
||||
const update = async (
|
||||
id: string,
|
||||
{ name, description }: { name: string; description: string | null },
|
||||
) => {
|
||||
lastEditedFlag = id;
|
||||
try {
|
||||
await fastRequest("PATCH", `/users/@me/flags/${id}`, {
|
||||
body: { name, description },
|
||||
token: data.token,
|
||||
});
|
||||
ok = { ok: true, error: null };
|
||||
|
||||
const idx = flags.findIndex((f) => f.id === id);
|
||||
if (idx === -1) return;
|
||||
console.log("yippee");
|
||||
flags[idx] = { ...flags[idx], name, description };
|
||||
} catch (e) {
|
||||
log.error("Could not update flag %s:", id, e);
|
||||
if (e instanceof ApiError) ok = { ok: false, error: e.obj };
|
||||
}
|
||||
};
|
||||
|
||||
const deleteFlag = async (id: string) => {
|
||||
lastEditedFlag = id;
|
||||
try {
|
||||
await fastRequest("DELETE", `/users/@me/flags/${id}`, { token: data.token });
|
||||
ok = { ok: true, error: null };
|
||||
|
||||
const idx = flags.findIndex((f) => f.id === id);
|
||||
if (idx === -1) return;
|
||||
flags.splice(idx, 1);
|
||||
flags = [...flags];
|
||||
} catch (e) {
|
||||
log.error("Could not remove flag %s:", id, e);
|
||||
if (e instanceof ApiError) ok = { ok: false, error: e.obj };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<h3>{$t("settings.flag-title")}</h3>
|
||||
|
||||
<NoscriptWarning />
|
||||
|
||||
<form method="POST" action="?/upload" enctype="multipart/form-data">
|
||||
<FormStatusMarker
|
||||
{form}
|
||||
successMessage="Successfully uploaded your flag! It may take a few seconds before it's saved."
|
||||
/>
|
||||
<h4>{$t("settings.flag-upload-title")}</h4>
|
||||
<input
|
||||
type="file"
|
||||
name="image"
|
||||
accept="image/png, image/jpeg, image/gif, image/webp"
|
||||
class="mb-2 form-control"
|
||||
required
|
||||
/>
|
||||
<input class="mb-2 form-control" name="name" placeholder="Name" autocomplete="off" required />
|
||||
<input class="mb-2 form-control" name="desc" placeholder="Description" autocomplete="off" />
|
||||
<button type="submit" class="btn btn-primary">{$t("settings.flag-upload-button")}</button>
|
||||
</form>
|
||||
|
||||
<h4 class="mt-3">
|
||||
{$t("settings.flag-current-flags-title", {
|
||||
count: data.flags.length,
|
||||
max: data.meta.limits.max_flags,
|
||||
})}
|
||||
</h4>
|
||||
|
||||
<ClientPaginator center bind:currentPage {pageCount} />
|
||||
|
||||
<Accordion>
|
||||
{#each arr as flag (flag.id)}
|
||||
<AccordionItem>
|
||||
<span slot="header">
|
||||
<EditorFlagImage {flag} />
|
||||
{flag.name}
|
||||
</span>
|
||||
|
||||
{#if lastEditedFlag === flag.id}<FormStatusMarker form={ok} />{/if}
|
||||
<FlagEditor {flag} {update} {deleteFlag} />
|
||||
</AccordionItem>
|
||||
{/each}
|
||||
</Accordion>
|
||||
|
||||
<ClientPaginator center bind:currentPage {pageCount} />
|
|
@ -1,18 +1,15 @@
|
|||
import paginate from "$lib/paginate";
|
||||
|
||||
const MEMBERS_PER_PAGE = 15;
|
||||
|
||||
export const load = async ({ url, parent }) => {
|
||||
const { user } = await parent();
|
||||
|
||||
let currentPage = Number(url.searchParams.get("page") || "0");
|
||||
let pageCount = Math.ceil(user.members.length / MEMBERS_PER_PAGE);
|
||||
let members = user.members.slice(
|
||||
currentPage * MEMBERS_PER_PAGE,
|
||||
(currentPage + 1) * MEMBERS_PER_PAGE,
|
||||
const { data, currentPage, pageCount } = paginate(
|
||||
user.members,
|
||||
url.searchParams.get("page"),
|
||||
MEMBERS_PER_PAGE,
|
||||
);
|
||||
if (members.length === 0) {
|
||||
members = user.members.slice(0, MEMBERS_PER_PAGE);
|
||||
currentPage = 0;
|
||||
}
|
||||
|
||||
return { members, currentPage, pageCount };
|
||||
return { members: data, currentPage, pageCount };
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue