- Save class filter, timetable and timetable groups in profiles - Easily switch between profiles - Rename profiles - Export/Import/Duplicate profiles
178 lines
4.2 KiB
Vue
178 lines
4.2 KiB
Vue
<script setup>
|
|
import TimetableCard from "@/components/settings/timetable-card.vue";
|
|
import {
|
|
timetable,
|
|
timetables,
|
|
localTimetables,
|
|
activeProfile,
|
|
baseUrl,
|
|
fetchTimetables,
|
|
} from "@/store";
|
|
import { canEditTimetable } from "@/permission";
|
|
import { PlusIcon, PaperclipIcon } from "lucide-vue-next";
|
|
import { toRaw, ref } from "vue";
|
|
import download from "downloadjs";
|
|
import { loading, loadingProgress } from "@/store";
|
|
|
|
function copyTimetable(timetable) {
|
|
const newTimetable = structuredClone(toRaw(timetable));
|
|
newTimetable.title = "Copy of " + timetable.title;
|
|
newTimetable.id = new Date().getTime();
|
|
localTimetables.value.push(newTimetable);
|
|
}
|
|
|
|
function createTimetable() {
|
|
localTimetables.value.push({
|
|
id: new Date().getTime(),
|
|
title: "New timetable",
|
|
data: [],
|
|
});
|
|
}
|
|
|
|
const fileInput = ref();
|
|
function importTimetable(event) {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
const contents = e.target.result;
|
|
try {
|
|
const data = JSON.parse(contents);
|
|
if (!data.data) throw "Invalid data";
|
|
localTimetables.value.push(data);
|
|
} catch (e) {
|
|
console.log(e.stack);
|
|
alert("Import failed! Check your timetable file!");
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function exportTimetable(timetable) {
|
|
download(
|
|
JSON.stringify(timetable),
|
|
`timetable-${timetable.id}.json`,
|
|
"application/json",
|
|
);
|
|
}
|
|
|
|
async function uploadTimetable(id) {
|
|
loadingProgress.value = 0.1;
|
|
loading.value = true;
|
|
|
|
const timetableData = timetable.value;
|
|
const response = await fetch(
|
|
baseUrl + "/timetable?id=" + encodeURIComponent(id),
|
|
{
|
|
method: "put",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(timetableData),
|
|
},
|
|
);
|
|
|
|
if (response.status != 201) {
|
|
alert("Failed uploading timetable!");
|
|
} else {
|
|
loadingProgress.value = 0.5;
|
|
await fetchTimetables();
|
|
activeProfile.value.timetableId = id;
|
|
}
|
|
|
|
loadingProgress.value = 1;
|
|
loading.value = false;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<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="activeProfile.timetableId == timetable.id"
|
|
:editable="true"
|
|
:remote="false"
|
|
@click="activeProfile.timetableId = timetable.id"
|
|
@edit="$router.push('timetable/edit/' + timetable.id)"
|
|
@copy="copyTimetable(timetable)"
|
|
@delete="
|
|
localTimetables.splice(
|
|
localTimetables.findIndex((e) => e.id == timetable.id),
|
|
1,
|
|
)
|
|
"
|
|
@export="exportTimetable(timetable)"
|
|
/>
|
|
</div>
|
|
<div class="buttons">
|
|
<div class="create" @click="createTimetable">
|
|
<PlusIcon /> {{ $t("settings.text.createTimetable") }}
|
|
</div>
|
|
<div class="import" @click="fileInput.click()">
|
|
<PaperclipIcon /> {{ $t("settings.text.importTimetable") }}
|
|
<input
|
|
type="file"
|
|
ref="fileInput"
|
|
style="display: none"
|
|
@change="importTimetable"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<h2>{{ $t("settings.heading.remoteTimetables") }}</h2>
|
|
<div class="list">
|
|
<TimetableCard
|
|
v-for="timetable in timetables"
|
|
:key="timetable.id"
|
|
:timetable="timetable"
|
|
:selected="activeProfile.timetableId == timetable.id"
|
|
:editable="canEditTimetable(timetable.id)"
|
|
:remote="true"
|
|
@click="activeProfile.timetableId = timetable.id"
|
|
@copy="copyTimetable(timetable)"
|
|
@export="exportTimetable(timetable)"
|
|
@upload="uploadTimetable(timetable.id)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<RouterView v-else />
|
|
</template>
|
|
|
|
<style scoped>
|
|
h2 {
|
|
margin: 5px 0px;
|
|
}
|
|
|
|
p {
|
|
margin: 5px 0px;
|
|
}
|
|
|
|
.list {
|
|
display: grid;
|
|
gap: 8px;
|
|
}
|
|
|
|
.buttons {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin-top: 5px;
|
|
user-select: none;
|
|
}
|
|
|
|
.create,
|
|
.import {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 10px;
|
|
gap: 5px;
|
|
cursor: pointer;
|
|
}
|
|
</style>
|