فهرست منبع

File Explorer: optimize styles and performance improvements (#188)

- Bugfix: icons are too large in mobile view
- Adjust top and bottom padding of `fileOprBtn` on mobile
- Rewrite part of code with native JS for better performance
- Update translations
GT610 5 ماه پیش
والد
کامیت
9f96016672

+ 7 - 11
src/web/SystemAO/file_system/file_explorer.css

@@ -249,17 +249,6 @@ body.darkTheme .ui.icon.button{
     color:var(--fileopr_oprtxt);
 }
 
-.navibar .twolines.fileOprBtn{
-    padding-top: 0.1em;
-    padding-bottom: 0.1em;
-}
-.navibar .twolines.fileOprBtn .opricon{
-    width: 40px;
-}
-.navibar .twolines.fileOprBtn .oprtxt{
-    font-size: 1em;
-}
-
 .navibar.mobile .fileOprBtn{
     padding-bottom: 0.4em;
     padding-top: 0.2em;
@@ -309,6 +298,13 @@ body.darkTheme .ui.icon.button{
     height: 64px;
 }
 
+
+.navibar .fileoprSmallBtnMobile .opricon {
+    width: 20px;
+    height: 20px;
+    vertical-align: middle;
+}
+
 .ui.selection.dropdown{
     background-color: var(--dropdown_bg) !important;
     color: var(--dropdown_txt) !important;

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 18
src/web/SystemAO/file_system/file_explorer.html


+ 34 - 22
src/web/SystemAO/file_system/file_permission.html

@@ -5,14 +5,12 @@
     <title>File Permissions</title>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-    <link rel="stylesheet" href="../../script/semantic/semantic.css">
+    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
     <script type="text/javascript" src="../../script/jquery.min.js"></script>
     <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
     <script src="../../script/applocale.js"></script>
     <script type="text/javascript" src="../../script/ao_module.js"></script>
     <style>
-        body {}
-
         .fitted.checkbox {
             height: 20px;
         }
@@ -162,6 +160,30 @@
         var fileProperties = [];
         var groupID = 0;
 
+        // Permission map
+        const permissionMap = [
+            // User
+            { id: 1, pos: 0 }, // user_read (0b100000000)
+            { id: 2, pos: 1 }, // user_write (0b010000000)
+            { id: 3, pos: 2 }, // user_execute (0b001000000)
+
+            // Group
+            { id: 4, pos: 3 }, // group_read (0b000100000)
+            { id: 5, pos: 4 }, // group_write (0b000010000)
+            { id: 6, pos: 5 }, // group_execute (0b000001000)
+
+            // Other
+            { id: 7, pos: 6 }, // other_read (0b000000100)
+            { id: 8, pos: 7 }, // other_write (0b000000010)
+            { id: 9, pos: 8 }  // other_execute (0b000000001)
+        ];
+
+        // Cache the permission checkboxes
+        const permissionCheckboxes = permissionMap.reduce((acc, { id }) => {
+            acc[id] = document.getElementById(id);
+            return acc;
+        }, {});
+
         for (var i = 0; i < files.length; i++) {
             getPermission(files[i]);
         }
@@ -197,7 +219,7 @@
                     //Assign the permission checkbox
                     for (var i = 0; i < permissionBites.length; i++) {
                         if (permissionBites[i] == "1") {
-                            $("#" + (i + 1))[0].checked = true;
+                            permissionCheckboxes[i + 1].checked = true;
                         }
                     }
 
@@ -218,9 +240,9 @@
         }
 
         function resetCheckboxes() {
-            for (var i = 1; i < 10; i++) {
-                $("#" + i)[0].checked = false;
-            }
+            permissionMap.forEach(({ id }) => {
+                permissionCheckboxes[id].checked = false;
+            });
         }
 
         function update() {
@@ -243,24 +265,14 @@
 
         }
 
-
         function generatePermissionString() {
-            var binary = [];
-            for (var i = 1; i < 10; i++) {
-                if ($("#" + i)[0].checked == true) {
-                    binary.push(1);
-                } else {
-                    binary.push(0);
-                }
-            }
-
-            //Convert the binary into hex
-            var octalRepresentation = parseInt(binary.join(""), 2).toString(8);
-
-            return groupID + octalRepresentation;
+            const binary = new Array(9).fill(0);
+            permissionMap.forEach(({ id, pos }) => {
+                binary[pos] = permissionCheckboxes[id].checked ? 1 : 0; // 使用缓存引用
+            });
+            return groupID + parseInt(binary.join(''), 2).toString(8);
         }
 
-
     </script>
 </body>
 

+ 431 - 419
src/web/SystemAO/file_system/file_properties.html

@@ -1,471 +1,483 @@
 <!DOCTYPE html>
 <html>
-    <head>
-        <title locale="title/title">File Properties</title>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
-        <script type="text/javascript" src="../../script/jquery.min.js"></script>
-        <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
-        <script type="text/javascript" src="../../script/ao_module.js"></script>
-        <script type="text/javascript" src="../../script/applocale.js"></script>
-        <link rel="stylesheet" href="../../script/ao.css">
-        <style>
-            body{
-                overflow:hidden;
-            }
-
-            #filePropertiesWindow{
-                background-color: var(--body_background);
-                color: var(--body_text);
-            }
-
-            #filePropertiesWindow td,.header,p,div{
-                color: var(--body_text) !important;
-            }
-
-            #filePropertiesWindow input{
-                background-color: var(--body_background_secondary) !important;
-                color: var(--text_color) !important;
-                font-size: 1.2em;
-            }
-
-            .small.basic.white.fluid.button{
-                color: var(--text_color_secondary) !important;
-            }
 
-            .small.basic.white.fluid.button:hover{
-                background-color: var(--body_background_active) !important;
-            }
+<head>
+    <title locale="title/title">File Properties</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
+    <script type="text/javascript" src="../../script/jquery.min.js"></script>
+    <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
+    <script type="text/javascript" src="../../script/ao_module.js"></script>
+    <script type="text/javascript" src="../../script/applocale.js"></script>
+    <link rel="stylesheet" href="../../script/ao.css">
+    <style>
+        body {
+            overflow: hidden;
+        }
+
+        #filePropertiesWindow {
+            background-color: var(--body_background);
+            color: var(--body_text);
+        }
+
+        #filePropertiesWindow td,
+        .header,
+        p,
+        div {
+            color: var(--body_text) !important;
+        }
+
+        #filePropertiesWindow input {
+            background-color: var(--body_background_secondary) !important;
+            color: var(--text_color) !important;
+            font-size: 1.2em;
+        }
+
+        .hidden {
+            display: none !important;
+        }
+
+        .vertical-margin {
+            margin-top: 4px !important;
+        }
+
+        .small.basic.white.fluid.button {
+            color: var(--text_color_secondary) !important;
+        }
+
+        .small.basic.white.fluid.button:hover {
+            background-color: var(--body_background_active) !important;
+        }
+    </style>
+</head>
+
+<body id="filePropertiesWindow">
+    <br>
+    <div class="ui container">
+        <h3 class="ui header">
+            <span locale="title/title">File Properties</span>
+            <div class="sub header" locale="title/desc">Basic File Information</div>
+        </h3>
+        <div class="ui divider"></div>
+        <div id="properties">
 
-        </style>
-    </head>
-    <body id="filePropertiesWindow">
-        <br>
-        <div class="ui container">
-            <h3 class="ui header">
-                <span locale="title/title">File Properties</span>
-                <div class="sub header" locale="title/desc">Basic File Information</div>
-            </h3>
-            <div class="ui divider"></div>
-            <div id="properties">
-
-            </div>
-            <br>
-            <button style="display:none;" class="ui small white basic fluid button singleFileOnly" onclick="changeDefaultWebApp();"><i class="ui external square alternate icon blue"></i><span locale="button/changeDefault">Change Default WebApp</span></button>
-            <button style="display:none; margin-top: 4px;" class="ui small basic white fluid button singleFileOnly" onclick="viewVersionHistory();"><i class="ui undo green icon"></i><span locale="button/versionHistory">View Version History</span></button>
-            <button style="margin-top: 4px;" class="ui small basic white fluid button linuxonly" onclick="openFilePermissionPanel();" locale="button/changeFilePermission">Change File Permissions</button>
-            <br>
-        </div>
-        <div id="filesizeLoader" class="ui active dimmer">
-            <div class="ui indeterminate text loader" locale="loader/loadingFileSize">Calculating File Size</div>
         </div>
-        <script>
-            //Initiate the view model
-            var files = ao_module_loadInputFiles();
-            var fileProperties = [];
-            var fileInfo = {};
-            function initFileProperties(){
-                $("#properties").html("");
-                if (files.length == 1){
-                    //There are only 1 file to be shown
-                    getFileProp(files[0], renderSingleObject);
-                }else if (files.length > 1){
-                    for (var i =0; i < files.length; i++){
-                        getFileProp(files[i], function(data){
-                            fileProperties.push(data);
-                            if (fileProperties.length == files.length){
-                                renderMultipleObjects();
-                            }
-                        });
-                    }
+        <br>
+        <button class="ui small white basic fluid button singleFileOnly hidden" onclick="changeDefaultWebApp();"><i
+                class="ui external square alternate icon blue"></i><span locale="button/changeDefault">Change Default
+                WebApp</span></button>
+        <button class="ui small basic white fluid button singleFileOnly hidden vertical-margin"
+            onclick="viewVersionHistory();"><i class="ui undo green icon"></i><span locale="button/versionHistory">View
+                Version History</span></button>
+        <button class="ui small basic white fluid button linuxonly vertical-margin" onclick="openFilePermissionPanel();"
+            locale="button/changeFilePermission">Change File Permissions</button>
+        <br>
+    </div>
+    <div id="filesizeLoader" class="ui active dimmer">
+        <div class="ui indeterminate text loader" locale="loader/loadingFileSize">Calculating File Size</div>
+    </div>
+    <script>
+        //Initiate the view model
+        var files = ao_module_loadInputFiles();
+        var fileProperties = [];
+        var fileInfo = {};
+        function initFileProperties() {
+            $("#properties").html("");
+            if (files.length == 1) {
+                //There are only 1 file to be shown
+                getFileProp(files[0], renderSingleObject);
+            } else if (files.length > 1) {
+                for (var i = 0; i < files.length; i++) {
+                    getFileProp(files[i], function (data) {
+                        fileProperties.push(data);
+                        if (fileProperties.length == files.length) {
+                            renderMultipleObjects();
+                        }
+                    });
                 }
             }
-
-            applocale.init("../locale/file_properties.json", function(){
-                applocale.translate();
-                initFileProperties();
+        }
+
+        applocale.init("../locale/file_properties.json", function () {
+            applocale.translate();
+            initFileProperties();
+        });
+
+        //Hide windows / linux only operations
+        $.get("/system/info/getArOZInfo", function (data) {
+            if (data.HostOS == "windows") {
+                $(".linuxonly").hide();
+            } else {
+                $(".windowsonly").hide();
+            }
+        });
+
+        function viewVersionHistory() {
+            var hashPassthrough = encodeURIComponent(JSON.stringify(files));
+            ao_module_newfw({
+                url: "SystemAO/file_system/file_versions.html#" + hashPassthrough,
+                width: 570,
+                height: 480,
+                appicon: "SystemAO/file_system/img/properties.png",
+                title: "File Version History",
             });
+        }
+
+        function getFileProp(vpath, callback) {
+            $.ajax({
+                url: "../../system/file_system/getProperties",
+                data: { path: vpath },
+                method: "POST",
+                success: function (data) {
+                    callback(data);
+                    fileInfo = data;
+                    //Initialize system theme
+                    fpw_loadPreference("file_explorer/theme", function (data) {
+                        if (data.error === undefined) {
+                            if (data == "darkTheme") {
+                                $("body").addClass("darkTheme");
+                            } else {
+                                $("body").addClass("whiteTheme");
+                            }
+                        }
+                    });
 
-           
-          
-
-            //Hide windows / linux only operations
-            $.get("/system/info/getArOZInfo", function(data){
-                if (data.HostOS == "windows"){
-                    $(".linuxonly").hide();
-                }else{
-                    $(".windowsonly").hide();
                 }
+            })
+        }
+
+        function openFilePermissionPanel() {
+            var hashPassthrough = encodeURIComponent(JSON.stringify(files));
+            ao_module_newfw({
+                url: "SystemAO/file_system/file_permission.html#" + hashPassthrough,
+                width: 340,
+                height: 480,
+                appicon: "SystemAO/file_system/img/properties.png",
+                title: "File Permissions",
             });
+        }
 
-            function viewVersionHistory(){
-                var hashPassthrough = encodeURIComponent(JSON.stringify(files));
-                ao_module_newfw({
-                    url: "SystemAO/file_system/file_versions.html#" + hashPassthrough,
-                    width: 570,
-                    height: 480,
-                    appicon: "SystemAO/file_system/img/properties.png",
-                    title: "File Version History",
-                });
-            }
+        function renderMultipleObjects() {
+            hideLoader();
 
-            function getFileProp(vpath, callback){
-                $.ajax({
-                    url: "../../system/file_system/getProperties",
-                    data: {path: vpath},
-                    method: "POST",
-                    success: function(data){
-                        callback(data);
-                        fileInfo = data;
-                        //Initialize system theme
-                        fpw_loadPreference("file_explorer/theme",function(data){
-                            if (data.error === undefined){
-                                if (data == "darkTheme"){
-                                    $("body").addClass("darkTheme");
-                                }else{
-                                    $("body").addClass("whiteTheme");
-                                }
-                            }
-                        });
+            const filesizeSum = sumProperties(fileProperties, "Filesize");
+            let filecount = 0, foldercount = 0;
 
-                    }
-                })
-            }
+            // Pre-build HTML
+            let html = ui_getInput(fileProperties[0].VirtualDirname + "/", "Root Name");
 
-            function openFilePermissionPanel(){
-                var hashPassthrough = encodeURIComponent(JSON.stringify(files));
-                ao_module_newfw({
-                    url: "SystemAO/file_system/file_permission.html#" + hashPassthrough,
-                    width: 340,
-                    height: 480,
-                    appicon: "SystemAO/file_system/img/properties.png",
-                    title: "File Permissions",
-                });
-            }
+            // Get counts
+            fileProperties.forEach(item => {
+                item.IsDirectory ? foldercount++ : filecount++;
+            });
 
-            function renderMultipleObjects(){
-                hideLoader();
-                var filesizeSum = sumProperties(fileProperties, "Filesize");
-                $("#properties").append(ui_getInput(fileProperties[0].VirtualDirname + "/", "Root Name"));
-                var filecount = 0;
-                var foldercount = 0;
-                for (var i =0; i < fileProperties.length; i++){
-                    if (fileProperties[i].IsDirectory){
-                        foldercount++;
-                    }else{
-                        filecount++;
-                    }
-                }
-                $("#properties").append(ui_getText(applocale.getString("selection/multi", "Multiple selections")));
-                $("#properties").append(ui_getText(filecount + applocale.getString("counter/files", " Files")));
-                $("#properties").append(ui_getText(foldercount + applocale.getString("counter/folders", " Folders")));
-                
-                let totalSizeText = bytesToSize(filesizeSum) + ` (${filesizeSum} bytes)`
-                if (filesizeSum < 0){
-                    //Network folder. Do not render size
-                    totalSizeText = `<i class="times circle outline yellow icon"></i> ${applocale.getString("properties/error/Not available for network folders", "Not available for network folders")}`;
+            // Add summary table
+            html += ui_getText(applocale.getString("selection/multi", "Multiple selections"));
+            html += buildCounterElements(filecount, foldercount);
+            html += buildSummaryTable(filesizeSum);
+
+            // Inject DOM once
+            $("#properties").html(html);
+        }
+
+        // Build counter elements
+        function buildCounterElements(fileCount, folderCount) {
+            return [
+                ui_getText(`${fileCount} ${applocale.getString("counter/files", "Files")}`),
+                ui_getText(`${folderCount} ${applocale.getString("counter/folders", "Folders")}`)
+            ].join("");
+        }
+
+        // Build summary table
+        function buildSummaryTable(filesizeSum) {
+            const totalSizeText = formatSize(filesizeSum);
+            return ui_getTable([], [
+                ["Virtual Directory", fileProperties[0].VirtualDirname + "/"],
+                ["Total Size", totalSizeText]
+            ]);
+        }
+
+        function sumProperties(data, propName) {
+            var sum = 0;
+            for (var i = 0; i < data.length; i++) {
+                sum += data[i][propName];
+            }
+            return sum;
+        }
+
+        //Render one object property to the ui element
+        function renderSingleObject(data) {
+            hideLoader();
+            let html = ''; // Pre-build html
+
+            if (data.error !== undefined) {
+                //Something went wrong
+                html = `<h4 class="ui header">
+                    <i class="question icon"></i>
+                    <div class="content">
+                        File Properties Unknown
+                        <div class="sub header">The system were unable to read the selected file properties.</div>
+                    </div>
+                </h4>
+                <div class="ui divider"></div>
+                <small>${data.error}</small>`;
+            } else {
+                // Info
+                var filesizeText = "File Size";
+                const isDir = data.IsDirectory;
+                html += ui_getInput(data.Basename, isDir ? "Folder Name" : "File Name");
+                html += ui_getText(data.MimeType);;
+
+                if (!isDir && data.Basename.split(".").pop() !== "shortcut") {
+                    // Async content placeholder
+                    html += '<div id="asyncContent"></div>';
+                } else {
+                    // Sync content
+                    html += buildFileTable(data, isDir);
                 }
-
-                //Append other properties as table
-                $("#properties").append(ui_getTable(
-                        [],
-                        [
-                            ["Virtual Directory", fileProperties[0].VirtualDirname + "/"],
-                            ["Storage Directory", fileProperties[0].StorageDirname + "/"],
-                            ["Total Size", totalSizeText],
-                        ]
-                    ));
-
-                
             }
 
-            function sumProperties(data, propName){
-                var sum = 0;
-                for (var i = 0; i < data.length; i++){
-                    sum += data[i][propName];
-                }
-                return sum;
+            // Add DOM once
+            $("#properties").html(html);
+
+            // Async content
+            if (!data.IsDirectory && data.Basename.split(".").pop() !== "shortcut") {
+                loadAsyncContent(data);
             }
-            
-            //Render one object property to the ui element
-            function renderSingleObject(data){
-                hideLoader();
-                if (data.error !== undefined){
-                    //Something went wrong
-                    $("#properties").append(`<h4 class="ui header">
-                        <i class="question icon"></i>
-                        <div class="content">
-                            File Properties Unknown
-                            <div class="sub header">The system were unable to read the selected file properties.</div>
-                        </div>
-                    </h4>
-                    <div class="ui divider"></div>
-                    <small>${data.error}</small>
-                    `);
-                }else{
-                    //Append Filename
-                    var filesizeText = "File Size";
-                    if (data.IsDirectory){
-                        $("#properties").append(ui_getInput(data.Basename, "Folder Name"));
-                        filesizeText = "Folder Size";
-                    }else{
-                        $("#properties").append(ui_getInput(data.Basename, "File Name"));
-                    }
-                    
-                    //Append MIME Type
-                    $("#properties").append(ui_getText(data.MimeType));
-                    
-                    //Get the default opener
-                    if (!data.IsDirectory){
-                        //Check if this file is shortcut
-                        if ( data.Basename.split(".").pop() == "shortcut"){
-                            //This is shortcut file
-                            $("#properties").append(ui_getTable(
-                                        [],
-                                        [
-                                            ["Virtual Path", data.VirtualPath],
-                                            ["Storage Path", data.StoragePath],
-                                            ["Permission", data.Permission],
-                                            ["Last Modified", generateDisplayLastModTime(data.LastModTime)],
-                                            ["File Type", "System Shortcut"],
-                                            ["Owner",data.Owner],
-
-                                        ]
-                                    ));
-                        }else{
-                            //Normal Files
-                            $(".singleFileOnly").show();
-                            $.ajax({
-                                url: "../../system/modules/getDefault",
-                                method: "GET",
-                                data: {
-                                    opr: "launch",
-                                    ext: "." + data.Basename.split(".").pop(),
-                                    mode: "launch"
-                                },
-                                success: function(openerinfo) {
-                                    //Check if the module is set.
-                                    var defaultWebAppField = ["Default WebApp",`<img class="ui mini spaced image" style="margin-left: 0px; padding-right: 8px;" src="../../${openerinfo.IconPath}">` + openerinfo.Name];
-                                    if ( openerinfo.Name == undefined){
-                                        //Not set. 
-                                        defaultWebAppField = ["Default WebApp", `<a href="#" onclick="changeDefaultWebApp();">Set Default WebApp</a>`];
-                                    }
-                                    //Append other properties as table
-                                    $("#properties").append(ui_getTable(
-                                        [],
-                                        [
-                                            defaultWebAppField,
-                                            ["Virtual Path", data.VirtualPath],
-                                            ["Storage Path", data.StoragePath],
-                                            [filesizeText, bytesToSize(data.Filesize) + ` (${data.Filesize} bytes)`],
-                                            ["Permission", data.Permission],
-                                            ["Last Modified", generateDisplayLastModTime(data.LastModTime)],
-                                            ["File Type", "File"],
-                                            ["Owner",data.Owner],
-
-                                        ]
-                                    ));
-                                }
-                            });
-                        }
-                        
-                    }else{
-                        let folderSizeText = bytesToSize(data.Filesize) + ` (${data.Filesize} bytes)`;
-                        if (data.Filesize < 0){
-                            //Network folder. Do not render size
-                            folderSizeText = `<i class="times circle outline yellow icon"></i> ${applocale.getString("properties/error/Not available for network folders", "Not available for network folders")}`;
-                        }
-                        let lastModTimeText = generateDisplayLastModTime(data.LastModTime);
-                        if (data.LastModUnix == 0){
-                            lastModTimeText = `<i class="times circle outline yellow icon"></i> ${applocale.getString("properties/error/No record", "No record")}`;
-                        }
-                        $("#properties").append(ui_getTable(
-                            [],
-                            [
-                                ["Virtual Path", data.VirtualPath],
-                                ["Storage Path", data.StoragePath],
-                                [filesizeText, folderSizeText ],
-                                ["Permission", data.Permission],
-                                ["Last Modified", lastModTimeText],
-                                ["File Type", "Folder"],
-                                ["Owner",data.Owner],
-
-                            ]
-                        ));
-                    }
-                   
 
+        }
+
+        // Build file table
+        function buildFileTable(data, isDir) {
+            let sizeText = formatSize(data.Filesize);
+            let lastModText = generateDisplayLastModTime(data.LastModTime);
+
+            return ui_getTable([], [
+                ["Virtual Path", data.VirtualPath],
+                [isDir ? "Folder Size" : "File Size", sizeText],
+                ["Permission", data.Permission],
+                ["Last Modified", lastModText],
+                ["File Type", isDir ? "Folder" : "File"],
+                ["Owner", data.Owner]
+            ]);
+        }
+
+        function loadAsyncContent(data) {
+            $(".singleFileOnly").show();
+            $.ajax({
+                url: "../../system/modules/getDefault",
+                method: "GET",
+                data: {
+                    opr: "launch",
+                    ext: "." + data.Basename.split(".").pop(),
+                    mode: "launch"
+                },
+                success: function (openerinfo) {
+                    const content = buildFileTable(data, openerinfo);
+                    $("#asyncContent").replaceWith(content);
                 }
-                
+            });
+        }
+
+        // Format bytes to human readable
+        function formatSize(bytes) {
+            if (bytes < 0) {
+                return `<i class="times circle outline yellow icon"></i> ${applocale.getString(
+                    "properties/error/Not available for network folders",
+                    "Not available for network folders"
+                )}`;
             }
+            return bytesToSize(bytes) + ` (${bytes} bytes)`;
+        }
 
-            function hideLoader(){
-                $("#filesizeLoader").hide();
-                $("body").css('overflow-y',"auto");
-            }
+        function hideLoader() {
+            $("#filesizeLoader").hide();
+            $("body").css('overflow-y', "auto");
+        }
 
-            //Model rendering scripts
-            function ui_getInput(value, placeholder="", type="text"){
-                return `<div class="ui fluid small input">
+        //Model rendering scripts
+        function ui_getInput(value, placeholder = "", type = "text") {
+            return `<div class="ui fluid small input">
                             <input type="${type}" placeholder="${placeholder}" value="${value}" readonly="true">
                         </div>`
+        }
+
+        function ui_getText(value, color = "black") {
+            return `<p style="color:${color}; margin-bottom:0px;">${value}</p>`;
+        }
+
+        function ui_getDivider() {
+            return `<div class="ui divider"></div>`;
+        }
+
+        //head is a 1D array and table is 2D array
+        function ui_getTable(heads, table) {
+            // Cache the HTML parts
+            const htmlParts = ['<table class="ui very basic fluid table">'];
+
+            // Build thead
+            if (heads.length > 0) {
+                htmlParts.push(
+                    '<thead><tr>',
+                    heads.map(head => `<th>${head}</th>`).join(''),
+                    '</tr></thead>'
+                );
             }
 
-            function ui_getText(value, color="black"){
-                return `<p style="color:${color}; margin-bottom:0px;">${value}</p>`;
-            }
+            // Build tbody
+            htmlParts.push(
+                '<tbody>',
+                table.map(row =>
+                    `<tr>${row.map((cell, cellIndex) => {
+                        // Only translate the first column
+                        let content = cell;
+                        if (cellIndex === 0 && applocale) {
+                            const localeKey = `properties/key/${cell.trim()}`;
+                            content = applocale.getString(localeKey, cell);
+                        }
+                        return `<td style="word-break: break-all;">${content}</td>`;
+                    }).join('')
+                    }</tr>`
+                ).join(''),
+                '</tbody></table>'
+            );
 
-            function ui_getDivider(){
-                return `<div class="ui divider"></div>`;
-            }
+            return htmlParts.join('');
+        }
 
-            //head is a 1D array and table is 2D array
-            function ui_getTable(heads, table){
-                html =  `<table class="ui very basic fluid table">`;
-                if (heads.length > 0){
-                    html += `<thead><tr>`;
-                    for (var i =0; i < heads.length; i++){
-                        html += `<th>${heads[i]}</th>`;
-                    }
-                    html += `</tr></thead>`;
-                }
-                html += `<tbody>`;
-                for (var i =0; i < table.length; i++){
-                    html += `<tr>`;
-                    for (var j =0; j < table[i].length; j++){
-                        var keyString = table[i][j];
-                        if (j == 0 && applocale){
-                            keyString = applocale.getString("properties/key/" + keyString.trim(), keyString);
-                        }
-                        html += `<td style="word-break: break-all;">${keyString}</td>`
-                    }
-                    html += `</tr>`;
-                }
-                
-                html += `</tbody>
-                        </table>`;
-                return html
-            }
+        function bytesToSize(bytes) {
+            var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+            if (bytes == 0) return '0 Byte';
+            var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+            return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
+        }
 
-            function bytesToSize(bytes) {
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-                if (bytes == 0) return '0 Byte';
-                var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-                return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
-            }
+        /*
 
-            /*
+        Updates Oct 2020 - Matching File Explorer Theme on other file system tabs
 
-            Updates Oct 2020 - Matching File Explorer Theme on other file system tabs
+        */
 
-            */
-           
-            function fpw_toggleDarkTheme(){
-                $("#filePropertiesWindow").css({
-                    "background-color":"#242330",
-                    "color":"white",
-                });
+        function fpw_toggleDarkTheme() {
+            $("#filePropertiesWindow").css({
+                "background-color": "#242330",
+                "color": "white",
+            });
 
-                $("#filePropertiesWindow td,.header,p,div").css({
-                    "color":"white",
-                });
+            $("#filePropertiesWindow td,.header,p,div").css({
+                "color": "white",
+            });
 
-                $("#filePropertiesWindow .input").addClass("inverted transparent big")
-            }
+            $("#filePropertiesWindow .input").addClass("inverted transparent big")
+        }
 
 
-            function fpw_loadPreference(key, callback){
-                $.get("../../system/file_system/preference?key=" + key,function(data){
-                    callback(data);
-                });
-            }
+        function fpw_loadPreference(key, callback) {
+            $.get("../../system/file_system/preference?key=" + key, function (data) {
+                callback(data);
+            });
+        }
 
 
-            /*
-                Updates 30 Jan 2021: Added change of file opener
-            */
+        /*
+            Updates 30 Jan 2021: Added change of file opener
+        */
 
-             //Open Opener Selector for the given file
-            function changeDefaultWebApp(){
-                var ext = fileInfo.Ext;
-                var openFileList = [];
-                var openFileObject = {
-                    filepath: fileInfo.VirtualPath,
-                    filename: fileInfo.Basename,
-                }
-                openFileList.push(openFileObject);
-                var openParamter = encodeURIComponent(JSON.stringify(openFileObject));
-                ao_module_newfw({
-                    url: "SystemAO/file_system/defaultOpener.html#" + openParamter,
-                    width: 320,
-                    height: 510,
-                    appicon: "SystemAO/file_system/img/opener.png",
-                    title: "Default WebApp for " + ext,
-                    parent: ao_module_windowID,
-                    callback: "handleRefresh"
-                });
+        //Open Opener Selector for the given file
+        function changeDefaultWebApp() {
+            var ext = fileInfo.Ext;
+            var openFileList = [];
+            var openFileObject = {
+                filepath: fileInfo.VirtualPath,
+                filename: fileInfo.Basename,
             }
-
-            function handleRefresh(){
-                //Default opener changed. Update the display
-                initFileProperties();
+            openFileList.push(openFileObject);
+            var openParamter = encodeURIComponent(JSON.stringify(openFileObject));
+            ao_module_newfw({
+                url: "SystemAO/file_system/defaultOpener.html#" + openParamter,
+                width: 320,
+                height: 510,
+                appicon: "SystemAO/file_system/img/opener.png",
+                title: "Default WebApp for " + ext,
+                parent: ao_module_windowID,
+                callback: "handleRefresh"
+            });
+        }
+
+        function handleRefresh() {
+            //Default opener changed. Update the display
+            initFileProperties();
+        }
+
+        // Generate the display text for last modified time
+        function generateDisplayLastModTime(lastModTime) {
+            // Parse the date 
+            const parseDate = (str) => {
+                const [datePart] = str.split(" ");
+                const [year, month, day] = datePart.split("-");
+                return new Date(year, month - 1, day); // Month index
+            };
+
+            const modTime = parseDate(lastModTime);
+            const now = new Date();
+            const [years, months, days] = calcDate(now, modTime);
+
+            let displayText = "Unknown";
+
+            if (years > 0) {
+                if (years > 1 && applocale.getString("lastmod/time/s")) {
+                    displayText += applocale.getString("lastmod/time/s", "s");
+                }
+                displayText = `${years}${applocale.getString("lastmod/time/year", "year")}${applocale.getString("lastmod/time/ago")}`;
+            } else if (months > 0) {
+                if (months > 1 && applocale.getString("lastmod/time/s")) {
+                    displayText += applocale.getString("lastmod/time/s", "s");
+                }
+                displayText = `${months}${applocale.getString("lastmod/time/month", "month")}${applocale.getString("lastmod/time/ago")}`;
+            } else if (days > 0) {
+                if (days > 1 && applocale.getString("lastmod/time/s")) {
+                    displayText += applocale.getString("lastmod/time/s", "s");
+                }
+                displayText = `${days}${applocale.getString("lastmod/time/days", "day")}${applocale.getString("lastmod/time/ago")}`;
+            } else {
+                displayText = applocale.getString("lastmod/time/today", "Today");
             }
 
-            /*
-                Updates 9 April 2021: Added day compare for last modification days
-            */
-
-            function generateDisplayLastModTime(lastModTime){
-                //Try to split the date into js date format
-                var dateInfo = (lastModTime.split(" ")[0]).split("-");
-                var modTime = new Date(dateInfo[0],dateInfo[1],dateInfo[2]);
-                var diff = calcDate(new Date(), modTime);
-                var displayText = "Unknown";
-                if (diff[2] > 0){
-                    //years
-                    displayText = diff[2] + applocale.getString("lastmod/time/year", " year");
-                    if (diff[2] > 1){
-                        displayText += applocale.getString("lastmod/time/s", "s")
-                    }
-
-                    displayText += applocale.getString("lastmod/time/ago", " ago");
-                    
-                }else if (diff[1] > 0){
-                    //months
-                    displayText = diff[1] +  applocale.getString("lastmod/time/month", "  month");
-                    if (diff[1] > 1){
-                        displayText += applocale.getString("lastmod/time/s", "s")
-                    }
-
-                    displayText += applocale.getString("lastmod/time/ago", " ago");
-                }else if (diff [0] > 0){
-                    //days
-                    displayText = diff[0] + applocale.getString("lastmod/time/days", "  day");
-                    if (diff[0] > 1){
-                        displayText += applocale.getString("lastmod/time/s", "s");
-                    }
-
-                    displayText += applocale.getString("lastmod/time/ago", " ago");
-                }else{
-                    //just now
-                    displayText = applocale.getString("lastmod/time/today", "Today");
-                }
+            return `${displayText} (${lastModTime})`;
+        }
 
-                return displayText + " (" + lastModTime + ")";
+        function calcDate(endDate = new Date(), startDate) {
+            // Make sure startDate is a Date object
+            if (!(startDate instanceof Date)) {
+                startDate = new Date(startDate);
             }
 
-            function calcDate(date1 = new Date(),date2) {
-                var diff = Math.floor(date1.getTime() - date2.getTime());
-                var day = 1000 * 60 * 60 * 24;
+            let years = endDate.getFullYear() - startDate.getFullYear();
+            let months = endDate.getMonth() - startDate.getMonth();
+            let days = endDate.getDate() - startDate.getDate();
+
+            // Cross months
+            if (days < 0) {
+                const lastMonth = new Date(endDate);
+                lastMonth.setMonth(lastMonth.getMonth() - 1);
+                days += new Date(
+                    lastMonth.getFullYear(),
+                    lastMonth.getMonth() + 1,
+                    0
+                ).getDate();
+                months--;
+            }
 
-                var days = Math.floor(diff/day);
-                var months = Math.floor(days/31);
-                var years = Math.floor(months/12);
+            // Cross years
+            if (months < 0) {
+                years--;
+                months += 12;
+            }
 
+            return [years, months, days];
+        }
+    </script>
+</body>
 
-                return [days, months, years];
-            }
-        </script>
-    </body>
 </html>

+ 1 - 1
src/web/SystemAO/file_system/file_versions.html

@@ -4,7 +4,7 @@
         <title locale="title/title">File Version History</title>
         <meta charset="UTF-8">
         <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
         <script type="text/javascript" src="../../script/jquery.min.js"></script>
         <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
         <script type="text/javascript" src="../../script/ao_module.js"></script>

+ 1 - 1
src/web/SystemAO/file_system/sharelist.html

@@ -4,7 +4,7 @@
         <title locale="title/title">Share Entry List</title>
         <meta charset="UTF-8">
         <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-        <link rel="stylesheet" href="../../script/semantic/semantic.css">
+        <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
         <script type="text/javascript" src="../../script/jquery.min.js"></script>
         <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
         <script type="text/javascript" src="../../script/ao_module.js"></script>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 184 - 277
src/web/SystemAO/locale/file_explorer.json


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است