{error}
+[\w-]{2,255})(?:$|\b)/gi;
+// matches :host/invite/:code
+export const SPACEBAR_INVITE_REGEX = /(?:^|\b)[\w\\.-]+(?:\/invite|\.gg(?:\/invite)?)\/(?[\w-]{2,255})(?:$|\b)/gi;
diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts
index f9a930f5..8251001c 100644
--- a/src/utils/debounce.ts
+++ b/src/utils/debounce.ts
@@ -1,6 +1,6 @@
// https://github.com/revoltchat/revite/blob/master/src/lib/debounce.ts#L5
-export function debounce(cb: (...args: unknown[]) => void, duration: number) {
+export default function debounce(cb: (...args: unknown[]) => void, duration: number) {
// Store the timer variable.
let timer: NodeJS.Timeout;
// This function is given to React.
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 00000000..a8648c6b
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1,15 @@
+export * from "./BitField";
+export * from "./constants";
+export { default as debounce } from "./debounce";
+export * from "./Globals";
+export * from "./i18n";
+export { default as Logger } from "./Logger";
+export { default as useForkRef } from "./mui/useForkRef";
+export * from "./Permissions";
+export { default as REST } from "./REST";
+export * from "./revison";
+export { default as Snowflake } from "./Snowflake";
+export * from "./Utils";
+
+export * from "./interfaces/api";
+export * from "./interfaces/common";
diff --git a/src/utils/isTouchscreenDevice.ts b/src/utils/isTouchscreenDevice.ts
deleted file mode 100644
index 58ef8d68..00000000
--- a/src/utils/isTouchscreenDevice.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// https://github.com/revoltchat/revite/blob/master/src/lib/isTouchscreenDevice.ts
-
-import { isDesktop, isMobile, isTablet } from "react-device-detect";
-
-export const isTouchscreenDevice =
- isDesktop || isTablet ? false : (typeof window !== "undefined" ? navigator.maxTouchPoints > 0 : false) || isMobile;
diff --git a/src/utils/messageFromFieldError.ts b/src/utils/messageFromFieldError.ts
deleted file mode 100644
index 4a5deb1c..00000000
--- a/src/utils/messageFromFieldError.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-export function messageFromFieldError(
- e:
- | {
- [key: string]: {
- _errors: {
- code: string;
- message: string;
- }[];
- };
- }
- | {
- [key: string]: {
- code: string;
- message: string;
- }[];
- },
- prevKey?: string,
-): { field: string | undefined; error: string } | null {
- for (const key in e) {
- const obj = e[key];
- if (obj) {
- if (key === "_errors" && Array.isArray(obj)) {
- const r = obj[0];
- return r ? { field: prevKey, error: r.message } : null;
- }
- if (typeof obj === "object") {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return messageFromFieldError(obj as any, key);
- }
- }
- }
- return null;
-}
diff --git a/src/utils/mui/debounce.ts b/src/utils/mui/debounce.ts
new file mode 100644
index 00000000..e64e061b
--- /dev/null
+++ b/src/utils/mui/debounce.ts
@@ -0,0 +1,24 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+export interface Cancelable {
+ clear(): void;
+}
+
+// Corresponds to 10 frames at 60 Hz.
+// A few bytes payload overhead when lodash/debounce is ~3 kB and debounce ~300 B.
+export default function debounce any>(func: T, wait = 166) {
+ let timeout: ReturnType;
+ function debounced(...args: Parameters) {
+ const later = () => {
+ // @ts-expect-error types
+ func.apply(this, args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ }
+
+ debounced.clear = () => {
+ clearTimeout(timeout);
+ };
+
+ return debounced as T & Cancelable;
+}
diff --git a/src/utils/mui/index.ts b/src/utils/mui/index.ts
new file mode 100644
index 00000000..414f79cb
--- /dev/null
+++ b/src/utils/mui/index.ts
@@ -0,0 +1,6 @@
+export { default as muiDebounce } from "./debounce";
+export { default as ownerDocument } from "./ownerDocument";
+export { default as ownerWindow } from "./ownerWindow";
+export { default as setRef } from "./setRef";
+export { default as useEnhancedEffect } from "./useEnhancedEffect";
+export { default as useForkRef } from "./useForkRef";
diff --git a/src/utils/mui/ownerDocument.ts b/src/utils/mui/ownerDocument.ts
new file mode 100644
index 00000000..98fac832
--- /dev/null
+++ b/src/utils/mui/ownerDocument.ts
@@ -0,0 +1,4 @@
+// https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument/ownerDocument.ts
+export default function ownerDocument(node: Node | null | undefined): Document {
+ return (node && node.ownerDocument) || document;
+}
diff --git a/src/utils/mui/ownerWindow.ts b/src/utils/mui/ownerWindow.ts
new file mode 100644
index 00000000..e17cc484
--- /dev/null
+++ b/src/utils/mui/ownerWindow.ts
@@ -0,0 +1,7 @@
+import ownerDocument from "./ownerDocument";
+
+// https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow/ownerWindow.ts
+export default function ownerWindow(node: Node | undefined): Window {
+ const doc = ownerDocument(node);
+ return doc.defaultView || window;
+}
diff --git a/src/utils/mui/setRef.ts b/src/utils/mui/setRef.ts
new file mode 100644
index 00000000..2167b880
--- /dev/null
+++ b/src/utils/mui/setRef.ts
@@ -0,0 +1,27 @@
+// https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/setRef/setRef.ts
+
+import * as React from "react";
+
+/**
+ * TODO v5: consider making it private
+ *
+ * passes {value} to {ref}
+ *
+ * WARNING: Be sure to only call this inside a callback that is passed as a ref.
+ * Otherwise, make sure to cleanup the previous {ref} if it changes. See
+ * https://github.com/mui/material-ui/issues/13539
+ *
+ * Useful if you want to expose the ref of an inner component to the public API
+ * while still using it inside the component.
+ * @param ref A ref callback or ref object. If anything falsy, this is a no-op.
+ */
+export default function setRef(
+ ref: React.MutableRefObject | ((instance: T | null) => void) | null | undefined,
+ value: T | null,
+): void {
+ if (typeof ref === "function") {
+ ref(value);
+ } else if (ref) {
+ ref.current = value;
+ }
+}
diff --git a/src/utils/mui/useEnhancedEffect.ts b/src/utils/mui/useEnhancedEffect.ts
new file mode 100644
index 00000000..fa613b17
--- /dev/null
+++ b/src/utils/mui/useEnhancedEffect.ts
@@ -0,0 +1,12 @@
+import * as React from "react";
+
+/**
+ * A version of `React.useLayoutEffect` that does not show a warning when server-side rendering.
+ * This is useful for effects that are only needed for client-side rendering but not for SSR.
+ *
+ * Before you use this hook, make sure to read https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
+ * and confirm it doesn't apply to your use-case.
+ */
+const useEnhancedEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
+
+export default useEnhancedEffect;
diff --git a/src/utils/mui/useForkRef.ts b/src/utils/mui/useForkRef.ts
new file mode 100644
index 00000000..fe32231b
--- /dev/null
+++ b/src/utils/mui/useForkRef.ts
@@ -0,0 +1,27 @@
+// https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/useForkRef/useForkRef.ts
+
+import React from "react";
+import setRef from "./setRef";
+
+export default function useForkRef(
+ ...refs: Array | undefined>
+): React.RefCallback | null {
+ /**
+ * This will create a new function if the refs passed to this hook change and are all defined.
+ * This means react will call the old forkRef with `null` and the new forkRef
+ * with the ref. Cleanup naturally emerges from this behavior.
+ */
+ return React.useMemo(() => {
+ if (refs.every((ref) => ref == null)) {
+ return null;
+ }
+
+ return (instance) => {
+ refs.forEach((ref) => {
+ setRef(ref, instance);
+ });
+ };
+ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- intentionally ignoring that the dependency array must be an array literal
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, refs);
+}
diff --git a/tsconfig.json b/tsconfig.json
index f7542ca1..f83fbdf6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,6 +7,7 @@
"skipLibCheck": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
+ "baseUrl": "./src",
/* Bundler mode */
"moduleResolution": "bundler",
@@ -20,7 +21,25 @@
"strict": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+
+ /* Path aliases */
+ "paths": {
+ "@/*": ["./*"],
+ "@utils": ["utils/index.ts"],
+ "@utils/*": ["utils/*"],
+ "@components": ["components/index.ts"],
+ "@components/*": ["components/*"],
+ "@assets/*": ["assets/*"],
+ "@modals/*": ["modals/*"],
+ "@pages/*": ["pages/*"],
+ "@stores": ["stores/index.ts"],
+ "@stores/*": ["stores/*"],
+ "@hooks/*": ["hooks/*"],
+ "@contexts/*": ["contexts/*"],
+ "@structures": ["stores/objects/index.ts"],
+ "@structures/*": ["stores/objects/*"]
+ }
},
"include": ["src", "src/custom.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
diff --git a/vite.config.ts b/vite.config.ts
index 4bbf572a..f6eaf037 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,5 +1,6 @@
import replace from "@rollup/plugin-replace";
-import react from "@vitejs/plugin-react";
+// import react from "@vitejs/plugin-react";
+import react from "@vitejs/plugin-react-swc";
import fs, { readFileSync } from "fs";
import path, { resolve } from "path";
import type { Plugin } from "vite";
@@ -43,14 +44,43 @@ function getVersion() {
return JSON.parse(readFileSync("package.json").toString()).version;
}
+const PATH_ALIASES = {
+ "@": path.resolve(__dirname, "src"),
+ "@utils": path.resolve(__dirname, "src", "utils"),
+ "@components": path.resolve(__dirname, "src", "components"),
+ "@assets": path.resolve(__dirname, "src", "assets"),
+ "@modals": path.resolve(__dirname, "src", "modals"),
+ "@pages": path.resolve(__dirname, "src", "pages"),
+ "@stores": path.resolve(__dirname, "src", "stores"),
+ "@hooks": path.resolve(__dirname, "src", "hooks"),
+ "@contexts": path.resolve(__dirname, "src", "contexts"),
+ "@structures": path.resolve(__dirname, "src", "stores", "objects"),
+};
+
const host = process.env.TAURI_DEV_HOST;
+const isDevBuild = !!process.env.VITE_ENV_DEV || !!process.env.TAURI_ENV_DEBUG;
+
+console.log(`Sourcemaps: ${isDevBuild}`);
+console.log(`Minification: ${isDevBuild ? false : "esbuild"}`);
+console.log(
+ `Target: ${
+ process.env.TAURI_ENV_PLATFORM !== undefined
+ ? process.env.TAURI_ENV_PLATFORM == "windows"
+ ? "chrome105"
+ : "safari13"
+ : "modules"
+ }`,
+);
// https://vitejs.dev/config/
-export default defineConfig(async () => ({
+export default defineConfig({
+ resolve: {
+ alias: PATH_ALIASES,
+ },
plugins: [
cleanPlugin(),
reactVirtualized(),
- react(),
+ react({ tsDecorators: true, plugins: [["@swc/plugin-styled-components", {}]] }),
svgr(),
chunkSplitPlugin({
strategy: "unbundle",
@@ -67,10 +97,10 @@ export default defineConfig(async () => ({
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
- // 1. prevent vite from obscuring rust errors
+ // prevent vite from obscuring rust errors
clearScreen: false,
- // 2. tauri expects a fixed port, fail if that port is not available
server: {
+ // Tauri expects a fixed port, fail if that port is not available
host: host || false,
port: 1420,
hmr: host
@@ -80,15 +110,26 @@ export default defineConfig(async () => ({
port: 1430,
}
: undefined,
+ // Tauri expects a fixed port, fail if that port is not available
strictPort: true,
},
- // 3. to make use of `TAURI_DEBUG` and other env variables
- // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
+ // Env variables starting with the item of `envPrefix` will be exposed in tauri's source code through `import.meta.env`.
+ // https://v2.tauri.app/reference/config/buildconfig.beforedevcommand
envPrefix: ["VITE_", "TAURI_"],
build: {
outDir: "dist",
- sourcemap: true,
+ // produce sourcemaps for debug builds
+ sourcemap: isDevBuild,
+ // don't minify for debug builds
+ minify: isDevBuild ? false : "esbuild",
+ // Tauri uses Chromium on Windows and WebKit on macOS and Linux
+ target:
+ process.env.TAURI_ENV_PLATFORM !== undefined
+ ? process.env.TAURI_ENV_PLATFORM == "windows"
+ ? "chrome105"
+ : "safari13"
+ : "modules",
rollupOptions: {
input: {
main: resolve(__dirname, "index.html"),
@@ -100,7 +141,7 @@ export default defineConfig(async () => ({
},
},
},
-}));
+});
const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`;
export function reactVirtualized(): Plugin {