arcane-os 0.1.0-dev.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. package/src/workspace.mjs +449 -0
@@ -0,0 +1,1715 @@
1
+ <style>
2
+ :host{
3
+ display:block;
4
+ overflow-y:auto;
5
+ height:100%;
6
+ }
7
+
8
+ .file-manager{
9
+ min-height:100%;
10
+ }
11
+
12
+ .directory{
13
+ list-style:none;
14
+ padding:1em;
15
+ margin:0;
16
+ max-height: 1em;
17
+ overflow: hidden;
18
+ transition: max-height 250ms ease-out;
19
+ }
20
+
21
+ .directory.open{
22
+ max-height:none;
23
+ overflow:visible;
24
+ }
25
+
26
+ .file{
27
+ display:flex;
28
+ align-items:flex-start;
29
+ cursor:pointer;
30
+ line-height:1;
31
+ }
32
+
33
+ img.icon{
34
+ filter:var(--file-icon-filter,none);
35
+ transition:filter 180ms,transform 180ms;
36
+ }
37
+
38
+ img.action-icon{
39
+ filter:var(--icon-filter,none);
40
+ }
41
+
42
+ .directory .icon{
43
+ width:2em;
44
+ height:2em;
45
+ margin-right:0.5em;
46
+ }
47
+
48
+ .file span{
49
+ line-height:2.5em;
50
+ }
51
+
52
+ .file-count{
53
+ margin-left:auto;
54
+ opacity:.6;
55
+ }
56
+
57
+ .empty-directory{
58
+ list-style:none;
59
+ opacity:.6;
60
+ padding:.75em 2.5em;
61
+ }
62
+
63
+ :host([data-layout="grid"]) .file-manager{
64
+ display:grid;
65
+ grid-template-columns:repeat(auto-fit,minmax(12em,1fr));
66
+ gap:1em;
67
+ min-height:0;
68
+ }
69
+
70
+ :host([data-layout="grid"]) .directory{
71
+ background:var(--background);
72
+ border:1px solid var(--border-color);
73
+ border-radius:.5em;
74
+ box-shadow:0 .15em .45em var(--shadow-color);
75
+ max-height:none;
76
+ padding:1em;
77
+ }
78
+
79
+ :host([data-layout="grid"]) .directory>.file:not(.folder),
80
+ :host([data-layout="grid"]) .empty-directory{
81
+ display:none;
82
+ }
83
+
84
+ :host([data-layout="grid"]) .folder{
85
+ align-items:center;
86
+ flex-direction:column;
87
+ text-align:center;
88
+ }
89
+
90
+ :host([data-layout="grid"]) .folder .icon{
91
+ height:4em;
92
+ margin:0;
93
+ width:4em;
94
+ }
95
+
96
+ :host([data-layout="grid"]) .folder:hover .icon{
97
+ filter:var(--file-icon-filter,none) drop-shadow(0 .2em .25em var(--shadow-color));
98
+ transform:translateY(-.12em);
99
+ }
100
+
101
+ :host([data-layout="grid"]) .folder .file-count{
102
+ line-height:1.5em;
103
+ margin:0;
104
+ }
105
+
106
+ :host([data-layout="files"]) .directory{
107
+ max-height:none;
108
+ padding:0;
109
+ }
110
+
111
+ :host([data-layout="files"]) .folder{
112
+ display:none;
113
+ }
114
+
115
+ .directory-file-list{
116
+ list-style:none;
117
+ margin:1em 0 0;
118
+ padding:0;
119
+ }
120
+
121
+ .directory-actions,
122
+ .file-view-actions{
123
+ display:flex;
124
+ justify-content:flex-end;
125
+ }
126
+
127
+ .file-view-delete{
128
+ align-items:center;
129
+ display:flex;
130
+ gap:.5em;
131
+ }
132
+
133
+ .file-view-delete img{
134
+ height:1.25em;
135
+ width:1.25em;
136
+ }
137
+
138
+ .file-row,
139
+ .modal .file-row{
140
+ align-items:center;
141
+ background:transparent;
142
+ border:0;
143
+ border-radius:0;
144
+ box-sizing:border-box;
145
+ box-shadow:none;
146
+ color:var(--text-color);
147
+ display:flex;
148
+ line-height:1;
149
+ margin:0;
150
+ overflow-wrap:anywhere;
151
+ padding:0;
152
+ text-align:left;
153
+ }
154
+
155
+ .file-row-open,
156
+ .modal .file-row-open{
157
+ align-items:flex-start;
158
+ background:transparent;
159
+ border:0;
160
+ border-radius:0;
161
+ box-shadow:none;
162
+ color:var(--text-color);
163
+ display:flex;
164
+ flex:1 1 auto;
165
+ gap:.5em;
166
+ margin:0;
167
+ min-width:0;
168
+ padding:0;
169
+ text-align:left;
170
+ }
171
+
172
+ .file-row-open:hover{
173
+ background:var(--secondary-color);
174
+ }
175
+
176
+ .file-delete,
177
+ .modal .file-delete{
178
+ background:transparent;
179
+ border:0;
180
+ box-shadow:none;
181
+ flex:0 0 auto;
182
+ margin:0;
183
+ opacity:.55;
184
+ padding:.35em;
185
+ }
186
+
187
+ .file-delete:hover{
188
+ opacity:1;
189
+ }
190
+
191
+ .file-delete img{
192
+ display:block;
193
+ height:1.25em;
194
+ width:1.25em;
195
+ }
196
+
197
+ :host([data-layout="files"]) .file-row,
198
+ [data-layout="files"] .file-row{
199
+ border-bottom:1px solid var(--border-color);
200
+ padding:.5em;
201
+ width:100%;
202
+ }
203
+
204
+ .file-row .file-icon{
205
+ flex:0 0 2em;
206
+ height:2em;
207
+ width:2em;
208
+ }
209
+
210
+ .file-row .file-details{
211
+ display:flex;
212
+ flex-direction:column;
213
+ min-width:0;
214
+ }
215
+
216
+ .file-row .file-name{
217
+ display:block;
218
+ line-height:2.5em;
219
+ overflow-wrap:anywhere;
220
+ }
221
+
222
+ :host([data-layout="files"]) .file-row .file-name,
223
+ [data-layout="files"] .file-row .file-name{
224
+ line-height:1.4em;
225
+ }
226
+
227
+ .file-row .file-metadata{
228
+ display:block;
229
+ font-size:.8em;
230
+ line-height:1.4em;
231
+ margin-top:.25em;
232
+ opacity:.65;
233
+ }
234
+
235
+ :host([data-layout="tree"]){
236
+ overflow:auto;
237
+ }
238
+
239
+ :host([data-layout="tree"]) .file-manager{
240
+ box-sizing:border-box;
241
+ min-height:0;
242
+ padding:.25em;
243
+ }
244
+
245
+ .tree-root,
246
+ .tree-group{
247
+ list-style:none;
248
+ margin:0;
249
+ padding:0;
250
+ }
251
+
252
+ .tree-group{
253
+ margin-left:.7em;
254
+ padding-left:.7em;
255
+ border-left:1px solid var(--border-color);
256
+ }
257
+
258
+ .tree-item{
259
+ position:relative;
260
+ margin:.08em 0;
261
+ border-radius:.35em;
262
+ outline:none;
263
+ }
264
+
265
+ .tree-item-row{
266
+ box-sizing:border-box;
267
+ min-height:2em;
268
+ display:grid;
269
+ grid-template-columns:.8em 1.35em minmax(0,1fr) auto;
270
+ align-items:center;
271
+ gap:.35em;
272
+ padding:.3em .4em;
273
+ border:1px solid transparent;
274
+ border-radius:inherit;
275
+ color:var(--text-color);
276
+ cursor:pointer;
277
+ }
278
+
279
+ .tree-item:hover>.tree-item-row,
280
+ .tree-item:focus>.tree-item-row{
281
+ border-color:var(--border-color);
282
+ background:var(--secondary-color);
283
+ }
284
+
285
+ .tree-item[aria-selected="true"]>.tree-item-row{
286
+ border-color:var(--focus-color,var(--accent-color));
287
+ background:color-mix(in srgb,var(--secondary-color) 70%,transparent);
288
+ }
289
+
290
+ .tree-item[data-status="needs-review"]>.tree-item-row{
291
+ box-shadow:inset .2em 0 var(--danger-color);
292
+ }
293
+
294
+ .tree-disclosure{
295
+ width:.7em;
296
+ color:var(--platinum,var(--text-color));
297
+ text-align:center;
298
+ transition:transform 120ms ease-out;
299
+ }
300
+
301
+ .tree-item[aria-expanded="true"]>.tree-item-row .tree-disclosure{
302
+ transform:rotate(90deg);
303
+ }
304
+
305
+ .tree-item[data-kind="file"]>.tree-item-row .tree-disclosure{
306
+ visibility:hidden;
307
+ }
308
+
309
+ .tree-entry-icon{
310
+ width:1.25em;
311
+ height:1.25em;
312
+ filter:var(--file-icon-filter,none);
313
+ }
314
+
315
+ .tree-entry-name{
316
+ min-width:0;
317
+ overflow:hidden;
318
+ text-overflow:ellipsis;
319
+ white-space:nowrap;
320
+ }
321
+
322
+ .tree-file-count{
323
+ color:var(--platinum,var(--text-color));
324
+ font-size:.75em;
325
+ white-space:nowrap;
326
+ }
327
+
328
+ .tree-empty,
329
+ .tree-error{
330
+ margin:.75em;
331
+ color:var(--platinum,var(--text-color));
332
+ }
333
+
334
+ .tree-error{
335
+ color:var(--danger-color);
336
+ }
337
+
338
+ </style>
339
+
340
+ <div class="file-manager">
341
+
342
+ </div>
343
+
344
+ <input id="fileUpload" type="file" hidden multiple>
345
+
346
+ <html-import id="directoryModal" class="modal" href="./arcane/components/modal.html?v=13"></html-import>
347
+ <html-import id="fileModal" class="modal" href="./arcane/components/modal.html?v=13"></html-import>
348
+ <html-import id="deleteModal" class="modal" href="./arcane/components/modal.html?v=13"></html-import>
349
+
350
+ <script type="module">
351
+ import('../modules/DBOPFS.js');
352
+ //importing Document at the end of the file to avoid blocking;
353
+
354
+ const host=this;
355
+ const {default:waitForComponent}=await import('../modules/WaitForComponent.js');
356
+
357
+ const fileManager=host.shadowRoot.querySelector('.file-manager');
358
+ const componentStyles=host.shadowRoot.querySelector('style');
359
+ const fileUpload=host.shadowRoot.querySelector('#fileUpload');
360
+ const directoryModal=host.shadowRoot.querySelector('#directoryModal');
361
+ const fileModal=host.shadowRoot.querySelector('#fileModal');
362
+ const deleteModal=host.shadowRoot.querySelector('#deleteModal');
363
+ const layout=host.dataset.layout||'';
364
+ const openMode=host.dataset.openMode||(
365
+ host.hasAttribute('data-event-only-open')?'event':''
366
+ );
367
+ const dirs=host.dataset.dirs?.split(',').map(
368
+ directory=>directory.trim()
369
+ ).filter(Boolean);
370
+ const fileEntityPromise=import('../entities/File.js');
371
+
372
+ let activeFileURL='';
373
+ let uploadDirectory='';
374
+ let initialized=false;
375
+ let provider=null;
376
+ let filter=null;
377
+ let selectedPath='';
378
+ let treeItems=new Map();
379
+ let treeLoadSequence=0;
380
+
381
+ host.ready=false;
382
+ host.loadAll=loadAll;
383
+ host.setProvider=setProvider;
384
+ host.setFilter=setFilter;
385
+ host.select=select;
386
+ host.clearSelection=clearSelection;
387
+
388
+ Object.defineProperty(
389
+ host,
390
+ 'selectedPath',
391
+ {
392
+ configurable:true,
393
+ enumerable:true,
394
+ get:()=>selectedPath
395
+ }
396
+ );
397
+
398
+ Object.defineProperty(
399
+ host,
400
+ 'provider',
401
+ {
402
+ configurable:true,
403
+ enumerable:true,
404
+ get:()=>provider
405
+ }
406
+ );
407
+
408
+ function configuredHiddenPrefixes(){
409
+ if(host.hasAttribute('data-show-hidden')){
410
+ return [];
411
+ }
412
+
413
+ const configured=host.dataset.hiddenPrefixes;
414
+
415
+ if(configured!==undefined){
416
+ return configured.split(',').map(value=>value.trim()).filter(Boolean);
417
+ }
418
+
419
+ return [];
420
+ }
421
+
422
+ function isVisibleEntry(entry={}){
423
+ const normalized=typeof entry==='string'
424
+ ?{name:entry,path:entry,kind:'file'}
425
+ :entry;
426
+ const activeFilter=filter||provider?.filter;
427
+
428
+ if(typeof activeFilter==='function'){
429
+ return activeFilter(normalized)!==false;
430
+ }
431
+
432
+ const name=String(normalized.name||'');
433
+ return !configuredHiddenPrefixes().some(prefix=>name.startsWith(prefix));
434
+ }
435
+
436
+ function isVisibleFileName(fileName=''){
437
+ return isVisibleEntry({name:fileName,path:fileName,kind:'file'});
438
+ }
439
+
440
+ window.addEventListener(
441
+ 'dbopfs-ready',
442
+ init,
443
+ {once:true}
444
+ );
445
+
446
+ if(window.dbopfs?.ready){
447
+ init();
448
+ }
449
+
450
+ fileUpload.addEventListener(
451
+ 'change',
452
+ ()=>uploadFiles().catch(
453
+ error=>console.error('Unable to upload files:',error)
454
+ )
455
+ );
456
+
457
+ fileManager.addEventListener(
458
+ 'click',
459
+ async function(e){
460
+ const folder=e.target.closest('.folder');
461
+
462
+ if(folder){
463
+ const directoryElement=folder.closest('.directory');
464
+ if(!directoryElement) return;
465
+
466
+ if(layout==='grid'){
467
+ openDirectory(directoryElement.dataset.directory).catch(
468
+ error=>console.error('Unable to open directory:',error)
469
+ );
470
+ return;
471
+ }
472
+
473
+ directoryElement.classList.toggle('open');
474
+ return;
475
+ }
476
+
477
+ }
478
+ );
479
+
480
+ fileManager.addEventListener('keydown',onTreeKeydown);
481
+
482
+ function emit(name,detail={},cancelable=false){
483
+ return host.dispatchEvent(
484
+ new CustomEvent(
485
+ name,
486
+ {
487
+ bubbles:true,
488
+ cancelable:cancelable,
489
+ composed:true,
490
+ detail:detail
491
+ }
492
+ )
493
+ );
494
+ }
495
+
496
+ function emitAction(action,entry={},detail={},cancelable=false){
497
+ return emit(
498
+ 'file-manager-action',
499
+ {
500
+ action:action,
501
+ path:entry.path||'',
502
+ entry:entry,
503
+ ...detail
504
+ },
505
+ cancelable
506
+ );
507
+ }
508
+
509
+ async function setProvider(nextProvider=null){
510
+ if(nextProvider!==null&&typeof nextProvider?.list!=='function'){
511
+ throw new TypeError('File manager providers must expose list(path).');
512
+ }
513
+
514
+ provider=nextProvider;
515
+
516
+ if(layout==='tree'){
517
+ if(!initialized){
518
+ await init();
519
+ }else{
520
+ await loadAll();
521
+ }
522
+ }
523
+
524
+ return provider;
525
+ }
526
+
527
+ async function setFilter(nextFilter=null){
528
+ if(nextFilter!==null&&typeof nextFilter!=='function'){
529
+ throw new TypeError('File manager filters must be functions or null.');
530
+ }
531
+
532
+ filter=nextFilter;
533
+
534
+ if(initialized){
535
+ await loadAll();
536
+ }
537
+
538
+ return filter;
539
+ }
540
+
541
+ function normalizeProviderPath(path=''){
542
+ return String(path||'')
543
+ .replaceAll('\\','/')
544
+ .split('/')
545
+ .filter(segment=>segment&&segment!=='.')
546
+ .join('/');
547
+ }
548
+
549
+ function joinProviderPath(parentPath='',name=''){
550
+ return [normalizeProviderPath(parentPath),normalizeProviderPath(name)]
551
+ .filter(Boolean)
552
+ .join('/');
553
+ }
554
+
555
+ function normalizeProviderEntry(candidate={},parentPath=''){
556
+ if(!candidate||typeof candidate!=='object'){
557
+ return null;
558
+ }
559
+
560
+ const kind=candidate.kind==='directory'?'directory':'file';
561
+ const rawPath=normalizeProviderPath(candidate.path||'');
562
+ const name=String(
563
+ candidate.name
564
+ ||rawPath.split('/').pop()
565
+ ||''
566
+ ).trim();
567
+
568
+ if(!name){
569
+ return null;
570
+ }
571
+
572
+ const path=rawPath||joinProviderPath(parentPath,name);
573
+
574
+ if(!path||path===normalizeProviderPath(parentPath)){
575
+ return null;
576
+ }
577
+
578
+ return {
579
+ ...candidate,
580
+ name:name,
581
+ path:path,
582
+ kind:kind,
583
+ size:Number.isFinite(Number(candidate.size))?Number(candidate.size):0,
584
+ mimeType:String(candidate.mimeType||''),
585
+ lastModified:candidate.lastModified||'',
586
+ status:String(candidate.status||''),
587
+ hasChildren:kind==='directory'&&candidate.hasChildren!==false
588
+ };
589
+ }
590
+
591
+ function normalizeProviderEntries(entries=[],parentPath=''){
592
+ if(!Array.isArray(entries)){
593
+ throw new TypeError('File manager provider list(path) must return an array.');
594
+ }
595
+
596
+ const unique=new Map();
597
+
598
+ for(const candidate of entries){
599
+ const entry=normalizeProviderEntry(candidate,parentPath);
600
+
601
+ if(entry&&isVisibleEntry(entry)&&!unique.has(entry.path)){
602
+ unique.set(entry.path,entry);
603
+ }
604
+ }
605
+
606
+ return Array.from(unique.values()).sort((left,right)=>{
607
+ if(left.kind!==right.kind){
608
+ return left.kind==='directory'?-1:1;
609
+ }
610
+
611
+ return left.name.localeCompare(right.name,undefined,{numeric:true,sensitivity:'base'});
612
+ });
613
+ }
614
+
615
+ const dbopfsTreeProvider={
616
+ async list(path=''){
617
+ const normalizedPath=normalizeProviderPath(path);
618
+
619
+ if(!normalizedPath){
620
+ let tableNames=dirs;
621
+
622
+ if(!tableNames?.length){
623
+ tableNames=await dbopfs.getTableNames(true);
624
+ }
625
+
626
+ return tableNames.map(tableName=>({
627
+ name:tableName,
628
+ path:tableName,
629
+ kind:'directory',
630
+ hasChildren:true
631
+ }));
632
+ }
633
+
634
+ const files=(await dbopfs.getAllKeys(normalizedPath))
635
+ .filter(isVisibleFileName)
636
+ .sort();
637
+ const metadata=await getFilesMetadata(normalizedPath,files);
638
+
639
+ return files.map((fileName,index)=>({
640
+ name:fileName,
641
+ path:joinProviderPath(normalizedPath,fileName),
642
+ kind:'file',
643
+ size:metadata[index]?.size,
644
+ mimeType:metadata[index]?.type||metadata[index]?.mimeType||'',
645
+ lastModified:metadata[index]?.lastModified,
646
+ directory:normalizedPath,
647
+ fileName:fileName
648
+ }));
649
+ },
650
+ async open(entry={}){
651
+ if(entry.directory&&entry.fileName){
652
+ return openFile(entry.directory,entry.fileName);
653
+ }
654
+ }
655
+ };
656
+
657
+ function activeTreeProvider(){
658
+ return provider||dbopfsTreeProvider;
659
+ }
660
+
661
+ async function loadTree(){
662
+ const sequence=++treeLoadSequence;
663
+ fileManager.replaceChildren();
664
+ fileManager.setAttribute('role','tree');
665
+ fileManager.setAttribute(
666
+ 'aria-label',
667
+ host.getAttribute('aria-label')||'Files'
668
+ );
669
+ fileManager.setAttribute('aria-busy','true');
670
+ treeItems=new Map();
671
+
672
+ if(!provider&&!window.dbopfs?.ready){
673
+ const waiting=document.createElement('p');
674
+ waiting.className='tree-empty';
675
+ waiting.innerText='Files will appear when storage is ready.';
676
+ fileManager.append(waiting);
677
+ fileManager.setAttribute('aria-busy','false');
678
+ return [];
679
+ }
680
+
681
+ try{
682
+ const entries=normalizeProviderEntries(
683
+ await activeTreeProvider().list(''),
684
+ ''
685
+ );
686
+
687
+ if(sequence!==treeLoadSequence){
688
+ return [];
689
+ }
690
+
691
+ const root=document.createElement('ul');
692
+ root.className='tree-root';
693
+ root.setAttribute('role','group');
694
+ appendTreeEntries(root,entries);
695
+ fileManager.append(root);
696
+
697
+ if(!entries.length){
698
+ const empty=document.createElement('p');
699
+ empty.className='tree-empty';
700
+ empty.innerText=host.dataset.emptyLabel||'No files are available.';
701
+ fileManager.append(empty);
702
+ }
703
+
704
+ const restoredPath=selectedPath;
705
+ const firstItem=treeItems.values().next().value;
706
+
707
+ if(firstItem){
708
+ setRovingTreeItem(firstItem,false);
709
+ }
710
+
711
+ if(restoredPath){
712
+ const restored=await select(
713
+ restoredPath,
714
+ {emit:false,focus:false}
715
+ );
716
+
717
+ if(!restored){
718
+ selectedPath='';
719
+ }
720
+ }
721
+
722
+ return entries;
723
+ }catch(error){
724
+ if(sequence!==treeLoadSequence){
725
+ return [];
726
+ }
727
+
728
+ const message=document.createElement('p');
729
+ message.className='tree-error';
730
+ message.innerText='Unable to list files.';
731
+ fileManager.append(message);
732
+ emitAction('error',{}, {error:error});
733
+ throw error;
734
+ }finally{
735
+ if(sequence===treeLoadSequence){
736
+ fileManager.setAttribute('aria-busy','false');
737
+ }
738
+ }
739
+ }
740
+
741
+ function appendTreeEntries(group,entries=[]){
742
+ const size=entries.length;
743
+
744
+ entries.forEach((entry,index)=>{
745
+ group.append(createTreeItem(entry,index+1,size));
746
+ });
747
+ }
748
+
749
+ function createTreeItem(entry,position=1,setSize=1){
750
+ const item=document.createElement('li');
751
+ const row=document.createElement('div');
752
+ const disclosure=document.createElement('span');
753
+ const icon=document.createElement('img');
754
+ const name=document.createElement('span');
755
+ const count=document.createElement('span');
756
+
757
+ item.className='tree-item';
758
+ item.dataset.kind=entry.kind;
759
+ item.dataset.path=entry.path;
760
+ item.dataset.status=entry.status||'';
761
+ item.tabIndex=-1;
762
+ item.setAttribute('role','treeitem');
763
+ item.setAttribute('aria-selected',String(entry.path===selectedPath));
764
+ item.setAttribute('aria-posinset',String(position));
765
+ item.setAttribute('aria-setsize',String(setSize));
766
+ item._fileManagerEntry=entry;
767
+
768
+ row.className='tree-item-row';
769
+ row.title=entry.path;
770
+ disclosure.className='tree-disclosure';
771
+ disclosure.setAttribute('aria-hidden','true');
772
+ disclosure.innerText='>';
773
+ icon.className='tree-entry-icon';
774
+ icon.alt='';
775
+ icon.src=entry.kind==='directory'
776
+ ?'./arcane/img/folder.svg'
777
+ :'./arcane/img/doc.svg';
778
+ name.className='tree-entry-name';
779
+ name.innerText=entry.name;
780
+ count.className='tree-file-count';
781
+
782
+ if(Number.isFinite(Number(entry.fileCount))){
783
+ setFileCountLabel(count,Number(entry.fileCount),false);
784
+ item.dataset.fileCount=String(Number(entry.fileCount));
785
+ }
786
+
787
+ row.append(disclosure,icon,name,count);
788
+ item.append(row);
789
+ treeItems.set(entry.path,item);
790
+
791
+ if(entry.kind==='directory'&&entry.hasChildren){
792
+ const group=document.createElement('ul');
793
+ group.className='tree-group';
794
+ group.hidden=true;
795
+ group.setAttribute('role','group');
796
+ item.setAttribute('aria-expanded','false');
797
+ item.append(group);
798
+ }else if(entry.kind==='directory'){
799
+ setFileCountLabel(count,0,false);
800
+ item.dataset.fileCount='0';
801
+ }
802
+
803
+ row.addEventListener('click',async()=>{
804
+ await select(entry.path,{entry:entry,focus:true});
805
+
806
+ if(entry.kind==='directory'){
807
+ await toggleTreeItem(item);
808
+ }
809
+ });
810
+ row.addEventListener('dblclick',async event=>{
811
+ if(entry.kind==='file'){
812
+ event.preventDefault();
813
+ await openTreeItem(item,'double-click');
814
+ }
815
+ });
816
+ item.addEventListener('focus',()=>setRovingTreeItem(item,false));
817
+
818
+ return item;
819
+ }
820
+
821
+ function setFileCountLabel(element,count=0,partial=false){
822
+ const suffix=partial?'+':'';
823
+ element.innerText=`${count}${suffix} ${count===1&&!partial?'file':'files'}`;
824
+ }
825
+
826
+ function updateTreeCounts(item){
827
+ let current=item;
828
+
829
+ while(current){
830
+ if(current.dataset.kind==='directory'){
831
+ const exact=Number(current._fileManagerEntry?.fileCount);
832
+ const countElement=current.querySelector(':scope>.tree-item-row>.tree-file-count');
833
+
834
+ if(Number.isFinite(exact)){
835
+ current.dataset.fileCount=String(exact);
836
+ setFileCountLabel(countElement,exact,false);
837
+ }else if(current.dataset.loaded==='true'){
838
+ const group=current.querySelector(':scope>.tree-group');
839
+ const count=group
840
+ ?group.querySelectorAll('.tree-item[data-kind="file"]').length
841
+ :0;
842
+ const partial=Boolean(
843
+ group&&Array.from(
844
+ group.querySelectorAll('.tree-item[data-kind="directory"]')
845
+ ).some(directory=>
846
+ directory._fileManagerEntry?.hasChildren
847
+ &&directory.dataset.loaded!=='true'
848
+ )
849
+ );
850
+ current.dataset.fileCount=String(count);
851
+ setFileCountLabel(countElement,count,partial);
852
+ }
853
+ }
854
+
855
+ current=current.parentElement?.closest('.tree-item')||null;
856
+ }
857
+ }
858
+
859
+ async function expandTreeItem(item,{emitEvent=true}={}){
860
+ if(!item||item.dataset.kind!=='directory'){
861
+ return false;
862
+ }
863
+
864
+ const group=item.querySelector(':scope>.tree-group');
865
+
866
+ if(!group){
867
+ return false;
868
+ }
869
+
870
+ if(item.dataset.loaded!=='true'){
871
+ const entry=item._fileManagerEntry;
872
+ const sequence=treeLoadSequence;
873
+ item.setAttribute('aria-busy','true');
874
+
875
+ try{
876
+ const entries=normalizeProviderEntries(
877
+ await activeTreeProvider().list(entry.path),
878
+ entry.path
879
+ );
880
+
881
+ if(sequence!==treeLoadSequence||!fileManager.contains(item)){
882
+ return false;
883
+ }
884
+
885
+ group.replaceChildren();
886
+ appendTreeEntries(group,entries);
887
+
888
+ if(!entries.length){
889
+ const empty=document.createElement('li');
890
+ empty.className='tree-empty';
891
+ empty.setAttribute('role','none');
892
+ empty.innerText='Empty';
893
+ group.append(empty);
894
+ }
895
+
896
+ item.dataset.loaded='true';
897
+ updateTreeCounts(item);
898
+ }catch(error){
899
+ group.replaceChildren();
900
+ const message=document.createElement('li');
901
+ message.className='tree-error';
902
+ message.setAttribute('role','none');
903
+ message.innerText='Unable to list this folder.';
904
+ group.append(message);
905
+ emitAction('error',entry,{error:error});
906
+ return false;
907
+ }finally{
908
+ item.removeAttribute('aria-busy');
909
+ }
910
+ }
911
+
912
+ group.hidden=false;
913
+ item.setAttribute('aria-expanded','true');
914
+
915
+ if(emitEvent){
916
+ emitAction('expand',item._fileManagerEntry);
917
+ }
918
+
919
+ return true;
920
+ }
921
+
922
+ function collapseTreeItem(item,{emitEvent=true}={}){
923
+ const group=item?.querySelector(':scope>.tree-group');
924
+
925
+ if(!group){
926
+ return false;
927
+ }
928
+
929
+ group.hidden=true;
930
+ item.setAttribute('aria-expanded','false');
931
+
932
+ if(emitEvent){
933
+ emitAction('collapse',item._fileManagerEntry);
934
+ }
935
+
936
+ return true;
937
+ }
938
+
939
+ async function toggleTreeItem(item){
940
+ if(item?.getAttribute('aria-expanded')==='true'){
941
+ return collapseTreeItem(item);
942
+ }
943
+
944
+ return expandTreeItem(item);
945
+ }
946
+
947
+ function setRovingTreeItem(item,focus=true){
948
+ if(!item){
949
+ return;
950
+ }
951
+
952
+ for(const treeItem of treeItems.values()){
953
+ treeItem.tabIndex=treeItem===item?0:-1;
954
+ }
955
+
956
+ if(focus){
957
+ item.focus();
958
+ }
959
+ }
960
+
961
+ function visibleTreeItems(){
962
+ return Array.from(
963
+ fileManager.querySelectorAll('.tree-item[role="treeitem"]')
964
+ ).filter(
965
+ item=>!item.closest('.tree-group[hidden]')
966
+ );
967
+ }
968
+
969
+ async function revealTreePath(path=''){
970
+ const normalizedPath=normalizeProviderPath(path);
971
+ let item=treeItems.get(normalizedPath)||null;
972
+
973
+ if(item){
974
+ return item;
975
+ }
976
+
977
+ const segments=normalizedPath.split('/').filter(Boolean);
978
+
979
+ for(let i=1;i<segments.length;i++){
980
+ const parentPath=segments.slice(0,i).join('/');
981
+ const parent=treeItems.get(parentPath);
982
+
983
+ if(parent?.dataset.kind==='directory'){
984
+ await expandTreeItem(parent,{emitEvent:false});
985
+ item=treeItems.get(normalizedPath)||null;
986
+
987
+ if(item){
988
+ return item;
989
+ }
990
+ }
991
+ }
992
+
993
+ return null;
994
+ }
995
+
996
+ async function select(path='',options={}){
997
+ const normalizedPath=normalizeProviderPath(path);
998
+
999
+ if(!normalizedPath){
1000
+ return clearSelection(options);
1001
+ }
1002
+
1003
+ let item=null;
1004
+ let entry=options.entry||null;
1005
+
1006
+ if(layout==='tree'){
1007
+ item=await revealTreePath(normalizedPath);
1008
+
1009
+ if(!item){
1010
+ return false;
1011
+ }
1012
+
1013
+ entry=item._fileManagerEntry;
1014
+ }
1015
+
1016
+ selectedPath=normalizedPath;
1017
+
1018
+ if(layout==='tree'){
1019
+ for(const treeItem of treeItems.values()){
1020
+ treeItem.setAttribute(
1021
+ 'aria-selected',
1022
+ String(treeItem===item)
1023
+ );
1024
+ }
1025
+ setRovingTreeItem(item,options.focus===true);
1026
+ }else{
1027
+ fileManager.querySelectorAll('[data-file-path]').forEach(element=>{
1028
+ element.setAttribute(
1029
+ 'aria-selected',
1030
+ String(element.dataset.filePath===selectedPath)
1031
+ );
1032
+ });
1033
+ }
1034
+
1035
+ if(options.emit!==false){
1036
+ emit('file-manager-select',{path:selectedPath,entry:entry});
1037
+ }
1038
+
1039
+ return true;
1040
+ }
1041
+
1042
+ function clearSelection(options={}){
1043
+ const changed=Boolean(selectedPath);
1044
+ selectedPath='';
1045
+
1046
+ if(layout==='tree'){
1047
+ for(const treeItem of treeItems.values()){
1048
+ treeItem.setAttribute('aria-selected','false');
1049
+ }
1050
+ }else{
1051
+ fileManager.querySelectorAll('[data-file-path]').forEach(element=>{
1052
+ element.setAttribute('aria-selected','false');
1053
+ });
1054
+ }
1055
+
1056
+ if(options.emit===true){
1057
+ emit('file-manager-select',{path:'',entry:null});
1058
+ }
1059
+
1060
+ return changed;
1061
+ }
1062
+
1063
+ async function openTreeItem(item,trigger='open'){
1064
+ const entry=item?._fileManagerEntry;
1065
+
1066
+ if(!entry||entry.kind!=='file'){
1067
+ return false;
1068
+ }
1069
+
1070
+ await select(entry.path,{entry:entry,focus:true});
1071
+ const detail={path:entry.path,entry:entry,trigger:trigger};
1072
+ const openAllowed=emit('file-manager-open',detail,true);
1073
+ const actionAllowed=emitAction('open',entry,{trigger:trigger},true);
1074
+
1075
+ if(!openAllowed||!actionAllowed||openMode==='event'){
1076
+ return true;
1077
+ }
1078
+
1079
+ if(typeof activeTreeProvider().open==='function'){
1080
+ try{
1081
+ await activeTreeProvider().open(entry);
1082
+ }catch(error){
1083
+ emitAction('error',entry,{error:error});
1084
+ console.error('Unable to open file:',error);
1085
+ return false;
1086
+ }
1087
+ }
1088
+
1089
+ return true;
1090
+ }
1091
+
1092
+ async function onTreeKeydown(event){
1093
+ if(layout!=='tree'){
1094
+ return;
1095
+ }
1096
+
1097
+ const item=event.target.closest?.('.tree-item[role="treeitem"]');
1098
+
1099
+ if(!item||!fileManager.contains(item)){
1100
+ return;
1101
+ }
1102
+
1103
+ const items=visibleTreeItems();
1104
+ const index=items.indexOf(item);
1105
+ const focusAt=nextIndex=>{
1106
+ const next=items[nextIndex];
1107
+ if(next){
1108
+ setRovingTreeItem(next,true);
1109
+ }
1110
+ };
1111
+
1112
+ switch(event.key){
1113
+ case 'ArrowDown':
1114
+ event.preventDefault();
1115
+ focusAt(Math.min(items.length-1,index+1));
1116
+ break;
1117
+ case 'ArrowUp':
1118
+ event.preventDefault();
1119
+ focusAt(Math.max(0,index-1));
1120
+ break;
1121
+ case 'Home':
1122
+ event.preventDefault();
1123
+ focusAt(0);
1124
+ break;
1125
+ case 'End':
1126
+ event.preventDefault();
1127
+ focusAt(items.length-1);
1128
+ break;
1129
+ case 'ArrowRight':
1130
+ if(item.dataset.kind!=='directory'){
1131
+ break;
1132
+ }
1133
+ event.preventDefault();
1134
+ if(item.getAttribute('aria-expanded')!=='true'){
1135
+ await expandTreeItem(item);
1136
+ }else{
1137
+ const firstChild=item.querySelector(':scope>.tree-group>.tree-item');
1138
+ if(firstChild){
1139
+ setRovingTreeItem(firstChild,true);
1140
+ }
1141
+ }
1142
+ break;
1143
+ case 'ArrowLeft':{
1144
+ event.preventDefault();
1145
+ if(item.dataset.kind==='directory'&&item.getAttribute('aria-expanded')==='true'){
1146
+ collapseTreeItem(item);
1147
+ }else{
1148
+ const parent=item.parentElement?.closest('.tree-item');
1149
+ if(parent){
1150
+ setRovingTreeItem(parent,true);
1151
+ }
1152
+ }
1153
+ break;
1154
+ }
1155
+ case 'Enter':
1156
+ event.preventDefault();
1157
+ if(item.dataset.kind==='directory'){
1158
+ await toggleTreeItem(item);
1159
+ await select(item.dataset.path,{entry:item._fileManagerEntry});
1160
+ }else{
1161
+ await openTreeItem(item,'keyboard');
1162
+ }
1163
+ break;
1164
+ case ' ':
1165
+ event.preventDefault();
1166
+ await select(item.dataset.path,{entry:item._fileManagerEntry});
1167
+ break;
1168
+ }
1169
+ }
1170
+
1171
+ async function openDirectory(directory=''){
1172
+ const modal=await waitForComponent(
1173
+ directoryModal,
1174
+ {
1175
+ methods:['populate','open'],
1176
+ property:'ready',
1177
+ event:'modal-ready'
1178
+ }
1179
+ );
1180
+ const files=(await dbopfs.getAllKeys(directory))
1181
+ .filter(isVisibleFileName)
1182
+ .sort();
1183
+ const metadata=await getFilesMetadata(directory,files);
1184
+ const content=document.createElement('section');
1185
+ const styles=componentStyles.cloneNode(true);
1186
+ const heading=document.createElement('h1');
1187
+ const actions=document.createElement('section');
1188
+ const uploadButton=document.createElement('button');
1189
+ const list=document.createElement('ul');
1190
+
1191
+ heading.innerText=directory;
1192
+ actions.className='directory-actions';
1193
+ uploadButton.type='button';
1194
+ uploadButton.dataset.directory=directory;
1195
+ uploadButton.innerText='Upload Files';
1196
+ uploadButton.addEventListener(
1197
+ 'click',
1198
+ ()=>selectFiles(directory)
1199
+ );
1200
+ list.className='directory-file-list';
1201
+ list.dataset.layout='files';
1202
+
1203
+ actions.append(uploadButton);
1204
+ content.append(styles,heading,actions);
1205
+
1206
+ if(!files.length){
1207
+ const empty=document.createElement('p');
1208
+ empty.innerText='This directory is empty.';
1209
+ content.append(empty);
1210
+ }else{
1211
+ for(let i=0;i<files.length;i++){
1212
+ list.append(
1213
+ createFileRow(
1214
+ directory,
1215
+ files[i],
1216
+ metadata[i]
1217
+ )
1218
+ );
1219
+ }
1220
+
1221
+ content.append(list);
1222
+ }
1223
+
1224
+ await modal.populate(content,false);
1225
+ modal.open();
1226
+ }
1227
+
1228
+ async function openFile(directory='',fileName=''){
1229
+ const modal=await waitForComponent(
1230
+ fileModal,
1231
+ {
1232
+ methods:['populate','open'],
1233
+ property:'ready',
1234
+ event:'modal-ready'
1235
+ }
1236
+ );
1237
+ const content=document.createElement('section');
1238
+ const styles=componentStyles.cloneNode(true);
1239
+ const heading=document.createElement('h1');
1240
+ const actions=document.createElement('section');
1241
+ const deleteButton=document.createElement('button');
1242
+ const deleteIcon=document.createElement('img');
1243
+ const status=document.createElement('p');
1244
+
1245
+ heading.innerText=`${directory}/${fileName}`;
1246
+ actions.className='file-view-actions';
1247
+ deleteButton.type='button';
1248
+ deleteButton.className='file-view-delete';
1249
+ deleteButton.innerText='Delete File';
1250
+ deleteIcon.alt='';
1251
+ deleteIcon.className='action-icon';
1252
+ deleteIcon.src='./arcane/img/trash.svg';
1253
+ deleteButton.prepend(deleteIcon);
1254
+ deleteButton.addEventListener(
1255
+ 'click',
1256
+ ()=>confirmDelete(directory,fileName,true).catch(
1257
+ error=>console.error('Unable to delete file:',error)
1258
+ )
1259
+ );
1260
+ status.innerText='Opening file...';
1261
+ actions.append(deleteButton);
1262
+ content.append(
1263
+ styles,
1264
+ heading,
1265
+ actions,
1266
+ document.createElement('hr'),
1267
+ status
1268
+ );
1269
+
1270
+ modal.populate(content,false);
1271
+ modal.open();
1272
+
1273
+ try{
1274
+ const {default:FileEntity}=await fileEntityPromise;
1275
+ const file=new FileEntity(fileName,directory);
1276
+ const fileRef=await file.open();
1277
+
1278
+ status.remove();
1279
+
1280
+ if(fileRef.size<1){
1281
+ const empty=document.createElement('h2');
1282
+
1283
+ if(!layout){
1284
+ await dbopfs.delete(directory,fileName);
1285
+ await loadAll();
1286
+ empty.innerText='File was empty and has been deleted';
1287
+ }else{
1288
+ empty.innerText='File is empty';
1289
+ }
1290
+
1291
+ content.append(empty);
1292
+ }else{
1293
+ content.append(await renderFile(fileRef,{directory,fileName}));
1294
+ }
1295
+ }catch(error){
1296
+ status.innerText='Unable to open this file.';
1297
+ console.error('Unable to open file:',error);
1298
+ }
1299
+ }
1300
+
1301
+ async function renderFile(fileRef,context={}){
1302
+ if(activeFileURL){
1303
+ URL.revokeObjectURL(activeFileURL);
1304
+ activeFileURL='';
1305
+ }
1306
+
1307
+ if(['png','jpg','jpeg','webp','svg'].includes(fileRef.ext)){
1308
+ const image=document.createElement('img');
1309
+
1310
+ activeFileURL=URL.createObjectURL(
1311
+ new Blob([fileRef],{type:fileRef.mime})
1312
+ );
1313
+ image.alt=fileRef.name;
1314
+ image.src=activeFileURL;
1315
+ image.style.maxWidth='100%';
1316
+ return image;
1317
+ }
1318
+
1319
+ if(fileRef.ext==='pdf'){
1320
+ const frame=document.createElement('iframe');
1321
+
1322
+ activeFileURL=URL.createObjectURL(
1323
+ new Blob([fileRef],{type:fileRef.mime})
1324
+ );
1325
+ frame.src=activeFileURL;
1326
+ frame.style.height='60vh';
1327
+ frame.style.width='100%';
1328
+ frame.title=fileRef.name;
1329
+ return frame;
1330
+ }
1331
+
1332
+ if(!layout&&['html','md'].includes(fileRef.ext)){
1333
+ const rendered=document.createElement('div');
1334
+
1335
+ rendered.innerHTML=fileRef.ext==='md'
1336
+ ?fileRef.parsed
1337
+ :await fileRef.text();
1338
+ return rendered;
1339
+ }
1340
+
1341
+ const rendered=document.createElement('div');
1342
+ let preview=fileRef.parsed;
1343
+
1344
+ if(
1345
+ ['json','jsonl','ndjson'].includes(fileRef.ext)
1346
+ &&typeof host.previewTransform==='function'
1347
+ ){
1348
+ preview=await host.previewTransform(
1349
+ preview,
1350
+ Object.freeze(
1351
+ {
1352
+ directory:String(context.directory||''),
1353
+ extension:String(fileRef.ext||''),
1354
+ fileName:String(context.fileName||fileRef.name||'')
1355
+ }
1356
+ )
1357
+ );
1358
+ }
1359
+
1360
+ switch(fileRef.ext){
1361
+ case 'json':
1362
+ rendered.append(
1363
+ createPreformatted(
1364
+ JSON.stringify(preview,null,4)
1365
+ )
1366
+ );
1367
+ break;
1368
+ case 'jsonl':
1369
+ case 'ndjson':{
1370
+ const records=Array.isArray(preview)?preview:[];
1371
+
1372
+ for(let i=0;i<records.length;i++){
1373
+ rendered.append(
1374
+ createPreformatted(
1375
+ JSON.stringify(records[i],null,4)
1376
+ )
1377
+ );
1378
+
1379
+ if(i<records.length-1){
1380
+ rendered.append(document.createElement('hr'));
1381
+ }
1382
+ }
1383
+ break;
1384
+ }
1385
+ default:
1386
+ rendered.append(
1387
+ createPreformatted(await fileRef.text())
1388
+ );
1389
+ }
1390
+
1391
+ return rendered;
1392
+ }
1393
+
1394
+ function createPreformatted(content=''){
1395
+ const pre=document.createElement('pre');
1396
+ pre.innerText=content;
1397
+ return pre;
1398
+ }
1399
+
1400
+ async function getFilesMetadata(directory='',files=[]){
1401
+ return Promise.all(
1402
+ files.map(
1403
+ fileName=>dbopfs.getFileMetadata(
1404
+ directory,
1405
+ fileName
1406
+ ).catch(()=>({}))
1407
+ )
1408
+ );
1409
+ }
1410
+
1411
+ function formatLastModified(metadata={}){
1412
+ if(!metadata.lastModified){
1413
+ return '';
1414
+ }
1415
+
1416
+ return `Updated: ${new Date(metadata.lastModified).toLocaleString()}`;
1417
+ }
1418
+
1419
+ function createFileRow(directory='',fileName='',metadata={}){
1420
+ const file=document.createElement('li');
1421
+ const openButton=document.createElement('button');
1422
+ const fileIcon=document.createElement('img');
1423
+ const fileDetails=document.createElement('span');
1424
+ const fileLabel=document.createElement('span');
1425
+ const fileMetadata=document.createElement('small');
1426
+ const deleteButton=document.createElement('button');
1427
+ const deleteIcon=document.createElement('img');
1428
+ const entry={
1429
+ name:fileName,
1430
+ path:joinProviderPath(directory,fileName),
1431
+ kind:'file',
1432
+ size:Number(metadata.size)||0,
1433
+ mimeType:String(metadata.type||metadata.mimeType||''),
1434
+ lastModified:metadata.lastModified||'',
1435
+ directory:directory,
1436
+ fileName:fileName
1437
+ };
1438
+
1439
+ file.className='file file-row';
1440
+
1441
+ file.title=fileName;
1442
+ openButton.type='button';
1443
+ openButton.className='file-row-open';
1444
+ openButton.dataset.fileAction='open';
1445
+ openButton.dataset.directory=directory;
1446
+ openButton.dataset.file=fileName;
1447
+ openButton.dataset.filePath=entry.path;
1448
+ openButton.setAttribute('aria-selected',String(entry.path===selectedPath));
1449
+
1450
+ fileIcon.alt='';
1451
+ fileIcon.className='icon file-icon';
1452
+ fileIcon.src='./arcane/img/doc.svg';
1453
+ fileDetails.className='file-details';
1454
+ fileLabel.className='file-name';
1455
+ fileLabel.innerText=fileName;
1456
+ fileMetadata.className='file-metadata';
1457
+ fileMetadata.innerText=formatLastModified(metadata);
1458
+
1459
+ fileDetails.append(fileLabel);
1460
+
1461
+ if(fileMetadata.innerText){
1462
+ fileDetails.append(fileMetadata);
1463
+ }
1464
+
1465
+ deleteButton.type='button';
1466
+ deleteButton.className='file-delete';
1467
+ deleteButton.dataset.fileAction='delete';
1468
+ deleteButton.dataset.directory=directory;
1469
+ deleteButton.dataset.file=fileName;
1470
+ deleteButton.title=`Delete ${fileName}`;
1471
+ deleteButton.setAttribute('aria-label',`Delete ${fileName}`);
1472
+
1473
+ deleteIcon.alt='';
1474
+ deleteIcon.className='action-icon';
1475
+ deleteIcon.src='./arcane/img/trash.svg';
1476
+
1477
+ openButton.addEventListener(
1478
+ 'click',
1479
+ async()=>{
1480
+ await select(entry.path,{entry:entry});
1481
+ const detail={path:entry.path,entry:entry,trigger:'click'};
1482
+ const openAllowed=emit('file-manager-open',detail,true);
1483
+ const actionAllowed=emitAction('open',entry,{trigger:'click'},true);
1484
+
1485
+ if(!openAllowed||!actionAllowed||openMode==='event'){
1486
+ return;
1487
+ }
1488
+
1489
+ openFile(directory,fileName).catch(
1490
+ error=>console.error('Unable to open file:',error)
1491
+ );
1492
+ }
1493
+ );
1494
+ deleteButton.addEventListener(
1495
+ 'click',
1496
+ ()=>{
1497
+ if(!emitAction('delete',entry,{},true)){
1498
+ return;
1499
+ }
1500
+
1501
+ confirmDelete(directory,fileName).catch(
1502
+ error=>console.error('Unable to delete file:',error)
1503
+ );
1504
+ }
1505
+ );
1506
+
1507
+ openButton.append(fileIcon,fileDetails);
1508
+ deleteButton.append(deleteIcon);
1509
+ file.append(openButton,deleteButton);
1510
+
1511
+ return file;
1512
+ }
1513
+
1514
+ async function confirmDelete(directory='',fileName='',closeFile=false){
1515
+ if(!directory||!fileName){
1516
+ return false;
1517
+ }
1518
+
1519
+ const modal=await waitForComponent(
1520
+ deleteModal,
1521
+ {
1522
+ methods:['populate','open','close'],
1523
+ property:'ready',
1524
+ event:'modal-ready'
1525
+ }
1526
+ );
1527
+ const content=document.createElement('section');
1528
+ const heading=document.createElement('h1');
1529
+ const message=document.createElement('p');
1530
+ const cancelButton=document.createElement('button');
1531
+ const deleteButton=document.createElement('button');
1532
+
1533
+ heading.innerText='Delete File';
1534
+ message.innerText=`Delete ${fileName}? This cannot be undone.`;
1535
+ cancelButton.type='button';
1536
+ cancelButton.innerText='Cancel';
1537
+ deleteButton.type='button';
1538
+ deleteButton.innerText='Delete';
1539
+
1540
+ cancelButton.addEventListener(
1541
+ 'click',
1542
+ ()=>modal.close(undefined,true)
1543
+ );
1544
+ deleteButton.addEventListener(
1545
+ 'click',
1546
+ async function deleteSelectedFile(){
1547
+ cancelButton.disabled=true;
1548
+ deleteButton.disabled=true;
1549
+
1550
+ try{
1551
+ await dbopfs.delete(directory,fileName);
1552
+ await modal.close(undefined,true);
1553
+
1554
+ if(closeFile&&typeof fileModal.close==='function'){
1555
+ await fileModal.close(undefined,true);
1556
+ }
1557
+
1558
+ await loadAll();
1559
+
1560
+ if(layout==='grid'){
1561
+ await openDirectory(directory);
1562
+ }
1563
+ }catch(error){
1564
+ message.innerText=`Unable to delete ${fileName}.`;
1565
+ cancelButton.disabled=false;
1566
+ deleteButton.disabled=false;
1567
+ console.error('Unable to delete file:',error);
1568
+ }
1569
+ }
1570
+ );
1571
+
1572
+ content.append(
1573
+ heading,
1574
+ message,
1575
+ cancelButton,
1576
+ deleteButton
1577
+ );
1578
+
1579
+ await modal.populate(content,false);
1580
+ modal.open();
1581
+
1582
+ return true;
1583
+ }
1584
+
1585
+ function selectFiles(directory=''){
1586
+ if(!directory){
1587
+ return;
1588
+ }
1589
+
1590
+ uploadDirectory=directory;
1591
+ fileUpload.click();
1592
+ }
1593
+
1594
+ async function uploadFiles(){
1595
+ const files=Array.from(fileUpload.files||[]);
1596
+ const directory=uploadDirectory;
1597
+ fileUpload.value='';
1598
+
1599
+ if(!files.length||!directory){
1600
+ return;
1601
+ }
1602
+
1603
+ const {default:FileEntity}=await fileEntityPromise;
1604
+
1605
+ const results=await Promise.allSettled(
1606
+ files.map(
1607
+ function saveUploadedFile(file){
1608
+ return new FileEntity(
1609
+ file.name,
1610
+ directory
1611
+ ).save(file);
1612
+ }
1613
+ )
1614
+ );
1615
+
1616
+ await loadAll();
1617
+
1618
+ if(layout==='grid'){
1619
+ await openDirectory(directory);
1620
+ }
1621
+
1622
+ const failed=results.find(
1623
+ result=>result.status==='rejected'
1624
+ );
1625
+
1626
+ if(failed){
1627
+ throw failed.reason;
1628
+ }
1629
+ }
1630
+
1631
+ async function init(){
1632
+ if(initialized){
1633
+ return;
1634
+ }
1635
+
1636
+ initialized=true;
1637
+
1638
+ try{
1639
+ if(layout!=='tree'){
1640
+ await fileEntityPromise;
1641
+ }
1642
+ await loadAll();
1643
+ host.ready=true;
1644
+ host.dispatchEvent(
1645
+ new CustomEvent('file-manager-ready')
1646
+ );
1647
+ }catch(error){
1648
+ initialized=false;
1649
+ console.error('Unable to initialize file manager:',error);
1650
+ }
1651
+ }
1652
+
1653
+ async function loadAll(){
1654
+ if(layout==='tree'){
1655
+ return loadTree();
1656
+ }
1657
+
1658
+ let tableNames=dirs;
1659
+
1660
+ if(!tableNames?.length){
1661
+ tableNames=await dbopfs.getTableNames(Boolean(layout));
1662
+ }
1663
+
1664
+ tableNames.sort();
1665
+ fileManager.replaceChildren();
1666
+
1667
+ for(let i=0;i<tableNames.length;i++){
1668
+ const tableName=tableNames[i];
1669
+ const files=(await dbopfs.getAllKeys(tableName))
1670
+ .filter(isVisibleFileName)
1671
+ .sort();
1672
+ const metadata=layout==='files'
1673
+ ?await getFilesMetadata(tableName,files)
1674
+ :[];
1675
+ const directory=document.createElement('ul');
1676
+ const folder=document.createElement('li');
1677
+ const folderIcon=document.createElement('img');
1678
+ const folderName=document.createElement('span');
1679
+ const fileCount=document.createElement('span');
1680
+
1681
+ directory.className='directory';
1682
+ directory.dataset.directory=tableName;
1683
+ folder.className='file folder';
1684
+ folderIcon.className='icon';
1685
+ folderIcon.src='./arcane/img/folder.svg';
1686
+ folderName.innerText=tableName;
1687
+ fileCount.className='file-count';
1688
+ fileCount.innerText=`${files.length} ${files.length===1?'file':'files'}`;
1689
+
1690
+ folder.append(folderIcon,folderName,fileCount);
1691
+ directory.append(folder);
1692
+
1693
+ for(let j=0;j<files.length;j++){
1694
+ const fileName=files[j];
1695
+
1696
+ directory.append(
1697
+ createFileRow(
1698
+ tableName,
1699
+ fileName,
1700
+ metadata[j]
1701
+ )
1702
+ );
1703
+ }
1704
+
1705
+ if(!files.length){
1706
+ const empty=document.createElement('li');
1707
+ empty.className='empty-directory';
1708
+ empty.innerText='Empty';
1709
+ directory.append(empty);
1710
+ }
1711
+
1712
+ fileManager.append(directory);
1713
+ }
1714
+ }
1715
+ </script>