From b58142fe5fa1ba1a99d850baa1465df6fa6e0d3b Mon Sep 17 00:00:00 2001 From: Moritz Strohm Date: Fri, 16 Jan 2026 09:36:16 +0000 Subject: updated Fullcalendar to version 6, closes #4887 Closes #4887 Merge request studip/studip!4438 --- app/controllers/admin/courseplanning.php | 89 +- app/controllers/admin/overlapping.php | 16 +- app/controllers/calendar/calendar.php | 127 +-- app/controllers/calendar/date.php | 48 +- app/controllers/calendar/schedule.php | 85 +- app/controllers/institute/schedule.php | 14 +- app/controllers/resources/ajax.php | 6 +- app/controllers/resources/booking.php | 20 +- app/controllers/resources/print.php | 13 +- app/controllers/resources/room_planning.php | 12 +- app/controllers/room_management/planning.php | 12 +- app/views/admin/courseplanning/index.php | 61 +- app/views/admin/courseplanning/pick_color.php | 16 +- app/views/admin/courseplanning/weekday.php | 8 +- app/views/calendar/schedule/index.php | 2 +- app/views/resources/print/clipboard_rooms.php | 10 +- .../resources/print/individual_booking_plan.php | 29 +- app/views/resources/resource/booking_plan.php | 8 +- .../room_planning/_sidebar_date_selection.php | 12 - app/views/resources/room_planning/booking_plan.php | 42 +- .../resources/room_planning/semester_plan.php | 23 +- app/views/resources/room_request/planning.php | 18 +- app/views/room_management/planning/index.php | 18 +- .../room_management/planning/semester_plan.php | 15 +- lib/classes/Fullcalendar.php | 88 +- lib/classes/InstituteCalendarHelper.php | 45 +- lib/classes/calendar/EventData.php | 2 +- lib/classes/calendar/Helper.php | 60 +- lib/models/calendar/CalendarDateAssignment.php | 8 +- lib/models/calendar/ScheduleEntry.php | 5 +- package-lock.json | 145 ++- package.json | 15 +- .../assets/javascripts/bootstrap/fullcalendar.js | 48 - .../assets/javascripts/bootstrap/resources.js | 195 ---- resources/assets/javascripts/chunk-loader.js | 7 - .../assets/javascripts/chunks/fullcalendar.js | 11 - resources/assets/javascripts/entry-base.js | 1 - resources/assets/javascripts/init.js | 4 +- resources/assets/javascripts/lib/action.ts | 26 + resources/assets/javascripts/lib/calendar.ts | 226 +++++ resources/assets/javascripts/lib/datetime.js | 85 -- resources/assets/javascripts/lib/datetime.ts | 141 +++ resources/assets/javascripts/lib/fullcalendar.js | 977 --------------------- resources/assets/javascripts/lib/holiday.ts | 254 ++++++ resources/assets/stylesheets/fullcalendar.scss | 306 ------- resources/assets/stylesheets/print.scss | 6 +- resources/assets/stylesheets/scss/calendar.scss | 47 +- .../stylesheets/scss/fullcalendar-print.scss | 165 ++-- .../assets/stylesheets/scss/fullcalendar.scss | 341 +++++++ resources/vue/apps/StudipCalendar.vue | 596 +++++++++++++ resources/vue/components/StudipDateTime.vue | 7 +- .../vue/components/form_inputs/DateListInput.vue | 7 +- .../resources_individual_booking_plan_sidebar.php | 20 +- templates/studip-fullcalendar.php | 36 +- 54 files changed, 2392 insertions(+), 2186 deletions(-) delete mode 100644 app/views/resources/room_planning/_sidebar_date_selection.php delete mode 100644 resources/assets/javascripts/bootstrap/fullcalendar.js delete mode 100644 resources/assets/javascripts/chunks/fullcalendar.js create mode 100644 resources/assets/javascripts/lib/action.ts create mode 100644 resources/assets/javascripts/lib/calendar.ts delete mode 100644 resources/assets/javascripts/lib/datetime.js create mode 100644 resources/assets/javascripts/lib/datetime.ts delete mode 100644 resources/assets/javascripts/lib/fullcalendar.js create mode 100644 resources/assets/javascripts/lib/holiday.ts delete mode 100644 resources/assets/stylesheets/fullcalendar.scss create mode 100644 resources/assets/stylesheets/scss/fullcalendar.scss create mode 100644 resources/vue/apps/StudipCalendar.vue diff --git a/app/controllers/admin/courseplanning.php b/app/controllers/admin/courseplanning.php index 132b35a..d86f8cd 100644 --- a/app/controllers/admin/courseplanning.php +++ b/app/controllers/admin/courseplanning.php @@ -67,9 +67,9 @@ class Admin_CourseplanningController extends AuthenticatedController $sidebar = Sidebar::get(); $actions = $sidebar->addWidget(new ActionsWidget()); $actions->addLink( - _('Veranstaltungs-Stundenplan PDF'), - 'javascript:STUDIP.Fullcalendar.downloadPDF();', - Icon::create('file-pdf') + _('Drucken'), + 'javascript:void(window.print());', + Icon::create('print') ); Sidebar::get()->getWidget('actions')->addLink( @@ -341,29 +341,23 @@ class Admin_CourseplanningController extends AuthenticatedController $this->non_conform_dates = $non_rasters; } - public function add_event_action() + public function add_event_action($course_id) { - $course_id = Request::option('course_id'); - $begin = Request::get('begin'); - $end = Request::get('end'); - $success = false; + $start = Request::getDateTime('start', DateTimeInterface::RFC3339_EXTENDED); + $end = Request::getDateTime('end', DateTimeInterface::RFC3339_EXTENDED); + $success = false; $cycle_start = null; - $cycle_end = null; - - if ($course_id && $this->semester) { - $begin_date = new DateTime($begin); - $end_date = new DateTime($end); - $begin_date->setTimezone(new DateTimeZone('UTC')); - $end_date->setTimezone(new DateTimeZone('UTC')); + $cycle_end = null; + $course = Course::find($course_id); - $course = Course::find($course_id); - - if (count($course->semesters) > 1) { // course over more than one semester + if ($course && $this->semester && $start && $end) { + if (count($course->semesters) > 1) { + //The course spans over more than one semester. $start_weeks = $course->start_semester->getStartWeeks($course->end_semester); - $sem_weeks = $this->semester->getStartWeeks(); + $sem_weeks = $this->semester->getStartWeeks(); $sem_weeks_start = explode(' Semesterwoche ', $sem_weeks[0]); - $sem_weeks_end = explode(' Semesterwoche ', end($sem_weeks)); + $sem_weeks_end = explode(' Semesterwoche ', end($sem_weeks)); foreach ($start_weeks as $week_num => $week_text) { if ($cycle_start && $cycle_end) break; @@ -378,14 +372,14 @@ class Admin_CourseplanningController extends AuthenticatedController $cycle = new SeminarCycleDate(); $cycle->seminar_id = $course_id; - $cycle->weekday = $begin_date->format('w'); + $cycle->weekday = $start->format('w'); $cycle->description = ''; $cycle->sws = floatVal(0.0); $cycle->cycle = 0; - $cycle->week_offset = $cycle_start?$cycle_start:0; - $cycle->end_offset = $cycle_end?$cycle_end:intVal(count($this->semester->getStartWeeks()) -1); - $cycle->start_time = $begin_date->format('H:i:s'); - $cycle->end_time = $end_date->format('H:i:s'); + $cycle->week_offset = $cycle_start ? $cycle_start : 0; + $cycle->end_offset = $cycle_end ? $cycle_end : intval(count($this->semester->getStartWeeks()) -1); + $cycle->start_time = $start->format('H:i:s'); + $cycle->end_time = $end->format('H:i:s'); $success = $cycle->store(); } @@ -402,24 +396,36 @@ class Admin_CourseplanningController extends AuthenticatedController } $this->render_nothing(); - return; } public function pick_color_action($metadate_id, $from_action, $weekday = null) { PageLayout::setTitle(_('Farbwähler')); + $this->available_colours = [ + 'yellow' => '#ffbd33', + 'orange' => '#f26e00', + 'red' => '#d60000', + 'violet' => '#b02e7c', + 'dark-violet' => '#682c8b', + 'green' => '#6ead10', + 'dark-green' => '#008512', + 'petrol' => '#0E817B', + 'brown' => '#a85d45' + ]; + $cdate = SeminarCycleDate::find($metadate_id); if ($cdate) { $course = Course::find($cdate->Seminar_id); $semtype = $course->getSemType(); - if (Request::submitted('save') && Request::submitted('event_color')) { - if (Request::get('event_color_semtype')) { + if (Request::submitted('save') && Request::submitted('event_colour')) { + $selected_colour_index = Request::get('event_colour'); + if (Request::get('event_colour_semtype')) { if (InstituteCalendarHelper::setSemtypeEventcolor( Course::find($cdate->seminar_id), $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT, - Request::get('event_color') + $this->available_colours[$selected_colour_index] ?? '', )) { PageLayout::postSuccess(sprintf( _('Die Farbe wurde allen VA des Typs %s zugewiesen.'), @@ -433,7 +439,7 @@ class Admin_CourseplanningController extends AuthenticatedController Course::find($cdate->seminar_id), $metadate_id, $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT, - Request::get('event_color') + $this->available_colours[$selected_colour_index] ?? '' )) { PageLayout::postSuccess(_('Die Farbe wurde der VA zugewiesen.')); } else { @@ -447,7 +453,7 @@ class Admin_CourseplanningController extends AuthenticatedController $course_colors = InstituteCalendarHelper::getCourseEventcolors($course); if (array_key_exists($metadate_id, $course_colors)) { - $this->color = $course_colors[$metadate_id][$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT]; + $this->color = $course_colors[$metadate_id][$GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT] ?? ''; } else { $this->color = '#28497c'; } @@ -459,31 +465,26 @@ class Admin_CourseplanningController extends AuthenticatedController $this->weekday = $weekday; } - public function move_event_action() + public function move_event_action($cycle_date_id) { - $metadate_id = Request::option('cycle_id'); - $begin = Request::get('begin'); - $end = Request::get('end'); + $start = Request::getDateTime('start', DateTimeInterface::RFC3339_EXTENDED); + $end = Request::getDateTime('end', DateTimeInterface::RFC3339_EXTENDED); $success = false; - $cdate = SeminarCycleDate::find($metadate_id); + $cdate = SeminarCycleDate::find($cycle_date_id); if ($cdate) { if (Request::submitted('resource_id')) { InstituteCalendarHelper::setCourseEventcolumn( Course::find($cdate->seminar_id), - $metadate_id, + $cycle_date_id, $GLOBALS['user']->cfg->MY_INSTITUTES_DEFAULT, Request::get('resource_id', '0') ); } - $begin_date = new DateTime($begin); - $end_date = new DateTime($end); - $begin_date->setTimezone(new DateTimeZone('UTC')); - $end_date->setTimezone(new DateTimeZone('UTC')); - $weekday = $begin_date->format('w'); - $cdate->start_time = $begin_date->format('H:i:s'); - $cdate->end_time = $end_date->format('H:i:s'); + $weekday = $start->format('w'); + $cdate->start_time = $start->format('H:i:s'); + $cdate->end_time = $end->format('H:i:s'); $cdate->weekday = $weekday; $success = $cdate->store(); } diff --git a/app/controllers/admin/overlapping.php b/app/controllers/admin/overlapping.php index b339e83..69a93fc 100644 --- a/app/controllers/admin/overlapping.php +++ b/app/controllers/admin/overlapping.php @@ -362,7 +362,7 @@ class Admin_OverlappingController extends AuthenticatedController $selection_id = $selection_id ?: $_SESSION['MVV_OVL_SELECTION_ID'] ?? null; - $this->fullcalendar = Studip\Fullcalendar::create( + $this->fullcalendar = \Studip\Fullcalendar::create( _('Kalender'), [ 'editable' => false, @@ -374,20 +374,20 @@ class Admin_OverlappingController extends AuthenticatedController 'defaultDate' => date('Y-m-d', $this->selected_semester->vorles_beginn), 'allDaySlot' => false, 'allDayText' => '', - 'header' => [ - 'left' => false, - 'center' => $this->selected_semester->name, - 'right' => false, + 'headerToolbar' => [ + 'start' => false, + 'center' => $this->selected_semester->name, + 'end' => false, ], 'weekNumbers' => false, 'views' => [ - 'timeGridWeek' => [ - 'columnHeaderFormat' => ['weekday' => 'short', 'omitCommas' => true], + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'short', 'omitCommas' => true], 'weekends' => true, 'slotDuration' => '00:30:00' ], ], - 'defaultView' => 'timeGridWeek', + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, 'timeGridEventMinHeight' => 20, 'eventSources' => [ [ diff --git a/app/controllers/calendar/calendar.php b/app/controllers/calendar/calendar.php index 6e79d3a..1f9a4a6 100644 --- a/app/controllers/calendar/calendar.php +++ b/app/controllers/calendar/calendar.php @@ -347,17 +347,17 @@ class Calendar_CalendarController extends AuthenticatedController //Map calendar settings to fullcalendar settings: - $default_view = 'timeGridWeek'; + $default_view = \Studip\Fullcalendar::VIEW_WEEK; if ($timeline_view) { - $default_view = 'resourceTimelineWeek'; + $default_view = \Studip\Fullcalendar::GROUP_WEEK; if ($calendar_settings['view'] === 'day') { - $default_view = 'resourceTimelineDay'; + $default_view = \Studip\Fullcalendar::GROUP_DAY; } } elseif (!empty($calendar_settings['view'])) { if ($calendar_settings['view'] === 'day') { - $default_view = 'timeGridDay'; + $default_view = \Studip\Fullcalendar::VIEW_DAY; } elseif ($calendar_settings['view'] === 'month') { - $default_view = 'dayGridMonth'; + $default_view = \Studip\Fullcalendar::VIEW_MONTH; } } @@ -373,59 +373,68 @@ class Calendar_CalendarController extends AuthenticatedController $data_url_params['timeline_view'] = '1'; } + $available_views = []; + if ($timeline_view) { + $available_views = [ + \Studip\Fullcalendar::GROUP_WEEK, + \Studip\Fullcalendar::GROUP_DAY + ]; + } else { + $available_views = [ + \Studip\Fullcalendar::VIEW_MONTH, + \Studip\Fullcalendar::VIEW_WEEK, + \Studip\Fullcalendar::VIEW_DAY + ]; + } + $this->fullcalendar = Studip\Fullcalendar::create( _('Kalender'), [ - 'editable' => $write_permissions, - 'selectable' => $write_permissions, - 'studip_urls' => $fullcalendar_studip_urls, - 'dialog_size' => 'auto', - 'minTime' => sprintf('%02u:00', $calendar_settings['start'] ?? 8), - 'maxTime' => sprintf('%02u:00', $calendar_settings['end'] ?? 20), - 'defaultDate' => $default_date->format('Y-m-d'), - 'allDaySlot' => true, - 'allDayText' => '', - 'header' => [ - 'left' => ( - $timeline_view - ? 'resourceTimelineWeek,resourceTimelineDay' - : 'dayGridYear,dayGridMonth,timeGridWeek,timeGridDay' - ), + 'editable' => $write_permissions, + 'selectable' => $write_permissions, + 'studip_urls' => $fullcalendar_studip_urls, + 'slotMinTime' => sprintf('%02u:00', $calendar_settings['start'] ?? 8), + 'slotMaxTime' => sprintf('%02u:00', $calendar_settings['end'] ?? 20), + 'initialDate' => $default_date->format('Y-m-d'), + 'allDaySlot' => true, + 'allDayText' => '', + 'headerToolbar' => [ + 'start' => implode(',', $available_views), 'center' => 'title', - 'right' => 'prev,today,next' + 'end' => 'prev,today,next' ], 'weekNumbers' => true, 'views' => [ - 'dayGridMonth' => [ + \Studip\Fullcalendar::VIEW_MONTH => [ 'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'], 'titleFormat' => ['year' => 'numeric', 'month' => 'long'], 'displayEventEnd' => true ], - 'timeGridWeek' => [ - 'columnHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], - 'weekends' => $calendar_settings['type_week'] === 'LONG', - 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], - 'slotDuration' => $slot_durations['week'] + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], + 'weekends' => $calendar_settings['type_week'] === 'LONG', + 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], + 'slotDuration' => $slot_durations['week'] ], - 'timeGridDay' => [ - 'columnHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], - 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], - 'slotDuration' => $slot_durations['day'] + \Studip\Fullcalendar::VIEW_DAY => [ + 'dayHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], + 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], + 'slotDuration' => $slot_durations['day'] ], - 'resourceTimelineWeek' => [ - 'columnHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], - 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], - 'weekends' => $calendar_settings['type_week'] === 'LONG', - 'slotDuration' => $slot_durations['week_group'] + \Studip\Fullcalendar::GROUP_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], + 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], + 'weekends' => $calendar_settings['type_week'] === 'LONG', + 'slotDuration' => $slot_durations['week_group'] ], - 'resourceTimelineDay' => [ - 'columnHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], - 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], - 'slotDuration' => $slot_durations['day_group'] + \Studip\Fullcalendar::GROUP_DAY => [ + 'dayHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], + 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], + 'slotDuration' => $slot_durations['day_group'] ] ], - 'defaultView' => $default_view, - 'timeGridEventMinHeight' => 20, + 'initialView' => $default_view, + 'eventMinHeight' => 20, 'eventSources' => [ [ 'url' => $this->url_for( @@ -436,12 +445,11 @@ class Calendar_CalendarController extends AuthenticatedController ), $data_url_params ), - 'method' => 'GET', - 'extraParams' => [] + 'method' => 'GET' ] ], - 'resources' => $calendar_resources, - 'resourceLabelText' => $calendar_group_title + 'resources' => $calendar_resources, + 'resourceAreaHeaderContent' => $calendar_group_title ] ); } @@ -504,6 +512,13 @@ class Calendar_CalendarController extends AuthenticatedController $fullcalendar_studip_urls['add'] = $this->url_for('calendar/date/add/course_' . $course->id); } + $available_views = [ + 'dayGridYear', + \Studip\Fullcalendar::VIEW_MONTH, + \Studip\Fullcalendar::VIEW_WEEK, + \Studip\Fullcalendar::VIEW_DAY + ]; + $this->fullcalendar = Studip\Fullcalendar::create( _('Veranstaltungskalender'), [ @@ -514,31 +529,31 @@ class Calendar_CalendarController extends AuthenticatedController 'maxTime' => sprintf('%02u:00', $calendar_settings['end'] ?? 20), 'allDaySlot' => true, 'allDayText' => '', - 'header' => [ - 'left' => 'dayGridYear,dayGridMonth,timeGridWeek,timeGridDay', - 'center' => 'title', - 'right' => 'prev,today,next' + 'headerToolbar' => [ + 'start' => implode(',', $available_views), + 'center' => 'title', + 'end' => 'prev,today,next' ], 'weekNumbers' => true, 'views' => [ - 'dayGridMonth' => [ + \Studip\Fullcalendar::VIEW_MONTH => [ 'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'], 'titleFormat' => ['year' => 'numeric', 'month' => 'long'], 'displayEventEnd' => true ], - 'timeGridWeek' => [ - 'columnHeaderFormat' => [ 'weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ], + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => [ 'weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ], 'weekends' => $calendar_settings['type_week'] === 'LONG', 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], 'slotDuration' => $slot_settings['week'] ], - 'timeGridDay' => [ - 'columnHeaderFormat' => [ 'weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ], + \Studip\Fullcalendar::VIEW_DAY => [ + 'dayHeaderFormat' => [ 'weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ], 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], 'slotDuration' => $slot_settings['day'] ] ], - 'defaultView' => 'timeGridWeek', + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, 'timeGridEventMinHeight' => 20, 'eventSources' => [ [ diff --git a/app/controllers/calendar/date.php b/app/controllers/calendar/date.php index 07a77b9..a33ec42 100644 --- a/app/controllers/calendar/date.php +++ b/app/controllers/calendar/date.php @@ -223,9 +223,26 @@ class Calendar_DateController extends AuthenticatedController $owner = $this->getCalendarOwner($range_and_id); $this->date = new CalendarDate(); - if (Request::submitted('begin') && Request::submitted('end')) { - $this->date->begin = Request::get('begin'); - $this->date->end = Request::get('end'); + if (Request::submitted('start') && Request::submitted('end')) { + $tz_date = new DateTime(); + $start = Request::getDateTime('start', DateTimeInterface::RFC3339_EXTENDED); + $end = Request::getDateTime('end', DateTimeInterface::RFC3339_EXTENDED); + //The date might be in UTC instead of the default time zone, so it has to be converted: + $start->setTimezone($tz_date->getTimezone()); + $end->setTimezone($tz_date->getTimezone()); + if ($start === false || $end === false) { + throw new InvalidArgumentException(_('Ungültiges Datumsformat')); + } + if (Request::get('all_day')) { + $start->setTime(0, 0, 0); + //Special case for the end timestamp: + //In Fullcalendar, all-day events end on the next day while in Stud.IP + //they end on 23:59:59 on the same day. + $end->setTime(0, 0, 0); + $end = $end->sub(new DateInterval('PT1S')); + } + $this->date->begin = $start->getTimestamp(); + $this->date->end = $end->getTimestamp(); $this->date->repetition_end = $this->date->end; } elseif (Request::submitted('begin_str') && Request::submitted('end_str')) { //Assume the textual format d.m.Y H:i: @@ -646,11 +663,32 @@ class Calendar_DateController extends AuthenticatedController ); } - $this->begin = Request::getDateTime('begin', \DateTime::RFC3339); - $this->end = Request::getDateTime('end', \DateTime::RFC3339); + $tz_date = new DateTime(); + $this->begin = Request::getDateTime('start', \DateTime::RFC3339_EXTENDED); + $this->end = Request::getDateTime('end', \DateTime::RFC3339_EXTENDED); + if ($this->begin) { + $this->begin->setTimezone($tz_date->getTimezone()); + } + if (Request::get('all_day') && $this->begin) { + if (!$this->end) { + //Set the end to the end of the day the event starts: + $this->end = clone $this->begin; + $this->end = $this->end->add(new DateInterval('P1D')); + $this->end->setTime(0,0,0); + } + //At this point, there should be an end date. + //If it is on 0:00:00 in the local time zone, we need to get to + //the last second of the day before so that the event is + //detected as all-day event. + $this->end->setTimezone($tz_date->getTimezone()); + if ($this->end->format('His') == 0) { + $this->end = $this->end->sub(new DateInterval('PT1S')); + } + } if (!$this->begin || !$this->end) { throw new InvalidArgumentException(); } + $this->end->setTimezone($tz_date->getTimezone()); //In case the moved event is a repetition event, we must know the original date from where //it was moved from to correctly move the whole date series: diff --git a/app/controllers/calendar/schedule.php b/app/controllers/calendar/schedule.php index 6abe4b1..2f9e628 100644 --- a/app/controllers/calendar/schedule.php +++ b/app/controllers/calendar/schedule.php @@ -160,8 +160,8 @@ class Calendar_ScheduleController extends AuthenticatedController //The range is not necessary a full week: If fullcalendar starts in the day //view (like in the mobile view), the start of the range may lie in the middle //of the week. - $begin = Request::getDateTime('start', \DateTime::RFC3339); - $end = Request::getDateTime('end', \DateTime::RFC3339); + $begin = Request::getDateTime('start', \DateTimeInterface::RFC3339); + $end = Request::getDateTime('end', \DateTimeInterface::RFC3339); if (!($begin instanceof \DateTime) || !($end instanceof \DateTime)) { //No time range specified. throw new InvalidArgumentException('Invalid parameters!'); @@ -335,8 +335,6 @@ class Calendar_ScheduleController extends AuthenticatedController $weekly_dates = ScheduleEntry::findByUser_id($GLOBALS['user']->id); foreach ($weekly_dates as $date) { $event_data = $date->toEventData($GLOBALS['user']->id); - //Disable fullcalendar drag & drop actions: - $event_data->editable = false; $result[] = $event_data->toFullcalendarEvent(); } @@ -344,6 +342,56 @@ class Calendar_ScheduleController extends AuthenticatedController } /** + * Handles moving an entry in the calendar. + * + * @param string $entry_id + * + * @return void + */ + public function move_entry_action(string $entry_id) + { + if (!$entry_id) { + $this->response->set_status(400, 'No entry-ID provided.'); + $this->render_nothing(); + return; + } + $entry = ScheduleEntry::find($entry_id); + if (!$entry) { + $this->response->set_status(404, 'Entry not found.'); + $this->render_nothing(); + return; + } + //Check if the current user owns this entry: + if ($entry->user_id !== $GLOBALS['user']->id) { + throw new AccessDeniedException(); + } + + $start = Request::getDateTime('start', DateTimeInterface::RFC3339_EXTENDED); + $end = Request::getDateTime('end', DateTimeInterface::RFC3339_EXTENDED); + + if (!$start || !$end) { + $this->response->set_status(400, 'Invalid date format.'); + $this->render_nothing(); + return; + } + + $start->setTimezone(new DateTimeZone('Europe/Berlin')); + $end->setTimezone(new DateTimeZone('Europe/Berlin')); + + $entry->start_time = $start->format('Hi'); + $entry->end_time = $end->format('Hi'); + $entry->dow = $start->format('N'); + if (!$entry->store()) { + $this->response->set_status(500, 'Cannot store entry.'); + $this->render_nothing(); + return; + } + + $entry_event = $entry->toEventData($GLOBALS['user']->id); + $this->render_json($entry_event->toFullcalendarEvent()); + } + + /** * This action handles adding and editing schedule entries. * * @param string $entry_id The ID of the entry to be modified. In case the ID is set to "add", a new entry @@ -360,18 +408,23 @@ class Calendar_ScheduleController extends AuthenticatedController //Provide good default values: $this->entry->colour_id = 1; if (Request::submitted('start')) { - //String format - $this->entry->dow = Request::int('dow',date('N')); - $this->entry->setFormattedStart(Request::get('start', date('H:00', strtotime('+1 hour')))); - $this->entry->setFormattedEnd(Request::get('end', date('H:00', strtotime('+2 hours')))); - } elseif (Request::submitted('begin')) { - //Fullcalendar: Timestamps - $begin = Request::get('begin'); - $end = Request::get('end'); - if ($begin && $end) { - $this->entry->dow = intval(date('N', $begin)); - $this->entry->setFormattedStart(date('H:i', $begin)); - $this->entry->setFormattedEnd(date('H:i', $end)); + //Fullcalendar provides date and time in RFC3339_EXTENDED format. + $start = Request::getDateTime('start', \DateTimeInterface::RFC3339_EXTENDED); + $end = Request::getDateTime('end', \DateTimeInterface::RFC3339_EXTENDED); + if ($start) { + //Correct the timezone first before setting start and end time. + $local_datetime = new DateTime(); + $start->setTimezone($local_datetime->getTimezone()); + $this->entry->dow = $start->format('N'); + $this->entry->start_time = $start->format('Hi'); + if ($end) { + $end->setTimezone($local_datetime->getTimezone()); + $this->entry->end_time = $end->format('Hi'); + } else { + $end = clone $start; + $end = $end->add(new DateInterval('PT1H')); + $this->entry->end_time = $end->format('Hi'); + } } } else { $begin = time() + 3600; diff --git a/app/controllers/institute/schedule.php b/app/controllers/institute/schedule.php index 1c5d091..931bdb4 100644 --- a/app/controllers/institute/schedule.php +++ b/app/controllers/institute/schedule.php @@ -50,19 +50,19 @@ class Institute_ScheduleController extends AuthenticatedController 'minTime' => '08:00', 'maxTime' => '20:00', 'allDaySlot' => false, - 'header' => [ - 'left' => '', - 'right' => '' + 'headerToolbar' => [ + 'start' => '', + 'end' => '' ], 'views' => [ - 'timeGridWeek' => [ - 'columnHeaderFormat' => ['weekday' => 'long'], + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'long'], 'weekends' => $calendar_settings['type_week'] === 'LONG', 'slotDuration' => $week_slot_duration ] ], - 'defaultView' => 'timeGridWeek', - 'defaultDate' => date('Y-m-d'), + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => date('Y-m-d'), 'timeGridEventMinHeight' => 20, 'eventSources' => [ [ diff --git a/app/controllers/resources/ajax.php b/app/controllers/resources/ajax.php index 2d7c27b..8b771b3 100644 --- a/app/controllers/resources/ajax.php +++ b/app/controllers/resources/ajax.php @@ -756,7 +756,7 @@ class Resources_AjaxController extends AuthenticatedController $resource_id = Request::get('resource_id'); $interval_id = Request::get('interval_id'); - $begin = $this->convertDatetime(Request::get('begin')); + $begin = $this->convertDatetime(Request::get('start')); $end = $this->convertDatetime(Request::get('end')); //Check if a specific interval has been moved: @@ -842,7 +842,7 @@ class Resources_AjaxController extends AuthenticatedController throw new AccessDeniedException(); } - $request->begin = $this->convertDatetime(Request::get('begin')); + $request->begin = $this->convertDatetime(Request::get('start')); $request->end = $this->convertDatetime(Request::get('end')); try { @@ -913,7 +913,7 @@ class Resources_AjaxController extends AuthenticatedController return null; } - return DateTime::createFromFormat(DateTime::RFC3339, $input) + return DateTime::createFromFormat(DateTime::RFC3339_EXTENDED, $input) ?? DateTime::createFromFormat( 'Y-m-d\TH:i:s', $input, diff --git a/app/controllers/resources/booking.php b/app/controllers/resources/booking.php index f5dec25..8bc2d78 100644 --- a/app/controllers/resources/booking.php +++ b/app/controllers/resources/booking.php @@ -902,11 +902,21 @@ class Resources_BookingController extends AuthenticatedController $this->max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME; if ($mode == 'add') { - //In case a begin and end time are already given + //In case a start and end time are already given //use those values instead: - if (Request::submitted('begin') && Request::submitted('end')) { - $this->begin->setTimestamp(Request::get('begin')); - $this->end->setTimestamp(Request::get('end')); + if ((Request::submitted('begin') || Request::submitted('start')) && Request::submitted('end')) { + if (Request::submitted('start')) { + $this->begin = Request::getDateTime('start', DateTimeInterface::RFC3339_EXTENDED); + $this->end = Request::getDateTime('end', DateTimeInterface::RFC3339_EXTENDED); + //The date might be in UTC instead of the default time zone, so it has to be converted: + $this->begin->setTimezone(new DateTimeZone('Europe/Berlin')); + $this->end->setTimezone(new DateTimeZone('Europe/Berlin')); + } else { + //For backwards compatibility: Handle the timestamp in the old "begin" parameter. + //To be removed with Stud.IP 7.0. + $this->begin->setTimestamp(Request::get('begin')); + $this->end->setTimestamp(Request::get('end')); + } if (Request::get('semester_id')) { $this->booking_style = 'repeat'; $this->repetition_style = 'weekly'; @@ -1493,7 +1503,7 @@ class Resources_BookingController extends AuthenticatedController public function add_action($resource_id = null, $booking_type = null) { if (!$resource_id) { - $resource_id = Request::option('ressource_id'); + $resource_id = Request::option('resource_id'); } $resource_ids = Request::getArray('resource_ids'); diff --git a/app/controllers/resources/print.php b/app/controllers/resources/print.php index ea7da07..b8abb4d 100644 --- a/app/controllers/resources/print.php +++ b/app/controllers/resources/print.php @@ -52,12 +52,17 @@ class Resources_PrintController extends AuthenticatedController throw new AccessDeniedException(); } - $this->timestamp = Request::get('timestamp', time()); - - $this->date = new DateTime(); - $this->date->setTimestamp($this->timestamp); + $this->semester = Semester::find(Request::get('semester_id')); + if (!$this->semester) { + $this->semester = Semester::findCurrent(); + } $sidebar = Sidebar::get(); + $sidebar->addWidget( + new SemesterSelectorWidget( + $this->url_for('resources/print/individual_booking_plan/' . $resource_id), + ) + ); $views = new ViewsWidget(); if ($GLOBALS['user']->id && ($GLOBALS['user']->id != 'nobody')) { diff --git a/app/controllers/resources/room_planning.php b/app/controllers/resources/room_planning.php index b06e166..acd52ca 100644 --- a/app/controllers/resources/room_planning.php +++ b/app/controllers/resources/room_planning.php @@ -265,11 +265,13 @@ class Resources_RoomPlanningController extends AuthenticatedController $sidebar->addWidget($options); } - $sidebar->addWidget(new TemplateWidget( - _('Datum'), - $this->get_template_factory()->open('resources/room_planning/_sidebar_date_selection.php'), - ['date' => $this->date] - )); + $date_selector = new DateSelectWidget(); + $date_selector->setCalendarControl(true); + $default_date = Request::getDateTime('defaultDate'); + if ($default_date) { + $date_selector->setDate($default_date); + } + $sidebar->addWidget($date_selector); $actions = new ActionsWidget(); if ($GLOBALS['user']->id && $GLOBALS['user']->id !== 'nobody') { diff --git a/app/controllers/room_management/planning.php b/app/controllers/room_management/planning.php index c7fda25..312146c 100644 --- a/app/controllers/room_management/planning.php +++ b/app/controllers/room_management/planning.php @@ -83,10 +83,13 @@ class RoomManagement_PlanningController extends AuthenticatedController } $sidebar->addWidget($views); - $sidebar->addWidget(new TemplateWidget( - _('Datum'), - $this->get_template_factory()->open('resources/room_planning/_sidebar_date_selection.php') - )); + $date_selector = new DateSelectWidget(); + $date_selector->setCalendarControl(true); + $default_date = Request::getDateTime('defaultDate'); + if ($default_date) { + $date_selector->setDate($default_date); + } + $sidebar->addWidget($date_selector); $clipboards = Clipboard::getClipboardsForUser($GLOBALS['user']->id); if (!empty($clipboards)) { @@ -133,7 +136,6 @@ class RoomManagement_PlanningController extends AuthenticatedController foreach ($rooms as $room) { $this->scheduler_resources[] = [ 'id' => $room->id, - 'parent_name' => $room->building->name, 'title' => $room->name ]; } diff --git a/app/views/admin/courseplanning/index.php b/app/views/admin/courseplanning/index.php index 5fd28e9..75803ec 100644 --- a/app/views/admin/courseplanning/index.php +++ b/app/views/admin/courseplanning/index.php @@ -11,26 +11,36 @@ $max_time = Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR . ':00'; ?> $min_time, - 'maxTime' => $max_time, + 'slotMinTime' => $min_time, + 'slotMaxTime' => $max_time, 'allDaySlot' => false, 'nowIndicator' => false, 'slotDuration' => '01:00:00', 'slotLabelInterval' => '01:00', 'slotLabelFormat' => ['hour' => '2-digit', 'minute' => '2-digit'], - 'timeZone' => 'UTC', - 'header' => [ - 'left' => '', - 'right' => '' + 'headerToolbar' => [ + 'start' => '', + 'end' => '' ], - 'columnHeaderFormat' => ['weekday' => 'long'], - 'defaultView' => 'timeGridWeek', + 'dayHeaderFormat' => ['weekday' => 'long'], + 'views' => [ + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'short'], + 'weekends' => true, + 'titleFormat' => [], + 'weekText' => '' + ] + ], + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, + 'display_holidays' => false, + 'display_vacations' => false, 'eventSources' => [compact('events')], 'slotEventOverlap' => false, 'displayEventTime' => false, 'editable' => true, 'droppable' => true, // this allows things to be dropped onto the calendar - 'actionCalled' => 'index' + 'external_droppable_container_id' => 'droppable-course-container', + 'external_droppable_event_selector' => 'td.draggable-course' ], [ 'class' => 'institute-plan' ]) ?> @@ -38,22 +48,25 @@ $max_time = Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR . ':00';
- +
- - $cname): ?> - - - + $cname) : ?> + + $cname, + 'duration' => '02:00', + 'studip_api_urls' => [ + 'receive' => $controller->link_for('admin/courseplanning/add_event/' . $cid) + ] + ]; + ?> + + +
- -
+ +
- + diff --git a/app/views/admin/courseplanning/pick_color.php b/app/views/admin/courseplanning/pick_color.php index 61cc7fc..e713dc5 100644 --- a/app/views/admin/courseplanning/pick_color.php +++ b/app/views/admin/courseplanning/pick_color.php @@ -5,17 +5,27 @@ * @var string $from_action * @var string $weekday * @var string $semtype + * @var array $available_colours * @var string $color */ ?>
- -
+
+ $colour_value) : ?> + > + + +
diff --git a/app/views/admin/courseplanning/weekday.php b/app/views/admin/courseplanning/weekday.php index 4ff6f21..a368982 100644 --- a/app/views/admin/courseplanning/weekday.php +++ b/app/views/admin/courseplanning/weekday.php @@ -25,16 +25,16 @@ $max_time = Config::get()->INSTITUTE_COURSE_PLAN_END_HOUR . ':00'; 'maxTime' => $max_time, 'allDaySlot' => false, 'nowIndicator' => false, - 'header' => [ - 'left' => '', - 'right' => '' + 'headerToolbar' => [ + 'start' => '', + 'end' => '' ], 'slotDuration' => '01:00:00', 'slotLabelInterval' => '01:00', 'slotLabelFormat' => ['hour' => '2-digit', 'minute' => '2-digit'], 'timeZone' => 'UTC', 'defaultDate' => $cal_date->format('Y-m-d'), - 'defaultView' => 'resourceTimeGridDay', + 'defaultView' => \Studip\Fullcalendar::GROUP_DAY, 'eventSources' => [compact('events')], 'slotEventOverlap' => false, 'displayEventTime' => false, diff --git a/app/views/calendar/schedule/index.php b/app/views/calendar/schedule/index.php index 689ae23..bb2dfa6 100644 --- a/app/views/calendar/schedule/index.php +++ b/app/views/calendar/schedule/index.php @@ -5,7 +5,7 @@ */ ?> -

+

$semester->name] diff --git a/app/views/resources/print/clipboard_rooms.php b/app/views/resources/print/clipboard_rooms.php index 3d035a1..a0642ac 100644 --- a/app/views/resources/print/clipboard_rooms.php +++ b/app/views/resources/print/clipboard_rooms.php @@ -160,14 +160,14 @@ 'maxTime' => ($max_time), 'allDaySlot' => false, 'header' => [ - 'left' => 'dayGridMonth,timeGridWeek,timeGridDay', + 'left' => implode(',', [\Studip\Fullcalendar::VIEW_MONTH, \Studip\Fullcalendar::VIEW_WEEK, \Studip\Fullcalendar::VIEW_DAY]), 'right' => 'prev,next' ], - 'defaultView' => - in_array(Request::get("defaultView"), ['dayGridMonth','timeGridWeek','timeGridDay']) + 'initialView' => + in_array(Request::get("defaultView"), [\Studip\Fullcalendar::VIEW_MONTH, \Studip\Fullcalendar::VIEW_WEEK, \Studip\Fullcalendar::VIEW_DAY]) ? Request::get("defaultView") - : 'timeGridWeek', - 'defaultDate' => Request::get("defaultDate", $print_date), + : \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => Request::get("defaultDate", $print_date), 'eventSources' => [ [ 'url' => URLHelper::getURL( diff --git a/app/views/resources/print/individual_booking_plan.php b/app/views/resources/print/individual_booking_plan.php index 630f306..9c9a18a 100644 --- a/app/views/resources/print/individual_booking_plan.php +++ b/app/views/resources/print/individual_booking_plan.php @@ -7,7 +7,7 @@ } ?>
[ [ @@ -21,20 +21,25 @@ ResourceBooking::TYPE_RESERVATION, ResourceBooking::TYPE_LOCK, ], + 'semester_id' => $semester->id, + 'semester_timerange' => Request::get('semester_timerange', 'vorles'), ] ] ], - 'minTime' => ($min_time), - 'maxTime' => ($max_time), + 'slotMinTime' => ($min_time), + 'slotMaxTime' => ($max_time), + 'headerToolbar' => [ + 'start' => '', + 'center' => '', + 'end' => '' + ], 'allDaySlot' => false, - 'defaultView' => - in_array(Request::get("defaultView"), ['dayGridMonth','timeGridWeek','timeGridDay']) - ? Request::get("defaultView") - : 'timeGridWeek', - 'defaultDate' => Request::get("defaultDate"), - 'editable' => false - ], - ['class' => 'individual-booking-plan'], - 'resources-fullcalendar' + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => ((Request::get('semester_timerange') === 'fullsem') ? date('Y-m-d', $semester->beginn) : date('Y-m-d', $semester->vorles_beginn)), + 'display_holidays' => false, + 'display_vacations' => false, + 'editable' => false, + 'event_colour_picker' => true + ] ) ?>
diff --git a/app/views/resources/resource/booking_plan.php b/app/views/resources/resource/booking_plan.php index b9400dc..5562c17 100644 --- a/app/views/resources/resource/booking_plan.php +++ b/app/views/resources/resource/booking_plan.php @@ -11,11 +11,11 @@ 'minTime' => ($min_time), 'maxTime' => ($max_time), 'allDaySlot' => false, - 'defaultView' => - in_array(Request::get("defaultView"), ['dayGridMonth','timeGridWeek','timeGridDay']) + 'initialView' => + in_array(Request::get("defaultView"), [\Studip\Fullcalendar::VIEW_MONTH, \Studip\Fullcalendar::VIEW_WEEK, \Studip\Fullcalendar::VIEW_DAY]) ? Request::get("defaultView") - : 'timeGridWeek', - 'defaultDate' => Request::get("defaultDate"), + : \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => Request::get("defaultDate"), 'eventSources' => [ [ 'url' => URLHelper::getLink( diff --git a/app/views/resources/room_planning/_sidebar_date_selection.php b/app/views/resources/room_planning/_sidebar_date_selection.php deleted file mode 100644 index d1ac876..0000000 --- a/app/views/resources/room_planning/_sidebar_date_selection.php +++ /dev/null @@ -1,12 +0,0 @@ - - date('Y-m-d')]) - ); ?> - - diff --git a/app/views/resources/room_planning/booking_plan.php b/app/views/resources/room_planning/booking_plan.php index e3addfa..52b98ad 100644 --- a/app/views/resources/room_planning/booking_plan.php +++ b/app/views/resources/room_planning/booking_plan.php @@ -45,34 +45,40 @@ true, - 'selectable' => !empty($fullcalendar_studip_urls['add']), + 'editable' => true, + 'selectable' => !empty($fullcalendar_studip_urls['add']), 'studip_urls' => $fullcalendar_studip_urls, - 'minTime' => $min_time, - 'maxTime' => $max_time, - 'allDaySlot' => false, - 'header' => [ - 'left' => 'dayGridMonth,timeGridWeek,timeGridDay', - 'right' => 'prev,next' + 'slotMinTime' => $min_time, + 'slotMaxTime' => $max_time, + 'allDaySlot' => false, + 'headerToolbar' => [ + 'start' => implode( + ',', + [\Studip\Fullcalendar::VIEW_MONTH, \Studip\Fullcalendar::VIEW_WEEK, \Studip\Fullcalendar::VIEW_DAY] + ), + 'end' => 'prev,next' ], 'weekNumbers' => true, 'views' => [ - 'dayGridMonth' => [ + \Studip\Fullcalendar::VIEW_MONTH => [ 'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'], 'displayEventEnd' => true ], - 'timeGridWeek' => [ - 'columnHeaderFormat' => [ 'weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ] + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true] ], - 'timeGridDay' => [ - 'columnHeaderFormat' => [ 'weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true ] - ] + \Studip\Fullcalendar::VIEW_DAY => [ + 'dayHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true] + ] ], - 'defaultView' => - in_array(Request::get("defaultView"), ['dayGridMonth','timeGridWeek','timeGridDay']) + 'initialView' => + in_array( + Request::get("defaultView"), + [\Studip\Fullcalendar::VIEW_MONTH, \Studip\Fullcalendar::VIEW_WEEK, \Studip\Fullcalendar::VIEW_DAY] + ) ? Request::get("defaultView") - : 'timeGridWeek', - 'defaultDate' => $date->format('Y-m-d'), + : \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => $date->format('Y-m-d'), 'eventSources' => [ [ 'url' => URLHelper::getURL( diff --git a/app/views/resources/room_planning/semester_plan.php b/app/views/resources/room_planning/semester_plan.php index 2816328..7855f19 100644 --- a/app/views/resources/room_planning/semester_plan.php +++ b/app/views/resources/room_planning/semester_plan.php @@ -87,19 +87,24 @@ 'editable' => true, 'selectable' => isset($fullcalendar_studip_urls['add']), 'studip_urls' => $fullcalendar_studip_urls, - 'minTime' => $min_time, - 'maxTime' => $max_time, + 'slotMinTime' => $min_time, + 'slotMaxTime' => $max_time, 'allDaySlot' => false, - 'columnHeaderFormat' => ['weekday'=> 'short'], - 'header' => [ + 'dayHeaderFormat' => ['weekday'=> 'short'], + 'headerToolbar' => [ 'left' => '', 'right' => '' ], - 'defaultView' => - in_array(Request::get('defaultView'), ['dayGridMonth','timeGridWeek','timeGridDay']) - ? Request::get('defaultView') - : 'timeGridWeek', - 'defaultDate' => ((Request::get('semester_timerange') === 'fullsem') ? date('Y-m-d', $semester->beginn) : date('Y-m-d', $semester->vorles_beginn)), + 'views' => [ + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true] + ], + ], + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => ((Request::get('semester_timerange') === 'fullsem') ? date('Y-m-d', $semester->beginn) : date('Y-m-d', $semester->vorles_beginn)), + 'display_holidays' => false, + 'display_vacations' => false, + 'weekNumbers' => false, 'eventSources' => [ [ 'url' => URLHelper::getURL( diff --git a/app/views/resources/room_request/planning.php b/app/views/resources/room_request/planning.php index 20d7ac7..8011dee 100644 --- a/app/views/resources/room_request/planning.php +++ b/app/views/resources/room_request/planning.php @@ -62,19 +62,19 @@ 'editable' => true, 'selectable' => isset($fullcalendar_studip_urls['add']), 'studip_urls' => $fullcalendar_studip_urls ?? [], - 'minTime' => ($min_time), - 'maxTime' => ($max_time), + 'slotMinTime' => $min_time, + 'slotMaxTime' => $max_time, 'allDaySlot' => false, - 'columnHeaderFormat' => ['weekday' => 'short'], - 'header' => [ + 'dayHeaderFormat' => ['weekday' => 'short'], + 'headerToolbar' => [ 'left' => '', 'right' => '' ], - 'defaultView' => - in_array(Request::get("defaultView"), ['dayGridMonth', 'timeGridWeek', 'timeGridDay']) - ? Request::get("defaultView") - : 'timeGridWeek', - 'defaultDate' => date('Y-m-d', $default_date), + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => date('Y-m-d', $default_date), + 'display_holidays' => false, + 'display_vacations' => false, + 'weekNumbers' => false, 'eventSources' => [ [ 'url' => URLHelper::getURL( diff --git a/app/views/room_management/planning/index.php b/app/views/room_management/planning/index.php index 7289733..18064a6 100644 --- a/app/views/room_management/planning/index.php +++ b/app/views/room_management/planning/index.php @@ -37,22 +37,20 @@ 'editable' => true, 'selectable' => $all_rooms_booking_rights, 'studip_urls' => $fullcalendar_studip_urls, - 'minTime' => ($min_time), - 'maxTime' => ($max_time), + 'slotMinTime' => $min_time, + 'slotMaxTime' => $max_time, 'allDaySlot' => false, - 'header' => [ - 'left' => '',//'resourceTimelineMonth,resourceTimelineWeek,resourceTimelineDay', - 'right' => 'prev,next' + 'headerToolbar' => [ + 'start' => '', + 'center' => 'title', + 'end' => 'prev,next' ], 'slotLabelFormat' => [ ['hour'=> '2-digit', 'hour12' => false] ], - 'defaultView' => - in_array(Request::get("defaultView"), ['resourceTimelineMonth', 'resourceTimelineWeek', 'resourceTimelineDay']) - ? Request::get("defaultView") - : 'resourceTimelineDay', - 'defaultDate' => Request::get("defaultDate"), + 'initialView' => \Studip\Fullcalendar::GROUP_DAY, + 'initialDate' => Request::get("defaultDate"), 'eventSources' => [ [ 'url' => URLHelper::getLink( diff --git a/app/views/room_management/planning/semester_plan.php b/app/views/room_management/planning/semester_plan.php index 58e8feb..254be5e 100644 --- a/app/views/room_management/planning/semester_plan.php +++ b/app/views/room_management/planning/semester_plan.php @@ -41,23 +41,22 @@ [ 'resources' => $scheduler_resources, 'resourceLabelText' => _('Raum'), - 'minTime' => ($min_time), - 'maxTime' => ($max_time), + 'slotMinTime' => ($min_time), + 'slotMaxTime' => ($max_time), 'allDaySlot' => false, 'slotLabelFormat' => [ ['weekday'=> 'short'], // top level of text ['hour'=> '2-digit', 'hour12' => false] // lower level of text ], - 'header' => [ + 'headerToolbar' => [ 'left' => '', 'right' => '' ], - 'defaultView' => - in_array(Request::get("defaultView"), ['resourceTimelineMonth', 'resourceTimelineWeek', 'resourceTimelineDay']) - ? Request::get("defaultView") - : 'resourceTimelineWeek', - 'defaultDate' => ((Request::get("semester_timerange") == 'fullsem') ? date('Y-m-d',$semester->beginn) : date('Y-m-d',$semester->vorles_beginn)), + 'initialView' => \Studip\Fullcalendar::GROUP_WEEK, + 'initialDate' => ((Request::get("semester_timerange") == 'fullsem') ? date('Y-m-d',$semester->beginn) : date('Y-m-d',$semester->vorles_beginn)), + 'display_holidays' => false, + 'display_vacations' => false, 'eventSources' => [ [ 'url' => URLHelper::getLink( diff --git a/lib/classes/Fullcalendar.php b/lib/classes/Fullcalendar.php index 8674601..d3bc823 100644 --- a/lib/classes/Fullcalendar.php +++ b/lib/classes/Fullcalendar.php @@ -3,6 +3,32 @@ namespace Studip; class Fullcalendar { + /** + * The standard month view for Stud.IP calendars. + */ + const VIEW_MONTH = 'dayGridMonth'; + + /** + * The standard week view for Stud.IP calendars. + */ + const VIEW_WEEK = 'timeGridWeek'; + + /** + * The standard day view for Stud.IP calendars. + */ + const VIEW_DAY = 'timeGridDay'; + + /** + * The standard week view for Stud.IP group calendars. + */ + const GROUP_WEEK = 'resourceTimelineWeek'; + + /** + * The standard day view for Stud.IP group calendars. + */ + const GROUP_DAY = 'resourceTimelineDay'; + + protected $title; /** @@ -58,9 +84,9 @@ class Fullcalendar public function setDefaultView(?string $view): void { if ($view === null) { - unset($this->config['defaultView']); + unset($this->config['initialView']); } else { - $this->config['defaultView'] = $view; + $this->config['initialView'] = $view; } } @@ -78,16 +104,56 @@ class Fullcalendar $factory = new \Flexi\Factory($GLOBALS['STUDIP_BASE_PATH'] . '/templates'); $template = $factory->open('studip-fullcalendar.php'); $real_data_name = sprintf('data-%s', $this->data_name); - return $template->render( - [ - 'title' => $this->title, - 'config' => $this->config, - 'attributes' => array_merge( - $this->attributes, - [$real_data_name => '1'] - ) - ] + + //Move the Stud.IP parts of the configuration out of the + //fullcalendar configuration: + $fullcalendar_config = $this->config; + $template_params = [ + 'title' => $this->title, + 'dialogSize' => 'auto', + 'actionUrls' => [], + 'displayHolidays' => true, + 'displayVacations' => true, + 'externalDroppableContainerId' => '', + 'externalDroppableEventSelector' => '', + 'eventColourPicker' => false + ]; + if (!empty($this->attributes['class'])) { + $template_params['extraClasses'] = $this->attributes['class']; + unset($this->attributes['class']); + } else { + $template_params['extraClasses'] = ''; + } + $template_params['attributes'] = array_merge( + $this->attributes, + [$real_data_name => '1'] ); + if (array_key_exists('studip_urls', $fullcalendar_config) + && is_array($fullcalendar_config['studip_urls']) + ) { + $template_params['actionUrls'] = $fullcalendar_config['studip_urls']; + unset($fullcalendar_config['studip_urls']); + } + + $studip_config_map = [ + 'dialog_size' => 'dialogSize', + 'display_holidays' => 'displayHolidays', + 'display_vacations' => 'displayVacations', + 'external_droppable_container_id' => 'externalDroppableContainerId', + 'external_droppable_event_selector' => 'externalDroppableEventSelector', + 'event_colour_picker' => 'eventColourPicker', + ]; + + foreach ($studip_config_map as $config_key => $param_key) { + if (array_key_exists($config_key, $fullcalendar_config)) { + $template_params[$param_key] = $fullcalendar_config[$config_key]; + unset($fullcalendar_config[$config_key]); + } + } + + $template_params['config'] = $fullcalendar_config; + + return $template->render($template_params); } /** diff --git a/lib/classes/InstituteCalendarHelper.php b/lib/classes/InstituteCalendarHelper.php index 54393fa..404ab6a 100644 --- a/lib/classes/InstituteCalendarHelper.php +++ b/lib/classes/InstituteCalendarHelper.php @@ -190,7 +190,7 @@ class InstituteCalendarHelper } $event_colors[$event_id][$institut_id] = $color; $df[0]->content = serialize($event_colors); - return $df[0]->store(); + return $df[0]->store() !== false; } return false; } @@ -395,7 +395,7 @@ class InstituteCalendarHelper $end_time[0] += 2; $end_time = implode(':', $end_time); - $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event'); + $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event/' . $cycle_date->id); $name = $course->getFullName('number-name'); if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) { @@ -451,7 +451,7 @@ class InstituteCalendarHelper $events[] = [ 'resourceId' => $resource_column, 'id' => $cycle_date->id, - 'title' => empty($fields) ? $name : '', + 'title' => '', 'start' => $start, 'end' => $end, 'textColor' => $textcolor, @@ -462,14 +462,23 @@ class InstituteCalendarHelper 'durationEditable' => $is_duration_editable, 'resourceEditable' => true, 'studip_api_urls' => ['move' => $move_url], - 'studip_view_urls' => ['edit' => URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id)], + 'studip_view_urls' => [ + 'show' =>URLHelper::getURL('dispatch.php/course/details/index/' . $cycle_date->seminar_id) + ], 'metadate_id' => $cycle_date->metadate_id, 'course_id' => $cycle_date->seminar_id, 'tooltip' => self::getCycleInfos($course, $cycle_date), 'icon' => $is_start_editable ? '' : 'lock-locked', 'conform' => $conform, // custom props (event.extendedProps) - 'content_fields' => $fields, + 'title-lines' => $fields, + 'action-icons' => [ + [ + 'url' => URLHelper::getURL('dispatch.php/admin/courseplanning/pick_color/' . $cycle_date->id . '/index'), + 'icon_name' => 'group4', + 'label' => _('Farbe wählen') + ] + ] ]; } }, array_keys($courses)); @@ -535,7 +544,7 @@ class InstituteCalendarHelper $event_columns = self::getCourseEventcolumns($course); $event_colors = self::getCourseEventcolors($course); - $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event'); + $move_url = URLHelper::getURL('dispatch.php/admin/courseplanning/move_event/' . $cycle_date->id); $name = $course->getFullName('number-name'); if ($start_time != $cycle_date['start_time'] && $end_time != $cycle_date['end_time']) { @@ -574,6 +583,13 @@ class InstituteCalendarHelper } } + $fields = [ + 'course_number' => UserConfig::get($GLOBALS['user']->id)->TIMETABLE_COURSE_NUMBER_VISIBLE ? ($course->veranstaltungsnummer ?: _('(keine VA-Nummer)')) : null, + 'course_name' => UserConfig::get($GLOBALS['user']->id)->TIMETABLE_COURSE_NAME_VISIBLE ? $course->getFullName('name') : null, + 'lecturers' => UserConfig::get($GLOBALS['user']->id)->TIMETABLE_LECTURERS_VISIBLE ? self::getLecturers($course) : null, + 'room' => UserConfig::get($GLOBALS['user']->id)->TIMETABLE_ROOMS_VISIBLE ? '' : null + ]; + return [ 'resourceId' => $resource_column, 'id' => $cycle_date->id, @@ -592,7 +608,16 @@ class InstituteCalendarHelper 'metadate_id' => $cycle_date->metadate_id, 'course_id' => $cycle_date->seminar_id, 'tooltip' => self::getCycleInfos($course, $cycle_date), - 'icon' => $is_start_editable ? '' : 'lock-locked' + 'icon' => $is_start_editable ? '' : 'lock-locked', + // custom props (event.extendedProps) + 'title-lines' => $fields, + 'action-icons' => [ + [ + 'url' => URLHelper::getURL('dispatch.php/admin/courseplanning/pick_color/' . $cycle_date->id . '/index'), + 'icon_name' => 'group4', + 'label' => _('Farbe wählen') + ] + ] ]; } @@ -638,8 +663,8 @@ class InstituteCalendarHelper if (!$GLOBALS['MVV_MODUL']['STATUS']['values'][$modul->stat]['public']) { return false; } - $modul_start = Semester::find($modul->start)->beginn ?: 0; - $modul_end = Semester::find($modul->end)->ende ?: PHP_INT_MAX; + $modul_start = Semester::find($modul->start)->beginn ?? 0; + $modul_end = Semester::find($modul->end)->ende ?? PHP_INT_MAX; return $modul_end >= $course->start_semester->beginn && ( $course->isOpenEnded() @@ -718,7 +743,7 @@ class InstituteCalendarHelper return $info_string; } - private static function getLecturers(Course $course): array + private static function getLecturers(Course $course): string { $dozenten = []; $lecturers = ''; diff --git a/lib/classes/calendar/EventData.php b/lib/classes/calendar/EventData.php index db1e472..233f131 100644 --- a/lib/classes/calendar/EventData.php +++ b/lib/classes/calendar/EventData.php @@ -116,7 +116,7 @@ class EventData 'studip_range_id' => $this->range_id, 'studip_view_urls' => $this->view_urls, 'studip_api_urls' => $this->api_urls, - 'icon' => $this->icon + 'icons' => $this->icon ? explode(',', $this->icon) : [] //Backwards compatibility in case only one icon is set. ] + ($this->group_id ? ['groupId' => $this->group_id] : []); } } diff --git a/lib/classes/calendar/Helper.php b/lib/classes/calendar/Helper.php index 4bcd4f6..afcfbfd 100644 --- a/lib/classes/calendar/Helper.php +++ b/lib/classes/calendar/Helper.php @@ -159,15 +159,15 @@ class Helper } $available_views = [ - 'timeGridWeek' => [ - 'columnHeaderFormat' => ['weekday' => 'short'], + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'short'], 'slotDuration' => $slot_duration ] ]; if (!in_array(date('N'), $hidden_days)) { //The current day is visible: Allow a day view: - $available_views['timeGridDay'] = [ - 'columnHeaderFormat' => ['weekday' => 'short'], + $available_views[\Studip\Fullcalendar::VIEW_DAY] = [ + 'dayHeaderFormat' => ['weekday' => 'short'], 'slotDuration' => $slot_duration ]; } @@ -175,27 +175,28 @@ class Helper return new \Studip\Fullcalendar( _('Stundenplan'), [ - 'editable' => true, - 'selectable' => true, - 'dialog_size' => 'auto', - 'minTime' => $schedule_settings['start_time'] ?? '08:00', - 'maxTime' => $schedule_settings['end_time'] ?? '20:00', - 'allDaySlot' => false, - 'header' => [ - 'left' => count($available_views) > 1 ? implode(',', array_keys($available_views)) : '', - 'right' => 'prev,today,next' + 'editable' => true, + 'selectable' => true, + 'dialog_size' => 'auto', + 'slotMinTime' => $schedule_settings['start_time'] ?? '08:00', + 'slotMaxTime' => $schedule_settings['end_time'] ?? '20:00', + 'allDaySlot' => false, + 'headerToolbar' => [ + 'start' => count($available_views) > 1 ? implode(',', array_keys($available_views)) : '', + 'end' => 'prev,today,next' ], 'views' => $available_views, - 'columnHeaderFormat' => ['weekday' => 'short'], - 'defaultView' => 'timeGridWeek', - 'defaultDate' => date('Y-m-d'), + 'dayHeaderFormat' => ['weekday' => 'short'], + 'initialView' => \Studip\Fullcalendar::VIEW_WEEK, + 'initialDate' => date('Y-m-d'), 'slotLabelFormat' => [ 'hour' => 'numeric', 'minute' => '2-digit', 'omitZeroMinute' => false ], - 'weekends' => true, - 'hiddenDays' => $fullcalendar_hidden_days, + 'weekends' => true, + 'weekNumbers' => false, + 'hiddenDays' => $fullcalendar_hidden_days, 'timeGridEventMinHeight' => 20, 'eventSources' => [ [ @@ -230,12 +231,12 @@ class Helper $calendar_settings = $calendar_owner->getConfiguration()->CALENDAR_SETTINGS ?? []; //Map calendar settings to fullcalendar settings: - $default_view = 'timeGridWeek'; + $default_view = \Studip\Fullcalendar::VIEW_WEEK; if (!empty($calendar_settings['view'])) { if ($calendar_settings['view'] === 'day') { - $default_view = 'timeGridDay'; + $default_view = \Studip\Fullcalendar::VIEW_DAY; } elseif ($calendar_settings['view'] === 'month') { - $default_view = 'dayGridMonth'; + $default_view = \Studip\Fullcalendar::VIEW_MONTH; } } @@ -259,29 +260,32 @@ class Helper 'allDaySlot' => true, 'allDayText' => '', 'header' => [ - 'left' => 'dayGridYear,dayGridMonth,timeGridWeek,timeGridDay', + 'left' => implode( + ',', + [\Studip\Fullcalendar::VIEW_MONTH, \Studip\Fullcalendar::VIEW_WEEK, \Studip\Fullcalendar::VIEW_DAY] + ), 'right' => 'prev,today,next' ], 'weekNumbers' => true, 'views' => [ - 'dayGridMonth' => [ + \Studip\Fullcalendar::VIEW_MONTH => [ 'eventTimeFormat' => ['hour' => 'numeric', 'minute' => '2-digit'], 'titleFormat' => ['year' => 'numeric', 'month' => 'long'], 'displayEventEnd' => true ], - 'timeGridWeek' => [ - 'columnHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], + \Studip\Fullcalendar::VIEW_WEEK => [ + 'dayHeaderFormat' => ['weekday' => 'short', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], 'weekends' => $calendar_settings['type_week'] === 'LONG', 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], 'slotDuration' => $slot_durations['week'] ], - 'timeGridDay' => [ - 'columnHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], + \Studip\Fullcalendar::VIEW_DAY => [ + 'dayHeaderFormat' => ['weekday' => 'long', 'year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit', 'omitCommas' => true], 'titleFormat' => ['year' => 'numeric', 'month' => '2-digit', 'day' => '2-digit'], 'slotDuration' => $slot_durations['day'] ] ], - 'defaultView' => $default_view, + 'initialView' => $default_view, 'eventSources' => [ [ 'url' => \URLHelper::getURL( diff --git a/lib/models/calendar/CalendarDateAssignment.php b/lib/models/calendar/CalendarDateAssignment.php index 515f26b..0e2f1e8 100644 --- a/lib/models/calendar/CalendarDateAssignment.php +++ b/lib/models/calendar/CalendarDateAssignment.php @@ -30,8 +30,8 @@ - "ACKNOWLEDGED": The calendar owner only acknowledged that the date exists but doesn't necessarily participate in it. * @property CalendarDate $calendar_date The associated calendar date object. - * @property User $user - * @property Course $course + * @property User $user + * @property Course $course */ class CalendarDateAssignment extends SimpleORMap implements Event { @@ -663,8 +663,8 @@ class CalendarDateAssignment extends SimpleORMap implements Event $studip_urls['show'] = URLHelper::getURL('dispatch.php/calendar/date/index/' . $this->calendar_date_id, $show_url_params); if ($this->isWritable($user_id)) { - $action_urls['resize_dialog'] = URLHelper::getURL('dispatch.php/calendar/date/move/' . $this->calendar_date_id); - $action_urls['move_dialog'] = URLHelper::getURL('dispatch.php/calendar/date/move/' . $this->calendar_date_id, ['original_date' => $begin->format('Y-m-d')]); + $action_urls['resize'] = URLHelper::getURL('dispatch.php/calendar/date/move/' . $this->calendar_date_id); + $action_urls['move'] = URLHelper::getURL('dispatch.php/calendar/date/move/' . $this->calendar_date_id, ['original_date' => $begin->format('Y-m-d')]); } } diff --git a/lib/models/calendar/ScheduleEntry.php b/lib/models/calendar/ScheduleEntry.php index ca72e99..c9eb13c 100644 --- a/lib/models/calendar/ScheduleEntry.php +++ b/lib/models/calendar/ScheduleEntry.php @@ -312,7 +312,10 @@ class ScheduleEntry extends SimpleORMap implements Event [ 'show' => URLHelper::getURL('dispatch.php/calendar/schedule/entry/' . $this->id) ], - [], + [ + 'resize' => URLHelper::getURL('dispatch.php/calendar/schedule/move_entry/' . $this->id), + 'move' => URLHelper::getURL('dispatch.php/calendar/schedule/move_entry/' . $this->id) + ], '', $GLOBALS['PERS_TERMIN_KAT'][$this->colour_id]['border_color'] ?? '#000000', $this->isAllDayEvent() diff --git a/package-lock.json b/package-lock.json index cf565df..09d74d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "6.1.0", "license": "GPL-2.0", "dependencies": { + "@fullcalendar/resource": "^6.1.19", + "@fullcalendar/vue3": "^6.1.19", "@vojtechlanka/vue-tags-input": "^3.1.1", "jsonapi-serializer": "^3.6.9", "qrcode.vue": "^3.6.0", @@ -24,13 +26,12 @@ "@babel/register": "^7.25.9", "@ckeditor/ckeditor5-vue": "^5.1.0", "@eslint/js": "^9.16.0", - "@fullcalendar/core": "^4.3.1", - "@fullcalendar/daygrid": "^4.3.0", - "@fullcalendar/interaction": "^4.3.0", - "@fullcalendar/resource-common": "^4.3.1", - "@fullcalendar/resource-timegrid": "^4.3.0", - "@fullcalendar/resource-timeline": "^4.3.0", - "@fullcalendar/timegrid": "^4.3.0", + "@fullcalendar/core": "^6.1.19", + "@fullcalendar/daygrid": "^6.1.19", + "@fullcalendar/interaction": "^6.1.19", + "@fullcalendar/resource-timegrid": "^6.1.19", + "@fullcalendar/resource-timeline": "^6.1.19", + "@fullcalendar/timegrid": "^6.1.19", "@isaul32/ckeditor5-math": "43.2.x", "@playwright/test": "^1.33.0", "@popperjs/core": "^2.11.2", @@ -2677,88 +2678,150 @@ } }, "node_modules/@fullcalendar/core": { - "version": "4.4.2", - "dev": true, - "license": "MIT" + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.19.tgz", + "integrity": "sha512-z0aVlO5e4Wah6p6mouM0UEqtRf1MZZPt4mwzEyU6kusaNL+dlWQgAasF2cK23hwT4cmxkEmr4inULXgpyeExdQ==", + "license": "MIT", + "dependencies": { + "preact": "~10.12.1" + } }, "node_modules/@fullcalendar/daygrid": { - "version": "4.4.2", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.19.tgz", + "integrity": "sha512-IAAfnMICnVWPjpT4zi87i3FEw0xxSza0avqY/HedKEz+l5MTBYvCDPOWDATpzXoLut3aACsjktIyw9thvIcRYQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19" } }, "node_modules/@fullcalendar/interaction": { - "version": "4.4.2", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.19.tgz", + "integrity": "sha512-GOciy79xe8JMVp+1evAU3ytdwN/7tv35t5i1vFkifiuWcQMLC/JnLg/RA2s4sYmQwoYhTw/p4GLcP0gO5B3X5w==", "dev": true, "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19" } }, - "node_modules/@fullcalendar/resource-common": { - "version": "4.4.2", - "dev": true, + "node_modules/@fullcalendar/premium-common": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/premium-common/-/premium-common-6.1.19.tgz", + "integrity": "sha512-bOWHm1u1dUy6M4fQ0hNK7qEI7SrVWrN1ovv/z4/FE/ybfM19ukz7SFs907Ur7KUBWLNKTQYXBtdrY/ginwWraw==", "license": "SEE LICENSE IN LICENSE.md", "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19" + } + }, + "node_modules/@fullcalendar/resource": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource/-/resource-6.1.19.tgz", + "integrity": "sha512-br1ylX/aIOfd8m7Tzl2LpJBSI+N9Q6aS1qw7K9qnQjYXWQyHBlfLG6ZcPmmkjfaqTUJc8ARRbtNWj1ts5qOZgQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.19" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.19" } }, "node_modules/@fullcalendar/resource-daygrid": { - "version": "4.4.2", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource-daygrid/-/resource-daygrid-6.1.19.tgz", + "integrity": "sha512-ylcPQwxwbMS4sIvhwJltNWUZhCinD/IhsYvIxfv/wXb0UBds8d9zacPEjcRRKiXShXj/95W/YWVF+9JcCiRfIQ==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@fullcalendar/daygrid": "~4.4.0", - "@fullcalendar/resource-common": "~4.4.0" + "@fullcalendar/daygrid": "~6.1.19", + "@fullcalendar/premium-common": "~6.1.19" }, "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19", + "@fullcalendar/resource": "~6.1.19" } }, "node_modules/@fullcalendar/resource-timegrid": { - "version": "4.4.2", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource-timegrid/-/resource-timegrid-6.1.19.tgz", + "integrity": "sha512-Y16w/Z28gZwAVxVKmicKjbaSY4DixV9canEeY2A09Ka3golaxSrTDQ72PMbxzPGEMv3S7HaqDojofNsfh+wH/g==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@fullcalendar/resource-common": "~4.4.0", - "@fullcalendar/resource-daygrid": "~4.4.0", - "@fullcalendar/timegrid": "~4.4.0" + "@fullcalendar/premium-common": "~6.1.19", + "@fullcalendar/resource-daygrid": "~6.1.19", + "@fullcalendar/timegrid": "~6.1.19" }, "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19", + "@fullcalendar/resource": "~6.1.19" } }, "node_modules/@fullcalendar/resource-timeline": { - "version": "4.4.2", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource-timeline/-/resource-timeline-6.1.19.tgz", + "integrity": "sha512-oC3aVR++dLqJNeBwmLHq9sDgRDFfIG0qSteV7bgBekvNlqEMqXx8wPjUxnELrq8rrhMmK4iV3wO7AB/48IVgyg==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@fullcalendar/resource-common": "~4.4.0", - "@fullcalendar/timeline": "~4.4.0" + "@fullcalendar/premium-common": "~6.1.19", + "@fullcalendar/scrollgrid": "~6.1.19", + "@fullcalendar/timeline": "~6.1.19" }, "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19", + "@fullcalendar/resource": "~6.1.19" + } + }, + "node_modules/@fullcalendar/scrollgrid": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/scrollgrid/-/scrollgrid-6.1.19.tgz", + "integrity": "sha512-S1pbiYHvmV0ep6z5sWXJQfgW4Y/jrS5iLIAqSagDFPK0jr327nBxl7Ryi3Zb5UdMIP0/O4GXs8jwZabQPd8SOg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.19" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.19" } }, "node_modules/@fullcalendar/timegrid": { - "version": "4.4.2", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.19.tgz", + "integrity": "sha512-OuzpUueyO9wB5OZ8rs7TWIoqvu4v3yEqdDxZ2VcsMldCpYJRiOe7yHWKr4ap5Tb0fs7Rjbserc/b6Nt7ol6BRg==", "dev": true, "license": "MIT", "dependencies": { - "@fullcalendar/daygrid": "~4.4.0" + "@fullcalendar/daygrid": "~6.1.19" }, "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19" } }, "node_modules/@fullcalendar/timeline": { - "version": "4.4.3", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/timeline/-/timeline-6.1.19.tgz", + "integrity": "sha512-d2P961mnUTXtJeWNmIq1neoDmZcrPUaK7nGFoc+jQAlnmG3aNSVWQmD1ia694AMqLWtcWkwipW9MuaJgx2QvrA==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.19", + "@fullcalendar/scrollgrid": "~6.1.19" + }, "peerDependencies": { - "@fullcalendar/core": "~4.4.0" + "@fullcalendar/core": "~6.1.19" + } + }, + "node_modules/@fullcalendar/vue3": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.19.tgz", + "integrity": "sha512-j5eUSxx0xIy3ADljo0f5B9PhjqXnCQ+7nUMPfsslc2eGVjp4F74YvY3dyd6OBbg13IvpsjowkjncGipYMQWmTA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.19", + "vue": "^3.0.11" } }, "node_modules/@humanwhocodes/config-array": { @@ -11309,6 +11372,16 @@ "dev": true, "license": "MIT" }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, diff --git a/package.json b/package.json index 965531f..0d48bad 100644 --- a/package.json +++ b/package.json @@ -30,13 +30,12 @@ "@babel/register": "^7.25.9", "@ckeditor/ckeditor5-vue": "^5.1.0", "@eslint/js": "^9.16.0", - "@fullcalendar/core": "^4.3.1", - "@fullcalendar/daygrid": "^4.3.0", - "@fullcalendar/interaction": "^4.3.0", - "@fullcalendar/resource-common": "^4.3.1", - "@fullcalendar/resource-timegrid": "^4.3.0", - "@fullcalendar/resource-timeline": "^4.3.0", - "@fullcalendar/timegrid": "^4.3.0", + "@fullcalendar/core": "^6.1.19", + "@fullcalendar/daygrid": "^6.1.19", + "@fullcalendar/interaction": "^6.1.19", + "@fullcalendar/resource-timegrid": "^6.1.19", + "@fullcalendar/resource-timeline": "^6.1.19", + "@fullcalendar/timegrid": "^6.1.19", "@isaul32/ckeditor5-math": "43.2.x", "@playwright/test": "^1.33.0", "@popperjs/core": "^2.11.2", @@ -159,6 +158,8 @@ "output": "./.reports/eslint-report.xml" }, "dependencies": { + "@fullcalendar/resource": "^6.1.19", + "@fullcalendar/vue3": "^6.1.19", "@vojtechlanka/vue-tags-input": "^3.1.1", "jsonapi-serializer": "^3.6.9" } diff --git a/resources/assets/javascripts/bootstrap/fullcalendar.js b/resources/assets/javascripts/bootstrap/fullcalendar.js deleted file mode 100644 index 44786d9..0000000 --- a/resources/assets/javascripts/bootstrap/fullcalendar.js +++ /dev/null @@ -1,48 +0,0 @@ -STUDIP.ready(function () { - //Check if fullcalendar instances are to be displayed: - $('*[data-fullcalendar="1"]').each(function () { - STUDIP.loadChunk('fullcalendar').then(() => { - if (this.calendar === undefined) { - let calendar; - if ($(this).hasClass('semester-plan')) { - calendar = STUDIP.Fullcalendar.createSemesterCalendarFromNode(this); - } else { - calendar = STUDIP.Fullcalendar.createFromNode(this); - } - - continuousRefresh(calendar, 10); - } - }); - }); - - function continuousRefresh(calendar, ttl) { - setTimeout(() => { - calendar.updateSize(); - if (ttl > 0) { - continuousRefresh(calendar, ttl - 1); - } - }, 200); - } - - if ($('#event-color-picker > option').length <= 1) { - var selectedColor = $('#selected-color').val(); - var colors = ['yellow', 'orange', 'red', 'violet', 'dark-violet', 'green', 'dark-green', 'petrol', 'brown']; - - var style = window.getComputedStyle(document.body); - colors.forEach(color => { - let real_color = style.getPropertyValue(`--${color}`).trim(); - $('#event-color-picker').append([ - $('').attr({ - id: color, - value: real_color, - checked: selectedColor === real_color - }), - $('