fix(quickschedule): Several UI fixes

Summary:
Fixes a number of smaller UI issues. Fixes T6173, T6207
Full list of changes:
 - Ensure time slots / days display in order
 - Allow time blocks of any size to be set on half hour intervals
 - Don't reset calendar when changing event length
 - Expand calendar on window resize
 - Change window title from "Electron"
 - Add delete button to cancel individual calendar events
 - Move event details box to the left side
 - Prevent addition of duplicate time slots

Test Plan: manual

Reviewers: bengotow

Reviewed By: bengotow

Maniphest Tasks: T6173, T6207

Differential Revision: https://phab.nylas.com/D2333
This commit is contained in:
Drew Regitsky 2015-12-09 16:41:30 -08:00
parent 2dc43d141b
commit a3e986d94a
3 changed files with 52 additions and 17 deletions

View file

@ -48,8 +48,14 @@
return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
} }
function resizeCalendar() {
$('#calendar').fullCalendar('option', 'height', $(window).height()-55);
}
var availableTimes = []; var availableTimes = [];
var eventSlots = {};
var meetingLength = 0.5; var meetingLength = 0.5;
var nextId = 0;
function resetCalendar(opts,firstCall) { function resetCalendar(opts,firstCall) {
if(!firstCall) $('#calendar').fullCalendar("destroy"); if(!firstCall) $('#calendar').fullCalendar("destroy");
if(!opts) opts = {}; if(!opts) opts = {};
@ -61,7 +67,20 @@
right: 'today' right: 'today'
}, },
defaultView: 'agendaWeek', defaultView: 'agendaWeek',
handleWindowResize: false, handleWindowResize: true,
windowResize: function(){
resizeCalendar();
},
eventRender: function(event, element) {
if(event.id === "available") {
element.append( "<span class='closeon'></span>" );
element.find(".closeon").click(function() {
$('#calendar').fullCalendar('removeEvents',function(e){return e===event});
availableTimes = availableTimes.filter(function(e){return e.ext_id !== event.ext_id});
delete eventSlots[event.start.format() + event.end.format()];
});
}
},
aspectRatio: 1.3, aspectRatio: 1.3,
timezone: 'local', timezone: 'local',
selectable: true, selectable: true,
@ -73,18 +92,22 @@
var new_events = []; var new_events = [];
//increment through selected time range, in blocks of size `meetingLength` //increment through selected time range, in blocks of size `meetingLength`
while(start.isBefore(end)) { while(start.isBefore(end)) {
new_events.push({ e = {
title: "Available", title: "Available",
start: moment(start), start: moment(start),
end: moment(start).add(meetingLength, "hours"), end: moment(start).add(meetingLength, "hours"),
color:"#8CA", color:"#8CA",
id: "available" id: "available",
}); ext_id: nextId++
};
if(!eventSlots[e.start.format() + e.end.format()])
new_events.push(e);
start.add(meetingLength, "hours"); start.add(meetingLength, "hours");
} }
//add the events to the calendar and to `availableTimes` //add the events to the calendar and to `availableTimes`
new_events.forEach(function(e){ new_events.forEach(function(e){
availableTimes.push(e); availableTimes.push(e);
eventSlots[e.start.format() + e.end.format()] = true;
$('#calendar').fullCalendar('renderEvent', e, true); $('#calendar').fullCalendar('renderEvent', e, true);
}); });
$('#calendar').fullCalendar('unselect'); $('#calendar').fullCalendar('unselect');
@ -112,6 +135,7 @@
defaults[key] = opts[key]; defaults[key] = opts[key];
} }
$('#calendar').fullCalendar(defaults); $('#calendar').fullCalendar(defaults);
resizeCalendar();
} }
$(document).ready(function() { $(document).ready(function() {
@ -125,16 +149,12 @@
$("#reset").click(function(e){ $("#reset").click(function(e){
$('#calendar').fullCalendar('removeEvents', "available"); $('#calendar').fullCalendar('removeEvents', "available");
availableTimes = []; availableTimes = [];
eventSlots = {};
}); });
//handle meeting length dropdown //handle meeting length dropdown
$("#meeting-length").change(function(e){ $("#meeting-length").change(function(e){
meetingLength = parseFloat($(this).val()); meetingLength = parseFloat($(this).val());
$('#calendar').fullCalendar('removeEvents', "available");
availableTimes = [];
resetCalendar({
snapDuration: {hours: meetingLength}
});
}); });
//handle done button click //handle done button click
@ -155,7 +175,7 @@
" ("+moment().tz(moment.tz.guess()).format("z")+")", //local time zone abbrev " ("+moment().tz(moment.tz.guess()).format("z")+")", //local time zone abbrev
serverKey: randomId(128) //string to access this time slot on the server serverKey: randomId(128) //string to access this time slot on the server
} }
}) }).sort(function(a,b) { return a.start - b.start })
}); });
window.close(); window.close();
}); });
@ -168,8 +188,8 @@
#calendar { #calendar {
position: fixed; position: fixed;
left: 5px; left: 205px;
right: 205px; right: 5px;
top: 50px; top: 50px;
bottom: 5px; bottom: 5px;
} }
@ -180,7 +200,7 @@
#options { #options {
position: fixed; position: fixed;
width: 200px; width: 200px;
right: 0; left: 0;
top: 45px; top: 45px;
bottom: 0; bottom: 0;
padding: 5px; padding: 5px;
@ -208,6 +228,21 @@
height: 150px; height: 150px;
} }
.closeon {
display: none;
color: #000;
position: absolute;
right: 0;
top: 0;
z-index: 100;
cursor: pointer;
}
.fc-event:hover .closeon {
display: block;
}
.fc-event:hover .closeon:hover {
color: #800;
}
</style> </style>
</head> </head>
@ -241,7 +276,7 @@
<div class="form-group"> <div class="form-group">
<button class="btn btn-default" id="reset">Reset</button> <button class="btn btn-default" id="reset">Reset</button>
<button class="btn btn-default" id="done">Done</button> <button class="btn btn-primary" id="done">Done</button>
</div> </div>
<p> Use this tool to offer options for scheduling a meeting or event. Click and drag in the <p> Use this tool to offer options for scheduling a meeting or event. Click and drag in the
calendar to select time ranges when you're available. Enter event details on the left, calendar to select time ranges when you're available. Enter event details on the left,

View file

@ -17,7 +17,6 @@ class AvailabilityComposerExtension extends ComposerExtension
data.attendees = [] data.attendees = []
data.attendees = participants.map (p) -> data.attendees = participants.map (p) ->
name: p.name, email: p.email, isSender: p.isMe() name: p.name, email: p.email, isSender: p.isMe()
console.log "Sending request!\n",JSON.stringify data
serverUrl = "https://quickschedule.herokuapp.com/register-events" serverUrl = "https://quickschedule.herokuapp.com/register-events"
request.post {url: serverUrl, body: JSON.stringify(data)}, (error, resp, data) => request.post {url: serverUrl, body: JSON.stringify(data)}, (error, resp, data) =>
console.log(error,resp,data) console.log(error,resp,data)

View file

@ -12,11 +12,12 @@ class CalendarButton extends React.Component
_onClick: => _onClick: =>
BrowserWindow = require('remote').require('browser-window') BrowserWindow = require('remote').require('browser-window')
w = new BrowserWindow w = new BrowserWindow
title: 'N1 QuickSchedule'
nodeIntegration: false nodeIntegration: false
webPreferences: webPreferences:
webSecurity:false webSecurity:false
width: 700 width: 800
height: 600 height: 650
# Here, we load an arbitrary html file into the Composer! # Here, we load an arbitrary html file into the Composer!
path = require 'path' path = require 'path'