| 1 | import SwiftUI |
| 2 | |
| 3 | struct SharedMember: Decodable { |
| 4 | let ID: UInt64 |
| 5 | let Address: String |
| 6 | } |
| 7 | struct SharedPerson: Decodable, Identifiable { |
| 8 | let Member: SharedMember |
| 9 | let Role: String |
| 10 | var id: UInt64 { Member.ID } |
| 11 | } |
| 12 | struct SharedLink: Decodable, Identifiable { |
| 13 | let ID: UInt64 |
| 14 | let Role: String |
| 15 | var id: UInt64 { self.ID } |
| 16 | } |
| 17 | struct SharingPage: Decodable { |
| 18 | let People: [SharedPerson]? |
| 19 | let Links: [SharedLink]? |
| 20 | let NewLink: String |
| 21 | let Notice: String |
| 22 | } |
| 23 | struct SheetSharing: View { |
| 24 | @Bindable var model: SheetModel |
| 25 | @State private var sharing: SharingPage? |
| 26 | @State private var address = "" |
| 27 | @State private var role = "viewer" |
| 28 | @State private var linkRole = "viewer" |
| 29 | var body: some View { |
| 30 | Group { |
| 31 | Text("People you share with can see your team’s name.").font(.callout) |
| 32 | TextField("Email address", text: $address) |
| 33 | Picker("Access", selection: $role) { |
| 34 | ForEach(["viewer", "commenter", "editor", "owner"], id: \.self) { |
| 35 | Text($0.capitalized).tag($0) |
| 36 | } |
| 37 | } |
| 38 | Button("Grant access") { |
| 39 | Task { |
| 40 | await model.command("/share", ["address": address, "role": role]) |
| 41 | await load() |
| 42 | } |
| 43 | }.disabled(address.isEmpty) |
| 44 | ForEach(sharing?.People ?? []) { person in |
| 45 | LabeledContent(person.Member.Address, value: person.Role) |
| 46 | Button("Remove access", role: .destructive) { |
| 47 | Task { |
| 48 | await model.command("/share/revoke", ["member": String(person.id)]) |
| 49 | await load() |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | Picker("Link access", selection: $linkRole) { |
| 54 | Text("Viewer").tag("viewer") |
| 55 | Text("Commenter").tag("commenter") |
| 56 | } |
| 57 | Button("Create link") { |
| 58 | Task { |
| 59 | do { |
| 60 | sharing = try await model.form(model.base + "/share/link", ["role": linkRole]) |
| 61 | } catch { model.error = error.localizedDescription } |
| 62 | } |
| 63 | } |
| 64 | if let sharing, !sharing.NewLink.isEmpty { |
| 65 | Text(sharing.NewLink).textSelection(.enabled) |
| 66 | ShareLink(item: sharing.NewLink) |
| 67 | Text("Copy this link now. It is shown only once.").font(.caption) |
| 68 | } |
| 69 | ForEach(sharing?.Links ?? []) { link in |
| 70 | Button("Revoke \(link.Role) link \(link.ID)", role: .destructive) { |
| 71 | Task { |
| 72 | await model.command("/share/link/revoke", ["link": String(link.ID)]) |
| 73 | await load() |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | Text(model.notice).font(.callout) |
| 78 | }.task { await load() } |
| 79 | } |
| 80 | private func load() async { |
| 81 | do { sharing = try await model.client.request(model.base + "/share") } catch { |
| 82 | model.error = error.localizedDescription |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |