diff --git a/www/calendar/inner.js b/www/calendar/inner.js index 1eecc5157..d1aafcd8d 100644 --- a/www/calendar/inner.js +++ b/www/calendar/inner.js @@ -39,18 +39,98 @@ define([ ) { var APP = window.APP = { + calendars: {} }; -console.log(Calendar); - var common; var sframeChan; Messages.calendar = "Calendar"; // XXX +Messages.calendar_default = "My calendar"; // XXX + + var newCalendar = function (data, cb) { + APP.module.execCommand('CREATE', data, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + var newEvent = function (data, cb) { + var start = data.start; + var end = data.end; + data.start = +new Date(start._date); + data.end = +new Date(end._date); + APP.module.execCommand('CREATE_EVENT', data, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + var updateEvent = function (data, cb) { + APP.module.execCommand('UPDATE_EVENT', data, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + var deleteEvent = function (data, cb) { + APP.module.execCommand('DELETE_EVENT', data, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + var getContrast = function (color) { + var rgb = Util.hexToRGB(color); + // http://www.w3.org/TR/AERT#color-contrast + var brightness = Math.round(((parseInt(rgb[0]) * 299) + + (parseInt(rgb[1]) * 587) + + (parseInt(rgb[2]) * 114)) / 1000); + return (brightness > 125) ? 'black' : 'white'; + }; + var getCalendars = function () { + return Object.keys(APP.calendars).map(function (id) { + var c = APP.calendars[id]; + var md = Util.find(c, ['content', 'metadata']); + if (!md) { return void console.error('Ignore calendar without metadata'); } + return { + id: id, + name: Util.fixHTML(md.title), + color: getContrast(md.color), + bgColor: md.color, + dragBgColor: md.color, + borderColor: md.color, + }; + }); + }; + var getSchedules = function () { + var s = []; + Object.keys(APP.calendars).forEach(function (id) { + var c = APP.calendars[id]; + var data = c.content || {}; + Object.keys(data.content || {}).forEach(function (uid) { + var obj = data.content[uid]; + obj.title = Util.fixHTML(obj.title || ""); + obj.location = Util.fixHTML(obj.location || ""); + s.push(data.content[uid]); + }); + }); + return s; + }; var updateCalendar = function (data) { - console.log(data); + var cal = APP.calendar; + + // Is it a new calendar? + var isNew = !APP.calendars[data.id]; + + // Update local data + APP.calendars[data.id] = data; + // If this calendar is new, add it + if (cal && isNew) { cal.setCalendars(getCalendars()); } + + // If calendar if initialized, update it + if (!cal) { return; } + cal.clear(); + cal.createSchedules(getSchedules(), true); + cal.render(); }; var templates = { @@ -68,34 +148,14 @@ Messages.calendar = "Calendar"; // XXX h('div#cp-calendar') ]); - var cal = new Calendar('#cp-calendar', { + var cal = APP.calendar = new Calendar('#cp-calendar', { defaultView: 'week', // weekly view option useCreationPopup: true, useDetailPopup: true, usageStatistics: false, - calendars: [{ - id: '1', - name: 'My Calendar', - color: '#ffffff', - bgColor: '#9e5fff', - dragBgColor: '#9e5fff', - borderColor: '#9e5fff' - }, { - id: '2', - name: 'Company', - color: '#00a9ff', - bgColor: '#00a9ff', - dragBgColor: '#00a9ff', - borderColor: '#00a9ff' - }] + calendars: getCalendars(), }); cal.on('beforeCreateSchedule', function(event) { - var startTime = event.start; - var endTime = event.end; - var isAllDay = event.isAllDay; - var guide = event.guide; - var triggerEventName = event.triggerEventName; - // XXX Recurrence (later) // On creation, select a recurrence rule (daily / weekly / monthly / more weird rules) // then mark it under recurrence rule with a uid (the same for all the recurring events) @@ -110,23 +170,49 @@ Messages.calendar = "Calendar"; // XXX category: "time", location: Util.fixHTML(event.location), start: event.start, + isAllDay: event.isAllDay, end: event.end, }; - /* - if (triggerEventName === 'click') { - // open writing simple schedule popup - schedule = { - }; - } else if (triggerEventName === 'dblclick') { - // open writing detail schedule popup - schedule = { - }; - } - */ + newEvent(schedule, function (err) { + if (err) { + console.error(err); + return void UI.warn(err); + } + cal.createSchedules([schedule]); + }); + }); + cal.on('beforeUpdateSchedule', function(event) { + var changes = event.changes || {}; + delete changes.state; + if (changes.end) { changes.end = +new Date(changes.end._date); } + if (changes.start) { changes.start = +new Date(changes.start._date); } + var old = event.schedule; - cal.createSchedules([schedule]); + updateEvent({ + ev: old, + changes: changes + }, function (err) { + if (err) { + console.error(err); + return void UI.warn(err); + } + cal.updateSchedule(old.id, old.calendarId, changes); + }); }); + cal.on('beforeDeleteSchedule', function(event) { + var data = event.schedule; + deleteEvent(event.schedule, function (err) { + if (err) { + console.error(err); + return void UI.warn(err); + } + cal.deleteSchedule(data.id, data.calendarId); + }); + }); + + cal.createSchedules(getSchedules(), true); + cal.render(); }; var createToolbar = function () { @@ -161,6 +247,10 @@ Messages.calendar = "Calendar"; // XXX sframeChan.onReady(waitFor()); }).nThen(function (/*waitFor*/) { createToolbar(); + var metadataMgr = common.getMetadataMgr(); + var privateData = metadataMgr.getPrivateData(); + var user = metadataMgr.getUserData(); + // Fix flatpickr selection var MutationObserver = window.MutationObserver; @@ -193,7 +283,6 @@ Messages.calendar = "Calendar"; // XXX $el.find('input').attr('autocomplete', 'off'); $el.find('.tui-full-calendar-dropdown-button').addClass('btn btn-secondary'); $el.find('.tui-full-calendar-popup-close').addClass('btn btn-cancel fa fa-times cp-calendar-close').empty(); - console.log(el); }; var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { @@ -214,16 +303,26 @@ Messages.calendar = "Calendar"; // XXX APP.module = common.makeUniversal('calendar', { onEvent: onEvent }); - APP.module.execCommand('SUBSCRIBE', null, function () { + APP.module.execCommand('SUBSCRIBE', null, function (obj) { + if (obj.empty) { + // No calendar yet, create one + newCalendar({ + teamId: 1, + color: user.color, + title: Messages.calendar_default + }, function (err, obj) { + if (err) { return void UI.errorLoadingScreen(Messages.error); } // XXX + makeCalendar(); + UI.removeLoadingScreen(); + }); + return; + } console.error('subscribed'); // XXX build UI makeCalendar(); UI.removeLoadingScreen(); }); - var metadataMgr = common.getMetadataMgr(); - var privateData = metadataMgr.getPrivateData(); - APP.origin = privateData.origin; diff --git a/www/common/outer/calendar.js b/www/common/outer/calendar.js index b5bfd2629..a88d2e2ec 100644 --- a/www/common/outer/calendar.js +++ b/www/common/outer/calendar.js @@ -25,22 +25,16 @@ define([ * Own drive { calendars: { - own: calendar, - extra: { - uid: calendar, - uid: calendar - } + uid: calendar, + uid: calendar } } * Team drive { calendars: { - own: calendar, - extra: { - uid: calendar, - uid: calendar - } + uid: calendar, + uid: calendar } } @@ -85,10 +79,11 @@ ctx.calendars[channel] = { var initializeCalendars = function (ctx, cb) { var proxy = ctx.store.proxy; var calendars = proxy.calendars = proxy.calendars || {}; - if (!calendars.own) { + /*if (!calendars.own) { var own = calendars.own = makeCalendar(true); own.color = ctx.Store.getUserColor(); } + */ setTimeout(cb); // XXX for each team, if we have edit rights, create the team calendar? // XXX or maybe do it from the team app? @@ -99,11 +94,12 @@ ctx.calendars[channel] = { teams: c.stores, id: c.channel, readOnly: c.readOnly, - data: Util.clone(c.proxy) + content: Util.clone(c.proxy) }, ctx.clients); }; - var openChannel = function (ctx, cfg) { + var openChannel = function (ctx, cfg, _cb) { + var cb = Util.once(Util.mkAsync(_cb || function () {})); var teamId = cfg.storeId; var data = cfg.data; var channel = data.channel; @@ -165,34 +161,36 @@ ctx.calendars[channel] = { console.error(channel, config); var lm = Listmap.create(config); c.lm = lm; - c.proxy = lm.proxy; + var proxy = c.proxy = lm.proxy; lm.proxy.on('ready', function () { c.ready = true; console.warn('READY', channel); + if (!proxy.metadata) { + proxy.metadata = { + color: data.color, + title: data.title + }; + } setTimeout(update); + if (cb) { cb(null, lm.proxy); } }).on('change', [], function () { setTimeout(update); + }).on('error', function (info) { + if (info && info.error) { cb(info); } }); }; var openChannels = function (ctx) { var findFromStore = function (store) { var c = store.proxy.calendars; if (!c) { return; } - if (c.own) { + Object.keys(c).forEach(function (channel) { + console.log(c[channel]); openChannel(ctx, { storeId: store.id || 1, - data: c.own + data: c[channel] }); - } - if (c.extra) { - Object.keys(c.extra).forEach(function (channel) { - openChannel(ctx, { - storeId: store.id || 1, - data: c.extra[channel] - }); - }); - } + }); }; // Personal drive @@ -206,7 +204,9 @@ ctx.calendars[channel] = { if (idx === -1) { ctx.clients.push(cId); } - cb(); + cb({ + empty: !Object.keys(ctx.calendars).length + }); Object.keys(ctx.calendars).forEach(function (channel) { var c = ctx.calendars[channel] || {}; console.log(channel, c); @@ -215,6 +215,76 @@ ctx.calendars[channel] = { }); }; + var getStore = function (ctx, id) { + if (!id || id === 1) { + return ctx.store; + } + var m = ctx.store.modules && ctx.store.modules.team; + if (!m) { return; } + return m.getTeam(id); + }; + + var createCalendar = function (ctx, data, cId, cb) { + console.error(data); + var store = getStore(ctx, data.teamId); + if (!store) { return void cb({error: "NO_STORE"}); } + // Check team edit rights: viewers in teams don't have rpc + if (!store.rpc) { return void cb({error: "EFORBIDDEN"}); } + + var c = store.proxy.calendars = store.proxy.calendars || {}; + var cal = makeCalendar(); + cal.color = data.color; + cal.title = data.title; + openChannel(ctx, { + storeId: store.id || 1, + data: cal + }, function (err, proxy) { + if (err) { + // Can't open this channel, don't store it + console.error(err); + return void cb({error: err.error}) + } + // Add the calendar and call back + c[cal.channel] = cal; + ctx.Store.onSync(store.id, cb); + }); + }; + + var createEvent = function (ctx, data, cId, cb) { + var id = data.calendarId; + var c = ctx.calendars[id]; + if (!c) { return void cb({error: "ENOENT"}); } + c.proxy.content = c.proxy.content || {}; + c.proxy.content[data.id] = data; + Realtime.whenRealtimeSyncs(c.lm.realtime, cb); + }; + var updateEvent = function (ctx, data, cId, cb) { + if (!data || !data.ev) { return void cb({error: 'EINVAL'}); } + var id = data.ev.calendarId; + var c = ctx.calendars[id]; + if (!c || !c.proxy || !c.proxy.content) { return void cb({error: "ENOENT"}); } + + // Find the event + var ev = c.proxy.content[data.ev.id]; + if (!ev) { return void cb({error: "EINVAL"}); } + + // update the event + var changes = data.changes || {}; + Object.keys(changes).forEach(function (key) { + ev[key] = changes[key]; + }); + + Realtime.whenRealtimeSyncs(c.lm.realtime, cb); + }; + var deleteEvent = function (ctx, data, cId, cb) { + var id = data.calendarId; + var c = ctx.calendars[id]; + if (!c) { return void cb({error: "ENOENT"}); } + c.proxy.content = c.proxy.content || {}; + delete c.proxy.content[data.id]; + Realtime.whenRealtimeSyncs(c.lm.realtime, cb); + }; + var removeClient = function (ctx, cId) { var idx = ctx.clients.indexOf(cId); ctx.clients.splice(idx, 1); @@ -249,6 +319,18 @@ ctx.calendars[channel] = { if (cmd === 'SUBSCRIBE') { return void subscribe(ctx, data, clientId, cb); } + if (cmd === 'CREATE') { + return void createCalendar(ctx, data, clientId, cb); + } + if (cmd === 'CREATE_EVENT') { + return void createEvent(ctx, data, clientId, cb); + } + if (cmd === 'UPDATE_EVENT') { + return void updateEvent(ctx, data, clientId, cb); + } + if (cmd === 'DELETE_EVENT') { + return void deleteEvent(ctx, data, clientId, cb); + } }; return calendar; diff --git a/www/lib/calendar/date-picker.js b/www/lib/calendar/date-picker.js index b44a5e82a..156094892 100644 --- a/www/lib/calendar/date-picker.js +++ b/www/lib/calendar/date-picker.js @@ -7,8 +7,6 @@ define([ var createRangePicker = function (cfg) { var start = cfg.startpicker; var end = cfg.endpicker; - console.log(cfg); - console.error(start, end); var e = $(end.input)[0]; var endPickr = Flatpickr(e, {