@@ -13,14 +13,16 @@ import IdentifiedCollections
13
13
@MainActor
14
14
@Observable
15
15
final class AppModel {
16
- var panels : IdentifiedArrayOf < PanelModel > {
16
+ let root : PanelModel
17
+
18
+ var panels : IdentifiedArrayOf < PanelModel > = [ ] {
17
19
didSet {
18
20
bindPanelModels ( )
19
21
}
20
22
}
21
23
22
- init ( panels : IdentifiedArrayOf < PanelModel > ) {
23
- self . panels = panels
24
+ init ( root : PanelModel ) {
25
+ self . root = root
24
26
bindPanelModels ( )
25
27
}
26
28
@@ -33,22 +35,15 @@ final class AppModel {
33
35
}
34
36
35
37
var selectedFile : FileObject ? {
36
- panels. last? . selectedItem
38
+ nil // panels.last?.selectedItem
37
39
}
38
40
39
41
private func bindPanelModels( ) {
40
- for panel in panels {
42
+ for panel in [ root ] + panels {
41
43
panel. onSelectItem = { [ weak self, weak panel] item in
42
44
guard let self, let panel else { return }
43
45
44
- // self.panels.append(PanelModel(path: self.path.appending))
45
- //
46
- // if let name = item.name, item.id == nil {
47
- // self.panels.replaceSubrange(
48
- // (index + 1)...,
49
- // with: [PanelModel(path: self.path.appending("/\(name)"))]
50
- // )
51
- // }
46
+ self . panels. append ( PanelModel ( path: panel. path. appending ( " / \( item. name!) " ) ) )
52
47
}
53
48
}
54
49
}
@@ -58,35 +53,11 @@ struct AppView: View {
58
53
@Bindable var model : AppModel
59
54
60
55
var body : some View {
61
- VStack ( alignment: . leading, spacing: 0 ) {
62
- breadcrump
63
-
64
- ScrollView ( . horizontal) {
65
- HStack {
66
- ForEach ( model. panels) { panel in
67
- PanelView (
68
- model: panel
69
- // model: PanelModel(path: path[0 ... pathIndex].joined(separator: "/"))
70
- // path: path[0 ... pathIndex].joined(separator: "/"),
71
- // selectedItem: Binding(
72
- // get: {
73
- // selectedItemPerPath[path[pathIndex]]
74
- // },
75
- // set: { newValue in
76
- // selectedItemPerPath[path[pathIndex]] = newValue
77
- //
78
- // if let newValue, let name = newValue.name, newValue.id == nil {
79
- // path.replaceSubrange((pathIndex + 1)..., with: [name])
80
- // } else {
81
- // path.replaceSubrange((pathIndex + 1)..., with: [])
82
- // }
83
- // }
84
- // )
85
- )
86
- . frame ( width: 200 )
87
- }
56
+ NavigationStack ( path: $model. panels) {
57
+ PanelView ( model: model. root)
58
+ . navigationDestination ( for: PanelModel . self) { model in
59
+ PanelView ( model: model)
88
60
}
89
- }
90
61
}
91
62
. overlay ( alignment: . trailing) {
92
63
if let selectedFile = model. selectedFile {
@@ -110,24 +81,8 @@ struct AppView: View {
110
81
. transition ( . move( edge: . trailing) )
111
82
}
112
83
}
113
- . animation ( . default, value: model. path)
114
- . animation ( . default, value: model. selectedFile)
115
- }
116
-
117
- var breadcrump : some View {
118
- HStack {
119
- ForEach ( Array ( zip ( model. pathComponents. indices, model. pathComponents) ) , id: \. 0 ) { idx, path in
120
- Button ( path) {
121
- // self.path.replaceSubrange((idx + 1)..., with: [])
122
- }
123
- . buttonStyle ( . plain)
124
-
125
- // if idx != self.path.indices.last {
126
- // Text(">")
127
- // }
128
- }
129
- }
130
- . padding ( )
84
+ // .animation(.default, value: model.path)
85
+ // .animation(.default, value: model.selectedFile)
131
86
}
132
87
}
133
88
@@ -138,9 +93,17 @@ struct DragValue: Codable {
138
93
139
94
@MainActor
140
95
@Observable
141
- final class PanelModel : Identifiable {
96
+ final class PanelModel : Identifiable , Hashable {
97
+ nonisolated func hash( into hasher: inout Hasher ) {
98
+ hasher. combine ( ObjectIdentifier ( self ) )
99
+ }
100
+
101
+ nonisolated static func == ( lhs: PanelModel , rhs: PanelModel ) -> Bool {
102
+ lhs === rhs
103
+ }
104
+
142
105
let path : String
143
- var selectedItem : FileObject ?
106
+ var selectedItem : FileObject . ID ?
144
107
145
108
var items : [ FileObject ] = [ ]
146
109
@@ -160,15 +123,21 @@ final class PanelModel: Identifiable {
160
123
}
161
124
}
162
125
163
- func didSelectItem ( _ item : FileObject ) {
164
- self . selectedItem = item
126
+ func onPrimaryAction ( _ itemID : FileObject . ID ) {
127
+ guard let item = items . first ( where : { $0 . id == itemID } ) else { return }
165
128
onSelectItem ( item)
166
129
}
167
130
131
+ // func didSelectItem(_ item: FileObject) {
132
+ // self.selectedItem = item
133
+ // onSelectItem(item)
134
+ // }
135
+
168
136
func newFolderButtonTapped( ) async {
169
137
do {
170
138
try await supabase. storage. from ( " main " )
171
139
. upload ( path: " \( path) /Untiltled/.dummy " , file: Data ( ) )
140
+ await load ( )
172
141
} catch {
173
142
174
143
}
@@ -181,6 +150,7 @@ final class PanelModel: Identifiable {
181
150
let file = try Data ( contentsOf: url)
182
151
try await supabase. storage. from ( " main " )
183
152
. upload ( path: " \( self . path) / \( path) " , file: file)
153
+ await load ( )
184
154
} catch { }
185
155
}
186
156
}
@@ -191,33 +161,58 @@ struct PanelView: View {
191
161
@State private var isDraggingOver = false
192
162
193
163
var body : some View {
194
- List {
195
- ForEach ( model. items) { item in
196
- Text ( item. name ?? " " )
197
- . bold ( model. selectedItem == item)
198
- . onTapGesture {
199
- model. didSelectItem ( item)
200
- }
201
- . onDrag {
202
- let data = try ! JSONEncoder ( ) . encode ( DragValue ( path: model. path, object: item) )
203
- let string = String ( decoding: data, as: UTF8 . self)
204
- return NSItemProvider ( object: string as NSString )
205
- }
164
+ Table ( model. items, selection: $model. selectedItem) {
165
+ TableColumn ( " Name " ) { item in
166
+ Text ( item. name ?? " No name " )
206
167
}
207
- . onInsert ( of: [ " public.text " ] ) { index, items in
208
- for item in items {
209
- Task {
210
- guard let data = try await item. loadItem ( forTypeIdentifier: " public.text " ) as? Data ,
211
- let value = try ? JSONDecoder ( ) . decode ( DragValue . self, from: data) else {
212
- return
213
- }
214
168
215
- self . model. items. insert ( value. object, at: index)
216
- }
169
+ TableColumn ( " Date modified " ) { item in
170
+ if let lastModifiedStringValue = item. metadata ? [ " lastModified " ] ? . stringValue,
171
+ let lastModified = try ? Date ( lastModifiedStringValue, strategy: . iso8601. day ( ) . month ( ) . year ( ) . dateTimeSeparator ( . standard) . time ( includingFractionalSeconds: true ) )
172
+ {
173
+ Text ( lastModified. formatted ( date: . abbreviated, time: . shortened) )
174
+ } else {
175
+ Text ( " - " )
176
+ }
177
+ }
178
+
179
+ TableColumn ( " Size " ) { item in
180
+ if let sizeRawValue = item. metadata ? [ " size " ] ? . intValue {
181
+ Text ( sizeRawValue. formatted ( . byteCount( style: . file) ) )
182
+ } else {
183
+ Text ( " - " )
217
184
}
218
- print ( index, items)
219
185
}
220
186
}
187
+ . contextMenu (
188
+ forSelectionType: FileObject . ID. self,
189
+ menu: { items in
190
+ Button ( " New folder " ) {
191
+ Task {
192
+ await model. newFolderButtonTapped ( )
193
+ }
194
+ }
195
+ } ,
196
+ primaryAction: { items in
197
+ guard let item = items. first else { return }
198
+
199
+ model. onPrimaryAction ( item)
200
+ }
201
+ )
202
+ // .onInsert(of: ["public.text"]) { index, items in
203
+ // for item in items {
204
+ // Task {
205
+ // guard let data = try await item.loadItem(forTypeIdentifier: "public.text") as? Data,
206
+ // let value = try? JSONDecoder().decode(DragValue.self, from: data) else {
207
+ // return
208
+ // }
209
+ //
210
+ // self.model.items.insert(value.object, at: index)
211
+ // }
212
+ // }
213
+ // print(index, items)
214
+ // }
215
+ . navigationTitle ( model. path)
221
216
. task {
222
217
await model. load ( )
223
218
}
@@ -240,16 +235,5 @@ struct PanelView: View {
240
235
Color . gray. opacity ( 0.2 )
241
236
}
242
237
}
243
- . contextMenu {
244
- Button ( " New folder " ) {
245
- Task {
246
- await model. newFolderButtonTapped ( )
247
- }
248
- }
249
- }
250
238
}
251
239
}
252
-
253
- #Preview {
254
- AppView ( model: AppModel ( panels: [ ] ) )
255
- }
0 commit comments