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 */; };
|
||||
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 */; };
|
||||
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EA2E357FD300CA9374 /* Haptics.swift */; };
|
||||
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
|
||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -357,6 +359,7 @@
|
||||
A9D8192A2E0E904900442D38 /* Themes */,
|
||||
A90936862E1AC4C600856059 /* Fonts */,
|
||||
A93F283F2E2A5EC80092B8D5 /* Snippets */,
|
||||
A9C060E92E357FC400CA9374 /* Misc */,
|
||||
A92538D32DEE0749007E0A18 /* Views */,
|
||||
A90936852E1AC33C00856059 /* Info.plist */,
|
||||
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
||||
@@ -535,6 +538,14 @@
|
||||
path = Keys;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A9C060E92E357FC400CA9374 /* Misc */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A9C060EA2E357FD300CA9374 /* Haptics.swift */,
|
||||
);
|
||||
path = Misc;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A9C8976F2DF1980900EF9A5F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -783,6 +794,7 @@
|
||||
A9485C762E1AF59F00209824 /* FontManagerView.swift in Sources */,
|
||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||
A93F283D2E2A5DCB0092B8D5 /* SnippetManagerView.swift in Sources */,
|
||||
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */,
|
||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
||||
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
|
||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
||||
|
||||
@@ -188,12 +188,14 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
var newTheme = themes[index]
|
||||
newTheme.name = newName
|
||||
newTheme.id = UUID().uuidString
|
||||
Haptic.medium.trigger()
|
||||
withAnimation { themes[index] = newTheme }
|
||||
saveThemes()
|
||||
}
|
||||
|
||||
func deleteTheme(_ themeToDel: Theme) {
|
||||
guard let index = themes.firstIndex(where: {$0 == themeToDel}) else { return }
|
||||
Haptic.medium.trigger()
|
||||
themes.remove(at: index)
|
||||
saveThemes()
|
||||
}
|
||||
@@ -204,6 +206,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
guard var theme = Theme.decodeTheme(data: data) else { return }
|
||||
theme.name = fromUrl?.lastPathComponent.replacingOccurrences(of: ".itermcolors", with: "") ?? ""
|
||||
self.themes.append(theme)
|
||||
Haptic.success.trigger()
|
||||
saveThemes()
|
||||
}
|
||||
|
||||
@@ -234,6 +237,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
} else {
|
||||
withAnimation { hosts.append(updatedHost) }
|
||||
}
|
||||
Haptic.medium.trigger()
|
||||
}
|
||||
|
||||
func duplicateHost(_ hostToDup: Host) {
|
||||
@@ -241,6 +245,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
hostNewID.id = UUID()
|
||||
if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
|
||||
hosts.insert(hostNewID, at: index+1)
|
||||
Haptic.medium.trigger()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +275,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
func removeHost(_ host: Host) {
|
||||
if let index = hosts.firstIndex(where: { $0.id == host.id }) {
|
||||
let _ = withAnimation { hosts.remove(at: index) }
|
||||
Haptic.medium.trigger()
|
||||
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)
|
||||
|
||||
Picker("Private key", selection: $handler.host.privateKeyID) {
|
||||
Text("None")
|
||||
.tag(nil as UUID?)
|
||||
Divider()
|
||||
ForEach(keyManager.keypairs) { keypair in
|
||||
Text(keypair.label)
|
||||
.tag(keypair.id as UUID?)
|
||||
}
|
||||
Divider()
|
||||
Text("None")
|
||||
.tag(nil as UUID?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ struct AddSnippetView: View {
|
||||
.foregroundStyle(.gray)
|
||||
.listRowSeparator(.hidden)
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(alignment: .leading)
|
||||
TextEditor(text: $content)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
|
||||
@@ -17,12 +17,31 @@ struct SnippetManagerView: View {
|
||||
.ignoresSafeArea(.all)
|
||||
NavigationStack {
|
||||
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
|
||||
Group {
|
||||
VStack(alignment: .leading) {
|
||||
Text(snip.name)
|
||||
.bold()
|
||||
.foregroundStyle(.gray)
|
||||
.font(.subheadline)
|
||||
Text(snip.content)
|
||||
.lineLimit(3)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
@@ -37,6 +56,12 @@ struct SnippetManagerView: View {
|
||||
Label("Duplicate", systemImage: "square.filled.on.square")
|
||||
}
|
||||
.tint(.blue)
|
||||
Button {
|
||||
UIPasteboard().string = snip.content
|
||||
} label: {
|
||||
Label("Copy", systemImage: "doc.on.clipboard")
|
||||
}
|
||||
.tint(.blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user