@meeovi/layer-shared 1.0.0

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 (327) hide show
  1. package/README.md +0 -0
  2. package/app/components/Gallery/Gallery.vue +187 -0
  3. package/app/components/Gallery/__tests__/Gallery.spec.ts +14 -0
  4. package/app/components/Heading/Heading.vue +14 -0
  5. package/app/components/Heading/__tests__/Heading.spec.ts +14 -0
  6. package/app/components/Heading/types.ts +5 -0
  7. package/app/components/media/audioGallery.vue +70 -0
  8. package/app/components/media/dragDropUpload.vue +67 -0
  9. package/app/components/media/fullscreenMediaModal.vue +66 -0
  10. package/app/components/media/imageCard.vue +65 -0
  11. package/app/components/media/imageGallery.vue +40 -0
  12. package/app/components/media/mediaCard.vue +89 -0
  13. package/app/components/media/mediaCarousel.vue +65 -0
  14. package/app/components/media/mediaFolderSidebar.vue +72 -0
  15. package/app/components/media/mediaPlayer.vue +40 -0
  16. package/app/components/media/mediaSearchBar.vue +16 -0
  17. package/app/components/media/videoGallery.vue +77 -0
  18. package/app/components/ui/AccordionItem/AccordionItem.vue +24 -0
  19. package/app/components/ui/AccordionItem/__tests__/AccordionItem.spec.ts +14 -0
  20. package/app/components/ui/AccordionItem/types.ts +5 -0
  21. package/app/components/ui/Alert/Alert.vue +34 -0
  22. package/app/components/ui/Alert/types.ts +5 -0
  23. package/app/components/ui/Breadcrumbs/Breadcrumbs.vue +76 -0
  24. package/app/components/ui/Breadcrumbs/__tests__/Breadcrumbs.spec.ts +14 -0
  25. package/app/components/ui/Breadcrumbs/types.ts +8 -0
  26. package/app/components/ui/CartProductCard/CartProductCard.vue +66 -0
  27. package/app/components/ui/CartProductCard/types.ts +18 -0
  28. package/app/components/ui/CategoryCard/CategoryCard.vue +41 -0
  29. package/app/components/ui/CategoryCard/types.ts +9 -0
  30. package/app/components/ui/Display/Display.vue +55 -0
  31. package/app/components/ui/Display/types.ts +12 -0
  32. package/app/components/ui/Divider/Divider.vue +3 -0
  33. package/app/components/ui/Divider/__tests__/Divider.spec.tsx +10 -0
  34. package/app/components/ui/Footer.vue +92 -0
  35. package/app/components/ui/Form/FormHelperText.vue +5 -0
  36. package/app/components/ui/Form/FormLabel.vue +5 -0
  37. package/app/components/ui/Form/FormPasswordInput.vue +15 -0
  38. package/app/components/ui/Form/__tests__/FormHelperText.spec.ts +10 -0
  39. package/app/components/ui/Form/__tests__/FormLabel.spec.ts +10 -0
  40. package/app/components/ui/Hero/Hero.vue +44 -0
  41. package/app/components/ui/Hero/types.ts +10 -0
  42. package/app/components/ui/Modal/Modal.vue +19 -0
  43. package/app/components/ui/Modal/types.ts +8 -0
  44. package/app/components/ui/Motionable.vue +45 -0
  45. package/app/components/ui/NavbarBottom.vue +63 -0
  46. package/app/components/ui/NavbarTop.vue +25 -0
  47. package/app/components/ui/Overlay/Overlay.vue +14 -0
  48. package/app/components/ui/Overlay/__tests__/Overlay.spec.ts +14 -0
  49. package/app/components/ui/Overlay/types.ts +3 -0
  50. package/app/components/ui/PageBuilder.vue +39 -0
  51. package/app/components/ui/PageContainer.vue +5 -0
  52. package/app/components/ui/Pagination/Pagination.vue +151 -0
  53. package/app/components/ui/Pagination/__tests__/Pagination.spec.ts +17 -0
  54. package/app/components/ui/Pagination/types.ts +6 -0
  55. package/app/components/ui/ProductCard/ProductCard.vue +55 -0
  56. package/app/components/ui/ProductCard/__tests__/ProductCard.spec.ts +16 -0
  57. package/app/components/ui/ProductCard/types.ts +12 -0
  58. package/app/components/ui/ProductCardHorizontal/ProductCardHorizontal.vue +34 -0
  59. package/app/components/ui/ProductCardHorizontal/__tests__/ProductCardHorizontal.spec.ts +36 -0
  60. package/app/components/ui/ProductCardHorizontal/types.ts +8 -0
  61. package/app/components/ui/PurchaseCard/PurchaseCard.vue +109 -0
  62. package/app/components/ui/PurchaseCard/__tests__/PurchaseCard.spec.ts +15 -0
  63. package/app/components/ui/PurchaseCard/types.ts +5 -0
  64. package/app/components/ui/QuantitySelector/QuantitySelector.vue +69 -0
  65. package/app/components/ui/QuantitySelector/__tests__/QuantitySelector.spec.ts +15 -0
  66. package/app/components/ui/QuantitySelector/types.ts +5 -0
  67. package/app/components/ui/RadialProgress.vue +44 -0
  68. package/app/components/ui/Review/Review.vue +57 -0
  69. package/app/components/ui/Review/__tests__/Review.spec.ts +15 -0
  70. package/app/components/ui/Review/types.ts +5 -0
  71. package/app/components/ui/ScrollTop.vue +82 -0
  72. package/app/components/ui/Search.vue +54 -0
  73. package/app/components/ui/Tag/Tag.vue +38 -0
  74. package/app/components/ui/Tag/__tests__/Tag.spec.ts +10 -0
  75. package/app/components/ui/Tag/types.ts +16 -0
  76. package/app/components/ui/VsfLogo.vue +7 -0
  77. package/app/components/ui/forms/BooleanInput.vue +34 -0
  78. package/app/components/ui/forms/DateTime.vue +44 -0
  79. package/app/components/ui/forms/DirectusFormElement.vue +60 -0
  80. package/app/components/ui/forms/DynamicTableElement.vue +57 -0
  81. package/app/components/ui/forms/FileInput.vue +85 -0
  82. package/app/components/ui/forms/FormField.vue +34 -0
  83. package/app/components/ui/forms/RelationSelect.vue +63 -0
  84. package/app/components/ui/forms/RepeaterInput.vue +121 -0
  85. package/app/components/ui/forms/SelectInput.vue +65 -0
  86. package/app/components/ui/forms/TextArea.vue +59 -0
  87. package/app/components/ui/forms/TextInput.vue +42 -0
  88. package/app/components/ui/forms/TiptapEditor.vue +136 -0
  89. package/app/components/ui/forms/[collection].vue +22 -0
  90. package/app/components/ui/studio/builder.vue +57 -0
  91. package/app/components/ui/studio/document.vue +69 -0
  92. package/app/components/ui/studio/email.vue +57 -0
  93. package/app/composables/globals/uploadFiles.js +41 -0
  94. package/app/composables/globals/useAdminTable.ts +12 -0
  95. package/app/composables/globals/useCustomFetch.ts +13 -0
  96. package/app/composables/globals/useDirectusField.ts +144 -0
  97. package/app/composables/globals/useDirectusForm.ts +70 -0
  98. package/app/composables/globals/useDirectusSchema.js +9 -0
  99. package/app/composables/globals/useFileManager.ts +76 -0
  100. package/app/composables/globals/useLivePreview.ts +17 -0
  101. package/app/composables/globals/useLoading.ts +23 -0
  102. package/app/composables/globals/useNavigation.js +19 -0
  103. package/app/composables/globals/useNotifications.ts +153 -0
  104. package/app/composables/globals/usePages.js +36 -0
  105. package/app/composables/globals/useRichText.ts +33 -0
  106. package/app/composables/globals/useServerRootMixin.ts +19 -0
  107. package/app/composables/globals/useVisualEditing.ts +38 -0
  108. package/app/composables/media/useFile.ts +6 -0
  109. package/app/composables/media/useMediaCenter.ts +353 -0
  110. package/app/composables/media/useVideojs.ts +45 -0
  111. package/app/composables/registry.ts +13 -0
  112. package/app/composables/types.ts +12 -0
  113. package/app/composables/useContent.ts +13 -0
  114. package/app/composables/useDirectusRequest.ts +32 -0
  115. package/app/stores/index.ts +0 -0
  116. package/app/types/api/global-search.ts +8 -0
  117. package/app/types/blocks/block-button-group.ts +7 -0
  118. package/app/types/blocks/block-button.ts +14 -0
  119. package/app/types/blocks/block-column.ts +20 -0
  120. package/app/types/blocks/block-cta.ts +10 -0
  121. package/app/types/blocks/block-divider.ts +4 -0
  122. package/app/types/blocks/block-faq.ts +12 -0
  123. package/app/types/blocks/block-form.ts +8 -0
  124. package/app/types/blocks/block-gallery.ts +14 -0
  125. package/app/types/blocks/block-hero.ts +12 -0
  126. package/app/types/blocks/block-html.ts +4 -0
  127. package/app/types/blocks/block-logocloud.ts +14 -0
  128. package/app/types/blocks/block-quote.ts +11 -0
  129. package/app/types/blocks/block-richtext.ts +7 -0
  130. package/app/types/blocks/block-steps.ts +22 -0
  131. package/app/types/blocks/block-team.ts +6 -0
  132. package/app/types/blocks/block-testimonial.ts +14 -0
  133. package/app/types/blocks/block-video.ts +10 -0
  134. package/app/types/blocks/block.ts +49 -0
  135. package/app/types/blocks/index.ts +18 -0
  136. package/app/types/componentMap.ts +15 -0
  137. package/app/types/content/category.ts +11 -0
  138. package/app/types/content/form.ts +20 -0
  139. package/app/types/content/index.ts +6 -0
  140. package/app/types/content/page.ts +76 -0
  141. package/app/types/content/post.ts +39 -0
  142. package/app/types/content/team.ts +16 -0
  143. package/app/types/content/testimonial.ts +19 -0
  144. package/app/types/directus.d.ts +47 -0
  145. package/app/types/env.d.ts +8 -0
  146. package/app/types/help/index.ts +53 -0
  147. package/app/types/index.d.ts +9 -0
  148. package/app/types/index.ts +7 -0
  149. package/app/types/meta/analytics.ts +18 -0
  150. package/app/types/meta/config.ts +21 -0
  151. package/app/types/meta/globals.ts +30 -0
  152. package/app/types/meta/index.ts +6 -0
  153. package/app/types/meta/navigation.ts +32 -0
  154. package/app/types/meta/redirect.ts +13 -0
  155. package/app/types/meta/seo.ts +19 -0
  156. package/app/types/os/contact.ts +23 -0
  157. package/app/types/os/conversation.ts +25 -0
  158. package/app/types/os/index.ts +16 -0
  159. package/app/types/os/organization.ts +54 -0
  160. package/app/types/os/os-activity.ts +28 -0
  161. package/app/types/os/os-deal.ts +45 -0
  162. package/app/types/os/os-expense.ts +22 -0
  163. package/app/types/os/os-invoice.ts +48 -0
  164. package/app/types/os/os-item.ts +18 -0
  165. package/app/types/os/os-payment.ts +29 -0
  166. package/app/types/os/os-project.ts +47 -0
  167. package/app/types/os/os-proposal.ts +84 -0
  168. package/app/types/os/os-settings.ts +19 -0
  169. package/app/types/os/os-subscription.ts +12 -0
  170. package/app/types/os/os-task.ts +34 -0
  171. package/app/types/os/os-tax-rate.ts +13 -0
  172. package/app/types/pageComponentMap.ts +8 -0
  173. package/app/types/schema.d.ts +39 -0
  174. package/app/types/schema.ts +151 -0
  175. package/app/types/system/file.ts +46 -0
  176. package/app/types/system/folder.ts +8 -0
  177. package/app/types/system/index.ts +4 -0
  178. package/app/types/system/role.ts +21 -0
  179. package/app/types/system/user.ts +56 -0
  180. package/app/utils/Timer.js +44 -0
  181. package/app/utils/billing-address.ts +24 -0
  182. package/app/utils/color.ts +14 -0
  183. package/app/utils/currency.ts +29 -0
  184. package/app/utils/embed.ts +57 -0
  185. package/app/utils/errors.ts +9 -0
  186. package/app/utils/fieldRegistry.js +89 -0
  187. package/app/utils/fonts.ts +24 -0
  188. package/app/utils/formkit.ts +75 -0
  189. package/app/utils/icons.ts +62 -0
  190. package/app/utils/index.js +0 -0
  191. package/app/utils/links.ts +28 -0
  192. package/app/utils/lodash.ts +33 -0
  193. package/app/utils/markdown.ts +9 -0
  194. package/app/utils/math.ts +25 -0
  195. package/app/utils/navigation.ts +11 -0
  196. package/app/utils/objects.ts +11 -0
  197. package/app/utils/paths.ts +21 -0
  198. package/app/utils/relations.ts +33 -0
  199. package/app/utils/strings.ts +113 -0
  200. package/app/utils/time.ts +148 -0
  201. package/app/utils/url.ts +22 -0
  202. package/app/utils/user-name.ts +21 -0
  203. package/app/utils/video/README.md +51 -0
  204. package/app/utils/video/playlist.js +0 -0
  205. package/dist/api/global-search.d.ts +8 -0
  206. package/dist/api/global-search.js +1 -0
  207. package/dist/blocks/block-button-group.d.ts +6 -0
  208. package/dist/blocks/block-button-group.js +1 -0
  209. package/dist/blocks/block-button.d.ts +13 -0
  210. package/dist/blocks/block-button.js +1 -0
  211. package/dist/blocks/block-column.d.ts +18 -0
  212. package/dist/blocks/block-column.js +1 -0
  213. package/dist/blocks/block-cta.d.ts +11 -0
  214. package/dist/blocks/block-cta.js +1 -0
  215. package/dist/blocks/block-divider.d.ts +4 -0
  216. package/dist/blocks/block-divider.js +1 -0
  217. package/dist/blocks/block-faq.d.ts +11 -0
  218. package/dist/blocks/block-faq.js +1 -0
  219. package/dist/blocks/block-form.d.ts +7 -0
  220. package/dist/blocks/block-form.js +1 -0
  221. package/dist/blocks/block-gallery.d.ts +13 -0
  222. package/dist/blocks/block-gallery.js +1 -0
  223. package/dist/blocks/block-hero.d.ts +11 -0
  224. package/dist/blocks/block-hero.js +1 -0
  225. package/dist/blocks/block-html.d.ts +4 -0
  226. package/dist/blocks/block-html.js +1 -0
  227. package/dist/blocks/block-logocloud.d.ts +13 -0
  228. package/dist/blocks/block-logocloud.js +1 -0
  229. package/dist/blocks/block-quote.d.ts +10 -0
  230. package/dist/blocks/block-quote.js +1 -0
  231. package/dist/blocks/block-richtext.d.ts +7 -0
  232. package/dist/blocks/block-richtext.js +1 -0
  233. package/dist/blocks/block-steps.d.ts +21 -0
  234. package/dist/blocks/block-steps.js +1 -0
  235. package/dist/blocks/block-team.d.ts +6 -0
  236. package/dist/blocks/block-team.js +1 -0
  237. package/dist/blocks/block-testimonial.d.ts +13 -0
  238. package/dist/blocks/block-testimonial.js +1 -0
  239. package/dist/blocks/block-video.d.ts +9 -0
  240. package/dist/blocks/block-video.js +1 -0
  241. package/dist/blocks/block.d.ts +17 -0
  242. package/dist/blocks/block.js +1 -0
  243. package/dist/blocks/index.d.ts +18 -0
  244. package/dist/blocks/index.js +1 -0
  245. package/dist/componentMap.d.ts +6 -0
  246. package/dist/componentMap.js +8 -0
  247. package/dist/content/category.d.ts +10 -0
  248. package/dist/content/category.js +1 -0
  249. package/dist/content/form.d.ts +21 -0
  250. package/dist/content/form.js +1 -0
  251. package/dist/content/index.d.ts +6 -0
  252. package/dist/content/index.js +1 -0
  253. package/dist/content/page.d.ts +38 -0
  254. package/dist/content/page.js +1 -0
  255. package/dist/content/post.d.ts +38 -0
  256. package/dist/content/post.js +1 -0
  257. package/dist/content/team.d.ts +17 -0
  258. package/dist/content/team.js +1 -0
  259. package/dist/content/testimonial.d.ts +18 -0
  260. package/dist/content/testimonial.js +1 -0
  261. package/dist/help/index.d.ts +51 -0
  262. package/dist/help/index.js +1 -0
  263. package/dist/index.d.ts +7 -0
  264. package/dist/index.js +1 -0
  265. package/dist/meta/analytics.d.ts +21 -0
  266. package/dist/meta/analytics.js +1 -0
  267. package/dist/meta/config.d.ts +22 -0
  268. package/dist/meta/config.js +1 -0
  269. package/dist/meta/globals.d.ts +33 -0
  270. package/dist/meta/globals.js +1 -0
  271. package/dist/meta/index.d.ts +6 -0
  272. package/dist/meta/index.js +1 -0
  273. package/dist/meta/navigation.d.ts +31 -0
  274. package/dist/meta/navigation.js +1 -0
  275. package/dist/meta/redirect.d.ts +12 -0
  276. package/dist/meta/redirect.js +1 -0
  277. package/dist/meta/seo.d.ts +19 -0
  278. package/dist/meta/seo.js +1 -0
  279. package/dist/os/contact.d.ts +22 -0
  280. package/dist/os/contact.js +1 -0
  281. package/dist/os/conversation.d.ts +23 -0
  282. package/dist/os/conversation.js +1 -0
  283. package/dist/os/index.d.ts +16 -0
  284. package/dist/os/index.js +1 -0
  285. package/dist/os/organization.d.ts +51 -0
  286. package/dist/os/organization.js +1 -0
  287. package/dist/os/os-activity.d.ts +26 -0
  288. package/dist/os/os-activity.js +1 -0
  289. package/dist/os/os-deal.d.ts +42 -0
  290. package/dist/os/os-deal.js +1 -0
  291. package/dist/os/os-expense.d.ts +21 -0
  292. package/dist/os/os-expense.js +1 -0
  293. package/dist/os/os-invoice.d.ts +46 -0
  294. package/dist/os/os-invoice.js +1 -0
  295. package/dist/os/os-item.d.ts +17 -0
  296. package/dist/os/os-item.js +1 -0
  297. package/dist/os/os-payment.d.ts +27 -0
  298. package/dist/os/os-payment.js +1 -0
  299. package/dist/os/os-project.d.ts +45 -0
  300. package/dist/os/os-project.js +1 -0
  301. package/dist/os/os-proposal.d.ts +61 -0
  302. package/dist/os/os-proposal.js +1 -0
  303. package/dist/os/os-settings.d.ts +17 -0
  304. package/dist/os/os-settings.js +1 -0
  305. package/dist/os/os-subscription.d.ts +12 -0
  306. package/dist/os/os-subscription.js +1 -0
  307. package/dist/os/os-task.d.ts +32 -0
  308. package/dist/os/os-task.js +1 -0
  309. package/dist/os/os-tax-rate.d.ts +12 -0
  310. package/dist/os/os-tax-rate.js +1 -0
  311. package/dist/pageComponentMap.d.ts +2 -0
  312. package/dist/pageComponentMap.js +7 -0
  313. package/dist/schema.d.ts +78 -0
  314. package/dist/schema.js +1 -0
  315. package/dist/system/file.d.ts +47 -0
  316. package/dist/system/file.js +1 -0
  317. package/dist/system/folder.d.ts +8 -0
  318. package/dist/system/folder.js +1 -0
  319. package/dist/system/index.d.ts +4 -0
  320. package/dist/system/index.js +1 -0
  321. package/dist/system/role.d.ts +20 -0
  322. package/dist/system/role.js +1 -0
  323. package/dist/system/user.d.ts +57 -0
  324. package/dist/system/user.js +1 -0
  325. package/nuxt.config.ts +5 -0
  326. package/package.json +42 -0
  327. package/tsconfig.json +15 -0
@@ -0,0 +1,353 @@
1
+ import { ref, computed, unref } from 'vue'
2
+ import { useNuxtApp } from '#imports'
3
+ import useDirectusRequest from '../useDirectusRequest'
4
+ import { useAuth } from '~/composables/globals/useAuth'
5
+
6
+ export function useMediaCenter() {
7
+ // Prefer a BetterAuth `useAuth()` composable when available (provided by the auth layer).
8
+ // Fallback to null if not present so the rest of the composable degrades gracefully.
9
+ // `useAuth()` typically exposes `user` and `session` refs and a `fetchSession()` method.
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ const auth: any = (globalThis as any).$useAuth ?? (typeof useAuth !== 'undefined' ? useAuth() : null)
12
+
13
+ const user = computed(() => {
14
+ if (!auth) return null
15
+ // If auth exposes `user` ref directly
16
+ if (typeof auth.user !== 'undefined') return unref(auth.user) || null
17
+ // If auth exposes `session` ref containing `{ user }`
18
+ if (typeof auth.session !== 'undefined') return unref(auth.session)?.user || null
19
+ return null
20
+ })
21
+ const userId = computed(() => user.value?.id || null)
22
+
23
+ const {
24
+ $directus,
25
+ $readItems,
26
+ $createItem,
27
+ $uploadFiles,
28
+ } = useNuxtApp()
29
+
30
+ // CORE MEDIA STATE
31
+ const allMedia = ref<any[]>([])
32
+ const isInitialLoading = ref(true)
33
+ const isLoadingMore = ref(false)
34
+ const hasMore = ref(true)
35
+ const limit = 24
36
+ const offset = ref(0)
37
+
38
+ // FOLDERS
39
+ const folders = ref<any[]>([])
40
+ const currentFolder = ref<string | null>(null)
41
+
42
+ // SHARED & SEARCH
43
+ const sharedWithMe = ref<any[]>([])
44
+ const searchResults = ref<any[]>([])
45
+
46
+ // SMART ALBUMS
47
+ const smartAlbums = ref<any[]>([])
48
+
49
+ /* ---------- MIME HELPERS ---------- */
50
+ const mime = (i: any) => i?.directus_files_id?.type || ''
51
+ const isAudio = (i: any) => mime(i).startsWith('audio')
52
+ const isVideo = (i: any) => mime(i).startsWith('video')
53
+ const isImage = (i: any) => mime(i).startsWith('image')
54
+ const isText = (i: any) => mime(i).startsWith('text')
55
+ const isDoc = (i: any) =>
56
+ [
57
+ 'application/pdf',
58
+ 'application/msword',
59
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
60
+ 'application/vnd.ms-excel',
61
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
62
+ ].includes(mime(i))
63
+
64
+ /* ---------- FILTERED LISTS ---------- */
65
+ const filteredMedia = computed(() => {
66
+ if (!currentFolder.value) return allMedia.value
67
+ return allMedia.value.filter(m => m.folder === currentFolder.value)
68
+ })
69
+
70
+ const audioMedia = computed(() => filteredMedia.value.filter(isAudio))
71
+ const videoMedia = computed(() => filteredMedia.value.filter(isVideo))
72
+ const imageMedia = computed(() => filteredMedia.value.filter(isImage))
73
+ const textMedia = computed(() => filteredMedia.value.filter(isText))
74
+ const documentMedia = computed(() => filteredMedia.value.filter(isDoc))
75
+
76
+ /* ---------- RESET PAGINATION ---------- */
77
+ function resetPagination() {
78
+ allMedia.value = []
79
+ offset.value = 0
80
+ hasMore.value = true
81
+ }
82
+
83
+ /* ---------- FETCH USER MEDIA (PAGINATED) ---------- */
84
+ async function fetchNextPage() {
85
+ if (!userId.value) {
86
+ isInitialLoading.value = false
87
+ return
88
+ }
89
+ if (!hasMore.value) return
90
+
91
+ isLoadingMore.value = true
92
+
93
+ const filter: any = { user: { _eq: userId.value } }
94
+ if (currentFolder.value) {
95
+ filter.folder = { _eq: currentFolder.value }
96
+ }
97
+
98
+ try {
99
+ const items = await $readItems('media', {
100
+ fields: [
101
+ '*',
102
+ { directus_files_id: ['id', 'type', 'filename_download', 'title'] },
103
+ ],
104
+ filter,
105
+ sort: ['-date_created'],
106
+ limit,
107
+ offset: offset.value,
108
+ })
109
+
110
+ if (!items.length) {
111
+ hasMore.value = false
112
+ } else {
113
+ allMedia.value.push(...items)
114
+ offset.value += items.length
115
+ if (items.length < limit) hasMore.value = false
116
+ }
117
+ } finally {
118
+ isLoadingMore.value = false
119
+ isInitialLoading.value = false
120
+ }
121
+ }
122
+
123
+ /* ---------- UPLOAD FILES ---------- */
124
+ /**
125
+ * Upload files to Directus and create `media` entries.
126
+ * Uploads are performed one-by-one so callers can track progress.
127
+ *
128
+ * @param files - array of File objects
129
+ * @param onProgress - optional callback called with { index, total, status, name }
130
+ */
131
+ async function uploadFiles(files: File[], onProgress?: (info: { index: number; total: number; status: 'starting' | 'done' | 'error'; name?: string }) => void) {
132
+ if (!userId.value) {
133
+ // Try to fetch session once in case the auth state wasn't loaded yet
134
+ try {
135
+ if (auth && typeof auth.fetchSession === 'function') await auth.fetchSession()
136
+ else if (auth && typeof auth.fetch === 'function') await auth.fetch()
137
+ } catch (_) {}
138
+
139
+ if (!userId.value) {
140
+ try {
141
+ const nuxt = useNuxtApp() as any
142
+ const toast = nuxt?.$toast
143
+ if (toast && typeof toast.error === 'function') toast.error('You must be signed in to upload files')
144
+ } catch (_) {}
145
+ throw new Error('User not authenticated')
146
+ }
147
+ }
148
+
149
+ const { request } = useDirectusRequest()
150
+ const newItems: any[] = []
151
+
152
+ for (let i = 0; i < files.length; i++) {
153
+ const file = files[i]
154
+ if (!file) {
155
+ onProgress && onProgress({ index: i, total: files.length, status: 'error' })
156
+ continue
157
+ }
158
+ try {
159
+ onProgress && onProgress({ index: i, total: files.length, status: 'starting', name: file.name })
160
+
161
+ const formData = new FormData()
162
+ formData.append('file', file)
163
+
164
+ const resp = await request($uploadFiles(formData))
165
+ const uploaded = Array.isArray(resp) ? resp : (resp ? [resp] : [])
166
+
167
+ for (const up of uploaded) {
168
+ const mediaItem = await $createItem('media', {
169
+ user: userId.value,
170
+ directus_files_id: up.id,
171
+ title: up.filename_download || up.title || up.id,
172
+ folder: currentFolder.value || null,
173
+ })
174
+
175
+ newItems.push({ ...mediaItem, directus_files_id: up })
176
+ }
177
+
178
+ onProgress && onProgress({ index: i, total: files.length, status: 'done', name: file.name })
179
+ } catch (e) {
180
+ onProgress && onProgress({ index: i, total: files.length, status: 'error', name: file.name })
181
+ try {
182
+ const nuxt = useNuxtApp() as any
183
+ const toast = nuxt?.$toast
184
+ if (toast && typeof toast.error === 'function') toast.error(`Upload failed: ${file.name}`)
185
+ } catch (_) {}
186
+ console.error('uploadFiles failed for file', file.name, e)
187
+ }
188
+ }
189
+
190
+ if (newItems.length) {
191
+ // add newly created items to the beginning of allMedia
192
+ allMedia.value.unshift(...newItems)
193
+ try {
194
+ const nuxt = useNuxtApp() as any
195
+ const toast = nuxt?.$toast
196
+ if (toast && typeof toast.success === 'function') toast.success(`Uploaded ${newItems.length} file${newItems.length === 1 ? '' : 's'}`)
197
+ } catch (_) {}
198
+ }
199
+
200
+ return newItems
201
+ }
202
+
203
+ /* ---------- FOLDERS ---------- */
204
+ async function fetchFolders() {
205
+ if (!userId.value) return
206
+ folders.value = await $readItems('folders', {
207
+ filter: { user: { _eq: userId.value } },
208
+ sort: ['sort', 'name'],
209
+ })
210
+ }
211
+
212
+ async function createFolder(name: string, parent: string | null = null) {
213
+ await $createItem('folders', {
214
+ name,
215
+ user: userId.value,
216
+ parent_folder: parent,
217
+ })
218
+ await fetchFolders()
219
+ }
220
+
221
+ function filterByFolder(folderId: string | null) {
222
+ currentFolder.value = folderId
223
+ resetPagination()
224
+ fetchNextPage()
225
+ }
226
+
227
+ async function reorderFolders(updated: any[]) {
228
+ // You can persist a "sort" field here if you add it to folders.
229
+ // Example (pseudo-batched):
230
+ //
231
+ // for (let index = 0; index < updated.length; index++) {
232
+ // const folder = updated[index]
233
+ // await $directus.request(
234
+ // $updateItem('folders', folder.id, { sort: index })
235
+ // )
236
+ // }
237
+ //
238
+ // For now, we just update local state:
239
+ folders.value = [...updated]
240
+ }
241
+
242
+ /* ---------- SHARED WITH ME ---------- */
243
+ async function fetchSharedWithMe() {
244
+ if (!userId.value) return
245
+ sharedWithMe.value = await $readItems('media', {
246
+ fields: [
247
+ '*',
248
+ { directus_files_id: ['id', 'type', 'filename_download', 'title'] },
249
+ ],
250
+ filter: { shared_with: { _contains: userId.value } },
251
+ sort: ['-date_created'],
252
+ })
253
+ }
254
+
255
+ /* ---------- SEARCH ---------- */
256
+ async function searchMedia(query: string) {
257
+ if (!query) {
258
+ searchResults.value = []
259
+ return
260
+ }
261
+
262
+ searchResults.value = await $readItems('media', {
263
+ fields: [
264
+ '*',
265
+ { directus_files_id: ['id', 'type', 'filename_download', 'title'] },
266
+ ],
267
+ filter: {
268
+ _or: [
269
+ { title: { _icontains: query } },
270
+ { tags: { _icontains: query } },
271
+ {
272
+ directus_files_id: {
273
+ title: { _icontains: query },
274
+ },
275
+ },
276
+ ],
277
+ },
278
+ sort: ['-date_created'],
279
+ })
280
+ }
281
+
282
+ /* ---------- SMART ALBUMS ---------- */
283
+ const smartAlbumsComputed = computed(() => {
284
+ const albums: { id: string; label: string; items: any[] }[] = []
285
+
286
+ const now = new Date()
287
+ const todayKey = now.toISOString().slice(0, 10)
288
+
289
+ const buckets: Record<string, any[]> = {}
290
+
291
+ for (const item of allMedia.value) {
292
+ const dateKey = (item.date_created || '').slice(0, 10) || 'unknown'
293
+ const typeBucket = isImage(item)
294
+ ? 'Images'
295
+ : isVideo(item)
296
+ ? 'Videos'
297
+ : isAudio(item)
298
+ ? 'Audio'
299
+ : 'Other'
300
+
301
+ const key =
302
+ dateKey === todayKey
303
+ ? `Today / ${typeBucket}`
304
+ : `${dateKey} / ${typeBucket}`
305
+
306
+ if (!buckets[key]) buckets[key] = []
307
+ buckets[key].push(item)
308
+ }
309
+
310
+ for (const key of Object.keys(buckets)) {
311
+ albums.push({
312
+ id: key,
313
+ label: key,
314
+ items: buckets[key]!,
315
+ })
316
+ }
317
+
318
+ return albums
319
+ })
320
+
321
+ smartAlbums.value = smartAlbumsComputed.value
322
+
323
+ /* ---------- RETURN API ---------- */
324
+ return {
325
+ // state
326
+ allMedia,
327
+ audioMedia,
328
+ videoMedia,
329
+ imageMedia,
330
+ textMedia,
331
+ documentMedia,
332
+ folders,
333
+ currentFolder,
334
+ sharedWithMe,
335
+ searchResults,
336
+ smartAlbums,
337
+
338
+ // flags
339
+ isInitialLoading,
340
+ isLoadingMore,
341
+ hasMore,
342
+
343
+ // actions
344
+ fetchNextPage,
345
+ uploadFiles,
346
+ fetchFolders,
347
+ createFolder,
348
+ filterByFolder,
349
+ reorderFolders,
350
+ fetchSharedWithMe,
351
+ searchMedia,
352
+ }
353
+ }
@@ -0,0 +1,45 @@
1
+ import { useNuxtApp } from '#imports'
2
+
3
+ /**
4
+ * Composable wrapper for the shared `videojs` Nuxt plugin.
5
+ * Provides access to the provided `videojs` instance and registration helpers.
6
+ */
7
+ export default function useVideojs() {
8
+ const nuxtApp = useNuxtApp()
9
+
10
+ // Provided by layers/shared-app/app/plugins/videojs.client.js
11
+ const videojs = (nuxtApp as any).$videojs || null
12
+ const registerVideojsPlugin = (nuxtApp as any).$registerVideojsPlugin || ((name: string, plugin: any) => {})
13
+ const registerVideojsPlugins = (nuxtApp as any).$registerVideojsPlugins || ((defs: any) => {})
14
+
15
+ function createPlayer(el: Element | string | null, options?: any, ready?: (this: any) => void) {
16
+ if (!videojs) {
17
+ // fallback: try to import dynamically (client-side only)
18
+ // but avoid bundling here; return null instead
19
+ return null
20
+ }
21
+ try {
22
+ return videojs(el as any, options, ready)
23
+ } catch (e) {
24
+ // eslint-disable-next-line no-console
25
+ console.warn('[useVideojs] createPlayer failed', e)
26
+ return null
27
+ }
28
+ }
29
+
30
+ function disposePlayer(player: any) {
31
+ try {
32
+ player && typeof player.dispose === 'function' && player.dispose()
33
+ } catch (e) {
34
+ // ignore
35
+ }
36
+ }
37
+
38
+ return {
39
+ videojs,
40
+ registerVideojsPlugin,
41
+ registerVideojsPlugins,
42
+ createPlayer,
43
+ disposePlayer,
44
+ }
45
+ }
@@ -0,0 +1,13 @@
1
+ import { ContentProvider } from "./types"
2
+
3
+ const providers: Record<string, ContentProvider> = {}
4
+
5
+ export function registerContentProvider(name: string, provider: ContentProvider) {
6
+ providers[name] = provider
7
+ }
8
+
9
+ export function getContentProvider(name: string) {
10
+ const provider = providers[name]
11
+ if (!provider) throw new Error(`Content provider "${name}" not found`)
12
+ return provider
13
+ }
@@ -0,0 +1,12 @@
1
+ export interface ContentItem {
2
+ id: string
3
+ title: string
4
+ body: any
5
+ slug?: string
6
+ [key: string]: any
7
+ }
8
+
9
+ export interface ContentProvider {
10
+ getContent(slug: string): Promise<ContentItem>
11
+ listContent(params?: Record<string, any>): Promise<ContentItem[]>
12
+ }
@@ -0,0 +1,13 @@
1
+ import { getContentProvider } from './registry'
2
+ import { useRuntimeConfig } from '#imports'
3
+
4
+ export function useContent() {
5
+ const config = useRuntimeConfig()
6
+ const providerName = config.public.contentProvider || 'directus'
7
+ const provider = getContentProvider(providerName)
8
+
9
+ return {
10
+ getContent: provider.getContent,
11
+ listContent: provider.listContent
12
+ }
13
+ }
@@ -0,0 +1,32 @@
1
+ import { useNuxtApp } from '#imports'
2
+
3
+ /**
4
+ * Centralized safe wrapper around `$directus.request`.
5
+ * Shows a toast error when the Directus client or `.request` is not available.
6
+ */
7
+ export default function useDirectusRequest() {
8
+ const nuxt = useNuxtApp() as any
9
+
10
+ async function request(config: any) {
11
+ try {
12
+ const client = nuxt?.$directus
13
+ if (!client || typeof client.request !== 'function') {
14
+ try {
15
+ const toast = nuxt?.$toast
16
+ if (toast && typeof toast.error === 'function') toast.error('Directus client unavailable')
17
+ } catch (_) {}
18
+ throw new Error('Directus client.request is not available')
19
+ }
20
+
21
+ return await client.request(config)
22
+ } catch (e) {
23
+ try {
24
+ const toast = nuxt?.$toast
25
+ if (toast && typeof toast.error === 'function') toast.error('Request failed')
26
+ } catch (_) {}
27
+ throw e
28
+ }
29
+ }
30
+
31
+ return { request }
32
+ }
File without changes
@@ -0,0 +1,8 @@
1
+ export interface GlobalSearchResult {
2
+ id?: string;
3
+ title?: string;
4
+ type?: string;
5
+ description?: string;
6
+ image?: string;
7
+ url?: string;
8
+ }
@@ -0,0 +1,7 @@
1
+ import type { BlockButton } from './block-button';
2
+
3
+ export interface BlockButtonGroup {
4
+ id: string;
5
+ buttons: (string | BlockButton)[] | null;
6
+ alignment: 'left' | 'center' | null;
7
+ }
@@ -0,0 +1,14 @@
1
+ import type { Post, Page } from '../content';
2
+
3
+ export interface BlockButton {
4
+ id: string;
5
+ sort: number | null;
6
+ type: ('pages' | 'posts' | 'external') | null;
7
+ label: string | null;
8
+ color: 'primary' | 'white' | 'gray' | 'white' | 'black';
9
+ variant: 'solid' | 'outline' | 'ghost' | 'link' | 'soft';
10
+ page: string | Page | null;
11
+ post: string | Post | null;
12
+ external_url: string | null;
13
+ icon: string | null;
14
+ }
@@ -0,0 +1,20 @@
1
+ import type { File } from '../system';
2
+ import type { BlockButtonGroup } from '.';
3
+
4
+ export interface BlockColumn {
5
+ headline?: string | null;
6
+ id?: string;
7
+ title?: string | null;
8
+ rows?: (number | BlockColumnRow)[];
9
+ }
10
+
11
+ export interface BlockColumnRow {
12
+ block_columns?: (string | BlockColumn) | null;
13
+ content?: string | null;
14
+ headline?: string | null;
15
+ id?: string;
16
+ image?: (string | File) | null;
17
+ image_position?: string | null;
18
+ title?: string | null;
19
+ button_group?: (string | BlockButtonGroup) | null;
20
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockButtonGroup } from '.';
2
+
3
+ export interface BlockCta {
4
+ buttons?: { [key: string]: any } | null;
5
+ content?: string | null;
6
+ headline?: string | null;
7
+ id?: string;
8
+ title?: string | null;
9
+ button_group?: (string | BlockButtonGroup) | null;
10
+ }
@@ -0,0 +1,4 @@
1
+ export interface BlockDivider {
2
+ id?: string;
3
+ title?: string | null;
4
+ }
@@ -0,0 +1,12 @@
1
+ export interface BlockFaq {
2
+ faqs?: BlockFaqQuestion[] | null;
3
+ headline?: string | null;
4
+ id?: string;
5
+ title?: string | null;
6
+ alignment?: 'left' | 'center' | null;
7
+ }
8
+
9
+ export interface BlockFaqQuestion {
10
+ title: string | null;
11
+ answer: string | null;
12
+ }
@@ -0,0 +1,8 @@
1
+ import type { Form } from '../content';
2
+
3
+ export interface BlockForm {
4
+ form?: (string | Form) | null;
5
+ headline?: string | null;
6
+ id?: string;
7
+ title?: string | null;
8
+ }
@@ -0,0 +1,14 @@
1
+ import type { File } from '../system';
2
+
3
+ export interface BlockGallery {
4
+ headline?: string | null;
5
+ id?: string;
6
+ title?: string | null;
7
+ gallery_items?: BlockGalleryFile[] | null;
8
+ }
9
+ export interface BlockGalleryFile {
10
+ block_gallery?: (string | BlockGallery) | null;
11
+ directus_files_id?: (string | File) | null;
12
+ id?: number;
13
+ sort?: number | null;
14
+ }
@@ -0,0 +1,12 @@
1
+ import type { File } from '../system';
2
+ import type { BlockButtonGroup } from '.';
3
+
4
+ export interface BlockHero {
5
+ id?: string;
6
+ title?: string | null;
7
+ headline?: string | null;
8
+ content?: string | null;
9
+ image?: (string | File) | null;
10
+ image_position?: 'left' | 'right' | null;
11
+ button_group?: (string | BlockButtonGroup) | null;
12
+ }
@@ -0,0 +1,4 @@
1
+ export interface BlockHtml {
2
+ id?: string;
3
+ raw_html?: string | null;
4
+ }
@@ -0,0 +1,14 @@
1
+ import type { File } from '../system';
2
+
3
+ export interface BlockLogocloud {
4
+ headline?: string | null;
5
+ id?: string;
6
+ title?: string | null;
7
+ logos?: (string | BlockLogocloudFile)[];
8
+ }
9
+ export interface BlockLogocloudFile {
10
+ id?: string;
11
+ sort?: number | null;
12
+ block_logocloud_id?: (string | BlockLogocloud) | null;
13
+ directus_files_id?: (string | File) | null;
14
+ }
@@ -0,0 +1,11 @@
1
+ import type { File } from '../system';
2
+
3
+ export interface BlockQuote {
4
+ background_color?: string | null;
5
+ content?: string | null;
6
+ headline?: string | null;
7
+ id?: string;
8
+ image?: (string | File) | null;
9
+ subtitle?: string | null;
10
+ title?: string | null;
11
+ }
@@ -0,0 +1,7 @@
1
+ export interface BlockRichtext {
2
+ content?: string | null;
3
+ headline?: string | null;
4
+ id?: string;
5
+ title?: string | null;
6
+ alignment?: 'left' | 'center' | null;
7
+ }
@@ -0,0 +1,22 @@
1
+ import type { File } from '../system';
2
+ import type { BlockButtonGroup } from '.';
3
+
4
+ export interface BlockStep {
5
+ id?: string;
6
+ title?: string | null;
7
+ headline?: string | null;
8
+ /** If enabled, image position is alternated between left and right. */
9
+ alternate_image_position?: boolean;
10
+ /** Show the step numbers on the website. For example: (Step 1, Step 2, etc) */
11
+ show_step_numbers?: boolean | null;
12
+ steps?: (number | BlockStepItem)[];
13
+ }
14
+ export interface BlockStepItem {
15
+ id?: string;
16
+ title?: string | null;
17
+ content?: string | null;
18
+ image?: (string | File) | null;
19
+ sort?: number | null;
20
+ block_steps?: (string | BlockStep) | null;
21
+ button_group?: (string | BlockButtonGroup) | null;
22
+ }
@@ -0,0 +1,6 @@
1
+ export interface BlockTeam {
2
+ content?: string | null;
3
+ headline?: string | null;
4
+ id?: string;
5
+ title?: string | null;
6
+ }