Files
Termind/TFM/Features/SFTP/SFTPFileContextMenu.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)
}
}