mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-02 21:58:43 +00:00
Refactor UI dark/bright mode (#2590)
- collapse pipeline configs by default, closes https://github.com/woodpecker-ci/woodpecker/discussions/2557 - refactor ui themes: instead of just differentiating between "is dark" or "is not dark", add a third "auto" option following the browser settings (closes https://github.com/woodpecker-ci/woodpecker/discussions/2204) and put everything into an enum. also move the option from the navbar to user settings. --------- Co-authored-by: Anbraten <anton@ju60.de>
This commit is contained in:
parent
4198c447fb
commit
2ff916c804
8 changed files with 56 additions and 62 deletions
|
@ -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",
|
||||
|
@ -453,7 +451,13 @@
|
|||
"settings": "User Settings",
|
||||
"general": {
|
||||
"general": "General",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"theme": {
|
||||
"theme": "Theme",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"auto": "Auto"
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"secrets": "Secrets",
|
||||
|
|
|
@ -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);
|
||||
</script>
|
||||
|
|
|
@ -26,13 +26,6 @@
|
|||
</div>
|
||||
<!-- Right Icons Box -->
|
||||
<div class="flex ml-auto -m-1.5 items-center space-x-2">
|
||||
<!-- Dark Mode Toggle -->
|
||||
<IconButton
|
||||
:icon="darkMode ? 'dark' : 'light'"
|
||||
:title="$t(darkMode ? 'color_scheme_dark' : 'color_scheme_light')"
|
||||
class="navbar-icon"
|
||||
@click="darkMode = !darkMode"
|
||||
/>
|
||||
<!-- Admin Settings -->
|
||||
<IconButton
|
||||
v-if="user?.admin"
|
||||
|
@ -62,7 +55,6 @@ import Button from '~/components/atomic/Button.vue';
|
|||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import useAuthentication from '~/compositions/useAuthentication';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
import { useDarkMode } from '~/compositions/useDarkMode';
|
||||
|
||||
import ActivePipelines from './ActivePipelines.vue';
|
||||
|
||||
|
@ -70,7 +62,6 @@ const config = useConfig();
|
|||
const route = useRoute();
|
||||
const authentication = useAuthentication();
|
||||
const { user } = authentication;
|
||||
const { darkMode } = useDarkMode();
|
||||
const apiUrl = `${config.rootPath ?? ''}/swagger/index.html`;
|
||||
|
||||
function doLogin() {
|
||||
|
|
|
@ -3,6 +3,16 @@
|
|||
<InputField :label="$t('user.settings.general.language')">
|
||||
<SelectField v-model="selectedLocale" :options="localeOptions" />
|
||||
</InputField>
|
||||
<InputField :label="$t('user.settings.general.theme.theme')">
|
||||
<SelectField
|
||||
v-model="storeTheme"
|
||||
:options="[
|
||||
{ value: 'auto', text: $t('user.settings.general.theme.auto') },
|
||||
{ value: 'light', text: $t('user.settings.general.theme.light') },
|
||||
{ value: 'dark', text: $t('user.settings.general.theme.dark') },
|
||||
]"
|
||||
/>
|
||||
</InputField>
|
||||
</Settings>
|
||||
</template>
|
||||
|
||||
|
@ -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 { useTheme } from '~/compositions/useTheme';
|
||||
|
||||
const { locale } = useI18n();
|
||||
const { storeTheme } = useTheme();
|
||||
|
||||
const localeOptions = computed(() =>
|
||||
SUPPORTED_LOCALES.map((supportedLocale) => ({
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
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 { theme } = useTheme();
|
||||
const darkMode = computed(() => theme.value);
|
||||
|
||||
type Status = 'default' | 'success' | 'pending' | 'error';
|
||||
const faviconStatus = ref<Status>('default');
|
||||
|
|
31
web/src/compositions/useTheme.ts
Normal file
31
web/src/compositions/useTheme.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { useColorMode } from '@vueuse/core';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const { store: storeTheme, state: resolvedTheme } = useColorMode({
|
||||
storageKey: 'woodpecker:theme',
|
||||
});
|
||||
|
||||
function updateTheme() {
|
||||
if (resolvedTheme.value === '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
|
||||
}
|
||||
}
|
||||
|
||||
watch(storeTheme, updateTheme);
|
||||
|
||||
updateTheme();
|
||||
|
||||
export function useTheme() {
|
||||
return {
|
||||
theme: resolvedTheme,
|
||||
storeTheme,
|
||||
};
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
v-for="pipelineConfig in pipelineConfigs || []"
|
||||
:key="pipelineConfig.hash"
|
||||
collapsable
|
||||
collapsed-by-default
|
||||
:title="pipelineConfig.name"
|
||||
>
|
||||
<SyntaxHighlight class="font-mono whitespace-pre overflow-auto" language="yaml" :code="pipelineConfig.data" />
|
||||
|
|
Loading…
Reference in a new issue