From 19ad5d90e84c39c7e0510da5494a48117d9824c0 Mon Sep 17 00:00:00 2001
From: yflory <yann.flory@xwiki.com>
Date: Tue, 6 Apr 2021 14:03:51 +0200
Subject: [PATCH] Offline calendars

---
 www/calendar/inner.js            | 51 +++++++++++++++++++++++++++-----
 www/calendar/main.js             |  3 +-
 www/common/outer/async-store.js  |  3 +-
 www/common/outer/calendar.js     | 24 +++++++++++----
 www/common/outer/sharedfolder.js |  2 +-
 5 files changed, 66 insertions(+), 17 deletions(-)

diff --git a/www/calendar/inner.js b/www/calendar/inner.js
index 698e60035..16d6c95dd 100644
--- a/www/calendar/inner.js
+++ b/www/calendar/inner.js
@@ -56,7 +56,7 @@ Messages.calendar_today = "Today";
 Messages.calendar_deleteConfirm = "Are you sure you want to delete this calendar from your account?";
 Messages.calendar_deleteTeamConfirm = "Are you sure you want to delete this calendar from this team?";
 Messages.calendar_deleteOwned = " It will still be visible for the users it has been shared with.";
-Messages.calendar_errorNoCalendar = "No calendar selected!";
+Messages.calendar_errorNoCalendar = "No editable calendar selected!";
 
     var onCalendarsUpdate = Util.mkEvent();
 
@@ -125,7 +125,7 @@ Messages.calendar_errorNoCalendar = "No calendar selected!";
     var getCalendars = function () {
         return Object.keys(APP.calendars).map(function (id) {
             var c = APP.calendars[id];
-            if (c.hidden) { return; }
+            if (c.hidden || c.restricted) { return; }
             var md = Util.find(c, ['content', 'metadata']);
             if (!md) { return void console.error('Ignore calendar without metadata'); }
             return {
@@ -142,12 +142,15 @@ Messages.calendar_errorNoCalendar = "No calendar selected!";
         var s = [];
         Object.keys(APP.calendars).forEach(function (id) {
             var c = APP.calendars[id];
-            if (c.hidden) { return; }
+            if (c.hidden || c.restricted) { return; }
             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 || "");
+                if (c.readOnly) {
+                    obj.isReadOnly = true;
+                }
                 s.push(data.content[uid]);
             });
         });
@@ -347,6 +350,7 @@ Messages.calendar_errorNoCalendar = "No calendar selected!";
         return UIElements.createDropdown(dropdownConfig)[0];
     };
     var makeCalendarEntry = function (id, teamId) {
+        // XXX handle RESTRICTED calendars (data.restricted)
         var data = APP.calendars[id];
         var edit;
         if (!data.readOnly) {
@@ -523,6 +527,15 @@ Messages.calendar_errorNoCalendar = "No calendar selected!";
             });
         });
 
+        cal.on('clickSchedule', function (event) {
+            var id = event.calendar && event.calendar.id;
+            if (!id || !APP.calendars[id]) {
+                APP.lastClicked = false;
+                return;
+            }
+            APP.lastClicked = id;
+        });
+
         renderCalendar();
     };
 
@@ -593,7 +606,20 @@ Messages.calendar_errorNoCalendar = "No calendar selected!";
             $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();
-            if ($el.find('.tui-full-calendar-hide.tui-full-calendar-dropdown').length) {
+
+            var calendars = APP.calendars || {};
+            var show = false;
+            $el.find('.tui-full-calendar-dropdown-menu li').each(function (i, li) {
+                var $li = $(li);
+                var id = $li.attr('data-calendar-id');
+                var c = calendars[id];
+                if (!c || c.readOnly) {
+                    return void $li.remove();
+                }
+                // If at least one calendar is editable, show the popup
+                show = true;
+            });
+            if ($el.find('.tui-full-calendar-hide.tui-full-calendar-dropdown').length || !show) {
                 $el.hide();
                 UI.warn(Messages.calendar_errorNoCalendar);
                 return;
@@ -604,14 +630,24 @@ Messages.calendar_errorNoCalendar = "No calendar selected!";
                 $el.find('.tui-full-calendar-dropdown-menu').addClass('cp-forcehide');
             }
         };
+        var onCalendarEditPopup = function (el) {
+            // TODO
+        };
         var observer = new MutationObserver(function(mutations) {
             mutations.forEach(function(mutation) {
                 var node;
                 for (var i = 0; i < mutation.addedNodes.length; i++) {
                     var node = mutation.addedNodes[i];
-                    if (node.classList && node.classList.contains('tui-full-calendar-popup')) {
-                        onCalendarPopup(node);
-                    }
+                    try {
+                        if (node.classList && node.classList.contains('tui-full-calendar-popup')
+                                && node.parentNode.classList.contains('tui-view-26')) {
+                            onCalendarPopup(node);
+                        }
+                        if (node.classList && node.classList.contains('tui-full-calendar-popup')
+                                && node.parentNode.classList.contains('tui-view-29')) {
+                            onCalendarEditPopup(node);
+                        }
+                    } catch (e) {}
                 }
             });
         });
@@ -637,7 +673,6 @@ Messages.calendar_errorNoCalendar = "No calendar selected!";
                 });
                 return;
             }
-            console.error('subscribed');
             // XXX build UI
             makeCalendar();
             UI.removeLoadingScreen();
diff --git a/www/calendar/main.js b/www/calendar/main.js
index 476152b1e..bf1d9712d 100644
--- a/www/calendar/main.js
+++ b/www/calendar/main.js
@@ -26,7 +26,8 @@ define([
             //addRpc: addRpc,
             //addData: addData,
             //owned: true,
-            noRealtime: true
+            noRealtime: true,
+            cache: true,
         });
     });
 });
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index 2a494e112..5b381c8ec 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -1494,7 +1494,7 @@ define([
                     }
                 };
                 // Teams support offline/cache mode
-                if (obj.type === "team") { return void todo(); }
+                if (['team', 'calendar'].indexOf(obj.type) !== -1) { return void todo(); }
                 // If we're in "noDrive" mode
                 if (!store.proxy) { return void todo(); }
                 // Other modules should wait for the ready event
@@ -2652,6 +2652,7 @@ define([
                 }, true);
             }).nThen(function (waitFor) {
                 loadUniversal(Team, 'team', waitFor, clientId);
+                loadUniversal(Calendar, 'calendar', waitFor);
             }).nThen(function () {
                 cb();
             });
diff --git a/www/common/outer/calendar.js b/www/common/outer/calendar.js
index 9c36aa1c7..db4c0b654 100644
--- a/www/common/outer/calendar.js
+++ b/www/common/outer/calendar.js
@@ -3,12 +3,13 @@ define([
     '/common/common-hash.js',
     '/common/common-constants.js',
     '/common/common-realtime.js',
+    '/common/outer/cache-store.js',
     '/customize/messages.js',
     '/bower_components/nthen/index.js',
     '/bower_components/chainpad-listmap/chainpad-listmap.js',
     '/bower_components/chainpad-crypto/crypto.js',
     '/bower_components/chainpad/chainpad.dist.js',
-], function (Util, Hash, Constants, Realtime, Messages, nThen, Listmap, Crypto, ChainPad) {
+], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) {
     var Calendar = {};
 
 
@@ -103,8 +104,9 @@ ctx.calendars[channel] = {
         ctx.emit('UPDATE', {
             teams: c.stores,
             id: c.channel,
-            readOnly: c.readOnly,
+            readOnly: c.readOnly || (!c.ready && c.cacheready),
             deleted: !c.stores.length,
+            restricted: c.restricted,
             owned: false, // XXX XXX destroy
             content: Util.clone(c.proxy)
         }, ctx.clients);
@@ -178,14 +180,16 @@ ctx.calendars[channel] = {
 
             var config = {
                 data: {},
-                network: ctx.store.network, // XXX offline
+                network: ctx.store.network || ctx.store.networkPromise, // XXX offline
                 channel: secret.channel,
                 crypto: crypto,
                 owners: [edPublic],
                 ChainPad: ChainPad,
                 validateKey: secret.keys.validateKey || undefined,
                 userName: 'calendar',
-                classic: true
+                Cache: Cache,
+                classic: true,
+                onRejected: ctx.Store && ctx.Store.onRejected
             };
 
             console.error(channel, config);
@@ -213,7 +217,12 @@ ctx.calendars[channel] = {
                 });
             };
 
-            lm.proxy.on('ready', function () {
+            lm.proxy.on('cacheready', function () {
+                if (!proxy.metadata) { return; }
+                c.cacheready = true;
+                setTimeout(update);
+                if (cb) { cb(null, lm.proxy); }
+            }).on('ready', function () {
                 c.ready = true;
                 if (!proxy.metadata) {
                     if (!cfg.isNew) {
@@ -240,6 +249,9 @@ ctx.calendars[channel] = {
                 if (info.error === "EDELETED" ) {
                     return void onDeleted();
                 }
+                if (info.error === "ERESTRICTED" ) {
+                    c.restricted = true;
+                }
                 cb(info);
             });
         });
@@ -274,7 +286,7 @@ ctx.calendars[channel] = {
         Object.keys(ctx.calendars).forEach(function (channel) {
             var c = ctx.calendars[channel] || {};
             console.log(channel, c);
-            if (!c.ready) { return; }
+            if (!c.ready && !c.cacheready) { return; }
             sendUpdate(ctx, c);
         });
     };
diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js
index 188fb2e40..3bc39546c 100644
--- a/www/common/outer/sharedfolder.js
+++ b/www/common/outer/sharedfolder.js
@@ -186,7 +186,7 @@ define([
                 ChainPad: ChainPad,
                 classic: true,
                 network: network,
-                Cache: Cache, // ICE shared-folder cache
+                Cache: Cache, // shared-folder cache
                 metadata: {
                     validateKey: secret.keys.validateKey || undefined,
                     owners: owners