mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-07-16 17:48:51 +02:00
feat:#5 added first offline calendar functions
This commit is contained in:
@ -15,7 +15,8 @@
|
|||||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { Module } from "../model/module";
|
import { Module } from "../model/module";
|
||||||
import { Calendar } from "../model/calendar";
|
import tokenStore from "@/store/tokenStore.ts";
|
||||||
|
import { Calendar } from "@/model/calendar.ts";
|
||||||
|
|
||||||
export async function getCalender(token: string): Promise<Module[]> {
|
export async function getCalender(token: string): Promise<Module[]> {
|
||||||
const request = new Request("/api/collections/feeds/records/" + token, {
|
const request = new Request("/api/collections/feeds/records/" + token, {
|
||||||
@ -24,9 +25,10 @@ export async function getCalender(token: string): Promise<Module[]> {
|
|||||||
|
|
||||||
return await fetch(request).then((response) => {
|
return await fetch(request).then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response
|
return response.json().then((calendarResponse: Calendar) => {
|
||||||
.json()
|
tokenStore().feed = calendarResponse;
|
||||||
.then((calendarResponse: Calendar) => calendarResponse.modules);
|
return calendarResponse.modules;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -21,16 +21,16 @@ import FullCalendar from "@fullcalendar/vue3";
|
|||||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
import interactionPlugin from "@fullcalendar/interaction";
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
import {computed, ComputedRef, inject, ref, Ref, watch} from "vue";
|
import { computed, ComputedRef, inject, ref, Ref, watch } from "vue";
|
||||||
import {CalendarOptions, DatesSetArg, EventInput} from "@fullcalendar/core";
|
import { CalendarOptions, DatesSetArg, EventInput } from "@fullcalendar/core";
|
||||||
import {useI18n} from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import allLocales from "@fullcalendar/core/locales-all";
|
import allLocales from "@fullcalendar/core/locales-all";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import {formatYearMonthDay} from "@/helpers/dates";
|
import { formatYearMonthDay } from "@/helpers/dates";
|
||||||
import {useQuery} from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import {fetchRoomOccupancy} from "@/api/fetchRoomOccupancy";
|
import { fetchRoomOccupancy } from "@/api/fetchRoomOccupancy";
|
||||||
import {isValid} from "date-fns";
|
import { isValid } from "date-fns";
|
||||||
import {RoomOccupancyList} from "@/model/roomOccupancyList";
|
import { RoomOccupancyList } from "@/model/roomOccupancyList";
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
|
@ -95,8 +95,13 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"successDetail": "calendar successfully deleted",
|
"successDetail": "calendar successfully deleted",
|
||||||
"errorDetail": "calendar could not be deleted",
|
"errorDetail": "calendar could not be deleted",
|
||||||
"successDetailLoad": "calendar successfully loaded"
|
"successDetailLoad": "calendar successfully loaded",
|
||||||
}
|
"info": "info"
|
||||||
|
},
|
||||||
|
"calendarContainsNoModules": "calendar contains no modules",
|
||||||
|
"noCalendarInOfflineMode": "calendar isn't available in offline mode",
|
||||||
|
"calendarLoadedFromOfflineMode": "calendar loaded from offline cache",
|
||||||
|
"calendarLoadedFromServer": "calendar loaded from web"
|
||||||
},
|
},
|
||||||
"additionalModules": {
|
"additionalModules": {
|
||||||
"subTitle": "select additional modules that are not listed in the regular semester for your course",
|
"subTitle": "select additional modules that are not listed in the regular semester for your course",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
//You should have received a copy of the GNU Affero General Public License
|
//You should have received a copy of the GNU Affero General Public License
|
||||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import {Binary, Document} from "bson";
|
import { Binary, Document } from "bson";
|
||||||
import { AnonymizedOccupancy } from "./event";
|
import { AnonymizedOccupancy } from "./event";
|
||||||
import {
|
import {
|
||||||
Duration,
|
Duration,
|
||||||
@ -218,16 +218,23 @@ export class RoomOccupancyList {
|
|||||||
// Iterate over all bits in the current byte
|
// Iterate over all bits in the current byte
|
||||||
for (let bit_i = 0; bit_i < 8; bit_i++) {
|
for (let bit_i = 0; bit_i < 8; bit_i++) {
|
||||||
const isOccupied = (byte & (1 << (7 - bit_i))) !== 0;
|
const isOccupied = (byte & (1 << (7 - bit_i))) !== 0;
|
||||||
const calculateOccupancyBitIndex = (byte_i: number, bit_i: number) => byte_i * 8 + bit_i;
|
const calculateOccupancyBitIndex = (byte_i: number, bit_i: number) =>
|
||||||
|
byte_i * 8 + bit_i;
|
||||||
|
|
||||||
if(firstOccupancyBit === null){
|
if (firstOccupancyBit === null) {
|
||||||
if (isOccupied) {
|
if (isOccupied) {
|
||||||
firstOccupancyBit = calculateOccupancyBitIndex(byte_i, bit_i);
|
firstOccupancyBit = calculateOccupancyBitIndex(byte_i, bit_i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isOccupied) {
|
if (!isOccupied) {
|
||||||
const startTime = addMinutes(start, firstOccupancyBit * granularity);
|
const startTime = addMinutes(
|
||||||
const endTime = addMinutes(start, calculateOccupancyBitIndex(byte_i, bit_i) * granularity);
|
start,
|
||||||
|
firstOccupancyBit * granularity,
|
||||||
|
);
|
||||||
|
const endTime = addMinutes(
|
||||||
|
start,
|
||||||
|
calculateOccupancyBitIndex(byte_i, bit_i) * granularity,
|
||||||
|
);
|
||||||
// add event between start and end of a block of boolean true values
|
// add event between start and end of a block of boolean true values
|
||||||
occupancyList.push(
|
occupancyList.push(
|
||||||
new AnonymizedOccupancy(
|
new AnonymizedOccupancy(
|
||||||
@ -315,7 +322,8 @@ export class RoomOccupancyList {
|
|||||||
json.granularity,
|
json.granularity,
|
||||||
json.blocks,
|
json.blocks,
|
||||||
json.rooms.map(
|
json.rooms.map(
|
||||||
(room: { name: string, occupancy: Binary }) => new RoomOccupancy(room.name, room.occupancy),
|
(room: { name: string; occupancy: Binary }) =>
|
||||||
|
new RoomOccupancy(room.name, room.occupancy),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,20 @@
|
|||||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
|
import { Calendar } from "../model/calendar";
|
||||||
|
|
||||||
const tokenStore = defineStore("tokenStore", {
|
const tokenStore = defineStore("tokenStore", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
token: "",
|
token: "",
|
||||||
|
feed: useLocalStorage("feed", {
|
||||||
|
collectionId: "",
|
||||||
|
collectionName: "",
|
||||||
|
created: "",
|
||||||
|
id: "",
|
||||||
|
modules: [],
|
||||||
|
updated: "",
|
||||||
|
} as Calendar),
|
||||||
}),
|
}),
|
||||||
persist: true,
|
persist: true,
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -50,22 +50,67 @@ function loadCalendar(): void {
|
|||||||
moduleStore().removeAllModules();
|
moduleStore().removeAllModules();
|
||||||
tokenStore().setToken(token.value);
|
tokenStore().setToken(token.value);
|
||||||
|
|
||||||
|
if (navigator.onLine) {
|
||||||
getCalender(token.value).then((data: Module[]) => {
|
getCalender(token.value).then((data: Module[]) => {
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
data.forEach((module) => {
|
data.forEach((module) => {
|
||||||
moduleStore().addModule(module);
|
moduleStore().addModule(module);
|
||||||
});
|
});
|
||||||
modules.value = moduleStore().modules;
|
modules.value = moduleStore().modules;
|
||||||
|
toast.add({
|
||||||
|
severity: "success",
|
||||||
|
summary: t("editCalendarView.toast.success"),
|
||||||
|
detail: t("editCalendarView.calendarLoadedFromServer"),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
router.push("/edit-calendar");
|
router.push("/edit-calendar");
|
||||||
} else {
|
} else {
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: "error",
|
severity: "error",
|
||||||
summary: t("editCalendarView.error"),
|
summary: t("editCalendarView.toast.error"),
|
||||||
detail: t("editCalendarView.noCalendarFound"),
|
detail: t("editCalendarView.noCalendarFound"),
|
||||||
life: 3000,
|
life: 3000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
if(tokenStore().feed.id === token.value) {
|
||||||
|
// get data from tokenStore feed if offline
|
||||||
|
const offlineModules = tokenStore().feed.modules;
|
||||||
|
|
||||||
|
if (offlineModules.length > 0) {
|
||||||
|
offlineModules.forEach((module) => {
|
||||||
|
moduleStore().addModule(module);
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: "info",
|
||||||
|
summary: t("editCalendarView.toast.info"),
|
||||||
|
detail: t("editCalendarView.calendarLoadedFromOfflineMode"),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toast.add({
|
||||||
|
severity: "info",
|
||||||
|
summary: t("editCalendarView.info"),
|
||||||
|
detail: t("editCalendarView.calendarContainsNoModules"),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.add({
|
||||||
|
severity: "error",
|
||||||
|
summary: t("editCalendarView.toast.error"),
|
||||||
|
detail: t("editCalendarView.noCalendarInOfflineMode"),
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modules.value = moduleStore().modules;
|
||||||
|
router.push("/edit-calendar");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ const { t } = useI18n({ useScope: "global" });
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const token = ref(tokenStore().token || ("" as string));
|
const token = ref(tokenStore().token || ("" as string));
|
||||||
|
const calendarViewerRef = ref<InstanceType<typeof CalendarViewer>>();
|
||||||
|
|
||||||
// parse token from query parameter
|
// parse token from query parameter
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
@ -22,8 +23,6 @@ if (tokenFromUrl) {
|
|||||||
loadCalendar();
|
loadCalendar();
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarViewerRef = ref<InstanceType<typeof CalendarViewer>>();
|
|
||||||
|
|
||||||
function loadCalendar() {
|
function loadCalendar() {
|
||||||
try {
|
try {
|
||||||
token.value = extractToken(token.value);
|
token.value = extractToken(token.value);
|
||||||
|
@ -96,6 +96,18 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Add the runtime caching strategy for /api/modules
|
||||||
|
urlPattern: ({url}) => url.pathname.startsWith('/api/modules'),
|
||||||
|
method: 'GET',
|
||||||
|
handler: 'NetworkFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'modules-cache',
|
||||||
|
expiration: {
|
||||||
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
urlPattern: /^https?.*/,
|
urlPattern: /^https?.*/,
|
||||||
handler: "NetworkFirst",
|
handler: "NetworkFirst",
|
||||||
|
Reference in New Issue
Block a user