diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ae298019..d89ea84bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - Updated jquery-minicolors to 2.3.5 - Updated angular-bootstrap-switch to 0.5.2 - Updated bootstrap-switch to 3.4.0 -- Updated fullCalendar to 2.9.1 +- Updated fullCalendar to 3.10.2 ## v4.5.6 2020 September 1st diff --git a/app/frontend/src/javascript/controllers/admin/calendar.js b/app/frontend/src/javascript/controllers/admin/calendar.js index 3975b9330..54ad2bec1 100644 --- a/app/frontend/src/javascript/controllers/admin/calendar.js +++ b/app/frontend/src/javascript/controllers/admin/calendar.js @@ -46,11 +46,10 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state $scope.eventsInCalendar = (settingsPromise.events_in_calendar === 'true'); // bind the availabilities slots with full-Calendar events - $scope.eventSources = []; - $scope.eventSources.push({ + $scope.eventSources = [{ url: '/api/availabilities', textColor: 'black' - }); + }]; // fullCalendar (v2) configuration $scope.calendarConfig = CalendarConfig({ @@ -101,7 +100,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state { id: slot.slot_id }, function (data, status) { // success // update the canceled_at attribute - for (let resa of Array.from($scope.reservations)) { + for (const resa of Array.from($scope.reservations)) { if (resa.slot_id === data.id) { resa.canceled_at = data.canceled_at; break; @@ -185,9 +184,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state }, function () { // the admin has confirmed, remove the plan - const plans = _.drop($scope.availability.plan_ids, plan.id); + _.drop($scope.availability.plan_ids, plan.id); - return Availability.update({ id: $scope.availability.id }, { availability: { plans_attributes: [{ id: plan.id, _destroy: true }] } } + Availability.update({ id: $scope.availability.id }, { availability: { plans_attributes: [{ id: plan.id, _destroy: true }] } } , function (data, status) { // success // update the plan_ids attribute $scope.availability.plan_ids = data.plan_ids; @@ -282,7 +281,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state }); // once the dialog was closed, do things depending on the result modalInstance.result.then(function (res) { - if (res.status == 'success') { + if (res.status === 'success') { $scope.availability = null; } for (const availability of res.availabilities) { @@ -355,7 +354,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('calendar') < 0) { uitour.start(); } - } + }; /* PRIVATE SCOPE */ @@ -380,12 +379,12 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state * * @returns {array} */ - var availabilityPlans = function() { + const availabilityPlans = function () { const plansClassifiedByGroup = []; - const _plans = _.filter(plansPromise, function (p) { return _.include($scope.availability.plan_ids, p.id) }); - for (let group of Array.from(groupsPromise)) { + const _plans = _.filter(plansPromise, function (p) { return _.includes($scope.availability.plan_ids, p.id); }); + for (const group of Array.from(groupsPromise)) { const groupObj = { id: group.id, name: group.name, plans: [] }; - for (let plan of Array.from(_plans)) { + for (const plan of Array.from(_plans)) { if (plan.group_id === group.id) { groupObj.plans.push(plan); } } if (groupObj.plans.length > 0) { @@ -424,9 +423,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state backdrop: 'static', keyboard: false, resolve: { - start() { return start; }, - end() { return end; }, - slots() { return Math.ceil(slots); }, + start () { return start; }, + end () { return end; }, + slots () { return Math.ceil(slots); }, machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], @@ -434,7 +433,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }], groupsPromise: ['Group', function (Group) { return Group.query().$promise; }], slotDurationPromise: ['Setting', function (Setting) { return Setting.get({ name: 'slot_duration' }).$promise; }] - } }); + } + }); // when the modal is closed, we send the slot to the server for saving modalInstance.result.then( function (availability) { @@ -471,10 +471,10 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state $scope.availability.plans = availabilityPlans(); if ($scope.availabilityDom) { - $scope.availabilityDom.classList.remove("fc-selected") + $scope.availabilityDom.classList.remove('fc-selected'); } $scope.availabilityDom = jsEvent.target.closest('.fc-event'); - $scope.availabilityDom.classList.add("fc-selected") + $scope.availabilityDom.classList.add('fc-selected'); // if the user has clicked on the delete event button, delete the event if ($(jsEvent.target).hasClass('remove-event')) { @@ -494,14 +494,13 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state if (event.available_type !== 'event') { element.find('.fc-content').prepend(''); } - if (event.tags.length > 0) { + if (event.tags && event.tags.length > 0) { let html = ''; - for (let tag of Array.from(event.tags)) { + for (const tag of Array.from(event.tags)) { html += `${tag.name} `; } element.find('.fc-title').append(`
${html}`); } - // force return to prevent coffee-script auto-return to return random value (possiblity falsy) }; /** @@ -509,10 +508,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state * @see https://fullcalendar.io/docs/resource_data/loading/ */ const loadingCb = function (isLoading, view) { - if (isLoading) { + if (isLoading && uiCalendarConfig.calendars.calendar) { // we remove existing events when fetching starts to prevent duplicates uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents'); - } }; @@ -520,7 +518,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state * Triggered when the view is changed * @see https://fullcalendar.io/docs/v3/viewRender#v2 */ - const viewRenderCb = function(view, element) { + const viewRenderCb = function (view, element) { // we unselect the current event to keep consistency $scope.availability = null; $scope.availabilityDom = null; @@ -633,7 +631,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui /** * Select/unselect all the machines */ - $scope.toggleAll = function() { + $scope.toggleAll = function () { const count = $scope.selectedMachines.length; $scope.selectedMachines = []; $scope.selectedMachinesBinding = {}; @@ -641,9 +639,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui $scope.machines.forEach(function (machine) { $scope.selectedMachines.push(machine); $scope.selectedMachinesBinding[machine.id] = true; - }) + }); } - } + }; /** * Adds or removes the provided plan from the current slot @@ -661,7 +659,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui /** * Select/unselect all the plans */ - $scope.toggleAllPlans = function() { + $scope.toggleAllPlans = function () { const count = $scope.selectedPlans.length; $scope.selectedPlans = []; $scope.selectedPlansBinding = {}; @@ -669,7 +667,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui plansPromise.forEach(function (plan) { $scope.selectedPlans.push(plan); $scope.selectedPlansBinding[plan.id] = true; - }) + }); } }; @@ -738,7 +736,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui */ $scope.isTypeDivided = function () { return isTypeDivided($scope.availability.available_type); - } + }; /* PRIVATE SCOPE */ @@ -754,7 +752,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui } // when disable is only subscriptions option, reset all selected plans - $scope.$watch('isOnlySubscriptions', function(value) { + $scope.$watch('isOnlySubscriptions', function (value) { if (!value) { $scope.selectedPlans = []; $scope.selectedPlansBinding = {}; @@ -762,14 +760,14 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui }); // group plans by Group - for (let group of groupsPromise.filter(g => !g.disabled)) { - const groupObj = { id: group.id, name: group.name, plans: [] }; - for (let plan of plansPromise.filter(g => !g.disabled)) { - if (plan.group_id === group.id) { groupObj.plans.push(plan); } - } - if (groupObj.plans.length > 0) { - $scope.plansClassifiedByGroup.push(groupObj); - } + for (const group of groupsPromise.filter(g => !g.disabled)) { + const groupObj = { id: group.id, name: group.name, plans: [] }; + for (const plan of plansPromise.filter(g => !g.disabled)) { + if (plan.group_id === group.id) { groupObj.plans.push(plan); } + } + if (groupObj.plans.length > 0) { + $scope.plansClassifiedByGroup.push(groupObj); + } } // When the slot duration changes, we increment the availability to match the value @@ -783,9 +781,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui // When the number of slot changes, we increment the availability to match the value $scope.$watch('slots_nb', function (newValue, oldValue, scope) { - start = moment($scope.start); - start.add($scope.availability.slot_duration * newValue, 'minutes'); - $scope.end = start.toDate(); + start = moment($scope.start); + start.add($scope.availability.slot_duration * newValue, 'minutes'); + $scope.end = start.toDate(); }); // When we configure a machine/space availability, do not let the user change the end time, as the total @@ -802,7 +800,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui $scope.end = moment($scope.start).add(upper, 'minutes').toDate(); $scope.slots_nb = upperSlots; } else { - $scope.slots_nb = slotsCurrentRange; + $scope.slots_nb = slotsCurrentRange; } $scope.availability.end_at = $scope.end; } else { @@ -843,8 +841,8 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui * Test if the provided availability type is divided in slots */ const isTypeDivided = function (type) { - return ((type === 'machines') || (type === 'space')); - } + return ((type === 'machines') || (type === 'space')); + }; /** * Validates that a machine or more was/were selected before continuing to step 3 (adjust time + tags) @@ -890,7 +888,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui $scope.availability.slot_duration = parseInt(slotDurationPromise.setting.value, 10); } $scope.step++; - } + }; /** * Compute the various occurrences of the availability, according to the recurrence settings @@ -922,7 +920,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui $scope.reservableName = ''; switch ($scope.availability.available_type) { case 'machines': - $scope.reservableName = localizedList($scope.selectedMachines) + $scope.reservableName = localizedList($scope.selectedMachines); break; case 'training': $scope.reservableName = `${$scope.selectedTraining.name}`; @@ -931,25 +929,25 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui $scope.reservableName = `${$scope.selectedSpace.name}`; break; default: - $scope.reservableName = `${_t("app.admin.calendar.none")}`; + $scope.reservableName = `${_t('app.admin.calendar.none')}`; } const tags = $scope.tags.filter(function (t) { return $scope.availability.tag_ids.indexOf(t.id) > -1; - }) + }); $scope.tagsName = localizedList(tags); if ($scope.isOnlySubscriptions && $scope.selectedPlans.length > 0) { $scope.plansName = localizedList($scope.selectedPlans); } - } + }; const localizedList = function (items) { - if (items.length === 0) return `${_t("app.admin.calendar.none")}`; + if (items.length === 0) return `${_t('app.admin.calendar.none')}`; const names = items.map(function (i) { return $sce.trustAsHtml(`${i.name}`); }); if (items.length > 1) return names.slice(0, -1).join(', ') + ` ${_t('app.admin.calendar.and')} ` + names[names.length - 1]; return names[0]; - } + }; // !!! MUST BE CALLED AT THE END of the controller return initialize(); @@ -961,7 +959,6 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui */ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$scope', '$uibModalInstance', 'Availability', 'availabilityPromise', 'growl', '_t', function ($scope, $uibModalInstance, Availability, availabilityPromise, growl, _t) { - // is the current slot (to be deleted) recurrent? $scope.isRecurrent = availabilityPromise.is_recurrent; @@ -981,17 +978,17 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s if (res.deleted > 1) { growl.success(_t( 'app.admin.calendar.slots_deleted', - {START: moment(start_at).format('LL LT'), COUNT: res.deleted - 1} + { START: moment(start_at).format('LL LT'), COUNT: res.deleted - 1 } )); } else { growl.success(_t( 'app.admin.calendar.slot_successfully_deleted', - {START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')} + { START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT') } )); } $uibModalInstance.close({ status: 'success', - availabilities: res.details.map(function (d) { return d.availability.id }) + availabilities: res.details.map(function (d) { return d.availability.id; }) }); }, function (res) { @@ -1000,32 +997,30 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s if (data.total > 1) { growl.warning(_t( 'app.admin.calendar.slots_not_deleted', - {TOTAL: data.total, COUNT: data.total - data.deleted} + { TOTAL: data.total, COUNT: data.total - data.deleted } )); } else { growl.error(_t( 'app.admin.calendar.unable_to_delete_the_slot', - {START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')} + { START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT') } )); } $uibModalInstance.close({ status: 'failed', - availabilities: data.details.filter(function (d) { return d.status }).map(function (d) { return d.availability.id }) + availabilities: data.details.filter(function (d) { return d.status; }).map(function (d) { return d.availability.id; }) }); }); - } + }; /** * Cancellation callback */ $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); - } + }; } ]); - - /** * Controller used in the iCalendar (ICS) imports management page */ @@ -1060,8 +1055,8 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale // failed growl.error(_t('app.admin.icalendar.create_error')); console.error(error); - }) - } + }); + }; /** * Return a CSS-like style of the given calendar configuration @@ -1070,11 +1065,11 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale $scope.calendarStyle = function (calendar) { return { 'border-color': calendar.color, - 'color': calendar.text_color, - 'width': calendar.text_hidden ? '50px' : 'auto', - 'height': calendar.text_hidden ? '21px' : 'auto' + color: calendar.text_color, + width: calendar.text_hidden ? '50px' : 'auto', + height: calendar.text_hidden ? '21px' : 'auto' }; - } + }; /** * Delete the given calendar from the database @@ -1107,8 +1102,8 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale } ); } - ) - } + ); + }; /** * Asynchronously re-fetches the events from the given calendar @@ -1125,7 +1120,7 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale growl.error(_t('app.admin.icalendar.sync_failed')); console.error(error); } - ) - } + ); + }; } ]); diff --git a/app/frontend/src/javascript/services/calendar.js b/app/frontend/src/javascript/services/calendar.js index 531ca8b9e..02920a739 100644 --- a/app/frontend/src/javascript/services/calendar.js +++ b/app/frontend/src/javascript/services/calendar.js @@ -28,11 +28,15 @@ Application.Services.factory('CalendarConfig', [() => prev: 'left-single-arrow', next: 'right-single-arrow' }, - timeFormat: { - agenda: 'H:mm', - month: 'H(:mm)' - }, slotLabelFormat: 'H:mm', + views: { + agendaWeek: { + timeFormat: 'H:mm' + }, + month: { + timeFormat: 'H(:mm)' + } + }, allDaySlot: false, defaultView: 'agendaWeek', diff --git a/package.json b/package.json index 7feb31bff..a92e64e4f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "angular-translate-interpolation-messageformat": "2.18", "angular-translate-loader-partial": "2.18", "angular-ui-bootstrap": "1.2.5", - "angular-ui-calendar": "https://github.com/sophilabs-forks/ui-calendar.git#master", + "angular-ui-calendar": "^1.0.2", "angular-ui-codemirror": "^0.3.0", "angular-ui-tour": "https://github.com/Ross-Byrne/angular-ui-tour.git#master", "angular-unsavedchanges": "0.2", @@ -72,7 +72,7 @@ "codemirror": "^4.8.0", "d3": "3.5", "elasticsearch-browser": "3.1", - "fullcalendar": "3.3.1", + "fullcalendar": "3.10.2", "holderjs": "2.6", "jasny-bootstrap": "3.1", "jquery": ">=3.5.0", diff --git a/yarn.lock b/yarn.lock index 3b3fc588e..b8a8e88c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1346,11 +1346,10 @@ angular-ui-bootstrap@1.2.5: resolved "https://registry.yarnpkg.com/angular-ui-bootstrap/-/angular-ui-bootstrap-1.2.5.tgz#b0c1eff0bf3b7a65668984a1b81820a90dc60995" integrity sha1-sMHv8L87emVmiYShuBggqQ3GCZU= -"angular-ui-calendar@https://github.com/sophilabs-forks/ui-calendar.git#master": +angular-ui-calendar@^1.0.2: version "1.0.2" - resolved "https://github.com/sophilabs-forks/ui-calendar.git#21c4d834cb82f6f4f4c6417669970ff02e193c27" - dependencies: - fullcalendar "~3.3.x" + resolved "https://registry.yarnpkg.com/angular-ui-calendar/-/angular-ui-calendar-1.0.2.tgz#fa271057425572efaadf6660243cc33cb99f33c1" + integrity sha1-+icQV0JVcu+q32ZgJDzDPLmfM8E= angular-ui-codemirror@^0.3.0: version "0.3.0" @@ -3897,13 +3896,10 @@ fstream@^1.0.0, fstream@^1.0.12: mkdirp ">=0.5 0" rimraf "2" -fullcalendar@3.3.1, fullcalendar@~3.3.x: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fullcalendar/-/fullcalendar-3.3.1.tgz#e8d458d64b7dcb5ba70ce890dda2488b8911539a" - integrity sha1-6NRY1kt9y1unDOiQ3aJIi4kRU5o= - dependencies: - jquery "2 - 3" - moment "^2.9.0" +fullcalendar@3.10.2: + version "3.10.2" + resolved "https://registry.yarnpkg.com/fullcalendar/-/fullcalendar-3.10.2.tgz#9b1ba84bb02803621b761d1bba91a4f18affafb7" + integrity sha512-YWZaHdp8ZLBqhPz615PoXdA49ymsBTUF+MGDM6H3vyz71Pv/ZW9Pm9/Mj3x6n822k6bs2txFO7muRTSvBhsqKg== function-bind@^1.1.1: version "1.1.1" @@ -4908,7 +4904,7 @@ jquery-ujs@^1.2.2: dependencies: jquery ">=1.8.0" -"jquery@2 - 3", jquery@>=1.8.0, jquery@>=3.5.0: +jquery@>=1.8.0, jquery@>=3.5.0: version "3.5.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== @@ -5573,11 +5569,6 @@ moment@2.22, "moment@>=2.8.0 <3.0.0": resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== -moment@^2.9.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.0.tgz#fcbef955844d91deb55438613ddcec56e86a3425" - integrity sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA== - move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"