🚧 💄 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:
@ -1,7 +1,7 @@
|
||||
.app.theme-dark {
|
||||
--bg-color: #353535;
|
||||
--element-color: #333030;
|
||||
--element-color-hover: #292424;
|
||||
--element-color: #212121;
|
||||
--element-color-hover: #1b1b1b;
|
||||
--element-border-input: #4e7a3a;
|
||||
--element-border-focus: #9ac982;
|
||||
--element-border-action: #1f5b63;
|
||||
|
41
src/components/settings/page-card.vue
Normal file
41
src/components/settings/page-card.vue
Normal 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>
|
30
src/components/settings/radio-buttons.vue
Normal file
30
src/components/settings/radio-buttons.vue
Normal 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>
|
@ -9,8 +9,8 @@ const router = useRouter();
|
||||
const routeName = computed(() => route.name);
|
||||
function goBack() {
|
||||
if (!routeName.value.startsWith("title.settings")) return;
|
||||
if (lastDataRoute.value.name) router.push(lastDataRoute.value.path);
|
||||
else router.push("/timetable");
|
||||
if (lastDataRoute.value) router.push(lastDataRoute.value.path);
|
||||
else router.push("/");
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -7,6 +7,9 @@ import HistoryView from "@/views/HistoryView.vue";
|
||||
import SettingsView from "@/views/SettingsView.vue";
|
||||
import LoginView from "@/views/LoginView.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({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -41,8 +44,25 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
name: "title.settings",
|
||||
name: "title.settings.main",
|
||||
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",
|
||||
|
@ -4,9 +4,14 @@ export const strings = {
|
||||
timetable: "Timetable",
|
||||
substitutions: "Substitutions",
|
||||
history: "History",
|
||||
settings: "Settings",
|
||||
login: "Login",
|
||||
token: "Token",
|
||||
settings: {
|
||||
main: "Settings",
|
||||
filtering: "Filtering",
|
||||
appearance: "Appearance",
|
||||
about: "About",
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
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.",
|
||||
},
|
||||
other: "Other",
|
||||
back: "Back",
|
||||
none: "None",
|
||||
version: "Version",
|
||||
theme: {
|
||||
@ -102,8 +108,13 @@ export const strings = {
|
||||
timetable: "Stundenplan",
|
||||
substitutions: "Vertretungsplan",
|
||||
history: "Verlauf",
|
||||
settings: "Einstellungen",
|
||||
login: "Anmelden",
|
||||
settings: {
|
||||
main: "Einstellungen",
|
||||
filtering: "Filter",
|
||||
appearance: "Aussehen",
|
||||
about: "Über",
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
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.",
|
||||
},
|
||||
other: "Andere",
|
||||
back: "Zurück",
|
||||
none: "Keine",
|
||||
version: "Version",
|
||||
theme: {
|
||||
|
@ -2,126 +2,71 @@
|
||||
import ScrollableContainer from "@/components/scrollable-container.vue";
|
||||
import PageCard from "@/components/settings/page-card.vue";
|
||||
import {
|
||||
classList,
|
||||
classFilter,
|
||||
possibleTimetableGroups,
|
||||
timetableGroups,
|
||||
theme,
|
||||
} from "../store";
|
||||
import { language } from "../i18n";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const gitHash = GITVERSION;
|
||||
FilterIcon,
|
||||
PaletteIcon,
|
||||
InfoIcon,
|
||||
ChevronLeft,
|
||||
} from "lucide-vue-next";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScrollableContainer class="settings">
|
||||
<!-- Filtering -->
|
||||
<h2>{{ $t("settings.heading.filtering") }}</h2>
|
||||
<p>{{ $t("settings.text.filtering") }}</p>
|
||||
<select v-model="classFilter">
|
||||
<option value="none">{{ $t("timetable.setup.prompt") }}</option>
|
||||
<option
|
||||
v-for="option in classList.length > 0 ? classList : [classFilter]"
|
||||
:value="option"
|
||||
:key="option"
|
||||
>
|
||||
{{ option }}
|
||||
</option>
|
||||
<option value="other">{{ $t("settings.other") }}</option>
|
||||
</select>
|
||||
<div class="spacer"></div>
|
||||
|
||||
<!-- Timetable Groups -->
|
||||
<h2>{{ $t("settings.heading.timetableGroups") }}</h2>
|
||||
<p>{{ $t("settings.text.timetableGroups") }}</p>
|
||||
<select v-model="timetableGroups" multiple>
|
||||
<option value="none">{{ $t("settings.none") }}</option>
|
||||
<option
|
||||
v-for="option in possibleTimetableGroups"
|
||||
:value="option"
|
||||
:key="option"
|
||||
>
|
||||
{{ 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>
|
||||
<div class="wrapper" v-if="$route.name == 'title.settings.main'">
|
||||
<div class="cards">
|
||||
<PageCard
|
||||
:name="$t('title.settings.filtering')"
|
||||
:icon="FilterIcon"
|
||||
route="settings/filtering"
|
||||
/>
|
||||
<PageCard
|
||||
:name="$t('title.settings.appearance')"
|
||||
:icon="PaletteIcon"
|
||||
route="settings/appearance"
|
||||
/>
|
||||
<PageCard
|
||||
:name="$t('title.settings.about')"
|
||||
:icon="InfoIcon"
|
||||
route="settings/about"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subroute" v-else>
|
||||
<RouterLink to="/settings" class="back">
|
||||
<ChevronLeft />
|
||||
<span>{{ $t("settings.back") }}</span>
|
||||
</RouterLink>
|
||||
<RouterView />
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.settings {
|
||||
padding: 15px 10px 0px 10px;
|
||||
padding: 20px 10px 0px 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0px;
|
||||
.wrapper {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px 0px;
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 5px 0px;
|
||||
margin: 5px 0px;
|
||||
outline: none;
|
||||
border: 2px solid var(--element-border-input);
|
||||
border-radius: 5px;
|
||||
background-color: var(--element-color);
|
||||
transition: 0.2s;
|
||||
transition-property: background-color border-color;
|
||||
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;
|
||||
.back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
width: max-content;
|
||||
text-decoration: unset;
|
||||
color: unset;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
|
37
src/views/settings/AboutPage.vue
Normal file
37
src/views/settings/AboutPage.vue
Normal 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>
|
28
src/views/settings/AppearanceSettings.vue
Normal file
28
src/views/settings/AppearanceSettings.vue
Normal 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>
|
24
src/views/settings/FilteringSettings.vue
Normal file
24
src/views/settings/FilteringSettings.vue
Normal 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>
|
@ -11,6 +11,9 @@ export default defineConfig({
|
||||
GITVERSION: JSON.stringify(
|
||||
child.execSync("git rev-parse --short HEAD").toString()
|
||||
),
|
||||
GITURL: JSON.stringify(
|
||||
child.execSync("git config --get remote.origin.url").toString()
|
||||
),
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
|
Reference in New Issue
Block a user