menu icons

Signed-off-by: FTCHD <144691102+FTCHD@users.noreply.github.com>
This commit is contained in:
FTCHD
2025-09-21 17:20:12 +02:00
parent ebb93c3d60
commit b3d905116c
59 changed files with 135 additions and 112 deletions
+134 -91
View File
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/browser' import * as Sentry from '@sentry/browser'
import { Menu, MenuItem, PredefinedMenuItem, Submenu } from '@tauri-apps/api/menu' import { Menu, MenuItem, PredefinedMenuItem, Submenu } from '@tauri-apps/api/menu'
import { sep } from '@tauri-apps/api/path' import { resolveResource, sep } from '@tauri-apps/api/path'
import { getCurrentWindow } from '@tauri-apps/api/window' import { getCurrentWindow } from '@tauri-apps/api/window'
import { ask, message, open } from '@tauri-apps/plugin-dialog' import { ask, message, open } from '@tauri-apps/plugin-dialog'
import { exists, mkdir, remove } from '@tauri-apps/plugin-fs' import { exists, mkdir, remove } from '@tauri-apps/plugin-fs'
@@ -11,12 +11,14 @@ import notify from './notify'
import { import {
cleanupRemote, cleanupRemote,
deleteRemote, deleteRemote,
getRemote,
listMounts, listMounts,
listServes, listServes,
mountRemote, mountRemote,
stopServe, stopServe,
unmountRemote, unmountRemote,
} from './rclone/api' } from './rclone/api'
import { compareVersions, getRcloneVersion } from './rclone/common'
import { dialogGetMountPlugin, needsMountPlugin } from './rclone/mount' import { dialogGetMountPlugin, needsMountPlugin } from './rclone/mount'
import { usePersistedStore, useStore } from './store' import { usePersistedStore, useStore } from './store'
import { showDefaultTray, showLoadingTray } from './tray' import { showDefaultTray, showLoadingTray } from './tray'
@@ -33,15 +35,22 @@ async function parseRemotes(remotes: string[]) {
const currentServes = await listServes() const currentServes = await listServes()
console.log('[parseRemotes] currentServes', currentServes) console.log('[parseRemotes] currentServes', currentServes)
const parsedRemotes: Record<string, (MenuItem | Submenu | PredefinedMenuItem)[]> = {} const parsedRemotes: Record<
string,
{ icon: string; items: (MenuItem | Submenu | PredefinedMenuItem)[] }
> = {}
for (const remote of remotes) { for (const remote of remotes) {
console.log('[parseRemotes] remote', remote) console.log('[parseRemotes] remote', remote)
const remoteConfig = persistedStoreState.remoteConfigList?.[remote] const remoteConfig = persistedStoreState.remoteConfigList?.[remote]
if (remoteConfig?.disabledActions?.includes('tray')) { if (remoteConfig?.disabledActions?.includes('tray')) {
continue continue
} }
const remoteInfo = await getRemote(remote).catch(null)
console.log('[parseRemotes] remoteInfo', remoteInfo)
const submenuItems: (MenuItem | Submenu | PredefinedMenuItem)[] = [] const submenuItems: (MenuItem | Submenu | PredefinedMenuItem)[] = []
if (!remoteConfig?.disabledActions?.includes('tray-mount')) { if (!remoteConfig?.disabledActions?.includes('tray-mount')) {
@@ -206,37 +215,42 @@ async function parseRemotes(remotes: string[]) {
} }
if (!remoteConfig?.disabledActions?.includes('tray-cleanup')) { if (!remoteConfig?.disabledActions?.includes('tray-cleanup')) {
const cleanupMenuItem = await MenuItem.new({ if (
id: `cleanup-${remote}`, remoteInfo?.provider &&
text: 'Cleanup', ['s3', 'b2', 'box', 'drive', 'onedrive'].includes(remoteInfo.provider)
action: async () => { ) {
try { const cleanupMenuItem = await MenuItem.new({
const confirmed = await ask(`Start cleanup on ${remote}?`, { id: `cleanup-${remote}`,
title: `Cleaning up ${remote}`, text: 'Cleanup',
kind: 'warning', action: async () => {
okLabel: 'Start', try {
cancelLabel: 'Cancel', const confirmed = await ask(`Start cleanup on ${remote}?`, {
}) title: `Cleaning up ${remote}`,
kind: 'warning',
okLabel: 'Start',
cancelLabel: 'Cancel',
})
if (!confirmed) { if (!confirmed) {
return return
}
await cleanupRemote(remote)
await message(`Cleanup started for ${remote}`, {
title: 'Cleanup Started',
okLabel: 'OK',
})
} catch (error) {
await message(`Failed to start cleanup job, ${error}`, {
title: 'Cleanup Error',
kind: 'error',
okLabel: 'OK',
})
} }
},
await cleanupRemote(remote) })
await message(`Cleanup started for ${remote}`, { submenuItems.push(cleanupMenuItem)
title: 'Cleanup Started', }
okLabel: 'OK',
})
} catch (error) {
await message(`Failed to start cleanup job, ${error}`, {
title: 'Cleanup Error',
kind: 'error',
okLabel: 'OK',
})
}
},
})
submenuItems.push(cleanupMenuItem)
} }
if (!remoteConfig?.disabledActions?.includes('tray-remove')) { if (!remoteConfig?.disabledActions?.includes('tray-remove')) {
@@ -358,7 +372,12 @@ async function parseRemotes(remotes: string[]) {
submenuItems.push(stopServeMenuItem) submenuItems.push(stopServeMenuItem)
} }
parsedRemotes[remote] = submenuItems parsedRemotes[remote] = {
icon: remoteInfo?.provider
? await resolveResource(`icons/small/providers/${remoteInfo.provider}.png`)
: await resolveResource(`icons/small/backends/${remoteInfo?.type}.png`),
items: submenuItems,
}
} }
return parsedRemotes return parsedRemotes
@@ -383,13 +402,14 @@ export async function buildMenu() {
}) })
menuItems.push(noRemotesMenuItem) menuItems.push(noRemotesMenuItem)
} else if (remotes.length > 5) { } else if (remotes.length > 5) {
const items = await parseRemotes(remotes) const parsedRemotes = await parseRemotes(remotes)
const submenuItems: (MenuItem | Submenu | PredefinedMenuItem)[] = [] const submenuItems: (MenuItem | Submenu | PredefinedMenuItem)[] = []
for (const remote in items) { for (const r in parsedRemotes) {
const sub = await Submenu.new({ const sub = await Submenu.new({
items: items[remote], items: parsedRemotes[r].items,
text: remote, icon: parsedRemotes[r].icon,
text: r,
}) })
submenuItems.push(sub) submenuItems.push(sub)
} }
@@ -401,12 +421,13 @@ export async function buildMenu() {
menuItems.push(sub) menuItems.push(sub)
} else { } else {
const items = await parseRemotes(remotes) const parsedRemotes = await parseRemotes(remotes)
for (const remote in items) { for (const r in parsedRemotes) {
const sub = await Submenu.new({ const sub = await Submenu.new({
items: items[remote], items: parsedRemotes[r].items,
text: remote, icon: parsedRemotes[r].icon,
text: r,
}) })
menuItems.push(sub) menuItems.push(sub)
} }
@@ -446,20 +467,6 @@ export async function buildMenu() {
menuItems.push(copyMenuItem) menuItems.push(copyMenuItem)
} }
if (!persistedStoreState.disabledActions?.includes('tray-move')) {
const moveMenuItem = await MenuItem.new({
id: 'move',
text: 'Move',
action: async () => {
await openWindow({
name: 'Move',
url: '/move',
})
},
})
menuItems.push(moveMenuItem)
}
if (!persistedStoreState.disabledActions?.includes('tray-sync')) { if (!persistedStoreState.disabledActions?.includes('tray-sync')) {
const syncMenuItem = await MenuItem.new({ const syncMenuItem = await MenuItem.new({
id: 'sync', id: 'sync',
@@ -474,46 +481,82 @@ export async function buildMenu() {
menuItems.push(syncMenuItem) menuItems.push(syncMenuItem)
} }
if (!persistedStoreState.disabledActions?.includes('tray-serve')) { const allowsAdditional =
const serveToMenuItem = await MenuItem.new({ !persistedStoreState.disabledActions?.includes('tray-move') ||
id: 'serve', !persistedStoreState.disabledActions?.includes('tray-serve') ||
text: 'Serve', !persistedStoreState.disabledActions?.includes('tray-purge') ||
action: async () => { !persistedStoreState.disabledActions?.includes('tray-delete')
await openWindow({
name: 'Serve',
url: '/serve',
})
},
})
menuItems.push(serveToMenuItem)
}
if (!persistedStoreState.disabledActions?.includes('tray-purge')) { if (allowsAdditional) {
const purgeMenuItem = await MenuItem.new({ const commandsSubmenuItems: (MenuItem | Submenu | PredefinedMenuItem)[] = []
id: 'purge',
text: 'Purge',
action: async () => {
await openWindow({
name: 'Purge',
url: '/purge',
})
},
})
menuItems.push(purgeMenuItem)
}
if (!persistedStoreState.disabledActions?.includes('tray-delete')) { if (!persistedStoreState.disabledActions?.includes('tray-move')) {
const deleteMenuItem = await MenuItem.new({ const moveMenuItem = await MenuItem.new({
id: 'delete', id: 'move',
text: 'Delete', text: 'Move',
action: async () => { action: async () => {
await openWindow({ await openWindow({
name: 'Delete', name: 'Move',
url: '/delete', url: '/move',
}) })
}, },
})
commandsSubmenuItems.push(moveMenuItem)
}
const rcloneVersion = await getRcloneVersion()
if (
rcloneVersion?.yours &&
compareVersions(rcloneVersion.yours, '1.70.0') === 1 &&
!persistedStoreState.disabledActions?.includes('tray-serve')
) {
const serveToMenuItem = await MenuItem.new({
id: 'serve',
text: 'Serve',
action: async () => {
await openWindow({
name: 'Serve',
url: '/serve',
})
},
})
commandsSubmenuItems.push(serveToMenuItem)
}
if (!persistedStoreState.disabledActions?.includes('tray-purge')) {
const purgeMenuItem = await MenuItem.new({
id: 'purge',
text: 'Purge',
action: async () => {
await openWindow({
name: 'Purge',
url: '/purge',
})
},
})
commandsSubmenuItems.push(purgeMenuItem)
}
if (!persistedStoreState.disabledActions?.includes('tray-delete')) {
const deleteMenuItem = await MenuItem.new({
id: 'delete',
text: 'Delete',
action: async () => {
await openWindow({
name: 'Delete',
url: '/delete',
})
},
})
commandsSubmenuItems.push(deleteMenuItem)
}
const commandsSubmenu = await Submenu.new({
items: commandsSubmenuItems,
text: 'Commands',
}) })
menuItems.push(deleteMenuItem) menuItems.push(commandsSubmenu)
} }
await PredefinedMenuItem.new({ await PredefinedMenuItem.new({
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+1 -21
View File
@@ -44,27 +44,7 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"resources": [ "resources": ["icons/favicon/", "icons/small/"],
"icons/favicon/icon.png",
"icons/favicon/frame_00_delay-0.1s.png",
"icons/favicon/frame_01_delay-0.1s.png",
"icons/favicon/frame_02_delay-0.1s.png",
"icons/favicon/frame_03_delay-0.1s.png",
"icons/favicon/frame_04_delay-0.1s.png",
"icons/favicon/frame_05_delay-0.1s.png",
"icons/favicon/frame_06_delay-0.1s.png",
"icons/favicon/frame_07_delay-0.1s.png",
"icons/favicon/frame_08_delay-0.1s.png",
"icons/favicon/frame_09_delay-0.1s.png",
"icons/favicon/frame_10_delay-0.1s.png",
"icons/favicon/frame_11_delay-0.1s.png",
"icons/favicon/frame_12_delay-0.1s.png",
"icons/favicon/frame_13_delay-0.1s.png",
"icons/favicon/frame_14_delay-0.1s.png",
"icons/favicon/frame_15_delay-0.1s.png",
"icons/favicon/frame_16_delay-0.1s.png",
"icons/favicon/frame_17_delay-0.1s.png"
],
"shortDescription": "Cross-platform GUI for rclone & S3", "shortDescription": "Cross-platform GUI for rclone & S3",
"longDescription": "Cross-platform GUI for rclone & S3", "longDescription": "Cross-platform GUI for rclone & S3",
"license": "Apache-2.0", "license": "Apache-2.0",