From 1e0d7c7e280c064fbd3e31ebe56afcfab9026361 Mon Sep 17 00:00:00 2001 From: Sasha Sorokin <10401817+brawaru@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:42:55 +0200 Subject: [PATCH] Rewrite cosmetics and theme preferences (#1328) - 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. - For the future, the theme plugin now supports additional light themes. Light theme preferences are currently not stored, but this can easily be fixed if the need arises. - 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. - Compared to the previous attempt, this commit has been improved to be more robust in cases where the theme cookie contains invalid values. Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> --- 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 | 86 +++++++++--------- apps/frontend/src/pages/user/[id].vue | 1 - apps/frontend/src/plugins/1.theme.js | 27 ------ apps/frontend/src/plugins/cosmetics.ts | 60 +++++++++++++ apps/frontend/src/plugins/theme/index.ts | 90 +++++++++++++++++++ .../src/plugins/theme/native-theme.ts | 38 ++++++++ .../src/plugins/theme/preferred-theme.ts | 32 +++++++ .../src/plugins/theme/theme-settings.ts | 38 ++++++++ apps/frontend/src/plugins/theme/themes.ts | 27 ++++++ apps/frontend/src/server/plugins/theme.js | 38 -------- apps/frontend/src/utils/analytics.js | 21 ++--- 25 files changed, 379 insertions(+), 252 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/index.ts create mode 100644 apps/frontend/src/plugins/theme/native-theme.ts create mode 100644 apps/frontend/src/plugins/theme/preferred-theme.ts create mode 100644 apps/frontend/src/plugins/theme/theme-settings.ts create mode 100644 apps/frontend/src/plugins/theme/themes.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 dcb3cbd5..3088e788 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -408,6 +408,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 852c687d..36a603f5 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 00000000..5130f626 --- /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 460ebdf1..00000000 --- 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 00000000..cf49bc46 --- /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 4be98be2..863ad8b3 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" > -