mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 05:19:13 +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 themes: [Theme] = []
|
||||
@Published var selectedTheme: Theme = Theme.defaultTheme
|
||||
@Published var selectedAnsi: Int = 1
|
||||
|
||||
init() {
|
||||
loadHosts()
|
||||
@@ -33,11 +34,25 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
self.themes.append(synthedTheme)
|
||||
}
|
||||
|
||||
|
||||
guard let dataSelTheme = userDefaults.data(forKey: "selectedTheme") else { return }
|
||||
guard let decodedSelTheme = Theme.decodeTheme(data: dataSelTheme) else { return }
|
||||
//name doesnt matter
|
||||
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?) {
|
||||
@@ -87,17 +102,6 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
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? {
|
||||
if let index = hosts.firstIndex(where: { $0.id == hostSearchingFor.id }) {
|
||||
return index
|
||||
|
||||
@@ -28,6 +28,7 @@ struct ShhShellApp: App {
|
||||
keyManager: keyManager
|
||||
)
|
||||
.colorScheme(.dark)
|
||||
.tint(hostsManager.selectedTheme.ansi[hostsManager.selectedAnsi].suiColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,7 @@ struct SessionView: View {
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $shellPresented) {
|
||||
ShellView(
|
||||
handler: container.sessions[key]?.handler ?? SSHHandler(host: Host.blank, keyManager: keyManager),
|
||||
hostsManager: hostsManager
|
||||
)
|
||||
ShellTabView(handler: nil, hostsManager: hostsManager, selectedID: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ShellTabView: View {
|
||||
@ObservedObject var handler: SSHHandler
|
||||
@State var handler: SSHHandler?
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
|
||||
@ObservedObject var container = TerminalViewContainer.shared
|
||||
@@ -23,13 +23,38 @@ struct ShellTabView: View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 0) {
|
||||
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 {
|
||||
Rectangle()
|
||||
.fill(selectedID == id ? .orange : .gray)
|
||||
.opacity(0.5)
|
||||
Text(container.sessions[id]!.handler.host.description)
|
||||
.frame(width: oneTabWidth)
|
||||
.fill(selected ? ansi7 : background)
|
||||
HStack {
|
||||
if selected {
|
||||
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)
|
||||
.onTapGesture {
|
||||
withAnimation { selectedID = id }
|
||||
@@ -40,7 +65,11 @@ struct ShellTabView: View {
|
||||
.frame(height: 30)
|
||||
.onAppear {
|
||||
if selectedID == nil {
|
||||
selectedID = handler.sessionID
|
||||
if let handler {
|
||||
selectedID = handler.sessionID
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +91,11 @@ struct ShellTabView: View {
|
||||
.id(selectedID)
|
||||
.transition(.opacity)
|
||||
} 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 {
|
||||
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)
|
||||
.ignoresSafeArea(.all)
|
||||
GeometryReader { geo in
|
||||
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.")
|
||||
VStack {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(0..<8, id: \.self) { index in
|
||||
Rectangle()
|
||||
.fill(hostsManager.selectedTheme.ansi[index].suiColor)
|
||||
.onTapGesture {
|
||||
hostsManager.selectedAnsi = index
|
||||
hostsManager.saveThemes()
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack(spacing: 0) {
|
||||
ForEach(8..<16, id: \.self) { index in
|
||||
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) {
|
||||
ForEach(hostsManager.themes) { theme in
|
||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true)
|
||||
ForEach(Theme.builtinThemes) { theme in
|
||||
ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.animation(.default, value: hostsManager.themes)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("Built-in Themes")
|
||||
.padding(.top)
|
||||
.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() {
|
||||
.navigationTitle("Themes")
|
||||
.alert("Enter URL", isPresented: $showAlert) {
|
||||
TextField("", text: $importURL, prompt: Text("URL"))
|
||||
Button("Cancel") {}
|
||||
Button() {
|
||||
UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!)
|
||||
hostsManager.downloadTheme(fromUrl: URL(string: importURL))
|
||||
importURL = ""
|
||||
} label: {
|
||||
Label("Open themes site", systemImage: "safari")
|
||||
Label("Import", systemImage: "square.and.arrow.down")
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
showAlert.toggle()
|
||||
} label: {
|
||||
Label("From URL", systemImage: "link")
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!)
|
||||
} label: {
|
||||
Label("Open themes site", systemImage: "safari")
|
||||
}
|
||||
}
|
||||
ToolbarItem() {
|
||||
Button() {
|
||||
showAlert.toggle()
|
||||
} label: {
|
||||
Label("From URL", systemImage: "link")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user