Use logo as default avatar

This commit is contained in:
Kieran 2023-12-25 18:56:26 +00:00
parent 9837d917b9
commit 097f97edc1
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
9 changed files with 217 additions and 214 deletions

View File

@ -1,192 +1,192 @@
import { import {
AdminProfile, AdminProfile,
AdminUserListResult, AdminUserListResult,
ApiError, ApiError,
ApiKey, ApiKey,
LoginSession, LoginSession,
PagedRequest, PagedRequest,
PagedResponse, PagedResponse,
PaymentOrder, PaymentOrder,
Profile, Profile,
SetPaymentConfigRequest, SetPaymentConfigRequest,
SiteInfoResponse, SiteInfoResponse,
VoidFileResponse, VoidFileResponse,
} from "./index"; } from "./index";
import { import {
ProgressHandler, ProgressHandler,
ProxyChallengeHandler, ProxyChallengeHandler,
StateChangeHandler, StateChangeHandler,
VoidUploader, VoidUploader,
} from "./upload"; } from "./upload";
import {StreamUploader} from "./stream-uploader"; import { StreamUploader } from "./stream-uploader";
import {XHRUploader} from "./xhr-uploader"; import { XHRUploader } from "./xhr-uploader";
export type AuthHandler = (url: string, method: string) => Promise<string>; export type AuthHandler = (url: string, method: string) => Promise<string>;
export class VoidApi { export class VoidApi {
readonly #uri: string; readonly #uri: string;
readonly #auth?: AuthHandler; readonly #auth?: AuthHandler;
constructor(uri?: string, auth?: AuthHandler) { constructor(uri?: string, auth?: AuthHandler) {
this.#uri = uri ?? ""; this.#uri = uri ?? "";
this.#auth = auth; this.#auth = auth;
}
async #req<T>(method: string, url: string, body?: object): Promise<T> {
const absoluteUrl = `${this.#uri}${url}`;
const headers: HeadersInit = {
Accept: "application/json",
};
if (this.#auth) {
headers["Authorization"] = await this.#auth(absoluteUrl, method);
}
if (body) {
headers["Content-Type"] = "application/json";
} }
async #req<T>(method: string, url: string, body?: object): Promise<T> { const res = await fetch(absoluteUrl, {
const absoluteUrl = `${this.#uri}${url}`; method,
const headers: HeadersInit = { headers,
Accept: "application/json", mode: "cors",
}; body: body ? JSON.stringify(body) : undefined,
if (this.#auth) { });
headers["Authorization"] = await this.#auth(absoluteUrl, method); const text = await res.text();
} if (res.ok) {
if (body) { return text ? (JSON.parse(text) as T) : ({} as T);
headers["Content-Type"] = "application/json"; } else {
} throw new ApiError(res.status, text);
const res = await fetch(absoluteUrl, {
method,
headers,
mode: "cors",
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
if (res.ok) {
return text ? (JSON.parse(text) as T) : ({} as T);
} else {
throw new ApiError(res.status, text);
}
} }
}
/** /**
* Get uploader for uploading files * Get uploader for uploading files
*/ */
getUploader( getUploader(
file: File | Blob, file: File | Blob,
stateChange?: StateChangeHandler, stateChange?: StateChangeHandler,
progress?: ProgressHandler, progress?: ProgressHandler,
proxyChallenge?: ProxyChallengeHandler, proxyChallenge?: ProxyChallengeHandler,
chunkSize?: number, chunkSize?: number,
): VoidUploader { ): VoidUploader {
if (StreamUploader.canUse()) { if (StreamUploader.canUse()) {
return new StreamUploader( return new StreamUploader(
this.#uri, this.#uri,
file, file,
stateChange, stateChange,
progress, progress,
proxyChallenge, proxyChallenge,
this.#auth, this.#auth,
chunkSize, chunkSize,
); );
} else { } else {
return new XHRUploader( return new XHRUploader(
this.#uri, this.#uri,
file, file,
stateChange, stateChange,
progress, progress,
proxyChallenge, proxyChallenge,
this.#auth, this.#auth,
chunkSize, chunkSize,
); );
}
} }
}
/** /**
* General site information * General site information
*/ */
info() { info() {
return this.#req<SiteInfoResponse>("GET", "/info"); return this.#req<SiteInfoResponse>("GET", "/info");
} }
fileInfo(id: string) { fileInfo(id: string) {
return this.#req<VoidFileResponse>("GET", `/upload/${id}`); return this.#req<VoidFileResponse>("GET", `/upload/${id}`);
} }
setPaymentConfig(id: string, cfg: SetPaymentConfigRequest) { setPaymentConfig(id: string, cfg: SetPaymentConfigRequest) {
return this.#req("POST", `/upload/${id}/payment`, cfg); return this.#req("POST", `/upload/${id}/payment`, cfg);
} }
createOrder(id: string) { createOrder(id: string) {
return this.#req<PaymentOrder>("GET", `/upload/${id}/payment`); return this.#req<PaymentOrder>("GET", `/upload/${id}/payment`);
} }
getOrder(file: string, order: string) { getOrder(file: string, order: string) {
return this.#req<PaymentOrder>("GET", `/upload/${file}/payment/${order}`); return this.#req<PaymentOrder>("GET", `/upload/${file}/payment/${order}`);
} }
login(username: string, password: string, captcha?: string) { login(username: string, password: string, captcha?: string) {
return this.#req<LoginSession>("POST", `/auth/login`, { return this.#req<LoginSession>("POST", `/auth/login`, {
username, username,
password, password,
captcha, captcha,
}); });
} }
register(username: string, password: string, captcha?: string) { register(username: string, password: string, captcha?: string) {
return this.#req<LoginSession>("POST", `/auth/register`, { return this.#req<LoginSession>("POST", `/auth/register`, {
username, username,
password, password,
captcha, captcha,
}); });
} }
getUser(id: string) { getUser(id: string) {
return this.#req<Profile>("GET", `/user/${id}`); return this.#req<Profile>("GET", `/user/${id}`);
} }
updateUser(u: Profile) { updateUser(u: Profile) {
return this.#req<void>("POST", `/user/${u.id}`, u); return this.#req<void>("POST", `/user/${u.id}`, u);
} }
listUserFiles(uid: string, pageReq: PagedRequest) { listUserFiles(uid: string, pageReq: PagedRequest) {
return this.#req<PagedResponse<VoidFileResponse>>( return this.#req<PagedResponse<VoidFileResponse>>(
"POST", "POST",
`/user/${uid}/files`, `/user/${uid}/files`,
pageReq, pageReq,
); );
} }
submitVerifyCode(uid: string, code: string) { submitVerifyCode(uid: string, code: string) {
return this.#req<void>("POST", `/user/${uid}/verify`, {code}); return this.#req<void>("POST", `/user/${uid}/verify`, { code });
} }
sendNewCode(uid: string) { sendNewCode(uid: string) {
return this.#req<void>("GET", `/user/${uid}/verify`); return this.#req<void>("GET", `/user/${uid}/verify`);
} }
updateFileMetadata(id: string, meta: any) { updateFileMetadata(id: string, meta: any) {
return this.#req<void>("POST", `/upload/${id}/meta`, meta); return this.#req<void>("POST", `/upload/${id}/meta`, meta);
} }
listApiKeys() { listApiKeys() {
return this.#req<Array<ApiKey>>("GET", `/auth/api-key`); return this.#req<Array<ApiKey>>("GET", `/auth/api-key`);
} }
createApiKey(req: any) { createApiKey(req: any) {
return this.#req<ApiKey>("POST", `/auth/api-key`, req); return this.#req<ApiKey>("POST", `/auth/api-key`, req);
} }
adminListFiles(pageReq: PagedRequest) { adminListFiles(pageReq: PagedRequest) {
return this.#req<PagedResponse<VoidFileResponse>>( return this.#req<PagedResponse<VoidFileResponse>>(
"POST", "POST",
"/admin/file", "/admin/file",
pageReq, pageReq,
); );
} }
adminDeleteFile(id: string) { adminDeleteFile(id: string) {
return this.#req<void>("DELETE", `/admin/file/${id}`); return this.#req<void>("DELETE", `/admin/file/${id}`);
} }
adminUserList(pageReq: PagedRequest) { adminUserList(pageReq: PagedRequest) {
return this.#req<PagedResponse<AdminUserListResult>>( return this.#req<PagedResponse<AdminUserListResult>>(
"POST", "POST",
`/admin/users`, `/admin/users`,
pageReq, pageReq,
); );
} }
adminUpdateUser(u: AdminProfile) { adminUpdateUser(u: AdminProfile) {
return this.#req<void>("POST", `/admin/update-user`, u); return this.#req<void>("POST", `/admin/update-user`, u);
} }
} }

View File

@ -1,18 +1,18 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8" />
<link rel="icon" href="/logo_32.png"/> <link rel="icon" href="/logo_32.png" />
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000"/> <meta name="theme-color" content="#000000" />
<meta name="description" content="void.cat - free, simple file sharing."/> <meta name="description" content="void.cat - free, simple file sharing." />
<link rel="apple-touch-icon" href="/logo_256.png"/> <link rel="apple-touch-icon" href="/logo_256.png" />
<link rel="manifest" href="/manifest.json"/> <link rel="manifest" href="/manifest.json" />
<title>void.cat</title> <title>void.cat</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<script src="./src/index.tsx" type="module"></script> <script src="./src/index.tsx" type="module"></script>
</body> </body>
</html> </html>

View File

@ -34,7 +34,12 @@ const router = createBrowserRouter([
path: "/u/:id", path: "/u/:id",
loader: async ({ params }: LoaderFunctionArgs) => { loader: async ({ params }: LoaderFunctionArgs) => {
const state = store.getState(); const state = store.getState();
const api = new VoidApi(import.meta.env.VITE_API_HOST, state.login.jwt ? () => Promise.resolve(`Bearer ${state.login.jwt}`) : undefined); const api = new VoidApi(
import.meta.env.VITE_API_HOST,
state.login.jwt
? () => Promise.resolve(`Bearer ${state.login.jwt}`)
: undefined,
);
if (params.id) { if (params.id) {
try { try {
return await api.getUser(params.id); return await api.getUser(params.id);

View File

@ -3,8 +3,6 @@ import { CSSProperties } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Profile } from "@void-cat/api"; import { Profile } from "@void-cat/api";
import { DefaultAvatar } from "@/Const";
const DefaultSize = 64; const DefaultSize = 64;
interface InlineProfileProps { interface InlineProfileProps {
@ -24,8 +22,8 @@ export function InlineProfile({ profile, options }: InlineProfileProps) {
...options, ...options,
}; };
let avatarUrl = profile.avatar ?? DefaultAvatar; let avatarUrl = profile.avatar ?? "/logo_256.jpg";
if (!avatarUrl.startsWith("http")) { if (profile.avatar && !avatarUrl.startsWith("http")) {
avatarUrl = `/d/${avatarUrl}`; avatarUrl = `/d/${avatarUrl}`;
} }
let avatarStyles = { let avatarStyles = {

View File

@ -1,5 +1,3 @@
export const DefaultAvatar = "https://i.imgur.com/8A5Fu65.jpeg";
/** /**
* @constant {number} - Size of 1 kiB * @constant {number} - Size of 1 kiB
*/ */

View File

@ -5,5 +5,8 @@ import { RootState } from "@/Store";
export default function useApi() { export default function useApi() {
const auth = useSelector((s: RootState) => s.login.jwt); const auth = useSelector((s: RootState) => s.login.jwt);
return new VoidApi(import.meta.env.VITE_API_HOST, auth ? () => Promise.resolve(`Bearer ${auth}`) : undefined); return new VoidApi(
import.meta.env.VITE_API_HOST,
auth ? () => Promise.resolve(`Bearer ${auth}`) : undefined,
);
} }

View File

@ -248,7 +248,9 @@ export function FilePreview() {
useEffect(() => { useEffect(() => {
if (info) { if (info) {
const fileLink = info.metadata?.url ?? `${import.meta.env.VITE_API_HOST ?? ""}/d/${info.id}`; const fileLink =
info.metadata?.url ??
`${import.meta.env.VITE_API_HOST ?? ""}/d/${info.id}`;
setFileSize(info.metadata?.size ?? 0); setFileSize(info.metadata?.size ?? 0);
const order = window.localStorage.getItem(`payment-${info.id}`); const order = window.localStorage.getItem(`payment-${info.id}`);

View File

@ -7,7 +7,6 @@ import { Profile } from "@void-cat/api";
import useApi from "@/Hooks/UseApi"; import useApi from "@/Hooks/UseApi";
import { RootState } from "@/Store"; import { RootState } from "@/Store";
import { DefaultAvatar } from "@/Const";
import { logout, setProfile as setGlobalProfile } from "@/LoginState"; import { logout, setProfile as setGlobalProfile } from "@/LoginState";
import { FileList } from "@/Components/Shared/FileList"; import { FileList } from "@/Components/Shared/FileList";
@ -173,9 +172,8 @@ export function ProfilePage() {
} }
if (profile) { if (profile) {
let avatarUrl = profile.avatar ?? DefaultAvatar; let avatarUrl = profile.avatar ?? "/logo_256.jpg";
if (!avatarUrl.startsWith("http")) { if (profile.avatar && !avatarUrl.startsWith("http")) {
// assume void-cat hosted avatar
avatarUrl = `/d/${avatarUrl}`; avatarUrl = `/d/${avatarUrl}`;
} }
let avatarStyles = { let avatarStyles = {

View File

@ -1,34 +1,33 @@
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import {visualizer} from "rollup-plugin-visualizer"; import { visualizer } from "rollup-plugin-visualizer";
import {defineConfig} from "vite"; import { defineConfig } from "vite";
import {vitePluginVersionMark} from "vite-plugin-version-mark"; import { vitePluginVersionMark } from "vite-plugin-version-mark";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
react(), react(),
visualizer({ visualizer({
open: true, open: true,
gzipSize: true, gzipSize: true,
filename: "build/stats.html", filename: "build/stats.html",
}), }),
vitePluginVersionMark({ vitePluginVersionMark({
name: "void_cat", name: "void_cat",
ifGitSHA: true, ifGitSHA: true,
command: "git describe --always --tags", command: "git describe --always --tags",
ifMeta: false, ifMeta: false,
}), }),
], ],
assetsInclude: [], assetsInclude: [],
build: { build: {
outDir: "build" outDir: "build",
},
base: "/",
clearScreen: false,
resolve: {
alias: {
"@": "/src",
}, },
base: "/", },
clearScreen: false, define: {},
resolve: {
alias: {
"@": "/src",
},
},
define: {}
}); });