@open-commune/web 0.1.3

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 (132) hide show
  1. package/README.md +41 -0
  2. package/app.config.ts +10 -0
  3. package/app.vue +13 -0
  4. package/assets/css/main.css +13 -0
  5. package/components/data/display/row/data-avatar.vue +23 -0
  6. package/components/data/display/row/data-badge.vue +34 -0
  7. package/components/data/display/row/data-color.vue +17 -0
  8. package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
  9. package/components/data/display/row/data-label.vue +37 -0
  10. package/components/data/display/row/data-last-update.vue +45 -0
  11. package/components/data/display/row/data-spacer.vue +4 -0
  12. package/components/data/display/row/data-summary.vue +44 -0
  13. package/components/data/display/row/data-text.vue +14 -0
  14. package/components/data/display/row/data-toggle.vue +16 -0
  15. package/components/data/display/row.vue +111 -0
  16. package/components/data/lists/data-list.vue +85 -0
  17. package/components/data/lists/data-lists-table.vue +155 -0
  18. package/components/elements/avatars/avatar-circular.vue +68 -0
  19. package/components/elements/avatars/avatar-with-text.vue +47 -0
  20. package/components/elements/badges/badge.vue +85 -0
  21. package/components/elements/buttons/primary.vue +32 -0
  22. package/components/elements/buttons/secondary.vue +35 -0
  23. package/components/elements/buttons/tertiary.vue +25 -0
  24. package/components/elements/dropdowns/dots.vue +75 -0
  25. package/components/elements/media/image.vue +82 -0
  26. package/components/elements/notifications/default.vue +90 -0
  27. package/components/forms/handlers/handler-collections.vue +189 -0
  28. package/components/forms/handlers/handler-publication-status.vue +193 -0
  29. package/components/forms/handlers/handler-records.vue +138 -0
  30. package/components/forms/handlers/handler-roles.vue +176 -0
  31. package/components/forms/handlers/handler-schedules.vue +218 -0
  32. package/components/forms/handlers/handler-steps.vue +147 -0
  33. package/components/forms/handlers/handler-tokens.vue +79 -0
  34. package/components/forms/inputs/input-color-picker.vue +159 -0
  35. package/components/forms/inputs/input-editor.vue +363 -0
  36. package/components/forms/inputs/input-files-media.vue +150 -0
  37. package/components/forms/inputs/input-files.vue +98 -0
  38. package/components/forms/inputs/input-json.vue +65 -0
  39. package/components/forms/inputs/input-location.vue +179 -0
  40. package/components/forms/inputs/input-text-area.vue +65 -0
  41. package/components/forms/inputs/input-text.vue +122 -0
  42. package/components/forms/inputs/input-toggle.vue +125 -0
  43. package/components/forms/inputs/media-section/media-editor.vue +95 -0
  44. package/components/forms/layouts/form.vue +203 -0
  45. package/components/forms/layouts/layout.vue +47 -0
  46. package/components/forms/layouts/modal.vue +96 -0
  47. package/components/forms/layouts/navigation.vue +48 -0
  48. package/components/forms/layouts/section.vue +118 -0
  49. package/components/forms/selects/select-collection-enum.vue +195 -0
  50. package/components/forms/selects/select-date.vue +245 -0
  51. package/components/forms/selects/select-enum.vue +186 -0
  52. package/components/forms/selects/select-record.vue +274 -0
  53. package/components/layout/headings/headings-collection.vue +29 -0
  54. package/components/layout/headings/headings-record.vue +33 -0
  55. package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
  56. package/components/layout/navigations/primary.vue +132 -0
  57. package/components/layout/notifications/notifications-list.vue +19 -0
  58. package/composables/core/domains/use-definitions.ts +57 -0
  59. package/composables/core/domains/use-entries.ts +62 -0
  60. package/composables/core/domains/use-media.ts +82 -0
  61. package/composables/core/use-core.ts +12 -0
  62. package/composables/use-ability.js +38 -0
  63. package/composables/use-models.js +27 -0
  64. package/composables/use-session.ts +80 -0
  65. package/composables/use-timer.js +62 -0
  66. package/composables/use-trpc.js +17 -0
  67. package/editor/index.ts +44 -0
  68. package/editor/views/CalloutView.vue +22 -0
  69. package/editor/views/DetailsContentView.vue +11 -0
  70. package/editor/views/DetailsSummaryView.vue +11 -0
  71. package/editor/views/DetailsView.vue +72 -0
  72. package/editor/views/EmbedView.vue +53 -0
  73. package/editor/views/FileView.vue +79 -0
  74. package/editor/views/ImageView.vue +56 -0
  75. package/editor/views/UploadView.vue +71 -0
  76. package/layouts/default.vue +21 -0
  77. package/layouts/entrance.vue +10 -0
  78. package/layouts/record.vue +21 -0
  79. package/middleware/00.auth.global.js +16 -0
  80. package/middleware/01.instance.global.js +21 -0
  81. package/middleware/03.verify-permissions.global.js +11 -0
  82. package/middleware/04.prefetch-page.global.js +18 -0
  83. package/middleware/05.headings-page.global.js +25 -0
  84. package/models/_commun.js +243 -0
  85. package/models/_factory.js +34 -0
  86. package/models/collection.js +87 -0
  87. package/models/entrance.js +42 -0
  88. package/models/media.js +82 -0
  89. package/models/organization.js +92 -0
  90. package/models/record.js +165 -0
  91. package/models/role.js +66 -0
  92. package/models/token.js +56 -0
  93. package/models/user.js +97 -0
  94. package/nuxt.config.ts +151 -0
  95. package/package.json +71 -0
  96. package/pages/[collection]/[record]/index.vue +88 -0
  97. package/pages/[collection]/index.vue +55 -0
  98. package/pages/_lab/editor.vue +149 -0
  99. package/pages/auth/callback.vue +31 -0
  100. package/pages/index/index.vue +85 -0
  101. package/pages/index/password/define/[token].vue +79 -0
  102. package/pages/index/password/recover/index.vue +72 -0
  103. package/pages/index/welcome/[token].vue +90 -0
  104. package/pages/index.vue +44 -0
  105. package/pages/management/index.vue +15 -0
  106. package/pages/management/members.vue +176 -0
  107. package/pages/management/organizations.vue +192 -0
  108. package/pages/management/roles.vue +183 -0
  109. package/pages/overview.vue +15 -0
  110. package/pages/settings.vue +197 -0
  111. package/plugins/01.session.client.ts +5 -0
  112. package/plugins/02.vue-query.ts +25 -0
  113. package/public/favicon.ico +0 -0
  114. package/public/logo-black.svg +1 -0
  115. package/public/logo-white.svg +1 -0
  116. package/server/api/trpc/[...trpc].ts +28 -0
  117. package/server/api/v1/content/deployment.get.ts +45 -0
  118. package/server/api/v1/content/records.get.ts +12 -0
  119. package/server/middleware/0.security.ts +19 -0
  120. package/server/middleware/1.cors.ts +31 -0
  121. package/server/middleware/2.session.ts +20 -0
  122. package/server/middleware/3.api-token.ts +20 -0
  123. package/server/plugins/core.ts +8 -0
  124. package/server/routes/_tasks/[name].post.ts +20 -0
  125. package/server/routes/health.get.ts +6 -0
  126. package/server/tasks/deploy.ts +26 -0
  127. package/server/types.d.ts +8 -0
  128. package/server/utils/core.ts +18 -0
  129. package/store/layout/headings.js +21 -0
  130. package/store/layout/modal.js +80 -0
  131. package/store/layout/notifications.js +52 -0
  132. package/store/layout/workspace.js +80 -0
@@ -0,0 +1,179 @@
1
+ <template>
2
+ <div class="relative">
3
+ <div class="relative">
4
+ <input-text
5
+ :auto-complete="'street-address'"
6
+ :label="label"
7
+ :name="name"
8
+ :place-holder="placeHolder"
9
+ :value="search || selection.address"
10
+ @change="search = $event"
11
+ />
12
+ <transition
13
+ leave-active-class="transition ease-in duration-100"
14
+ leave-from-class="opacity-100"
15
+ leave-to-class="opacity-0"
16
+ >
17
+ <div
18
+ v-if="display && places.length"
19
+ ref="dropdown"
20
+ class="absolute mt-1 w-full rounded-md bg-white shadow-lg z-50"
21
+ >
22
+ <ul tabindex="-1" role="listbox" aria-labelledby="listbox-label" aria-activedescendant="listbox-item-3" class="max-h-60 rounded-md py-1 text-base ring-1 ring-black/5 overflow-auto focus:outline-none sm:text-sm">
23
+ <li
24
+ v-for="({ id, place }, i) in places"
25
+ :id="`listbox-item-${i}`"
26
+ :key="id"
27
+ class="cursor-default select-none relative py-2 pl-4 pr-4 hover:text-white hover:bg-indigo-600 text-gray-900"
28
+ role="option"
29
+ @click="_select(id)"
30
+ >
31
+ <span class="font-normal block truncate">
32
+ {{ place }}
33
+ </span>
34
+ </li>
35
+ </ul>
36
+ </div>
37
+ </transition>
38
+ </div>
39
+ <div class="w-full mt-2 border border-gray-200 rounded-md" style="min-height: 18rem;">
40
+ <div id="map" class="w-full h-full" style="min-height: 18rem;" />
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <script setup>
46
+ import { onClickOutside, useDebounceFn } from '@vueuse/core'
47
+ import axios from 'axios'
48
+ import mapbox, { Map, Marker } from 'mapbox-gl'
49
+ import inputText from '~/components/forms/inputs/input-text'
50
+
51
+ // Prepare composable
52
+ const $config = useRuntimeConfig()
53
+
54
+ // Prepare $emitters
55
+ const $emitters = defineEmits(['change'])
56
+
57
+ // Prepare props
58
+ const $props = defineProps({
59
+ name: {
60
+ type: String,
61
+ default: ''
62
+ },
63
+ label: {
64
+ type: String,
65
+ default: ''
66
+ },
67
+ placeHolder: {
68
+ type: String,
69
+ default: ''
70
+ },
71
+ value: {
72
+ type: Object,
73
+ default: () => ({})
74
+ }
75
+ })
76
+
77
+ // Prepare constant
78
+ const MAPBOX_CLIENT = axios.create({ baseURL: 'https://api.mapbox.com/geocoding/v5/mapbox.places' })
79
+
80
+ // Prepare data
81
+ const dropdown = ref(undefined)
82
+ const map = ref(undefined)
83
+ const marker = ref(undefined)
84
+ const search = ref('')
85
+ const list = ref([])
86
+ const selection = ref({})
87
+ const display = ref(false)
88
+ const isLoading = ref(false)
89
+
90
+ // Prepare computed
91
+ const places = computed(() => list.value.map(({ id, place_name_fr: place, center }) => ({ id, place, center })))
92
+
93
+ // Prepare methods
94
+ const _createMap = () => {
95
+ mapbox.accessToken = $config.public.mapBoxToken
96
+ map.value = new Map({
97
+ container: 'map',
98
+ style: 'mapbox://styles/mapbox/light-v11?optimized=true',
99
+ zoom: 13,
100
+ center: selection.value?.coordinates,
101
+ attributionControl: false,
102
+ interactive: true
103
+ })
104
+ map.value.on('load', _updateMarker)
105
+ }
106
+ const _updateMarker = () => {
107
+ if (selection.value?.coordinates?.length) {
108
+ marker.value = new Marker({ color: '#000' })
109
+ .setLngLat(selection.value?.coordinates)
110
+ .addTo(map.value)
111
+ map.value.flyTo({ center: selection.value.coordinates })
112
+ }
113
+ }
114
+ const _findLocation = useDebounceFn(async () => {
115
+ if (!isLoading.value && search.value) {
116
+ isLoading.value = true
117
+ try {
118
+ const { data: { features } } = await MAPBOX_CLIENT.get(
119
+ `/${search.value}.json?access_token=${$config.public.mapBoxToken}&types=postcode,district,place,locality,neighborhood,address,poi&autocomplete=true&country=fr&language=fr`
120
+ )
121
+ list.value = [...features]
122
+ } catch (err) {
123
+ console.log(err)
124
+ } finally {
125
+ isLoading.value = false
126
+ }
127
+ }
128
+ }, 300)
129
+ const _select = (id) => {
130
+ const { place_name_fr: address, center: coordinates } = list.value.find(({ id: check }) => check === id) || {}
131
+ if (address && coordinates) {
132
+ selection.value = {
133
+ address,
134
+ coordinates
135
+ }
136
+ _updateMarker()
137
+ _close()
138
+ $emitters('change', selection.value)
139
+ }
140
+ }
141
+ const _toggle = () => {
142
+ display.value = !display.value
143
+ }
144
+ const _close = () => {
145
+ search.value = ''
146
+ display.value = false
147
+ }
148
+
149
+ // Prepare watchers
150
+ watch(
151
+ search,
152
+ (newValue) => {
153
+ if (newValue && newValue !== selection.value?.address) {
154
+ if (!display.value) {
155
+ _toggle()
156
+ }
157
+ _findLocation(newValue)
158
+ } else {
159
+ _close()
160
+ }
161
+ }
162
+ )
163
+ watch(
164
+ () => $props.value,
165
+ (newValue) => {
166
+ if (newValue.address && newValue.coordinates) {
167
+ selection.value = {
168
+ address: newValue.address,
169
+ coordinates: newValue.coordinates,
170
+ }
171
+ }
172
+ },
173
+ { immediate: true }
174
+ )
175
+
176
+ // Prepare lifecycle hooks
177
+ onMounted(_createMap)
178
+ onClickOutside(dropdown, _close)
179
+ </script>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <div>
3
+ <label
4
+ :for="name"
5
+ class="block text-2xs font-light tracking-wider text-gray-500 uppercase"
6
+ >
7
+ {{ label }}
8
+ </label>
9
+ <textarea
10
+ :id="name"
11
+ v-model="data"
12
+ :name="name"
13
+ :class="{ 'mt-1':!!label }"
14
+ :placeholder="placeHolder"
15
+ :disabled="readOnly"
16
+ rows="3"
17
+ class="w-full rounded-md py-2 px-3 shadow-xs border border-gray-200 focus:outline-none focus:ring-gray-50 focus:border-black sm:text-sm disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500 disabled:ring-gray-200"
18
+ />
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+ // Prepare $emitters
24
+ const $emitters = defineEmits(['keyup.enter', 'change'])
25
+
26
+ // Prepare $props
27
+ const $props = defineProps({
28
+ label: {
29
+ type: String,
30
+ default: ''
31
+ },
32
+ name: {
33
+ type: String,
34
+ default: 'input'
35
+ },
36
+ placeHolder: {
37
+ type: String,
38
+ default: ''
39
+ },
40
+ value: {
41
+ type: String,
42
+ default: ''
43
+ },
44
+ readOnly: {
45
+ type: Boolean,
46
+ default: false
47
+ }
48
+ })
49
+
50
+ // Prepare reactive data
51
+ const data = ref('')
52
+
53
+ // Prepare watchers
54
+ watch(
55
+ () => $props.value,
56
+ (newValue) => {
57
+ data.value = newValue
58
+ },
59
+ { immediate: true }
60
+ )
61
+ watch(
62
+ data,
63
+ (newValue) => $emitters('change', newValue)
64
+ )
65
+ </script>
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <div>
3
+ <label
4
+ :for="name"
5
+ class="block text-2xs font-light tracking-wider text-gray-500 uppercase"
6
+ >
7
+ {{ label }}
8
+ </label>
9
+ <div :class="{ 'mt-1':!!label }" class="relative block w-full">
10
+ <div v-if="icon" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
11
+ <svg
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ class="h-5 w-5 text-gray-400"
14
+ fill="none"
15
+ viewBox="0 0 24 24"
16
+ stroke="currentColor"
17
+ stroke-width="2"
18
+ >
19
+ <path
20
+ stroke-linecap="round"
21
+ stroke-linejoin="round"
22
+ :d="icon"
23
+ />
24
+ </svg>
25
+ </div>
26
+ <input
27
+ :id="name"
28
+ v-model="inputValue"
29
+ :name="name"
30
+ :autocomplete="autoComplete"
31
+ :placeholder="placeHolder"
32
+ :class="{ 'pl-10':!!icon }"
33
+ :type="type"
34
+ :required="required"
35
+ :disabled="readOnly"
36
+ class="w-full rounded-md shadow-xs border border-gray-200 sm:text-sm py-2 px-3 focus:outline-none focus:ring-gray-50 focus:border-black disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500 disabled:ring-gray-200"
37
+ @keyup.enter="$emitters('enter')"
38
+ >
39
+ <!-- <p v-show="slugPreview" class="mt-1 ml-3 text-sm font-light text-gray-500">
40
+ Aperçu dans l'url {{ slugifyValue(data) }}
41
+ </p> -->
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <script setup>
47
+ // import slugify from 'slugify'
48
+ // const slugifyOptions = {
49
+ // replacement: '-',
50
+ // lower: true,
51
+ // locale: 'fr',
52
+ // trim: true
53
+ // }
54
+
55
+ // Prepare $props
56
+ const $props = defineProps({
57
+ autoComplete: {
58
+ type: String,
59
+ default: 'given-name'
60
+ },
61
+ label: {
62
+ type: String,
63
+ default: ''
64
+ },
65
+ name: {
66
+ type: String,
67
+ default: 'input'
68
+ },
69
+ type: {
70
+ type: String,
71
+ default: 'text'
72
+ },
73
+ icon: {
74
+ type: String,
75
+ default: ''
76
+ },
77
+ placeHolder: {
78
+ type: String,
79
+ default: ''
80
+ },
81
+ value: {
82
+ type: String,
83
+ default: ''
84
+ },
85
+ readOnly: {
86
+ type: Boolean,
87
+ default: false
88
+ },
89
+ required: {
90
+ type: Boolean,
91
+ default: false
92
+ },
93
+ slugPreview: {
94
+ type: Boolean,
95
+ default: false
96
+ }
97
+ })
98
+
99
+ // Prepare $emitters
100
+ const $emitters = defineEmits(['keyup.enter', 'change'])
101
+
102
+ // Prepare reactive $data
103
+ const inputValue = ref('')
104
+
105
+ // Prepare $watchers
106
+ watch(
107
+ () => $props.value,
108
+ (newValue) => {
109
+ if (newValue !== inputValue.value) {
110
+ inputValue.value = newValue
111
+ }
112
+ },
113
+ { immediate: true }
114
+ )
115
+ watch(
116
+ inputValue,
117
+ (newValue) => $emitters('change', newValue)
118
+ )
119
+
120
+ // Prepare methods
121
+ // const _slugify = (value) => slugify(value, slugifyOptions)
122
+ </script>
@@ -0,0 +1,125 @@
1
+ <template>
2
+ <li class="py-1 flex items-center justify-start">
3
+ <button
4
+ :class="{
5
+ '!bg-black': selected,
6
+ '!h-3 !w-6': small
7
+ }"
8
+ type="button"
9
+ class="bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
10
+ :aria-pressed="selected"
11
+ :aria-labelledby="`${name}-option-label`"
12
+ :aria-describedby="`${name}-option-description`"
13
+ @click="_toggle"
14
+ >
15
+ <span class="sr-only">{{ name }} setting</span>
16
+ <span
17
+ :class="{
18
+ 'translate-x-5': selected && !small,
19
+ 'translate-x-3 !-mr-0.5': selected && small,
20
+ '!-ml-0.5': !selected && small,
21
+ '!h-3 !w-3 -my-0.5': small
22
+ }"
23
+ aria-hidden="true"
24
+ class="translate-x-0 pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
25
+ >
26
+ <span
27
+ v-if="icon"
28
+ :class="selected ? 'opacity-0 ease-out duration-100' : 'opacity-100 ease-in duration-200'"
29
+ class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
30
+ aria-hidden="true"
31
+ >
32
+ <svg class="h-3 w-3 text-gray-400" fill="none" viewBox="0 0 12 12">
33
+ <path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
34
+ </svg>
35
+ </span>
36
+ <!-- Enabled: "", Not Enabled: "" -->
37
+ <span
38
+ v-if="icon"
39
+ :class="{
40
+ '!opacity-100 !ease-in !duration-200': selected,
41
+ }"
42
+ class="opacity-0 ease-out duration-100 absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
43
+ aria-hidden="true"
44
+ >
45
+ <svg class="h-3 w-3 text-black" fill="currentColor" viewBox="0 0 12 12">
46
+ <path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
47
+ </svg>
48
+ </span>
49
+ </span>
50
+ </button>
51
+ <div v-if="label || description" class="ml-4 text-left">
52
+ <p v-if="label" :id="`${name}-option-label`" class="block text-2xs font-light tracking-wider text-gray-500 uppercase">
53
+ {{ label }}
54
+ </p>
55
+ <p v-if="description" :id="`${name}-option-description`" class="text-sm text-gray-900">
56
+ {{ description }}
57
+ </p>
58
+ </div>
59
+ </li>
60
+ </template>
61
+
62
+ <script setup>
63
+ // Prepare $emitters
64
+ const $emitters = defineEmits(['keyup.enter', 'change'])
65
+
66
+ // Prepare $props
67
+ const $props = defineProps({
68
+ icon: {
69
+ type: Boolean,
70
+ default: true
71
+ },
72
+ label: {
73
+ type: String,
74
+ default: ''
75
+ },
76
+ description: {
77
+ type: String,
78
+ default: ''
79
+ },
80
+ name: {
81
+ type: String,
82
+ default: 'input'
83
+ },
84
+ defaultValue: {
85
+ type: Boolean,
86
+ default: false
87
+ },
88
+ value: {
89
+ type: [Boolean, Object],
90
+ default: undefined
91
+ },
92
+ small: {
93
+ type: Boolean,
94
+ default: false
95
+ },
96
+ })
97
+
98
+ // Prepare reactive data
99
+ const selected = ref(false)
100
+
101
+ // Prepare watchers
102
+ watch(
103
+ () => $props.value,
104
+ (newValue) => {
105
+ selected.value = newValue
106
+ },
107
+ { immediate: true }
108
+ )
109
+ watch(
110
+ selected,
111
+ (newValue) => $emitters('change', newValue)
112
+ )
113
+
114
+ // Prepare methods
115
+ const _toggle = () => {
116
+ selected.value = !selected.value
117
+ }
118
+
119
+ // Prepare lifecycle methods
120
+ onMounted(() => {
121
+ if (typeof $props.value === 'undefined') {
122
+ selected.value = $props.defaultValue
123
+ }
124
+ })
125
+ </script>
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <li class="relative group">
3
+ <div
4
+ class="h-48 min-h-full w-full border border-gray-100 rounded-md shadow hover:border-gray-300"
5
+ >
6
+ <div v-if="!readOnly" class="absolute flex items-center justify-center right-0 top-0 p-1 mr-2 mt-2 rounded hover:bg-gray-300">
7
+ <dots-dropdown class="w-full h-full" :options="DOTS_ACTIONS" @action="_remove" />
8
+ </div>
9
+ <media-image v-if="media && mime.includes('image')" :media-id="value" class="object-cover object-center w-full h-full pointer-events-none !m-0" />
10
+ <p class="h-full w-full flex items-center justify-center text-gray-500" v-else>
11
+ {{ media?.originalName || 'Média non trouvé' }}
12
+ </p>
13
+ </div>
14
+ <!-- <div
15
+ v-else
16
+ class="flex items-center w-full h-full bg-gray-100 border border-gray-400 rounded text-center group-hover:border-black focus:outline-none"
17
+ >
18
+ <div v-if="!readOnly" class="absolute flex items-center justify-center right-0 top-0 p-1 mr-2 mt-2 rounded hover:bg-gray-300">
19
+ <dots-dropdown class="w-full h-full" :options="DOTS_ACTIONS" @remove="_remove" />
20
+ </div>
21
+ <media-image v-if="mime.includes('image')" :media-id="value" class="object-none object-center w-48 h-24 pointer-events-none !m-" />
22
+ <span class="pl-4 text-xs text-gray-900">
23
+ {{ name }}
24
+ </span>
25
+ </div> -->
26
+ </li>
27
+ </template>
28
+
29
+ <script setup>
30
+ // import { Icon } from '@iconify/vue2'
31
+ import dotsDropdown from '@/components/elements/dropdowns/dots'
32
+ import mediaImage from '@/components/elements/media/image'
33
+
34
+ const { Media } = useModels()
35
+
36
+ // Prepare constants
37
+ const DOTS_ACTIONS = [
38
+ {
39
+ label: 'Supprimer',
40
+ action: 'remove'
41
+ }
42
+ ]
43
+
44
+ // Prepare
45
+ const $emitters = defineEmits(['remove'])
46
+
47
+ // Prepare props
48
+ const $props = defineProps({
49
+ value: {
50
+ type: String,
51
+ default: ''
52
+ },
53
+ fullWidth: {
54
+ type: Boolean,
55
+ default: false
56
+ },
57
+ readOnly: {
58
+ type: Boolean,
59
+ default: false
60
+ }
61
+ })
62
+
63
+ // Prepare reactive data
64
+ const media = ref(undefined)
65
+
66
+ // Prepare computed
67
+ const mime = computed(() => media.value?.mime)
68
+
69
+ // Prepare methods
70
+ const _getMediaRecord = async (_id) => {
71
+ let data
72
+ [data] = Media.repo.query().where('_id', _id).get()
73
+ if (!data) {
74
+ (data = await Media.read(_id))
75
+ }
76
+ media.value = data
77
+ }
78
+ // TODO: refactor here to handle media edition
79
+ const _remove = (action) => {
80
+ if (action === 'remove') {
81
+ $emitters('remove')
82
+ }
83
+ }
84
+
85
+ // Prepare watchers
86
+ watch(
87
+ () => $props.value,
88
+ async (newValue) => {
89
+ if (newValue) {
90
+ await _getMediaRecord(newValue)
91
+ }
92
+ },
93
+ { immediate: true }
94
+ )
95
+ </script>