fix sidebar: single click selects, double click opens, reorder on edit only
This commit is contained in:
parent
108484481a
commit
358e30f613
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue