Просмотр исходного кода

WEB UI: HREF for Upload, Refresh button, and CSS fixes

MatthewHana 1 год назад
Родитель
Сommit
e66055de08

+ 71 - 54
radicale/web/internal_data/css/loading.svg

@@ -1,55 +1,72 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; display: block;" width="264px" height="264px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
-<g transform="rotate(0 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#4e9a06">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.8026755852842808s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(27.692307692307693 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#71cc1a">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.7357859531772575s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(55.38461538461539 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#8ce139">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.6688963210702341s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(83.07692307692308 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#cdff9c">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.6020066889632106s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(110.76923076923077 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#cdf7a6">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.5351170568561873s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(138.46153846153845 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#fcfcfc">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.46822742474916385s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(166.15384615384616 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#fefefe">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.4013377926421404s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(193.84615384615384 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#f4f4f4">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.33444816053511706s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(221.53846153846155 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#ffd6d6">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.26755852842809363s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(249.23076923076923 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#f86f6f">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.2006688963210702s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(276.9230769230769 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#e73c3c">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.13377926421404682s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(304.61538461538464 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#da2121">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.06688963210702341s" repeatCount="indefinite"></animate>
-  </rect>
-</g><g transform="rotate(332.3076923076923 50 50)">
-  <rect x="45.5" y="32" rx="0" ry="0" width="9" height="4" fill="#a40000">
-    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="0s" repeatCount="indefinite"></animate>
-  </rect>
-</g>
+<svg xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
+    <g transform="matrix(10.8 0 0 10.8 540 540)">
+        <g style="">
+            <g transform="matrix(2.64 0 0 2.64 0 -42.24)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(78,154,6); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.8026755852842808s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(2.34 1.23 -1.23 2.34 19.63 -37.4)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(113,204,26); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.7357859531772575s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(1.5 2.17 -2.17 1.5 34.76 -24)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(140,225,57); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.6688963210702341s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(0.32 2.62 -2.62 0.32 41.93 -5.09)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(205,255,156); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.6020066889632106s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(-0.94 2.47 -2.47 -0.94 39.5 14.98)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(205,247,166); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.5351170568561873s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(-1.98 1.75 -1.75 -1.98 28.01 31.62)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(252,252,252); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.46822742474916385s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(-2.56 0.63 -0.63 -2.56 10.11 41.01)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(254,254,254); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.4013377926421404s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(-2.56 -0.63 0.63 -2.56 -10.11 41.01)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(244,244,244); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.33444816053511706s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(-1.98 -1.75 1.75 -1.98 -28.01 31.62)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,214,214); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.26755852842809363s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(-0.94 -2.47 2.47 -0.94 -39.5 14.98)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(248,111,111); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.2006688963210702s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(0.32 -2.62 2.62 0.32 -41.93 -5.09)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(231,60,60); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.13377926421404682s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(1.5 -2.17 2.17 1.5 -34.76 -24)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(218,33,33); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="-0.06688963210702341s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+            <g transform="matrix(2.34 -1.23 1.23 2.34 -19.63 -37.4)">
+                <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(164,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-4.5" y="-2" rx="0" ry="0" width="9" height="4">
+                    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="0.8695652173913042s" begin="0s" repeatCount="indefinite"></animate>
+                </rect>
+            </g>
+        </g>
+    </g>
 </svg>

+ 31 - 11
radicale/web/internal_data/css/main.css

@@ -103,11 +103,20 @@ main{
     color: white;
     text-decoration: none;
     padding: 3px 10px;
-    position: absolute;
-    right: 25px;
+    position: relative;
     border-radius: 4px;
 }
 
+#logoutview a[data-name=logout]{
+    right: 25px;
+    float: right;
+}
+
+#logoutview a[data-name=refresh]{
+    left: 25px;
+    float: left;
+}
+
 #collectionsscene{
     display: flex;
     flex-direction: row;
@@ -116,11 +125,11 @@ main{
     align-items: center;
     margin-top: 50px;
     width: 100%;
-    height: calc(100vh - 50px);
+    height: 100vh;
 }
 
 #collectionsscene article{
-    width: 250px;
+    width: 275px;
     background: rgb(250, 250, 250);
     border-radius: 8px;
     box-shadow: 2px 2px 3px #0000001a;
@@ -129,7 +138,7 @@ main{
     padding-top: 0;
     margin: 10px;
     float: left;
-    height: 350px;
+    min-height: 375px;
     overflow: hidden;
 }
 
@@ -170,11 +179,12 @@ main{
 }
 
 #collectionsscene article:hover ul{
-    display: flex !important;
+    visibility: visible;
 }
 
 #collectionsscene ul{
-    display: none;
+    visibility: hidden;
+    display: flex;
     justify-content: space-evenly;
     width: 60%;
     margin: 0 20%;
@@ -216,7 +226,7 @@ main{
 #uploadcollectionscene ul{
     margin: 10px -30px;
     max-height: 600px;
-    overflow: overlay;
+    overflow-y: scroll;
 }
 
 #uploadcollectionscene li{
@@ -225,6 +235,11 @@ main{
     padding-bottom: 10px;
 }
 
+#uploadcollectionscene div[data-name=pending]{
+    width: 100%;
+    text-align: center;
+}
+
 #uploadcollectionscene .successmessage{
     color: #4e9a06;
     width: 100%;
@@ -291,6 +306,11 @@ main{
     padding-top: 15px;
 }
 
+img.loading{
+    width: 150px;
+    height: 150px;
+}
+
 .error::before{
     content: "!";
     height: 1em;
@@ -391,7 +411,7 @@ button.blue:active, a.blue:active{
 
     #collectionsscene article{
         height: auto;
-        min-height: 350px;
+        min-height: 375px;
     }
 
     .container{
@@ -399,10 +419,10 @@ button.blue:active, a.blue:active{
     }
 
     #collectionsscene ul{
-        display: flex !important;
+        visibility: visible !important;
     }
 
     #logoutview span{
-        text-align: left;
+        padding: 0 5px;
     }
 }

+ 152 - 84
radicale/web/internal_data/fn.js

@@ -542,7 +542,8 @@ function LoginScene() {
     let error_form = html_scene.querySelector("[data-name=error]");
     let logout_view = document.getElementById("logoutview");
     let logout_user_form = logout_view.querySelector("[data-name=user]");
-    let logout_btn = logout_view.querySelector("[data-name=link]");
+    let logout_btn = logout_view.querySelector("[data-name=logout]");
+    let refresh_btn = logout_view.querySelector("[data-name=refresh]");
 
     /** @type {?number} */ let scene_index = null;
     let user = "";
@@ -573,6 +574,7 @@ function LoginScene() {
                 // setup logout
                 logout_view.classList.remove("hidden");
                 logout_btn.onclick = onlogout;
+                refresh_btn.onclick = refresh;
                 logout_user_form.textContent = user + "'s Collections";
                 // Fetch principal
                 let loading_scene = new LoadingScene();
@@ -623,9 +625,17 @@ function LoginScene() {
     function remove_logout() {
         logout_view.classList.add("hidden");
         logout_btn.onclick = null;
+        refresh_btn.onclick = null;
         logout_user_form.textContent = "";
     }
 
+    function refresh(){
+        //The easiest way to refresh is to push a LoadingScene onto the stack and then pop it
+        //forcing the scene below it, the Collections Scene to refresh itself.
+        push_scene(new LoadingScene(), false);
+        pop_scene(scene_stack.length-2);
+    }
+
     this.show = function() {
         remove_logout();
         fill_form();
@@ -684,12 +694,6 @@ function CollectionsScene(user, password, collection, onerror) {
     /** @type {?XMLHttpRequest} */ let collections_req = null;
     /** @type {?Array<Collection>} */ let collections = null;
     /** @type {Array<Node>} */ let nodes = [];
-    let filesInput = document.createElement("input");
-    filesInput.setAttribute("type", "file");
-    filesInput.setAttribute("accept", ".ics, .vcf");
-    filesInput.setAttribute("multiple", "");
-    let filesInputForm = document.createElement("form");
-    filesInputForm.appendChild(filesInput);
 
     function onnew() {
         try {
@@ -702,17 +706,9 @@ function CollectionsScene(user, password, collection, onerror) {
     }
 
     function onupload() {
-        filesInput.click();
-        return false;
-    }
-
-    function onfileschange() {
         try {
-            let files = filesInput.files;
-            if (files.length > 0) {
-                let upload_scene = new UploadCollectionScene(user, password, collection, files);
-                push_scene(upload_scene);
-            }
+            let upload_scene = new UploadCollectionScene(user, password, collection);
+            push_scene(upload_scene);
         } catch(err) {
             console.error(err);
         }
@@ -740,6 +736,9 @@ function CollectionsScene(user, password, collection, onerror) {
     }
 
     function show_collections(collections) {
+        let heightOfNavBar = document.querySelector("#logoutview").offsetHeight + "px";
+        html_scene.style.marginTop = heightOfNavBar;
+        html_scene.style.height = "calc(100vh - " + heightOfNavBar +")";
         collections.forEach(function (collection) {
             let node = template.cloneNode(true);
             node.classList.remove("hidden");
@@ -820,8 +819,6 @@ function CollectionsScene(user, password, collection, onerror) {
         html_scene.classList.remove("hidden");
         new_btn.onclick = onnew;
         upload_btn.onclick = onupload;
-        filesInputForm.reset();
-        filesInput.onchange = onfileschange;
         if (collections === null) {
             update();
         } else {
@@ -834,7 +831,6 @@ function CollectionsScene(user, password, collection, onerror) {
         scene_index = scene_stack.length - 1;
         new_btn.onclick = null;
         upload_btn.onclick = null;
-        filesInput.onchange = null;
         collections = null;
         // remove collection
         nodes.forEach(function(node) {
@@ -849,7 +845,6 @@ function CollectionsScene(user, password, collection, onerror) {
             collections_req = null;
         }
         collections = null;
-        filesInputForm.reset();
     };
 }
 
@@ -861,41 +856,87 @@ function CollectionsScene(user, password, collection, onerror) {
  * @param {Collection} collection parent collection
  * @param {Array<File>} files
  */
-function UploadCollectionScene(user, password, collection, files) {
+function UploadCollectionScene(user, password, collection) {
     let html_scene = document.getElementById("uploadcollectionscene");
     let template = html_scene.querySelector("[data-name=filetemplate]");
+    let upload_btn = html_scene.querySelector("[data-name=submit]");
     let close_btn = html_scene.querySelector("[data-name=close]");
+    let uploadfile_form = html_scene.querySelector("[data-name=uploadfile]");
+    let uploadfile_lbl = html_scene.querySelector("label[for=uploadfile]");
+    let href_form = html_scene.querySelector("[data-name=href]");
+    let href_label = html_scene.querySelector("label[for=href]");
+    let hreflimitmsg_html = html_scene.querySelector("[data-name=hreflimitmsg]");
+    let pending_html = html_scene.querySelector("[data-name=pending]");
+
+    let files = uploadfile_form.files;
+    href_form.addEventListener("keydown", cleanHREFinput);
+    upload_btn.onclick = upload_start;
+    uploadfile_form.onchange = onfileschange;
+
+    let href = random_uuid();
+    href_form.value = href;
 
     /** @type {?number} */ let scene_index = null;
     /** @type {?XMLHttpRequest} */ let upload_req = null;
-    /** @type {Array<string>} */ let errors = [];
+    /** @type {Array<string>} */ let results = [];
     /** @type {?Array<Node>} */ let nodes = null;
 
-    function upload_next() {
+    function upload_start() {
         try {
-            if (files.length === errors.length) {
-                if (errors.every(error => error === null)) {
-                    pop_scene(scene_index - 1);
-                } else {
-                    close_btn.classList.remove("hidden");
-                }
+            if(!read_form()){
+                return false;
+            }
+            uploadfile_form.classList.add("hidden");
+            uploadfile_lbl.classList.add("hidden");
+            href_form.classList.add("hidden");
+            href_label.classList.add("hidden");
+            hreflimitmsg_html.classList.add("hidden");
+            upload_btn.classList.add("hidden");
+            close_btn.classList.add("hidden");
+
+            pending_html.classList.remove("hidden");
+
+            nodes = [];
+            for (let i = 0; i < files.length; i++) {
+                let file = files[i];
+                let node = template.cloneNode(true);
+                node.classList.remove("hidden");
+                let name_form = node.querySelector("[data-name=name]");
+                name_form.textContent = file.name;
+                node.classList.remove("hidden");
+                nodes.push(node);
+                updateFileStatus(i);
+                template.parentNode.insertBefore(node, template);
+            }
+            upload_next();
+        } catch(err) {
+            console.error(err);
+        }
+        return false;
+    }
+
+    function upload_next(){
+        try{
+            if (files.length === results.length) {
+                pending_html.classList.add("hidden");
+                close_btn.classList.remove("hidden");
+                return;
             } else {
-                let file = files[errors.length];
-                let upload_href = collection.href + random_uuid() + "/";
-                upload_req = upload_collection(user, password, upload_href, file, function(error) {
-                    if (scene_index === null) {
-                        return;
-                    }
+                let file = files[results.length];
+                if(files.length > 1 || href.length == 0){
+                    href = random_uuid();
+                }
+                let upload_href = collection.href + "/" + href + "/";
+                upload_req = upload_collection(user, password, upload_href, file, function(result) {
                     upload_req = null;
-                    errors.push(error);
-                    updateFileStatus(errors.length - 1);
+                    results.push(result);
+                    updateFileStatus(results.length - 1);
                     upload_next();
                 });
             }
-        } catch(err) {
+        }catch(err){
             console.error(err);
         }
-        return false;
     }
 
     function onclose() {
@@ -911,54 +952,77 @@ function UploadCollectionScene(user, password, collection, files) {
         if (nodes === null) {
             return;
         }
-        let pending_form = nodes[i].querySelector("[data-name=pending]");
         let success_form = nodes[i].querySelector("[data-name=success]");
         let error_form = nodes[i].querySelector("[data-name=error]");
-        if (errors.length > i) {
-            pending_form.classList.add("hidden");
-            if (errors[i]) {
+        if (results.length > i) {
+            if (results[i]) {
                 success_form.classList.add("hidden");
-                error_form.textContent = "Error: " + errors[i];
+                error_form.textContent = "Error: " + results[i];
                 error_form.classList.remove("hidden");
             } else {
               success_form.classList.remove("hidden");
               error_form.classList.add("hidden");
             }
         } else {
-            pending_form.classList.remove("hidden");
             success_form.classList.add("hidden");
             error_form.classList.add("hidden");
         }
     }
 
-    this.show = function() {
-        html_scene.classList.remove("hidden");
-        if (errors.length < files.length) {
-            close_btn.classList.add("hidden");
+    function read_form() {
+        cleanHREFinput(href_form);
+        let newhreftxtvalue = href_form.value.trim().toLowerCase();
+        if(!isValidHREF(newhreftxtvalue)){
+            alert("You must enter a valid HREF");
+            return false;
         }
-        close_btn.onclick = onclose;
-        nodes = [];
-        for (let i = 0; i < files.length; i++) {
-            let file = files[i];
-            let node = template.cloneNode(true);
-            node.classList.remove("hidden");
-            let name_form = node.querySelector("[data-name=name]");
-            name_form.textContent = file.name;
-            node.classList.remove("hidden");
-            nodes.push(node);
-            updateFileStatus(i);
-            template.parentNode.insertBefore(node, template);
+        href = newhreftxtvalue;
+
+        if(uploadfile_form.files.length == 0){
+            alert("You must select at least one file to upload");
+            return false;
         }
-        if (scene_index === null) {
-            scene_index = scene_stack.length - 1;
-            upload_next();
+        files = uploadfile_form.files;
+        return true;
+    }
+
+    function onfileschange() {
+        files = uploadfile_form.files;
+        if(files.length > 1){
+            hreflimitmsg_html.classList.remove("hidden");
+            href_form.classList.add("hidden");
+            href_label.classList.add("hidden");    
+        }else{
+            hreflimitmsg_html.classList.add("hidden");
+            href_form.classList.remove("hidden");
+            href_label.classList.remove("hidden");    
         }
+        return false;
+    }
+
+    this.show = function() {
+        scene_index = scene_stack.length - 1;
+        html_scene.classList.remove("hidden");
+        close_btn.onclick = onclose;
     };
 
     this.hide = function() {
         html_scene.classList.add("hidden");
         close_btn.classList.remove("hidden");
+        upload_btn.classList.remove("hidden");
+        uploadfile_form.classList.remove("hidden");
+        uploadfile_lbl.classList.remove("hidden");
+        href_form.classList.remove("hidden");
+        href_label.classList.remove("hidden");
+        hreflimitmsg_html.classList.add("hidden");
+        pending_html.classList.add("hidden");
         close_btn.onclick = null;
+        upload_btn.onclick = null;
+        href_form.value = "";
+        uploadfile_form.value = "";
+        if(nodes == null){
+            return;
+        }
         nodes.forEach(function(node) {
             node.parentNode.removeChild(node);
         });
@@ -1142,7 +1206,7 @@ function CreateEditCollectionScene(user, password, collection) {
 
     function read_form() {
         if(!edit){
-            cleanHREFinput();
+            cleanHREFinput(href_form);
             let newhreftxtvalue = href_form.value.trim().toLowerCase();
             if(!isValidHREF(newhreftxtvalue)){
                 alert("You must enter a valid HREF");
@@ -1226,12 +1290,6 @@ function CreateEditCollectionScene(user, password, collection) {
         return false;
     }
 
-    function cleanHREFinput(event){
-        let currentTxtVal = href_form.value.trim().toLowerCase();
-        //Clean the HREF to remove non lowercase letters and dashes
-        currentTxtVal = currentTxtVal.replace(/(?![0-9a-z\-\_])./g, '');
-        href_form.value = currentTxtVal;
-    }
 
     function onTypeChange(e){
         if(type_form.value == CollectionType.WEBCAL){
@@ -1243,17 +1301,6 @@ function CreateEditCollectionScene(user, password, collection) {
         }
     }
 
-    function isValidHREF(href){
-        if(href.length < 1){
-            return false;
-        }
-        if(href.indexOf("/") != -1){
-            return false;
-        }
-
-        return true;
-    }
-
     this.show = function() {
         this.release();
         scene_index = scene_stack.length - 1;
@@ -1304,7 +1351,28 @@ function bytesToHumanReadable(bytes, dp=1) {
     var i = bytes == 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024));
     return (bytes / Math.pow(1024, i)).toFixed(dp) * 1 + ' ' + ['b', 'kb', 'mb', 'gb', 'tb'][i];
 }
-  
+
+function cleanHREFinput(a){
+    let href_form = a;
+    if(a.target){
+        href_form = a.target;
+    }
+    let currentTxtVal = href_form.value.trim().toLowerCase();
+    //Clean the HREF to remove non lowercase letters and dashes
+    currentTxtVal = currentTxtVal.replace(/(?![0-9a-z\-\_])./g, '');
+    href_form.value = currentTxtVal;
+}
+
+function isValidHREF(href){
+    if(href.length < 1){
+        return false;
+    }
+    if(href.indexOf("/") != -1){
+        return false;
+    }
+
+    return true;
+}
 
 function main() {
     // Hide startup loading message

+ 12 - 3
radicale/web/internal_data/index.html

@@ -14,12 +14,13 @@
   <body>
     <nav id="logoutview" class="hidden">
       <span data-name="user" style="word-wrap:break-word;"></span>
-      <a href="" class="red" data-name="link" title="Logout">Logout</a>
+      <a href="#" class="green" data-name="refresh" title="Refresh">Refresh</a>
+      <a href="#" class="red" data-name="logout" title="Logout">Logout</a>
     </nav>
 	
     <main>
       <section id="loadingscene">
-        <img src="css/loading.svg" alt="Loading">
+        <img src="css/loading.svg" alt="Loading..." class="loading">
         <h2>Loading</h2>
         <p>Please wait...</p>
         <noscript>JavaScript is required</noscript>
@@ -155,12 +156,20 @@
         <ul>
           <li data-name="filetemplate" class="hidden"> Uploading <span data-name="name">name</span>
             <br>
-            <img data-name="pending" src="css/loading.svg" alt="Please wait...">
             <span class="successmessage" data-name="success">Uploaded Successfully!</span>
             <span class="error" data-name="error"></span>
           </li>
         </ul>
+        <div data-name="pending" class="hidden">
+          <img src="css/loading.svg" class="loading" alt="Please wait..."/>
+        </div>
         <form>
+          <label for="uploadfile">File:</label>
+          <input data-name="uploadfile" type="file" accept=".ics, .vcf" multiple>
+          <label for="href">HREF:</label>
+          <input data-name="href" type="text">
+          <small data-name="hreflimitmsg" class="hidden">You can only specify the HREF if you upload 1 file.</small>
+          <button type="submit" class="green" data-name="submit">Upload</button>
           <button type="button" class="red" data-name="close">Close</button>
         </form>
       </section>