From 8141203769c7940b6b60c4fe155ebee24ac5aa62 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 15 Oct 2023 16:25:32 +0200 Subject: [PATCH 1/8] Collapse pipeline configs by default --- web/src/components/layout/Panel.vue | 3 ++- web/src/views/repo/pipeline/PipelineConfig.vue | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/components/layout/Panel.vue b/web/src/components/layout/Panel.vue index c4960079f2..6a8431660f 100644 --- a/web/src/components/layout/Panel.vue +++ b/web/src/components/layout/Panel.vue @@ -39,13 +39,14 @@ import Icon from '~/components/atomic/Icon.vue'; const props = defineProps<{ title?: string; collapsable?: boolean; + collapsedByDefault?: boolean; }>(); /** * _collapsed is used to store the internal state of the panel, but is * ignored if the panel is not collapsable. */ -const _collapsed = ref(false); +const _collapsed = ref(props.collapsedByDefault || false); const collapsed = computed(() => props.collapsable && _collapsed.value); diff --git a/web/src/views/repo/pipeline/PipelineConfig.vue b/web/src/views/repo/pipeline/PipelineConfig.vue index f63a551543..6c9b398276 100644 --- a/web/src/views/repo/pipeline/PipelineConfig.vue +++ b/web/src/views/repo/pipeline/PipelineConfig.vue @@ -4,6 +4,7 @@ v-for="pipelineConfig in pipelineConfigs || []" :key="pipelineConfig.hash" collapsable + collapsed-by-default :title="pipelineConfig.name" > From 159ab00d7577375b66e6e413d5ac340695b23a28 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 15 Oct 2023 17:03:45 +0200 Subject: [PATCH 2/8] Refactor themes + add auto theme --- web/src/assets/locales/en.json | 10 +++- web/src/components/layout/header/Navbar.vue | 9 --- web/src/components/user/UserGeneralTab.vue | 21 +++++++ web/src/compositions/useDarkMode.ts | 47 ---------------- web/src/compositions/useFavicon.ts | 4 +- web/src/compositions/useTheme.ts | 61 +++++++++++++++++++++ 6 files changed, 91 insertions(+), 61 deletions(-) delete mode 100644 web/src/compositions/useDarkMode.ts create mode 100644 web/src/compositions/useTheme.ts diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json index 03ff4bde08..cd13356610 100644 --- a/web/src/assets/locales/en.json +++ b/web/src/assets/locales/en.json @@ -12,8 +12,6 @@ "password": "Password", "url": "URL", "back": "Back", - "color_scheme_light": "Switch to dark mode", - "color_scheme_dark": "Switch to light mode", "unknown_error": "An unknown error occurred", "documentation_for": "Documentation for \"{topic}\"", "pipeline_feed": "Pipeline feed", @@ -449,7 +447,13 @@ "settings": "User Settings", "general": { "general": "General", - "language": "Language" + "language": "Language", + "theme": { + "theme": "Theme", + "light": "Light", + "dark": "Dark", + "auto": "Auto" + } }, "secrets": { "secrets": "Secrets", diff --git a/web/src/components/layout/header/Navbar.vue b/web/src/components/layout/header/Navbar.vue index 3187dc8329..1667e1a271 100644 --- a/web/src/components/layout/header/Navbar.vue +++ b/web/src/components/layout/header/Navbar.vue @@ -24,13 +24,6 @@
- - + + + @@ -17,8 +27,10 @@ import { useI18n } from 'vue-i18n'; import SelectField from '~/components/form/SelectField.vue'; import Settings from '~/components/layout/Settings.vue'; import { setI18nLanguage } from '~/compositions/useI18n'; +import { Theme, useTheme } from '~/compositions/useTheme'; const { locale } = useI18n(); +const { theme } = useTheme(); const localeOptions = computed(() => SUPPORTED_LOCALES.map((supportedLocale) => ({ @@ -39,4 +51,13 @@ const selectedLocale = computed({ return storedLocale.value; }, }); + +const selectedTheme = computed({ + set(_selectedTheme) { + theme.value = _selectedTheme; + }, + get() { + return theme.value; + }, +}); diff --git a/web/src/compositions/useDarkMode.ts b/web/src/compositions/useDarkMode.ts deleted file mode 100644 index c28c4aefb1..0000000000 --- a/web/src/compositions/useDarkMode.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { computed, ref, watch } from 'vue'; - -const LS_DARK_MODE = 'woodpecker:dark-mode'; -const isDarkModeActive = ref(false); - -watch(isDarkModeActive, (isActive) => { - if (isActive) { - document.documentElement.classList.remove('light'); - document.documentElement.classList.add('dark'); - document.documentElement.setAttribute('data-theme', 'dark'); - document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#2A2E3A'); // internal-wp-secondary-600 (see windi.config.ts) - } else { - document.documentElement.classList.remove('dark'); - document.documentElement.classList.add('light'); - document.documentElement.setAttribute('data-theme', 'light'); - document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#369943'); // internal-wp-primary-400 - } -}); - -function setDarkMode(isActive: boolean) { - isDarkModeActive.value = isActive; - localStorage.setItem(LS_DARK_MODE, isActive ? 'dark' : 'light'); -} - -function load() { - const isActive = localStorage.getItem(LS_DARK_MODE) as 'dark' | 'light' | null; - if (isActive === null) { - setDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches); - } else { - setDarkMode(isActive === 'dark'); - } -} - -load(); - -export function useDarkMode() { - return { - darkMode: computed({ - get() { - return isDarkModeActive.value; - }, - set(isActive: boolean) { - setDarkMode(isActive); - }, - }), - }; -} diff --git a/web/src/compositions/useFavicon.ts b/web/src/compositions/useFavicon.ts index f3fbcc3deb..661390b52d 100644 --- a/web/src/compositions/useFavicon.ts +++ b/web/src/compositions/useFavicon.ts @@ -1,10 +1,10 @@ import { computed, ref, watch } from 'vue'; import useConfig from '~/compositions/useConfig'; -import { useDarkMode } from '~/compositions/useDarkMode'; +import { useTheme } from '~/compositions/useTheme'; import { PipelineStatus } from '~/lib/api/types'; -const darkMode = computed(() => (useDarkMode().darkMode.value ? 'dark' : 'light')); +const darkMode = computed(() => (useTheme().darkMode.value ? 'dark' : 'light')); type Status = 'default' | 'success' | 'pending' | 'error'; const faviconStatus = ref('default'); diff --git a/web/src/compositions/useTheme.ts b/web/src/compositions/useTheme.ts new file mode 100644 index 0000000000..cfc40206af --- /dev/null +++ b/web/src/compositions/useTheme.ts @@ -0,0 +1,61 @@ +import { computed, ref, watch } from 'vue'; + +export enum Theme { + Auto = 'auto', + Light = 'light', + Dark = 'dark', +} + +const LS_THEME = 'woodpecker:theme'; +const activeTheme = ref(Theme.Auto); + +function resolveAuto(theme: Theme) { + if (theme === Theme.Auto) { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.Dark : Theme.Light; + } + return theme; +} + +watch(activeTheme, (theme) => { + if (resolveAuto(theme) === Theme.Dark) { + document.documentElement.classList.remove('light'); + document.documentElement.classList.add('dark'); + document.documentElement.setAttribute('data-theme', 'dark'); + document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#2A2E3A'); // internal-wp-secondary-600 (see windi.config.ts) + } else { + document.documentElement.classList.remove('dark'); + document.documentElement.classList.add('light'); + document.documentElement.setAttribute('data-theme', 'light'); + document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#369943'); // internal-wp-primary-400 + } +}); + +function setTheme(theme: Theme) { + activeTheme.value = theme; + localStorage.setItem(LS_THEME, theme); +} + +function load() { + const isActive = localStorage.getItem(LS_THEME) as Theme | null; + if (isActive === null) { + setTheme(Theme.Auto); + } else { + setTheme(isActive); + } +} + +load(); + +export function useTheme() { + return { + darkMode: computed(() => resolveAuto(activeTheme.value) === Theme.Dark), + theme: computed({ + get() { + return activeTheme.value; + }, + set(theme: Theme) { + setTheme(theme); + }, + }), + }; +} From a03242a2116ff1f22fc0dc68ab7d329a37b5c3df Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 23 Oct 2023 16:10:12 +0200 Subject: [PATCH 3/8] Use vue-use helper --- web/src/components/user/UserGeneralTab.vue | 19 +++------ web/src/compositions/useTheme.ts | 46 ++++++---------------- 2 files changed, 17 insertions(+), 48 deletions(-) diff --git a/web/src/components/user/UserGeneralTab.vue b/web/src/components/user/UserGeneralTab.vue index c3dcbd2f2a..65f4e470a7 100644 --- a/web/src/components/user/UserGeneralTab.vue +++ b/web/src/components/user/UserGeneralTab.vue @@ -5,11 +5,11 @@ @@ -27,7 +27,7 @@ import { useI18n } from 'vue-i18n'; import SelectField from '~/components/form/SelectField.vue'; import Settings from '~/components/layout/Settings.vue'; import { setI18nLanguage } from '~/compositions/useI18n'; -import { Theme, useTheme } from '~/compositions/useTheme'; +import { useTheme } from '~/compositions/useTheme'; const { locale } = useI18n(); const { theme } = useTheme(); @@ -51,13 +51,4 @@ const selectedLocale = computed({ return storedLocale.value; }, }); - -const selectedTheme = computed({ - set(_selectedTheme) { - theme.value = _selectedTheme; - }, - get() { - return theme.value; - }, -}); diff --git a/web/src/compositions/useTheme.ts b/web/src/compositions/useTheme.ts index cfc40206af..3f7faec169 100644 --- a/web/src/compositions/useTheme.ts +++ b/web/src/compositions/useTheme.ts @@ -1,23 +1,11 @@ -import { computed, ref, watch } from 'vue'; +import { BasicColorSchema, useColorMode } from '@vueuse/core'; +import { computed, watch } from 'vue'; -export enum Theme { - Auto = 'auto', - Light = 'light', - Dark = 'dark', -} - -const LS_THEME = 'woodpecker:theme'; -const activeTheme = ref(Theme.Auto); - -function resolveAuto(theme: Theme) { - if (theme === Theme.Auto) { - return window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.Dark : Theme.Light; - } - return theme; -} +const { system, store } = useColorMode(); +const resolvedTheme = computed(() => (store.value === 'auto' ? system.value : store.value)); -watch(activeTheme, (theme) => { - if (resolveAuto(theme) === Theme.Dark) { +watch(store, () => { + if (resolvedTheme.value === 'dark') { document.documentElement.classList.remove('light'); document.documentElement.classList.add('dark'); document.documentElement.setAttribute('data-theme', 'dark'); @@ -30,30 +18,20 @@ watch(activeTheme, (theme) => { } }); -function setTheme(theme: Theme) { - activeTheme.value = theme; - localStorage.setItem(LS_THEME, theme); -} - -function load() { - const isActive = localStorage.getItem(LS_THEME) as Theme | null; - if (isActive === null) { - setTheme(Theme.Auto); - } else { - setTheme(isActive); - } +function setTheme(theme: BasicColorSchema) { + store.value = theme; } -load(); +setTheme(store.value); export function useTheme() { return { - darkMode: computed(() => resolveAuto(activeTheme.value) === Theme.Dark), + darkMode: computed(() => resolvedTheme.value === 'dark'), theme: computed({ get() { - return activeTheme.value; + return store.value; }, - set(theme: Theme) { + set(theme: BasicColorSchema) { setTheme(theme); }, }), From b3f23a625904903874ac3b726ef35fda3e50ae34 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 23 Oct 2023 18:39:00 +0200 Subject: [PATCH 4/8] Apply reviews --- web/src/compositions/useFavicon.ts | 2 +- web/src/compositions/useTheme.ts | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/web/src/compositions/useFavicon.ts b/web/src/compositions/useFavicon.ts index 661390b52d..bc350c2863 100644 --- a/web/src/compositions/useFavicon.ts +++ b/web/src/compositions/useFavicon.ts @@ -4,7 +4,7 @@ import useConfig from '~/compositions/useConfig'; import { useTheme } from '~/compositions/useTheme'; import { PipelineStatus } from '~/lib/api/types'; -const darkMode = computed(() => (useTheme().darkMode.value ? 'dark' : 'light')); +const darkMode = computed(() => useTheme().resolvedTheme.value); type Status = 'default' | 'success' | 'pending' | 'error'; const faviconStatus = ref('default'); diff --git a/web/src/compositions/useTheme.ts b/web/src/compositions/useTheme.ts index 3f7faec169..1305b147d0 100644 --- a/web/src/compositions/useTheme.ts +++ b/web/src/compositions/useTheme.ts @@ -1,10 +1,13 @@ import { BasicColorSchema, useColorMode } from '@vueuse/core'; import { computed, watch } from 'vue'; -const { system, store } = useColorMode(); -const resolvedTheme = computed(() => (store.value === 'auto' ? system.value : store.value)); +const { store: storeTheme, state: resolvedTheme } = useColorMode({ + storageKey: 'woodpecker:theme', +}); + +watch(storeTheme, updateTheme); -watch(store, () => { +function updateTheme() { if (resolvedTheme.value === 'dark') { document.documentElement.classList.remove('light'); document.documentElement.classList.add('dark'); @@ -16,23 +19,19 @@ watch(store, () => { document.documentElement.setAttribute('data-theme', 'light'); document.querySelector('meta[name=theme-color]')?.setAttribute('content', '#369943'); // internal-wp-primary-400 } -}); - -function setTheme(theme: BasicColorSchema) { - store.value = theme; } -setTheme(store.value); +updateTheme(); export function useTheme() { return { - darkMode: computed(() => resolvedTheme.value === 'dark'), + resolvedTheme, theme: computed({ get() { - return store.value; + return storeTheme.value; }, set(theme: BasicColorSchema) { - setTheme(theme); + storeTheme.value = theme; }, }), }; From 7d44f8fff7941c181585411604c900eee8c7fbf3 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Thu, 26 Oct 2023 09:03:02 +0200 Subject: [PATCH 5/8] fix lint --- web/src/compositions/useTheme.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/compositions/useTheme.ts b/web/src/compositions/useTheme.ts index 1305b147d0..01a129bbe8 100644 --- a/web/src/compositions/useTheme.ts +++ b/web/src/compositions/useTheme.ts @@ -5,8 +5,6 @@ const { store: storeTheme, state: resolvedTheme } = useColorMode({ storageKey: 'woodpecker:theme', }); -watch(storeTheme, updateTheme); - function updateTheme() { if (resolvedTheme.value === 'dark') { document.documentElement.classList.remove('light'); @@ -21,6 +19,8 @@ function updateTheme() { } } +watch(storeTheme, updateTheme); + updateTheme(); export function useTheme() { From 02128c10436e3ae45bee1402b685d68a25d8de0d Mon Sep 17 00:00:00 2001 From: qwerty287 <80460567+qwerty287@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:20:42 +0100 Subject: [PATCH 6/8] Apply suggestions from code review Co-authored-by: Anbraten --- web/src/compositions/useFavicon.ts | 3 ++- web/src/compositions/useTheme.ts | 9 +-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/web/src/compositions/useFavicon.ts b/web/src/compositions/useFavicon.ts index bc350c2863..5f97399125 100644 --- a/web/src/compositions/useFavicon.ts +++ b/web/src/compositions/useFavicon.ts @@ -4,7 +4,8 @@ import useConfig from '~/compositions/useConfig'; import { useTheme } from '~/compositions/useTheme'; import { PipelineStatus } from '~/lib/api/types'; -const darkMode = computed(() => useTheme().resolvedTheme.value); +const { resolvedTheme } = useTheme(); +const darkMode = computed(() => resolvedTheme.value); type Status = 'default' | 'success' | 'pending' | 'error'; const faviconStatus = ref('default'); diff --git a/web/src/compositions/useTheme.ts b/web/src/compositions/useTheme.ts index 01a129bbe8..4b0a651122 100644 --- a/web/src/compositions/useTheme.ts +++ b/web/src/compositions/useTheme.ts @@ -26,13 +26,6 @@ updateTheme(); export function useTheme() { return { resolvedTheme, - theme: computed({ - get() { - return storeTheme.value; - }, - set(theme: BasicColorSchema) { - storeTheme.value = theme; - }, - }), + theme: storeTheme, }; } From a09517f54ca823d61f62e1b9e96fc8b4de4ebe7c Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 1 Nov 2023 10:23:44 +0100 Subject: [PATCH 7/8] rename --- web/src/components/user/UserGeneralTab.vue | 4 ++-- web/src/compositions/useFavicon.ts | 4 ++-- web/src/compositions/useTheme.ts | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/src/components/user/UserGeneralTab.vue b/web/src/components/user/UserGeneralTab.vue index 65f4e470a7..b63500e3e2 100644 --- a/web/src/components/user/UserGeneralTab.vue +++ b/web/src/components/user/UserGeneralTab.vue @@ -5,7 +5,7 @@