Dateiverwaltung für die WebBox
ulrich
2020-01-24 05e9c4c92b3ed8e96c010ade8671964e74a2a1ae
Vorbereitung fuer neue Bedienoberflaeche
1 files modified
15 files added
1142 ■■■■■ changed files
README.md 11 ●●●●● patch | view | raw | blame | history
web/ui2/data/menu/datei.json 27 ●●●●● patch | view | raw | blame | history
web/ui2/data/menu/edit.json 27 ●●●●● patch | view | raw | blame | history
web/ui2/data/menu/hauptmenue.json 27 ●●●●● patch | view | raw | blame | history
web/ui2/data/menu/nutzer.json 27 ●●●●● patch | view | raw | blame | history
web/ui2/data/menu/untermenue-1.json 27 ●●●●● patch | view | raw | blame | history
web/ui2/data/menu/untermenue-2.json 27 ●●●●● patch | view | raw | blame | history
web/ui2/data/tpl/app-menu.tpl 20 ●●●●● patch | view | raw | blame | history
web/ui2/data/tpl/dlg-info.tpl 8 ●●●●● patch | view | raw | blame | history
web/ui2/font/pikto.ttf patch | view | raw | blame | history
web/ui2/index.html 66 ●●●●● patch | view | raw | blame | history
web/ui2/js/app-menu.js 137 ●●●●● patch | view | raw | blame | history
web/ui2/js/app.js 208 ●●●●● patch | view | raw | blame | history
web/ui2/js/data.js 148 ●●●●● patch | view | raw | blame | history
web/ui2/js/vorlagen.js 66 ●●●●● patch | view | raw | blame | history
web/ui2/stile.css 316 ●●●●● patch | view | raw | blame | history
README.md
@@ -142,3 +142,14 @@
## Lizenz
Die Dateiverwaltung wird zu den Bedingungen der [GNU Affero General Public License](/gitblit/doc/file-cms.git/master/web!agpl.txt) bereitgestellt. Die von der Dateiverwaltung verwendeten Komponenten unterliegen zum Teil anderen Lizenzen. Deren Nutzungsbedingungen sind an den Quellen der Komponenten angegeben.
## ui2 Prototyp
Versuch einer neuen Bedienoberfläche basierend auf `app-vorlage` und `app-menu`
### Änderungen gegenüber der Bedienoberfläche des file-cms
- kein Bootstrap mehr
- kein jQuery mehr (allerdings benötigt Fancybox jQuery -> evtl. in separate .html-Datei auslagern)
- kein FontAwesome mehr, die wenigen benötigtgten Piktogramme sind mit Hilfe von Fontello aus FontAwesome entnommen und in das file-cms-Projekt kopiert (nur wenige KB)
web/ui2/data/menu/datei.json
New file
@@ -0,0 +1,27 @@
{
  "menue": {
    "menuetitel": "Datei",
    "wurzel": false,
    "vorgaenger": {
      "vtitel": "Hauptmenü",
      "vverweis": "hauptmenue.json"
    },
    "inhalt": [
      {
        "titel": "Neuer Text",
        "umenue": false,
        "funktion": "app.datei_neuer_text"
      },
      {
        "titel": "noch mehr",
        "umenue": true,
        "verweis": "untermenue-2.json"
      },
      {
        "titel": "Benachrichtigung 2",
        "umenue": false,
        "funktion": "app.message_2"
      }
    ]
  }
}
web/ui2/data/menu/edit.json
New file
@@ -0,0 +1,27 @@
{
  "menue": {
    "menuetitel": "Untermenü 1",
    "wurzel": false,
    "vorgaenger": {
      "vtitel": "Hauptmenü",
      "vverweis": "hauptmenue.json"
    },
    "inhalt": [
      {
        "titel": "Benachrichtigung 1",
        "umenue": false,
        "funktion": "app.message_1"
      },
      {
        "titel": "noch mehr",
        "umenue": true,
        "verweis": "untermenue-2.json"
      },
      {
        "titel": "Benachrichtigung 2",
        "umenue": false,
        "funktion": "app.message_2"
      }
    ]
  }
}
web/ui2/data/menu/hauptmenue.json
New file
@@ -0,0 +1,27 @@
{
  "menue": {
    "menuetitel": "Hauptmenü",
    "wurzel": true,
    "vorgaenger": {
      "vtitel": "",
      "vverweis": ""
    },
    "inhalt":  [
      {
        "titel": "Datei",
        "umenue": true,
        "verweis": "datei.json"
      },
      {
        "titel": "Bearbeiten",
        "umenue": true,
        "verweis": "edit.json"
      },
      {
        "titel": "Nutzer",
        "umenue": true,
        "verweis": "nutzer.json"
      }
    ]
  }
}
web/ui2/data/menu/nutzer.json
New file
@@ -0,0 +1,27 @@
{
  "menue": {
    "menuetitel": "Untermenü 1",
    "wurzel": false,
    "vorgaenger": {
      "vtitel": "Hauptmenü",
      "vverweis": "hauptmenue.json"
    },
    "inhalt": [
      {
        "titel": "Benachrichtigung 1",
        "umenue": false,
        "funktion": "app.message_1"
      },
      {
        "titel": "noch mehr",
        "umenue": true,
        "verweis": "untermenue-2.json"
      },
      {
        "titel": "Benachrichtigung 2",
        "umenue": false,
        "funktion": "app.message_2"
      }
    ]
  }
}
web/ui2/data/menu/untermenue-1.json
New file
@@ -0,0 +1,27 @@
{
  "menue": {
    "menuetitel": "Untermenü 1",
    "wurzel": false,
    "vorgaenger": {
      "vtitel": "Hauptmenü",
      "vverweis": "hauptmenue.json"
    },
    "inhalt": [
      {
        "titel": "Benachrichtigung 1",
        "umenue": false,
        "funktion": "app.message_1"
      },
      {
        "titel": "noch mehr",
        "umenue": true,
        "verweis": "untermenue-2.json"
      },
      {
        "titel": "Benachrichtigung 2",
        "umenue": false,
        "funktion": "app.message_2"
      }
    ]
  }
}
web/ui2/data/menu/untermenue-2.json
New file
@@ -0,0 +1,27 @@
{
  "menue": {
    "menuetitel": "Untermenü 2",
    "wurzel": false,
    "vorgaenger": {
      "vtitel": "Untermenü 1",
      "vverweis": "untermenue-1.json"
    },
    "inhalt":  [
      {
        "titel": "Funktion U2.1",
        "umenue": false,
        "funktion": "app.message_3('U2.1')"
      },
      {
        "titel": "Funktion U2.2",
        "umenue": false,
        "funktion": "app.message_3('U2.2')"
      },
      {
        "titel": "Funktion U2.3",
        "umenue": false,
        "funktion": "app.message_3('U2.3')"
      }
    ]
  }
}
web/ui2/data/tpl/app-menu.tpl
New file
@@ -0,0 +1,20 @@
{{#menue}}
  <p class="app-menu-kopf">
    {{menuetitel}}
  </p>
  <ul class="app-menu">
    {{^wurzel}}
      <li class="app-menu-item-back bitem" data-verweis="{{vorgaenger.vverweis}}">
        &#10094; {{vorgaenger.vtitel}}</i>
      </li>
    {{/wurzel}}
    {{#inhalt}}
      {{#umenue}}
        <li class="app-menu-item smenu" data-verweis="{{verweis}}">{{titel}}&nbsp;&#10095;</li>
      {{/umenue}}
      {{^umenue}}
        <li class="app-menu-item mitem" data-verweis="{{funktion}}" >{{titel}}&nbsp;<span class="app-menu-item-submark">&#10095;</span></i></li>
      {{/umenue}}
    {{/inhalt}}
  </ul>
{{/menue}}
web/ui2/data/tpl/dlg-info.tpl
New file
@@ -0,0 +1,8 @@
<div class="dlg-info">
  <span class="close-btn pointer-cursor">&#10006;</span>
  <div class="dlg-behaelter">
    <div class="dlg-info-app-titel">app-vorlage</div>
    <div class="dlg-info-app-info">Eine Vorlage f&uuml;r Apps von <a href='https://uhilger.de'>Ulrich Hilger</a>.</div>
    <div class="dlg-info-app-info">Weitere Infos im <a href='/gitblit/docs/web!app-vorlage.git'>Code-Repository</a>.</div>
  </div>
</div>
web/ui2/font/pikto.ttf
Binary files differ
web/ui2/index.html
New file
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
  <head>
    <title>Dateiverwaltung</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <link href="https://fonts.googleapis.com/css?family=Roboto+Condensed" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="stile.css">
  </head>
  <body>
    <!-- Kopfzeile -->
    <div class="nord">
      <div id="nav-menu">
        <div id="nav-toggle" class="hamburger hamburger--elastic">
          <div class="hamburger-box">
            <div class="hamburger-inner"></div>
          </div>
        </div>
      </div>
      <div class="app-titel">
        <span id="app-titel">Dateiverwaltung</span>
      </div>
    </div>
    <div class="inhalt">
      <!-- westliche Seitenleiste -->
      <div class="west">
        westliche Seitenleiste
      </div>
      <div class="zentrum-behaelter">
        <!-- Einblendbereich -->
        <div class="dialog"></div>
        <!-- zentraler Inhaltsbereich -->
        <div class="zentrum">
          <div class="zentraler-inhalt">
            <p>
              Hier kommen die Dateien und Ordner hin.
            </p>
          </div>
        </div>
      </div>
      <!-- oestliche Seitenleiste -->
      <div class="ost ost-open">
        &ouml;stliche Seitenleiste
      </div>
    </div>
    <!-- Fusszeile -->
    <div class="sued sued-open">
      Fußzeile
    </div>
    <!-- Skripte -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script>
    <script src="/jslib/moment/moment-with-locales.min.js"></script>
    <script src="/jslib/numeral/numeral.min.js"></script>
    <script src="js/app-menu.js"></script>
    <script src="js/vorlagen.js"></script>
    <script src="js/app.js"></script>
    <script>
      var app;
      document.addEventListener('DOMContentLoaded', function () {
        app = new AppVorlage();
        app.init();
      });
    </script>
  </body>
</html>
web/ui2/js/app-menu.js
New file
@@ -0,0 +1,137 @@
function AppMenu() {
  var self = this;
  var _app_menu_selector;
  var _app_menu_mbreite;
  var _app_menu_url_prefix = "";
  var _app_menu_template;
  /*
   * die nachfolgenden Funktionen steuern das ein- und
   * ausblenden des menues
   */
  this.init = function (url_prefix, mdesc, mtpl, mselector, mbreite) {
    self._app_menu_selector = mselector;
    self._app_menu_mbreite = mbreite;
    var menu = document.querySelector(self._app_menu_selector);
    menu.style.flexBasis = '0em';
    self._app_menu_url_prefix = url_prefix;
    /*
      Die Menue-Vorlage wird einmal zu Beginn geladen und
      waehrend dem Programmlauf immer wieder neu zum Rendern
      einer dynamisch gelandenen Menuebeschreibung verwendet
    */
    var request = new XMLHttpRequest();
    request.open("GET", mtpl);
    request.addEventListener('load', function(event) {
       if (request.status >= 200 && request.status < 300) {
          self._app_menu_template = request.responseText;
          Mustache.parse(self._app_menu_template);   // optional, speeds up future uses
          self.app_menu_laden(mdesc);
       } else {
          console.warn(request.statusText, request.responseText);
       }
    });
    request.send();
  };
  this.app_menu_do_toggle = function(elem) {
    self.toggle();
  };
  this.toggle = function() {
    var menuDiv = document.querySelector(self._app_menu_selector);
    if(menuDiv.classList.contains('app-menu-open')) {
      menuDiv.classList.remove('app-menu-open');
      menuDiv.style.flexBasis = '0em';
    } else {
      menuDiv.classList.add('app-menu-open');
      menuDiv.style.flexBasis = self._app_menu_mbreite;
    }
  };
  /*
   * ab hier Steuerung des Menueinhalts
   */
  /*
   * Menuebeschreibung als JSON-Datei laden
   * mdesc: der URL einer JSON-Datei mit einer Menuebeschreibung
   * richtung: z.Zt. unbenutzt: Animationsrichtung
   */
  this.app_menu_laden = function(mdesc, richtung) {
    var xmlhttp = new XMLHttpRequest();
    var url = self._app_menu_url_prefix + mdesc;
    xmlhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        self.app_menu_bauen(JSON.parse(this.responseText), richtung);
      }
    };
    xmlhttp.open("GET", url, true);
    xmlhttp.send();
  };
  /*
    Aus einer Menuebeschreibung im JSON-Format mit Hilfe
    von Mustache und der zu Beginn geladenen HTML-Vorlage
    ein div-Element zusammenbauen, das als Menue eingeblendet
    werden kann und dem Element _app_menu_selector hinzufuegen
  */
  this.app_menu_bauen = function(menuejs, richtung) {
    // neues Menue als div-Element zusammensetzen
    var menuDiv = document.createElement("div");
    menuDiv.classList.add('app-menu-content');
    menuDiv.style.position = 'relative';
    menuDiv.innerHTML = Mustache.render(self._app_menu_template, menuejs);
    // altes Menue loeschen
    self.app_menu_remove_event_listener_multi('.smenu', 'click', self.app_menu_klick_herunter);
    self.app_menu_remove_event_listener_multi('.bitem', 'click', self.app_menu_klick_herauf);
    self.app_menu_remove_event_listener_multi('.mitem', 'click', self.app_menu_ausfuehren);
    var menu = document.querySelector(self._app_menu_selector);
    menu.innerHTML = '';
    // neues Menue hinzufuegen
    menu.append(menuDiv);
    self.app_menu_add_event_listener_multi('.smenu', 'click', self.app_menu_klick_herunter);
    self.app_menu_add_event_listener_multi('.bitem', 'click', self.app_menu_klick_herauf);
    self.app_menu_add_event_listener_multi('.mitem', 'click', self.app_menu_ausfuehren);
    menuDiv = document.querySelector('.app-menu-content');
    menuDiv.classList.add('slidein-from-right');
  };
  this.app_menu_klick_herunter = function() {
    self.app_menu_laden(this.getAttribute('data-verweis'), 'herunter');
  };
  this.app_menu_klick_herauf = function() {
    self.app_menu_laden(this.getAttribute('data-verweis'), 'herauf');
  };
  this.app_menu_ausfuehren = function() {
    var functionName = this.getAttribute('data-verweis');
    eval(functionName + "(this)");
  };
  /* --- Helferlein ---*/
  /*
    sel - '.smenu'
    evt - 'click' fuer onclick
    func - der verweis auf die funktion
  */
  this.app_menu_remove_event_listener_multi = function(sel, evt, func) {
    var elem = document.querySelectorAll(sel);
    for (var index = 0; index < elem.length; index++) {
      elem[index].removeEventListener(evt, func);
    }
  };
  this.app_menu_add_event_listener_multi = function(sel, evt, func) {
    var elem = document.querySelectorAll(sel);
    for (var index = 0; index < elem.length; index++) {
      elem[index].addEventListener(evt, func);
    }
  };
}
web/ui2/js/app.js
New file
@@ -0,0 +1,208 @@
function AppVorlage() {
  var self = this;
  var appMenu;
  var vorlagen;
  var api;
  this.datei_neuer_text = function() {
    self.menu_message("Neuer Text");
  };
  /* Funktionen aus App-Vorlage */
  this.init = function() {
    self.vorlagen = new Vorlagen();
    self.appMenu = new AppMenu();
    self.appMenu.init(
      "data/menu/",
      "hauptmenue.json",
      "data/tpl/app-menu.tpl",
      ".west",
      "8em");
    document.querySelector('.hamburger').addEventListener('click', function(e) {
      self.menue_umschalten();
    });
  };
  this.menue_umschalten = function() {
    var ham = document.querySelector(".hamburger");
    ham.classList.toggle("is-active"); // hamburger-icon umschalten
    self.appMenu.toggle(); // menue oeffnen/schliessen
  };
  this.info_dialog_zeigen = function() {
    self.dialog_laden_und_zeigen('data/tpl/dlg-info.tpl', '');
    self.menue_umschalten();
  };
  this.seitenleiste_umschalten = function() {
    var ostDiv = document.querySelector('.ost');
    if(ostDiv.classList.contains('ost-open')) {
      ostDiv.classList.remove('ost-open');
        ostDiv.style.flexBasis = '0em';
    } else {
        ostDiv.classList.add('ost-open');
        ostDiv.style.flexBasis = '6em';
    }
    self.menue_umschalten();
  };
  this.fusszeile_umschalten = function() {
    var suedDiv = document.querySelector('.sued');
    if(suedDiv.classList.contains('sued-open')) {
      suedDiv.classList.remove('sued-open');
        suedDiv.style.height = '0';
    } else {
      suedDiv.classList.add('sued-open');
        suedDiv.style.height = '1.5em';
    }
    self.menue_umschalten();
  };
  this.menu_message = function(msg) {
    self.meldung_mit_timeout(msg, 1500);
    var suedDiv = document.querySelector('.sued');
    if(suedDiv.classList.contains('sued-open')) {
    } else {
      suedDiv.classList.add('sued-open');
        suedDiv.style.height = '1.5em';
    }
    self.menue_umschalten();
  };
  this.message_1 = function() {
    self.menu_message('Eine Mitteilung.');
  };
  this.message_2 = function() {
    self.menu_message('Was wir schon immer sagen wollten.');
  };
  this.message_3 = function(text) {
    self.menu_message(text);
  };
  this.meldung_mit_timeout = function(meldung, timeout) {
    var s = document.querySelector('.sued');
    s.textContent = meldung;
    setTimeout(function() {
      s.textContent = 'Bereit.';
      setTimeout(function() {
        var suedDiv = document.querySelector('.sued');
        if(suedDiv.classList.contains('sued-open')) {
              suedDiv.classList.remove('sued-open');
              suedDiv.style.height = '0';
        }
      }, 500);
    }, timeout);
  };
  /* Dialog-Funktionen */
  /*
    Einen Dialog aus Vorlagen erzeugen
    vurl - URL zur Dialogvorlage
    msgTpl - URL mit einer Vorlage eines Mitteilungstextes (optional)
  */
  this.dialog_laden_und_zeigen = function(vurl, msgTpl) {
    if(msgTpl !== '') {
      fetch(msgTpl)
        .then(data => {
          // Handle data
          self.dialog_zeigen(vurl, data);
        }).catch(error => {
          // Handle error
        });
    } else {
      self.dialog_zeigen(vurl, '');
    }
  };
  this.dialog_zeigen = function(vurl, inhalt) {
    var dlg = document.querySelector(".dialog");
    self.vorlagen.html_erzeugen(
      vurl,
      inhalt,
      function(html) {
        //dlg.html(html);
        dlg.style.height = '5em';
        dlg.innerHTML = html;
        document.querySelector('.close-btn').addEventListener('click', self.dialog_schliessen);
        //dlg.slideDown(300);
    });
  };
  self.dialog_schliessen = function() {
    document.querySelector('.close-btn').removeEventListener('click', self.dialog_schliessen);
    //$('.dialog').slideUp(300);
    var dlg = document.querySelector('.dialog');
    //dlg.style.display = "none";
    dlg.style.height = '0';
    dlg.innerHTML = '';
  };
    /* API functions */
    // http://localhost:8079/file-cms/svc?c=de.uhilger.filecms.api.FileMgr&f=JSONNICE&m=list&p=
    this.fm_get_list = function(relPfad) {
      $('#ansicht').attr('onclick','').unbind('click');
      var m = '?c=de.uhilger.filecms.api.FileMgr&m=list&p=' + relPfad;
      var u = '../svc' + m;
      self.fm_get(u, "json", function(resp) {
        if(resp.List[0].FileRef !== undefined) {
          var files = new Array();
          if(resp.List[0].FileRef instanceof Array) {
            for(var i = 0; i < resp.List[0].FileRef.length; i++) {
              files.push(new FileRef(resp.List[0].FileRef[i]));
            }
          } else {
            files.push(new FileRef(resp.List[0].FileRef));
          }
          var fl = new FileList(files);
          fm_render_list(fl);
        } else {
          $('#dateien').empty();
        }
      });
    };
/* -------- ajax helper functions ----------- */
    this.fm_get = function(u, dtype, scallback) {
      $.ajax({
        url: u,
        type: "GET",
        dataType: dtype,
        success: scallback,
        error: function (xhr, status, errorThrown) {
          alert("Error: " + errorThrown + " Status: " + status + " URL: " + u);
        },
        complete: function (xhr, status) {
          //console.log( "The request is complete!" );
        }
      });
    };
    this.fm_post = function(u, d, dtype, scallback) {
      $.ajax({
        url: u,
        data: d,
        type: "POST",
        dataType: dtype,
        success: scallback,
        error: function (xhr, status, errorThrown) {
          $('#fehler').html("Error: " + errorThrown + " Status: " + status);
        },
        complete: function (xhr, status) {
          //alert( "The request is complete!" );
        }
      });
    };
}
web/ui2/js/data.js
New file
@@ -0,0 +1,148 @@
/*
 Dateiverwaltung - File management in your browser
 Copyright (C) 2017 Ulrich Hilger, http://uhilger.de
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.
 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * In data.js finden sich die Objekte vom Browser-Client
 * der Dateiverwaltung
 */
/* ----- Objekte ----- */
function IssueList(il) {
  this.issues = il;
}
function CompilerIssue(sn, ms, ki, ln) {
  var self = this;
  this.sourceName = sn;
  this.message = ms;
  this.kind = ki;
  this.lineNumber = ln;
}
function FileList(fl) {
  this.files = fl;
}
function FileRef(obj) {
  var self = this;
  this.fr = obj;
  this.fnx;
  this.fext = '';
  this.typeClass = function () {
    if (modus == 'kacheln') {
      if (self.fr.isDirectory) {
        return 'fa-folder ordner';
      } else {
        return 'fa-file datei';
      }
    } else {
      if (self.fr.isDirectory) {
        return 'fa-folder ordner';
      } else {
        return 'fa-file-o datei';
      }
    }
  };
  this.mini = function () {
    var miniatur = false;
    var namen = self.fr.absolutePath.split('/');
    if (namen.length > 0) {
      self.fnx = decodeURIComponent(namen[namen.length - 1]);
    } else {
      self.fnx = decodeURIComponent(self.fr.absolutePath);
    }
    if (self.fnx.indexOf('.jpg') > -1 || self.fnx.indexOf('.png') > -1 ||
            self.fnx.indexOf('.gif') > -1 || self.fnx.indexOf('.jpeg') > -1) {
      miniatur = true;
    }
    return miniatur;
  };
  this.dia = function () {
    return fm_slideshow;
  };
  this.miniurl = function () {
    var userid = $('#userMenu').text();
    if (self.fext === '') {
      //self.fext = '';
      var dotpos = self.fnx.indexOf('.');
      if (dotpos > -1) {
        var fny = self.fnx;
        self.fnx = self.fnx.substring(0, dotpos);
        self.fext = fny.substr(dotpos);
      }
    }
    var path = fm_get_path(userid);
    var imgurl = loc + path + '/' + self.fnx + '_tn' + self.fext;
    return imgurl;
  };
  this.bildurl = function () {
    var userid = $('#userMenu').text();
    if (self.fext === '') {
      //self.fext = '';
      var dotpos = self.fnx.indexOf('.');
      if (dotpos > -1) {
        var fny = self.fnx;
        self.fnx = self.fnx.substring(0, dotpos);
        self.fext = fny.substr(dotpos);
      }
    }
    var path = fm_get_path(userid);
    var imgurl = loc + path + '/' + self.fnx + self.fext;
    return imgurl;
  };
  this.fileName = function () {
    var namen = self.fr.absolutePath.split('/');
    if (namen.length > 0) {
      return decodeURIComponent(namen[namen.length - 1]);
    } else {
      return decodeURIComponent(self.fr.absolutePath);
    }
  };
  this.fileDate = function () {
    return moment(self.fr.lastModified).format("YYYY-MM-DD-hh-mm-ss-SSS");
  };
  this.fileSize = function () {
    return numeral(self.fr.length).format("0.00 b");
  };
}
function BcrFiles(fl) {
  this.files = fl;
}
function BcrFile(rp, n) {
  this.relPath = rp;
  this.fName = n;
}
web/ui2/js/vorlagen.js
New file
@@ -0,0 +1,66 @@
function Vorlagen() {
  var self = this;
  this.cache = {}; // mustache templates
  /*
    Das HTML erzeugen, das entsteht, wenn eine Vorlage mit Inhalt
    gefüllt wird
    Das Füllen erfolgt asynchron, d.h. der Programmlauf geht nach dem
    Aufruf weiter ohne auf das Laden und Füllen der Vorlage zu warten.
    Das fertige HTML wird der Callback-Funktion übergeben
    sobald die Vorlage geladen und gefüllt ist, unabhängig davon, wo der
    Programmlauf zu diesem Zeitpunkt mittlerweile ist.
    vurl - URL zur Vorlagendatei
    inhalt - die JSON-Struktur, deren Inhalt in die
              Vorlage gefüllt werden soll
    cb - Callback-Funktion, die gerufen wird, wenn die Vorlage gefüllt ist.
          Dieser Callback-Funktion wird das fertige HTML übergeben
  */
  this.html_erzeugen = function(vurl, inhalt, cb) {
    var vorlage = self.cache[vurl];
    if(vorlage === undefined) {
      self.vorlage_laden_und_fuellen(vurl, inhalt, cb);
    } else {
      self.vorlage_fuellen(vurl, inhalt, cb);
    }
  };
  this.vorlage_fuellen = function(vurl, inhalt, cb) {
    cb(Mustache.render(self.cache[vurl], inhalt));
  };
  /*
    Eine Vorlage vom Server in den lokalen Speicher laden
    vurl - der URL unter dem die Vorlage zu finden ist
    inhalt - die JSON-Struktur, deren Inhalt in die
              Vorlage gefüllt werden soll
    cb - callback: Diese Funktion wird gerufen, wenn die Vorlage mit dem
            Inhalt gefüllt ist
  */
  this.vorlage_laden_und_fuellen = function(vurl, inhalt, cb) {
    /*
    $.ajax({
      url: vurl,
      type: "GET",
      dataType : "text"
    }).done(function( vorlage ) {
      self.cache[vurl] = vorlage;
      self.vorlage_fuellen(vurl, inhalt, cb);
    });
    */
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        self.cache[vurl] = this.responseText;
        self.vorlage_fuellen(vurl, inhalt, cb);
      }
    };
    xmlhttp.open("GET", vurl, true);
    xmlhttp.send();
  };
}
web/ui2/stile.css
New file
@@ -0,0 +1,316 @@
/*
    Created on : 24.01.2020, 09:08:45
    Author     : ulrich
*/
/* aus App-Vorlage */
html, body {
  margin: 0;
  padding: 0;
  height: 100%; /* Anmerkung 2 */
  font-size: larger;
  font-family: 'Roboto Condensed';
}
body {
  min-height: 0; /* Anmerkung 1 */
  display: flex;
  flex-flow: column;
}
.inhalt {
  display: flex;
  flex-flow: row;
  height: 100%; /* Anmerkung 2 */
  min-height: 0; /* Anmerkung 1 */
  background-color: #ededed;
  overflow: hidden;
}
.nord {
  background-color: black;
  display: flex;
  flex-flow: row;
  height: 2em;
  align-items: center;
}
.sued {
  height: 1.5em;
  overflow: hidden;
  transition: all 0.3s ease-in;
  background-color: lightgray;
}
.west {
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: 4em;
  background-color: white;
  transition: all 0.3s ease-in;
  overflow: hidden;
  white-space: nowrap;
}
.ost {
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: 6em;
  transition: all 0.3s ease-in;
  background-color: antiquewhite;
  overflow: hidden;
}
.zentrum-behaelter {
  display: flex;
  flex-flow: column;
  /* background-color: #eaeaea; */
  width: 100%;
}
.zentrum {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.zentraler-inhalt {
  padding: 0.5em;
}
/*
  Anmerkungen:
  1.) min.height: 0 fuer body und inhalt ist gegen einen Bug, vgl.
      http://stackoverflow.com/questions/33859811/understanding-flexbox-and-overflowauto
  2.) height 100% fuer html, body und inhalt sorgt dafuer, dass sich alles
      immer ueber das gesamte Browserfenster ausdehnt.
*/
.app-titel {
  margin-left: 0.6em;
  color: white;
}
.pointer-cursor {
  cursor: pointer;
}
.dialog {
  position: relative;
  /* height: 0.1em; */
  transition: all 0.3s ease-in;
}
.dlg-behaelter {
  line-height: 1.6;
  padding: 0.4em;
}
.dlg-info {
  background-color: #dcf2fb; /* blau */
  padding: 0.4em;
}
/*
  Close Button
  <div>
    <span class="close-btn">&#10006;</span>
  </div>
*/
.close-btn {
  position: absolute;
  top: 0px;
  right: 0.4em;
  margin: 0;
  padding: 0;
  font-size: 1.3em;
  color: #b8b8b8;
}
/* für app-menu */
.app-menu {
  margin: 0;
  padding: 0;
}
.app-menu-kopf {
  text-align: center;
}
ul.app-menu {
    list-style: none;
}
.app-menu-item-back {
  margin-bottom: 0.3em;
  cursor: pointer;
}
.app-menu-item {
  text-align: right;
  cursor: pointer;
}
.app-menu-item-submark {
  color: transparent;
  cursor: pointer;
}
/*
  Das div-Element, das das Menue aufnimmt erhaelt
  die Klasse app-menu-content
*/
.app-menu-content {
  overflow: hidden;
}
/* für Hamburger Icon */
.hamburger {
  display: inline-block;
  cursor: pointer;
  transition-property: opacity, filter;
  transition-duration: 0.15s;
  transition-timing-function: linear;
  font: inherit;
  color: inherit;
  text-transform: none;
  background-color: transparent;
  border: 0;
  margin: 0;
  overflow: visible;
}
.hamburger:hover {
  opacity: 0.7;
}
.hamburger-box {
  width: 40px;
  height: 24px;
  display: inline-block;
  position: relative;
}
.hamburger-inner {
  display: block;
  top: 50%;
  margin: 0;
}
.hamburger-inner, .hamburger-inner::before, .hamburger-inner::after {
  width: 30px;
  height: 4px;
  background-color: white; /* #000; */
  border-radius: 4px;
  position: absolute;
  transition-property: transform;
  transition-duration: 0.15s;
  transition-timing-function: ease;
}
.hamburger-inner::before, .hamburger-inner::after {
  content: "";
  display: block;
}
.hamburger-inner::before {
  top: -10px;
}
.hamburger-inner::after {
  bottom: -10px;
}
/*
 * Elastic
 */
.hamburger--elastic .hamburger-inner {
  top: 2px;
  transition-duration: 0.275s;
  transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.hamburger--elastic .hamburger-inner::before {
  top: 10px;
  transition: opacity 0.125s 0.275s ease;
}
.hamburger--elastic .hamburger-inner::after {
  top: 20px;
  transition: transform 0.275s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.hamburger--elastic.is-active .hamburger-inner {
  transform: translate3d(0, 10px, 0) rotate(135deg);
  transition-delay: 0.075s;
}
.hamburger--elastic.is-active .hamburger-inner::before {
  transition-delay: 0s;
  opacity: 0;
}
.hamburger--elastic.is-active .hamburger-inner::after {
  transform: translate3d(0, -20px, 0) rotate(-270deg);
  transition-delay: 0.075s;
}
/* Font für Piktogramme mit Fontello aus FontAwesome erzeugt */
@font-face {
  font-family: 'pikto';
  src: url('font/pikto.ttf?37040783') format('truetype');
  font-weight: normal;
  font-style: normal;
}
 [class^="icon-"]:before, [class*=" icon-"]:before {
  font-family: "pikto";
  font-style: normal;
  font-weight: normal;
  speak: none;
  display: inline-block;
  text-decoration: inherit;
  width: 1em;
  margin-right: .2em;
  text-align: center;
  /* opacity: .8; */
  /* For safety - reset parent styles, that can break glyph codes*/
  font-variant: normal;
  text-transform: none;
  /* fix buttons height, for twitter bootstrap */
  line-height: 1em;
  /* Animation center compensation - margins should be symmetric */
  /* remove if not needed */
  margin-left: .2em;
  /* you can be more comfortable with increased icons size */
  /* font-size: 120%; */
  /* Font smoothing. That was taken from TWBS */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Uncomment for 3D effect */
  /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-folder:before { content: '\e800'; } /* '' */
.icon-folder-open:before { content: '\e801'; } /* '' */
.icon-th-large:before { content: '\e802'; } /* '' */
.icon-th-list:before { content: '\e803'; } /* '' */
.icon-doc-text:before { content: '\f0f6'; } /* '' */
.icon-folder-empty:before { content: '\f114'; } /* '' */
.icon-folder-open-empty:before { content: '\f115'; } /* '' */
.icon-doc-inv:before { content: '\f15b'; } /* '' */
.icon-doc-text-inv:before { content: '\f15c'; } /* '' */