Repository navigation
Expand file tree
/
Copy pathScratchView.swift
More file actions
95 lines (87 loc) · 3.52 KB
/
Copy pathScratchView.swift
File metadata and controls
95 lines (87 loc) · 3.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import SwiftUI
/// The PC's scratchpad: notes, the first line is the title. Edits save themselves after a pause.
/// A split view: notes beside the editor on iPad, push navigation on a phone.
struct ScratchView: View {
@ObservedObject var model: AppModel
@State private var notes: [Note] = []
@State private var selected: String?
var body: some View {
NavigationSplitView {
List(selection: $selected) {
if notes.isEmpty { Text("No notes").foregroundStyle(.secondary) }
ForEach(notes) { note in
VStack(alignment: .leading, spacing: 2) {
Text(note.title).lineLimit(1)
Text(note.body.split(separator: "\n").dropFirst().first.map(String.init) ?? "")
.font(.caption).foregroundStyle(.secondary).lineLimit(1)
}
.tag(note.id)
.swipeActions { Button("Delete", role: .destructive) { remove(note.id) } }
.contextMenu { Button("Delete", role: .destructive) { remove(note.id) } }
}
}
.scrollContentBackground(.hidden)
.background(Palette.background)
.navigationTitle("Scratchpad")
.navigationSplitViewColumnWidth(min: 240, ideal: 300, max: 400)
.toolbar {
Button {
Task {
if let n = try? await model.server.post("/api/scratch", ["body": ""], as: Note.self) {
await load()
selected = n.id
}
}
} label: { Image(systemName: "square.and.pencil") }
}
.refreshable { await load() }
.task { await load() }
} detail: {
if let id = selected {
NoteEditor(server: model.server, note: notes.first { $0.id == id } ?? Note(id: id, body: ""))
.id(id)
.onDisappear { Task { await load() } }
} else {
ContentUnavailableView("No note open", systemImage: "note.text")
.background(Palette.background)
}
}
}
private func remove(_ id: String) {
Task {
if selected == id { selected = nil }
try? await model.server.delete("/api/scratch/\(id)")
await load()
}
}
private func load() async {
if let list = try? await model.server.get("/api/scratch", as: Notes.self) { notes = list.notes }
}
private struct Notes: Decodable { let notes: [Note] }
}
struct NoteEditor: View {
let server: Server
@State var note: Note
@State private var saving: Task<Void, Never>?
var body: some View {
TextEditor(text: $note.body)
.font(.body.monospaced())
.scrollContentBackground(.hidden)
.padding(.horizontal, 8)
.background(Palette.background)
.navigationTitle(note.title)
.navigationBarTitleDisplayMode(.inline)
.onChange(of: note.body) {
saving?.cancel()
saving = Task {
try? await Task.sleep(for: .milliseconds(600))
if !Task.isCancelled { save() }
}
}
.onDisappear { save() }
}
private func save() {
let body = note.body
Task { _ = try? await server.request("/api/scratch/\(note.id)", method: "PUT", body: ["body": body]) }
}
}