diff --git a/src/AppState.swift b/src/AppState.swift index f92911f..5086fd8 100644 --- a/src/AppState.swift +++ b/src/AppState.swift @@ -14,6 +14,7 @@ class AppState: ObservableObject { @Published var evalResults: [Int: String] = [:] @Published var noteList: [NoteInfo] = [] @Published var currentNoteID: UUID + @Published var selectedNoteIDs: Set = [] @Published var modified: Bool = false private let bridge = RustBridge.shared @@ -25,6 +26,7 @@ class AppState: ObservableObject { init() { let id = bridge.newDocument() self.currentNoteID = id + self.selectedNoteIDs = [id] refreshNoteList() } @@ -114,23 +116,50 @@ class AppState: ObservableObject { cleanupBlankNote(currentNoteID) let id = bridge.newDocument() currentNoteID = id + selectedNoteIDs = [id] documentText = "" evalResults = [:] modified = false refreshNoteList() } - func loadNote(_ id: UUID) { + func selectNote(_ id: UUID, extend: Bool = false, range: Bool = false) { + if range, let anchor = selectedNoteIDs.first { + guard let anchorIdx = noteList.firstIndex(where: { $0.id == anchor }), + let targetIdx = noteList.firstIndex(where: { $0.id == id }) else { + selectedNoteIDs = [id] + return + } + let lo = min(anchorIdx, targetIdx) + let hi = max(anchorIdx, targetIdx) + selectedNoteIDs = Set(noteList[lo...hi].map(\.id)) + } else if extend { + if selectedNoteIDs.contains(id) { + selectedNoteIDs.remove(id) + } else { + selectedNoteIDs.insert(id) + } + } else { + selectedNoteIDs = [id] + } + } + + func openNote(_ id: UUID) { saveCurrentIfNeeded() cleanupBlankNote(currentNoteID) if bridge.cacheLoad(id) { currentNoteID = id + selectedNoteIDs = [id] documentText = bridge.getText(id) modified = false evaluate() } } + func loadNote(_ id: UUID) { + openNote(id) + } + func saveNote() { bridge.setText(currentNoteID, text: documentText) let _ = bridge.cacheSave(currentNoteID) diff --git a/src/SidebarView.swift b/src/SidebarView.swift index a370aec..75c29f5 100644 --- a/src/SidebarView.swift +++ b/src/SidebarView.swift @@ -2,7 +2,6 @@ import SwiftUI struct SidebarView: View { @ObservedObject var state: AppState - @State private var selection: Set = [] @State private var lastClickedID: UUID? private let dateFormatter: DateFormatter = { @@ -45,24 +44,21 @@ struct SidebarView: View { ForEach(Array(state.noteList.enumerated()), id: \.element.id) { index, note in NoteRow( note: note, + isSelected: state.selectedNoteIDs.contains(note.id), isActive: note.id == state.currentNoteID, - isSelected: selection.contains(note.id), dateFormatter: dateFormatter ) .contentShape(Rectangle()) .onTapGesture(count: 2) { - selection = [note.id] - lastClickedID = note.id - state.loadNote(note.id) + state.openNote(note.id) } .onTapGesture(count: 1) { - handleClick(note: note, index: index, modifiers: currentModifiers()) + handleClick(note: note, index: index) } .contextMenu { - if selection.count > 1 && selection.contains(note.id) { - Button("Delete \(selection.count) Notes") { - state.deleteNotes(selection) - selection.removeAll() + if state.selectedNoteIDs.count > 1 && state.selectedNoteIDs.contains(note.id) { + Button("Delete \(state.selectedNoteIDs.count) Notes") { + state.deleteNotes(state.selectedNoteIDs) lastClickedID = nil } } else { @@ -73,9 +69,8 @@ struct SidebarView: View { } } .onDeleteCommand { - guard !selection.isEmpty else { return } - state.deleteNotes(selection) - selection.removeAll() + guard !state.selectedNoteIDs.isEmpty else { return } + state.deleteNotes(state.selectedNoteIDs) lastClickedID = nil } } @@ -84,41 +79,31 @@ struct SidebarView: View { .onAppear { state.refreshNoteList() } } - private func handleClick(note: NoteInfo, index: Int, modifiers: EventModifiers) { - if modifiers.contains(.command) { - if selection.contains(note.id) { - selection.remove(note.id) - } else { - selection.insert(note.id) - } + private func handleClick(note: NoteInfo, index: Int) { + let flags = NSEvent.modifierFlags + if flags.contains(.command) { + state.selectNote(note.id, extend: true) lastClickedID = note.id - } else if modifiers.contains(.shift), let lastID = lastClickedID { + } else if flags.contains(.shift), let lastID = lastClickedID { if let lastIndex = state.noteList.firstIndex(where: { $0.id == lastID }) { let range = min(lastIndex, index)...max(lastIndex, index) + var ids = Set() for i in range { - selection.insert(state.noteList[i].id) + ids.insert(state.noteList[i].id) } + state.selectedNoteIDs = ids } } else { - selection = [note.id] + state.selectNote(note.id) lastClickedID = note.id - state.loadNote(note.id) } } - - private func currentModifiers() -> EventModifiers { - let flags = NSEvent.modifierFlags - var mods: EventModifiers = [] - if flags.contains(.command) { mods.insert(.command) } - if flags.contains(.shift) { mods.insert(.shift) } - return mods - } } struct NoteRow: View { let note: NoteInfo - let isActive: Bool let isSelected: Bool + var isActive: Bool = false let dateFormatter: DateFormatter var body: some View {