From 8704d3acb35e8a0c5c5a53c4f5c4f5532aae749a Mon Sep 17 00:00:00 2001
From: Sasha Sorokin <10401817+brawaru@users.noreply.github.com>
Date: Sat, 13 Jul 2024 21:20:43 +0200
Subject: [PATCH 1/5] Rewrite cosmetics and theme preferences (#1292)
- Cosmetics and theme preferences are now only stored in cookies instead
of a combination of both cookies and state.
- The theme plugin now supports client hints. This allows the server
to render a page using the client-preferred theme provided it supplies
this information (any browser other than Firefox), helping to avoid an
annoying flash while the page is hydrating.
- The previous workaround using the Nitro plugin has been removed. Its
functionality is now handled by the Nuxt theme plugin with cleaner
code.
- All pages and components now use the new plugins.
---
apps/frontend/nuxt.config.ts | 8 ++
.../src/components/ui/charts/ChartDisplay.vue | 2 +-
apps/frontend/src/composables/cosmetics.js | 52 -------
.../src/composables/nuxt-accessors.ts | 7 +
apps/frontend/src/composables/theme.js | 58 --------
apps/frontend/src/composables/vue.ts | 14 ++
apps/frontend/src/layouts/default.vue | 18 +--
apps/frontend/src/pages/app.vue | 2 +-
.../src/pages/auth/reset-password.vue | 2 +-
apps/frontend/src/pages/auth/sign-in.vue | 2 +-
apps/frontend/src/pages/auth/sign-up.vue | 2 +-
apps/frontend/src/pages/collection/[id].vue | 1 -
apps/frontend/src/pages/index.vue | 4 +-
.../src/pages/search/[searchProjectType].vue | 1 -
apps/frontend/src/pages/settings/index.vue | 69 ++++------
apps/frontend/src/pages/user/[id].vue | 1 -
apps/frontend/src/plugins/1.theme.js | 27 ----
apps/frontend/src/plugins/cosmetics.ts | 57 ++++++++
apps/frontend/src/plugins/theme.ts | 130 ++++++++++++++++++
apps/frontend/src/server/plugins/theme.js | 38 -----
apps/frontend/src/utils/analytics.js | 21 +--
21 files changed, 268 insertions(+), 248 deletions(-)
delete mode 100644 apps/frontend/src/composables/cosmetics.js
create mode 100644 apps/frontend/src/composables/nuxt-accessors.ts
delete mode 100644 apps/frontend/src/composables/theme.js
create mode 100644 apps/frontend/src/composables/vue.ts
delete mode 100644 apps/frontend/src/plugins/1.theme.js
create mode 100644 apps/frontend/src/plugins/cosmetics.ts
create mode 100644 apps/frontend/src/plugins/theme.ts
delete mode 100644 apps/frontend/src/server/plugins/theme.js
diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts
index e8de813c9..513fd5e93 100644
--- a/apps/frontend/nuxt.config.ts
+++ b/apps/frontend/nuxt.config.ts
@@ -392,6 +392,14 @@ export default defineNuxtConfig({
autoprefixer: {},
},
},
+ routeRules: {
+ "/**": {
+ headers: {
+ "Accept-CH": "Sec-CH-Prefers-Color-Scheme",
+ "Critical-CH": "Sec-CH-Prefers-Color-Scheme",
+ },
+ },
+ },
compatibilityDate: "2024-07-03",
});
diff --git a/apps/frontend/src/components/ui/charts/ChartDisplay.vue b/apps/frontend/src/components/ui/charts/ChartDisplay.vue
index 852c687de..36a603f58 100644
--- a/apps/frontend/src/components/ui/charts/ChartDisplay.vue
+++ b/apps/frontend/src/components/ui/charts/ChartDisplay.vue
@@ -162,7 +162,7 @@
- useState("cosmetics", () => {
- const cosmetics = useCookie("cosmetics", {
- maxAge: 60 * 60 * 24 * 365 * 10,
- sameSite: "lax",
- secure: true,
- httpOnly: false,
- path: "/",
- });
-
- if (!cosmetics.value) {
- cosmetics.value = {
- searchLayout: false,
- projectLayout: false,
- advancedRendering: true,
- externalLinksNewTab: true,
- notUsingBlockers: false,
- hideModrinthAppPromos: false,
- preferredDarkTheme: "dark",
- searchDisplayMode: {
- mod: "list",
- plugin: "list",
- resourcepack: "gallery",
- modpack: "list",
- shader: "gallery",
- datapack: "list",
- user: "list",
- collection: "list",
- },
- hideStagingBanner: false,
- };
- }
-
- return cosmetics.value;
- });
-
-export const saveCosmetics = () => {
- const cosmetics = useCosmetics();
-
- console.log("SAVING COSMETICS:");
- console.log(cosmetics);
-
- const cosmeticsCookie = useCookie("cosmetics", {
- maxAge: 60 * 60 * 24 * 365 * 10,
- sameSite: "lax",
- secure: true,
- httpOnly: false,
- path: "/",
- });
-
- cosmeticsCookie.value = cosmetics.value;
-};
diff --git a/apps/frontend/src/composables/nuxt-accessors.ts b/apps/frontend/src/composables/nuxt-accessors.ts
new file mode 100644
index 000000000..5130f6268
--- /dev/null
+++ b/apps/frontend/src/composables/nuxt-accessors.ts
@@ -0,0 +1,7 @@
+export function useTheme() {
+ return useNuxtApp().$theme;
+}
+
+export function useCosmetics() {
+ return useNuxtApp().$cosmetics;
+}
diff --git a/apps/frontend/src/composables/theme.js b/apps/frontend/src/composables/theme.js
deleted file mode 100644
index 460ebdf1f..000000000
--- a/apps/frontend/src/composables/theme.js
+++ /dev/null
@@ -1,58 +0,0 @@
-export const useTheme = () =>
- useState("theme", () => {
- const colorMode = useCookie("color-mode", {
- maxAge: 60 * 60 * 24 * 365 * 10,
- sameSite: "lax",
- secure: true,
- httpOnly: false,
- path: "/",
- });
-
- if (!colorMode.value) {
- colorMode.value = {
- value: "dark",
- preference: "system",
- };
- }
-
- if (colorMode.value.preference !== "system") {
- colorMode.value.value = colorMode.value.preference;
- }
-
- return colorMode.value;
- });
-
-export const updateTheme = (value, updatePreference = false) => {
- const theme = useTheme();
- const cosmetics = useCosmetics();
-
- const themeCookie = useCookie("color-mode", {
- maxAge: 60 * 60 * 24 * 365 * 10,
- sameSite: "lax",
- secure: true,
- httpOnly: false,
- path: "/",
- });
-
- if (value === "system") {
- theme.value.preference = "system";
-
- const colorSchemeQueryList = window.matchMedia("(prefers-color-scheme: light)");
- if (colorSchemeQueryList.matches) {
- theme.value.value = "light";
- } else {
- theme.value.value = cosmetics.value.preferredDarkTheme;
- }
- } else {
- theme.value.value = value;
- if (updatePreference) theme.value.preference = value;
- }
-
- if (import.meta.client) {
- document.documentElement.className = `${theme.value.value}-mode`;
- }
-
- themeCookie.value = theme.value;
-};
-
-export const DARK_THEMES = ["dark", "oled", "retro"];
diff --git a/apps/frontend/src/composables/vue.ts b/apps/frontend/src/composables/vue.ts
new file mode 100644
index 000000000..cf49bc462
--- /dev/null
+++ b/apps/frontend/src/composables/vue.ts
@@ -0,0 +1,14 @@
+/**
+ * Creates a computed reference that uses a provide getter function called with an argument representing the current mount state of the component.
+ * @param getter A getter function that will run with `mounted` argument representing whether or not the component is mounted.
+ * @returns A computed reference that changes when component becomes mounted or unmounted.
+ */
+export function useMountedValue
(getter: (isMounted: boolean) => T) {
+ const mounted = ref(getCurrentInstance()?.isMounted ?? false);
+
+ onMounted(() => (mounted.value = true));
+
+ onUnmounted(() => (mounted.value = false));
+
+ return computed(() => getter(mounted.value));
+}
diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue
index 4be98be2a..863ad8b31 100644
--- a/apps/frontend/src/layouts/default.vue
+++ b/apps/frontend/src/layouts/default.vue
@@ -62,7 +62,7 @@
:title="formatMessage(messages.changeTheme)"
@click="changeTheme"
>
-
+
diff --git a/apps/frontend/src/pages/auth/sign-in.vue b/apps/frontend/src/pages/auth/sign-in.vue
index 7dc22f7b6..0110affe2 100644
--- a/apps/frontend/src/pages/auth/sign-in.vue
+++ b/apps/frontend/src/pages/auth/sign-in.vue
@@ -85,7 +85,7 @@
ref="turnstile"
v-model="token"
class="turnstile"
- :options="{ theme: $colorMode.value === 'light' ? 'light' : 'dark' }"
+ :options="{ theme: $theme.active === 'light' ? 'light' : 'dark' }"
/>
-
+
{{ colorTheme[option] ? formatMessage(colorTheme[option]) : option }}
@@ -170,7 +169,6 @@
v-model="cosmetics.externalLinksNewTab"
class="switch stylized-toggle"
type="checkbox"
- @change="saveCosmetics"
/>
@@ -187,7 +185,6 @@
v-model="cosmetics.hideModrinthAppPromos"
class="switch stylized-toggle"
type="checkbox"
- @change="saveCosmetics"
/>
@@ -204,7 +201,6 @@
v-model="cosmetics.searchLayout"
class="switch stylized-toggle"
type="checkbox"
- @change="saveCosmetics"
/>
@@ -221,19 +217,19 @@
v-model="cosmetics.projectLayout"
class="switch stylized-toggle"
type="checkbox"
- @change="saveCosmetics"
/>
-
diff --git a/apps/frontend/src/pages/user/[id].vue b/apps/frontend/src/pages/user/[id].vue
index dbd82fcba..f7dbd0107 100644
--- a/apps/frontend/src/pages/user/[id].vue
+++ b/apps/frontend/src/pages/user/[id].vue
@@ -489,7 +489,6 @@ function cycleSearchDisplayMode() {
cosmetics.value.searchDisplayMode.user,
tags.value.projectViewModes,
);
- saveCosmetics();
}
\ No newline at end of file
+
diff --git a/apps/app-frontend/src/components/ui/ExportModal.vue b/apps/app-frontend/src/components/ui/ExportModal.vue
index 35281e092..05c699928 100644
--- a/apps/app-frontend/src/components/ui/ExportModal.vue
+++ b/apps/app-frontend/src/components/ui/ExportModal.vue
@@ -7,8 +7,8 @@ import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/pro
import { open } from '@tauri-apps/api/dialog'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme'
-import { i18n } from '@/main.js';
-const t = i18n.global.t;
+import { i18n } from '@/main.js'
+const t = i18n.global.t
const props = defineProps({
instance: {
type: Object,
@@ -110,17 +110,22 @@ const exportPack = async () => {