oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1import SwiftUI
2
3struct SheetPanels: View {
4 @Bindable var model: SheetModel
5 let panel: String
6 var openImported: ((UInt64, String) -> Void)? = nil
7 @Environment(\.dismiss) private var dismiss
8 @State private var title = ""
9 @State private var width = 110
10 @State private var height = 32
11 @State private var mode = "auto"
12 var body: some View {
13 NavigationStack {
14 Form {
15 if !model.error.isEmpty { Text(model.error).foregroundStyle(.red) }
16 switch panel {
17 case "Rename":
18 TextField("Sheet title", text: $title)
19 Button("Rename") {
20 Task {
21 guard await model.command("/rename", ["title": title]) else { return }
22 await model.loadList()
23 if let sheet = model.sheets.first(where: { $0.ID == model.page?.Sheet.ID }) {
24 await model.open(sheet)
25 }
26 dismiss()
27 }
28 }
29 case "Sizes":
30 TextField("Column width", value: $width, format: .number)
31 Button("Set column width") { Task { await model.resize(axis: "col", size: width) } }
32 .disabled(!(16...2000).contains(width))
33 TextField("Row height", value: $height, format: .number)
34 Button("Set row height") { Task { await model.resize(axis: "row", size: height) } }
35 .disabled(!(16...2000).contains(height))
36 Text("Formatting and size changes do not have undo.").font(.caption)
37 case "History":
38 ForEach(model.history) { entry in
39 VStack(alignment: .leading) {
40 Text(entry.Ref).font(.headline)
41 Text(entry.Who).font(.caption)
42 Text("Before: " + entry.Old).textSelection(.enabled)
43 Text("After: " + entry.New).textSelection(.enabled)
44 if model.mayWrite {
45 Button("Restore before this edit") {
46 Task {
47 await model.command("/restore", ["event": String(entry.ID)])
48 await model.loadHistory()
49 }
50 }
51 }
52 }
53 }
54 case "Data":
55 Picker("Sort column", selection: $model.view.sortCol) {
56 Text("None").tag(-1)
57 ForEach(0..<26, id: \.self) { Text(Coordinate(r: 0, c: $0).ref.dropLast()).tag($0) }
58 }
59 Toggle("Descending", isOn: $model.view.sortDesc)
60 Picker("Filter column", selection: $model.view.filterCol) {
61 Text("None").tag(-1)
62 ForEach(0..<26, id: \.self) { Text(Coordinate(r: 0, c: $0).ref.dropLast()).tag($0) }
63 }
64 Picker("Comparison", selection: $model.view.filterOp) {
65 ForEach(["contains", "equals", "gt", "lt", "nonempty"], id: \.self) { Text($0).tag($0) }
66 }
67 TextField("Filter value", text: $model.view.filterVal)
68 Button("Apply and open table") {
69 Task {
70 guard await model.saveView() else { return }
71 model.showTable = true
72 dismiss()
73 }
74 }.disabled(!model.mayWrite)
75 Button("Open table") {
76 model.showTable = true
77 dismiss()
78 }
79 Text("Sorting changes display order. Cell references stay in their original positions.")
80 .font(.caption)
81 case "Comments": SheetComments(model: model)
82 case "Share": SheetSharing(model: model)
83 case "Import", "Export": SheetFiles(model: model, importing: panel == "Import", openImported: openImported)
84 default: EmptyView()
85 }
86 }.formStyle(.grouped).navigationTitle(panel)
87 .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { dismiss() } } }
88 }
89 #if os(macOS)
90 .frame(minWidth: 480, minHeight: 500)
91 #endif
92 .task {
93 title = model.page?.Sheet.Title ?? ""
94 if panel == "History" { await model.loadHistory() }
95 if panel == "Data" { await model.loadTable() }
96 }
97 }
98}
99