🚧 💄 Implement new settings menu and 3 subpages

- Implement new main settings menu
- Implement "filtering" page
- Implement "appearance" page
- Implement "about" page
- Make the Git hash a link to the Git repo
This commit is contained in:
2023-06-06 22:39:08 +02:00
parent 7183f467b4
commit 1ed9af766d
11 changed files with 253 additions and 113 deletions

View File

@ -1,7 +1,7 @@
.app.theme-dark { .app.theme-dark {
--bg-color: #353535; --bg-color: #353535;
--element-color: #333030; --element-color: #212121;
--element-color-hover: #292424; --element-color-hover: #1b1b1b;
--element-border-input: #4e7a3a; --element-border-input: #4e7a3a;
--element-border-focus: #9ac982; --element-border-focus: #9ac982;
--element-border-action: #1f5b63; --element-border-action: #1f5b63;

View File

@ -0,0 +1,41 @@
<script setup>
import { RouterLink } from "vue-router";
defineProps({
name: {
required: true,
},
icon: {
required: true,
},
route: {
required: true,
},
});
</script>
<template>
<RouterLink :to="route" class="card">
<component :is="icon" /><span>{{ name }}</span>
</RouterLink>
</template>
<style scoped>
.card {
text-decoration: unset;
color: unset;
display: flex;
background-color: var(--element-color);
padding: 15px;
border-radius: 8px;
font-size: 18px;
gap: 15px;
transition: 0.2s;
cursor: pointer;
box-shadow: 2px 2px 10px 0px rgba(0, 0, 0, 0.2);
}
.card:hover {
background-color: var(--element-color-hover);
}
</style>

View File

@ -0,0 +1,30 @@
<script setup>
import { CircleIcon, CheckCircleIcon } from "lucide-vue-next";
defineProps(["options", "values", "modelValue"]);
defineEmits(["update:modelValue"]);
</script>
<template>
<div
class="button"
v-for="(value, index) in values"
:key="value"
@click="$emit('update:modelValue', value)"
>
<CheckCircleIcon v-if="modelValue == value" />
<CircleIcon v-else />
<span class="text">{{ options[index] }}</span>
</div>
</template>
<style scoped>
.button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 5px;
cursor: pointer;
}
</style>

View File

@ -9,8 +9,8 @@ const router = useRouter();
const routeName = computed(() => route.name); const routeName = computed(() => route.name);
function goBack() { function goBack() {
if (!routeName.value.startsWith("title.settings")) return; if (!routeName.value.startsWith("title.settings")) return;
if (lastDataRoute.value.name) router.push(lastDataRoute.value.path); if (lastDataRoute.value) router.push(lastDataRoute.value.path);
else router.push("/timetable"); else router.push("/");
} }
</script> </script>

View File

@ -7,6 +7,9 @@ import HistoryView from "@/views/HistoryView.vue";
import SettingsView from "@/views/SettingsView.vue"; import SettingsView from "@/views/SettingsView.vue";
import LoginView from "@/views/LoginView.vue"; import LoginView from "@/views/LoginView.vue";
import TokenView from "@/views/TokenView.vue"; import TokenView from "@/views/TokenView.vue";
import FilteringSettings from "@/views/settings/FilteringSettings.vue";
import AppearanceSettings from "@/views/settings/AppearanceSettings.vue";
import AboutPage from "@/views/settings/AboutPage.vue";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -41,8 +44,25 @@ const router = createRouter({
}, },
{ {
path: "/settings", path: "/settings",
name: "title.settings", name: "title.settings.main",
component: SettingsView, component: SettingsView,
children: [
{
path: "filtering",
name: "title.settings.filtering",
component: FilteringSettings,
},
{
path: "appearance",
name: "title.settings.appearance",
component: AppearanceSettings,
},
{
path: "about",
name: "title.settings.about",
component: AboutPage,
},
],
}, },
{ {
path: "/login", path: "/login",

View File

@ -4,9 +4,14 @@ export const strings = {
timetable: "Timetable", timetable: "Timetable",
substitutions: "Substitutions", substitutions: "Substitutions",
history: "History", history: "History",
settings: "Settings",
login: "Login", login: "Login",
token: "Token", token: "Token",
settings: {
main: "Settings",
filtering: "Filtering",
appearance: "Appearance",
about: "About",
},
}, },
settings: { settings: {
heading: { heading: {
@ -28,6 +33,7 @@ export const strings = {
"Select a Theme to change the colors of the app. The 'Auto' option selects a theme based on your system preferences.", "Select a Theme to change the colors of the app. The 'Auto' option selects a theme based on your system preferences.",
}, },
other: "Other", other: "Other",
back: "Back",
none: "None", none: "None",
version: "Version", version: "Version",
theme: { theme: {
@ -102,8 +108,13 @@ export const strings = {
timetable: "Stundenplan", timetable: "Stundenplan",
substitutions: "Vertretungsplan", substitutions: "Vertretungsplan",
history: "Verlauf", history: "Verlauf",
settings: "Einstellungen",
login: "Anmelden", login: "Anmelden",
settings: {
main: "Einstellungen",
filtering: "Filter",
appearance: "Aussehen",
about: "Über",
},
}, },
settings: { settings: {
heading: { heading: {
@ -125,6 +136,7 @@ export const strings = {
"Wähle ein Farbschema aus, um die Farben dieser Anwendung anzupassen. Die Option 'Automatisch' wählt ein Farbschema basierend auf den Einstellungen deines Systems aus.", "Wähle ein Farbschema aus, um die Farben dieser Anwendung anzupassen. Die Option 'Automatisch' wählt ein Farbschema basierend auf den Einstellungen deines Systems aus.",
}, },
other: "Andere", other: "Andere",
back: "Zurück",
none: "Keine", none: "Keine",
version: "Version", version: "Version",
theme: { theme: {

View File

@ -2,126 +2,71 @@
import ScrollableContainer from "@/components/scrollable-container.vue"; import ScrollableContainer from "@/components/scrollable-container.vue";
import PageCard from "@/components/settings/page-card.vue"; import PageCard from "@/components/settings/page-card.vue";
import { import {
classList, FilterIcon,
classFilter, PaletteIcon,
possibleTimetableGroups, InfoIcon,
timetableGroups, ChevronLeft,
theme, } from "lucide-vue-next";
} from "../store";
import { language } from "../i18n";
// eslint-disable-next-line no-undef
const gitHash = GITVERSION;
</script> </script>
<template> <template>
<ScrollableContainer class="settings"> <ScrollableContainer class="settings">
<!-- Filtering --> <div class="wrapper" v-if="$route.name == 'title.settings.main'">
<h2>{{ $t("settings.heading.filtering") }}</h2> <div class="cards">
<p>{{ $t("settings.text.filtering") }}</p> <PageCard
<select v-model="classFilter"> :name="$t('title.settings.filtering')"
<option value="none">{{ $t("timetable.setup.prompt") }}</option> :icon="FilterIcon"
<option route="settings/filtering"
v-for="option in classList.length > 0 ? classList : [classFilter]" />
:value="option" <PageCard
:key="option" :name="$t('title.settings.appearance')"
> :icon="PaletteIcon"
{{ option }} route="settings/appearance"
</option> />
<option value="other">{{ $t("settings.other") }}</option> <PageCard
</select> :name="$t('title.settings.about')"
<div class="spacer"></div> :icon="InfoIcon"
route="settings/about"
<!-- Timetable Groups --> />
<h2>{{ $t("settings.heading.timetableGroups") }}</h2> </div>
<p>{{ $t("settings.text.timetableGroups") }}</p> </div>
<select v-model="timetableGroups" multiple> <div class="subroute" v-else>
<option value="none">{{ $t("settings.none") }}</option> <RouterLink to="/settings" class="back">
<option <ChevronLeft />
v-for="option in possibleTimetableGroups" <span>{{ $t("settings.back") }}</span>
:value="option" </RouterLink>
:key="option" <RouterView />
> </div>
{{ option }}
</option>
</select>
<div class="spacer"></div>
<!-- Language -->
<h2>{{ $t("settings.heading.language") }}</h2>
<p>{{ $t("settings.text.language") }}</p>
<select v-model="language">
<option
v-for="locale in $i18n.availableLocales"
:key="`locale-${locale}`"
:value="locale"
>
{{ locale }}
</option>
</select>
<div class="spacer"></div>
<!-- Theme -->
<h2>{{ $t("settings.heading.theme") }}</h2>
<p>{{ $t("settings.text.theme") }}</p>
<select v-model="theme">
<option value="auto">{{ $t("settings.theme.auto") }}</option>
<option value="dark">{{ $t("settings.theme.dark") }}</option>
<option value="light">{{ $t("settings.theme.light") }}</option>
</select>
<div class="spacer"></div>
<!-- About -->
<h2>{{ $t("settings.heading.about") }}</h2>
<p>{{ $t("settings.text.about") }}</p>
<div class="spacer"></div>
<p class="gray">{{ $t("settings.version") }}: {{ gitHash }}</p>
</ScrollableContainer> </ScrollableContainer>
</template> </template>
<style scoped> <style scoped>
.settings { .settings {
padding: 15px 10px 0px 10px; padding: 20px 10px 0px 10px;
} }
h2 { .wrapper {
margin: 0px; flex-direction: column;
align-items: center;
display: flex;
} }
p { .cards {
margin: 5px 0px; display: flex;
flex-direction: column;
width: 100%;
max-width: 500px;
gap: 10px;
} }
select { .back {
padding: 5px 0px; display: flex;
margin: 5px 0px; align-items: center;
outline: none; cursor: pointer;
border: 2px solid var(--element-border-input); user-select: none;
border-radius: 5px; width: max-content;
background-color: var(--element-color); text-decoration: unset;
transition: 0.2s; color: unset;
transition-property: background-color border-color; margin-bottom: 8px;
color: var(--text-color);
}
select:hover {
background-color: var(--element-color-hover);
}
select:focus {
border-color: var(--element-border-focus);
}
select[multiple] {
min-width: 90px;
}
.spacer {
display: block;
margin: 15px 0;
}
.gray {
opacity: 0.5;
} }
</style> </style>

View File

@ -0,0 +1,37 @@
<script setup>
// eslint-disable-next-line no-undef
const gitHash = GITVERSION;
// eslint-disable-next-line no-undef
const gitRepo = GITURL;
</script>
<template>
<h2>{{ $t("settings.heading.about") }}</h2>
<p>{{ $t("settings.text.about") }}</p>
<div class="spacer"></div>
<p class="gray">
{{ $t("settings.version") }}:
<a :href="gitRepo" target="_blank">{{ gitHash }}</a>
</p>
</template>
<style>
h2 {
margin: 0px;
}
p {
margin: 5px 0px;
line-height: 140%;
}
.gray {
opacity: 0.5;
}
a {
text-decoration-style: dotted;
text-underline-offset: 3px;
color: unset;
}
</style>

View File

@ -0,0 +1,28 @@
<script setup>
import { theme } from "@/store";
import RadioButtons from "@/components/settings/radio-buttons.vue";
</script>
<template>
<h2>{{ $t("settings.heading.theme") }}</h2>
<p>{{ $t("settings.text.theme") }}</p>
<RadioButtons
:options="[
$t('settings.theme.auto'),
$t('settings.theme.light'),
$t('settings.theme.dark'),
]"
:values="['auto', 'light', 'dark']"
v-model="theme"
/>
</template>
<style scoped>
h2 {
margin: 0px;
}
p {
margin: 5px 0px;
}
</style>

View File

@ -0,0 +1,24 @@
<script setup>
import { classList, classFilter } from "@/store";
import RadioButtons from "@/components/settings/radio-buttons.vue";
</script>
<template>
<h2>{{ $t("settings.heading.filtering") }}</h2>
<p>{{ $t("settings.text.filtering") }}</p>
<RadioButtons
:options="['None', ...classList]"
:values="['none', ...classList]"
v-model="classFilter"
/>
</template>
<style scoped>
h2 {
margin: 0px;
}
p {
margin: 5px 0px;
}
</style>

View File

@ -11,6 +11,9 @@ export default defineConfig({
GITVERSION: JSON.stringify( GITVERSION: JSON.stringify(
child.execSync("git rev-parse --short HEAD").toString() child.execSync("git rev-parse --short HEAD").toString()
), ),
GITURL: JSON.stringify(
child.execSync("git config --get remote.origin.url").toString()
),
}, },
plugins: [ plugins: [
vue(), vue(),