[Coffee] [PATCH v2] Add function for showing offline calendar

Michal Sojka Michal.Sojka at cvut.cz
Mon Sep 3 00:33:04 CEST 2018


Ahoj Tomáši,

jak už jsem psal, nemyslím si, že je potřeba mít tuto funkcionalitu
offline. Mám pro to následující důvody:

- Beru to tak, že události zaznamenávané do kalendáře nejsou kritické
  (nejde o prachy), takže pokud to tu a tam nebude fungovat, nic moc se
  nestane.

- Offline režim je schválně dělán jako minimalistický a toto z něj dělá
  režim maximalistický (viz níže).

- Bylo by potřeba dodělat do buildrootu
  (terminal/src/buildroot/package/merica/mt-apps/mt-apps.mk) instalaci
  všech souborů v adresáři html.
  
- Tím by se na terminál nainstalovalo 29 MB šrotu, což docela hodně (asi
  500x víc, než je současná velikost všech zdrojáků). Při instalaci jen
  dist/ souborů je to jen 6 MB, ale i to je docela dost.
  
- Při jakékoli změně v těchto souborech (a proto, že to je tolik dat,
  tak i těch změn určitě nebude málo) by se musela updatovat data na SD
  kartě terminálu. Ne, že by to nešlo, ale na terminálu je jen minimální
  instalace Linuxu, takže je to zbytečný "vopruz".

- V současném kódu je off-line and on-line funkcionalita dost propletena
  a je to hodně nepřehledné.

Myslím, že kalendář by mohl být klidně načten ze serveru a pokud server
na nějakou dobu vypadne, mohl by fungovat i chvíli bez něj. Ale není
potřeba, aby byl kalendář k dispozici hned po naběhnutí terminálu i bez
serveru.

Další komentáře posílám v kódu níže.

On Fri, Aug 31 2018, Prochazka, Tomas wrote:
> This commit implementing function for add events as 'Milk clean' to
> calendar. Calendar is running offline, so not need any data for create
> calendar from server.
>
> Every user sees events, which created another users (this events are
> displayed 'red' color ), but only user which created events(other
> color) he can edit it(delete). If user wants to create event, he must
> be logged in. After logging in, it will be displayed image with
> calendar.
> ---
>  .gitmodules                             |  15 +++
>  html/bower_components/bootstrap         |   1 +
>  html/bower_components/bootstrap3-dialog |   1 +
>  html/bower_components/fullcalendar      |   1 +
>  html/bower_components/jquery            |   1 +
>  html/bower_components/moment            |   1 +
>  html/calendar.html                      | 211 ++++++++++++++++++++++++++++++++
>  html/calendar.svg                       |  51 ++++++++
>  html/index.html                         |  37 +++++-
>  9 files changed, 317 insertions(+), 2 deletions(-)
>  create mode 160000 html/bower_components/bootstrap
>  create mode 160000 html/bower_components/bootstrap3-dialog
>  create mode 160000 html/bower_components/fullcalendar
>  create mode 160000 html/bower_components/jquery
>  create mode 160000 html/bower_components/moment
>  create mode 100644 html/calendar.html
>  create mode 100644 html/calendar.svg
>
> diff --git a/.gitmodules b/.gitmodules
> index 56c12ef..7850202 100644
> --- a/.gitmodules
> +++ b/.gitmodules
> @@ -1,3 +1,18 @@
>  [submodule "libwebsockets"]
>  	path = libwebsockets
>  	url = https://github.com/warmcat/libwebsockets.git
> +[submodule "html/bower_components/jquery"]
> +	path = html/bower_components/jquery
> +	url = https://github.com/jquery/jquery-dist.git
> +[submodule "html/bower_components/bootstrap"]
> +	path = html/bower_components/bootstrap
> +	url = https://github.com/twbs/bootstrap.git
> +[submodule "html/bower_components/bootstrap3-dialog"]
> +	path = html/bower_components/bootstrap3-dialog
> +	url = https://github.com/nakupanda/bootstrap3-dialog.git
> +[submodule "html/bower_components/moment"]
> +	path = html/bower_components/moment
> +	url = https://github.com/moment/moment
> +[submodule "html/bower_components/fullcalendar"]
> +	path = html/bower_components/fullcalendar
> +	url = https://github.com/fullcalendar/fullcalendar
> diff --git a/html/bower_components/bootstrap b/html/bower_components/bootstrap
> new file mode 160000
> index 0000000..0b9c4a4
> --- /dev/null
> +++ b/html/bower_components/bootstrap
> @@ -0,0 +1 @@
> +Subproject commit 0b9c4a4007c44201dce9a6cc1a38407005c26c86
> diff --git a/html/bower_components/bootstrap3-dialog b/html/bower_components/bootstrap3-dialog
> new file mode 160000
> index 0000000..3dd11d5
> --- /dev/null
> +++ b/html/bower_components/bootstrap3-dialog
> @@ -0,0 +1 @@
> +Subproject commit 3dd11d586f78de75356af418907ec6e3b347377c
> diff --git a/html/bower_components/fullcalendar b/html/bower_components/fullcalendar
> new file mode 160000
> index 0000000..3ddb3c8
> --- /dev/null
> +++ b/html/bower_components/fullcalendar
> @@ -0,0 +1 @@
> +Subproject commit 3ddb3c8d1461c6b841aed3487ba07d8ba338adb6
> diff --git a/html/bower_components/jquery b/html/bower_components/jquery
> new file mode 160000
> index 0000000..9e8ec3d
> --- /dev/null
> +++ b/html/bower_components/jquery
> @@ -0,0 +1 @@
> +Subproject commit 9e8ec3d10fad04748176144f108d7355662ae75e
> diff --git a/html/bower_components/moment b/html/bower_components/moment
> new file mode 160000
> index 0000000..db71a65
> --- /dev/null
> +++ b/html/bower_components/moment
> @@ -0,0 +1 @@
> +Subproject commit db71a655fc51fe58009675608a400d0d4cd0ca87
> diff --git a/html/calendar.html b/html/calendar.html
> new file mode 100644
> index 0000000..5c21b19
> --- /dev/null
> +++ b/html/calendar.html
> @@ -0,0 +1,211 @@
> +<!DOCTYPE html>
> +<html>
> +
> +<head>
> +    <meta charset='utf-8' />
> +    <link href='bower_components/fullcalendar/dist/fullcalendar.min.css' rel='stylesheet' />
> +    <link href='bower_components/fullcalendar/dist/fullcalendar.print.min.css' rel='stylesheet' media='print' />
> +    <script src='bower_components/moment/min/moment.min.js'></script>
> +    <script src='bower_components/jquery/dist/jquery.min.js'></script>
> +    <script src='bower_components/fullcalendar/dist/fullcalendar.min.js'></script>
> +    <script>
> +        var color_event = '#FF0000'; //color for events, which user can not edit

Lepší by bylo pojmenovat tu proměnnou event_color_ro (jako read only).
Ale nevím, jestli je dobré ji definovat jako globální proměnnou - může
se tlouct o jméno s jinými skripty (čert ví, co v těch 29 MB všecho je
:-). Nebyla by lepší definice v css?

> +        var Object_services = [{
> +            id: "clean_milk",
> +            name: "Clean milk",

Milk container cleaned

> 
> +            color: '#178006'
> +        }, {
> +            id: "open_new_box",
> +            name: "Open new box",

New coffee opened

> +            color: '#062e80'
> +        }, {
> +            id: "last_coffee_box",
> +            name: "Last coffee box",

Tohle má znamenat co? Že někdo načal poslední pytel kafe? Není jasné,
jestli se v takovém případě má zaškrtnout i předchozí možnost nebo ne.
Viz také níže.

> +            color: '#862e80'
> +        }];

Chybí tu Např. čištění celého kávovaru (ne jen piksly na mléko).

> +
> +        function settingRightVariables() {
> +            document.getElementById("id1").id = String(Object_services[0].id);
> +            document.getElementById("id1_label").textContent = String(Object_services[0].name);
> +            document.getElementById("id2").id = String(Object_services[1].id);
> +            document.getElementById("id2_label").textContent = String(Object_services[1].name);
> +            document.getElementById("id3").id = String(Object_services[2].id);
> +            document.getElementById("id3_label").textContent = String(Object_services[2].name);
> +        }

Proč to nastavuješ pomocí skriptu? Když ty id a textContent nastavíš
rovnou v html dole, tak to bude kratší a přehlednější.

> +        var calendar;
> +        $(document).ready(function() {
> +            calendar = $('#calendar').fullCalendar({
> +                header: {
> +                    left: 'prev,next today',
> +                    center: 'title',
> +                    right: ''
> +                },
> +                selectable: true,
> +                selectHelper: true,
> +                selectConstraint: {
> +                    start: '00:00',
> +                    end: '24:00'
> +                },
> +                select: function(start, end) {
> +                    var array = $('#calendar').fullCalendar('clientEvents', function(events) {
> +                        return (moment(events.start).format('YYYY-MM-DD') >= moment(start).format('YYYY-MM-DD') && moment(end).format('YYYY-MM-DD') > moment(events.start).format('YYYY-MM-DD'))

Tohle rozděl na víc řádků, ať se to dá pochopit i bez scrolování. A není
mi jasné to porovnání endu ze startem. Nevybereš tím všechny události
v budoucnosti?

> +                    });
> +                    var date_start = moment(start).format('YYYY-MM-DD');
> +                    var date_end = moment(end).format('YYYY-MM-DD');

Když tyhle proměnné nadefinuješ před array, tak ten kód funkce bude
kratší a přehlednější.

> +                    var i;
> +                    for (i = 0; i < Object_services.length; i++) {
> +                        var checkBox = document.getElementById(String(Object_services[i].id));
> +                        var foundValue = array.filter(obj => obj.title === Object_services[i].name);
> +                        var title;
> +                        if (checkBox.checked == true) {
> +                            var title = String(Object_services[i].name);
> +                            if (foundValue.length == 0) {
> +                                var eventData;
> +                                eventData = {
> +                                    title: title,
> +                                    start: start,
> +                                    color: Object_services[i].color
> +                                };
> +                                $('#calendar').fullCalendar('renderEvent', eventData, true); // stick? = true
> +                                let queue = JSON.parse(localStorage.getItem("events_local")) || [];
> +                                var id = localStorage.getItem("id_local");
> +                                queue.push({
> +                                    data: JSON.stringify({
> +                                        type: "events",
> +                                        time: date_start,
> +                                        title: title,
> +                                        id: id,
> +                                        delete_events: false
> +                                    })
> +                                });
> +                                localStorage.setItem("events_local", JSON.stringify(queue));
> +                            }
> +                        }
> +                    }

Tohle je dost divoké. Mám následující připomínky:

1) Přidávání událostí není moc intuitivní. Člověk neví, jestli má
   nejdřív kliknout do kalendáře a pak vybrat "Service", nebo naopak.
   Chtělo by to napsat uživateli, co a jak má dělat. Ale...

2) Přidávání událostí pomocí kalendáře je zbytečné či možná dokonce
   nežádoucí. Když vyčistím kávovar, tak chci kliknout, že jsem vyčistil
   kávovar a nechci klikat na datum, protože budu vždy klikat na dnešek.
   Čili, kalendář bych použil jen na zobrazování událostí, přidávání
   bych dělal jen stiskem jednoho tlačítka.

> +                    $('#calendar').fullCalendar('unselect');
> +                },
> +                eventClick: function(event) {
> +                    if (event.editable) {
> +                        var answer = confirm("Are you want remove this events (" + event.title + ") from " + moment(event.start).format('DD-MM-YYYY') + " ?")

Mazání nějak blbnw. Když si naklikám víc událostí v různých dnes a pak
je zkouším smazat, tak to občas nefunguje - ani se nezobrazí tato
otázka.

> +                        if (answer) {
> +                            $('#calendar').fullCalendar('removeEvents', event._id);
> +                            var id = localStorage.getItem("id_local");
> +                            let queue = JSON.parse(localStorage.getItem("events_local")) || [];
> +                            queue.push({
> +                                data: JSON.stringify({
> +                                    type: "events",
> +                                    time: moment(event.start).format('YYYY-MM-DD'),
> +                                    title: event.title,
> +                                    id: id,
> +                                    delete_events: true
> +                                })
> +                            });

Tohle je taky nějaký divoký. Když chceš smazat událost, tak ji tam
přidáš ještě jednou, ale nastavíš flag na true a odstraníš jí při druhém
průchodu? Nemůžeš jí odstranit rovnou bez toho flagu delete_events?

> +                            let queue2 = [];
> +                            if (Array.isArray(queue)) {
> +                                for (var i = queue.length - 1; i >= 0; i--) {

Proč se to prochází pozpátku? Bude pak dobře fungovat mazání zprávy ze
serveru, když smažu 

> +                                    let entry = queue[i];
> +                                    let entry2 = JSON.parse(entry.data);
> +                                    if (!((moment(event.start).format('YYYY-MM-DD') == entry2.time) && (event.title == entry2.title))) {
> +                                        queue2.push(entry);
> +                                    } else {
> +                                        entry2.delete_events = true;
> +                                        queue2.push({
> +                                            data: JSON.stringify(entry2) //if server offline, we must event remove from localStorage
> +                                        });
> +                                    }
> +                                }
> +                                localStorage.removeItem("events_local");
> +                                localStorage.setItem("events_local", JSON.stringify(queue2));
> +                            }

Aha, už to chápu, to je asi kvůli tomu, že to potřebuješ odstranit i ze
serveru. Ale stejně je to divoký. Kdyby se třeba časem přidávala
událost, která může nastat víckrát za den, tak se to bude muset celé
předělat. Další důvod pro to, nemít kalendář offline. Takhle se bojím,
že v té synchronizaci local storage a serveru budou chyby.

> +                        }
> +                    }
> +                },
> +                //editable: false,
> +                eventLimit: true
> +            });
> +
> +            let queue = JSON.parse(localStorage.getItem("events_server")) || []; //event from server

Kdo nastavuje events_server? Nikde jinde to nevidím. Pokud to jsou
události, které pošle flask, tak není důvod na to používat localStorage.
Kód pro přidávání událostí ze serveru může být celý na serveru.

> +            localStorage.removeItem("events_server");
> +            var id = localStorage.getItem("id_local"); //how user is login
> +            if (Array.isArray(queue)) {
> +                if (queue.length) {
> +                    for (var i = queue.length - 1; i >= 0; i--) {

Proč iteruješ pozpátku?

> +                        let entry = queue[i];
> +                        addEventstoCalendar(id, entry);
> +                    }
> +                } else {
> +                    let queue = JSON.parse(localStorage.getItem("events_local")) || []; //event from local
> +                    if (Array.isArray(queue)) {
> +                        for (var i = queue.length - 1; i >= 0; i--) {
> +                            let entry = queue[i];
> +                            entry = JSON.parse(entry.data);
> +                            if (!entry.delete_events) {
> +                                addEventstoCalendar(id, entry);
> +                            }
> +                        }
> +                    }
> +                }
> +            }
> +
> +        });
> +
> +        function addEventstoCalendar(id, entry) {
> +            var color = color_event;
> +            var eventData = {
> +                title: entry.title,
> +                start: entry.time,
> +                color: color,
> +                editable: false
> +            };
> +            if (id == entry.id) {
> +                for (var j = 0; j < Object_services.length; j++) {
> +                    if (entry.title == String(Object_services[j].name)) {
> +                        eventData.color = Object_services[j].color;
> +                        break;
> +                    }
> +                }
> +                eventData.editable = true;
> +            }
> +            calendar.fullCalendar('renderEvent', eventData, true);
> +        }
> +    </script>
> +    <style>
> +        body {
> +            margin: 20px 10px;
> +            padding: 0;
> +            font-family: "Lucida Grande", Helvetica, Arial, Verdana, sans-serif;
> +            font-size: 14px;
> +        }
> +        #calendar {
> +            max-width: 800px;
> +            margin: 0 auto;
> +        }
> +        fieldset {
> +            font-size: 15px;
> +            padding: 10px;
> +            width: 450px;
> +            margin: 0 auto;
> +            text-align: center;
> +            line-height: 1.8;
> +        }
> +    </style>
> +
> +</head>
> +
> +<body>
> +    <div id='calendar'></div>
> +    <script>
> +        settingRightVariables();
> +    </script>
> +    <fieldset style="text-align:center">
> +        <legend align="center"> Services</legend>
> +        <input style="vertical-align:middle" type="checkbox" id="id1">
> +        <label style="vertical-align:middle" id="id1_label">A</label>
> +        <input style="vertical-align:middle" type="checkbox" id="id2">
> +        <label style="vertical-align:middle" id="id2_label">B</label>
> +        <input style="vertical-align:middle" type="checkbox" id="id3">
> +        <label style="vertical-align:middle" id="id3_label">C</label>
> +    </fieldset>
> +</body>
> +
> +</html>
> diff --git a/html/calendar.svg b/html/calendar.svg
> new file mode 100644
> index 0000000..4d781eb
> --- /dev/null
> +++ b/html/calendar.svg
> @@ -0,0 +1,51 @@
> +<?xml version="1.0" encoding="utf-8"?>
> +<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
> +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
> +	 viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
> +<style type="text/css">
> +	.st0{fill:#77B3D4;}
> +	.st1{opacity:0.2;}
> +	.st2{fill:#231F20;}
> +	.st3{fill:#FFFFFF;}
> +	.st4{fill:#C75C5C;}
> +	.st5{fill:#4F5D73;}
> +	.st6{fill:#E0E0D1;}
> +</style>
> +<g id="Layer_1">
> +	<g>
> +		<circle class="st0" cx="32" cy="32" r="32"/>
> +	</g>
> +	<g>
> +		<g class="st1">
> +			<path class="st2" d="M12,25v25c0,2.2,1.8,4,4,4h32c2.2,0,4-1.8,4-4V25H12z"/>
> +		</g>
> +		<g>
> +			<path class="st3" d="M12,23v25c0,2.2,1.8,4,4,4h32c2.2,0,4-1.8,4-4V23H12z"/>
> +		</g>
> +		<g class="st1">
> +			<path class="st2" d="M48,14H16c-2.2,0-4,1.8-4,4v7h40v-7C52,15.8,50.2,14,48,14z"/>
> +		</g>
> +		<g>
> +			<path class="st4" d="M48,12H16c-2.2,0-4,1.8-4,4v7h40v-7C52,13.8,50.2,12,48,12z"/>
> +		</g>
> +		<g>
> +			<path class="st5" d="M32,48c-1.1,0-2-0.9-2-2c0-5.5,1.8-9.5,3.5-12H27c-1.1,0-2-0.9-2-2s0.9-2,2-2h11c0.9,0,1.6,0.6,1.9,1.4
> +				s0,1.7-0.7,2.2C39,33.8,34,37.5,34,46C34,47.1,33.1,48,32,48z"/>
> +		</g>
> +		<g class="st1">
> +			<path class="st2" d="M20,21c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C22,20.1,21.1,21,20,21L20,21z"/>
> +		</g>
> +		<g class="st1">
> +			<path class="st2" d="M45,21c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C47,20.1,46.1,21,45,21L45,21z"/>
> +		</g>
> +		<g>
> +			<path class="st6" d="M20,19c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C22,18.1,21.1,19,20,19L20,19z"/>
> +		</g>
> +		<g>
> +			<path class="st6" d="M45,19c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C47,18.1,46.1,19,45,19L45,19z"/>
> +		</g>
> +	</g>
> +</g>
> +<g id="Layer_2">
> +</g>
> +</svg>
> diff --git a/html/index.html b/html/index.html
> index 06f8fbf..b7e0122 100644
> --- a/html/index.html
> +++ b/html/index.html
> @@ -1,6 +1,13 @@
>  <!doctype html>
>  <html>
>  <head>
> +
> +<script src="/bower_components/jquery/dist/jquery.min.js"></script>
> +<link href="/bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"/>
> +<script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
> +<link href="/bower_components/bootstrap3-dialog/dist/css/bootstrap-dialog.min.css" rel="stylesheet" type="text/css" />
> +<script src="/bower_components/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js"></script>
> +
>  <meta charset="utf-8"/>
>  <title>IID Coffee Terminal</title>
>  <script>
> @@ -17,6 +24,7 @@
>      loadServerScript();
>  
>      var socket;
> +    var dialog;
>  
>      function showOfflineQueue() {
>          let queue = JSON.parse(localStorage.getItem("offlineQueue")) || [];
> @@ -51,6 +59,16 @@
>  
>          socket.onmessage = function(msg) {
>              updateJSONmsg(msg.data);
> +            var data = JSON.parse(msg.data);
> +            if (data.uid !== undefined) { //login
> +                localStorage.removeItem("id_local");
> +                localStorage.setItem("id_local",data.uid); //save id
> +                console.log(data.uid);
> +                document.getElementById("myImg").style.visibility='visible'; //show Image for using Calendar
> +            }else{
> +                dialog.close();
> +                document.getElementById("myImg").style.visibility='hidden';
> +            }
>              if (typeof updateRemote === "function") {
>                  updateRemote(msg.data);
>              } else {
> @@ -98,6 +116,7 @@
>      }
>  
>      function sendReset() {
> +        document.getElementById("myImg").style.visibility='hidden';
>          socket.send("reset");
>          console.log("reset");
>      }
> @@ -106,13 +125,25 @@
>          socket.send("close");
>          console.log("close");
>      }
> +
> + function show_calendar() {
> +    if (typeof updateRemote === "function") {
> +        replayOfflineEvents();
> +        listEventst();
> +    }
> +    dialog=BootstrapDialog.show({
> +        title: 'Calendar',
> +        message: $('<div></div>').load('calendar.html')
> +        });
> +    }
> +
>  </script>
>  </head>
>  <body>
>      <center>
>      <h1>IID Coffee Terminal</h1>
>      </center>
> -
> +    <img id="myImg" src="calendar.svg" width="100" height="100" align="left" onclick="show_calendar()">
>  <div id="remote"></div>
>  
>  <div id="local">
> @@ -138,6 +169,8 @@
>      <p>Input: <span id="inputStatus"></span>, JSON message: <span id="localJSON">no data received</span></p>
>      </center>
>  </div>
> -
> +<script>
> +    document.getElementById("myImg").style.visibility='hidden';
> +</script>
>  </body>
>  </html>
> -- 
> 2.11.0


More information about the Coffee mailing list