Skip to content

feat: New scheduler implementation #4775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app/components/schedule.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<FullCalendar
@events={{this.events}}
@aspectRatio={{if this.device.isMobile 0.7 1.8}}
@editable={{@editable}}
@droppable={{@droppable}}
@resources={{this.resources}}
@groupByResource={{true}}
@resourceLabelText="Rooms"
@header={{this.header}}
@timezone={{this.timezone}}
@views={{this.views}}
@viewName="agendaDay"
@defaultView="timelineDay"
@validRange={{this.validRange}}
@now={{moment-format (now) "YYYY-MM-DD"}}
@scrollTime="00:00"
@minTime={{this.minTime}}
@slotDuration="00:10:00"
@slotLabelFormat="HH:mm"
@defaultTimedEventDuration={{@defaultTimedEventDuration}}
@drop={{@drop}}
@eventResize={{@eventResize}}
@eventDrop={{@eventDrop}}
@eventRender={{@eventRender}} />
55 changes: 55 additions & 0 deletions app/components/schedule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Component from '@glimmer/component';

export default class Schedule extends Component {
header = {
left : 'today,prev,next',
center : 'title',
right : 'timelineDay,agendaDay,timelineThreeDays,agendaWeek'
}

get validRange() {
const { event } = this.args;
return {
start : event.startsAt.format('YYYY-MM-DD'),
end : event.endsAt.clone().add(1, 'day').format('YYYY-MM-DD')
};
}

get views() {
const { event } = this.args;
const durationDays = event.endsAt.diff(event.startsAt, 'days') + 1;
return {
timelineThreeDays: {
type : 'agenda',
duration : { days: durationDays },
buttonText : `${durationDays} day`
}
};
}

get timezone() {
return this.args.event.timezone;
}

get minTime() {
return this.args.event.startsAt.tz(this.timezone).format('HH:mm:ss');
}

get resources() {
return this.args.rooms.map(room => ({ id: room.id, title: room.name }));
}

get events() {
return this.args.sessions.map(session => {
const speakerNames = session.speakers.map(speaker => speaker.name).join(', ');
return {
title : `${session.title} | ${speakerNames}`,
start : session.startsAt.tz(this.timezone).format(),
end : session.endsAt.tz(this.timezone).format(),
resourceId : session.microlocation.get('id'),
color : session.track.get('color'),
serverId : session.get('id') // id of the session on BE
};
});
}
}
48 changes: 25 additions & 23 deletions app/controllers/events/view/scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { computed, action } from '@ember/object';
import moment from 'moment';
import { tracked } from '@glimmer/tracking';
import { matchPropertyIn } from 'open-event-frontend/utils/text';
import $ from 'jquery';

export default class extends Controller {
@computed('model.eventDetails.schedulePublishedOn')
@tracked filter = '';
isLoading = false;

@computed('model.event.schedulePublishedOn')
get isSchedulePublished() {
const schedulePublishStatus = this.model.eventDetails.schedulePublishedOn;
const schedulePublishStatus = this.model.event.schedulePublishedOn;
if (schedulePublishStatus != null) {
return schedulePublishStatus.toISOString() !== moment(0).toISOString();
}
Expand All @@ -23,27 +27,25 @@ export default class extends Controller {
|| session.speakers.map(speaker => speaker.name).join(',').toLowerCase().includes(this.filter.toLowerCase()));
}

@tracked filter = '';
isLoading = false;
header = {
left : 'today prev,next',
center : 'title',
right : 'timelineDay,timelineThreeDays,agendaWeek,month'
}
async updateSession(start, end, microlocationId, sessionId) {
if (!start !== !end) {
// If either one of start or end is missing, then return and throw an error
// Either both should be present or none

view = {
timelineThreeDays: {
type : 'timeline',
duration : { days: 3 }
this.notify.error('Start time or End time not present');
return;
}

if (start && end) {
start = moment.tz(start.format(), this.model.timezone).toISOString();
end = moment.tz(end.format(), this.model.timezone).toISOString();
}
}

updateSession(start, end, microlocationId, sessionId) {
const payload = {
data: {
attributes: {
'starts-at' : start ? start.toISOString() : null,
'ends-at' : end ? end.toISOString() : null
'starts-at' : start,
'ends-at' : end
},
relationships: {
microlocation: {
Expand Down Expand Up @@ -74,19 +76,19 @@ export default class extends Controller {
});
}

unscheduleSession(session) {
window.$('.full-calendar').fullCalendar('removeEvents', session._id);
this.updateSession(null, null, session.resourceId, session.serverId);
async unscheduleSession(session) {
$('.full-calendar').fullCalendar('removeEvents', session._id);
await this.updateSession(null, null, session.resourceId, session.serverId);
this.target.send('refresh');
}

@action
drop(date, jsEvent, ui, resourceId) {
async drop(date, jsEvent, ui, resourceId) {
const start = date;
const duration = this.model.defaultDuration.split(':');
const end = start.clone().add(duration[0], 'hours').add(duration[1], 'minutes');
this.updateSession(start, end, resourceId, window.$(ui.helper).data('serverId'));
window.$(ui.helper).remove();
await this.updateSession(start, end, resourceId, $(ui.helper).data('serverId'));
this.target.send('refresh');
}

@action
Expand Down
175 changes: 24 additions & 151 deletions app/routes/events/view/scheduler.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,5 @@
import Route from '@ember/routing/route';
import $ from 'jquery';

// TODO(Areeb): Remove once upgraded
// Workaround for https://github.com/fossasia/open-event-frontend/issues/4729
function patchFullCalendar() {
if (!window?.FullCalendar) {return}
window.FullCalendar.EventRenderer.prototype.renderFgSegEls = function(segs, disableResizing) {
const _this = this;
if (disableResizing === void 0) { disableResizing = false }
const hasEventRenderHandlers = this.view.hasPublicHandlers('eventRender');
let html = '';
const renderedSegs = [];
let i;
if (segs.length) {
// build a large concatenation of event segment HTML
for (i = 0; i < segs.length; i++) {
this.beforeFgSegHtml(segs[i]);
html += this.fgSegHtml(segs[i], disableResizing);
}
// Grab individual elements from the combined HTML string. Use each as the default rendering.
// Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false.
$(html).each(function(i, node) {
const seg = segs[i];
let el = $(node);
// Areeb: seg is undefined for single day events as i > seg.length due to some logical error
if (seg && hasEventRenderHandlers) { // Areeb: Added `seg && `
el = _this.filterEventRenderEl(seg.footprint, el);
}
if (seg && el) { // Areeb: Added `seg && `
el.data('fc-seg', seg); // used by handlers
seg.el = el;
renderedSegs.push(seg);
}
});
}
return renderedSegs;
};
}
import { patchFullCalendar } from 'open-event-frontend/utils/patches/fullcalendar';

export default Route.extend({
titleToken() {
Expand All @@ -49,145 +12,55 @@ export default Route.extend({
},
async model() {
patchFullCalendar();
const unscheduledFilterOptions = [
const filterOptions = [
{
and: [
or: [
{
or: [
{
name : 'starts-at',
op : 'eq',
val : null
},
{
name : 'ends-at',
op : 'eq',
val : null
}
]
name : 'state',
op : 'eq',
val : 'accepted'
},
{
or: [
{
name : 'state',
op : 'eq',
val : 'accepted'
},
{
name : 'state',
op : 'eq',
val : 'confirmed'
}
]
}
]
}
];

const scheduledFilterOptions = [
{
and: [
{
name : 'starts-at',
op : 'ne',
val : null
},
{
name : 'ends-at',
op : 'ne',
val : null
},
{
or: [
{
name : 'state',
op : 'eq',
val : 'accepted'
},
{
name : 'state',
op : 'eq',
val : 'confirmed'
}
]
name : 'state',
op : 'eq',
val : 'confirmed'
}
]
}
];

const eventDetails = this.modelFor('events.view');
const { timezone } = eventDetails;

const validRange = {
start : eventDetails.startsAt.format('YYYY-MM-DD'),
end : eventDetails.endsAt.clone().add(1, 'day').format('YYYY-MM-DD')
};

const durationDays = eventDetails.endsAt.diff(eventDetails.startsAt, 'days') + 1;
const views = {
timelineThreeDays: {
type : 'agenda',
duration : { days: durationDays },
buttonText : `${durationDays} day`
}
};

const header = {
left : 'today,prev,next',
center : 'title',
right : 'agendaDay,timelineThreeDays,agendaWeek'
};

const scheduledSessions = await eventDetails.query('sessions', {
const sessions = await eventDetails.query('sessions', {
include : 'speakers,microlocation,track',
filter : scheduledFilterOptions,
filter : filterOptions,
'page[size]' : 0
});
const scheduled = sessions.toArray().filter(session => session.startsAt && session.endsAt);
const unscheduled = sessions.toArray().filter(session => !session.startsAt || !session.endsAt);

const scheduled = []; // to convert sessions data to fullcalendar's requirements
scheduledSessions.forEach(function(session) {
const speakerNames = [];
session.speakers.forEach(function(speaker) {
speakerNames.push(speaker.name);
});
scheduled.push({
title : `${session.title} | ${speakerNames.join(', ')}`,
start : session.startsAt.format(),
end : session.endsAt.format(),
resourceId : session.microlocation.get('id'),
color : session.track.get('color'),
serverId : session.get('id') // id of the session on BE
sessions.forEach(session => {
session.speakers.forEach(() => {
// Nothing to see here, just avoiding a stupid ember bug
// https://github.com/emberjs/ember.js/issues/18613#issuecomment-674454524
});
});

const unscheduledSessions = await eventDetails.query('sessions', {
include : 'speakers,track',
filter : unscheduledFilterOptions,
'page[size]' : 0
});

const microlocations = await eventDetails.query('microlocations', {});
const resources = [];
microlocations.forEach(function(element) {
resources.push({ id: element.id, title: element.name });
});

/*
The start hour of the start day is considered the start hour for remaining days as well.
The end hour of the last day is considered the end hour for remaining days as well.
*/

return {
header,
timezone : 'UTC',
defaultView : 'agendaDay',
events : scheduled,
eventDetails,
resources,
unscheduled : unscheduledSessions,
minTime : eventDetails.startsAt.format('HH:mm:ss'),
maxTime : eventDetails.endsAt.format('HH:mm:ss'),
validRange,
views,
event : eventDetails,
sessions,
scheduled,
unscheduled,
microlocations,
timezone,
defaultDuration : '01:00'
};
}
Expand Down
Loading