aboutsummaryrefslogtreecommitdiff
path: root/Map/Presentation/Preferences/TemplatesPreferencesView.swift
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-07-05 11:29:17 +0200
committerRuben Beltran del Rio <git@r.bdr.sh>2025-07-05 11:29:17 +0200
commitcb4a713afc81326485becba4fa3a635f112114d4 (patch)
treee3efea48ccb7be53ed681eb9ed333af39d93ce67 /Map/Presentation/Preferences/TemplatesPreferencesView.swift
parente21ead77ffdff206d1ae17e5ce93ea34c4227414 (diff)
Add custom stages
Diffstat (limited to 'Map/Presentation/Preferences/TemplatesPreferencesView.swift')
-rw-r--r--Map/Presentation/Preferences/TemplatesPreferencesView.swift231
1 files changed, 231 insertions, 0 deletions
diff --git a/Map/Presentation/Preferences/TemplatesPreferencesView.swift b/Map/Presentation/Preferences/TemplatesPreferencesView.swift
new file mode 100644
index 0000000..806c22b
--- /dev/null
+++ b/Map/Presentation/Preferences/TemplatesPreferencesView.swift
@@ -0,0 +1,231 @@
+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
+
+import SwiftUI
+
+struct Template: Identifiable, Codable, Hashable {
+ var id = UUID()
+ var name: String
+ var content: String
+
+ init(name: String, content: String) {
+ self.name = name
+ self.content = content
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(id)
+ }
+
+ static func == (lhs: Template, rhs: Template) -> Bool {
+ lhs.id == rhs.id
+ }
+}
+
+struct SimpleTextEditor: NSViewRepresentable {
+ @Binding var text: String
+
+ func makeNSView(context: Context) -> NSScrollView {
+ let scrollView = NSTextView.scrollableTextView()
+ let textView = scrollView.documentView as! NSTextView
+
+ textView.backgroundColor = .textBackgroundColor
+ textView.allowsUndo = true
+ textView.delegate = context.coordinator
+ textView.string = text
+ textView.isEditable = true
+ textView.font = .monospacedSystemFont(ofSize: 14.0, weight: .regular)
+
+ return scrollView
+ }
+
+ func updateNSView(_ nsView: NSScrollView, context: Context) {
+ let textView = nsView.documentView as! NSTextView
+ if textView.string != text {
+ textView.string = text
+ }
+ }
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator(self)
+ }
+
+ class Coordinator: NSObject, NSTextViewDelegate {
+ var parent: SimpleTextEditor
+
+ init(_ parent: SimpleTextEditor) {
+ self.parent = parent
+ }
+
+ func textDidChange(_ notification: Notification) {
+ if let textView = notification.object as? NSTextView {
+ parent.text = textView.string
+ }
+ }
+ }
+}
+
+struct TemplatesPreferencesView: View {
+ @AppStorage("mapTemplates") private var templatesData: Data = Data()
+ @State private var templates: [Template] = []
+ @State private var selectedTemplate: Template?
+ @State private var templateText: String = ""
+ @State private var showingAddSheet = false
+ @State private var newTemplateName = ""
+
+ var body: some View {
+ HStack(spacing: 0) {
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text("Templates")
+ .font(.headline)
+
+ Spacer()
+
+ Button(action: { showingAddSheet = true }) {
+ Image(systemName: "plus")
+ }
+ .buttonStyle(.borderless)
+
+ Button(action: removeTemplate) {
+ Image(systemName: "minus")
+ }
+ .buttonStyle(.borderless)
+ .disabled(selectedTemplate == nil)
+ }
+
+ List(templates, selection: $selectedTemplate) { template in
+ Text(template.name)
+ .tag(template)
+ }
+ .listStyle(.sidebar)
+ }
+ .frame(width: 200)
+
+ Divider()
+
+ VStack(alignment: .leading, spacing: 8) {
+ if let selected = selectedTemplate {
+ HStack {
+ Text("Template: \(selected.name)")
+ .font(.headline)
+ Spacer()
+ }
+
+ SimpleTextEditor(text: $templateText)
+ .onChange(of: templateText) { _, newValue in
+ updateSelectedTemplate(newValue)
+ }
+ } else {
+ VStack {
+ Spacer()
+ Text("Select a template to edit")
+ .foregroundColor(.secondary)
+ Spacer()
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ }
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .padding(.leading)
+ }
+ .padding()
+ .onAppear {
+ loadTemplates()
+ }
+ .onChange(of: selectedTemplate) { _, newSelection in
+ if let template = newSelection {
+ templateText = template.content
+ }
+ }
+ .sheet(isPresented: $showingAddSheet) {
+ VStack(spacing: 16) {
+ Text("Add New Template")
+ .font(.headline)
+
+ TextField("Template Name", text: $newTemplateName)
+ .textFieldStyle(.roundedBorder)
+
+ HStack {
+ Button("Cancel") {
+ showingAddSheet = false
+ newTemplateName = ""
+ }
+
+ Spacer()
+
+ Button("Add") {
+ addTemplate()
+ }
+ .disabled(newTemplateName.isEmpty)
+ }
+ }
+ .padding()
+ .frame(width: 300, height: 120)
+ }
+ }
+
+ private func loadTemplates() {
+ if let decoded = try? JSONDecoder().decode([Template].self, from: templatesData) {
+ templates = decoded
+ } else {
+ templates = [
+ Template(
+ name: "Basic Map",
+ content:
+ "Component A (90, 20)\nComponent B (70, 40)\nComponent A -> Component B\n\n[Note] (50, 10) This is a sample template"
+ )
+ ]
+ saveTemplates()
+ }
+ }
+
+ private func saveTemplates() {
+ if let encoded = try? JSONEncoder().encode(templates) {
+ templatesData = encoded
+ }
+ }
+
+ private func addTemplate() {
+ let newTemplate = Template(name: newTemplateName, content: "")
+ templates.append(newTemplate)
+ selectedTemplate = newTemplate
+ templateText = ""
+ saveTemplates()
+ showingAddSheet = false
+ newTemplateName = ""
+ }
+
+ private func removeTemplate() {
+ guard let selected = selectedTemplate else { return }
+ templates.removeAll { $0.id == selected.id }
+ selectedTemplate = nil
+ templateText = ""
+ saveTemplates()
+ }
+
+ private func updateSelectedTemplate(_ newContent: String) {
+ guard let selected = selectedTemplate,
+ let index = templates.firstIndex(where: { $0.id == selected.id })
+ else { return }
+
+ templates[index].content = newContent
+ saveTemplates()
+ }
+}
+
+#Preview {
+ TemplatesPreferencesView()
+}