mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-22 22:49:32 +00:00
add changes for calendar flicker on event reload, and calendar scroll mods
This commit is contained in:
@@ -32,6 +32,7 @@ class KalenderLib
|
||||
$end_date = date('Y-m-d', strtotime($end_date . ' +1 day'));
|
||||
|
||||
$this->_ci->KalenderModel->addSelect('tbl_kalender.kalender_id,
|
||||
tbl_kalender.eindeutige_gruppen_id,
|
||||
tbl_kalender.status_kurzbz,
|
||||
tbl_kalender.typ,
|
||||
tbl_kalender.von,
|
||||
@@ -130,6 +131,8 @@ class KalenderLib
|
||||
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.von >=', $start_date);
|
||||
$this->_ci->KalenderModel->db->where('tbl_kalender.bis <', $end_date);
|
||||
|
||||
$this->_ci->KalenderModel->addOrder('tbl_kalender.eindeutige_gruppen_id', 'DESC');
|
||||
}
|
||||
|
||||
private function _mapEvents($data, $collisionCheck = true)
|
||||
@@ -151,6 +154,8 @@ class KalenderLib
|
||||
$bis = new DateTime($row->bis);
|
||||
|
||||
$events[$id] = (object) [
|
||||
'kalender_id' => $id,
|
||||
'eindeutige_gruppen_id' => $row->eindeutige_gruppen_id,
|
||||
'type' => $row->typ,
|
||||
'beginn' => $von->format('H:i:s'),
|
||||
'ende' => $bis->format('H:i:s'),
|
||||
@@ -166,7 +171,6 @@ class KalenderLib
|
||||
'farbe' => isset($row->farbe) ? $row->farbe : '',
|
||||
'lehrveranstaltung_id' => $row->lehrveranstaltung_id,
|
||||
'organisationseinheit' => isset($row->oe_kurzbz) ? $row->oe_kurzbz : '',
|
||||
'kalender_id' => $id,
|
||||
'lehreinheit_id' => [],
|
||||
'lektor' => [],
|
||||
'teilnehmer_gruppe' => [],
|
||||
|
||||
@@ -185,3 +185,69 @@ body {
|
||||
}
|
||||
|
||||
|
||||
.updated-event {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
animation: modernFocus 1s ease-out;
|
||||
}
|
||||
|
||||
.updated-event-long {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
animation: modernFocus 1.8s ease-out;
|
||||
}
|
||||
|
||||
@keyframes modernFocus {
|
||||
0% {
|
||||
background-color: #cfd4d8;
|
||||
box-shadow:
|
||||
0 0 0 0 rgba(120, 120, 120, 0.8),
|
||||
0 0 0 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
30% {
|
||||
background-color: #eef1f3;
|
||||
box-shadow:
|
||||
0 0 0 8px rgba(120, 120, 120, 0.25),
|
||||
0 0 30px 8px rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
60% {
|
||||
background-color: #f7f8f9;
|
||||
box-shadow:
|
||||
0 0 0 14px rgba(120, 120, 120, 0.15),
|
||||
0 0 45px 12px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: inherit;
|
||||
box-shadow:
|
||||
0 0 0 24px rgba(120, 120, 120, 0),
|
||||
0 0 60px 20px rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.deemphasized-event {
|
||||
animation: deEmphasize 1s ease-out;
|
||||
}
|
||||
|
||||
.deemphasized-event-long {
|
||||
animation: deEmphasize 1.8s ease-out;
|
||||
}
|
||||
|
||||
@keyframes deEmphasize {
|
||||
0% {
|
||||
opacity: 1;
|
||||
filter: saturate(1) brightness(1);
|
||||
}
|
||||
|
||||
30% {
|
||||
opacity: 0.55;
|
||||
filter: saturate(0.4) brightness(0.4);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
filter: saturate(1) brightness(1);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export default {
|
||||
></line-background>
|
||||
<line-event
|
||||
v-for="(event, i) in eventsWithRowInfo"
|
||||
:key="i"
|
||||
:key="event.orig.eindeutige_gruppen_id || i"
|
||||
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
|
||||
:event="event"
|
||||
@resize-start="$emit('resize-start', $event)"
|
||||
|
||||
@@ -110,6 +110,7 @@ export default {
|
||||
style="z-index: 2"
|
||||
:draggable="draggable"
|
||||
:data-id="'event-' + event.orig.kalender_id"
|
||||
:data-group-id="'event-group-' + event.orig.eindeutige_gruppen_id"
|
||||
ref="eventEl"
|
||||
@dragstart="onDragStart"
|
||||
v-draggable:move.noimage="draggable ? dragKalenderCollection : {}"
|
||||
|
||||
@@ -163,6 +163,7 @@ export default {
|
||||
assignedResources: [],
|
||||
areFormButtonsDisplayed: false,
|
||||
},
|
||||
currentlyUpdatedEvent: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -399,25 +400,99 @@ export default {
|
||||
if (hasStg) filter.stg = this.stg;
|
||||
if (hasLektoren) filter.uid = this.lecturers.map((l) => l.uid);
|
||||
|
||||
let response = null;
|
||||
if (this.previewRole === "lektor")
|
||||
return [
|
||||
response = [
|
||||
this.$api.call(
|
||||
ApiKalender.getPlanLecturer(start.toISODate(), end.toISODate()),
|
||||
),
|
||||
];
|
||||
|
||||
if (this.previewRole === "student")
|
||||
return [
|
||||
response = [
|
||||
this.$api.call(
|
||||
ApiKalender.getPlanStudent(start.toISODate(), end.toISODate()),
|
||||
),
|
||||
];
|
||||
|
||||
return [
|
||||
response = [
|
||||
this.$api.call(
|
||||
ApiKalender.getPlan(filter, start.toISODate(), end.toISODate()),
|
||||
),
|
||||
];
|
||||
|
||||
if (response) {
|
||||
response[0].then((result) => {
|
||||
if (!this.currentlyUpdatedEvent) return;
|
||||
|
||||
document.querySelectorAll(".updated-event").forEach((el) => {
|
||||
el.classList.remove("updated-event");
|
||||
});
|
||||
document.querySelectorAll(".updated-event-long").forEach((el) => {
|
||||
el.classList.remove("updated-event-long");
|
||||
});
|
||||
document.querySelectorAll(".deemphasized-event").forEach((el) => {
|
||||
el.classList.remove("deemphasized-event");
|
||||
});
|
||||
document
|
||||
.querySelectorAll(".deemphasized-event-long")
|
||||
.forEach((el) => {
|
||||
el.classList.remove("deemphasized-event-long");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const eventEl = document.querySelector(
|
||||
`[data-group-id="event-group-${this.currentlyUpdatedEvent.eindeutige_gruppen_id}"]`,
|
||||
);
|
||||
if (!eventEl) return;
|
||||
|
||||
const calendar = document.querySelector(".fhc-calendar-base-grid");
|
||||
const eventRect = eventEl.getBoundingClientRect();
|
||||
|
||||
const offset = 300;
|
||||
|
||||
const isInsideScrolledView =
|
||||
eventEl.offsetLeft < calendar.scrollLeft + calendar.clientWidth &&
|
||||
eventEl.offsetLeft + eventEl.offsetWidth > calendar.scrollLeft &&
|
||||
eventEl.offsetTop < calendar.scrollTop + calendar.clientHeight &&
|
||||
eventEl.offsetTop + eventEl.offsetHeight > calendar.scrollTop;
|
||||
|
||||
const rect = eventEl.getBoundingClientRect();
|
||||
if (!isInsideScrolledView) {
|
||||
eventEl.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
inline: "center",
|
||||
block: "nearest",
|
||||
});
|
||||
}
|
||||
|
||||
let timeout = 0;
|
||||
let emphasizeUpdateClassName = isInsideScrolledView
|
||||
? "updated-event"
|
||||
: "updated-event-long";
|
||||
let deemphasizedUpdateClassName = isInsideScrolledView
|
||||
? "deemphasized-event"
|
||||
: "deemphasized-event-long";
|
||||
|
||||
if (!isInsideScrolledView) timeout = 500;
|
||||
|
||||
setTimeout(() => {
|
||||
eventEl.classList.add(emphasizeUpdateClassName);
|
||||
document
|
||||
.querySelectorAll(".fhc-calendar-base-grid-line-event")
|
||||
.forEach((el) => {
|
||||
if (el !== eventEl) {
|
||||
el.classList.add(deemphasizedUpdateClassName);
|
||||
}
|
||||
});
|
||||
}, timeout);
|
||||
|
||||
this.currentlyUpdatedEvent = null;
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
toDateTime(value, timezone) {
|
||||
if (luxon.DateTime.isDateTime(value)) return value;
|
||||
@@ -502,7 +577,11 @@ export default {
|
||||
ApiKalender.updateKalenderEvent(obj.orig.kalender_id, updatedInfos),
|
||||
)
|
||||
.then(() => {
|
||||
if (onSuccess) onSuccess();
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
this.$refs.calendar.$refs.calendar.$refs.mode.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.currentlyUpdatedEvent = obj.orig;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -720,37 +799,41 @@ export default {
|
||||
);
|
||||
if (getAssignedResources.meta.status === "success") {
|
||||
return getAssignedResources.data
|
||||
.filter((unit) => !!unit)
|
||||
.map((unit) => {
|
||||
return {
|
||||
isNoteTextareaShown: unit.anmerkung && unit.anmerkung.trim() !== "",
|
||||
...unit,
|
||||
};
|
||||
}).filter((unit) => !!unit)
|
||||
.filter((unit) => !!unit)
|
||||
.map((unit) => {
|
||||
return {
|
||||
isNoteTextareaShown:
|
||||
unit.anmerkung && unit.anmerkung.trim() !== "",
|
||||
...unit,
|
||||
};
|
||||
})
|
||||
.filter((unit) => !!unit);
|
||||
} else {
|
||||
this.$fhcAlert.alertError(this.$p.t("ui", "failed_assigned_resources_fetch_error_message"));
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_assigned_resources_fetch_error_message"),
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
async fetchSchedulableResourcesByCalender(calendarID) {
|
||||
let getSchedulableResourcesByCalendar =
|
||||
await this.$api.call(
|
||||
ApiOperationalResourceToCalender.getSchedulableResourcesByCalendar(calendarID),
|
||||
);
|
||||
if (
|
||||
getSchedulableResourcesByCalendar.meta.status ===
|
||||
"success"
|
||||
) {
|
||||
let getSchedulableResourcesByCalendar = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.getSchedulableResourcesByCalendar(
|
||||
calendarID,
|
||||
),
|
||||
);
|
||||
if (getSchedulableResourcesByCalendar.meta.status === "success") {
|
||||
return getSchedulableResourcesByCalendar.data;
|
||||
} else {
|
||||
this.$fhcAlert.alertError(this.$p.t("ui", "failed_schedulable_resources_fetch_error_message"));
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_schedulable_resources_fetch_error_message"),
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
filterAvailableResources(event) {
|
||||
this.resourcesAssignmentModal.filteredAvailableResources
|
||||
this.resourcesAssignmentModal.filteredAvailableResources;
|
||||
const query = event.query.toLowerCase();
|
||||
if (!query) {
|
||||
return (this.resourcesAssignmentModal.filteredAvailableResources = [
|
||||
@@ -764,21 +847,23 @@ export default {
|
||||
|
||||
return (this.resourcesAssignmentModal.filteredAvailableResources =
|
||||
this.dropdownParsedAvailableResources
|
||||
.filter((unit) => {
|
||||
return !this.resourcesAssignmentModal.assignedResources.some(
|
||||
(assigned) => assigned.betriebsmittel_id === unit.value,
|
||||
);
|
||||
})
|
||||
.filter((unit) => {
|
||||
return unit.label.toLowerCase().includes(query);
|
||||
}));
|
||||
.filter((unit) => {
|
||||
return !this.resourcesAssignmentModal.assignedResources.some(
|
||||
(assigned) => assigned.betriebsmittel_id === unit.value,
|
||||
);
|
||||
})
|
||||
.filter((unit) => {
|
||||
return unit.label.toLowerCase().includes(query);
|
||||
}));
|
||||
},
|
||||
toggleAssignedResourceNoteInput(resource) {
|
||||
const index = this.resourcesAssignmentModal.assignedResources.findIndex(
|
||||
(assigned) => assigned.betriebsmittel_id === resource.betriebsmittel_id,
|
||||
);
|
||||
if (index !== -1) {
|
||||
this.resourcesAssignmentModal.assignedResources[index].isNoteTextareaShown =
|
||||
this.resourcesAssignmentModal.assignedResources[
|
||||
index
|
||||
].isNoteTextareaShown =
|
||||
!this.resourcesAssignmentModal.assignedResources[index]
|
||||
.isNoteTextareaShown;
|
||||
}
|
||||
@@ -788,35 +873,42 @@ export default {
|
||||
removeAssignedResource(resource) {
|
||||
this.resourcesAssignmentModal.assignedResources =
|
||||
this.resourcesAssignmentModal.assignedResources.filter(
|
||||
(assigned) => assigned.betriebsmittel_id !== resource.betriebsmittel_id,
|
||||
(assigned) =>
|
||||
assigned.betriebsmittel_id !== resource.betriebsmittel_id,
|
||||
);
|
||||
|
||||
this.resourcesAssignmentModal.areFormButtonsDisplayed = true;
|
||||
},
|
||||
async refreshResourcesAssignmentModalData(calenderItem) {
|
||||
this.resourcesAssignmentModal.availableResources =
|
||||
await this.fetchSchedulableResourcesByCalender(calenderItem.kalender_id);
|
||||
await this.fetchSchedulableResourcesByCalender(
|
||||
calenderItem.kalender_id,
|
||||
);
|
||||
this.resourcesAssignmentModal.filteredAvailableResources = [
|
||||
...this.dropdownParsedAvailableResources,
|
||||
];
|
||||
|
||||
this.resourcesAssignmentModal.assignedResources = await this.fetchAssignedResourcesByCalender(calenderItem.kalender_id);
|
||||
|
||||
this.resourcesAssignmentModal.assignedResources =
|
||||
await this.fetchAssignedResourcesByCalender(calenderItem.kalender_id);
|
||||
this.resourcesAssignmentModal.selectedAvailableResource = null;
|
||||
this.resourcesAssignmentModal.areFormButtonsDisplayed = false;
|
||||
},
|
||||
async saveAssignedResourcesToCalendarItem(calenderItem, assignedResources) {
|
||||
let getSchedulableResourcesByCalendar =
|
||||
await this.$api.call(
|
||||
ApiOperationalResourceToCalender.storeResourcesToCalendarRelationship(calenderItem.kalender_id, assignedResources)
|
||||
let getSchedulableResourcesByCalendar = await this.$api.call(
|
||||
ApiOperationalResourceToCalender.storeResourcesToCalendarRelationship(
|
||||
calenderItem.kalender_id,
|
||||
assignedResources,
|
||||
),
|
||||
);
|
||||
if (getSchedulableResourcesByCalendar.meta.status === "success") {
|
||||
this.$fhcAlert.alertSuccess(
|
||||
this.$p.t("ui", "assigned_resources_save_success_message"),
|
||||
);
|
||||
if (
|
||||
getSchedulableResourcesByCalendar.meta.status ===
|
||||
"success"
|
||||
) {
|
||||
this.$fhcAlert.alertSuccess(this.$p.t("ui", "assigned_resources_save_success_message"));
|
||||
await this.refreshResourcesAssignmentModalData(calenderItem);
|
||||
} else {
|
||||
this.$fhcAlert.alertError(this.$p.t("ui", "failed_assigned_resources_save_error_message"));
|
||||
this.$fhcAlert.alertError(
|
||||
this.$p.t("ui", "failed_assigned_resources_save_error_message"),
|
||||
);
|
||||
}
|
||||
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
// TODO(chris): load events that are longer than the interval without doubling it
|
||||
|
||||
export function useEventLoader(rangeInterval, getPromiseFunc) {
|
||||
export function useEventLoader(rangeInterval, getPromiseFunc, isLoaderVisible = true, isLoaderInitiallyVisible = true) {
|
||||
let hasFirstLoadOccurred = false;
|
||||
let loading_id = 0;
|
||||
|
||||
let tempEventsHolder = [];
|
||||
let rangeIntervalHolder = null;
|
||||
|
||||
const events = Vue.ref([]);
|
||||
const loadingEvents = Vue.ref([]);
|
||||
const allEvents = Vue.computed(() => events.value.concat(loadingEvents.value));
|
||||
@@ -100,13 +105,15 @@ export function useEventLoader(rangeInterval, getPromiseFunc) {
|
||||
|
||||
if (start.ts >= end.ts)
|
||||
return result;
|
||||
|
||||
loadingEvents.value.push({
|
||||
loading_id: loading_id++,
|
||||
type: "loading",
|
||||
isostart: start.toISODate() + 'T' + start.toISOTime(),
|
||||
isoend: end.toISODate() + 'T' + end.toISOTime()
|
||||
});
|
||||
|
||||
if (isLoaderVisible && (!hasFirstLoadOccurred && isLoaderInitiallyVisible)) {
|
||||
loadingEvents.value.push({
|
||||
loading_id: loading_id++,
|
||||
type: "loading",
|
||||
isostart: start.toISODate() + 'T' + start.toISOTime(),
|
||||
isoend: end.toISODate() + 'T' + end.toISOTime()
|
||||
});
|
||||
}
|
||||
|
||||
return mergePromiseArr(getPromiseFunc(start, end), result);
|
||||
};
|
||||
@@ -115,7 +122,14 @@ export function useEventLoader(rangeInterval, getPromiseFunc) {
|
||||
const range = Vue.toValue(rangeInterval);
|
||||
if (!(range instanceof luxon.Interval))
|
||||
return;
|
||||
|
||||
if (!rangeIntervalHolder || !range.equals(rangeIntervalHolder)) {
|
||||
hasFirstLoadOccurred = false;
|
||||
rangeIntervalHolder = range;
|
||||
}
|
||||
|
||||
const promises = markEventsLoaded(range.start, range.end);
|
||||
|
||||
Promise
|
||||
.allSettled(promises)
|
||||
.then(results => {
|
||||
@@ -127,18 +141,23 @@ export function useEventLoader(rangeInterval, getPromiseFunc) {
|
||||
if (res.value.meta.lv)
|
||||
lv.value = res.value.meta.lv;
|
||||
|
||||
events.value = events.value.concat(res.value.data);
|
||||
tempEventsHolder = tempEventsHolder.concat(res.value.data);
|
||||
loadingEvents.value = [];
|
||||
}
|
||||
})
|
||||
|
||||
events.value = tempEventsHolder;
|
||||
|
||||
});
|
||||
|
||||
hasFirstLoadOccurred = true;
|
||||
};
|
||||
|
||||
Vue.watchEffect(reload);
|
||||
|
||||
const reset = () => {
|
||||
loading_id = 0;
|
||||
events.value = [];
|
||||
tempEventsHolder = [];
|
||||
loadingEvents.value = [];
|
||||
eventsLoaded.splice(0, eventsLoaded.length);
|
||||
reload();
|
||||
|
||||
Reference in New Issue
Block a user