Files
oai-swift/oAI/Views/Screens/SkillEditorSheet.swift
T
runeandClaude Sonnet 5 0af644456e Update commercial licensing contact URL to oai.pm
Consolidates the mac.oai.pm subdomain references (introduced in the
PolyForm Noncommercial relicense) to the root oai.pm domain, across
the LICENSE file, README, and all Swift source file headers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:34:04 +02:00

164 lines
6.7 KiB
Swift

//
// ShortcutEditorSheet.swift
// oAI
//
// Create or edit a user-defined shortcut (prompt template)
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling oAI or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://oai.pm>.
import SwiftUI
struct ShortcutEditorSheet: View {
@Environment(\.dismiss) var dismiss
let isNew: Bool
let onSave: (Shortcut) -> Void
@State private var shortcutID: UUID
@State private var command: String
@State private var description: String
@State private var template: String
@State private var createdAt: Date
init(shortcut: Shortcut? = nil, onSave: @escaping (Shortcut) -> Void) {
self.isNew = shortcut == nil
self.onSave = onSave
let s = shortcut ?? Shortcut(command: "/", description: "", template: "")
_shortcutID = State(initialValue: s.id)
_command = State(initialValue: s.command)
_description = State(initialValue: s.description)
_template = State(initialValue: s.template)
_createdAt = State(initialValue: s.createdAt)
}
private var normalizedCommand: String {
var c = command.lowercased().trimmingCharacters(in: .whitespaces)
if !c.hasPrefix("/") { c = "/" + c }
c = c.components(separatedBy: .whitespaces).joined()
return c
}
private var isValid: Bool {
normalizedCommand.count > 1
&& !description.trimmingCharacters(in: .whitespaces).isEmpty
&& !template.trimmingCharacters(in: .whitespaces).isEmpty
}
var body: some View {
VStack(spacing: 0) {
HStack {
Text(isNew ? "New Shortcut" : "Edit Shortcut")
.font(.system(size: 18, weight: .bold))
Spacer()
Button { dismiss() } label: {
Image(systemName: "xmark.circle.fill")
.font(.title2).foregroundStyle(.secondary)
}
.buttonStyle(.plain)
.keyboardShortcut(.escape, modifiers: [])
}
.padding(.horizontal, 24).padding(.top, 20).padding(.bottom, 16)
Divider()
ScrollView {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 6) {
Text("Command").font(.system(size: 13, weight: .semibold))
TextField("/command", text: $command)
.textFieldStyle(.roundedBorder)
.font(.system(size: 13, design: .monospaced))
.onChange(of: command) {
if !command.isEmpty && !command.hasPrefix("/") {
command = "/" + command
}
}
Text("Lowercase letters, numbers, and hyphens only. No spaces.")
.font(.caption).foregroundStyle(.secondary)
}
VStack(alignment: .leading, spacing: 6) {
Text("Description").font(.system(size: 13, weight: .semibold))
TextField("Brief description shown in the command dropdown", text: $description)
.textFieldStyle(.roundedBorder).font(.system(size: 13))
}
VStack(alignment: .leading, spacing: 6) {
Text("Template").font(.system(size: 13, weight: .semibold))
TextEditor(text: $template)
.font(.system(size: 13, design: .monospaced))
.frame(minHeight: 120)
.scrollContentBackground(.hidden)
.padding(6)
.background(Color(nsColor: .textBackgroundColor))
.cornerRadius(6)
.overlay(RoundedRectangle(cornerRadius: 6)
.stroke(Color(nsColor: .separatorColor), lineWidth: 1))
HStack(alignment: .top, spacing: 8) {
Image(systemName: "info.circle").foregroundStyle(.blue).font(.callout)
VStack(alignment: .leading, spacing: 4) {
Text("Use **{{input}}** to insert whatever you type after the command.")
.font(.caption)
if template.contains("{{input}}") {
Label("Needs input — user types after the command", systemImage: "checkmark.circle.fill")
.font(.caption).foregroundStyle(.green)
} else {
Label("Executes immediately — no extra input needed", systemImage: "bolt.fill")
.font(.caption).foregroundStyle(.orange)
}
}
}
.padding(10)
.background(.blue.opacity(0.06), in: RoundedRectangle(cornerRadius: 8))
}
}
.padding(.horizontal, 24).padding(.vertical, 16)
}
Divider()
HStack {
Button("Cancel") { dismiss() }.buttonStyle(.bordered)
Spacer()
Button("Save") {
let shortcut = Shortcut(
id: shortcutID,
command: normalizedCommand,
description: description.trimmingCharacters(in: .whitespaces),
template: template,
createdAt: createdAt,
updatedAt: Date()
)
onSave(shortcut)
dismiss()
}
.buttonStyle(.borderedProminent)
.disabled(!isValid)
.keyboardShortcut(.return, modifiers: [.command])
}
.padding(.horizontal, 24).padding(.vertical, 12)
}
.frame(minWidth: 480, idealWidth: 540, minHeight: 460, idealHeight: 520)
}
}
#Preview {
ShortcutEditorSheet { _ in }
}