mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-20 18:59:33 +00:00
WIP
This commit is contained in:
@@ -109,3 +109,24 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.fhc-calendar-mode-range .fhc-calendar-base-grid-line-event {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fhc-calendar-mode-range .fhc-resize-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: auto;
|
||||
width: 15px;
|
||||
margin: 0;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.fhc-calendar-mode-range .fhc-resize-bar--left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.fhc-calendar-mode-range .fhc-resize-bar--right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@@ -479,7 +479,7 @@ export default {
|
||||
ctrlKey: !!(evt?.ctrlKey || evt?.metaKey)
|
||||
});
|
||||
},
|
||||
handleResizeStart({ edge, evt, el, event })
|
||||
handleResizeStart({ edge, evt, horizontal, el, event })
|
||||
{
|
||||
const gridEl = this.$refs.body;
|
||||
|
||||
@@ -490,6 +490,7 @@ export default {
|
||||
el,
|
||||
gridEl,
|
||||
event,
|
||||
horizontal,
|
||||
timeGrid: this.timeGrid,
|
||||
onEnd: ({ event, newStart, newEnd }) => {
|
||||
const orig = event?.orig;
|
||||
@@ -650,4 +651,4 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ export default {
|
||||
this.$emit('resize-start', {
|
||||
edge,
|
||||
evt,
|
||||
horizontal: this.mode === 'range',
|
||||
el: this.$refs.eventEl,
|
||||
event: this.event
|
||||
});
|
||||
@@ -153,7 +154,7 @@ export default {
|
||||
data-cy="calendar-event"
|
||||
>
|
||||
<div
|
||||
v-if="resizable"
|
||||
v-if="resizable && mode !== 'range'"
|
||||
class="fhc-resize-bar fhc-resize-bar--top"
|
||||
@pointerdown.prevent.stop="onResizeStart('start', $event)"
|
||||
@click.stop
|
||||
@@ -164,15 +165,30 @@ export default {
|
||||
{{ event.orig }}
|
||||
</slot>
|
||||
<div
|
||||
v-if="resizable"
|
||||
v-if="resizable && mode !== 'range'"
|
||||
class="fhc-resize-bar fhc-resize-bar--bottom"
|
||||
@pointerdown.prevent.stop="onResizeStart('end', $event)"
|
||||
@click.stop
|
||||
>
|
||||
<i class="fa-solid fa-grip-lines text-muted"></i>
|
||||
</div>
|
||||
<div
|
||||
v-if="resizable && mode === 'range'"
|
||||
class="fhc-resize-bar fhc-resize-bar--left"
|
||||
@pointerdown.prevent.stop="onResizeStart('start', $event)"
|
||||
@click.stop
|
||||
>
|
||||
<i class="fa-solid fa-grip-lines-vertical text-muted"></i>
|
||||
</div>
|
||||
<div
|
||||
v-if="resizable && mode === 'range'"
|
||||
class="fhc-resize-bar fhc-resize-bar--right"
|
||||
@pointerdown.prevent.stop="onResizeStart('end', $event)"
|
||||
@click.stop
|
||||
>
|
||||
<i class="fa-solid fa-grip-lines-vertical text-muted"></i>
|
||||
</div>
|
||||
|
||||
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-if="contextMenu.show"
|
||||
@@ -196,4 +212,4 @@ export default {
|
||||
</teleport>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,22 @@ import { useEventLoader } from '../../composables/EventLoader.js';
|
||||
import ModeWeek from './Mode/Week.js';
|
||||
import ModeMonth from './Mode/Month.js';
|
||||
import ModeTable from './Mode/Table.js';
|
||||
import ModeRange from './Mode/Range.js';
|
||||
import ApiKalender from '../../api/factory/tempus/kalender.js';
|
||||
import draggable from '../../directives/draggable.js';
|
||||
|
||||
import ApiStudiensemester from '../../api/factory/studiensemester.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarTempus",
|
||||
components: {
|
||||
FhcCalendar
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
rangeLength: Vue.computed(() => this.rangeLength),
|
||||
rangeViewPresets: Vue.computed(() => this.semesterRangePresets)
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
renderers: {from: 'renderers'},
|
||||
canToggleGrid: {
|
||||
@@ -25,7 +32,14 @@ export default {
|
||||
default: {
|
||||
visible_status: 'all'
|
||||
}
|
||||
}
|
||||
},
|
||||
rangeLength: {
|
||||
default: 1,
|
||||
},
|
||||
shouldIncludeRangeMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
draggable,
|
||||
@@ -79,13 +93,13 @@ export default {
|
||||
"event-unhover",
|
||||
"open-reservierung"
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
modes: {
|
||||
week: Vue.markRaw(ModeWeek),
|
||||
month: Vue.markRaw(ModeMonth),
|
||||
tableList: Vue.markRaw(ModeTable),
|
||||
range: Vue.markRaw(ModeRange),
|
||||
},
|
||||
modeOptions: {
|
||||
day: {
|
||||
@@ -100,6 +114,10 @@ export default {
|
||||
teachingunits: null,
|
||||
hoursplan: null,
|
||||
showRaster: true,
|
||||
semesterRangePresets: {
|
||||
label: null,
|
||||
presets: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -114,7 +132,9 @@ export default {
|
||||
end: now.startOf('day')
|
||||
}];
|
||||
}
|
||||
else
|
||||
else if (this.mode == 'Range') {
|
||||
return [];
|
||||
} else
|
||||
{
|
||||
past = [{
|
||||
class: 'background-past',
|
||||
@@ -183,6 +203,24 @@ export default {
|
||||
navigateNext() {
|
||||
this.$refs.calendar.clickNext();
|
||||
},
|
||||
async fetchSemesters() {
|
||||
const semestersResponse = await this.$api.call(ApiStudiensemester.getAll());
|
||||
if (semestersResponse.meta.status === "success") {
|
||||
this.semesterRangePresets = {
|
||||
label: "View specific semester",
|
||||
presets: semestersResponse.data.map((semester) => {
|
||||
let startDate = luxon.DateTime.fromISO(semester.start);
|
||||
let endDate = luxon.DateTime.fromISO(semester.ende);
|
||||
return {
|
||||
startDate,
|
||||
endDate,
|
||||
name: semester.studiensemester_kurzbz,
|
||||
description: semester.bezeichnung,
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
setup(props, context) {
|
||||
const rangeInterval = Vue.ref(null);
|
||||
@@ -220,6 +258,8 @@ export default {
|
||||
end: res.data.end
|
||||
};
|
||||
});
|
||||
|
||||
this.fetchSemesters();
|
||||
},
|
||||
template: /* html */`
|
||||
<fhc-calendar
|
||||
@@ -238,7 +278,7 @@ export default {
|
||||
show-btns
|
||||
:draggable-events="true"
|
||||
:resizable-events="true"
|
||||
:on-drop="currentMode === 'week' ? ondrop : null"
|
||||
:on-drop="['week', 'range'].includes(currentMode) ? ondrop : null"
|
||||
:on-resize="onresize"
|
||||
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
|
||||
@update:mode="(newMode, newDate) => { currentMode = newMode; $emit('update:mode', newMode, newDate) }"
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
export default {
|
||||
inject: {
|
||||
mode: {
|
||||
from: 'mode',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
@@ -148,19 +154,6 @@ export default {
|
||||
class="position-relative"
|
||||
@wheel.stop
|
||||
>
|
||||
<div class="position-absolute top-0 start-0 m-1 d-flex gap-1" >
|
||||
<i :class="statusIcon"></i>
|
||||
<i
|
||||
v-tooltip="resourcesTooltip"
|
||||
v-if="event.has_assigned_resources"
|
||||
class="fa-solid fa-table-list text-muted"
|
||||
></i>
|
||||
<i
|
||||
v-tooltip="tagsTooltip"
|
||||
v-if="tags?.length"
|
||||
class="fa-solid fa-tags"
|
||||
></i>
|
||||
</div>
|
||||
<div class="position-absolute bottom-0 start-0 m-1">
|
||||
{{event.verplante_stunden}}
|
||||
</div>
|
||||
@@ -172,7 +165,28 @@ export default {
|
||||
<span>{{ end }}</span>
|
||||
</div>
|
||||
<div class="event-text" v-tooltip="tooltipString">
|
||||
<span class="event-topic">{{ topicString }}</span>
|
||||
<div
|
||||
:class="{ 'd-flex align-items-center gap-1': mode === 'range' }"
|
||||
:style="mode === 'range' ? null : { display: 'contents' }"
|
||||
>
|
||||
<span class="event-topic">{{ topicString }}</span>
|
||||
<div
|
||||
class="d-flex gap-1"
|
||||
:class="{ 'position-absolute top-0 start-0 m-1': mode !== 'range' }"
|
||||
>
|
||||
<i :class="statusIcon"></i>
|
||||
<i
|
||||
v-if="event.has_assigned_resources"
|
||||
v-tooltip="resourcesTooltip"
|
||||
class="fa-solid fa-table-list text-muted"
|
||||
></i>
|
||||
<i
|
||||
v-if="tags?.length"
|
||||
v-tooltip="tagsTooltip"
|
||||
class="fa-solid fa-tags"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="event-place"
|
||||
data-cy="calendar-event-room"
|
||||
|
||||
@@ -59,7 +59,9 @@ export function useResizeGhost() {
|
||||
|
||||
return {
|
||||
startTop: topInGrid,
|
||||
startHeight: eventRect.height
|
||||
startHeight: eventRect.height,
|
||||
startLeft: leftInGrid,
|
||||
startWidth: eventRect.width
|
||||
};
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ export function useResizeGhost() {
|
||||
labelEl.textContent = text;
|
||||
}
|
||||
|
||||
function updatePosition(top, height)
|
||||
function updatePosition(top, height, width, left)
|
||||
{
|
||||
if (!ghostEl)
|
||||
return;
|
||||
@@ -77,15 +79,21 @@ export function useResizeGhost() {
|
||||
ghostEl.style.top = `${top}px`;
|
||||
if (height !== null)
|
||||
ghostEl.style.height = `${height}px`;
|
||||
if (width !== null)
|
||||
ghostEl.style.width = `${width}px`;
|
||||
if (left !== null)
|
||||
ghostEl.style.left = `${left}px`;
|
||||
}
|
||||
|
||||
function getPosition()
|
||||
{
|
||||
if (!ghostEl)
|
||||
return { top: 0, height: 0 };
|
||||
return { top: 0, height: 0, left: 0, width: 0 };
|
||||
return {
|
||||
top: parseFloat(ghostEl.style.top),
|
||||
height: parseFloat(ghostEl.style.height)
|
||||
height: parseFloat(ghostEl.style.height),
|
||||
left: parseFloat(ghostEl.style.left),
|
||||
width: parseFloat(ghostEl.style.width)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,4 +107,4 @@ export function useResizeGhost() {
|
||||
}
|
||||
|
||||
return { create, updateLabel, updatePosition, getPosition, remove };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,20 +41,30 @@ function getSnapTimes(timeGrid, dayISO, zoneName)
|
||||
|
||||
function calculateNewTimes(activeResize, ghostPosition)
|
||||
{
|
||||
const { edge, event, timeGrid, startTop, startHeight } = activeResize;
|
||||
const { edge, event, timeGrid, horizontal, startTop, startHeight, startLeft, startWidth } = activeResize;
|
||||
const { start, end } = event;
|
||||
|
||||
const durationMinutes = end.diff(start, 'minutes').minutes;
|
||||
if (!durationMinutes || durationMinutes <= 0)
|
||||
return null;
|
||||
|
||||
const pxPerMinute = startHeight / durationMinutes;
|
||||
const startSize = horizontal ? startWidth : startHeight;
|
||||
if (!Number.isFinite(startSize) || startSize <= 0)
|
||||
return null;
|
||||
|
||||
const pxPerMinute = startSize / durationMinutes;
|
||||
|
||||
let draggedPx = 0;
|
||||
if (edge === 'end')
|
||||
draggedPx = ghostPosition.height - startHeight;
|
||||
draggedPx = horizontal
|
||||
? ghostPosition.width - startWidth
|
||||
: ghostPosition.height - startHeight;
|
||||
if (edge === 'start')
|
||||
draggedPx = ghostPosition.top - startTop;
|
||||
draggedPx = horizontal
|
||||
? ghostPosition.left - startLeft
|
||||
: ghostPosition.top - startTop;
|
||||
if (!Number.isFinite(draggedPx))
|
||||
return null;
|
||||
|
||||
const draggedMinutes = snapToGrid(draggedPx / pxPerMinute, edge);
|
||||
|
||||
@@ -97,6 +107,12 @@ export function useResizeHandler() {
|
||||
return (evt.clientY - gridRect.top) + activeResize.gridEl.scrollTop;
|
||||
}
|
||||
|
||||
function getPointerXInGrid(evt)
|
||||
{
|
||||
const gridRect = activeResize.gridEl.getBoundingClientRect();
|
||||
return (evt.clientX - gridRect.left) + activeResize.gridEl.scrollLeft;
|
||||
}
|
||||
|
||||
function updateGhostLabel()
|
||||
{
|
||||
const result = calculateNewTimes(activeResize, ghost.getPosition());
|
||||
@@ -111,36 +127,59 @@ export function useResizeHandler() {
|
||||
return;
|
||||
evt.preventDefault();
|
||||
|
||||
const maxBottom = activeResize.gridEl.scrollHeight;
|
||||
const pointerY = getPointerYInGrid(evt);
|
||||
const draggedPx = pointerY - activeResize.dragStartY;
|
||||
const maxEnd = activeResize.horizontal
|
||||
? activeResize.gridEl.scrollWidth
|
||||
: activeResize.gridEl.scrollHeight;
|
||||
const pointer = activeResize.horizontal
|
||||
? getPointerXInGrid(evt)
|
||||
: getPointerYInGrid(evt);
|
||||
const draggedPx = pointer - (activeResize.horizontal
|
||||
? activeResize.dragStartX
|
||||
: activeResize.dragStartY);
|
||||
|
||||
if (activeResize.edge === 'end')
|
||||
{
|
||||
let newHeight = Math.max(MIN_HEIGHT_PX, activeResize.startHeight + draggedPx);
|
||||
if (activeResize.startTop + newHeight > maxBottom)
|
||||
newHeight = maxBottom - activeResize.startTop;
|
||||
const minSize = MIN_HEIGHT_PX;
|
||||
const newSize = Math.max(minSize, (activeResize.horizontal
|
||||
? activeResize.startWidth
|
||||
: activeResize.startHeight) + draggedPx);
|
||||
const startPosition = activeResize.horizontal
|
||||
? activeResize.startLeft
|
||||
: activeResize.startTop;
|
||||
const constrainedSize = Math.min(newSize, maxEnd - startPosition);
|
||||
|
||||
ghost.updatePosition(null, newHeight);
|
||||
if (activeResize.horizontal)
|
||||
ghost.updatePosition(null, null, constrainedSize);
|
||||
else
|
||||
ghost.updatePosition(null, constrainedSize);
|
||||
}
|
||||
else if (activeResize.edge === 'start')
|
||||
{
|
||||
let newTop = activeResize.startTop + draggedPx;
|
||||
let newHeight = activeResize.startHeight - draggedPx;
|
||||
const startPosition = activeResize.horizontal
|
||||
? activeResize.startLeft
|
||||
: activeResize.startTop;
|
||||
const startSize = activeResize.horizontal
|
||||
? activeResize.startWidth
|
||||
: activeResize.startHeight;
|
||||
let newPosition = startPosition + draggedPx;
|
||||
let newSize = startSize - draggedPx;
|
||||
|
||||
if (newTop < 0)
|
||||
if (newPosition < 0)
|
||||
{
|
||||
newHeight -= (0 - newTop);
|
||||
newTop = 0;
|
||||
newSize -= (0 - newPosition);
|
||||
newPosition = 0;
|
||||
}
|
||||
|
||||
if (newHeight < MIN_HEIGHT_PX)
|
||||
if (newSize < MIN_HEIGHT_PX)
|
||||
{
|
||||
newTop = (activeResize.startTop + activeResize.startHeight) - MIN_HEIGHT_PX;
|
||||
newHeight = MIN_HEIGHT_PX;
|
||||
newPosition = (startPosition + startSize) - MIN_HEIGHT_PX;
|
||||
newSize = MIN_HEIGHT_PX;
|
||||
}
|
||||
|
||||
ghost.updatePosition(newTop, newHeight);
|
||||
if (activeResize.horizontal)
|
||||
ghost.updatePosition(null, null, newSize, newPosition);
|
||||
else
|
||||
ghost.updatePosition(newPosition, newSize);
|
||||
}
|
||||
|
||||
updateGhostLabel();
|
||||
@@ -176,9 +215,9 @@ export function useResizeHandler() {
|
||||
activeResize = null;
|
||||
}
|
||||
|
||||
function startResize(edge, evt, { el, gridEl, event, timeGrid, onEnd })
|
||||
function startResize(edge, evt, { el, gridEl, event, horizontal = false, timeGrid, onEnd })
|
||||
{
|
||||
const { startTop, startHeight } = ghost.create(gridEl, el, edge);
|
||||
const { startTop, startHeight, startLeft, startWidth } = ghost.create(gridEl, el, edge);
|
||||
|
||||
activeResize = {
|
||||
edge,
|
||||
@@ -186,11 +225,15 @@ export function useResizeHandler() {
|
||||
eventEl: el,
|
||||
gridEl,
|
||||
event,
|
||||
horizontal,
|
||||
timeGrid,
|
||||
onEnd,
|
||||
dragStartY: (evt.clientY - gridEl.getBoundingClientRect().top) + gridEl.scrollTop,
|
||||
dragStartX: (evt.clientX - gridEl.getBoundingClientRect().left) + gridEl.scrollLeft,
|
||||
startTop,
|
||||
startHeight,
|
||||
startLeft,
|
||||
startWidth,
|
||||
originalOpacity: el.style.opacity,
|
||||
};
|
||||
|
||||
@@ -214,4 +257,4 @@ export function useResizeHandler() {
|
||||
}
|
||||
|
||||
return { startResize, cleanup };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user