🚧 💄 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 {
|
.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;
|
||||||
|
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);
|
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>
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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: {
|
||||||
|
@ -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>
|
||||||
|
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(
|
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(),
|
||||||
|
Reference in New Issue
Block a user