Browse Source

Smooth dark / light theme transition

- Add global color transition class
- Remove individual transition classes
- Pause chart update while transition to prevent freezes
- Change chart colors with css invert
pull/937/head
Sergei Birukov 2 years ago
parent
commit
cb8aaff912
  1. 4
      webui/src/App.vue
  2. 10
      webui/src/assets/style.css
  3. 2
      webui/src/components/Auth.vue
  4. 2
      webui/src/components/ClientAvatar.vue
  5. 21
      webui/src/components/ClientCharts.vue
  6. 10
      webui/src/components/ClientControls.vue
  7. 2
      webui/src/components/ClientNewButton.vue
  8. 4
      webui/src/components/Header.vue
  9. 2
      webui/src/components/ModalCreateClient.vue
  10. 2
      webui/src/components/ModalDeleteClient.vue
  11. 2
      webui/src/components/UpdateNotification.vue
  12. 2
      webui/src/components/icons/IconChart.vue
  13. 10
      webui/src/store/store.js

4
webui/src/App.vue

@ -6,7 +6,7 @@
<UpdateNotification :latest-release="latestRelease" :current-release="currentRelease" /> <UpdateNotification :latest-release="latestRelease" :current-release="currentRelease" />
<div class="shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"> <div class="shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden">
<div class="flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-gray-100 dark:border-neutral-600"> <div class="flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-neutral-200 dark:border-neutral-600">
<div class="flex-grow"> <div class="flex-grow">
<p class="text-2xl font-medium dark:text-neutral-200">{{ $t('clients') }}</p> <p class="text-2xl font-medium dark:text-neutral-200">{{ $t('clients') }}</p>
</div> </div>
@ -20,7 +20,7 @@
<div <div
v-for="client in clients" v-for="client in clients"
:key="client.id" :key="client.id"
class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid" class="relative overflow-hidden border-b last:border-b-0 border-neutral-200 dark:border-neutral-600 border-solid"
> >
<Client :client="client" /> <Client :client="client" />
</div> </div>

10
webui/src/assets/style.css

@ -1,3 +1,13 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer utilities {
html * {
@apply transition-colors ease-linear duration-200;
}
}
html.dark .vue-apexcharts {
filter: invert(1);
}

2
webui/src/components/Auth.vue

@ -28,7 +28,7 @@
<input <input
v-if="!authenticating && password" v-if="!authenticating && password"
type="submit" type="submit"
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white hover:bg-red-700 dark:hover:bg-red-700 transition cursor-pointer" class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white hover:bg-red-700 dark:hover:bg-red-700 cursor-pointer"
value="Sign In" value="Sign In"
/> />
<input <input

2
webui/src/components/ClientAvatar.vue

@ -1,5 +1,5 @@
<template> <template>
<div class="h-10 w-10 rounded-full bg-gray-50 relative"> <div class="h-10 w-10 rounded-full bg-gray-50 dark:bg-neutral-500 relative">
<IconAvatarDefault class="w-6 m-2 text-gray-300" /> <IconAvatarDefault class="w-6 m-2 text-gray-300" />
<img v-if="props.client.avatar" :src="props.client.avatar" class="w-10 rounded-full absolute top-0 left-0" /> <img v-if="props.client.avatar" :src="props.client.avatar" class="w-10 rounded-full absolute top-0 left-0" />

21
webui/src/components/ClientCharts.vue

@ -36,9 +36,9 @@ const UI_CHART_TYPES = [
]; ];
const CHART_COLORS = { const CHART_COLORS = {
rx: { light: 'rgba(128,128,128,0.3)', dark: 'rgba(255,255,255,0.3)' }, rx: 'rgba(0,0,0,0.2)' ,
tx: { light: 'rgba(128,128,128,0.4)', dark: 'rgba(255,255,255,0.3)' }, tx: 'rgba(0,0,0,0.3)' ,
gradient: { light: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,1.0)'], dark: ['rgba(128,128,128,0)', 'rgba(128,128,128,0)'] }, gradient: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,1.0)'] ,
}; };
defineProps({ defineProps({
@ -48,31 +48,24 @@ defineProps({
const store = useStore(); const store = useStore();
const { uiChartType, uiTheme, prefersDarkScheme, updateCharts } = storeToRefs(store); const { uiChartType, uiTheme, prefersDarkScheme, updateCharts } = storeToRefs(store);
const darkOrLight = computed(() => {
if (uiTheme.value === 'auto') {
return prefersDarkScheme.value.matches ? 'dark' : 'light';
}
return uiTheme.value;
});
const chartOptionsTX = computed(() => { const chartOptionsTX = computed(() => {
const opts = { const opts = {
...chartOptions, ...chartOptions,
colors: [CHART_COLORS.tx[darkOrLight.value]], colors: [CHART_COLORS.tx],
}; };
opts.chart.type = UI_CHART_TYPES[uiChartType.value].type || false; opts.chart.type = UI_CHART_TYPES[uiChartType.value].type || false;
opts.stroke.width = UI_CHART_TYPES[uiChartType.value].strokeWidth; opts.stroke.width = UI_CHART_TYPES[uiChartType.value].strokeWidth;
opts.fill.gradient.gradientToColors = CHART_COLORS.gradient[darkOrLight.value]; opts.fill.gradient.gradientToColors = CHART_COLORS.gradient;
return opts; return opts;
}); });
const chartOptionsRX = computed(() => { const chartOptionsRX = computed(() => {
const opts = { const opts = {
...chartOptions, ...chartOptions,
colors: [CHART_COLORS.rx[darkOrLight.value]], colors: [CHART_COLORS.rx],
}; };
opts.chart.type = UI_CHART_TYPES[uiChartType.value].type || false; opts.chart.type = UI_CHART_TYPES[uiChartType.value].type || false;
opts.stroke.width = UI_CHART_TYPES[uiChartType.value].strokeWidth; opts.stroke.width = UI_CHART_TYPES[uiChartType.value].strokeWidth;
opts.fill.gradient.gradientToColors = CHART_COLORS.gradient[darkOrLight.value]; opts.fill.gradient.gradientToColors = CHART_COLORS.gradient;
return opts; return opts;
}); });
</script> </script>

10
webui/src/components/ClientControls.vue

@ -4,7 +4,7 @@
<div <div
v-if="client.enabled === true" v-if="client.enabled === true"
:title="$t('disableClient')" :title="$t('disableClient')"
class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-red-800 cursor-pointer hover:bg-red-700 transition-all" class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-red-800 cursor-pointer hover:bg-red-700"
@click="disableClient" @click="disableClient"
> >
<div class="rounded-full w-4 h-4 m-1 ml-5 bg-white"></div> <div class="rounded-full w-4 h-4 m-1 ml-5 bg-white"></div>
@ -13,7 +13,7 @@
<div <div
v-if="client.enabled === false" v-if="client.enabled === false"
:title="$t('enableClient')" :title="$t('enableClient')"
class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-gray-200 dark:bg-neutral-400 cursor-pointer hover:bg-gray-300 dark:hover:bg-neutral-500 transition-all" class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-gray-200 dark:bg-neutral-400 cursor-pointer hover:bg-gray-300 dark:hover:bg-neutral-500"
@click="enableClient" @click="enableClient"
> >
<div class="rounded-full w-4 h-4 m-1 bg-white"></div> <div class="rounded-full w-4 h-4 m-1 bg-white"></div>
@ -23,7 +23,7 @@
<button <button
:disabled="!client.downloadableConfig" :disabled="!client.downloadableConfig"
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition" class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded"
:class="{ :class="{
'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig, 'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig,
'is-disabled': !client.downloadableConfig, 'is-disabled': !client.downloadableConfig,
@ -39,7 +39,7 @@
:disabled="!client.downloadableConfig" :disabled="!client.downloadableConfig"
:href="'./api/wireguard/client/' + client.id + '/configuration'" :href="'./api/wireguard/client/' + client.id + '/configuration'"
:download="client.downloadableConfig ? 'configuration' : null" :download="client.downloadableConfig ? 'configuration' : null"
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition" class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded"
:class="{ :class="{
'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig, 'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig,
'is-disabled': !client.downloadableConfig, 'is-disabled': !client.downloadableConfig,
@ -57,7 +57,7 @@
<!-- Delete --> <!-- Delete -->
<button <button
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition" class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded"
:title="$t('deleteClient')" :title="$t('deleteClient')"
@click="handleDeleteClick" @click="handleDeleteClick"
> >

2
webui/src/components/ClientNewButton.vue

@ -2,7 +2,7 @@
<template> <template>
<div> <div>
<button <button
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded inline-flex items-center transition" class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded inline-flex items-center"
@click="handleNewClient()" @click="handleNewClient()"
> >
<IconNew class="w-4 mr-2" /> <IconNew class="w-4 mr-2" />

4
webui/src/components/Header.vue

@ -8,7 +8,7 @@
<div class="flex items-center grow-0 gap-3 items-end self-end xxs:self-center"> <div class="flex items-center grow-0 gap-3 items-end self-end xxs:self-center">
<!-- Dark / light theme --> <!-- Dark / light theme -->
<button <button
class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 transition" class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600"
:title="$t(`theme.${uiTheme}`)" :title="$t(`theme.${uiTheme}`)"
@click="toggleTheme" @click="toggleTheme"
> >
@ -17,7 +17,7 @@
<!-- Show / hide charts --> <!-- Show / hide charts -->
<label <label
v-if="uiChartType > 0" v-if="uiChartType > 0"
class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group" class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap group"
:title="$t('toggleCharts')" :title="$t('toggleCharts')"
> >
<input v-model="uiShowCharts" type="checkbox" value="" class="sr-only peer" @change="toggleCharts" /> <input v-model="uiShowCharts" type="checkbox" value="" class="sr-only peer" @change="toggleCharts" />

2
webui/src/components/ModalCreateClient.vue

@ -28,7 +28,7 @@
--> -->
<div <div
class="inline-block align-bottom bg-white dark:bg-neutral-700 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full" class="inline-block align-bottom bg-white dark:bg-neutral-700 rounded-lg text-left overflow-hidden shadow-xl transform sm:my-8 sm:align-middle sm:max-w-lg w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline" aria-labelledby="modal-headline"

2
webui/src/components/ModalDeleteClient.vue

@ -29,7 +29,7 @@
--> -->
<div <div
class="inline-block align-bottom bg-white dark:bg-neutral-700 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full" class="inline-block align-bottom bg-white dark:bg-neutral-700 rounded-lg text-left overflow-hidden shadow-xl transform sm:my-8 sm:align-middle sm:max-w-lg w-full"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline" aria-labelledby="modal-headline"

2
webui/src/components/UpdateNotification.vue

@ -13,7 +13,7 @@
<a <a
href="https://github.com/wg-easy/wg-easy#updating" href="https://github.com/wg-easy/wg-easy#updating"
target="_blank" target="_blank"
class="p-3 rounded-md bg-white dark:bg-red-100 float-right font-sm font-semibold text-red-800 dark:text-red-600 flex-shrink-0 border-2 border-red-800 dark:border-red-600 hover:border-white dark:hover:border-red-600 hover:text-white dark:hover:text-red-100 hover:bg-red-800 dark:hover:bg-red-600 transition-all" class="p-3 rounded-md bg-white dark:bg-red-100 float-right font-sm font-semibold text-red-800 dark:text-red-600 flex-shrink-0 border-2 border-red-800 dark:border-red-600 hover:border-white dark:hover:border-red-600 hover:text-white dark:hover:text-red-100 hover:bg-red-800 dark:hover:bg-red-600"
> >
{{ $t('update') }} {{ $t('update') }}
</a> </a>

2
webui/src/components/icons/IconChart.vue

@ -4,7 +4,7 @@
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
fill="currentColor" fill="currentColor"
class="w-5 h-5 peer fill-gray-400 peer-checked:fill-gray-600 dark:fill-neutral-600 peer-checked:dark:fill-neutral-400 group-hover:dark:fill-neutral-500 transition" class="w-5 h-5 peer fill-gray-400 peer-checked:fill-gray-600 dark:fill-neutral-600 peer-checked:dark:fill-neutral-400 group-hover:dark:fill-neutral-500"
> >
<path <path
d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z" d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z"

10
webui/src/store/store.js

@ -27,6 +27,8 @@ export const useStore = defineStore('store', () => {
const uiShowCharts = ref(localStorage.getItem('uiShowCharts') === '1'); const uiShowCharts = ref(localStorage.getItem('uiShowCharts') === '1');
const uiTrafficStats = ref(false); const uiTrafficStats = ref(false);
const pauseCharts = ref(false);
const updateCharts = computed(() => { const updateCharts = computed(() => {
return uiChartType.value > 0 && uiShowCharts.value; return uiChartType.value > 0 && uiShowCharts.value;
}); });
@ -116,7 +118,7 @@ export const useStore = defineStore('store', () => {
client.latestHandshakeAt = new Date('2024-03-20'); client.latestHandshakeAt = new Date('2024-03-20');
} }
if (updateCharts.value) { if (updateCharts.value && !pauseCharts.value) {
clientsPersist[client.id].transferRxHistory.push(clientsPersist[client.id].transferRxCurrent); clientsPersist[client.id].transferRxHistory.push(clientsPersist[client.id].transferRxCurrent);
clientsPersist[client.id].transferRxHistory.shift(); clientsPersist[client.id].transferRxHistory.shift();
@ -147,12 +149,18 @@ export const useStore = defineStore('store', () => {
} }
function toggleTheme() { function toggleTheme() {
pauseCharts.value = true;
const themes = ['light', 'dark', 'auto']; const themes = ['light', 'dark', 'auto'];
const currentIndex = themes.indexOf(uiTheme.value); const currentIndex = themes.indexOf(uiTheme.value);
const newIndex = (currentIndex + 1) % themes.length; const newIndex = (currentIndex + 1) % themes.length;
uiTheme.value = themes[newIndex]; uiTheme.value = themes[newIndex];
localStorage.theme = uiTheme.value; localStorage.theme = uiTheme.value;
setTheme(uiTheme.value); setTheme(uiTheme.value);
setTimeout(() => {
pauseCharts.value = false;
}, 300);
} }
function setTheme(theme) { function setTheme(theme) {
const { classList } = document.documentElement; const { classList } = document.documentElement;

Loading…
Cancel
Save