Procházet zdrojové kódy

Added photo module basic functions

Toby Chui před 3 roky
rodič
revize
b4597b6e44

+ 20 - 8
error.go

@@ -33,6 +33,26 @@ func errorHandleNotFound(w http.ResponseWriter, r *http.Request) {
 
 }
 
+func errorHandlePermissionDenied(w http.ResponseWriter, r *http.Request) {
+	unauthorizedPage := "./web/SystemAO/unauthorized.html"
+	if fileExists(unauthorizedPage) {
+		notFoundTemplateBytes, err := ioutil.ReadFile(unauthorizedPage)
+		notFoundTemplate := string(notFoundTemplateBytes)
+		if err != nil {
+			http.NotFound(w, r)
+		} else {
+			//Replace the request URL inside the page
+			notFoundTemplate = strings.ReplaceAll(notFoundTemplate, "{{request_url}}", r.RequestURI)
+			rel := getRootEscapeFromCurrentPath(r.RequestURI)
+			notFoundTemplate = strings.ReplaceAll(notFoundTemplate, "{{root_escape}}", rel)
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(notFoundTemplate))
+		}
+	} else {
+		http.Error(w, "Not authorized", http.StatusUnauthorized)
+	}
+}
+
 //Get escape root path, example /asd/asd => ../../
 func getRootEscapeFromCurrentPath(requestURL string) string {
 	rel := ""
@@ -49,11 +69,3 @@ func getRootEscapeFromCurrentPath(requestURL string) string {
 
 	return rel
 }
-
-func errorHandleNotLoggedIn(w http.ResponseWriter, r *http.Request) {
-	http.Error(w, "Not authorized", 401)
-}
-
-func errorHandlePermissionDenied(w http.ResponseWriter, r *http.Request) {
-	http.Error(w, "Not authorized", 401)
-}

+ 2 - 2
permission.go

@@ -45,11 +45,11 @@ func permissionInit() {
 				if requestingUser != nil && requestingUser.IsAdmin() == true {
 					permissionHandler.HandleListGroup(w, r)
 				} else {
-					errorHandleNotLoggedIn(w, r)
+					errorHandlePermissionDenied(w, r)
 				}
 
 			} else {
-				errorHandleNotLoggedIn(w, r)
+				errorHandlePermissionDenied(w, r)
 				return
 			}
 		}

+ 1 - 1
scheduler.go

@@ -75,7 +75,7 @@ func SchedulerInit() {
 			newScheduler.HandleListJobs(w, r)
 		} else {
 			//User not logged in
-			http.NotFound(w, r)
+			errorHandlePermissionDenied(w, r)
 		}
 	})
 	router.HandleFunc("/system/arsm/aecron/add", newScheduler.HandleAddJob)

+ 1 - 1
user.go

@@ -268,7 +268,7 @@ func user_getInterfaceInfo(w http.ResponseWriter, r *http.Request) {
 	userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
 	if err != nil {
 		//User not logged in
-		errorHandleNotLoggedIn(w, r)
+		sendErrorResponse(w, "User not logged in")
 		return
 	}
 

binární
web/Paint/img/desktop_icon.png


binární
web/Paint/img/desktop_icon.psd


binární
web/Paint/img/small_icon.png


binární
web/Paint/img/small_icon.psd


+ 58 - 12
web/Photo/backend/listFolder.js

@@ -1,16 +1,62 @@
 requirelib("filelib")
-//Scan the folder
-var results = filelib.aglob(folder, "user");
-
-//Sort the files
-var files = [];
-var folders = [];
-for (var i = 0; i < results.length; i++){
-    var thisFile = results[i];
-    if (filelib.isDir(thisFile)){
-        folders.push(thisFile);
+
+function getExt(filename){
+    return filename.split(".").pop();
+}
+
+function isImage(filename){
+    var ext = getExt(filename);
+    if (ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "webp"){
+        return true;
+    }
+    return false;
+}
+
+function isHiddenFile(filepath){
+    var filename = filepath.split("/").pop();
+    if (filename.substring(0, 1) == "."){
+        return true;
     }else{
-        files.push(thisFile);
+        return false;
+    }
+}
+
+function folderContainSubFiles(filepath){
+    var results = filelib.aglob(filepath + "/*", "user");
+    if (results.length > 0){
+        return true;
     }
+    return false;
 }
-sendJSONResp(JSON.stringify([folders, files]));	
+
+function dirname(filepath){
+    var tmp = filepath.split("/");
+    tmp.pop();
+    return tmp.join("/");
+}
+
+
+function main(){
+    //Scan the folder
+    var results = filelib.aglob(folder, "user");
+
+    //Sort the files
+    var files = [];
+    var folders = [];
+    for (var i = 0; i < results.length; i++){
+        var thisFile = results[i];
+        if (filelib.isDir(thisFile)){
+            if (!isHiddenFile(thisFile) && folderContainSubFiles(thisFile)){
+                folders.push(thisFile);
+            }
+            
+        }else{
+            if (isImage(thisFile)){
+                files.push(thisFile);
+            }
+        }
+    }
+    sendJSONResp(JSON.stringify([folders, files]));	
+}
+
+main();

+ 26 - 0
web/Photo/backend/listRoots.js

@@ -0,0 +1,26 @@
+requirelib("filelib");
+
+function folderContainSubFiles(filepath){
+    var results = filelib.aglob(filepath + "/*", "default");
+    if (results.length > 0){
+        return true;
+    }
+    return false;
+}
+
+
+function main(){
+    var possibleRoots = [];
+    for ( var i = 0; i < USER_VROOTS.length; i++){
+        var thisRoot = USER_VROOTS[i];
+        if (thisRoot.Filesystem != "virtual" && filelib.fileExists(thisRoot.UUID + ":/Photo") && folderContainSubFiles(thisRoot.UUID + ":/Photo/")){
+            possibleRoots.push([thisRoot.Name, thisRoot.UUID + ":/", thisRoot.UUID + ":/Photo"]);
+        }else{
+
+        }
+    }
+
+    sendJSONResp(JSON.stringify(possibleRoots));
+}
+
+main();

+ 8 - 0
web/Photo/img/arrow-left.svg

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<polygon fill="#FFFFFF" stroke="#231815" stroke-width="3" stroke-miterlimit="10" points="105.518,113.641 20.981,64.833 
+	105.518,16.027 "/>
+</svg>

+ 8 - 0
web/Photo/img/arrow-right.svg

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<polygon fill="#FFFFFF" stroke="#231815" stroke-width="3" stroke-miterlimit="10" points="20.981,16.026 105.518,64.833 
+	20.981,113.64 "/>
+</svg>

binární
web/Photo/img/banner.png


binární
web/Photo/img/banner.psd


+ 96 - 32
web/Photo/index.html

@@ -4,15 +4,15 @@
 <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width,initial-scale=1" />
-    <meta name="theme-color" content="#000000" />
+    <meta name="theme-color" content="#f76c5d" />
     <meta name="description" content="ArozOS Photo" />
+    <link rel="manifest" crossorigin="use-credentials" href="manifest.json">
     <title>Photo</title>
     <link rel="stylesheet" href="../script/semantic/semantic.min.css">
     <script src="../script/alpine.min.js"></script>
     <script src="../script/jquery.min.js"></script>
     <script src="../script/semantic/semantic.min.js"></script>
     <script src="../script/ao_module.js"></script>
-    <script src="photo.js"></script>
     <style>
         @media only screen and (min-width: 1200px) {
             #menu {
@@ -34,8 +34,8 @@
             }
             #showmenuBtn {
                 position: fixed;
-                bottom: 30px;
-                left: 30px;
+                bottom: 1.8em;
+                left: 1em;
             }
         }
         
@@ -50,22 +50,64 @@
         }
         
         body {
-            overflow: hidden;
+            /*
+                overflow: hidden;
+            */
+        }
+
+        .imagecard{
+            border: 1px solid transparent;
+        }
+
+        .imagecard img{
+            width: 100%;
+        }
+
+        #viewbox{
+            margin-left: 0px !important;
+            margin-top: 0px !important;
+            position: relative;
+        }
+
+        #fadeplate{
+            position: absolute;
+            top: 0px;
+            left: 0px;
+            width: 100%;
+            height: 100%;
+            background: rgba(20,20,20,0.8);
+            backdrop-filter: blur(2px);
         }
     </style>
 
 </head>
-
 <body>
-    <div id="main">
+    <script src="photo.js"></script>
+    <div id="main" x-data='photoListObject()'>
         <div class="ui left vertical menu" id="menu">
-            <div class="item">
+            <div class="item" style="background-color: #f76c5d;">
                 <img class="ui fluid image" src="img/banner.png">
             </div>
             <div class="item">
                 Browse Photos
                 <div class="menu">
-                    <a class="active item">user:/</a>
+                    <template x-for="vroot in vroots" x-init="getRootInfo()">
+                        <a class="item" :rootpath="vroot[1]" x-on:click="updateRenderingPath(vroot[2]);"><i class="ui disk icon"></i> <span x-text="vroot[0] + ' (' + vroot[1] + ')'"></span></a>
+                    </template>
+                    
+                </div>
+            </div>
+            <a id="parentFolderButton" class="item" x-on:click="parentFolder();" style="display:none;">
+                <i class="reply icon"></i> Parent Folder
+            </a>
+            <div class="item">
+                Sub Folders
+                <div class="menu">
+                    <div class="item" id="nosubfolder" style="display:none;">No Sub Folders</div>
+                    <template x-for="folder in folders">
+                        <a class="item" x-on:click="updateRenderingPath(folder);" ><i class="ui folder open icon"></i> <span x-text="extractFolderName(folder);"></span></a>
+                    </template>
+                    
                 </div>
             </div>
 
@@ -82,42 +124,64 @@
 
         </div>
 
-        <div id="display" class="ui segment" x-data='folderObject()' x-init="init()">
-            <div class="ui six cards viewbox">
-                <template x-for="image in images">
-                <div class="ui small card" x-on:click="showImage($el);"
-                    :filedata="encodeURIComponent(JSON.stringify({'filename':image.split('/').pop(),'filepath':image}))">
-                    <a class="image">
-                        <img :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image">
-                    </a>
-                </div>
-            </template>
+        <div id="display" x-init="init()">
+            <div id="noimg" class="ui basic segment" style="display:none;">
+                <h4 class="ui header">
+                    <i class="red question icon"></i>
+                    <div class="content">
+                        Empty Folder
+                        <div class="sub header">There are no photo stored in <span x-text="currentPath + '/'"></span></div>
+                    </div>
+                </h4>
+            </div>
+            <div id="viewbox" class="ui six cards viewbox">
+                <template x-for="image in images" :key="image">
+                    <div class="imagecard" style="cursor: pointer;" x-on:click="showImage($el);" :style="{width: renderSize + 'px', height: renderSize + 'px'}" :filedata="encodeURIComponent(JSON.stringify({'filename':image.split('/').pop(),'filepath':image}))">
+                        <a class="image" x-init="updateImageSizes();">
+                            <img :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image">
+                        </a>
+                    </div>
+                </template>
             </div>
-            <button x-on:click="updateRenderingPath('user:/Photo/油圖/*.jpg');">Click Me</button>
         </div>
     </div>
-    <button id="showmenuBtn" onclick="$('#menu').slideToggle();">S</button>
+    <button id="showmenuBtn" class="ui basic big icon button" onclick="toggleMenu();"><i class="ui content icon"></i></button>
+
+    <!-- Photo Viewing Modal -->
+    <div class="ui basic small modal">
+        <i class="close icon" style="margin-top: -2em;"></i>
+        <div class="image content">
+            <img id="fullImage" class="ui fluid image" src="" onload="ShowModal();"/>
+        </div>
+    </div>
 </body>
 <script>
+    //Calculate image size on document ready
+
     $(window).on("resize ", function() {
         //check if the menu is opened
         if (window.innerWidth < 1200) {
-            $('#menu').slideUp();
+            $('#menu').slideUp('fast',function(){
+            });
         }
         if (window.innerWidth >= 1200) {
-            $('#menu').slideDown();
+            $('#menu').slideDown('fast',function(){
+            });
         }
 
-        if (window.innerWidth < 500) {
-            $(".viewbox ").attr("class ", "ui three cards viewbox ");
-        } else if (window.innerWidth < 800) {
-            $(".viewbox ").attr("class ", "ui four cards viewbox ");
-        } else if (window.innerWidth < 1200) {
-            $(".viewbox ").attr("class ", "ui six cards viewbox ");
-        } else {
-            $(".viewbox ").attr("class ", "ui ten cards viewbox ");
-        }
+        updateImageSizes();
     });
+
+    function toggleMenu(){
+        $('#menu').toggle('fast',function(){
+            updateImageSizes();
+            setTimeout(function(){
+                updateImageSizes();
+            }, 100);
+        });
+    }
+
+    
 </script>
 
 </html>

+ 5 - 4
web/Photo/manifest.json

@@ -1,6 +1,6 @@
 {
   "short_name": "Photo",
-  "name": "ArozOS Photo",
+  "name": "ArOZ Photo",
   "icons": [
     {
       "src": "favicon.ico",
@@ -18,8 +18,9 @@
       "sizes": "512x512"
     }
   ],
-  "start_url": ".",
-  "display": "standalone",
+  "start_url": "index.html",
+  "display": "fullscreen",
+  "scope": "./",
   "theme_color": "#000000",
-  "background_color": "#ffffff"
+  "background_color": "#f76c5d"
 }

+ 114 - 8
web/Photo/photo.js

@@ -8,30 +8,91 @@
 
 let photoList = [];
 
-function sideBarObject(){
-    return {
-        tags: [],
+function scrollbarVisable(){
+    return $("body")[0].scrollHeight > $("body").height();
+}
 
+function getImageWidth(){
+    let boxCount = 4;
+    if (window.innerWidth < 500) {
+        boxCount = 3;
+    } else if (window.innerWidth < 800) {
+        boxCount = 4;
+    } else if (window.innerWidth < 1200) {
+        boxCount = 6;
+    }else if (window.innerWidth < 1600){
+        boxCount = 8;
+    } else {
+        boxCount = 10;
     }
+
+    let offsets = 1;
+    if (scrollbarVisable()){
+        offsets = 3;
+    }
+
+    return $("#viewbox").width() / boxCount - offsets;
+}
+
+function updateImageSizes(){
+    let newImageWidth = getImageWidth();
+    //Updates all the size of the images
+    $(".imagecard").css({
+        width: newImageWidth,
+        height: newImageWidth
+    });
+}
+
+function closeSideMenu(){
+    $('#menu').toggle('fast', function(){
+        updateImageSizes();
+    });
+    
 }
 
-function folderObject() {
+function extractFolderName(folderpath){
+    return folderpath.split("/").pop();
+}
+
+function photoListObject() {
     return {
         // data
-        pathWildcard: "user:/Photo/*.jpg",
+        pathWildcard: "user:/Photo/*",
+        currentPath: "user:/Photo",
+        renderSize: 200,
+        vroots: [],
         images: [],
         folders: [],
         
         // init
         init() {
-            this.getFolderInfo()
+            this.getFolderInfo();
+            this.getRootInfo();
+            this.renderSize = getImageWidth();
+            updateImageSizes();
+            
         },
         
-        updateRenderingPath(newWidlcard){
-            this.pathWildcard = newWidlcard;
+        updateRenderingPath(newPath){
+            this.currentPath = JSON.parse(JSON.stringify(newPath));
+            this.pathWildcard = newPath + '/*';
+            if (this.pathWildcard.split("/").length == 3){
+                //Root path already
+                $("#parentFolderButton").hide();
+            }else{
+                $("#parentFolderButton").show();
+            }
             this.getFolderInfo();
         },
 
+        parentFolder(){
+            var parentPath = JSON.parse(JSON.stringify(this.currentPath));
+            parentPath = parentPath.split("/");
+            parentPath.pop();
+            this.currentPath = parentPath.join("/");
+            this.updateRenderingPath( this.currentPath);
+        },
+
         getFolderInfo() {
             fetch(ao_root + "system/ajgi/interface?script=Photo/backend/listFolder.js", {
                 method: 'POST',
@@ -47,13 +108,58 @@ function folderObject() {
                     console.log(data);
                     this.folders = data[0];
                     this.images = data[1];
+
+                    if (this.images.length == 0){
+                        $("#noimg").show();
+                    }else{
+                        $("#noimg").hide();
+                    }
+
+                    if (this.folders.length == 0){
+                        $("#nosubfolder").show();
+                    }else{
+                        $("#nosubfolder").hide();
+                    }
+                    console.log(this.folders);
                 });
             });
+        },
+
+        getRootInfo() {
+            fetch(ao_root + "system/ajgi/interface?script=Photo/backend/listRoots.js", {
+                method: 'POST',
+                cache: 'no-cache',
+                headers: {
+                  'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({})
+            }).then(resp => {
+                resp.json().then(data => {
+                    this.vroots = data;
+                });
+            })
         }
     }
 }
 
+
+function ShowModal(){
+    $('.ui.modal').modal('show');
+}
+
 function showImage(object){
     var fd = JSON.parse(decodeURIComponent($(object).attr("filedata")));
     console.log(fd);
+    $("#fullImage").attr('src', "../media?file=" + fd.filepath);
+    var nextCard = $(object).next(".imagecard");
+    var prevCard = $(object).prev(".imagecard");
+    if (nextCard.length > 0){
+
+    }
+
+    if (prevCard.length > 0){
+
+    }
+    
+    console.log(nextCard, prevCard);
 }

+ 1 - 1
web/SystemAO/notfound.html

@@ -37,7 +37,7 @@
             <div>
                 <h3 class="">Page Not Found</h3>
                 <div class="ui divider"></div>
-                <p>The page or resource you are trying to access does not exist. <br><a href="../">Back</a></p>
+                <p>The page or resource you are trying to access does not exist. <br><a href="{{root_escape}}">Back</a></p>
                 <div class="ui divider"></div>
                 <div style="text-align: left;">
                     <small>Request time: <span id="reqtime"></span></small><br>

+ 52 - 0
web/SystemAO/unauthorized.html

@@ -0,0 +1,52 @@
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+        <link rel="stylesheet" href="{{root_escape}}script/semantic/semantic.min.css">
+        <script type="text/javascript" src="{{root_escape}}script/jquery.min.js"></script>
+        <script type="text/javascript" src="{{root_escape}}script/semantic/semantic.min.js"></script>
+        <title>Unauthorized</title>
+        <style>
+            #msg{
+                position: absolute;
+                top: calc(50% - 150px);
+                left: calc(50% - 250px);
+                width: 500px;
+                height: 300px;
+                text-align: center;
+            }
+
+            #footer{
+                position: fixed;
+                padding: 2em;
+                padding-left: 5em;
+                padding-right: 5em;
+                bottom: 0px;
+                left: 0px;
+                width: 100%;
+            }   
+
+            small{
+                word-break: break-word;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="msg">
+            <h1 style="font-size: 6em; margin-bottom: 0px;">401</h1>
+            <div>
+                <h3 class="">Unauthorized</h3>
+                <div class="ui divider"></div>
+                <p>You do not have permission to view this directory or page. <br><a href="{{root_escape}}">Back</a></p>
+                <div class="ui divider"></div>
+                <div style="text-align: left;">
+                    <small>Request time: <span id="reqtime"></span></small><br>
+                    <small id="reqURLDisplay">Request URI: {{request_url}}</small>
+                </div>
+            </div>
+        </div>
+        <script>
+            $("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
+        </script>
+    </body>
+</html>