Procházet zdrojové kódy

Reorganize start menu categories and groups

Rework desktop start menu categories: Network, Engineering, Development, System, Utilities and Container Apps replace the old Download/Files/Settings/Tools set, with updated locale strings in all languages.

Add modules.IsUniversalGroup so Utilities and System Tools stay open to every user, and use it in RegisterModule.

/system/appproxy/apps now returns a Containers flag, backed by a new Options.ContainersAvailable callback (true when Docker is usable), so the desktop shows Container Apps only on hosts that run containers.

Move SQLite Admin to Development and Web Downloader to Internet. Document the Group rules in CLAUDE.md, add tests, and clarify the ao_module_getSystemThemeColor comment. Also adds a local drive-test-node launch config.
Toby Chui před 9 hodinami
rodič
revize
8013d31f15

+ 9 - 0
.claude/launch.json

@@ -63,6 +63,15 @@
         "8127"
       ],
       "port": 8127
+    },
+    {
+      "name": "drive-test-node",
+      "runtimeExecutable": "cmd",
+      "runtimeArgs": [
+        "/c",
+        "C:\\Users\\Toby\\AppData\\Local\\Temp\\claude\\D--Github-arozos\\292f5e47-462f-491c-9d4f-08a952384705\\scratchpad\\run-node.bat"
+      ],
+      "port": 8090
     }
   ]
 }

+ 10 - 0
CLAUDE.md

@@ -80,6 +80,16 @@ front-end assets (HTML/JS/CSS) plus an optional `init.agi` and any backend
   `Group`, `IconPath`, `Version`, `StartDir`, `SupportFW`/`LaunchFWDir`,
   `SupportEmb`/`LaunchEmb`, `InitFWSize`, `InitEmbSize`, `SupportedExt`. See
   [`src/web/Photo/init.agi`](src/web/Photo/init.agi) for a minimal example.
+- **`Group` (start menu category):** use one of `Media`, `Office`, `Internet`
+  (shown as *Network*), `Engineering` (e.g. Gerber Viewer, 3D Viewer),
+  `Development` (Code Studio, GitApp, Terminal, SQLite Admin, …), `System Tools`
+  or `Utilities`; anything else lands under *Other*. The desktop maps groups
+  to categories in `listMenuCategories` ([`src/web/desktop.html`](src/web/desktop.html)),
+  and adds a *Container Apps* category (published container apps, not
+  modules) only when `/system/appproxy/apps` reports `Containers: true`.
+  The group is also a **permission** decision: `Utilities`, `System Tools`
+  modules are open to every user (`modules.IsUniversalGroup`);
+  every other group needs a permission group grant.
 - **Launch modes:** full page (`StartDir`), **floatWindow** (`SupportFW` +
   `LaunchFWDir`, sized by `InitFWSize`), and **embedded** (`SupportEmb` +
   `LaunchEmb`, sized by `InitEmbSize`) — embedded is what loads when a file is

+ 3 - 0
src/appproxy.go

@@ -69,6 +69,9 @@ func AppProxyInit() {
 		Log: func(message string, err error) {
 			systemWideLogger.PrintAndLog("AppProxy", message, err)
 		},
+		ContainersAvailable: func() bool {
+			return dockerManager != nil
+		},
 	})
 	if err != nil {
 		systemWideLogger.PrintAndLog("AppProxy", "Unable to start the container app proxy", err)

+ 3 - 2
src/mod/appproxy/handlers.go

@@ -75,8 +75,9 @@ func (m *Manager) HandleUserApps(w http.ResponseWriter, r *http.Request) {
 		apps = append(apps, toAppInfo(ep))
 	}
 	sendJSON(w, map[string]interface{}{
-		"Apps":    apps,
-		"IsAdmin": id.IsAdmin,
+		"Apps":       apps,
+		"IsAdmin":    id.IsAdmin,
+		"Containers": m.opts.ContainersAvailable != nil && m.opts.ContainersAvailable(),
 	})
 }
 

+ 50 - 0
src/mod/appproxy/handlers_test.go

@@ -0,0 +1,50 @@
+package appproxy
+
+import (
+	"encoding/json"
+	"net/http/httptest"
+	"testing"
+)
+
+// TestHandleUserAppsContainers checks the desktop is told whether the host
+// runs containers, which decides if it shows the Container Apps category
+func TestHandleUserAppsContainers(t *testing.T) {
+	cases := []struct {
+		name      string
+		available func() bool
+		want      bool
+	}{
+		{"no callback", nil, false},
+		{"docker missing", func() bool { return false }, false},
+		{"docker usable", func() bool { return true }, true},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			m := newTestManager(t, nil)
+			m.opts.ContainersAvailable = c.available
+			if _, err := m.Save(pathEndpoint("grafana", "127.0.0.1:3000"), true, "admin", "nas.local"); err != nil {
+				t.Fatalf("create: %v", err)
+			}
+
+			req := httptest.NewRequest("GET", "/system/appproxy/apps", nil)
+			req.Header.Set("X-Test-User", "alice")
+			rec := httptest.NewRecorder()
+			m.HandleUserApps(rec, req)
+
+			var resp struct {
+				Apps       []AppInfo
+				IsAdmin    bool
+				Containers bool
+			}
+			if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
+				t.Fatalf("decode %q: %v", rec.Body.String(), err)
+			}
+			if resp.Containers != c.want {
+				t.Errorf("Containers = %v, want %v", resp.Containers, c.want)
+			}
+			if len(resp.Apps) != 1 || resp.Apps[0].Slug != "grafana" {
+				t.Errorf("Apps = %+v, want the grafana app", resp.Apps)
+			}
+		})
+	}
+}

+ 5 - 0
src/mod/appproxy/manager.go

@@ -61,6 +61,11 @@ type Options struct {
 
 	//Log receives operational messages
 	Log func(message string, err error)
+
+	//ContainersAvailable reports whether the host can run containers (Docker
+	//is usable), so the desktop only offers its Container Apps category there.
+	//Nil means no container support.
+	ContainersAvailable func() bool
 }
 
 // Stats are runtime counters of one endpoint

+ 14 - 3
src/mod/modules/module.go

@@ -70,13 +70,24 @@ func (m *ModuleHandler) HandleGetExtIcons(w http.ResponseWriter, r *http.Request
 	utils.SendJSONResponse(w, string(js))
 }
 
+// IsUniversalGroup reports whether modules of the given group are open to
+// every user without a permission group grant. Engineering apps are file
+// viewers and tools on the user's own files, so they stay as open as the
+// Utilities they were split from.
+func IsUniversalGroup(group string) bool {
+	switch strings.ToLower(strings.TrimSpace(group)) {
+	case "utilities", "system tools":
+		return true
+	}
+	return false
+}
+
 // Register endpoint. Provide moduleInfo datastructure or unparsed json
 func (m *ModuleHandler) RegisterModule(module ModuleInfo) {
 	m.LoadedModule = append(m.LoadedModule, &module)
 
-	//Add the module into universal module if it is utilities or system tools
-	moduleGroupLowerCase := strings.ToLower(module.Group)
-	if moduleGroupLowerCase == "utilities" || moduleGroupLowerCase == "system tools" {
+	//Add the module into universal module if every user may use its group
+	if IsUniversalGroup(module.Group) {
 		m.userHandler.UniversalModules = append(m.userHandler.UniversalModules, module.Name)
 	}
 }

+ 24 - 0
src/mod/modules/modules_test.go

@@ -229,6 +229,30 @@ func TestRegisterModule_NonUtilitiesGroup(t *testing.T) {
 	}
 }
 
+// TestIsUniversalGroup checks which groups every user may open
+func TestIsUniversalGroup(t *testing.T) {
+	cases := []struct {
+		group string
+		want  bool
+	}{
+		{"Utilities", true},
+		{"utilities", true},
+		{"System Tools", true},
+		{"Engineering", false},
+		{" engineering ", false},
+		{"Development", false},
+		{"System Settings", false},
+		{"Media", false},
+		{"Internet", false},
+		{"", false},
+	}
+	for _, c := range cases {
+		if got := IsUniversalGroup(c.group); got != c.want {
+			t.Errorf("IsUniversalGroup(%q) = %v, want %v", c.group, got, c.want)
+		}
+	}
+}
+
 // TestGetModuleNameList_Empty verifies an empty handler returns empty list
 func TestGetModuleNameList_Empty(t *testing.T) {
 	mh := &ModuleHandler{LoadedModule: []*ModuleInfo{}}

+ 1 - 1
src/web/3D Viewer/init.agi

@@ -13,7 +13,7 @@
 var moduleLaunchInfo = {
     Name: "3D Viewer",
     Desc: "View STL, OBJ, GLB, STEP, G-code and other 3D model files",
-    Group: "Utilities",
+    Group: "Engineering",
     IconPath: "3D Viewer/img/module_icon.svg",
     Version: "1.0",
     StartDir: "3D Viewer/index.html",

+ 3 - 1
src/web/3D Viewer/js/app.js

@@ -1280,7 +1280,9 @@ function applyTheme(theme) {
 
 function initTheme() {
     ao_module_setWindowTheme('white');
-    ao_module_getSystemThemeColor(function (theme) {
+    ao_module_getSystemThemeColor(function (pref) {
+        //The preference is stored as "darkTheme" / "whiteTheme"
+        var theme = (pref === 'darkTheme' || pref === 'dark') ? 'dark' : 'light';
         applyTheme(theme);
         ao_module_setWindowTheme(theme === 'dark' ? 'dark' : 'white');
     });

+ 1 - 1
src/web/Code Studio/init.agi

@@ -13,7 +13,7 @@ newDBTableIfNotExists("Code Studio")
 var moduleLaunchInfo = {
     Name: "Code Studio",
 	Desc: "The best code editor on ArozOS",
-	Group: "Office",
+	Group: "Development",
 	IconPath: _MODULE_ROOT_DIR + "/img/module_icon.png",
 	Version: "2.0",
 	StartDir: _MODULE_ROOT_DIR + "/index.html",

+ 1 - 1
src/web/SQLite Admin/init.agi

@@ -1,6 +1,6 @@
 var moduleLaunchInfo = {
     Name: "SQLite Admin",
-    Group: "Tools",
+    Group: "Development",
     IconPath: "SQLite Admin/img/small_icon.png",
     Version: "1.0",
     StartDir: "SQLite Admin/index.html",

+ 30 - 30
src/web/SystemAO/locale/desktop.json

@@ -13,12 +13,12 @@
                 "listmenu/catergory/all": "全部",
                 "listmenu/catergory/media": "媒體",
                 "listmenu/catergory/office": "辦工",
-                "listmenu/catergory/download": "下載",
-                "listmenu/catergory/files": "檔案",
-                "listmenu/catergory/internet": "網絡",
-                "listmenu/catergory/settings": "設定",
-                "listmenu/catergory/tools": "系統程式",
+                "listmenu/catergory/network": "網絡",
+                "listmenu/catergory/engineering": "工程",
+                "listmenu/catergory/development": "開發",
+                "listmenu/catergory/system": "系統",
                 "listmenu/catergory/utils": "附屬程式",
+                "listmenu/catergory/containerApps": "容器應用程式",
                 "listmenu/catergory/other": "其他",
                 "listmenu/catergory/exit": "登出",
                 "error/connlost/title": "連線中斷",
@@ -125,12 +125,12 @@
                 "listmenu/catergory/all": "全部",
                 "listmenu/catergory/media": "媒體",
                 "listmenu/catergory/office": "辦工",
-                "listmenu/catergory/download": "下載",
-                "listmenu/catergory/files": "檔案",
-                "listmenu/catergory/internet": "網絡",
-                "listmenu/catergory/settings": "設定",
-                "listmenu/catergory/tools": "系統程式",
+                "listmenu/catergory/network": "網絡",
+                "listmenu/catergory/engineering": "工程",
+                "listmenu/catergory/development": "開發",
+                "listmenu/catergory/system": "系統",
                 "listmenu/catergory/utils": "附屬程式",
+                "listmenu/catergory/containerApps": "容器應用程式",
                 "listmenu/catergory/other": "其他",
                 "listmenu/catergory/exit": "登出",
                 "error/connlost/title": "連線中斷",
@@ -237,12 +237,12 @@
                 "listmenu/catergory/all": "全部",
                 "listmenu/catergory/media": "媒体",
                 "listmenu/catergory/office": "办公",
-                "listmenu/catergory/download": "下载",
-                "listmenu/catergory/files": "文件",
-                "listmenu/catergory/internet": "网络",
-                "listmenu/catergory/settings": "设置",
-                "listmenu/catergory/tools": "系统程序",
+                "listmenu/catergory/network": "网络",
+                "listmenu/catergory/engineering": "工程",
+                "listmenu/catergory/development": "开发",
+                "listmenu/catergory/system": "系统",
                 "listmenu/catergory/utils": "附属程序",
+                "listmenu/catergory/containerApps": "容器应用",
                 "listmenu/catergory/other": "其他",
                 "listmenu/catergory/exit": "退出",
                 "error/connlost/title": "连接中断",
@@ -350,12 +350,12 @@
                 "listmenu/catergory/all": "All",
                 "listmenu/catergory/media": "Media",
                 "listmenu/catergory/office": "Office",
-                "listmenu/catergory/download": "Download",
-                "listmenu/catergory/files": "Files",
-                "listmenu/catergory/internet": "Internet",
-                "listmenu/catergory/settings": "Settings",
-                "listmenu/catergory/tools": "System Tools",
+                "listmenu/catergory/network": "Network",
+                "listmenu/catergory/engineering": "Engineering",
+                "listmenu/catergory/development": "Development",
+                "listmenu/catergory/system": "System",
                 "listmenu/catergory/utils": "Utilities",
+                "listmenu/catergory/containerApps": "Container Apps",
                 "listmenu/catergory/other": "Other",
                 "listmenu/catergory/exit": "Logout",
                 "error/connlost/title": "Connection Lost",
@@ -463,12 +463,12 @@
                 "listmenu/catergory/all": "すべて",
                 "listmenu/catergory/media": "メディア",
                 "listmenu/catergory/office": "オフィス",
-                "listmenu/catergory/download": "ダウンロード",
-                "listmenu/catergory/files": "ファイル",
-                "listmenu/catergory/internet": "インターネット",
-                "listmenu/catergory/settings": "設定",
-                "listmenu/catergory/tools": "システムツール",
+                "listmenu/catergory/network": "ネットワーク",
+                "listmenu/catergory/engineering": "エンジニアリング",
+                "listmenu/catergory/development": "開発",
+                "listmenu/catergory/system": "システム",
                 "listmenu/catergory/utils": "ユーティリティ",
+                "listmenu/catergory/containerApps": "コンテナアプリ",
                 "listmenu/catergory/other": "その他",
                 "listmenu/catergory/exit": "ログアウト",
                 "error/connlost/title": "接続が切断されました",
@@ -576,12 +576,12 @@
                 "listmenu/catergory/all": "모두",
                 "listmenu/catergory/media": "미디어",
                 "listmenu/catergory/office": "오피스",
-                "listmenu/catergory/download": "다운로드",
-                "listmenu/catergory/files": "파일",
-                "listmenu/catergory/internet": "인터넷",
-                "listmenu/catergory/settings": "설정",
-                "listmenu/catergory/tools": "시스템 프로그램",
+                "listmenu/catergory/network": "네트워크",
+                "listmenu/catergory/engineering": "엔지니어링",
+                "listmenu/catergory/development": "개발",
+                "listmenu/catergory/system": "시스템",
                 "listmenu/catergory/utils": "유틸리티",
+                "listmenu/catergory/containerApps": "컨테이너 앱",
                 "listmenu/catergory/other": "기타",
                 "listmenu/catergory/exit": "로그아웃",
                 "error/connlost/title": "연결 끊김",

+ 1 - 1
src/web/Web Downloader/init.agi

@@ -1,7 +1,7 @@
 //Define the launchInfo for the module
 var moduleLaunchInfo = {
     Name: "Web Downloader",
-	Group: "Download",
+	Group: "Internet",
 	IconPath: "Web Downloader/img/small_icon.png",
 	Version: "1.0",
 	StartDir: "Web Downloader/index.html",

+ 108 - 17
src/web/desktop.html

@@ -1334,12 +1334,12 @@
                 <div locale="listmenu/catergory/all" class="item groupType selected" group="All">All</div>
                 <div locale="listmenu/catergory/media" class="item groupType" group="Media">Media</div>
                 <div locale="listmenu/catergory/office" class="item groupType" group="Office">Office</div>
-                <div locale="listmenu/catergory/download" class="item groupType" group="Download">Download</div>
-                <div locale="listmenu/catergory/files" class="item groupType" group="Files">Files</div>
-                <div locale="listmenu/catergory/internet" class="item groupType" group="Internet">Internet</div>
-                <div locale="listmenu/catergory/settings" class="item groupType" group="System Settings">System Settings</div>
-                <div locale="listmenu/catergory/tools" class="item groupType" group="System Tools">System Tools</div>
+                <div locale="listmenu/catergory/network" class="item groupType" group="Network">Network</div>
+                <div locale="listmenu/catergory/engineering" class="item groupType" group="Engineering">Engineering</div>
+                <div locale="listmenu/catergory/development" class="item groupType" group="Development">Development</div>
+                <div locale="listmenu/catergory/system" class="item groupType" group="System">System</div>
                 <div locale="listmenu/catergory/utils" class="item groupType" group="Utilities">Utilities</div>
+                <div locale="listmenu/catergory/containerApps" id="containerAppsGroup" class="item groupType" group="Container Apps" style="display:none;">Container Apps</div>
                 <div locale="listmenu/catergory/other" class="item groupType" group="Other">Other</div>
                 <div class="poweroption">
                     <img id="powerIcon" style="width:18px; vertical-align: top; margin-right:4px;" src="img/system/power.svg"></img> <span locale="listmenu/catergory/exit">Exit</span>
@@ -1562,6 +1562,18 @@
         //ListMenu Related Paramters
         var moduleInstalled = [];
         var listMenuShown = false;
+        //Module groups listed under each list menu category. Groups of retired
+        //categories (Download, Files) and Internet still land somewhere sensible
+        //for third party modules; any other group goes to Other.
+        var listMenuCategories = {
+            "Media": ["Media"],
+            "Office": ["Office"],
+            "Network": ["Network", "Internet", "Download"],
+            "Engineering": ["Engineering"],
+            "Development": ["Development"],
+            "System": ["System Tools", "System Settings"],
+            "Utilities": ["Utilities", "Files"]
+        };
 
         //Context Menu variables
         var menuStartLocation = [0, 0];
@@ -2119,6 +2131,17 @@
                     }
                 }
             });
+
+            //Container Apps only exist as a category on hosts that can run containers
+            $.get("system/appproxy/apps", function(data) {
+                if (data && data.error === undefined && data.Containers === true) {
+                    $("#containerAppsGroup").show();
+                } else {
+                    $("#containerAppsGroup").hide();
+                }
+            }).fail(function() {
+                $("#containerAppsGroup").hide();
+            });
         }
 
         function hookLaunchMenuEvents(){
@@ -2145,8 +2168,67 @@
             return moduleInfo["StartDir"] != "" && moduleInfo["Group"] != "Interface Module";
         }
 
+        //Return the list menu category a module group is listed under
+        function listMenuCategoryOf(group) {
+            var g = (group || "").trim().toLowerCase();
+            for (var category in listMenuCategories) {
+                var groups = listMenuCategories[category];
+                for (var i = 0; i < groups.length; i++) {
+                    if (groups[i].toLowerCase() == g) {
+                        return category;
+                    }
+                }
+            }
+            return "Other";
+        }
+
+        //List the published container apps the user may open. Fetched on every
+        //open so apps an administrator just published show up at once
+        function listContainerApps() {
+            $("#listMenuItem").html("");
+            $.get("system/appproxy/apps", function(data) {
+                if ($(".groupType.selected").attr("group") != "Container Apps") {
+                    //The user moved on to another category meanwhile
+                    return;
+                }
+                $("#listMenuItem").html("");
+                var apps = (data && data.error === undefined && data.Apps) ? data.Apps : [];
+                apps.forEach(function(app) {
+                    generateContainerAppListMenuItem(app);
+                });
+                if (apps.length == 0) {
+                    $("#listMenuItem").html(`<div class="item module"><span><img src="img/system/not found.png">No container app found</span></div>`);
+                }
+            });
+        }
+
+        function generateContainerAppListMenuItem(app) {
+            var item = $(`<div class="item module" draggable="true"
+                        ondragstart="listMenuContainerAppDragStart(event,this);"
+                        onclick="openContainerAppFromMenu(this);" ontouchstart="openContainerAppFromMenu(this);">
+                        <span><img></img><span class="containerAppName"></span></span>
+                    </div>`);
+            //Names and icons are set as data so an app name is never parsed as HTML
+            item.attr("appslug", app.Slug).attr("appname", app.Name);
+            item.find("img").attr("src", app.Icon || "ContainerApps/img/app.svg");
+            item.find(".containerAppName").text(app.Name);
+            $("#listMenuItem").append(item);
+        }
+
+        function openContainerAppFromMenu(object) {
+            openContainerAppShortcut({
+                ShortcutName: $(object).attr("appname"),
+                ShortcutPath: $(object).attr("appslug"),
+                ShortcutImage: $(object).find("img").first().attr("src")
+            });
+        }
+
         function listModulesType(groupType) {
             var listingItems = [];
+            if (groupType == "Container Apps") {
+                listContainerApps();
+                return;
+            }
             if (groupType == "All") {
                 //List all of the items
                 for (var i = 0; i < moduleInstalled.length; i++) {
@@ -2154,18 +2236,11 @@
                         listingItems.push(moduleInstalled[i]);
                     }
                 }
-            } else if (groupType == "Other") {
-                var excludeList = ["Media", "Office", "Download", "Files", "Internet", "System Settings", "System Tools", "Utilities"];
-                //List the Other groups
-                for (var i = 0; i < moduleInstalled.length; i++) {
-                    if (excludeList.includes(moduleInstalled[i]["Group"]) == false && isLaunchableModule(moduleInstalled[i])) {
-                        listingItems.push(moduleInstalled[i]);
-                    }
-                }
             } else {
-                //List the given group
+                //List the modules whose group falls under the given category (Other
+                //collects every group no category claims)
                 for (var i = 0; i < moduleInstalled.length; i++) {
-                    if (moduleInstalled[i]["Group"] == groupType && isLaunchableModule(moduleInstalled[i])) {
+                    if (listMenuCategoryOf(moduleInstalled[i]["Group"]) == groupType && isLaunchableModule(moduleInstalled[i])) {
                         listingItems.push(moduleInstalled[i]);
                     }
                 }
@@ -4568,15 +4643,18 @@
             var sourceFilepath = ev.dataTransfer.getData("filepath");
 
             // ── List-menu app drag → create shortcut at drop location ──────────────
-            if (ev.dataTransfer.getData("moduledragtype") === "module" && target.attr("id") === "bgwrapper") {
+            var _dragType = ev.dataTransfer.getData("moduledragtype");
+            if ((_dragType === "module" || _dragType === "app") && target.attr("id") === "bgwrapper") {
                 var _mName = ev.dataTransfer.getData("modulename");
                 var _mIcon = ev.dataTransfer.getData("moduleicon");
+                //Container apps are opened by slug, modules by name
+                var _mPath = _dragType === "app" ? ev.dataTransfer.getData("moduleslug") : _mName;
                 var _locs  = gridPositionAllocate(ev.clientX, ev.clientY, 1);
                 var _gx = _locs[0][0], _gy = _locs[0][1];
                 $.ajax({
                     url:    "system/desktop/createShortcut",
                     method: "POST",
-                    data:   { stype: "module", stext: _mName, spath: _mName, sicon: _mIcon },
+                    data:   { stype: _dragType, stext: _mName, spath: _mPath, sicon: _mIcon },
                     success: function(data) {
                         if (data.error) { console.log(data.error); return; }
                         // Set position then refresh; filename is <name>.shortcut
@@ -5121,6 +5199,19 @@
             ev.dataTransfer.setData("filedata",  "");
         }
 
+        //Dragging a container app out of the list menu pins it as an "app" shortcut
+        function listMenuContainerAppDragStart(ev, el) {
+            ev.stopPropagation();
+            ev.dataTransfer.effectAllowed = "copy";
+            ev.dataTransfer.setData("moduledragtype", "app");
+            ev.dataTransfer.setData("modulename",     $(el).attr("appname"));
+            ev.dataTransfer.setData("moduleslug",     $(el).attr("appslug"));
+            ev.dataTransfer.setData("moduleicon",     $(el).find("img").first().attr("src"));
+            ev.dataTransfer.setData("filepath",  "");
+            ev.dataTransfer.setData("filename",  "");
+            ev.dataTransfer.setData("filedata",  "");
+        }
+
         //Pass in the object fullpath and return the icon object onscreen
         function getObjectFromPath(path) {
             var object;

+ 1 - 0
src/web/script/ao_module.js

@@ -178,6 +178,7 @@ function ao_module_setWindowTheme(newtheme="dark"){
 }
 
 // ao_module_getSystemThemeColor(callback) => Get the global theme color of current system, and return the color value in callback function.
+// The value is the raw preference: "darkTheme" or "whiteTheme" (not "dark" / "light" like ao_module_onThemeChanged).
 function ao_module_getSystemThemeColor(callback){
     $.get("../../system/file_system/preference?key=file_explorer/theme",function(data){
             callback(data);