mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
added a thingy to select an ansi color (accent color) in thememanger
improved ui for tab selector, uses theme colours and close button is in the tab made sshhandler optional for shelltabview implemented the tabbed terminal to sessionlistview in home added selectedAnsi to select an accent color from the 16 ansi colors remove toolbar and navbar in shellview, fix the wierd input issue
This commit is contained in:
@@ -15,6 +15,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
@Published var hosts: [Host] = []
|
@Published var hosts: [Host] = []
|
||||||
@Published var themes: [Theme] = []
|
@Published var themes: [Theme] = []
|
||||||
@Published var selectedTheme: Theme = Theme.defaultTheme
|
@Published var selectedTheme: Theme = Theme.defaultTheme
|
||||||
|
@Published var selectedAnsi: Int = 1
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
loadHosts()
|
loadHosts()
|
||||||
@@ -33,11 +34,25 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
self.themes.append(synthedTheme)
|
self.themes.append(synthedTheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
guard let dataSelTheme = userDefaults.data(forKey: "selectedTheme") else { return }
|
guard let dataSelTheme = userDefaults.data(forKey: "selectedTheme") else { return }
|
||||||
guard let decodedSelTheme = Theme.decodeTheme(data: dataSelTheme) else { return }
|
guard let decodedSelTheme = Theme.decodeTheme(data: dataSelTheme) else { return }
|
||||||
//name doesnt matter
|
//name doesnt matter
|
||||||
self.selectedTheme = decodedSelTheme
|
self.selectedTheme = decodedSelTheme
|
||||||
|
|
||||||
|
selectedAnsi = Int(userDefaults.longLong(forKey: "selectedAnsi"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveThemes() {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
// map the theme to themecodable
|
||||||
|
guard let encodedThemes = try? encoder.encode(themes.map({$0.themeCodable})) else { return }
|
||||||
|
userDefaults.set(encodedThemes, forKey: "themes")
|
||||||
|
|
||||||
|
guard let encodedSelectedTheme = try? encoder.encode(selectedTheme.themeCodable) else { return }
|
||||||
|
userDefaults.set(encodedSelectedTheme, forKey: "selectedTheme")
|
||||||
|
|
||||||
|
userDefaults.set(Int64(selectedAnsi), forKey: "selectedAnsi")
|
||||||
|
userDefaults.synchronize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadTheme(fromUrl: URL?) {
|
func downloadTheme(fromUrl: URL?) {
|
||||||
@@ -87,17 +102,6 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
saveThemes()
|
saveThemes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveThemes() {
|
|
||||||
let encoder = JSONEncoder()
|
|
||||||
// map the theme to themecodable
|
|
||||||
guard let encodedThemes = try? encoder.encode(themes.map({$0.themeCodable})) else { return }
|
|
||||||
userDefaults.set(encodedThemes, forKey: "themes")
|
|
||||||
|
|
||||||
guard let encodedSelectedTheme = try? encoder.encode(selectedTheme.themeCodable) else { return }
|
|
||||||
userDefaults.set(encodedSelectedTheme, forKey: "selectedTheme")
|
|
||||||
userDefaults.synchronize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHostIndexMatching(_ hostSearchingFor: Host) -> Int? {
|
func getHostIndexMatching(_ hostSearchingFor: Host) -> Int? {
|
||||||
if let index = hosts.firstIndex(where: { $0.id == hostSearchingFor.id }) {
|
if let index = hosts.firstIndex(where: { $0.id == hostSearchingFor.id }) {
|
||||||
return index
|
return index
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct ShhShellApp: App {
|
|||||||
keyManager: keyManager
|
keyManager: keyManager
|
||||||
)
|
)
|
||||||
.colorScheme(.dark)
|
.colorScheme(.dark)
|
||||||
|
.tint(hostsManager.selectedTheme.ansi[hostsManager.selectedAnsi].suiColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ struct SessionView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $shellPresented) {
|
.fullScreenCover(isPresented: $shellPresented) {
|
||||||
ShellView(
|
ShellTabView(handler: nil, hostsManager: hostsManager, selectedID: key)
|
||||||
handler: container.sessions[key]?.handler ?? SSHHandler(host: Host.blank, keyManager: keyManager),
|
|
||||||
hostsManager: hostsManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ShellTabView: View {
|
struct ShellTabView: View {
|
||||||
@ObservedObject var handler: SSHHandler
|
@State var handler: SSHHandler?
|
||||||
@ObservedObject var hostsManager: HostsManager
|
@ObservedObject var hostsManager: HostsManager
|
||||||
|
|
||||||
@ObservedObject var container = TerminalViewContainer.shared
|
@ObservedObject var container = TerminalViewContainer.shared
|
||||||
@@ -23,13 +23,38 @@ struct ShellTabView: View {
|
|||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
ForEach(container.sessionIDs, id: \.self) { id in
|
ForEach(container.sessionIDs, id: \.self) { id in
|
||||||
|
let selected = selectedID == id
|
||||||
|
let foreground = hostsManager.selectedTheme.foreground.suiColor
|
||||||
|
let ansi7 = hostsManager.selectedTheme.ansi[6].suiColor.opacity(0.7)
|
||||||
|
let background = hostsManager.selectedTheme.background.suiColor
|
||||||
ZStack {
|
ZStack {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(selectedID == id ? .orange : .gray)
|
.fill(selected ? ansi7 : background)
|
||||||
.opacity(0.5)
|
HStack {
|
||||||
Text(container.sessions[id]!.handler.host.description)
|
if selected {
|
||||||
.frame(width: oneTabWidth)
|
Button() {
|
||||||
|
container.sessions[id]?.handler.disconnect()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.app.fill")
|
||||||
|
.resizable().scaledToFit()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Text(container.sessions[id]!.handler.title)
|
||||||
|
.monospaced()
|
||||||
|
.foregroundStyle(foreground)
|
||||||
|
.bold(selected)
|
||||||
|
Text(container.sessions[id]!.handler.host.description)
|
||||||
|
.foregroundStyle(foreground.opacity(0.7))
|
||||||
|
.monospaced()
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.frame(width: oneTabWidth)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
withAnimation { selectedID = id }
|
withAnimation { selectedID = id }
|
||||||
@@ -40,7 +65,11 @@ struct ShellTabView: View {
|
|||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if selectedID == nil {
|
if selectedID == nil {
|
||||||
selectedID = handler.sessionID
|
if let handler {
|
||||||
|
selectedID = handler.sessionID
|
||||||
|
} else {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +91,11 @@ struct ShellTabView: View {
|
|||||||
.id(selectedID)
|
.id(selectedID)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
} else {
|
} else {
|
||||||
ShellView(handler: handler, hostsManager: hostsManager)
|
if let handler {
|
||||||
|
ShellView(handler: handler, hostsManager: hostsManager)
|
||||||
|
} else {
|
||||||
|
Text("No SSH Handler")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,24 +37,6 @@ struct ShellView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
handler.applySelectedTheme()
|
handler.applySelectedTheme()
|
||||||
}
|
}
|
||||||
.navigationTitle(handler.title)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
|
||||||
Button() {
|
|
||||||
handler.disconnect()
|
|
||||||
} label: {
|
|
||||||
Label("Disconnect", systemImage: "xmark.app.fill")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
|
||||||
Button() {
|
|
||||||
dismiss()
|
|
||||||
} label: {
|
|
||||||
Label("Close", systemImage: "arrow.down.right.and.arrow.up.left")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,74 +33,101 @@ struct ThemeManagerView: View {
|
|||||||
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
let columns: Int = max(1, Int((geo.size.width - 2*spacing) / (minColWidth + spacing)))
|
VStack {
|
||||||
let layout = Array(repeating: grid, count: columns)
|
VStack(spacing: 0) {
|
||||||
ScrollView {
|
HStack(spacing: 0) {
|
||||||
if hostsManager.themes.isEmpty {
|
ForEach(0..<8, id: \.self) { index in
|
||||||
VStack(alignment: .leading) {
|
Rectangle()
|
||||||
Image(systemName: "paintpalette")
|
.fill(hostsManager.selectedTheme.ansi[index].suiColor)
|
||||||
.resizable().scaledToFit()
|
.onTapGesture {
|
||||||
.symbolRenderingMode(.multicolor)
|
hostsManager.selectedAnsi = index
|
||||||
.frame(width: 50)
|
hostsManager.saveThemes()
|
||||||
Text("No themes (yet)")
|
}
|
||||||
.font(.title)
|
}
|
||||||
.padding(.vertical, 10)
|
}
|
||||||
.bold()
|
HStack(spacing: 0) {
|
||||||
Text("Tap the Safari icon at the top right to find themes!")
|
ForEach(8..<16, id: \.self) { index in
|
||||||
Text("Once you find one that you like, copy it's link and enter it here using the link button.")
|
Rectangle()
|
||||||
|
.fill(hostsManager.selectedTheme.ansi[index].suiColor)
|
||||||
|
.onTapGesture {
|
||||||
|
hostsManager.selectedAnsi = index
|
||||||
|
hostsManager.saveThemes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 15))
|
||||||
|
.frame(maxWidth: geo.size.width, maxHeight: geo.size.height/2)
|
||||||
|
|
||||||
|
let columns: Int = max(1, Int((geo.size.width - 2*spacing) / (minColWidth + spacing)))
|
||||||
|
let layout = Array(repeating: grid, count: columns)
|
||||||
|
ScrollView {
|
||||||
|
if hostsManager.themes.isEmpty {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Image(systemName: "paintpalette")
|
||||||
|
.resizable().scaledToFit()
|
||||||
|
.symbolRenderingMode(.multicolor)
|
||||||
|
.frame(width: 50)
|
||||||
|
Text("No themes (yet)")
|
||||||
|
.font(.title)
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.bold()
|
||||||
|
Text("Tap the Safari icon at the top right to find themes!")
|
||||||
|
Text("Once you find one that you like, copy it's link and enter it here using the link button.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
||||||
|
ForEach(hostsManager.themes) { theme in
|
||||||
|
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.animation(.default, value: hostsManager.themes)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Built-in Themes")
|
||||||
|
.padding(.top)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.font(.headline)
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
||||||
ForEach(hostsManager.themes) { theme in
|
ForEach(Theme.builtinThemes) { theme in
|
||||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true)
|
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.animation(.default, value: hostsManager.themes)
|
.animation(.default, value: hostsManager.themes)
|
||||||
}
|
}
|
||||||
|
.navigationTitle("Themes")
|
||||||
HStack {
|
.alert("Enter URL", isPresented: $showAlert) {
|
||||||
Text("Built-in Themes")
|
TextField("", text: $importURL, prompt: Text("URL"))
|
||||||
.padding(.top)
|
Button("Cancel") {}
|
||||||
.padding(.horizontal)
|
|
||||||
.font(.headline)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
LazyVGrid(columns: layout, alignment: .center, spacing: 8) {
|
|
||||||
ForEach(Theme.builtinThemes) { theme in
|
|
||||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
.animation(.default, value: hostsManager.themes)
|
|
||||||
}
|
|
||||||
.navigationTitle("Themes")
|
|
||||||
.alert("Enter URL", isPresented: $showAlert) {
|
|
||||||
TextField("", text: $importURL, prompt: Text("URL"))
|
|
||||||
Button("Cancel") {}
|
|
||||||
Button() {
|
|
||||||
hostsManager.downloadTheme(fromUrl: URL(string: importURL))
|
|
||||||
importURL = ""
|
|
||||||
} label: {
|
|
||||||
Label("Import", systemImage: "square.and.arrow.down")
|
|
||||||
.bold()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem() {
|
|
||||||
Button() {
|
Button() {
|
||||||
UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!)
|
hostsManager.downloadTheme(fromUrl: URL(string: importURL))
|
||||||
|
importURL = ""
|
||||||
} label: {
|
} label: {
|
||||||
Label("Open themes site", systemImage: "safari")
|
Label("Import", systemImage: "square.and.arrow.down")
|
||||||
|
.bold()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolbarItem() {
|
.toolbar {
|
||||||
Button() {
|
ToolbarItem() {
|
||||||
showAlert.toggle()
|
Button() {
|
||||||
} label: {
|
UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!)
|
||||||
Label("From URL", systemImage: "link")
|
} label: {
|
||||||
|
Label("Open themes site", systemImage: "safari")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem() {
|
||||||
|
Button() {
|
||||||
|
showAlert.toggle()
|
||||||
|
} label: {
|
||||||
|
Label("From URL", systemImage: "link")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user