Skip to content

Commit a83b0eb

Browse files
authored
Merge pull request #32 from CESNET/frontend_light_mode
feat(frontend): implement light mode
2 parents 748409e + 6b2bc81 commit a83b0eb

File tree

10 files changed

+121
-9
lines changed

10 files changed

+121
-9
lines changed

frontend/src/App.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
<script setup>
2-
import { inject, provide } from 'vue'
2+
import { inject, provide, watchEffect } from 'vue'
33
import { RouterView } from 'vue-router'
44
55
import { useRequestsStateStore } from '@/stores/requestsState'
6+
import { useThemeStore } from '@/stores/theme'
67
import NavigationBar from '@/components/NavigationBar.vue'
78
import RequestsState from '@/components/RequestsState.vue'
89
910
const axios = inject('axios')
1011
const requestsState = useRequestsStateStore()
12+
const theme = useThemeStore()
1113
1214
/**
1315
* Universal data getter
@@ -27,6 +29,10 @@ async function getData(urlPath, ...params) {
2729
2830
// Provide data getter to whole app
2931
provide('getData', getData)
32+
33+
watchEffect(() => {
34+
document.documentElement.setAttribute('data-bs-theme', theme.theme)
35+
})
3036
</script>
3137

3238
<template>

frontend/src/assets/logo-black.svg

Lines changed: 22 additions & 0 deletions
Loading

frontend/src/components/ActivityClassBadge.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ defineProps(['value'])
33
</script>
44

55
<template>
6-
<span v-if="value == 'off'" class="badge bg-dark-subtle">
6+
<span v-if="value == 'off'" class="badge bg-dark-subtle text-reset">
77
<i class="fa fa-power-off me-2" aria-hidden="true"></i>Off
88
</span>
9-
<span v-else-if="value == 'idle'" class="badge bg-dark-subtle border border-white">Idle</span>
9+
<span v-else-if="value == 'idle'" class="badge bg-dark-subtle border border-secondary text-reset"
10+
>Idle</span
11+
>
1012
<span v-else-if="value == 'light'" class="badge bg-info text-dark">Light</span>
1113
<span v-else-if="value == 'medium'" class="badge bg-warning text-dark">Medium</span>
1214
<span v-else-if="value == 'high'" class="badge bg-danger">High</span>

frontend/src/components/ActivityTimeline.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import {
1010
CHART_SCALE_X_OPTIONS,
1111
resampleTimedData,
1212
setChartDatetimeRange,
13+
themeTextColor,
1314
} from '@/utils/commonCharts.js'
15+
import { useThemeStore } from '@/stores/theme'
16+
17+
const themeStore = useThemeStore()
1418
1519
ChartJS.register(...registerables, annotationPlugin)
16-
ChartJS.defaults.color = '#dee2e6'
1720
ChartJS.defaults.borderColor = '#495057'
1821
1922
const props = defineProps({
@@ -44,12 +47,13 @@ const props = defineProps({
4447
})
4548
4649
const CHART_UNITS = ['pkt', 'flw', 'B']
47-
const CHART_COLORS = ['#0DCAF0', '#198754', '#FFC107']
50+
const CHART_COLORS = ['#3498DB', '#198754', '#F39C12']
4851
4952
const DIRECTION_PREFIX = props.incomingDirection ? 'in_' : 'out_'
5053
5154
const chartOptions = computed(() => {
5255
let scaleXOptions = { ...CHART_SCALE_X_OPTIONS }
56+
scaleXOptions.ticks.color = themeTextColor(themeStore.isDark)
5357
5458
// Set the time range of the chart
5559
setChartDatetimeRange(scaleXOptions, props.timePickerState.from, props.timePickerState.to)

frontend/src/components/NavigationBar.vue

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
<script setup>
22
import { RouterLink, useRoute } from 'vue-router'
33
4+
import { useThemeStore } from '@/stores/theme'
5+
import ThemeToggle from '@/components/ThemeToggle.vue'
6+
47
const route = useRoute()
8+
const theme = useThemeStore()
59
</script>
610

711
<template>
812
<nav class="navbar bg-body-tertiary navbar-expand-lg">
913
<div class="container">
1014
<div class="navbar-brand">
1115
<RouterLink :to="{ name: 'home' }" class="navbar-brand">
12-
<img src="@/assets/logo.svg" alt="PANDDA" class="logo" />
16+
<img
17+
v-if="theme.theme === 'dark'"
18+
src="@/assets/logo-white.svg"
19+
alt="PANDDA"
20+
class="logo"
21+
/>
22+
<img v-else src="@/assets/logo-black.svg" alt="PANDDA" class="logo" />
1323
</RouterLink>
1424
</div>
1525
<button
@@ -19,8 +29,8 @@ const route = useRoute()
1929
>
2030
<span class="navbar-toggler-icon"></span>
2131
</button>
22-
<div class="collapse navbar-collapse row" id="navbarSupportedContent">
23-
<ul class="navbar-nav px-2">
32+
<div class="collapse navbar-collapse" id="navbarSupportedContent">
33+
<ul class="navbar-nav px-2 me-auto">
2434
<li class="nav-item">
2535
<RouterLink
2636
:to="{ name: 'home' }"
@@ -42,6 +52,9 @@ const route = useRoute()
4252
</RouterLink>
4353
</li>
4454
</ul>
55+
<div class="m-2">
56+
<ThemeToggle />
57+
</div>
4558
</div>
4659
</div>
4760
</nav>

frontend/src/components/ObservationsTimeline.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import {
1010
CHART_SCALE_X_OPTIONS,
1111
resampleTimedData,
1212
setChartDatetimeRange,
13+
themeTextColor,
1314
} from '@/utils/commonCharts.js'
15+
import { useThemeStore } from '@/stores/theme'
16+
17+
const themeStore = useThemeStore()
1418
1519
ChartJS.register(...registerables, annotationPlugin)
16-
ChartJS.defaults.color = '#dee2e6'
1720
ChartJS.defaults.borderColor = '#495057'
1821
1922
const props = defineProps({
@@ -55,6 +58,7 @@ const props = defineProps({
5558
5659
const chartOptions = computed(() => {
5760
let scaleXOptions = { ...CHART_SCALE_X_OPTIONS }
61+
scaleXOptions.ticks.color = themeTextColor(themeStore.isDark)
5862
5963
// Set the time range of the chart
6064
setChartDatetimeRange(scaleXOptions, props.timePickerState.from, props.timePickerState.to)
@@ -69,6 +73,7 @@ const chartOptions = computed(() => {
6973
offset: true,
7074
ticks: {
7175
display: true,
76+
color: themeTextColor(themeStore.isDark),
7277
},
7378
grid: {
7479
display: false,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<template>
2+
<button
3+
type="button"
4+
class="btn btn-dark"
5+
:aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
6+
:title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
7+
@click="theme.setTheme(isDark ? 'light' : 'dark')"
8+
>
9+
<span v-if="isDark" aria-hidden="true">☀️</span>
10+
<span v-else aria-hidden="true">🌙</span>
11+
</button>
12+
</template>
13+
14+
<script setup>
15+
import { computed } from 'vue'
16+
import { useThemeStore } from '@/stores/theme'
17+
18+
const theme = useThemeStore()
19+
const isDark = computed(() => theme.theme === 'dark')
20+
</script>

frontend/src/stores/theme.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ref, computed } from 'vue'
2+
import { defineStore } from 'pinia'
3+
4+
/**
5+
* Tracking of the current theme (dark or light).
6+
*/
7+
export const useThemeStore = defineStore('theme', () => {
8+
const STORAGE_KEY = 'theme'
9+
const theme = ref(initialTheme())
10+
11+
/**
12+
* Determine the initial theme mode based on local storage or system preference.
13+
*/
14+
function initialTheme() {
15+
const saved = localStorage.getItem(STORAGE_KEY)
16+
if (saved === 'dark' || saved === 'light') return saved
17+
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
18+
}
19+
20+
/**
21+
* Sets the current theme.
22+
*/
23+
function setTheme(newTheme) {
24+
theme.value = newTheme === 'dark' ? 'dark' : 'light'
25+
localStorage.setItem(STORAGE_KEY, theme.value)
26+
}
27+
28+
return {
29+
theme: computed(() => theme.value),
30+
isDark: computed(() => theme.value === 'dark'),
31+
setTheme,
32+
}
33+
})

frontend/src/utils/commonCharts.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export const CHART_COMMON_OPTIONS = {
3535
maintainAspectRatio: false,
3636
}
3737

38+
/**
39+
* Gets text color based on current theme
40+
*/
41+
export function themeTextColor(isDark) {
42+
return isDark ? '#DEE2E6' : '#212529'
43+
}
44+
3845
/**
3946
* Sets chart datetime range if both `dtFrom` and `dtTo` are provided
4047
* @param {Object} chartScaleXOptions Options for the X axis

0 commit comments

Comments
 (0)