mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-12 15:04:04 +00:00
WIP
This commit is contained in:
@@ -44,6 +44,7 @@ class KalenderLib
|
||||
FROM lehre.tbl_kalender
|
||||
WHERE von >= timestamp %s
|
||||
AND bis < timestamp %s
|
||||
limit 4000
|
||||
)',
|
||||
$this->_ci->KalenderModel->db->escape(date('Y-m-d H:i:s', strtotime($start_date))),
|
||||
$this->_ci->KalenderModel->db->escape(date('Y-m-d H:i:s', strtotime($end_date)))
|
||||
|
||||
@@ -22,6 +22,7 @@ export default {
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
calendarVisibleDatesChanged: dates => this.$emit('update:visible-dates', dates),
|
||||
events: Vue.computed(() => this.convertedEvents),
|
||||
backgrounds: Vue.computed(() => this.convertedBackgrounds),
|
||||
dropAllowed: Vue.computed(() => !!this.onDrop),
|
||||
@@ -138,6 +139,7 @@ export default {
|
||||
"update:date",
|
||||
"update:mode",
|
||||
"update:range",
|
||||
"update:visible-dates",
|
||||
"update:date-range",
|
||||
"drop"
|
||||
],
|
||||
@@ -354,4 +356,4 @@ export default {
|
||||
</bs-modal>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,16 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
visibleStart() {
|
||||
return this.date.startOf('day');
|
||||
},
|
||||
visibleEnd() {
|
||||
const end = this.end.startOf('day');
|
||||
|
||||
return end > this.visibleStart
|
||||
? end
|
||||
: this.visibleStart.plus({ days: 1 });
|
||||
},
|
||||
formattedEvents() {
|
||||
let formattedEvents = this.events.map((event) => {
|
||||
event.rows = [1, -1];
|
||||
@@ -133,6 +143,8 @@ export default {
|
||||
template: /* html */ `
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line"
|
||||
:data-visible-start="visibleStart.toISO()"
|
||||
:data-visible-end="visibleEnd.toISO()"
|
||||
:class="{
|
||||
'fhc-calendar-base-grid-line-sunday': $props.date.weekday === 7
|
||||
}"
|
||||
|
||||
@@ -4,6 +4,10 @@ export default {
|
||||
time: {
|
||||
from: "sliderTime",
|
||||
default: ".3s"
|
||||
},
|
||||
onVisibleDatesChanged: {
|
||||
from: "calendarVisibleDatesChanged",
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
@@ -15,7 +19,14 @@ export default {
|
||||
extrasAfter: 0,
|
||||
extrasBefore: 0,
|
||||
running: false,
|
||||
promiseResolve: null
|
||||
promiseResolve: null,
|
||||
intersectionObserver: null,
|
||||
mutationObserver: null,
|
||||
gridLineResizeObserver: null,
|
||||
visibleGridLines: Vue.markRaw(new Set()),
|
||||
gridLineHeights: Vue.markRaw(new Map()),
|
||||
visibilityUpdateScheduled: false,
|
||||
lastVisibleDatesKey: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -61,6 +72,165 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startVisibilityTracking() {
|
||||
if (!this.onVisibleDatesChanged || typeof IntersectionObserver === 'undefined')
|
||||
return;
|
||||
|
||||
if (
|
||||
this.$el.closest('.fhc-calendar-mode-range')
|
||||
&& typeof ResizeObserver !== 'undefined'
|
||||
)
|
||||
{
|
||||
this.gridLineResizeObserver = Vue.markRaw(new ResizeObserver(entries => {
|
||||
entries.forEach(entry => this.preserveGridLineHeight(entry.target));
|
||||
}));
|
||||
}
|
||||
|
||||
this.intersectionObserver = Vue.markRaw(new IntersectionObserver(entries => {
|
||||
let changed = false;
|
||||
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && entry.intersectionRatio > 0) {
|
||||
if (!this.visibleGridLines.has(entry.target)) {
|
||||
this.visibleGridLines.add(entry.target);
|
||||
changed = true;
|
||||
}
|
||||
} else if (this.visibleGridLines.delete(entry.target)) {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (changed)
|
||||
this.scheduleVisibleDatesUpdate();
|
||||
}, {
|
||||
root: this.$el,
|
||||
threshold: 0
|
||||
}));
|
||||
|
||||
this.mutationObserver = Vue.markRaw(new MutationObserver(mutations => {
|
||||
let changed = false;
|
||||
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.type === 'attributes') {
|
||||
this.applyPreservedGridLineHeight(mutation.target);
|
||||
this.gridLineResizeObserver?.observe(mutation.target);
|
||||
if (this.visibleGridLines.has(mutation.target))
|
||||
changed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
mutation.addedNodes.forEach(node => this.observeGridLines(node));
|
||||
mutation.removedNodes.forEach(node => {
|
||||
this.getGridLines(node).forEach(line => {
|
||||
this.intersectionObserver.unobserve(line);
|
||||
this.gridLineResizeObserver?.unobserve(line);
|
||||
if (this.visibleGridLines.delete(line))
|
||||
changed = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (changed)
|
||||
this.scheduleVisibleDatesUpdate();
|
||||
}));
|
||||
|
||||
this.observeGridLines(this.$el);
|
||||
this.mutationObserver.observe(this.$el, {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['data-visible-start', 'data-visible-end']
|
||||
});
|
||||
},
|
||||
getGridLines(node) {
|
||||
if (!(node instanceof Element))
|
||||
return [];
|
||||
|
||||
const lines = node.matches('.fhc-calendar-base-grid-line') ? [node] : [];
|
||||
return lines.concat([...node.querySelectorAll('.fhc-calendar-base-grid-line')]);
|
||||
},
|
||||
observeGridLines(node) {
|
||||
this.getGridLines(node).forEach(line => {
|
||||
this.applyPreservedGridLineHeight(line);
|
||||
this.intersectionObserver.observe(line);
|
||||
this.gridLineResizeObserver?.observe(line);
|
||||
});
|
||||
},
|
||||
getGridLineKey(line) {
|
||||
const start = line.dataset.visibleStart;
|
||||
const end = line.dataset.visibleEnd;
|
||||
|
||||
return start && end ? start + '/' + end : null;
|
||||
},
|
||||
applyPreservedGridLineHeight(line) {
|
||||
if (!this.gridLineResizeObserver)
|
||||
return;
|
||||
|
||||
const key = this.getGridLineKey(line);
|
||||
const height = key ? this.gridLineHeights.get(key) : null;
|
||||
line.style.minHeight = height ? height + 'px' : '';
|
||||
},
|
||||
preserveGridLineHeight(line) {
|
||||
const key = this.getGridLineKey(line);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
const height = Math.ceil(line.getBoundingClientRect().height);
|
||||
const preservedHeight = this.gridLineHeights.get(key) || 0;
|
||||
if (height > preservedHeight)
|
||||
this.gridLineHeights.set(key, height);
|
||||
|
||||
const minHeight = Math.max(height, preservedHeight);
|
||||
if (minHeight && line.style.minHeight !== minHeight + 'px')
|
||||
line.style.minHeight = minHeight + 'px';
|
||||
},
|
||||
scheduleVisibleDatesUpdate() {
|
||||
if (this.visibilityUpdateScheduled)
|
||||
return;
|
||||
|
||||
this.visibilityUpdateScheduled = true;
|
||||
this.$nextTick(() => {
|
||||
this.visibilityUpdateScheduled = false;
|
||||
this.emitVisibleDates();
|
||||
});
|
||||
},
|
||||
emitVisibleDates() {
|
||||
const datesByKey = new Map();
|
||||
|
||||
this.visibleGridLines.forEach(line => {
|
||||
if (!line.isConnected)
|
||||
return;
|
||||
|
||||
const start = line.dataset.visibleStart;
|
||||
const end = line.dataset.visibleEnd;
|
||||
if (start && end)
|
||||
datesByKey.set(start + '/' + end, { start, end });
|
||||
});
|
||||
|
||||
const dates = [...datesByKey.values()]
|
||||
.sort((a, b) => a.start.localeCompare(b.start));
|
||||
if (!dates.length)
|
||||
return;
|
||||
|
||||
const key = dates.map(date => date.start + '/' + date.end).join('|');
|
||||
|
||||
if (key === this.lastVisibleDatesKey)
|
||||
return;
|
||||
|
||||
this.lastVisibleDatesKey = key;
|
||||
this.onVisibleDatesChanged(dates);
|
||||
},
|
||||
stopVisibilityTracking() {
|
||||
this.intersectionObserver?.disconnect();
|
||||
this.mutationObserver?.disconnect();
|
||||
this.gridLineResizeObserver?.disconnect();
|
||||
this.intersectionObserver = null;
|
||||
this.mutationObserver = null;
|
||||
this.gridLineResizeObserver = null;
|
||||
this.visibleGridLines.clear();
|
||||
this.gridLineHeights.clear();
|
||||
this.onVisibleDatesChanged?.(null);
|
||||
},
|
||||
prevPage() {
|
||||
return this.slidePages(-1);
|
||||
},
|
||||
@@ -93,6 +263,12 @@ export default {
|
||||
this.extrasAfter = this.extrasBefore = 0;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.startVisibilityTracking();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.stopVisibilityTracking();
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-slider h-100"
|
||||
|
||||
@@ -103,6 +103,7 @@ export default {
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
visibleDates: null,
|
||||
modes: {
|
||||
week: Vue.markRaw(ModeWeek),
|
||||
month: Vue.markRaw(ModeMonth),
|
||||
@@ -162,6 +163,34 @@ export default {
|
||||
{
|
||||
let list = this.events;
|
||||
|
||||
// Start with the first week and then keep events overlapping a grid line
|
||||
// that is visible inside the slider viewport, including a one-day buffer
|
||||
// before and after the visible dates.
|
||||
console.log('visibleDates', this.visibleDates);
|
||||
if (this.currentMode === 'range' && Array.isArray(this.visibleDates))
|
||||
{
|
||||
const visibleIntervals = this.visibleDates
|
||||
.map(date => luxon.Interval.fromDateTimes(
|
||||
luxon.DateTime.fromISO(date.start).setZone(this.timezone).minus({ days: 1 }),
|
||||
luxon.DateTime.fromISO(date.end).setZone(this.timezone).plus({ days: 1 })
|
||||
))
|
||||
.filter(interval => interval.isValid);
|
||||
|
||||
list = list.filter(event => {
|
||||
const eventStart = luxon.DateTime.fromISO(event.isostart).setZone(this.timezone);
|
||||
const eventEnd = luxon.DateTime.fromISO(event.isoend).setZone(this.timezone);
|
||||
|
||||
// Do not hide an event with an invalid date; it is safer to keep it
|
||||
// available for the renderer than to silently discard it.
|
||||
if (!eventStart.isValid || !eventEnd.isValid)
|
||||
return true;
|
||||
|
||||
return visibleIntervals.some(interval =>
|
||||
eventStart < interval.end && eventEnd > interval.start
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(this.visibleLecturers))
|
||||
{
|
||||
const visibleLectures = new Set(this.visibleLecturers);
|
||||
@@ -190,6 +219,18 @@ export default {
|
||||
return;
|
||||
|
||||
this.rangeInterval = rangeInterval;
|
||||
if (
|
||||
this.visibleDates === null
|
||||
&& rangeInterval instanceof luxon.Interval
|
||||
&& rangeInterval.isValid
|
||||
)
|
||||
{
|
||||
const start = rangeInterval.start.startOf('day');
|
||||
this.visibleDates = [{
|
||||
start: start.toISO(),
|
||||
end: start.plus({ days: 7 }).toISO()
|
||||
}];
|
||||
}
|
||||
this.$emit('update:range', rangeInterval);
|
||||
},
|
||||
handleDateRange({ start, end }) {
|
||||
@@ -299,6 +340,7 @@ export default {
|
||||
@update:date="handleDateUpdate"
|
||||
@update:mode="(newMode, newDate) => { currentMode = newMode; $emit('update:mode', newMode, newDate) }"
|
||||
@update:range="updateRange"
|
||||
@update:visible-dates="visibleDates = $event"
|
||||
@update:date-range="handleDateRange"
|
||||
>
|
||||
<template v-slot="{ event, mode }">
|
||||
|
||||
Reference in New Issue
Block a user