aboutsummaryrefslogtreecommitdiff
path: root/Map/Presentation/Preferences/TemplatesPreferencesView.swift
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-07-06 12:45:37 +0200
committerRuben Beltran del Rio <git@r.bdr.sh>2025-07-06 12:45:37 +0200
commit54f89bcd1a7fb0b92b65d8c0ea3847d635e11133 (patch)
treeda6c1e3f015c4b21a2e32854cadb09e71f9079e7 /Map/Presentation/Preferences/TemplatesPreferencesView.swift
parent3cd8f1f7200109d505b5e105d5ebe53f0c93d337 (diff)
Allow adding and removing templates
Diffstat (limited to 'Map/Presentation/Preferences/TemplatesPreferencesView.swift')
-rw-r--r--Map/Presentation/Preferences/TemplatesPreferencesView.swift255
1 files changed, 144 insertions, 111 deletions
diff --git a/Map/Presentation/Preferences/TemplatesPreferencesView.swift b/Map/Presentation/Preferences/TemplatesPreferencesView.swift
index 42db1a3..2994450 100644
--- a/Map/Presentation/Preferences/TemplatesPreferencesView.swift
+++ b/Map/Presentation/Preferences/TemplatesPreferencesView.swift
@@ -19,10 +19,12 @@ struct Template: Identifiable, Codable, Hashable {
var id = UUID()
var name: String
var content: String
+ var isDefault: Bool = false
- init(name: String, content: String) {
+ init(name: String, content: String, isDefault: Bool = false) {
self.name = name
self.content = content
+ self.isDefault = isDefault
}
func hash(into hasher: inout Hasher) {
@@ -34,56 +36,47 @@ struct Template: Identifiable, Codable, Hashable {
}
}
-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
- }
- }
+struct TemplatesPreferencesView: View {
+ @AppStorage("mapTemplates") private var templatesData: Data = Data()
+ @State private var selectedTemplate: Template?
+ @State private var showingAddSheet = false
+ @State private var newTemplateName = ""
- func makeCoordinator() -> Coordinator {
- Coordinator(self)
+ private var templates: Binding<[Template]> {
+ Binding(
+ get: {
+ if let decoded = try? JSONDecoder().decode([Template].self, from: templatesData) {
+ return decoded
+ }
+ return []
+ },
+ set: { newValue in
+ if let encoded = try? JSONEncoder().encode(newValue) {
+ templatesData = encoded
+ }
+ }
+ )
}
- class Coordinator: NSObject, NSTextViewDelegate {
- var parent: SimpleTextEditor
-
- init(_ parent: SimpleTextEditor) {
- self.parent = parent
- }
+ private var selectedTemplateContent: Binding<String> {
+ Binding(
+ get: {
+ guard let selected = selectedTemplate,
+ let template = templates.wrappedValue.first(where: { $0.id == selected.id })
+ else { return "" }
+ return template.content
+ },
+ set: { newContent in
+ guard let selected = selectedTemplate,
+ let index = templates.wrappedValue.firstIndex(where: { $0.id == selected.id })
+ else { return }
- func textDidChange(_ notification: Notification) {
- if let textView = notification.object as? NSTextView {
- parent.text = textView.string
+ var currentTemplates = templates.wrappedValue
+ currentTemplates[index].content = newContent
+ templates.wrappedValue = currentTemplates
}
- }
+ )
}
-}
-
-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 {
VStack(alignment: .leading, spacing: Dimensions.Spacing.regular) {
@@ -97,35 +90,65 @@ struct TemplatesPreferencesView: View {
.padding(.top, Dimensions.Spacing.loose)
.padding(.bottom, Dimensions.Spacing.regular)
HStack(spacing: 0) {
- VStack(alignment: .leading, spacing: 8) {
+ VStack(alignment: .leading, spacing: Dimensions.Spacing.regular) {
HStack {
- Text("Templates")
- .font(.headline)
-
+ Text("preferences.templates.table.title")
+ .font(.Theme.Body.emphasized)
+
Spacer()
-
+
Button(action: { showingAddSheet = true }) {
Image(systemName: "plus")
}
.buttonStyle(.borderless)
-
- Button(action: removeTemplate) {
+
+ Button(action: {
+ removeTemplate(selectedTemplate)
+ }) {
Image(systemName: "minus")
}
.buttonStyle(.borderless)
.disabled(selectedTemplate == nil)
}
-
- List(templates, selection: $selectedTemplate) { template in
- Text(template.name)
- .tag(template)
+
+ List(templates.wrappedValue) { template in
+ HStack {
+ Button(action: { setDefaultTemplate(template) }) {
+ Image(systemName: template.isDefault ? "largecircle.fill.circle" : "circle")
+ .foregroundColor(
+ selectedTemplate?.id == template.id
+ ? Color.white
+ : (template.isDefault
+ ? Color.Theme.UI.accent : Color.Theme.UI.foreground.opacity(0.5))
+ )
+ }
+ .buttonStyle(.plain)
+ .help("Set as default template")
+
+ Text(template.name)
+ .tag(template)
+ .foregroundColor(
+ selectedTemplate?.id == template.id ? Color.white : Color.Theme.UI.foreground
+ )
+
+ Spacer()
+ }
+ .padding(Dimensions.Spacing.cozy)
+ .background(selectedTemplate?.id == template.id ? Color.Theme.UI.accent : Color.clear)
+ .clipShape(RoundedRectangle(cornerRadius: Dimensions.Preferences.Sidebar.radius))
+ .padding(Dimensions.Spacing.coziest)
+ .listRowInsets(EdgeInsets())
+ .listRowSeparator(.hidden)
+ .onTapGesture {
+ selectedTemplate = template
+ }
}
- .listStyle(.sidebar)
+ .listStyle(.plain)
}
- .frame(width: 200)
-
+ .frame(width: Dimensions.Preferences.Sidebar.width)
+
Divider()
-
+
VStack(alignment: .leading, spacing: 8) {
if let selected = selectedTemplate {
HStack {
@@ -133,11 +156,8 @@ struct TemplatesPreferencesView: View {
.font(.headline)
Spacer()
}
-
- SimpleTextEditor(text: $templateText)
- .onChange(of: templateText) { _, newValue in
- updateSelectedTemplate(newValue)
- }
+
+ MapTextEditor(text: selectedTemplateContent)
} else {
VStack {
Spacer()
@@ -151,89 +171,102 @@ struct TemplatesPreferencesView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.leading)
}
- .padding()
.onAppear {
- loadTemplates()
- }
- .onChange(of: selectedTemplate) { _, newSelection in
- if let template = newSelection {
- templateText = template.content
+ // Initialize default templates if needed
+ if templates.wrappedValue.isEmpty
+ || !templates.wrappedValue.contains(where: { $0.isDefault })
+ {
+ createDefaultTemplates()
+ }
+ // Set first template as selected if none is selected
+ if selectedTemplate == nil && !templates.wrappedValue.isEmpty {
+ selectedTemplate = templates.wrappedValue.first
}
}
.sheet(isPresented: $showingAddSheet) {
- VStack(spacing: 16) {
+ VStack(spacing: Dimensions.Spacing.indulgent) {
Text("Add New Template")
- .font(.headline)
-
+ .font(.Theme.Body.emphasized)
+
TextField("Template Name", text: $newTemplateName)
.textFieldStyle(.roundedBorder)
-
+ .font(.Theme.Body.regular)
+
HStack {
Button("Cancel") {
showingAddSheet = false
newTemplateName = ""
}
-
+
Spacer()
-
+
Button("Add") {
addTemplate()
}
.disabled(newTemplateName.isEmpty)
}
}
+ .padding(Dimensions.Spacing.loose)
.frame(width: 300, height: 120)
}
}.padding(.vertical, Dimensions.Spacing.loose)
.padding(.horizontal, Dimensions.Spacing.indulgent)
}
- 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 createDefaultTemplates() {
+ let defaultTemplate = Template(
+ name: String(localized: "map_template.default.title"),
+ content: String(localized: "map_template.default.content"),
+ isDefault: true
+ )
+
+ let emptyTemplate = Template(
+ name: String(localized: "map_template.empty.title"),
+ content: "",
+ isDefault: false
+ )
- private func saveTemplates() {
- if let encoded = try? JSONEncoder().encode(templates) {
- templatesData = encoded
+ if templates.wrappedValue.isEmpty {
+ templates.wrappedValue = [defaultTemplate, emptyTemplate]
+ } else {
+ // Add default template if none exists
+ if !templates.wrappedValue.contains(where: { $0.isDefault }) {
+ var currentTemplates = templates.wrappedValue
+ currentTemplates.insert(defaultTemplate, at: 0)
+ templates.wrappedValue = currentTemplates
+ }
}
}
private func addTemplate() {
- let newTemplate = Template(name: newTemplateName, content: "")
- templates.append(newTemplate)
+ let newTemplate = Template(
+ name: newTemplateName.isEmpty
+ ? String(localized: "map_template.empty.title") : newTemplateName,
+ content: ""
+ )
+ var currentTemplates = templates.wrappedValue
+ currentTemplates.append(newTemplate)
+ templates.wrappedValue = currentTemplates
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 removeTemplate(_ template: Template?) {
+ guard let template else { return }
+ var currentTemplates = templates.wrappedValue
+ currentTemplates.removeAll { $0 == template }
+ templates.wrappedValue = currentTemplates
+ selectedTemplate = templates.wrappedValue.first
}
- 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()
+ private func setDefaultTemplate(_ template: Template) {
+ // Direct assignment to binding - this will automatically save via @AppStorage
+ templates.wrappedValue = templates.wrappedValue.map { t in
+ var newTemplate = t
+ newTemplate.isDefault = (t.id == template.id)
+ return newTemplate
+ }
}
}