fix sidebar: single click selects, double click opens, reorder on edit only

This commit is contained in:
jess 2026-04-06 02:39:20 -07:00
parent 108484481a
commit 358e30f613
2 changed files with 48 additions and 34 deletions

View File

@ -14,6 +14,7 @@ class AppState: ObservableObject {
@Published var evalResults: [Int: String] = [:] @Published var evalResults: [Int: String] = [:]
@Published var noteList: [NoteInfo] = [] @Published var noteList: [NoteInfo] = []
@Published var currentNoteID: UUID @Published var currentNoteID: UUID
@Published var selectedNoteIDs: Set<UUID> = []
@Published var modified: Bool = false @Published var modified: Bool = false
private let bridge = RustBridge.shared private let bridge = RustBridge.shared
@ -25,6 +26,7 @@ class AppState: ObservableObject {
init() { init() {
let id = bridge.newDocument() let id = bridge.newDocument()
self.currentNoteID = id self.currentNoteID = id
self.selectedNoteIDs = [id]
refreshNoteList() refreshNoteList()
} }
@ -114,23 +116,50 @@ class AppState: ObservableObject {
cleanupBlankNote(currentNoteID) cleanupBlankNote(currentNoteID)
let id = bridge.newDocument() let id = bridge.newDocument()
currentNoteID = id currentNoteID = id
selectedNoteIDs = [id]
documentText = "" documentText = ""
evalResults = [:] evalResults = [:]
modified = false modified = false
refreshNoteList() 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() saveCurrentIfNeeded()
cleanupBlankNote(currentNoteID) cleanupBlankNote(currentNoteID)
if bridge.cacheLoad(id) { if bridge.cacheLoad(id) {
currentNoteID = id currentNoteID = id
selectedNoteIDs = [id]
documentText = bridge.getText(id) documentText = bridge.getText(id)
modified = false modified = false
evaluate() evaluate()
} }
} }
func loadNote(_ id: UUID) {
openNote(id)
}
func saveNote() { func saveNote() {
bridge.setText(currentNoteID, text: documentText) bridge.setText(currentNoteID, text: documentText)
let _ = bridge.cacheSave(currentNoteID) let _ = bridge.cacheSave(currentNoteID)

View File

@ -2,7 +2,6 @@ import SwiftUI
struct SidebarView: View { struct SidebarView: View {
@ObservedObject var state: AppState @ObservedObject var state: AppState
@State private var selection: Set<UUID> = []
@State private var lastClickedID: UUID? @State private var lastClickedID: UUID?
private let dateFormatter: DateFormatter = { private let dateFormatter: DateFormatter = {
@ -45,24 +44,21 @@ struct SidebarView: View {
ForEach(Array(state.noteList.enumerated()), id: \.element.id) { index, note in ForEach(Array(state.noteList.enumerated()), id: \.element.id) { index, note in
NoteRow( NoteRow(
note: note, note: note,
isSelected: state.selectedNoteIDs.contains(note.id),
isActive: note.id == state.currentNoteID, isActive: note.id == state.currentNoteID,
isSelected: selection.contains(note.id),
dateFormatter: dateFormatter dateFormatter: dateFormatter
) )
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture(count: 2) { .onTapGesture(count: 2) {
selection = [note.id] state.openNote(note.id)
lastClickedID = note.id
state.loadNote(note.id)
} }
.onTapGesture(count: 1) { .onTapGesture(count: 1) {
handleClick(note: note, index: index, modifiers: currentModifiers()) handleClick(note: note, index: index)
} }
.contextMenu { .contextMenu {
if selection.count > 1 && selection.contains(note.id) { if state.selectedNoteIDs.count > 1 && state.selectedNoteIDs.contains(note.id) {
Button("Delete \(selection.count) Notes") { Button("Delete \(state.selectedNoteIDs.count) Notes") {
state.deleteNotes(selection) state.deleteNotes(state.selectedNoteIDs)
selection.removeAll()
lastClickedID = nil lastClickedID = nil
} }
} else { } else {
@ -73,9 +69,8 @@ struct SidebarView: View {
} }
} }
.onDeleteCommand { .onDeleteCommand {
guard !selection.isEmpty else { return } guard !state.selectedNoteIDs.isEmpty else { return }
state.deleteNotes(selection) state.deleteNotes(state.selectedNoteIDs)
selection.removeAll()
lastClickedID = nil lastClickedID = nil
} }
} }
@ -84,41 +79,31 @@ struct SidebarView: View {
.onAppear { state.refreshNoteList() } .onAppear { state.refreshNoteList() }
} }
private func handleClick(note: NoteInfo, index: Int, modifiers: EventModifiers) { private func handleClick(note: NoteInfo, index: Int) {
if modifiers.contains(.command) { let flags = NSEvent.modifierFlags
if selection.contains(note.id) { if flags.contains(.command) {
selection.remove(note.id) state.selectNote(note.id, extend: true)
} else {
selection.insert(note.id)
}
lastClickedID = note.id 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 }) { if let lastIndex = state.noteList.firstIndex(where: { $0.id == lastID }) {
let range = min(lastIndex, index)...max(lastIndex, index) let range = min(lastIndex, index)...max(lastIndex, index)
var ids = Set<UUID>()
for i in range { for i in range {
selection.insert(state.noteList[i].id) ids.insert(state.noteList[i].id)
} }
state.selectedNoteIDs = ids
} }
} else { } else {
selection = [note.id] state.selectNote(note.id)
lastClickedID = 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 { struct NoteRow: View {
let note: NoteInfo let note: NoteInfo
let isActive: Bool
let isSelected: Bool let isSelected: Bool
var isActive: Bool = false
let dateFormatter: DateFormatter let dateFormatter: DateFormatter
var body: some View { var body: some View {