8
8
import CustomDump
9
9
import Supabase
10
10
import SwiftUI
11
+ import IdentifiedCollections
12
+
13
+ @MainActor
14
+ @Observable
15
+ final class AppModel {
16
+ var panels : IdentifiedArrayOf < PanelModel > {
17
+ didSet {
18
+ bindPanelModels ( )
19
+ }
20
+ }
11
21
12
- struct AppView : View {
13
- @State var path : [ String ]
14
- @State var selectedItemPerPath : [ String : FileObject ] = [ : ]
22
+ init ( panels: IdentifiedArrayOf < PanelModel > ) {
23
+ self . panels = panels
24
+ bindPanelModels ( )
25
+ }
26
+
27
+ var path : String {
28
+ panels. last? . path ?? " "
29
+ }
30
+
31
+ var pathComponents : [ String ] {
32
+ path. components ( separatedBy: " / " )
33
+ }
34
+
35
+ var selectedFile : FileObject ? {
36
+ panels. last? . selectedItem
37
+ }
38
+
39
+ private func bindPanelModels( ) {
40
+ for panel in panels {
41
+ panel. onSelectItem = { [ weak self, weak panel] item in
42
+ guard let self, let panel else { return }
43
+
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
+ // }
52
+ }
53
+ }
54
+ }
55
+ }
15
56
16
- @State var reload = UUID ( )
57
+ struct AppView : View {
58
+ @Bindable var model : AppModel
17
59
18
60
var body : some View {
19
61
VStack ( alignment: . leading, spacing: 0 ) {
20
62
breadcrump
21
63
22
64
ScrollView ( . horizontal) {
23
65
HStack {
24
- ForEach ( path . indices , id : \ . self ) { pathIndex in
66
+ ForEach ( model . panels ) { panel in
25
67
PanelView (
26
- path: path [ 0 ... pathIndex] . joined ( separator: " / " ) ,
27
- selectedItem: Binding (
28
- get: {
29
- selectedItemPerPath [ path [ pathIndex] ]
30
- } ,
31
- set: { newValue in
32
- selectedItemPerPath [ path [ pathIndex] ] = newValue
33
-
34
- if let newValue, let name = newValue. name, newValue. id == nil {
35
- path. replaceSubrange ( ( pathIndex + 1 ) ... , with: [ name] )
36
- } else {
37
- path. replaceSubrange ( ( pathIndex + 1 ) ... , with: [ ] )
38
- }
39
- }
40
- )
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
+ // )
41
85
)
42
86
. frame ( width: 200 )
43
87
}
44
88
}
45
89
}
46
90
}
47
91
. overlay ( alignment: . trailing) {
48
- if
49
- let lastPath = path. last,
50
- let selectedItem = selectedItemPerPath [ lastPath] ,
51
- selectedItem. id != nil
52
- {
92
+ if let selectedFile = model. selectedFile {
53
93
Form {
54
- Text ( selectedItem . name ?? " " )
94
+ Text ( selectedFile . name ?? " " )
55
95
. font ( . title2)
56
96
Divider ( )
57
97
58
- if let contentLenth = selectedItem . metadata ? [ " contentLength " ] ? . intValue {
98
+ if let contentLenth = selectedFile . metadata ? [ " contentLength " ] ? . intValue {
59
99
LabeledContent ( " Size " , value: " \( contentLenth) " )
60
100
}
61
101
62
- if let mimeType = selectedItem . metadata ? [ " mimetype " ] ? . stringValue {
102
+ if let mimeType = selectedFile . metadata ? [ " mimetype " ] ? . stringValue {
63
103
LabeledContent ( " MIME Type " , value: mimeType)
64
104
}
65
105
}
@@ -70,56 +110,116 @@ struct AppView: View {
70
110
. transition ( . move( edge: . trailing) )
71
111
}
72
112
}
73
- . animation ( . default, value: path)
74
- . animation ( . default, value: selectedItemPerPath )
113
+ . animation ( . default, value: model . path)
114
+ . animation ( . default, value: model . selectedFile )
75
115
}
76
116
77
117
var breadcrump : some View {
78
118
HStack {
79
- ForEach ( Array ( zip ( path . indices, path ) ) , id: \. 0 ) { idx, path in
119
+ ForEach ( Array ( zip ( model . pathComponents . indices, model . pathComponents ) ) , id: \. 0 ) { idx, path in
80
120
Button ( path) {
81
- self . path. replaceSubrange ( ( idx + 1 ) ... , with: [ ] )
121
+ // self.path.replaceSubrange((idx + 1)..., with: [])
82
122
}
83
123
. buttonStyle ( . plain)
84
124
85
- if idx != self . path. indices. last {
86
- Text ( " > " )
87
- }
125
+ // if idx != self.path.indices.last {
126
+ // Text(">")
127
+ // }
88
128
}
89
129
}
90
130
. padding ( )
91
131
}
92
132
}
93
133
134
+ struct DragValue : Codable {
135
+ let path : String
136
+ let object : FileObject
137
+ }
138
+
139
+ @MainActor
140
+ @Observable
141
+ final class PanelModel : Identifiable {
142
+ let path : String
143
+ var selectedItem : FileObject ?
144
+
145
+ var items : [ FileObject ] = [ ]
146
+
147
+ @ObservationIgnored
148
+ var onSelectItem : @MainActor ( FileObject ) -> Void = { _ in }
149
+
150
+ init ( path: String ) {
151
+ self . path = path
152
+ }
153
+
154
+ func load( ) async {
155
+ do {
156
+ let files = try await supabase. storage. from ( " main " ) . list ( path: path)
157
+ items = files. filter { $0. name? . hasPrefix ( " . " ) == false }
158
+ } catch {
159
+ dump ( error)
160
+ }
161
+ }
162
+
163
+ func didSelectItem( _ item: FileObject ) {
164
+ self . selectedItem = item
165
+ onSelectItem ( item)
166
+ }
167
+
168
+ func newFolderButtonTapped( ) async {
169
+ do {
170
+ try await supabase. storage. from ( " main " )
171
+ . upload ( path: " \( path) /Untiltled/.dummy " , file: Data ( ) )
172
+ } catch {
173
+
174
+ }
175
+ }
176
+
177
+ func uploadFile( at url: URL ) async {
178
+ let path = url. lastPathComponent
179
+
180
+ do {
181
+ let file = try Data ( contentsOf: url)
182
+ try await supabase. storage. from ( " main " )
183
+ . upload ( path: " \( self . path) / \( path) " , file: file)
184
+ } catch { }
185
+ }
186
+ }
187
+
94
188
struct PanelView : View {
95
- var path : String
96
- @Binding var selectedItem : FileObject ?
189
+ @Bindable var model : PanelModel
97
190
98
191
@State private var isDraggingOver = false
99
- @State private var items : [ FileObject ] = [ ]
100
-
101
- @State private var reload = UUID ( )
102
192
103
193
var body : some View {
104
194
List {
105
- ForEach ( items) { item in
106
- Button {
107
- selectedItem = item
108
- } label: {
109
- Text ( item. name ?? " " )
110
- . bold ( selectedItem == item)
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
+ }
206
+ }
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
+
215
+ self . model. items. insert ( value. object, at: index)
216
+ }
111
217
}
218
+ print ( index, items)
112
219
}
113
- . buttonStyle ( . plain)
114
220
}
115
- . task ( id: reload) {
116
- do {
117
- let files = try await supabase. storage. from ( " main " ) . list ( path: path)
118
-
119
- items = files. filter { $0. name? . hasPrefix ( " . " ) == false }
120
- } catch {
121
- dump ( error)
122
- }
221
+ . task {
222
+ await model. load ( )
123
223
}
124
224
. onDrop ( of: [ . fileURL] , isTargeted: $isDraggingOver) { providers in
125
225
for provider in providers {
@@ -128,13 +228,8 @@ struct PanelView: View {
128
228
return
129
229
}
130
230
131
- Task { @MainActor in
132
- let path = url. lastPathComponent
133
- let file = try ! Data ( contentsOf: url)
134
- try ! await supabase. storage. from ( " main " )
135
- . upload ( path: " \( self . path) / \( path) " , file: file)
136
-
137
- reload = UUID ( )
231
+ Task {
232
+ await model. uploadFile ( at: url)
138
233
}
139
234
}
140
235
}
@@ -148,23 +243,13 @@ struct PanelView: View {
148
243
. contextMenu {
149
244
Button ( " New folder " ) {
150
245
Task {
151
- try ! await supabase. storage. from ( " main " )
152
- . upload ( path: " \( path) /Untiltled/.dummy " , file: Data ( ) )
153
- reload = UUID ( )
246
+ await model. newFolderButtonTapped ( )
154
247
}
155
248
}
156
249
}
157
250
}
158
251
}
159
252
160
- extension FileObject {
161
- var metadataDump : String {
162
- var output = " "
163
- customDump ( metadata, to: & output)
164
- return output
165
- }
166
- }
167
-
168
253
#Preview {
169
- AppView ( path : [ ] )
254
+ AppView ( model : AppModel ( panels : [ ] ) )
170
255
}
0 commit comments