diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-06 12:45:37 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-06 12:45:37 +0200 |
| commit | 54f89bcd1a7fb0b92b65d8c0ea3847d635e11133 (patch) | |
| tree | da6c1e3f015c4b21a2e32854cadb09e71f9079e7 /Map/Presentation/Preferences/TemplatesPreferencesView.swift | |
| parent | 3cd8f1f7200109d505b5e105d5ebe53f0c93d337 (diff) | |
Allow adding and removing templates
Diffstat (limited to 'Map/Presentation/Preferences/TemplatesPreferencesView.swift')
| -rw-r--r-- | Map/Presentation/Preferences/TemplatesPreferencesView.swift | 255 |
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 + } } } |