diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-05 11:29:17 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-05 11:29:17 +0200 |
| commit | cb4a713afc81326485becba4fa3a635f112114d4 (patch) | |
| tree | e3efea48ccb7be53ed681eb9ed333af39d93ce67 /Map/Presentation/Preferences/TemplatesPreferencesView.swift | |
| parent | e21ead77ffdff206d1ae17e5ce93ea34c4227414 (diff) | |
Add custom stages
Diffstat (limited to 'Map/Presentation/Preferences/TemplatesPreferencesView.swift')
| -rw-r--r-- | Map/Presentation/Preferences/TemplatesPreferencesView.swift | 231 |
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() +} |