terminalViewContainer is now an observable object

made the session list view more pretty
terminalviewcontaner has a static shared property which is an instance of itself
then it has sessions inside with a dict of uuid:terminalcontainer inside
hostsview is in the section "hosts"
This commit is contained in:
neon443
2025-06-29 16:25:49 +01:00
parent 331a921499
commit 8e69dfc165
9 changed files with 82 additions and 48 deletions

View File

@@ -341,8 +341,8 @@
A96C6B042E0C523E00F377FE /* Hosts */ = { A96C6B042E0C523E00F377FE /* Hosts */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A985545C2E055D4D009051BD /* ConnectionView.swift */,
A98554622E0587DF009051BD /* HostsView.swift */, A98554622E0587DF009051BD /* HostsView.swift */,
A985545C2E055D4D009051BD /* ConnectionView.swift */,
); );
path = Hosts; path = Hosts;
sourceTree = "<group>"; sourceTree = "<group>";

View File

@@ -133,7 +133,8 @@ class HostsManager: ObservableObject, @unchecked Sendable {
} }
} }
func makeLabel(forHost: Host) -> String { func makeLabel(forHost: Host?) -> String {
guard let forHost else { return "" }
if forHost.name.isEmpty && forHost.address.isEmpty { if forHost.name.isEmpty && forHost.address.isEmpty {
return forHost.id.uuidString return forHost.id.uuidString
} else if forHost.name.isEmpty { } else if forHost.name.isEmpty {

View File

@@ -15,6 +15,9 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
private var session: ssh_session? private var session: ssh_session?
private var channel: ssh_channel? private var channel: ssh_channel?
@MainActor var container: TerminalViewContainer {
TerminalViewContainer.shared
}
var sessionID: UUID? var sessionID: UUID?
var scrollback: [String] = [] var scrollback: [String] = []
@@ -140,7 +143,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
if let sessionID { if let sessionID {
Task { @MainActor in Task { @MainActor in
TerminalViewContainer.shared.removeValue(forKey: sessionID) container.sessions.removeValue(forKey: sessionID)
self.sessionID = nil self.sessionID = nil
} }
} }

View File

@@ -17,36 +17,38 @@ struct HostsView: View {
Text("Add your first Host!") Text("Add your first Host!")
} }
ForEach(hostsManager.hosts) { host in Section("Hosts") {
NavigationLink() { ForEach(hostsManager.hosts) { host in
ConnectionView( NavigationLink() {
handler: SSHHandler(host: host), ConnectionView(
hostsManager: hostsManager, handler: SSHHandler(host: host),
keyManager: keyManager hostsManager: hostsManager,
) keyManager: keyManager
} label: { )
SymbolPreview(symbol: host.symbol, label: host.label)
.frame(width: 40, height: 40)
Text(hostsManager.makeLabel(forHost: host))
}
.id(host)
.animation(.default, value: host)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
hostsManager.removeHost(host)
} label: { } label: {
Label("Delete", systemImage: "trash") SymbolPreview(symbol: host.symbol, label: host.label)
.frame(width: 40, height: 40)
Text(hostsManager.makeLabel(forHost: host))
} }
Button() { .id(host)
hostsManager.duplicateHost(host) .animation(.default, value: host)
} label: { .swipeActions(edge: .trailing) {
Label("Duplicate", systemImage: "square.filled.on.square") Button(role: .destructive) {
hostsManager.removeHost(host)
} label: {
Label("Delete", systemImage: "trash")
}
Button() {
hostsManager.duplicateHost(host)
} label: {
Label("Duplicate", systemImage: "square.filled.on.square")
}
} }
} }
.onMove(perform: {
hostsManager.moveHost(from: $0, to: $1)
})
} }
.onMove(perform: {
hostsManager.moveHost(from: $0, to: $1)
})
Section() { Section() {
NavigationLink { NavigationLink {

View File

@@ -9,20 +9,34 @@ import SwiftUI
struct SessionView: View { struct SessionView: View {
@ObservedObject var hostsManager: HostsManager @ObservedObject var hostsManager: HostsManager
@ObservedObject var container = TerminalViewContainer.shared
@State var key: UUID @State var key: UUID
@State var shellPresented: Bool = false @State var shellPresented: Bool = false
var host: Host {
container.sessions[key]?.handler.host ?? Host.blank
}
var body: some View { var body: some View {
Text(key.uuidString) HStack {
.onTapGesture { Image(systemName: "apple.terminal")
shellPresented.toggle() .resizable().scaledToFit()
} .frame(width: 40, height: 40)
.fullScreenCover(isPresented: $shellPresented) { .foregroundStyle(.terminalGreen)
ShellView( SymbolPreview(symbol: host.symbol, label: host.label)
handler: TerminalViewContainer.shared[key]!.handler, .frame(width: 40, height: 40)
hostsManager: hostsManager Text(hostsManager.makeLabel(forHost: host))
) }
} .onTapGesture {
shellPresented.toggle()
}
.fullScreenCover(isPresented: $shellPresented) {
ShellView(
handler: container.sessions[key]?.handler ?? SSHHandler(host: Host.blank),
hostsManager: hostsManager
)
}
} }
} }

View File

@@ -13,10 +13,15 @@ struct SessionsListView: View {
@ObservedObject var hostsManager: HostsManager @ObservedObject var hostsManager: HostsManager
@ObservedObject var keyManager: KeyManager @ObservedObject var keyManager: KeyManager
@ObservedObject var container = TerminalViewContainer.shared
var body: some View { var body: some View {
Section("Sessions") { if !container.sessions.isEmpty {
ForEach(TerminalViewContainer.shared.map {$0.key}, id: \.self) { key in Section("Sessions") {
SessionView(hostsManager: hostsManager, key: key) ForEach(container.sessionIDs, id: \.self) { key in
SessionView(hostsManager: hostsManager, key: key)
.id(container.sessions[key]!.handler.connected)
}
} }
} }
} }

View File

@@ -11,6 +11,8 @@ struct ShellView: View {
@ObservedObject var handler: SSHHandler @ObservedObject var handler: SSHHandler
@ObservedObject var hostsManager: HostsManager @ObservedObject var hostsManager: HostsManager
@ObservedObject var container = TerminalViewContainer.shared
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var body: some View { var body: some View {
@@ -19,7 +21,7 @@ struct ShellView: View {
TerminalController(handler: handler, hostsManager: hostsManager) TerminalController(handler: handler, hostsManager: hostsManager)
.onAppear { .onAppear {
if let sessionID = handler.sessionID { if let sessionID = handler.sessionID {
TerminalViewContainer.shared[sessionID]?.terminalView.restoreScrollback() container.sessions[sessionID]?.terminalView.restoreScrollback()
} }
} }

View File

@@ -14,9 +14,11 @@ struct TerminalController: UIViewRepresentable {
@ObservedObject var handler: SSHHandler @ObservedObject var handler: SSHHandler
@ObservedObject var hostsManager: HostsManager @ObservedObject var hostsManager: HostsManager
@ObservedObject var container = TerminalViewContainer.shared
func makeUIView(context: Context) -> TerminalView { func makeUIView(context: Context) -> TerminalView {
if let sessionID = handler.sessionID { if let sessionID = handler.sessionID {
if let existing = TerminalViewContainer.shared[sessionID] { if let existing = container.sessions[sessionID] {
return existing.terminalView return existing.terminalView
} }
} }
@@ -30,7 +32,7 @@ struct TerminalController: UIViewRepresentable {
tv.autoresizingMask = [.flexibleWidth, .flexibleHeight] tv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
if let sessionID = handler.sessionID { if let sessionID = handler.sessionID {
TerminalViewContainer.shared[sessionID] = TerminalContainer( container.sessions[sessionID] = TerminalContainer(
handler: handler, handler: handler,
terminalView: tv terminalView: tv
) )

View File

@@ -7,10 +7,15 @@
import Foundation import Foundation
public final class TerminalViewContainer { @MainActor
@MainActor static var shared: [ public final class TerminalViewContainer: ObservableObject {
UUID: TerminalContainer static let shared = TerminalViewContainer()
] = [:]
@Published var sessions: [UUID: TerminalContainer] = [:]
var sessionIDs: [UUID] {
return sessions.map({ $0.key })
}
} }
struct TerminalContainer { struct TerminalContainer {