ソースを参照

Revamp file explorer toolbar and sorting UI

Adds a classic operation toolbar above the nav bar with responsive collapse behavior, a persisted toolbar visibility toggle, and automatic de-duplication of overlapping actions in the overflow menu. Replaces the old sort dropdown with a dedicated sort menu/button, including active-state handling and updated sort interactions. Also improves mobile behavior (multi-select toggle, nav/status layout, zoom visibility), refines selection/checkmark logic, updates grid sizing/default zoom, and includes related icon and localization additions.
Toby Chui 1 週間 前
コミット
7326680670

+ 206 - 8
src/web/SystemAO/file_system/file_explorer.css

@@ -1586,6 +1586,7 @@ i.blue{
     top: 46px;
     max-height: 70vh;
     overflow-y: auto;
+    overflow-x: hidden;
 }
 .fmSortRow select {
     flex-grow: 1;
@@ -1770,14 +1771,21 @@ i.blue{
     transition: opacity 0.12s ease;
     vertical-align: middle;
 }
-.detailstable tr.fileObject:hover .fmCheck { opacity: 1; }
-.detailstable tr.fileObject.selected .fmCheck {
+/*
+    Check marks belong to multi-select, not to selection: a single click only
+    highlights. #folderView gains .fmMultiSelect once ctrl/shift is held, mobile
+    multi-select is on, or more than one item is already picked.
+*/
+.fmCheck { display: none; }
+#folderView.fmMultiSelect .fmCheck { display: inline-block; }
+#folderView.fmMultiSelect .detailstable tr.fileObject:hover .fmCheck { opacity: 1; }
+#folderView.fmMultiSelect .detailstable tr.fileObject.selected .fmCheck {
     opacity: 1;
     background: var(--fs-accent);
     border-color: var(--fs-accent);
     position: relative;
 }
-.detailstable tr.fileObject.selected .fmCheck::after {
+#folderView.fmMultiSelect .detailstable tr.fileObject.selected .fmCheck::after {
     content: "";
     position: absolute;
     left: 4.5px;
@@ -1796,13 +1804,12 @@ i.blue{
 .fileviewList .fileObject.item.selected,
 .detailstable tr.fileObject.details.selected {
     background: var(--fs-selected) !important;
-    box-shadow: inset 2px 0 0 var(--fs-accent);
 }
 .fileviewList .fileObject.item.selected .filename,
 .detailstable tr.fileObject.details.selected td { color: var(--fs-text) !important; }
 
 /* ---------- Grid view ---------- */
-#folderView { --fm-tile: 150px; }
+#folderView { --fm-tile: 170px; }
 
 #folderList.fileviewList,
 #fileList.fileviewList { padding: 0 !important; }
@@ -1873,7 +1880,7 @@ i.blue{
     border-width: 0 2px 2px 0;
     transform: rotate(45deg);
 }
-.fileObject.card.selected .fmTileCheck { display: block; }
+#folderView.fmMultiSelect .fileObject.card.selected .fmTileCheck { display: block; }
 
 /* Date group headers */
 .fmGroupHeader {
@@ -1932,8 +1939,18 @@ i.blue{
 .fmStatusBtn svg { display: block; width: 100%; height: 100%; }
 
 @media only screen and (max-width: 720px) {
-    #fmZoom { display: none; }
-    .fmStatusToggle { display: none; }
+    /*
+        updateZoomControlVisibility() shows this with an inline style, which
+        outranks a plain media query - hence !important. Without it the zoom
+        control rendered at full size on phones and pushed the status bar over.
+    */
+    /*
+        Only the zoom slider is too wide for a phone. The view toggle and the
+        properties button are small enough to keep, and hiding them left the
+        footer with no controls at all.
+    */
+    #fmZoom { display: none !important; }
+    .fmStatusToggle button { width: 30px; height: 26px; }
 }
 
 /* ---------- Details column widths + responsive column dropping ----------
@@ -1962,3 +1979,184 @@ i.blue{
 .detailstable tr.fileObject.details {
     border: none !important;
 }
+
+
+/* ---------- Classic file operation toolbar ---------- */
+/*
+    Sits above the nav row; toggled from the overflow menu and remembered server
+    side under file_explorer/oprbar.
+
+    It must never wrap to a second row - that was the old behaviour and it ate
+    vertical space the file list needs. Instead the bar collapses in stages as
+    the window narrows: first the stacked text-button groups drop out (rightmost
+    first, since those actions are the least used), then the big buttons lose
+    their labels and become icons. Anything dropped reappears in the overflow
+    menu automatically, because syncOprMenuDuplicates() checks what is actually
+    visible rather than assuming.
+*/
+.fileOprBar {
+    display: flex;
+    align-items: flex-start;
+    flex-wrap: nowrap;
+    gap: 2px;
+    padding: 6px 12px 4px 12px;
+    border-bottom: 1px solid var(--fs-line);
+    background: var(--fs-bg);
+    overflow: hidden;
+    white-space: nowrap;
+}
+
+.fileOprBar .fileOprBtn {
+    flex-shrink: 0;
+    min-width: 0;
+}
+
+/*
+    One line per split. The legacy .navibar .fileoprGroupDivider rule already
+    draws a border-right; adding a border-left here rendered two lines side by
+    side wherever two groups met.
+*/
+.fileOprBar .fileoprGroupDivider {
+    display: inline-block;
+    vertical-align: top;
+    border-left: none;
+    border-right: 1px solid var(--fs-line-soft);
+    margin: 0 6px 0 2px;
+    padding-right: 8px;
+    flex-shrink: 0;
+}
+
+.fileOprBar .fileOprBtn .oprtxt {
+    font-size: 12px;
+    margin: 2px 0 0 0;
+    white-space: nowrap;
+}
+
+/* Stage 1: drop the stacked groups, rightmost (least used) first */
+@media only screen and (max-width: 960px) {
+    .fileOprBar .fileoprGroupDivider:nth-of-type(4) { display: none; }
+}
+@media only screen and (max-width: 840px) {
+    .fileOprBar .fileoprGroupDivider:nth-of-type(3) { display: none; }
+}
+@media only screen and (max-width: 600px) {
+    .fileOprBar .fileoprGroupDivider { display: none; }
+}
+
+/* Stage 2: big buttons become icon-only */
+@media only screen and (max-width: 720px) {
+    .fileOprBar .fileOprBtn .oprtxt { display: none; }
+    .fileOprBar .fileOprBtn .opricon { width: 32px; }
+    .fileOprBar { padding-bottom: 6px; }
+}
+
+/* Stage 3: below this the nav row alone is the interface */
+@media only screen and (max-width: 720px) {
+    .fileOprBar { display: none !important; }
+}
+
+
+/* ---------- Grid tile corrections ---------- */
+/*
+    The legacy .card .content .description rule positions the size absolutely
+    against the card, which only worked while cards had a fixed 20em height.
+    Cards are content-sized now, so it has to sit in the normal flow or it
+    lands on top of the filename.
+*/
+.fileviewList .fileObject.card .content .description {
+    position: static !important;
+    bottom: auto !important;
+    left: auto !important;
+    font-size: 11px;
+    color: var(--fs-text-faint);
+    margin-top: 2px;
+}
+
+/* Folders are drawn glyphs, not photos - the thumbnail plate behind them just
+   boxes in the icon. Files keep it so real thumbnails have something to sit on. */
+.fileviewList .fileObject.card[type="folder"] .image {
+    background: transparent !important;
+}
+
+/* ---------- Sort menu ---------- */
+/* Anchored to the sort button on the right of the nav row, like the "..." menu */
+.fmSortMenu {
+    right: 52px;
+    top: 46px;
+    min-width: 210px;
+}
+
+/* Greyed section label above the options */
+.fsMenuHeader {
+    padding: 4px 9px 6px 9px;
+    font-size: 12px;
+    color: var(--fs-text-faint);
+    cursor: default;
+    user-select: none;
+}
+
+/* The tick sits in the icon column, so the labels stay aligned whether or not
+   a mode is active */
+.fmSortOption .fmSortCheck { visibility: hidden; color: var(--fs-accent); }
+.fmSortOption.active .fmSortCheck { visibility: visible; }
+.fmSortOption.active { color: var(--fs-text); }
+
+
+/* ======================================================================
+   Mobile layout
+   ====================================================================== */
+@media only screen and (max-width: 720px) {
+    /*
+        The nav row becomes two rows: controls on top, the path on its own row
+        underneath. A single row cannot hold the breadcrumb and the buttons at
+        phone widths without the path collapsing to nothing.
+    */
+    .fmNav {
+        flex-wrap: wrap;
+        row-gap: 6px;
+        padding: 6px 8px;
+    }
+
+    .fmNavBtns { flex-shrink: 0; }
+
+    .fmPathbar {
+        order: 10;
+        flex-basis: 100%;
+        min-width: 0;
+    }
+
+    /*
+        Push the sort and overflow buttons to the right edge. margin-left:auto on
+        the first of them absorbs the free space, so both end up flush right
+        regardless of how many nav buttons precede them.
+    */
+    .fmNav #fmSortBtn { margin-left: auto; }
+
+    /* Menus anchor to the row above the path */
+    .fmMoreMenu, .fmSortMenu {
+        top: 44px;
+        max-width: calc(100vw - 24px);
+    }
+
+    /* Status bar: text only, no controls to overflow */
+    #fmStatusBar {
+        padding: 5px 10px;
+        gap: 8px;
+        min-height: 0;
+    }
+    #fmStatusBar .fmStatusBtn { flex-shrink: 0; }
+    #selectInfo, #fmSelectionSize { white-space: nowrap; }
+
+    /* Guard: every icon in the bar stays at its intended box even if some
+       future rule forgets to size the button around it */
+    #fmStatusBar svg { width: 100%; height: 100%; }
+    #fmStatusBar .fmStatusBtn { width: 26px; height: 26px; }
+}
+
+/* Menu entries that cannot act on the current selection are hidden outright
+   rather than greyed - the menu is long enough already. */
+.fsMenuItem.fmOprUnusable { display: none !important; }
+
+
+/* Menu entries that only make sense on a touch device */
+.fmMobileOnly { display: none; }

+ 70 - 35
src/web/SystemAO/file_system/file_explorer.html

@@ -27,6 +27,38 @@
     </head>
     <body class="whiteTheme">
         <div id="navibar" class="navibar">
+            <!--
+                Classic file operation toolbar. Kept for users who prefer it and
+                shown by default; the preference lives server side under
+                file_explorer/oprbar so it follows the user between browsers.
+                Ignored on mobile, where the layout is different anyway.
+            -->
+            <div id="fileOprBar" class="fileOprBar">
+                <button class="fileOprBtn" title="Open" onclick="openViaButton();" data-opr="open"><img class="opricon" src="img/opr/open.svg"><p class="oprtxt" locale="fileopr/Open">Open</p></button>
+                <button class="fileOprBtn" title="Copy" onclick="copy();" data-opr="copy"><img class="opricon" src="img/opr/copy.svg"><p class="oprtxt" locale="fileopr/Copy">Copy</p></button>
+                <button class="fileOprBtn" title="Paste" onclick="paste();" data-opr="paste"><img class="opricon" src="img/opr/paste.svg"><p class="oprtxt" locale="fileopr/Paste">Paste</p></button>
+                <div class="fileoprGroupDivider">
+                    <button class="fileoprSmallBtn" title="Open with" onclick="openWith();" data-opr="openwith"><i class="external icon"></i> <span locale="fileopr/Open with">Open With</span></button><br>
+                    <button class="fileoprSmallBtn" title="Cut" onclick="cut();" data-opr="cut"><i class="blue cut icon"></i> <span locale="fileopr/Cut">Cut</span></button><br>
+                    <button class="fileoprSmallBtn" title="Rename" onclick="rename();" data-opr="rename"><i class="teal i cursor icon"></i> <span locale="fileopr/Rename">Rename</span></button>
+                </div>
+                <button class="fileOprBtn" title="Upload" onclick="upload(); exitMultiSelectMode();" data-opr="upload"><img class="opricon" src="img/opr/upload.svg"><p class="oprtxt" locale="fileopr/Upload">Upload</p></button>
+                <button class="fileOprBtn" title="Download" onclick="downloadFile(); exitMultiSelectMode();" data-opr="download"><img class="opricon" src="img/opr/download.svg"><p class="oprtxt" locale="fileopr/Download">Download</p></button>
+                <div class="fileoprGroupDivider"></div>
+                <button class="twolines fileOprBtn" title="New Folder" onclick="newFolder(); exitMultiSelectMode();" data-opr="newfolder"><img class="opricon" src="img/opr/new_folder.svg"><p class="oprtxt" locale="fileopr/New Folder">New<br>Folder</p></button>
+                <div class="fileoprGroupDivider">
+                    <button class="fileoprSmallBtn" title="New File" onclick="newfile();" data-opr="newfile"><i class="file outline icon"></i> <span locale="fileopr/New File">New File</span></button><br>
+                    <button class="fileoprSmallBtn" title="Create Zip" onclick="zipFile();" data-opr="zip"><i class="brown zip file icon"></i> <span locale="fileopr/Create Zip">Create Zip</span></button><br>
+                    <button class="fileoprSmallBtn" title="Unzip Here" onclick="unzipHere();" data-opr="unzip"><i class="inbox icon"></i> <span locale="fileopr/Unzip Here">Unzip Here</span></button><br>
+                </div>
+                <div class="fileoprGroupDivider">
+                    <button class="fileoprSmallBtn" title="Refresh" onclick="refreshList();" data-opr="refresh"><i class="green refresh icon"></i> <span locale="fileopr/Refresh">Refresh</span></button><br>
+                    <button class="fileoprSmallBtn" title="Home" onclick="openHomeDir();" data-opr="home"><i class="home icon"></i> <span locale="fileopr/Home">Home</span></button><br>
+                    <button class="fileoprSmallBtn" title="File Info" onclick="showFileProperties();" data-opr="fileinfo"><i class="blue info circle icon"></i> <span locale="fileopr/File Info">File Info</span></button><br>
+                </div>
+                <button class="fileOprBtn" title="Delete" onclick="deleteFile();" data-opr="delete"><img class="opricon" src="img/opr/delete.svg"><p class="oprtxt" locale="fileopr/Delete">Delete</p></button>
+                <button class="fileOprBtn" title="Share File" onclick="shareFile();" data-opr="share"><img class="opricon" src="img/opr/share.svg"><p class="oprtxt" locale="fileopr/Share">Share</p></button>
+            </div>
             <div class="fmNav">
                 <div class="fmNavBtns">
                     <button id="sidebarToggleBtn" class="fsIconBtn" title="Toggle Folder List" onclick="toggleSidebar();"><span data-fsicon="menu"></span></button>
@@ -37,6 +69,20 @@
                 </div>
 
                 <!-- Breadcrumb. Clicking the empty area switches it to a text field. -->
+                <!-- Sort menu. Same shell as the overflow menu; the active mode
+                     carries the tick in the icon column on the left. -->
+                <div class="fsMenu fmSortMenu" id="fmSortMenu">
+                    <div class="fsMenuHeader" locale="menu/sort/title">Sort items by</div>
+                    <div class="fsMenuItem fmSortOption" data-sort="default" onclick="setSortMode('default');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/asc">Name (A to Z)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="reverse" onclick="setSortMode('reverse');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/desc">Name (Z to A)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="smallToLarge" onclick="setSortMode('smallToLarge');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/small">Size (Smallest)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="largeToSmall" onclick="setSortMode('largeToSmall');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/large">Size (Largest)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="mostRecent" onclick="setSortMode('mostRecent');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/mostrecent">Date (Newest)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="leastRecent" onclick="setSortMode('leastRecent');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/leastrecent">Date (Oldest)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="fileTypeAsce" onclick="setSortMode('fileTypeAsce');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/typeAsc">Type (A to Z)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="fileTypeDesc" onclick="setSortMode('fileTypeDesc');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/typeDes">Type (Z to A)</span></div>
+                    <div class="fsMenuItem fmSortOption" data-sort="smart" onclick="setSortMode('smart');"><span class="fsMenuIcon fmSortCheck" data-fsicon="check"></span><span locale="menu/sort/smart">Smart</span></div>
+                </div>
                 <div class="fsPill fmPathbar" id="fmPathbar" onclick="showEditCurrentPathInput(event);">
                     <button class="fmPathHome" title="Home" onclick="event.stopPropagation(); openHomeDir();"><span data-fsicon="home"></span></button>
                     <div id="pathDisplayField" class="ui breadcrumb addressText pathDisplay">
@@ -55,14 +101,14 @@
                     <button class="fmCaseBtn" title="Case Sensitive" onclick="toggleCaseSensitive(this);">Aa</button>
                     <span class="fmSearchIcon" data-fsicon="search"></span>
                 </div>
-
-                <div class="fsViewToggle fmViewToggle">
+                
+                <!-- <div class="fsViewToggle fmViewToggle">
                     <button class="videmode" mode="grid" title="Block View" onclick="changeViewMode(this);"><span data-fsicon="viewGrid"></span></button>
                     <button class="videmode" mode="list" title="List View" onclick="changeViewMode(this);"><span data-fsicon="viewList"></span></button>
                     <button class="videmode" mode="details" title="Detail View" onclick="changeViewMode(this);"><span data-fsicon="viewDetails"></span></button>
-                </div>
+                </div>-->
 
-                <button id="togglePropertiesViewBtn" class="fsMoreBtn" title="Show Properties" onclick="togglePropertiesView(this);"><span data-fsicon="sidePanel"></span></button>
+                <button id="fmSortBtn" class="fsMoreBtn" title="Sort" onclick="toggleSortMenu(event);"><span data-fsicon="sort"></span></button>
                 <button id="fmMoreBtn" class="fsMoreBtn" title="More Options" onclick="toggleFileOprMenu(event);"><span data-fsicon="more"></span></button>
 
                 <!--
@@ -70,43 +116,32 @@
                     covers per-item actions; this covers the rest.
                 -->
                 <div class="fsMenu fmMoreMenu" id="fmMoreMenu">
-                    <div class="fsMenuItem" onclick="runFileOpr(openViaButton);"><span class="fsMenuIcon" data-fsicon="open"></span><span locale="fileopr/Open">Open</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(openWith);"><span class="fsMenuIcon" data-fsicon="openWith"></span><span locale="fileopr/Open with">Open With</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(openViaButton);" data-opr="open"><span class="fsMenuIcon" data-fsicon="open"></span><span locale="fileopr/Open">Open</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(openWith);" data-opr="openwith"><span class="fsMenuIcon" data-fsicon="openWith"></span><span locale="fileopr/Open with">Open With</span></div>
                     <div class="fsMenuSep"></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(copy);"><span class="fsMenuIcon" data-fsicon="copy"></span><span locale="fileopr/Copy">Copy</span><span class="fsMenuShortcut">Ctrl + C</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(cut);"><span class="fsMenuIcon" data-fsicon="cut"></span><span locale="fileopr/Cut">Cut</span><span class="fsMenuShortcut">Ctrl + X</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(paste);"><span class="fsMenuIcon" data-fsicon="paste"></span><span locale="fileopr/Paste">Paste</span><span class="fsMenuShortcut">Ctrl + V</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(rename);"><span class="fsMenuIcon" data-fsicon="rename"></span><span locale="fileopr/Rename">Rename</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(deleteFile);"><span class="fsMenuIcon" data-fsicon="trash"></span><span locale="fileopr/Delete">Delete</span><span class="fsMenuShortcut">Del</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(copy);" data-opr="copy"><span class="fsMenuIcon" data-fsicon="copy"></span><span locale="fileopr/Copy">Copy</span><span class="fsMenuShortcut">Ctrl + C</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(cut);" data-opr="cut"><span class="fsMenuIcon" data-fsicon="cut"></span><span locale="fileopr/Cut">Cut</span><span class="fsMenuShortcut">Ctrl + X</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(paste);" data-opr="paste"><span class="fsMenuIcon" data-fsicon="paste"></span><span locale="fileopr/Paste">Paste</span><span class="fsMenuShortcut">Ctrl + V</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(rename);" data-opr="rename"><span class="fsMenuIcon" data-fsicon="rename"></span><span locale="fileopr/Rename">Rename</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(deleteFile);" data-opr="delete"><span class="fsMenuIcon" data-fsicon="trash"></span><span locale="fileopr/Delete">Delete</span><span class="fsMenuShortcut">Del</span></div>
                     <div class="fsMenuSep"></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(upload);"><span class="fsMenuIcon" data-fsicon="upload"></span><span locale="fileopr/Upload">Upload</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(downloadFile);"><span class="fsMenuIcon" data-fsicon="download"></span><span locale="fileopr/Download">Download</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(shareFile);"><span class="fsMenuIcon" data-fsicon="share"></span><span locale="fileopr/Share">Share</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(upload);" data-opr="upload"><span class="fsMenuIcon" data-fsicon="upload"></span><span locale="fileopr/Upload">Upload</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(downloadFile);" data-opr="download"><span class="fsMenuIcon" data-fsicon="download"></span><span locale="fileopr/Download">Download</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(shareFile);" data-opr="share"><span class="fsMenuIcon" data-fsicon="share"></span><span locale="fileopr/Share">Share</span></div>
                     <div class="fsMenuSep"></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(newFolder);"><span class="fsMenuIcon" data-fsicon="newFolder"></span><span locale="fileopr/New Folder">New Folder</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(newfile);"><span class="fsMenuIcon" data-fsicon="newFile"></span><span locale="fileopr/New File">New File</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(zipFile);"><span class="fsMenuIcon" data-fsicon="zip"></span><span locale="fileopr/Create Zip">Create Zip</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(unzipHere);"><span class="fsMenuIcon" data-fsicon="unzip"></span><span locale="fileopr/Unzip Here">Unzip Here</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(newFolder);" data-opr="newfolder"><span class="fsMenuIcon" data-fsicon="newFolder"></span><span locale="fileopr/New Folder">New Folder</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(newfile);" data-opr="newfile"><span class="fsMenuIcon" data-fsicon="newFile"></span><span locale="fileopr/New File">New File</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(zipFile);" data-opr="zip"><span class="fsMenuIcon" data-fsicon="zip"></span><span locale="fileopr/Create Zip">Create Zip</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(unzipHere);" data-opr="unzip"><span class="fsMenuIcon" data-fsicon="unzip"></span><span locale="fileopr/Unzip Here">Unzip Here</span></div>
                     <div class="fsMenuSep"></div>
+                    <div class="fsMenuItem fmMobileOnly" id="fmMultiSelectItem" onclick="toggleMobileMultiSelect();"><span class="fsMenuIcon" data-fsicon="selectAll"></span><span locale="fileopr/Multi Select">Multi-select</span><span class="fsMenuCheck" data-fsicon="check"></span></div>
                     <div class="fsMenuItem" onclick="runFileOpr(selectAll);"><span class="fsMenuIcon" data-fsicon="selectAll"></span><span locale="fileopr/Select All">Select All</span><span class="fsMenuShortcut">Ctrl + A</span></div>
                     <div class="fsMenuItem" onclick="runFileOpr(clearSelection);"><span class="fsMenuIcon" data-fsicon="clearSel"></span><span locale="fileopr/Clear Select">Clear Selection</span></div>
-                    <div class="fsMenuItem" onclick="runFileOpr(showFileProperties);"><span class="fsMenuIcon" data-fsicon="info"></span><span locale="fileopr/File Info">File Info</span></div>
+                    <div class="fsMenuItem fmDupOfToolbar" onclick="runFileOpr(showFileProperties);" data-opr="fileinfo"><span class="fsMenuIcon" data-fsicon="info"></span><span locale="fileopr/File Info">File Info</span></div>
                     <div class="fsMenuSep"></div>
                     <div class="fsMenuItem" onclick="runFileOpr(toggleDarkTheme);"><span class="fsMenuIcon" data-fsicon="moon"></span><span locale="fileopr/Dark Theme">Dark Theme</span></div>
-                    <div class="fsMenuItem fmSortRow">
-                        <span class="fsMenuIcon" data-fsicon="viewDetails"></span>
-                        <select id="sortingMethodSelector" title="Sorting Method" onchange="updateSortingMethods();" autocomplete="off">
-                            <option value="default" locale="menu/sort/asc">Ascending</option>
-                            <option value="reverse" locale="menu/sort/desc">Descending</option>
-                            <option value="smallToLarge" locale="menu/sort/small">Smallest</option>
-                            <option value="largeToSmall" locale="menu/sort/large"> Largest</option>
-                            <option value="mostRecent" locale="menu/sort/mostrecent">Most Recent</option>
-                            <option value="leastRecent" locale="menu/sort/leastrecent">Least Recent</option>
-                            <option value="fileTypeAsce" locale="menu/sort/typeAsc">File Type (Asce)</option>
-                            <option value="fileTypeDesc" locale="menu/sort/typeDes">File Type (Desc)</option>
-                            <option value="smart" locale="menu/sort/smart">Smart</option>
-                        </select>
-                    </div>
+                    <div class="fsMenuSep fmDupSep"></div>
+                    <div class="fsMenuItem" id="fmToolbarToggle" onclick="toggleFileOprBar();"><span locale="fileopr/Toolbar">Operation Toolbar</span><span class="fsMenuCheck" data-fsicon="check"></span></div>
                 </div>
             </div>
 
@@ -167,14 +202,14 @@
             <div class="fmStatusSpacer"></div>
             <div id="fmZoom" title="Thumbnail Size">
                 <span class="fmZoomIcon" data-fsicon="viewGrid"></span>
-                <input type="range" id="fmZoomSlider" min="100" max="260" step="10" value="150" oninput="setGridZoom(this.value);">
+                <input type="range" id="fmZoomSlider" min="100" max="170" step="10" value="170" oninput="setGridZoom(this.value);">
             </div>
             <div class="fsViewToggle fmStatusToggle">
                 <button class="videmode" mode="grid" title="Block View" onclick="changeViewMode(this);"><span data-fsicon="viewGrid"></span></button>
                 <button class="videmode" mode="list" title="List View" onclick="changeViewMode(this);"><span data-fsicon="viewList"></span></button>
                 <button class="videmode" mode="details" title="Detail View" onclick="changeViewMode(this);"><span data-fsicon="viewDetails"></span></button>
             </div>
-            <button class="fmStatusBtn" title="Show Properties" onclick="togglePropertiesView(this);"><span data-fsicon="info"></span></button>
+            <button id="togglePropertiesViewBtn" class="fmStatusBtn" title="Show Properties" onclick="togglePropertiesView(this);"><span data-fsicon="sidePanel"></span></button>
         </div>
 
         <!-- Other popup windows -->

+ 14 - 5
src/web/SystemAO/file_system/js/explorer/boot.js

@@ -17,13 +17,25 @@ $(document).ready(function(){
         //Runs before the first listing so the toolbar never flashes empty.
         FSIcons.inject(document);
 
+        //Menu rows must stay single line even when the label carries a <br>
+        flattenMenuLabels();
+
+        //Multi-select only exists on touch devices, where a tap opens by default
+        $("#fmMultiSelectItem").css("display", isMobile ? "flex" : "none");
+
         //Delegated drag/drop/dblclick for the file list. Bound once, not per render.
         bindFileListDelegates();
 
+        //Restore the operation toolbar preference (shown unless turned off)
+        loadPreference("file_explorer/oprbar", function(value){
+            showOprBar = (value !== "false");
+            applyOprBarVisibility();
+        });
+
         //Restore the saved grid tile size
         loadPreference("file_explorer/gridZoom", function(value){
             let z = parseInt(value);
-            if (!isNaN(z) && z >= 100 && z <= 260){
+            if (!isNaN(z) && z >= 100 && z <= 170){
                 gridZoom = z;
                 $("#fmZoomSlider").val(z);
                 $("#folderView").css("--fm-tile", z + "px");
@@ -40,7 +52,7 @@ $(document).ready(function(){
         initSystemInfo();
         initUploadMode();
         $(".dropdown").dropdown();
-        $("#sortingMethodSelector").dropdown("set selected", sortMode);
+        updateSortMenuState();
         updateSelectedObjectsCount();
         initWindowSizes(false);
         
@@ -154,9 +166,6 @@ $(document).ready(function(){
         $(".viewportBtns").addClass("mobile");
         $(".addressBar").append($(".viewportBtns"));
 
-        //Move the sorting dropdown to sidebar
-        $("#mobileSortDropdown").append($("#sortingMethodSelector"));
-
         $(".desktopOnly").hide();
         $(".mobileOnly").show();
     }else{

+ 1 - 1
src/web/SystemAO/file_system/js/explorer/listing.js

@@ -121,7 +121,7 @@ function listDirectory(path, callback=undefined, recordUndo=true){
             if (data.error == undefined){
                 sortMode = data;
             }
-            $("#sortingMethodSelector").dropdown("set selected", sortMode);
+            updateSortMenuState();
             //Start listdir event
             $.ajax({
                 url: "../../system/file_system/listDir",

+ 4 - 1
src/web/SystemAO/file_system/js/explorer/render.js

@@ -263,7 +263,7 @@ function sortByColumn(key){
         return;
     }
     sortMode = (sortMode == col.asc) ? col.desc : col.asc;
-    $("#sortingMethodSelector").val(sortMode);
+    updateSortMenuState();
     $.ajax({
         url: "../../system/file_system/sortMode",
         method: "POST",
@@ -379,6 +379,9 @@ function computeGridSize(){
 
 function finaliseRender(folderCount, fileCount, callback){
     updateListDensity();
+    //The nav row can wrap to a second line (mobile), so its height is only
+    //settled once content has been laid out. Re-measure before sizing the panes.
+    initWindowSizes(false);
     bindFileObjectEvents();
     currentFilelist.sort();
 

+ 55 - 5
src/web/SystemAO/file_system/js/explorer/selection.js

@@ -26,9 +26,10 @@ function bindFileObjectEvents(){
             if ($(this).hasClass("selected")){
                 $(this).removeClass("selected");
 
-                //if there are no more selected files on the page
-                //exit multi-select mode
-                if ($(".fileObject.selected").length == 0){
+                //Deselecting the last item normally ends multi-select, but when
+                //the user switched it on deliberately it stays on until they
+                //switch it off again
+                if ($(".fileObject.selected").length == 0 && !stickyMultiSelect){
                     exitMultiSelectMode();
                 }
             }else{
@@ -270,8 +271,16 @@ function bindFileObjectEvents(){
 
     //Handle right click on storage roots
     $("#storageroot").off("contextmenu").on("contextmenu", function(e){
-        if ($(e.target).is("i")){
-            e.target = e.target.parentElement;
+        /*
+            Resolve the row from whatever was actually right clicked. The sidebar
+            rows now wrap their icon and label in spans (and the icon is an inline
+            SVG), so e.target is usually a descendant rather than the .dir.item
+            itself - the old "if it is an <i>, step up one level" check no longer
+            reached it and the menu never opened.
+        */
+        let row = $(e.target).closest(".dir.item")[0];
+        if (row != undefined){
+            e.target = row;
         }
 
         $("#storageroot").find(".dir.item").each(function(){
@@ -326,6 +335,18 @@ function updateSelectedObjectsCount(){
         }
     });
     $("#fmSelectionSize").text(bytes > 0 ? FileThumb.formatBytes(bytes) : "");
+    updateMultiSelectDisplay();
+}
+
+/*
+    Check marks are a multi-select affordance, not a selection indicator: a plain
+    click just highlights the row. They appear once the user is actually picking
+    several things - ctrl/shift held on desktop, multi-select toggled on mobile,
+    or more than one item already selected.
+*/
+function updateMultiSelectDisplay(){
+    let multi = ctrlHold || shiftHold || $(".fileObject.selected").length > 1;
+    $("#folderView").toggleClass("fmMultiSelect", multi);
 }
 
 $("#folderView").on("click", function(){
@@ -350,6 +371,10 @@ function getFileObjectFromFID(fid){
 }
 
 function exitMultiSelectMode(){
+    if (stickyMultiSelect){
+        //Held open by the Multi-select menu toggle
+        return;
+    }
     if (ctrlHold){
         ctrlHold = false;
         updateCtrlDisplay();
@@ -378,6 +403,7 @@ function clearSelection(){
 }
 
 function updateCtrlDisplay(){
+    updateMultiSelectDisplay();
     if (isMobile){
         //Change color of the navibar based on selection mode
         if (ctrlHold){
@@ -447,3 +473,27 @@ function setGridZoom(value){
     }
     setPreference("file_explorer/gridZoom", gridZoom);
 }
+
+
+/*
+    Mobile multi-select.
+
+    A plain tap on a phone opens the item, so there is otherwise no way to select
+    anything - which also left every selection-based action in the overflow menu
+    permanently unavailable. Turning this on makes taps select instead: tapping an
+    unselected item selects it, tapping a selected one deselects it, and folders
+    are selected rather than opened. It stays on until switched off.
+*/
+function toggleMobileMultiSelect(){
+    stickyMultiSelect = !stickyMultiSelect;
+    ctrlHold = stickyMultiSelect;
+
+    if (!stickyMultiSelect){
+        $(".fileObject.selected").removeClass("selected");
+    }
+
+    updateCtrlDisplay();
+    updateSelectedObjectsCount();
+    $("#fmMultiSelectItem").toggleClass("checked", stickyMultiSelect);
+    closeFileOprMenu();
+}

+ 3 - 1
src/web/SystemAO/file_system/js/explorer/state.js

@@ -13,7 +13,8 @@ let currentTheme = "whiteTheme"; //Default theme
 let viewMode = "list"; //Viewmode, support {list, grid, detail}
 let sortMode = "default"; //Sortmode, support {default, reverse, smallToLarge, largeToSmall, mostRecent, leastRecent}
 let currentPath = "user:/"
-let gridZoom = 150;                  //Grid tile width in px, driven by the status bar slider
+let showOprBar = true;               //Classic file operation toolbar, persisted server side
+let gridZoom = 170;                  //Grid tile width in px (100-170), driven by the status bar slider
 let viewHistory = [];
 let forwardHistory = [];             //Paths popped by Back, replayed by Forward. Cleared on any other navigation. //View history
 let currentFilelist = []; //The current file list in the currentPath
@@ -40,6 +41,7 @@ let hotSearchOffsetIndex = 0;
 let propertiesView = false; //Enable viewing properties on the right sidebar
 
 //Keypress listeners
+let stickyMultiSelect = false;      //Mobile multi-select stays on until the user turns it off
 let ctrlHold = false;
 let shiftHold = false;
 let lastClickedElement = undefined;

+ 172 - 2
src/web/SystemAO/file_system/js/explorer/ui.js

@@ -84,8 +84,15 @@ function toggleSidebar(useAnimation=true){
 
 function initWindowSizes(animate=true){
     var h = $("#navibar").css("height");
-    var hint = $("#navibar").height();
-    var windowHeight = window.innerHeight - hint - 12;
+    /*
+        outerHeight, not height: .height() returns the content box and leaves out
+        the nav bar's 2px bottom border, which pushed everything below it 2px
+        past the window edge. The status bar sits below #mainWindow in normal
+        flow, so its height has to come out of the panes above it too.
+    */
+    var hint = $("#navibar").outerHeight();
+    var statusHeight = $("#fmStatusBar").is(":visible") ? $("#fmStatusBar").outerHeight() : 0;
+    var windowHeight = window.innerHeight - hint - statusHeight;
     if (sideBarShown){
         //Resize the sidebar 
         $("#directorySidebar").css("top",h);
@@ -208,6 +215,11 @@ function toggleFileOprMenu(event){
     if ($("#fmMoreMenu").hasClass("open")){
         closeFileOprMenu();
     }else{
+        //Only one nav menu open at a time
+        if (typeof closeSortMenu == "function"){
+            closeSortMenu();
+        }
+        updateOprMenuRelevance();
         $("#fmMoreMenu").addClass("open");
         $("#fmMoreBtn").addClass("active");
     }
@@ -231,3 +243,161 @@ $(document).on("click", function(event){
         closeFileOprMenu();
     }
 });
+
+
+/* ---------------------------------------------------------------------- */
+/*  Classic file operation toolbar                                         */
+/* ---------------------------------------------------------------------- */
+/*
+    The toolbar and the overflow menu expose the same actions, so only one of
+    them should carry them at a time: with the toolbar visible the duplicated
+    entries are hidden from the menu, leaving just the things the toolbar has no
+    room for (select all, theme, sorting, and this toggle).
+
+    The preference is stored server side under file_explorer/oprbar so it follows
+    the user between browsers. Mobile always hides the bar - that layout has its
+    own arrangement and the bar does not fit it.
+*/
+function applyOprBarVisibility(){
+    let visible = showOprBar && !isMobile;
+    $("#fileOprBar").toggle(visible);
+    $("#fmToolbarToggle").toggleClass("checked", showOprBar);
+    syncOprMenuDuplicates();
+    initWindowSizes(false);
+}
+
+/*
+    Hide an overflow menu entry only while the toolbar is actually offering that
+    same action on screen.
+
+    This is measured rather than assumed, because the toolbar collapses as the
+    window narrows: whole button groups drop out to avoid wrapping to a second
+    row. A static "the toolbar covers these" list would then leave those actions
+    unreachable from either place.
+*/
+function syncOprMenuDuplicates(){
+    let offered = {};
+    if ($("#fileOprBar").is(":visible")){
+        $("#fileOprBar [data-opr]").each(function(){
+            if ($(this).is(":visible")){
+                offered[$(this).attr("data-opr")] = true;
+            }
+        });
+    }
+
+    $("#fmMoreMenu [data-opr]").each(function(){
+        let dup = offered[$(this).attr("data-opr")] === true;
+        //An entry shows only if the toolbar is not already offering it AND it
+        //can actually do something with the current selection / clipboard
+        let unusable = $(this).hasClass("fmOprUnusable");
+        $(this).toggle(!dup && !unusable);
+    });
+
+    tidyMenuSeparators();
+}
+
+/*
+    Hide separators that no longer divide anything. As toolbar entries are hidden
+    from the menu, whole blocks between two separators can disappear and leave
+    stacked or dangling rules behind.
+
+    A separator earns its place only if a visible item sits both above and below
+    it, and only the first of a consecutive run is kept.
+*/
+function tidyMenuSeparators(){
+    let children = $("#fmMoreMenu").children().toArray();
+
+    //Reset so a previously hidden separator can come back
+    $("#fmMoreMenu .fsMenuSep").show();
+
+    /*
+        Do not use :visible here. This runs while the menu itself is closed, and
+        jQuery reports every descendant of a hidden element as invisible - which
+        made each separator look like a leading one and hid them all. The
+        element's own computed display is what matters.
+    */
+    let shown = function(el){
+        return window.getComputedStyle(el).display != "none";
+    };
+
+    let seenItemAbove = false;
+    let lastShownSep = null;
+    for (let i = 0; i < children.length; i++){
+        let el = children[i];
+        if ($(el).hasClass("fsMenuSep")){
+            if (!seenItemAbove || lastShownSep != null){
+                //Leading, or directly after another separator
+                $(el).hide();
+            }else{
+                lastShownSep = el;
+            }
+            continue;
+        }
+        if (shown(el)){
+            seenItemAbove = true;
+            lastShownSep = null;
+        }
+    }
+
+    //Anything still marked as the last separator has no visible item under it
+    if (lastShownSep != null){
+        $(lastShownSep).hide();
+    }
+}
+
+function toggleFileOprBar(){
+    showOprBar = !showOprBar;
+    applyOprBarVisibility();
+    setPreference("file_explorer/oprbar", showOprBar ? "true" : "false");
+    closeFileOprMenu();
+}
+
+
+//The toolbar collapses at breakpoints, so what it offers changes with the window
+$(window).on("resize", function(){
+    syncOprMenuDuplicates();
+});
+
+
+/*
+    Hide menu entries that cannot act right now: most operations need a
+    selection, and Paste needs something on the clipboard.
+
+    This applies to the overflow menu only. The large toolbar buttons keep their
+    old behaviour of staying put and doing nothing, because a toolbar that
+    reflows every time the selection changes is worse than one with a few
+    inert buttons.
+*/
+var OPR_NEEDS_SELECTION = ["open", "openwith", "copy", "cut", "rename", "delete",
+                           "download", "share", "fileinfo"];
+
+function updateOprMenuRelevance(){
+    let hasSelection = $(".fileObject.selected").length > 0;
+    let hasClipboard = (typeof clipboard != "undefined") && clipboard.length > 0;
+
+    $("#fmMoreMenu [data-opr]").each(function(){
+        let opr = $(this).attr("data-opr");
+        let usable = true;
+        if (OPR_NEEDS_SELECTION.indexOf(opr) >= 0){
+            usable = hasSelection;
+        }else if (opr == "paste"){
+            usable = hasClipboard;
+        }
+        $(this).toggleClass("fmOprUnusable", !usable);
+    });
+
+    //Re-run the toolbar de-duplication so hidden-by-relevance entries are
+    //accounted for, then tidy the separators around whatever is left
+    syncOprMenuDuplicates();
+}
+
+
+/*
+    Some operation labels carry a <br> so the wide toolbar button can render on
+    two lines ("New<br>Folder"). applocale injects the string as HTML, so the
+    same label wraps inside the overflow menu where a menu row must stay on one
+    line. Swap those breaks for a space once, after localisation has run.
+*/
+function flattenMenuLabels(){
+    $("#fmMoreMenu .fsMenuItem br, #fmSortMenu .fsMenuItem br").replaceWith(" ");
+}

+ 56 - 7
src/web/SystemAO/file_system/js/explorer/viewmode.js

@@ -41,7 +41,14 @@
         }
     }
 
-    function updateViewmodeButtons(){
+    //The tile size slider only applies to the thumbnail grid
+function updateZoomControlVisibility(){
+    $("#fmZoom").toggle(viewMode == "grid");
+    initWindowSizes(false);
+}
+
+function updateViewmodeButtons(){
+    updateZoomControlVisibility();
         $(".videmode").removeClass('disabled');
         $(".videmode").each(function(){
             if ($(this).attr("mode") == viewMode){
@@ -65,21 +72,61 @@
     }
 
 //Update sorting method for file listing
-function updateSortingMethods(){
-    var method = $("#sortingMethodSelector").val();
-    sortMode = method;
+/*
+    Apply a sort mode, persist it for this folder and re-list.
+
+    The nav row's sort menu replaced the old <select>, but the stored vocabulary
+    is unchanged (default / reverse / smallToLarge / largeToSmall / mostRecent /
+    leastRecent / fileTypeAsce / fileTypeDesc / smart) so an older client reading
+    the same preference still understands it.
+*/
+function setSortMode(mode){
+    sortMode = mode;
+    updateSortMenuState();
+    closeSortMenu();
 
-    //Save it to server side
     $.ajax({
         url: "../../system/file_system/sortMode",
         method: "POST",
         data: {opr: "set", folder: currentPath, mode: sortMode},
-        success: function(data){
+        success: function(){
             refreshList();
         }
     });
 }
 
+//Tick the active mode in the sort menu
+function updateSortMenuState(){
+    $("#fmSortMenu .fmSortOption").each(function(){
+        $(this).toggleClass("active", $(this).attr("data-sort") == sortMode);
+    });
+}
+
+function toggleSortMenu(event){
+    if (event != undefined){
+        event.stopPropagation();
+    }
+    if ($("#fmSortMenu").hasClass("open")){
+        closeSortMenu();
+    }else{
+        closeFileOprMenu();
+        updateSortMenuState();
+        $("#fmSortMenu").addClass("open");
+        $("#fmSortBtn").addClass("active");
+    }
+}
+
+function closeSortMenu(){
+    $("#fmSortMenu").removeClass("open");
+    $("#fmSortBtn").removeClass("active");
+}
+
+$(document).on("click", function(event){
+    if ($(event.target).closest("#fmSortMenu, #fmSortBtn").length == 0){
+        closeSortMenu();
+    }
+});
+
 //Toggle preview windows size from small to large mode,
 //set restoreDefault to true for force small interface
 function togglePreviewWindowSize(restoreDefault = false){
@@ -113,4 +160,6 @@ function togglePreviewWindowSize(restoreDefault = false){
 window.changeViewMode = changeViewMode;
 window.togglePreviewWindowSize = togglePreviewWindowSize;
 window.togglePropertiesView = togglePropertiesView;
-window.updateSortingMethods = updateSortingMethods;
+window.setSortMode = setSortMode;
+            window.toggleSortMenu = toggleSortMenu;
+            window.updateSortMenuState = updateSortMenuState;

+ 2 - 0
src/web/SystemAO/file_system/shared/fsicons.js

@@ -26,6 +26,8 @@
         up:         S + '<path d="M12 19V5M5 12l7-7 7 7"/></svg>',
         refresh:    S + '<path d="M21 12a9 9 0 1 1-3.2-6.9"/><path d="M21 4v5h-5"/></svg>',
         menu:       S + '<path d="M4 6h16M4 12h16M4 18h16"/></svg>',
+        //Descending bars with a direction arrow - the sort control
+        sort:       S + '<path d="M4 6h11M4 12h7M4 18h4"/><path d="M18 8v12M14.5 16.5L18 20l3.5-3.5"/></svg>',
         search:     S + '<circle cx="11" cy="11" r="7"/><path d="M20 20l-3.6-3.6"/></svg>',
         close:      S + '<path d="M18 6L6 18M6 6l12 12"/></svg>',
         more:       S + '<circle cx="5" cy="12" r="1.4"/><circle cx="12" cy="12" r="1.4"/><circle cx="19" cy="12" r="1.4"/></svg>',

+ 12 - 0
src/web/SystemAO/locale/file_explorer.json

@@ -6,6 +6,8 @@
             "name": "繁體中文(台灣)",
             "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
             "strings": {
+                "menu/sort/title": "排序方式",
+                "fileopr/Toolbar": "操作工具列",
                 "list/name": "名稱",
                 "list/date": "修改日期",
                 "list/type": "類型",
@@ -284,6 +286,8 @@
             "name": "繁體中文(香港)",
             "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
             "strings": {
+                "menu/sort/title": "排序方式",
+                "fileopr/Toolbar": "操作工具列",
                 "list/name": "名稱",
                 "list/date": "修改日期",
                 "list/type": "類型",
@@ -562,6 +566,8 @@
             "name": "简体中文",
             "fontFamily": "\"Microsoft YaHei\",\"SimHei\", \"PingFangSC-Medium\", \"STHeiti\"",
             "strings": {
+                "menu/sort/title": "排序方式",
+                "fileopr/Toolbar": "操作工具栏",
                 "list/name": "名称",
                 "list/date": "修改日期",
                 "list/type": "类型",
@@ -841,6 +847,8 @@
             "name": "English (US)",
             "fontFamily": "Arial, Helvetica, sans-serif",
             "strings": {
+                "menu/sort/title": "Sort items by",
+                "fileopr/Toolbar": "Operation Toolbar",
                 "list/name": "Name",
                 "list/date": "Date Modified",
                 "list/type": "Type",
@@ -1121,6 +1129,8 @@
             "name": "日本語",
             "fontFamily": "\"Meiryo UI\", \"Arial Unicode MS\", \"Hiragino Kaku Gothic Pro\"",
             "strings": {
+                "menu/sort/title": "並べ替え",
+                "fileopr/Toolbar": "操作ツールバー",
                 "list/name": "名前",
                 "list/date": "更新日時",
                 "list/type": "種類",
@@ -1402,6 +1412,8 @@
             "name": "한국어",
             "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
             "strings": {
+                "menu/sort/title": "정렬 기준",
+                "fileopr/Toolbar": "작업 도구 모음",
                 "list/name": "이름",
                 "list/date": "수정한 날짜",
                 "list/type": "유형",