Browse Source

feat: add ability to save a snapshot or arbitrary file path
refactor: remove "New Window"

Sam Jaffe 2 weeks ago
parent
commit
3fe88fa02b

+ 22 - 2
Todos.xcodeproj/project.pbxproj

@@ -397,8 +397,18 @@
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
 				ENABLE_APP_SANDBOX = YES;
+				ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
+				ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
 				ENABLE_PREVIEWS = YES;
-				ENABLE_USER_SELECTED_FILES = readonly;
+				ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
+				ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
+				ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
+				ENABLE_RESOURCE_ACCESS_CAMERA = NO;
+				ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
+				ENABLE_RESOURCE_ACCESS_LOCATION = NO;
+				ENABLE_RESOURCE_ACCESS_PRINTING = NO;
+				ENABLE_RESOURCE_ACCESS_USB = NO;
+				ENABLE_USER_SELECTED_FILES = readwrite;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				LD_RUNPATH_SEARCH_PATHS = (
@@ -428,8 +438,18 @@
 				COMBINE_HIDPI_IMAGES = YES;
 				CURRENT_PROJECT_VERSION = 1;
 				ENABLE_APP_SANDBOX = YES;
+				ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
+				ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO;
 				ENABLE_PREVIEWS = YES;
-				ENABLE_USER_SELECTED_FILES = readonly;
+				ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
+				ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
+				ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
+				ENABLE_RESOURCE_ACCESS_CAMERA = NO;
+				ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
+				ENABLE_RESOURCE_ACCESS_LOCATION = NO;
+				ENABLE_RESOURCE_ACCESS_PRINTING = NO;
+				ENABLE_RESOURCE_ACCESS_USB = NO;
+				ENABLE_USER_SELECTED_FILES = readwrite;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				LD_RUNPATH_SEARCH_PATHS = (

+ 25 - 0
Todos/Controller/SaveController.swift

@@ -0,0 +1,25 @@
+//
+//  SaveController.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/1/26.
+//
+
+import Foundation
+
+final class SaveController {
+  static func filename(date: String) -> URL {
+    URL.documentsDirectory.appending(path: "Todo \(date).yaml")
+  }
+  
+  static func save(_ items: [Category], to: URL) {
+    let data = Data(items.map({ $0.yaml() }).joined().utf8)
+    do {
+      try data.write(to: to, options: [.atomic, .completeFileProtection])
+      let input = try String(contentsOf: to, encoding: .utf8)
+      print(input)
+    } catch {
+      print(error.localizedDescription)
+    }
+  }
+}

+ 8 - 0
Todos/TodosApp.swift

@@ -34,6 +34,14 @@ struct TodosApp: App {
       ContentView()
     }
     .modelContainer(sharedModelContainer)
+    .commands {
+      CommandGroup(replacing: .newItem) {
+        SaveSnapshotMenu()
+          .modelContainer(sharedModelContainer)
+        ExportMenu()
+          .modelContainer(sharedModelContainer)
+      }
+    }
     
     #if os(macOS)
     Settings {

+ 1 - 12
Todos/View/ContentView.swift

@@ -76,22 +76,11 @@ struct ContentView: View {
     }
     
     let ymd = weekStart.formatted(.iso8601.year().month().day())
-    save(to: URL.documentsDirectory.appending(path: "Todo \(ymd).yaml"))
+    SaveController.save(items, to: SaveController.filename(date: ymd))
     weekStart = now
     cleanup()
   }
   
-  private func save(to: URL) {
-    let data = Data(items.map({ $0.yaml() }).joined().utf8)
-    do {
-      try data.write(to: to, options: [.atomic, .completeFileProtection])
-      let input = try String(contentsOf: to, encoding: .utf8)
-      print(input)
-    } catch {
-      print(error.localizedDescription)
-    }
-  }
-  
   private func cleanup() {
     for item in items {
       item.tasks.removeAll(where: { $0.status == .Complete })

+ 37 - 0
Todos/View/ExportMenu.swift

@@ -0,0 +1,37 @@
+//
+//  ExportMenu.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/1/26.
+//
+
+import SwiftUI
+import SwiftData
+import UniformTypeIdentifiers
+
+struct ExportMenu: View {
+  @Environment(\.modelContext) private var modelContext
+  
+  @Query private var items: [Category]
+  @State private var showingExporter = false
+
+  var body: some View {
+    Button("Save As") {
+      showingExporter = true
+    }
+    .keyboardShortcut("S", modifiers: [.command, .shift])
+    .fileExporter(isPresented: $showingExporter, document: StubDocument(),
+                  contentType: .yaml) { result in
+      switch result {
+      case .success(let url):
+        SaveController.save(items, to: url)
+      case .failure(let error):
+        print(error.localizedDescription)
+      }
+    }
+  }
+}
+
+#Preview {
+  ExportMenu()
+}

+ 27 - 0
Todos/View/SaveSnapshotMenu.swift

@@ -0,0 +1,27 @@
+//
+//  SaveSnapshotMenu.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/1/26.
+//
+
+import SwiftUI
+import SwiftData
+
+struct SaveSnapshotMenu: View {
+  @Environment(\.modelContext) private var modelContext
+  
+  @Query private var items: [Category]
+  
+  var body: some View {
+    Button("Save Snapshot") {
+      let snapshot = Date().formatted(.iso8601)
+      SaveController.save(items, to: SaveController.filename(date: snapshot))
+    }
+    .keyboardShortcut("S", modifiers: .command)
+  }
+}
+
+#Preview {
+  SaveSnapshotMenu()
+}

+ 22 - 0
Todos/ViewModel/StubDocument.swift

@@ -0,0 +1,22 @@
+//
+//  StubDocument.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/1/26.
+//
+
+import Foundation
+import SwiftUI
+import UniformTypeIdentifiers
+
+struct StubDocument : FileDocument {
+  static var readableContentTypes = [UTType.yaml]
+    
+  init() {}
+  
+  init(configuration: ReadConfiguration) throws {}
+  
+  func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
+    return FileWrapper(regularFileWithContents: Data())
+  }
+}