268 lines
7.3 KiB
JavaScript
268 lines
7.3 KiB
JavaScript
import { ref, watch, computed } from "vue";
|
|
import { getNextAndPrevDay, setUTCMidnight } from "@/util";
|
|
import i18n from "@/i18n";
|
|
import {
|
|
DEMO_CLASS_LIST,
|
|
DEMO_SESSION_INFO,
|
|
DEMO_TIMETABLE,
|
|
getDemoHistory,
|
|
getDemoSubstitutions,
|
|
} from "./demoData";
|
|
|
|
/* Router */
|
|
export const shouldLogin = ref(false);
|
|
export const loading = ref(false);
|
|
export const loadingProgress = ref(0);
|
|
export const loadingFailed = ref(false);
|
|
|
|
/* Preferences */
|
|
export const profiles = ref(
|
|
JSON.parse(localStorage.getItem("profiles")) || [
|
|
{
|
|
id: 0,
|
|
name: "Default Profile",
|
|
classFilter: "none",
|
|
timetableId: 1,
|
|
timetableGroups: [],
|
|
},
|
|
],
|
|
);
|
|
export const activeProfile = computed(() => {
|
|
return (
|
|
profiles.value.find((e) => e.id == activeProfileId.value) ||
|
|
profiles.value[0]
|
|
);
|
|
});
|
|
export const activeProfileId = ref(
|
|
localStorage.getItem("activeProfile") || profiles.value[0].id,
|
|
);
|
|
|
|
watch(
|
|
profiles,
|
|
(newValue) => {
|
|
localStorage.setItem("profiles", JSON.stringify(newValue));
|
|
},
|
|
{ deep: true },
|
|
);
|
|
watch(activeProfileId, (newValue) => {
|
|
localStorage.setItem("activeProfile", newValue);
|
|
});
|
|
watch(
|
|
() => activeProfile.value.classFilter,
|
|
() => {
|
|
fetchData(getNextAndPrevDay(selectedDate.value), false);
|
|
},
|
|
);
|
|
|
|
export const cachedTimetables = ref(
|
|
JSON.parse(localStorage.getItem("cachedTimetables")) || {},
|
|
);
|
|
export const localTimetables = ref(
|
|
JSON.parse(localStorage.getItem("timetables")) || [],
|
|
);
|
|
|
|
watch(
|
|
cachedTimetables,
|
|
(newValue) => {
|
|
localStorage.setItem("cachedTimetables", JSON.stringify(newValue));
|
|
},
|
|
{ deep: true },
|
|
);
|
|
watch(
|
|
localTimetables,
|
|
(newValue) => {
|
|
localStorage.setItem("timetables", JSON.stringify(newValue));
|
|
},
|
|
{ deep: true },
|
|
);
|
|
|
|
export const theme = ref(localStorage.getItem("theme") || "auto");
|
|
watch(theme, (newValue) => {
|
|
localStorage.setItem("theme", newValue);
|
|
});
|
|
|
|
/* Date selector */
|
|
export const selectedDate = ref(setUTCMidnight(new Date()));
|
|
export const selectedDay = computed(() => selectedDate.value.getDay() - 1);
|
|
// Jump to next Monday if it is weekend
|
|
if (selectedDay.value == 5)
|
|
selectedDate.value = new Date(selectedDate.value.getTime() + 86400000 * 2);
|
|
if (selectedDay.value == -1)
|
|
selectedDate.value = new Date(selectedDate.value.getTime() + 86400000);
|
|
|
|
// Set this to a positive or negative integer
|
|
// to change selectedDate by this number
|
|
export const changeDay = ref(0);
|
|
// Set this to jump to a specific date
|
|
export const changeDate = ref(new Date());
|
|
|
|
/* Data store */
|
|
export const timetable = computed(() => {
|
|
const localTimetable = localTimetables.value.find(
|
|
(e) => e.id == activeProfile.value.timetableId,
|
|
);
|
|
const remoteTimetable = timetables.value.find(
|
|
(e) => e.id == activeProfile.value.timetableId,
|
|
);
|
|
return (
|
|
localTimetable || remoteTimetable || { trusted: true, source: "", data: [] }
|
|
);
|
|
});
|
|
export const sessionInfo = ref({});
|
|
export const timetables = ref(
|
|
(cachedTimetables.value[activeProfileId.value] || {}).timetables || [],
|
|
);
|
|
export const times = ref(
|
|
(cachedTimetables.value[activeProfileId.value] || {}).times || [],
|
|
);
|
|
export const substitutions = ref({});
|
|
export const history = ref({});
|
|
export const classList = ref([]);
|
|
|
|
/* API functions */
|
|
// Set the `VITE_API_ENDPOINT` env variable when
|
|
// building the frontend to use an external api server
|
|
export const baseUrl = import.meta.env.VITE_API_ENDPOINT || "/api";
|
|
|
|
export async function fetchData(days, partial) {
|
|
if (!timetable.value.data || Object.keys(classList.value).length == 0) {
|
|
partial = false;
|
|
}
|
|
|
|
const steps = 2 * days.length + (partial ? 0 : 2);
|
|
let step = 1;
|
|
loadingFailed.value = false;
|
|
loadingProgress.value = 0.1;
|
|
loading.value = true;
|
|
|
|
// Check if the API server is reachable
|
|
// and the user is authenticated
|
|
if (!(await fetchSessionInfo())) {
|
|
loadingFailed.value = true;
|
|
loadingProgress.value = 1;
|
|
return;
|
|
}
|
|
|
|
loadingProgress.value = step++ / steps;
|
|
|
|
if (!partial) {
|
|
await fetchClassList();
|
|
loadingProgress.value = step++ / steps;
|
|
await fetchTimetables();
|
|
loadingProgress.value = step++ / steps;
|
|
}
|
|
for (const day of days) {
|
|
await fetchSubstitutions(day);
|
|
loadingProgress.value = step++ / steps;
|
|
await fetchHistory(day);
|
|
loadingProgress.value = step++ / steps;
|
|
}
|
|
|
|
loadingProgress.value = 1;
|
|
loading.value = false;
|
|
}
|
|
|
|
// Load new data if date changes
|
|
watch(selectedDate, () =>
|
|
fetchData(getNextAndPrevDay(selectedDate.value), true),
|
|
);
|
|
|
|
export async function fetchSessionInfo() {
|
|
try {
|
|
sessionInfo.value = DEMO_SESSION_INFO;
|
|
} catch {
|
|
console.log("Error while fetching data: No internet connection!");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export async function fetchClassList() {
|
|
classList.value = DEMO_CLASS_LIST;
|
|
}
|
|
|
|
export async function fetchTimetables() {
|
|
if (activeProfile.value.classFilter == "Demo") {
|
|
timetables.value = DEMO_TIMETABLE.timetables;
|
|
} else {
|
|
timetables.value = [];
|
|
}
|
|
times.value = DEMO_TIMETABLE.times;
|
|
}
|
|
|
|
export async function fetchSubstitutions(day) {
|
|
if (activeProfile.value.classFilter == "Demo") {
|
|
substitutions.value[day] = getDemoSubstitutions(day);
|
|
} else {
|
|
substitutions.value[day] = [];
|
|
}
|
|
}
|
|
|
|
export async function fetchHistory(day) {
|
|
if (activeProfile.value.classFilter == "Demo") {
|
|
history.value[day] = getDemoHistory(day);
|
|
} else {
|
|
history.value[day] = [];
|
|
}
|
|
}
|
|
|
|
/* Preprocess the timetable data */
|
|
export const parsedTimetable = computed(() => {
|
|
// Check if timetable data exists
|
|
if (!timetable.value.data) return [];
|
|
return timetable.value.data.map((day) => {
|
|
const parsedDay = [];
|
|
for (const lesson of day) {
|
|
let usedLesson = { ...lesson };
|
|
// Check if lesson has multiple options
|
|
// (timetable groups)
|
|
if (Array.isArray(lesson) && lesson.length > 1) {
|
|
let matchingLesson = lesson.find((e) =>
|
|
activeProfile.value.timetableGroups.includes(e.group),
|
|
);
|
|
// If no valid timetable group is configured
|
|
// add a dummy lesson showing a notice
|
|
if (!matchingLesson) {
|
|
matchingLesson = {
|
|
subject: lesson.map((e) => e.subject).join(" / "),
|
|
teacher: i18n.global.t("timetable.configureTimetableGroup"),
|
|
length: lesson[0].length || 1,
|
|
};
|
|
}
|
|
usedLesson = { ...matchingLesson };
|
|
} else if (Array.isArray(lesson)) {
|
|
usedLesson = { ...lesson[0] };
|
|
}
|
|
// Duplicate the lesson if its length is > 1 for it
|
|
// to show up multiple times in the timetable view
|
|
const lessonLength = usedLesson.length || 1;
|
|
delete usedLesson.length;
|
|
for (var i = 0; i < lessonLength; i++) parsedDay.push(usedLesson);
|
|
}
|
|
return parsedDay;
|
|
});
|
|
});
|
|
|
|
export const possibleTimetableGroups = computed(() => {
|
|
const foundTimetableGroups = [];
|
|
if (!timetable.value.data) return [];
|
|
// Make a list of all possible timetable groups
|
|
// found in the current timetable
|
|
for (const day of timetable.value.data) {
|
|
for (const lesson of day) {
|
|
if (Array.isArray(lesson) && lesson.length > 1) {
|
|
for (const group of lesson) {
|
|
if (group.group && !foundTimetableGroups.includes(group.group)) {
|
|
foundTimetableGroups.push(group.group);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return foundTimetableGroups;
|
|
});
|
|
|
|
// Initially fetch data for the
|
|
// current, next and previous day
|
|
fetchData(getNextAndPrevDay(selectedDate.value), false);
|