Fix breaking changes of fullcalendar 5

pull/8529/head
Oliver Günther 4 years ago
parent 645b3319a6
commit e1e07609c8
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 19
      frontend/angular.json
  2. 4
      frontend/src/app/helpers/angular/until-destroyed.mixin.ts
  3. 2
      frontend/src/app/init-vendors.ts
  4. 2
      frontend/src/app/modules/bim/bcf/openproject-bcf.module.ts
  5. 99
      frontend/src/app/modules/calendar/te-calendar/te-calendar.component.ts
  6. 20
      frontend/src/app/modules/calendar/te-calendar/te-calendar.template.html
  7. 85
      frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts
  8. 11
      frontend/src/app/modules/calendar/wp-calendar/wp-calendar.template.html
  9. 2
      frontend/src/app/modules/work_packages/openproject-work-packages.module.ts
  10. 2
      frontend/src/app/modules/work_packages/routing/wp-view-base/view-services/wp-view-hierarchy.service.ts
  11. 2
      frontend/src/main.ts

@ -12,6 +12,23 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": [
"core-app/init-vendors",
"core-app/init-globals",
"dom-plane",
"pako",
"moment-timezone",
"observable-array",
"dragula",
"jquery",
"contra/emitter",
"crossvent",
"chart.js",
"create-point-cb",
"core-vendor/enjoyhint",
"tablesorter",
"chartjs-plugin-datalabels"
],
"preserveSymlinks": true,
"aot": true,
"deployUrl": "/assets/frontend/",
@ -29,7 +46,7 @@
"node_modules/jquery-ui/themes/base/core.css",
"node_modules/jquery-ui/themes/base/datepicker.css",
"node_modules/jquery-ui/themes/base/dialog.css",
"node_modules/@fullcalendar/core/main.css",
"node_modules/@fullcalendar/common/main.css",
"node_modules/@fullcalendar/daygrid/main.css",
"node_modules/@fullcalendar/timegrid/main.css",
"node_modules/flatpickr/dist/flatpickr.min.css"

@ -1,5 +1,5 @@
import {OnDestroyMixin, untilComponentDestroyed} from "@w11k/ngx-componentdestroyed";
import {OnDestroy} from "@angular/core";
import {Directive, OnDestroy} from "@angular/core";
import {Observable} from "rxjs";
/**
@ -8,7 +8,7 @@ import {Observable} from "rxjs";
*
* Use for rxjs with .pipe(this.untilDestroyed)
*/
// TODO: Add Angular decorator.
@Directive()
export class UntilDestroyedMixin extends OnDestroyMixin implements OnDestroy {
public componentDestroyed = false;

@ -78,4 +78,4 @@ require('urijs/src/URITemplate');
require("expose-loader?I18n!core-vendor/i18n");
// Localization for fullcalendar
require("@fullcalendar/core/locales-all.min");
require("@fullcalendar/core/locales-all");

@ -97,7 +97,7 @@ export class OpenprojectBcfModule {
// The static property prevents running the function
// multiple times. This happens e.g. when the module is included
// into a plugin's module.
public static bootstrap(injector:Injector) {
public static bootstrap(injector:Injector):void {
if (this.bootstrapCalled) {
return;
}

@ -6,7 +6,6 @@ import {
EventEmitter,
Injector,
Input,
OnInit,
Output,
SecurityContext,
ViewChild,
@ -20,9 +19,7 @@ import {StateService} from "@uirouter/core";
import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {DomSanitizer} from "@angular/platform-browser";
import timeGrid from '@fullcalendar/timegrid';
import {Duration, EventApi, EventInput, View} from '@fullcalendar/core';
import {EventSourceError} from '@fullcalendar/core/structs/event-source';
import {ToolbarInput} from '@fullcalendar/core/types/input-types';
import {CalendarOptions, Duration, EventApi, EventInput} from '@fullcalendar/core';
import {ConfigurationService} from "core-app/modules/common/config/configuration.service";
import {FilterOperator} from "core-components/api/api-v3/api-v3-filter-builder";
import {TimeEntryResource} from "core-app/modules/hal/resources/time-entry-resource";
@ -40,7 +37,6 @@ import {APIV3Service} from "core-app/modules/apiv3/api-v3.service";
interface CalendarViewEvent {
el:HTMLElement;
event:EventApi;
jsEvent:MouseEvent;
}
interface CalendarMoveEvent {
@ -49,8 +45,6 @@ interface CalendarMoveEvent {
oldEvent:EventApi;
delta:Duration;
revert:() => void;
jsEvent:Event;
view:View;
}
// An array of all the days that are displayed. The zero index represents Monday.
@ -74,13 +68,15 @@ const ADD_ENTRY_PROHIBITED_CLASS_NAME = '-prohibited';
HalResourceEditingService
]
})
export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
export class TimeEntryCalendarComponent implements AfterViewInit {
@ViewChild(FullCalendarComponent) ucCalendar:FullCalendarComponent;
@Input() projectIdentifier:string;
@Input() static:boolean = false;
@Input() set displayedDays(days:DisplayedDays) {
this.setHiddenDays(days);
}
@Output() entries = new EventEmitter<CollectionResource<TimeEntryResource>>();
// Not used by the calendar but rather is the maximum/minimum of the graph.
@ -89,24 +85,7 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
public labelIntervalHours = 2;
public scaleRatio = 1;
public calendarPlugins = [timeGrid, interactionPlugin];
public calendarEvents:Function;
public calendarHeader:ToolbarInput|boolean = {
right: '',
center: 'title',
left: 'prev,next today'
};
public calendarSlotLabelFormat = (info:any) => (this.maxHour - info.date.hour) / this.scaleRatio;
public calendarSlotLabelInterval = `${this.labelIntervalHours}:00:00`;
public calendarContentHeight = 605;
public calendarAllDaySlot = false;
public calendarDisplayEventTime = false;
public calendarSlotEventOverlap = false;
public calendarEditable = false;
public calendarMinTime = `${this.minHour - 1}:00:00`;
public calendarMaxTime = `${this.maxHour}:00:00`;
public calendarEventOverlap = (stillEvent:any) => !stillEvent.classNames.includes(TIME_ENTRY_CLASS_NAME);
protected memoizedTimeEntries:{ start:Date, end:Date, entries:Promise<CollectionResource<TimeEntryResource>> };
public memoizedCreateAllowed:boolean = false;
public hiddenDays:number[] = [];
@ -115,6 +94,31 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
logTime: this.i18n.t('js.button_log_time')
};
calendarOptions:CalendarOptions = {
editable: false,
locale: this.i18n.locale,
fixedWeekCount: false,
headerToolbar: {
right: '',
center: 'title',
left: 'prev,next today'
},
initialView: 'timeGridWeek',
firstDay: this.configuration.startOfWeek(),
hiddenDays: [],
contentHeight: 605,
slotEventOverlap: false,
slotLabelInterval: `${this.labelIntervalHours}:00:00`,
slotLabelFormat: (info:any) => ((this.maxHour - info.date.hour) / this.scaleRatio).toString(),
allDaySlot: false,
displayEventTime: false,
slotMinTime: `${this.minHour - 1}:00:00`,
slotMaxTime: `${this.maxHour}:00:00`,
events: this.calendarEventsFunction.bind(this),
eventOverlap: (stillEvent:any) => !stillEvent.classNames.includes(TIME_ENTRY_CLASS_NAME),
plugins: [timeGrid, interactionPlugin]
};
constructor(readonly states:States,
readonly apiV3Service:APIV3Service,
readonly $state:StateService,
@ -131,18 +135,14 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
private browserDetector:BrowserDetector) {
}
ngOnInit() {
this.initializeCalendar();
}
ngAfterViewInit() {
// The full-calendar component's outputs do not seem to work
// see: https://github.com/fullcalendar/fullcalendar-angular/issues/228#issuecomment-523505044
// Therefore, setting the outputs via the underlying API
this.ucCalendar.getApi().setOption('eventRender', (event:CalendarViewEvent) => {
this.ucCalendar.getApi().setOption('eventDidMount', (event:CalendarViewEvent) => {
this.alterEventEntry(event);
});
this.ucCalendar.getApi().setOption('eventDestroy', (event:CalendarViewEvent) => {
this.ucCalendar.getApi().setOption('eventWillUnmount', (event:CalendarViewEvent) => {
this.beforeEventRemove(event);
});
this.ucCalendar.getApi().setOption('eventClick', (event:CalendarViewEvent) => {
@ -155,7 +155,7 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
public calendarEventsFunction(fetchInfo:{ start:Date, end:Date },
successCallback:(events:EventInput[]) => void,
failureCallback:(error:EventSourceError) => void):void|PromiseLike<EventInput[]> {
failureCallback:(error:unknown) => void):void|PromiseLike<EventInput[]> {
this.fetchTimeEntries(fetchInfo.start, fetchInfo.end)
.then((collection) => {
@ -212,7 +212,8 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
// But it will trigger repainting the calendar.
// Weirdly, this.ucCalendar.getApi().rerender() does not.
this.ucCalendar.getApi().setOption('slotLabelFormat', (info:any) => {
return (this.maxHour - info.date.hour) / this.scaleRatio;
let val = (this.maxHour - info.date.hour) / this.scaleRatio;
return val.toString();
});
}
}
@ -330,30 +331,6 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
['user_id', '=', ['me']] as [string, FilterOperator, [string]]];
}
private initializeCalendar() {
this.calendarEvents = this.calendarEventsFunction.bind(this);
}
public get calendarEventLimit() {
return false;
}
public get calendarLocale() {
return this.i18n.locale;
}
public get calendarFixedWeekCount() {
return false;
}
public get calendarDefaultView() {
return 'timeGridWeek';
}
public get calendarFirstDay() {
return this.configuration.startOfWeek();
}
private get calendarElement() {
return jQuery(this.element.nativeElement).find('.fc-view-container');
}
@ -603,12 +580,16 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit {
}
protected setHiddenDays(displayedDays:DisplayedDays) {
this.hiddenDays = Array.from(displayedDays, (value, index) => {
let hiddenDays:number[] = Array
.from(displayedDays, (value, index) => {
if (!value) {
return (index + 1) % 7;
} else {
return null;
}
}).filter((value) => value !== null) as number[];
})
.filter((value) => value !== null) as number[];
this.calendarOptions = { ...this.calendarOptions, hiddenDays };
}
}

@ -14,24 +14,6 @@
</button>
<full-calendar #ucCalendar
[editable]="calendarEditable"
[eventLimit]="calendarEventLimit"
[locale]="calendarLocale"
[fixedWeekCount]="calendarFixedWeekCount"
[header]="calendarHeader"
[defaultView]="calendarDefaultView"
[firstDay]="calendarFirstDay"
[hiddenDays]="hiddenDays"
[contentHeight]="calendarContentHeight"
[slotEventOverlap]="calendarSlotEventOverlap"
[slotLabelInterval]="calendarSlotLabelInterval"
[slotLabelFormat]="calendarSlotLabelFormat"
[allDaySlot]="calendarAllDaySlot"
[displayEventTime]="calendarDisplayEventTime"
[minTime]="calendarMinTime"
[maxTime]="calendarMaxTime"
[events]="calendarEvents"
[eventOverlap]="calendarEventOverlap"
[plugins]="calendarPlugins">
[options]="calendarOptions">
</full-calendar>
</div>

@ -14,10 +14,9 @@ import {DomSanitizer} from "@angular/platform-browser";
import {WorkPackagesListChecksumService} from "core-components/wp-list/wp-list-checksum.service";
import {OpTitleService} from "core-components/html/op-title.service";
import dayGridPlugin from '@fullcalendar/daygrid';
import {EventApi, EventInput} from '@fullcalendar/core';
import {EventSourceError} from '@fullcalendar/core/structs/event-source';
import {CalendarOptions, EventApi, EventInput} from '@fullcalendar/core';
import {take} from 'rxjs/operators';
import {ToolbarInput} from '@fullcalendar/core/types/input-types';
import {ToolbarInput} from '@fullcalendar/common';
import {ConfigurationService} from "core-app/modules/common/config/configuration.service";
import {UntilDestroyedMixin} from "core-app/helpers/angular/until-destroyed.mixin";
import {SchemaCacheService} from "core-components/schemas/schema-cache.service";
@ -25,7 +24,6 @@ import {SchemaCacheService} from "core-components/schemas/schema-cache.service";
interface CalendarViewEvent {
el:HTMLElement;
event:EventApi;
jsEvent:MouseEvent;
}
@Component({
@ -41,13 +39,25 @@ export class WorkPackagesCalendarController extends UntilDestroyedMixin implemen
public tooManyResultsText:string|null;
public calendarPlugins = [dayGridPlugin];
public calendarHeight:Function;
public calendarEvents:Function;
public calendarHeader:ToolbarInput|boolean;
private alreadyLoaded = false;
calendarOptions:CalendarOptions = {
editable: false,
locale: this.i18n.locale,
fixedWeekCount: false,
height: undefined,
initialView: (() => {
if (this.static) {
return 'dayGridWeek';
} else {
return undefined;
}
})(),
firstDay: this.configuration.startOfWeek(),
events: this.calendarEventsFunction.bind(this),
plugins: [dayGridPlugin],
};
constructor(readonly states:States,
readonly $state:StateService,
readonly wpTableFilters:WorkPackageViewFiltersService,
@ -69,15 +79,15 @@ export class WorkPackagesCalendarController extends UntilDestroyedMixin implemen
this.querySpace.stopAllSubscriptions.next();
this.setupWorkPackagesListener();
this.initializeCalendar();
}
ngAfterViewInit() {
this.initializeCalendar();
// The full-calendar component's outputs do not seem to work
// see: https://github.com/fullcalendar/fullcalendar-angular/issues/228#issuecomment-523505044
// Therefore, setting the outputs via the underlying API
this.ucCalendar.getApi().setOption('eventRender', (event:CalendarViewEvent) => {
this.ucCalendar.getApi().setOption('eventDidMount', (event:CalendarViewEvent) => {
this.addTooltip(event);
});
this.ucCalendar.getApi().setOption('eventClick', (event:CalendarViewEvent) => {
@ -87,7 +97,7 @@ export class WorkPackagesCalendarController extends UntilDestroyedMixin implemen
public calendarEventsFunction(fetchInfo:{ start:Date, end:Date, timeZone:string },
successCallback:(events:EventInput[]) => void,
failureCallback:(error:EventSourceError) => void):void|PromiseLike<EventInput[]> {
failureCallback:(error:any) => void):void|PromiseLike<EventInput[]> {
if (this.alreadyLoaded) {
this.alreadyLoaded = false;
let events = this.updateResults(this.querySpace.results.value!);
@ -106,9 +116,11 @@ export class WorkPackagesCalendarController extends UntilDestroyedMixin implemen
}
private initializeCalendar() {
this.calendarEvents = this.calendarEventsFunction.bind(this);
this.setCalendarHeight();
this.setCalendarHeader();
this.calendarOptions = {
...this.calendarOptions,
height: this.calendarHeight(),
headerToolbar: this.buildHeader()
};
}
public updateTimeframe(fetchInfo:{ start:Date, end:Date, timeZone:string }) {
@ -170,42 +182,12 @@ export class WorkPackagesCalendarController extends UntilDestroyedMixin implemen
{ workPackageId: workPackage.id },
{ inherit: false });
}
public get calendarEditable() {
return false;
}
public get calendarEventLimit() {
return false;
}
public get calendarLocale() {
return this.i18n.locale;
}
public get calendarFixedWeekCount() {
return false;
}
public get calendarDefaultView() {
if (this.static) {
return 'dayGridWeek';
} else {
return null;
}
}
public get calendarFirstDay() {
return this.configuration.startOfWeek();
}
private get calendarElement() {
return jQuery(this.element.nativeElement).find('.fc-view-container');
}
private setCalendarHeight() {
private calendarHeight():number {
if (this.static) {
this.calendarHeight = () => {
let heightElement = jQuery(this.element.nativeElement);
while (!heightElement.height() && heightElement.parent()) {
@ -216,20 +198,17 @@ export class WorkPackagesCalendarController extends UntilDestroyedMixin implemen
let topOfHeightElement = heightElement.position().top;
return heightElement.height()! - (topOfCalendar - topOfHeightElement);
};
} else {
this.calendarHeight = () => {
// -12 for the bottom padding
return jQuery(window).height()! - this.calendarElement.offset()!.top - 12;
};
}
}
public setCalendarHeader() {
public buildHeader() {
if (this.static) {
this.calendarHeader = false;
return false;
} else {
this.calendarHeader = {
return {
right: 'dayGridMonth,dayGridWeek',
center: 'title',
left: 'prev,next today'

@ -3,16 +3,7 @@
[attr.data-indicator-name]="'table'"
style="position: relative">
<full-calendar #ucCalendar
[editable]="calendarEditable"
[eventLimit]="calendarEventLimit"
[locale]="calendarLocale"
[fixedWeekCount]="calendarFixedWeekCount"
[height]="calendarHeight"
[header]="calendarHeader"
[defaultView]="calendarDefaultView"
[firstDay]="calendarFirstDay"
[events]="calendarEvents"
[plugins]="calendarPlugins">
[options]="calendarOptions">
</full-calendar>
<div
*ngIf="static"

@ -425,7 +425,7 @@ export class OpenprojectWorkPackagesModule {
// The static property prevents running the function
// multiple times. This happens e.g. when the module is included
// into a plugin's module.
public static bootstrapAttributeGroups(injector:Injector) {
public static bootstrapAttributeGroups(injector:Injector):void {
if (this.bootstrapAttributeGroupsCalled) {
return;
}

@ -38,7 +38,7 @@ export class WorkPackageViewHierarchiesService extends WorkPackageQueryStateServ
}
public setEnabled(active:boolean = true) {
const state = { collapsed: {}, ...this.current, isVisible: active, last: null };
const state = { ...this.current, isVisible: active, last: null };
this.update(state);
}

@ -24,7 +24,7 @@ __webpack_public_path__ = window.appBasePath + ASSET_BASE_PATH;
window.ErrorReporter = new SentryReporter();
require('core-app/init-vendors');
require('./app/init-globals');
require('core-app/init-globals');
const meta = jQuery('meta[name=openproject_initializer]');
I18n.locale = meta.data('defaultLocale');

Loading…
Cancel
Save