✨ Implement timetable editing
This commit is contained in:
221
src/views/settings/TimetableEditor.vue
Normal file
221
src/views/settings/TimetableEditor.vue
Normal file
@ -0,0 +1,221 @@
|
||||
<script setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import { ref, toRaw } from "vue";
|
||||
import { localTimetables } from "@/store";
|
||||
import { PlusIcon } from "lucide-vue-next";
|
||||
import EditorNavbar from "@/components/settings/editor-navbar.vue";
|
||||
import DateSelector from "@/components/date-selector.vue";
|
||||
import ScrollableContainer from "@/components/scrollable-container.vue";
|
||||
import LessonGroupList from "@/components/lesson-group-list.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const timetable = localTimetables.value.find(
|
||||
(e) => e.id == route.params.id
|
||||
) || {
|
||||
data: [],
|
||||
};
|
||||
timetable.data = timetable.data.map((day) => {
|
||||
if (!day) return [];
|
||||
return day.map((lesson) => {
|
||||
if (Array.isArray(lesson)) return toRaw(lesson);
|
||||
else return [toRaw(lesson)];
|
||||
});
|
||||
});
|
||||
const timetableClone = ref(structuredClone(toRaw(timetable)));
|
||||
|
||||
const swiperElement = ref();
|
||||
function setSwiper(swiper) {
|
||||
swiperElement.value = swiper;
|
||||
}
|
||||
|
||||
function getLessonIndex(index, day) {
|
||||
let lessonIndex = 0;
|
||||
for (let i = 0; i < index; i++) {
|
||||
lessonIndex += day[i][0].length || 1;
|
||||
}
|
||||
return lessonIndex;
|
||||
}
|
||||
|
||||
function getLessonLength(lesson) {
|
||||
if (Array.isArray(lesson)) return lesson[0].length || 1;
|
||||
else return lesson.length || 1;
|
||||
}
|
||||
|
||||
function arrayMove(arr, fromIndex, toIndex) {
|
||||
let element = arr[fromIndex];
|
||||
arr.splice(fromIndex, 1);
|
||||
arr.splice(toIndex, 0, element);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="editor">
|
||||
<DateSelector
|
||||
:selectedDay="(swiperElement || { activeIndex: 0 }).activeIndex"
|
||||
@changeDay="
|
||||
(day) => swiperElement.slideTo(swiperElement.activeIndex + day)
|
||||
"
|
||||
/>
|
||||
<div class="settings">
|
||||
<span>Name: </span>
|
||||
<input type="text" v-model="timetableClone.title" />
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<swiper
|
||||
:slides-per-view="1"
|
||||
:space-between="50"
|
||||
:initial-slide="0"
|
||||
@swiper="setSwiper"
|
||||
>
|
||||
<swiper-slide :key="day" v-for="day in 5">
|
||||
<ScrollableContainer>
|
||||
<div class="list">
|
||||
<div
|
||||
class="lesson"
|
||||
v-for="(lesson, index) in timetableClone.data[day - 1]"
|
||||
:key="index"
|
||||
>
|
||||
<!-- Add main editable element -->
|
||||
<LessonGroupList
|
||||
:index="getLessonIndex(index, timetableClone.data[day - 1])"
|
||||
:lesson="lesson"
|
||||
:edit="true"
|
||||
@change="(e) => (timetableClone.data[day - 1][index] = e)"
|
||||
@delete="timetableClone.data[day - 1].splice(index, 1)"
|
||||
@move="
|
||||
(direction) => {
|
||||
if (direction == 'up' && index > 0) {
|
||||
arrayMove(
|
||||
timetableClone.data[day - 1],
|
||||
index,
|
||||
index - 1
|
||||
);
|
||||
} else if (
|
||||
direction == 'down' &&
|
||||
index < timetableClone.data[day - 1].length
|
||||
) {
|
||||
arrayMove(
|
||||
timetableClone.data[day - 1],
|
||||
index,
|
||||
index + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
"
|
||||
/>
|
||||
<!-- Add placeholder elements for lessons with length > 1 -->
|
||||
<div
|
||||
class="inactive"
|
||||
v-for="placeholderLesson in getLessonLength(lesson) - 1"
|
||||
:key="placeholderLesson"
|
||||
>
|
||||
<LessonGroupList
|
||||
:index="
|
||||
getLessonIndex(index, timetableClone.data[day - 1]) +
|
||||
placeholderLesson
|
||||
"
|
||||
:lesson="lesson"
|
||||
:edit="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="create"
|
||||
@click="
|
||||
if (timetableClone.data[day - 1]) {
|
||||
timetableClone.data[day - 1].push([{}]);
|
||||
} else {
|
||||
timetableClone.data[day - 1] = [[{}]];
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>{{ $t("editor.newLesson") }}</span>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar">
|
||||
<EditorNavbar
|
||||
@back="$router.back()"
|
||||
@save="
|
||||
() => {
|
||||
timetable.data = timetableClone.data;
|
||||
timetable.title = timetableClone.title;
|
||||
timetable.source = 'Manual';
|
||||
timetable.trusted = true;
|
||||
$router.back();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import "swiper/css";
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.editor {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings {
|
||||
padding: 5px 10px 0px 10px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.settings input {
|
||||
border: unset;
|
||||
background-color: unset;
|
||||
color: unset;
|
||||
border-bottom: 1px solid var(--text-color);
|
||||
outline: unset;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
padding: 5px 10px 0px 10px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
opacity: 0.7;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.create {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import TimetableCard from "@/components/settings/timetable-card.vue";
|
||||
import { timetables, localTimetables, timetableId } from "@/store";
|
||||
import { PlusIcon } from "lucide-vue-next";
|
||||
import { toRaw } from "vue";
|
||||
|
||||
function copyTimetable(timetable) {
|
||||
@ -9,39 +10,54 @@ function copyTimetable(timetable) {
|
||||
newTimetable.id = new Date().getTime();
|
||||
localTimetables.value.push(newTimetable);
|
||||
}
|
||||
|
||||
function createTimetable() {
|
||||
localTimetables.value.push({
|
||||
id: new Date().getTime(),
|
||||
title: "New timetable",
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>{{ $t("settings.heading.localTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in localTimetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="true"
|
||||
@click="timetableId = timetable.id"
|
||||
@copy="copyTimetable(timetable)"
|
||||
@delete="
|
||||
localTimetables.splice(
|
||||
localTimetables.findIndex((e) => e.id == timetable.id),
|
||||
1
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<h2>{{ $t("settings.heading.remoteTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in timetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="false"
|
||||
@click="timetableId = timetable.id"
|
||||
@copy="copyTimetable(timetable)"
|
||||
/>
|
||||
<div class="content" v-if="$route.name == 'title.settings.timetable'">
|
||||
<h2>{{ $t("settings.heading.localTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in localTimetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="true"
|
||||
@click="timetableId = timetable.id"
|
||||
@edit="$router.push('timetable/edit/' + timetable.id)"
|
||||
@copy="copyTimetable(timetable)"
|
||||
@delete="
|
||||
localTimetables.splice(
|
||||
localTimetables.findIndex((e) => e.id == timetable.id),
|
||||
1
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="create" @click="createTimetable">
|
||||
<PlusIcon /> {{ $t("settings.text.createTimetable") }}
|
||||
</div>
|
||||
<h2>{{ $t("settings.heading.remoteTimetables") }}</h2>
|
||||
<div class="list">
|
||||
<TimetableCard
|
||||
v-for="timetable in timetables"
|
||||
:key="timetable.id"
|
||||
:timetable="timetable"
|
||||
:selected="timetableId == timetable.id"
|
||||
:editable="false"
|
||||
@click="timetableId = timetable.id"
|
||||
@copy="copyTimetable(timetable)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<RouterView v-else />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@ -57,4 +73,13 @@ p {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.create {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user