mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
fixed snippet manager ui when no snippets
added haptics and stuff improved picker ui
This commit is contained in:
@@ -76,6 +76,7 @@
|
|||||||
A9BA1D192E1D9AE1005BDCEF /* SwiftTerm.Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */; };
|
A9BA1D192E1D9AE1005BDCEF /* SwiftTerm.Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */; };
|
||||||
A9BA1D1E2E1EAD51005BDCEF /* SF-Mono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */; };
|
A9BA1D1E2E1EAD51005BDCEF /* SF-Mono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */; };
|
||||||
A9BA1D1F2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */; };
|
A9BA1D1F2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */; };
|
||||||
|
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EA2E357FD300CA9374 /* Haptics.swift */; };
|
||||||
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
|
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
|
||||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
|
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
|
||||||
A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; };
|
A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; };
|
||||||
@@ -202,6 +203,7 @@
|
|||||||
A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTerm.Color.swift; sourceTree = "<group>"; };
|
A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTerm.Color.swift; sourceTree = "<group>"; };
|
||||||
A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-Bold.otf"; sourceTree = "<group>"; };
|
A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-Bold.otf"; sourceTree = "<group>"; };
|
||||||
A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-BoldItalic.otf"; sourceTree = "<group>"; };
|
A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-BoldItalic.otf"; sourceTree = "<group>"; };
|
||||||
|
A9C060EA2E357FD300CA9374 /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = "<group>"; };
|
||||||
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = "<group>"; };
|
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = "<group>"; };
|
||||||
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
|
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
|
||||||
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||||
@@ -357,6 +359,7 @@
|
|||||||
A9D8192A2E0E904900442D38 /* Themes */,
|
A9D8192A2E0E904900442D38 /* Themes */,
|
||||||
A90936862E1AC4C600856059 /* Fonts */,
|
A90936862E1AC4C600856059 /* Fonts */,
|
||||||
A93F283F2E2A5EC80092B8D5 /* Snippets */,
|
A93F283F2E2A5EC80092B8D5 /* Snippets */,
|
||||||
|
A9C060E92E357FC400CA9374 /* Misc */,
|
||||||
A92538D32DEE0749007E0A18 /* Views */,
|
A92538D32DEE0749007E0A18 /* Views */,
|
||||||
A90936852E1AC33C00856059 /* Info.plist */,
|
A90936852E1AC33C00856059 /* Info.plist */,
|
||||||
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
||||||
@@ -535,6 +538,14 @@
|
|||||||
path = Keys;
|
path = Keys;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A9C060E92E357FC400CA9374 /* Misc */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A9C060EA2E357FD300CA9374 /* Haptics.swift */,
|
||||||
|
);
|
||||||
|
path = Misc;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A9C8976F2DF1980900EF9A5F /* Frameworks */ = {
|
A9C8976F2DF1980900EF9A5F /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -783,6 +794,7 @@
|
|||||||
A9485C762E1AF59F00209824 /* FontManagerView.swift in Sources */,
|
A9485C762E1AF59F00209824 /* FontManagerView.swift in Sources */,
|
||||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||||
A93F283D2E2A5DCB0092B8D5 /* SnippetManagerView.swift in Sources */,
|
A93F283D2E2A5DCB0092B8D5 /* SnippetManagerView.swift in Sources */,
|
||||||
|
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */,
|
||||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
||||||
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
|
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
|
||||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
||||||
|
|||||||
@@ -188,12 +188,14 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
var newTheme = themes[index]
|
var newTheme = themes[index]
|
||||||
newTheme.name = newName
|
newTheme.name = newName
|
||||||
newTheme.id = UUID().uuidString
|
newTheme.id = UUID().uuidString
|
||||||
|
Haptic.medium.trigger()
|
||||||
withAnimation { themes[index] = newTheme }
|
withAnimation { themes[index] = newTheme }
|
||||||
saveThemes()
|
saveThemes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteTheme(_ themeToDel: Theme) {
|
func deleteTheme(_ themeToDel: Theme) {
|
||||||
guard let index = themes.firstIndex(where: {$0 == themeToDel}) else { return }
|
guard let index = themes.firstIndex(where: {$0 == themeToDel}) else { return }
|
||||||
|
Haptic.medium.trigger()
|
||||||
themes.remove(at: index)
|
themes.remove(at: index)
|
||||||
saveThemes()
|
saveThemes()
|
||||||
}
|
}
|
||||||
@@ -204,6 +206,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
guard var theme = Theme.decodeTheme(data: data) else { return }
|
guard var theme = Theme.decodeTheme(data: data) else { return }
|
||||||
theme.name = fromUrl?.lastPathComponent.replacingOccurrences(of: ".itermcolors", with: "") ?? ""
|
theme.name = fromUrl?.lastPathComponent.replacingOccurrences(of: ".itermcolors", with: "") ?? ""
|
||||||
self.themes.append(theme)
|
self.themes.append(theme)
|
||||||
|
Haptic.success.trigger()
|
||||||
saveThemes()
|
saveThemes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +237,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
} else {
|
} else {
|
||||||
withAnimation { hosts.append(updatedHost) }
|
withAnimation { hosts.append(updatedHost) }
|
||||||
}
|
}
|
||||||
|
Haptic.medium.trigger()
|
||||||
}
|
}
|
||||||
|
|
||||||
func duplicateHost(_ hostToDup: Host) {
|
func duplicateHost(_ hostToDup: Host) {
|
||||||
@@ -241,6 +245,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
hostNewID.id = UUID()
|
hostNewID.id = UUID()
|
||||||
if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
|
if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
|
||||||
hosts.insert(hostNewID, at: index+1)
|
hosts.insert(hostNewID, at: index+1)
|
||||||
|
Haptic.medium.trigger()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +275,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
func removeHost(_ host: Host) {
|
func removeHost(_ host: Host) {
|
||||||
if let index = hosts.firstIndex(where: { $0.id == host.id }) {
|
if let index = hosts.firstIndex(where: { $0.id == host.id }) {
|
||||||
let _ = withAnimation { hosts.remove(at: index) }
|
let _ = withAnimation { hosts.remove(at: index) }
|
||||||
|
Haptic.medium.trigger()
|
||||||
saveHosts()
|
saveHosts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
ShhShell/Misc/Haptics.swift
Normal file
68
ShhShell/Misc/Haptics.swift
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// Haptics.swift
|
||||||
|
// ShhShell
|
||||||
|
//
|
||||||
|
// Created by neon443 on 26/07/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
enum Haptic {
|
||||||
|
case success
|
||||||
|
case error
|
||||||
|
case light
|
||||||
|
case medium
|
||||||
|
case heavy
|
||||||
|
case soft
|
||||||
|
case rigid
|
||||||
|
|
||||||
|
var isUIImpact: Bool {
|
||||||
|
switch self {
|
||||||
|
case .light, .medium, .heavy, .soft, .rigid:
|
||||||
|
return true
|
||||||
|
case .success, .error:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
var uiImpact: UIImpactFeedbackGenerator? {
|
||||||
|
guard self.isUIImpact else { return nil }
|
||||||
|
switch self {
|
||||||
|
case .light, .medium, .heavy, .soft, .rigid:
|
||||||
|
switch self {
|
||||||
|
case .light:
|
||||||
|
return UIImpactFeedbackGenerator(style: .light)
|
||||||
|
case .medium:
|
||||||
|
return UIImpactFeedbackGenerator(style: .medium)
|
||||||
|
case .heavy:
|
||||||
|
return UIImpactFeedbackGenerator(style: .heavy)
|
||||||
|
case .soft:
|
||||||
|
return UIImpactFeedbackGenerator(style: .soft)
|
||||||
|
case .rigid:
|
||||||
|
return UIImpactFeedbackGenerator(style: .rigid)
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trigger() {
|
||||||
|
Task { @MainActor in
|
||||||
|
if self.isUIImpact {
|
||||||
|
self.uiImpact?.impactOccurred()
|
||||||
|
} else {
|
||||||
|
switch self {
|
||||||
|
case .success:
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
|
case .error:
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
default: print("idk atp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -80,13 +80,13 @@ struct ConnectionView: View {
|
|||||||
TextBox(label: "Password", text: $handler.host.password, prompt: "not required if using publickeys", secure: true)
|
TextBox(label: "Password", text: $handler.host.password, prompt: "not required if using publickeys", secure: true)
|
||||||
|
|
||||||
Picker("Private key", selection: $handler.host.privateKeyID) {
|
Picker("Private key", selection: $handler.host.privateKeyID) {
|
||||||
Text("None")
|
|
||||||
.tag(nil as UUID?)
|
|
||||||
Divider()
|
|
||||||
ForEach(keyManager.keypairs) { keypair in
|
ForEach(keyManager.keypairs) { keypair in
|
||||||
Text(keypair.label)
|
Text(keypair.label)
|
||||||
.tag(keypair.id as UUID?)
|
.tag(keypair.id as UUID?)
|
||||||
}
|
}
|
||||||
|
Divider()
|
||||||
|
Text("None")
|
||||||
|
.tag(nil as UUID?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ struct AddSnippetView: View {
|
|||||||
.foregroundStyle(.gray)
|
.foregroundStyle(.gray)
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
|
.frame(alignment: .leading)
|
||||||
TextEditor(text: $content)
|
TextEditor(text: $content)
|
||||||
.autocorrectionDisabled()
|
.autocorrectionDisabled()
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
|
|||||||
@@ -17,12 +17,31 @@ struct SnippetManagerView: View {
|
|||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
List {
|
List {
|
||||||
|
if hostsManager.snippets.isEmpty {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Image(systemName: "questionmark.square.dashed")
|
||||||
|
.resizable().scaledToFit()
|
||||||
|
.frame(width: 50)
|
||||||
|
.foregroundStyle(hostsManager.tint)
|
||||||
|
.shadow(color: hostsManager.tint, radius: 2)
|
||||||
|
Text("No Snippets")
|
||||||
|
.font(.title)
|
||||||
|
.monospaced()
|
||||||
|
.padding(.bottom)
|
||||||
|
Text("Snippets are strings of commands that can be run at once in a terminal.")
|
||||||
|
.padding(.bottom)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
.foregroundStyle(.foreground.opacity(0.7))
|
||||||
|
}
|
||||||
|
}
|
||||||
ForEach(hostsManager.snippets) { snip in
|
ForEach(hostsManager.snippets) { snip in
|
||||||
Group {
|
VStack(alignment: .leading) {
|
||||||
Text(snip.name)
|
Text(snip.name)
|
||||||
.bold()
|
.bold()
|
||||||
|
.foregroundStyle(.gray)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
Text(snip.content)
|
Text(snip.content)
|
||||||
|
.lineLimit(3)
|
||||||
}
|
}
|
||||||
.swipeActions(edge: .trailing) {
|
.swipeActions(edge: .trailing) {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
@@ -37,6 +56,12 @@ struct SnippetManagerView: View {
|
|||||||
Label("Duplicate", systemImage: "square.filled.on.square")
|
Label("Duplicate", systemImage: "square.filled.on.square")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
|
Button {
|
||||||
|
UIPasteboard().string = snip.content
|
||||||
|
} label: {
|
||||||
|
Label("Copy", systemImage: "doc.on.clipboard")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user