113 lines
4.3 KiB
Swift
113 lines
4.3 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
import TermindPresentation
|
|
|
|
/// A menu snapshot, built only in response to a context click, never during
|
|
/// SwiftUI row rendering. Actions retain the caller's corresponding targets.
|
|
@MainActor
|
|
struct SFTPFileContextMenu {
|
|
enum Action: Equatable {
|
|
case open
|
|
case openWith(URL)
|
|
case download, copy, copyNames, copyPaths, paste
|
|
case rename, editPermissions, delete, newFolder, upload, refresh
|
|
}
|
|
|
|
let file: RemoteFile
|
|
let targets: [RemoteFile]
|
|
let connected: Bool
|
|
let allowsRemoteMutations: Bool
|
|
let canPaste: Bool
|
|
let isLoading: Bool
|
|
let locale: Locale
|
|
|
|
func entries(
|
|
applications: @MainActor (String) -> [SFTPFileApplication] = SFTPApplicationOpener.applications(for:),
|
|
perform: @escaping (Action) -> Void
|
|
) -> [NativeMenuEntry] {
|
|
let single = targets.count == 1
|
|
var result: [NativeMenuEntry] = []
|
|
if single {
|
|
result.append(entry(AppStrings.Common.open, symbol: MenuSymbol.open) { perform(.open) })
|
|
if !file.isDirectory {
|
|
var openWith = SFTPOpenWithMenu(filename: file.name, locale: locale) {
|
|
perform(.openWith($0))
|
|
}.entry(applications: applications(file.name))
|
|
openWith.isEnabled = connected
|
|
result.append(openWith)
|
|
}
|
|
}
|
|
result.append(
|
|
entry(
|
|
single ? AppStrings.SFTP.download : AppStrings.SFTP.downloadItems(targets.count),
|
|
symbol: MenuSymbol.download
|
|
) {
|
|
perform(.download)
|
|
})
|
|
result.append(.separator)
|
|
result.append(
|
|
entry(single ? AppStrings.Common.copy : AppStrings.SFTP.copyItems(targets.count), symbol: MenuSymbol.copy) {
|
|
perform(.copy)
|
|
})
|
|
result.append(
|
|
NativeMenuEntry(
|
|
title: localized(AppStrings.SFTP.copyAs), symbol: MenuSymbol.copy,
|
|
children: [
|
|
entry(single ? AppStrings.Common.name : AppStrings.SFTP.names) { perform(.copyNames) },
|
|
entry(single ? AppStrings.SFTP.path : AppStrings.SFTP.paths) { perform(.copyPaths) },
|
|
]))
|
|
result.append(
|
|
entry(AppStrings.Common.paste, symbol: MenuSymbol.paste, enabled: connected && canPaste) {
|
|
perform(.paste)
|
|
})
|
|
if single {
|
|
result.append(
|
|
entry(AppStrings.Common.rename, symbol: MenuSymbol.rename, enabled: allowsRemoteMutations) {
|
|
perform(.rename)
|
|
})
|
|
if file.kind == .file || file.kind == .directory {
|
|
result.append(
|
|
entry(
|
|
AppStrings.SFTP.editPermissions, symbol: MenuSymbol.editPermissions,
|
|
enabled: allowsRemoteMutations
|
|
) {
|
|
perform(.editPermissions)
|
|
})
|
|
}
|
|
}
|
|
result.append(
|
|
entry(
|
|
single ? AppStrings.Common.delete : AppStrings.SFTP.deleteItems(targets.count),
|
|
symbol: MenuSymbol.delete, enabled: allowsRemoteMutations
|
|
) { perform(.delete) })
|
|
result.append(.separator)
|
|
result.append(
|
|
entry(AppStrings.SFTP.newFolder, symbol: MenuSymbol.newFolder, enabled: connected) { perform(.newFolder) })
|
|
result.append(
|
|
entry(AppStrings.SFTP.upload, symbol: MenuSymbol.upload, enabled: connected) { perform(.upload) })
|
|
result.append(
|
|
entry(AppStrings.Common.refresh, symbol: MenuSymbol.refresh, enabled: connected && !isLoading) {
|
|
perform(.refresh)
|
|
})
|
|
return result
|
|
}
|
|
|
|
private func localized(_ title: LocalizedStringResource) -> String {
|
|
AppLocalization.resolve(title, locale: locale)
|
|
}
|
|
|
|
private func entry(
|
|
_ title: LocalizedStringResource, symbol: String? = nil,
|
|
enabled: Bool = true,
|
|
action: @escaping () -> Void
|
|
) -> NativeMenuEntry {
|
|
NativeMenuEntry(
|
|
title: localized(title), symbol: symbol,
|
|
isEnabled: enabled, action: action)
|
|
}
|
|
}
|