Calendars improvements

pull/1/head
yflory 4 years ago
parent 9f4144703c
commit 2987e468a9

@ -117,6 +117,11 @@
&.no-margin {
margin: 0;
}
&.small {
line-height: initial;
padding: 5px;
height: auto;
}
&:hover, &:not(:disabled):not(.disabled):active, &:focus {
color: @cp_buttons-fg;

@ -1,16 +1,34 @@
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
@import (reference) '../../customize/src/less2/include/tools.less';
&.cp-app-calendar {
.framework_min_main();
.sidebar-layout_main();
display: flex;
flex-flow: column;
#cp-calendar {
#cp-sidebarlayout-container #cp-sidebarlayout-rightside {
padding: 0;
& > div {
margin: 0;
}
.tui-full-calendar-layout {
background-color: @cp_sidebar-right-bg !important;
color: @cryptpad_text_col;
.tui-full-calendar-month * {
color: @cryptpad_text_col !important; // XXX
}
}
.tui-full-calendar-timegrid-timezone {
background-color: @cp_sidebar-right-bg !important;
.tui-full-calendar-timegrid-hour {
color: @cryptpad_text_col !important;
}
color: @cryptpad_text_col;
}
.tui-full-calendar-timegrid-gridline, .tui-full-calendar-time-date {
border-color: @cp_calendar-border !important;
@ -62,5 +80,53 @@
}
}
#cp-sidebarlayout-leftside {
& > div {
padding: 10px
}
.cp-calendar-new {
display: flex;
align-items: center;
justify-content: space-between;
}
.cp-calendar-browse {
display: flex;
align-items: center;
justify-content: space-around;
justify-content: space-evenly;
}
.cp-calendar-list {
.cp-calendar-entry {
display: flex;
align-items: center;
justify-content: space-around;
padding: 5px;
cursor: pointer;
&:not(:last-child) {
margin-bottom: 10px;
}
&:hover {
background: fade(@cryptpad_text_col, 10%);
}
&.cp-active {
background: @cp_sidebar-left-active;
}
.tools_unselectable();
.cp-calendar-color {
display: inline-block;
border-radius: 50%;
width: 15px;
height: 15px;
}
}
}
}
.cp-calendar-colorpicker {
width: 100px;
height: 25px;
cursor: pointer;
border: 1px solid @cp_forms-border;
}
}

@ -9,7 +9,7 @@
</head>
<body class="cp-app-calendar">
<div id="cp-toolbar" class="cp-toolbar-container"></div>
<div id="cp-container"></div>
<div id="cp-sidebarlayout-container"></div>
<noscript>
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>

@ -16,6 +16,7 @@ define([
'/customize/application_config.js',
'/lib/calendar/tui-calendar.min.js',
'/common/jscolor.js',
'css!/lib/calendar/tui-calendar.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/calendar/app-calendar.less',
@ -47,6 +48,13 @@ define([
Messages.calendar = "Calendar"; // XXX
Messages.calendar_default = "My calendar"; // XXX
Messages.calendar_new = "New calendar"; // XXX
Messages.calendar_day = "Day";
Messages.calendar_week = "Week";
Messages.calendar_month = "Month";
Messages.calendar_today = "Today";
var onCalendarsUpdate = Util.mkEvent();
var newCalendar = function (data, cb) {
APP.module.execCommand('CREATE', data, function (obj) {
@ -54,6 +62,18 @@ Messages.calendar_default = "My calendar"; // XXX
cb(null, obj);
});
};
var updateCalendar = function (data, cb) {
APP.module.execCommand('UPDATE', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var deleteCalendar = function (data, cb) {
APP.module.execCommand('DELETE', 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;
@ -85,9 +105,23 @@ Messages.calendar_default = "My calendar"; // XXX
(parseInt(rgb[2]) * 114)) / 1000);
return (brightness > 125) ? 'black' : 'white';
};
var getWeekDays = function (locale) {
var baseDate = new Date(Date.UTC(2017, 0, 1)); // just a Sunday
var weekDays = [];
for(i = 0; i < 7; i++) {
weekDays.push(baseDate.toLocaleDateString(undefined, { weekday: 'long' }));
baseDate.setDate(baseDate.getDate() + 1);
}
return weekDays.map(function (day) { return day.replace(/^./, function (str) { return str.toUpperCase(); }) });
};
var getCalendars = function () {
return Object.keys(APP.calendars).map(function (id) {
var c = APP.calendars[id];
if (c.hidden) { return; }
var md = Util.find(c, ['content', 'metadata']);
if (!md) { return void console.error('Ignore calendar without metadata'); }
return {
@ -98,12 +132,13 @@ Messages.calendar_default = "My calendar"; // XXX
dragBgColor: md.color,
borderColor: md.color,
};
});
}).filter(Boolean);
};
var getSchedules = function () {
var s = [];
Object.keys(APP.calendars).forEach(function (id) {
var c = APP.calendars[id];
if (c.hidden) { return; }
var data = c.content || {};
Object.keys(data.content || {}).forEach(function (uid) {
var obj = data.content[uid];
@ -114,7 +149,15 @@ Messages.calendar_default = "My calendar"; // XXX
});
return s;
};
var updateCalendar = function (data) {
var renderCalendar = function () {
var cal = APP.calendar;
if (!cal) { return; }
cal.clear();
cal.createSchedules(getSchedules(), true);
cal.render();
};
var onCalendarUpdate = function (data) {
var cal = APP.calendar;
// Is it a new calendar?
@ -123,38 +166,244 @@ Messages.calendar_default = "My calendar"; // XXX
// 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();
cal.setCalendars(getCalendars());
onCalendarsUpdate.fire();
renderCalendar();
};
var getPadStart = function (value) {
value = value.toString();
return padStart.call(value, 2, '0');
};
var getTime = function (time) {
var d = new Date();
d.setHours(time.hour);
d.setMinutes(time.minutes);
return d.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
};
// If this browser doesn't support options to toLocaleTimeString, use default layout
if (!(function () {
// Modern browser will return a RangeError if the "locale" argument is invalid.
// Note: the "locale" argument has the same browser compatibility table as the "options"
try {
new Date().toLocaleTimeString('i');
} catch (e) {
return e.name === 'RangeError';
}
})()) { getTime = undefined; }
var templates = {
popupSave: function () {
return Messages.settings_save;
}
},
timegridDisplayTime: getTime,
timegridDisplayPrimaryTime: getTime,
};
var makeTeamSelector = function () {
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var keys = Object.keys(privateData.teams);
if (!keys.length) { return; }
var options = [];
keys.forEach(function (id) {
var t = privateData.teams[id];
if (t.viewer) { return; }
});
};
// XXX Note: always create calendars in your own proxy. If you want a team calendar, you can share it with the team later.
var editCalendar = function (id) {
var isNew = !id;
var data = APP.calendars[id];
if (id && !data) { return; }
var md = {};
if (!isNew) { md = Util.find(data, ['content', 'metadata']); }
if (!md) { return; }
// Create form data
var labelTitle = h('label', Messages.kanban_title);
var title = h('input');
var $title = $(title);
$title.val(md.title || Messages.calendar_new);
var labelColor = h('label', Messages.kanban_color);
var $colorPicker = $(h('div.cp-calendar-colorpicker'));
var jscolorL = new window.jscolor($colorPicker[0], { showOnClick: false, valueElement: undefined, zIndex: 100000 });
var to;
$colorPicker.click(function() {
jscolorL.show();
});
if (md.color) { jscolorL.fromString(md.color); }
var form = h('div', [
labelTitle,
title,
labelColor,
$colorPicker[0]
]);
var send = function (obj) {
if (isNew) {
return void newCalendar(obj, function (err) {
if (err) { console.error(err); return void UI.warn(Messages.error); }
UI.log(Messages.saved);
})
};
obj.id = id;
updateCalendar(obj, function (err) {
if (err) { console.error(err); return void UI.warn(Messages.error); }
UI.log(Messages.saved);
});
};
var m = UI.dialog.customModal(form, {
buttons: [{
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
}, {
className: 'primary',
name: Messages.settings_save,
onClick: function () {
var color = jscolorL.toHEXString();
var title = $title.val();
var obj = {
color: color,
title: title
}
if (!title || !title.trim() ||!/^#[0-9a-fA-F]{6}$/.test(color)) {
return true;
}
send(obj);
},
keys: [13]
}]
});
UI.openCustomModal(m);
};
var makeLeftside = function (calendar, $container) {
var $topContainer = $(h('div.cp-calendar-new')).appendTo($container);
// Add new button
var newButton = h('button.btn.btn-primary', [
h('i.fa.fa-plus'),
h('span', Messages.newButton)
]);
$(newButton).click(function () {
editCalendar();
});
$topContainer.append(newButton);
// Change view mode
var options = ['day', 'week', 'month'].map(function (k) {
return {
tag: 'a',
attributes: {
'class': 'cp-calendar-view',
'data-value': k,
'href': '#',
},
content: Messages['calendar_'+k]
// Messages.calendar_day
// Messages.calendar_week
// Messages.calendar_month
};
});
var dropdownConfig = {
text: Messages.calendar_week,
options: options, // Entries displayed in the menu
isSelect: true,
common: common,
caretDown: true,
buttonCls: 'btn btn-secondary'
};
var $block = UIElements.createDropdown(dropdownConfig);
$block.setValue('week');
var $views = $block.find('a');
$views.click(function () {
var mode = $(this).attr('data-value');
calendar.changeView(mode);
});
$topContainer.append($block);
// Change page
var goLeft = h('button.btn.btn-secondary.fa.fa-chevron-left');
var goRight = h('button.btn.btn-secondary.fa.fa-chevron-right');
var goToday = h('button.btn.btn-secondary', Messages.calendar_today);
$(goLeft).click(function () { calendar.prev(); });
$(goRight).click(function () { calendar.next(); });
$(goToday).click(function () { calendar.today(); });
$container.append(h('div.cp-calendar-browse', [
goLeft, goToday, goRight
]));
// Show calendars
var calendars = h('div.cp-calendar-list');
var $calendars = $(calendars).appendTo($container);
onCalendarsUpdate.reg(function () {
$calendars.empty();
Object.keys(APP.calendars || {}).forEach(function (id) {
var data = APP.calendars[id];
var edit;
if (!data.readOnly) {
edit = h('button.btn.btn-cancel.fa.fa-pencil.small');
$(edit).click(function (e) {
e.stopPropagation();
editCalendar(id);
});
}
var md = Util.find(data, ['content', 'metadata']);
if (!md) { return; }
var active = data.hidden ? '' : '.cp-active';
var calendar = h('div.cp-calendar-entry'+active, [
h('span.cp-calendar-color', {
style: 'background-color: '+md.color+';'
}),
h('span.cp-calendar-title', md.title),
edit
]);
$(calendar).click(function () {
data.hidden = !data.hidden;
$(calendar).toggleClass('cp-active', !data.hidden);
renderCalendar();
});
$calendars.append(calendar);
});
});
onCalendarsUpdate.fire();
};
var makeCalendar = function (ctx) {
var $container = $('#cp-container');
var $container = $('#cp-sidebarlayout-container');
var leftside;
$container.append([
h('div#menu', [
h('span#renderRange.render-range')
]),
h('div#cp-calendar')
leftside = h('div#cp-sidebarlayout-leftside'),
h('div#cp-sidebarlayout-rightside')
]);
var cal = APP.calendar = new Calendar('#cp-calendar', {
var cal = APP.calendar = new Calendar('#cp-sidebarlayout-rightside', {
defaultView: 'week', // weekly view option
useCreationPopup: true,
useDetailPopup: true,
usageStatistics: false,
calendars: getCalendars(),
template: templates,
month: {
daynames: getWeekDays(),
startDayOfWeek: 0,
},
week: {
daynames: getWeekDays(),
startDayOfWeek: 1,
}
});
makeLeftside(cal, $(leftside));
cal.on('beforeCreateSchedule', function(event) {
// XXX Recurrence (later)
// On creation, select a recurrence rule (daily / weekly / monthly / more weird rules)
@ -211,8 +460,7 @@ Messages.calendar_default = "My calendar"; // XXX
});
});
cal.createSchedules(getSchedules(), true);
cal.render();
renderCalendar();
};
var createToolbar = function () {
@ -232,8 +480,7 @@ Messages.calendar_default = "My calendar"; // XXX
var ev = obj.ev;
var data = obj.data;
if (ev === 'UPDATE') {
console.log('Update');
updateCalendar(data);
onCalendarUpdate(data);
return;
}
};

@ -98,6 +98,19 @@ ctx.calendars[channel] = {
}, ctx.clients);
};
var updateLocalCalendars = function (ctx, c, data) {
// Also update local data
c.stores.forEach(function (id) {
var s = getStore(ctx, id);
if (!s || !s.proxy) { return; }
if (!s.rpc) { return; } // team viewer
if (!s.proxy.calendars) { return; }
var cal = s.proxy.calendars[c.channel];
if (!cal) { return; }
if (cal.color !== data.color) { cal.color = data.color; }
if (cal.title !== data.title) { cal.title = data.title; }
});
};
var openChannel = function (ctx, cfg, _cb) {
var cb = Util.once(Util.mkAsync(_cb || function () {}));
var teamId = cfg.storeId;
@ -175,7 +188,13 @@ ctx.calendars[channel] = {
setTimeout(update);
if (cb) { cb(null, lm.proxy); }
}).on('change', [], function () {
if (!c.ready) { return; }
setTimeout(update);
}).on('change', ['metadata'], function () {
// if title or color have changed, update our local values
var md = proxy.metadata;
if (!md || !md.title || !md.color) { return; }
updateLocalCalendars(ctx, c, md);
}).on('error', function (info) {
if (info && info.error) { cb(info); }
});
@ -225,7 +244,6 @@ ctx.calendars[channel] = {
};
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
@ -246,9 +264,38 @@ ctx.calendars[channel] = {
}
// Add the calendar and call back
c[cal.channel] = cal;
// XXX PIN (also make sure it's included in the reset)
ctx.Store.onSync(store.id, cb);
});
};
var updateCalendar = function (ctx, data, cId, cb) {
console.error(data);
var id = data.id;
var c = ctx.calendars[id];
if (!c) { return void cb({error: "ENOENT"}); }
var md = Util.find(c, ['proxy', 'metadata']);
if (!md) { return void cb({error: 'EINVAL'}); }
md.title = data.title;
md.color = data.color;
Realtime.whenRealtimeSyncs(c.lm.realtime, cb);
sendUpdate(ctx, c);
updateLocalCalendars(ctx, c, data);
};
var deleteCalendar = function (ctx, data, cId, cb) {
var store = getStore(ctx, data.teamId);
if (!store) { return void cb({error: "NO_STORE"}); }
if (!store.rpc) { return void cb({error: "EFORBIDDEN"}); }
if (!store.proxy.calendars) { return; }
var id = data.id;
var cal = store.proxy.calendars[id];
if (!cal) { return void cb(); } // Already deleted
delete store.proxy.calendars[id];
ctx.Store.onSync(store.id, cb);
// XXX broadcast to inner
};
// XXX when we destroy a calendar, make sure we also delete it
var createEvent = function (ctx, data, cId, cb) {
var id = data.calendarId;
@ -322,6 +369,9 @@ ctx.calendars[channel] = {
if (cmd === 'CREATE') {
return void createCalendar(ctx, data, clientId, cb);
}
if (cmd === 'UPDATE') {
return void updateCalendar(ctx, data, clientId, cb);
}
if (cmd === 'CREATE_EVENT') {
return void createEvent(ctx, data, clientId, cb);
}

Loading…
Cancel
Save