Add multiple terminals per task and saved commands that start one
This commit is contained in:
@@ -42,6 +42,11 @@ struct SettingsView: View {
|
||||
ModelMenu(current: store.defaultModel, help: "Model for new tasks") { m in
|
||||
try await store.api.client.updateDefaultModel(body: .json(.init(model: m))).ok.body.json
|
||||
}
|
||||
heading("Saved commands")
|
||||
Text("Each is a button in the Terminal tab of every task on this server. It opens a new shell in the task's worktree and runs the command there.")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
SavedCommands()
|
||||
heading("iCloud sync")
|
||||
SyncState()
|
||||
heading("This Mac's permissions")
|
||||
@@ -217,6 +222,140 @@ private struct PrefsBox: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct SavedCommands: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var list: [SavedCommand] = []
|
||||
@ViewState private var editing: String?
|
||||
@ViewState private var name = ""
|
||||
@ViewState private var command = ""
|
||||
@ViewState private var off: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(list, id: \.id) { c in
|
||||
if editing == c.id {
|
||||
form(c.id)
|
||||
} else {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 12) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(c.name)
|
||||
Text(c.command)
|
||||
.font(Fonts.font(12))
|
||||
.foregroundStyle(Palette.muted)
|
||||
.textSelection(.enabled)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Button("Edit") {
|
||||
editing = c.id
|
||||
name = c.name
|
||||
command = c.command
|
||||
}
|
||||
.buttonStyle(SmallLinkButtonStyle())
|
||||
Button("Delete") {
|
||||
delete(c)
|
||||
}
|
||||
.buttonStyle(SmallLinkButtonStyle(color: Palette.danger))
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
.overlay(alignment: .top) {
|
||||
Rectangle().fill(Palette.border).frame(height: 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if editing == nil {
|
||||
form(nil)
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.onAppear {
|
||||
off = store.events.add { ev in
|
||||
if ev.kind == "command.saved" || ev.kind == "command.deleted" {
|
||||
load()
|
||||
}
|
||||
}
|
||||
load()
|
||||
}
|
||||
.onDisappear {
|
||||
off?()
|
||||
off = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func form(_ id: String?) -> some View {
|
||||
let valid = !name.trimmingCharacters(in: .whitespaces).isEmpty && !command.trimmingCharacters(in: .whitespaces).isEmpty
|
||||
return HStack(spacing: 6) {
|
||||
TextField("Name", text: $name)
|
||||
.modifier(FieldStyle())
|
||||
.frame(width: 180)
|
||||
.onSubmit {
|
||||
save(id, valid)
|
||||
}
|
||||
TextField("Command", text: $command)
|
||||
.modifier(FieldStyle())
|
||||
.onSubmit {
|
||||
save(id, valid)
|
||||
}
|
||||
Button(id == nil ? "Add" : "Save") {
|
||||
save(id, valid)
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
.disabled(!valid)
|
||||
if id != nil {
|
||||
Button("Cancel") {
|
||||
editing = nil
|
||||
name = ""
|
||||
command = ""
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
|
||||
private func load() {
|
||||
Task {
|
||||
do {
|
||||
list = try await store.api.client.listSavedCommands().ok.body.json
|
||||
} catch {
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func save(_ id: String?, _ valid: Bool) {
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
let body = Components.Schemas.SavedCommandSave(name: name, command: command)
|
||||
Task {
|
||||
do {
|
||||
if let id {
|
||||
_ = try await store.api.client.updateSavedCommand(path: .init(commandId: id), body: .json(body)).ok.body.json
|
||||
} else {
|
||||
_ = try await store.api.client.createSavedCommand(body: .json(body)).created.body.json
|
||||
}
|
||||
editing = nil
|
||||
name = ""
|
||||
command = ""
|
||||
} catch {
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func delete(_ c: SavedCommand) {
|
||||
Task {
|
||||
do {
|
||||
_ = try await store.api.client.deleteSavedCommand(path: .init(commandId: c.id)).noContent
|
||||
} catch {
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ModelMenu: View {
|
||||
let current: String
|
||||
let help: String
|
||||
|
||||
Reference in New Issue
Block a user