@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.
- package/README.md +41 -0
- package/app.config.ts +10 -0
- package/app.vue +13 -0
- package/assets/css/main.css +13 -0
- package/components/data/display/row/data-avatar.vue +23 -0
- package/components/data/display/row/data-badge.vue +34 -0
- package/components/data/display/row/data-color.vue +17 -0
- package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
- package/components/data/display/row/data-label.vue +37 -0
- package/components/data/display/row/data-last-update.vue +45 -0
- package/components/data/display/row/data-spacer.vue +4 -0
- package/components/data/display/row/data-summary.vue +44 -0
- package/components/data/display/row/data-text.vue +14 -0
- package/components/data/display/row/data-toggle.vue +16 -0
- package/components/data/display/row.vue +111 -0
- package/components/data/lists/data-list.vue +85 -0
- package/components/data/lists/data-lists-table.vue +155 -0
- package/components/elements/avatars/avatar-circular.vue +68 -0
- package/components/elements/avatars/avatar-with-text.vue +47 -0
- package/components/elements/badges/badge.vue +85 -0
- package/components/elements/buttons/primary.vue +32 -0
- package/components/elements/buttons/secondary.vue +35 -0
- package/components/elements/buttons/tertiary.vue +25 -0
- package/components/elements/dropdowns/dots.vue +75 -0
- package/components/elements/media/image.vue +82 -0
- package/components/elements/notifications/default.vue +90 -0
- package/components/forms/handlers/handler-collections.vue +189 -0
- package/components/forms/handlers/handler-publication-status.vue +193 -0
- package/components/forms/handlers/handler-records.vue +138 -0
- package/components/forms/handlers/handler-roles.vue +176 -0
- package/components/forms/handlers/handler-schedules.vue +218 -0
- package/components/forms/handlers/handler-steps.vue +147 -0
- package/components/forms/handlers/handler-tokens.vue +79 -0
- package/components/forms/inputs/input-color-picker.vue +159 -0
- package/components/forms/inputs/input-editor.vue +363 -0
- package/components/forms/inputs/input-files-media.vue +150 -0
- package/components/forms/inputs/input-files.vue +98 -0
- package/components/forms/inputs/input-json.vue +65 -0
- package/components/forms/inputs/input-location.vue +179 -0
- package/components/forms/inputs/input-text-area.vue +65 -0
- package/components/forms/inputs/input-text.vue +122 -0
- package/components/forms/inputs/input-toggle.vue +125 -0
- package/components/forms/inputs/media-section/media-editor.vue +95 -0
- package/components/forms/layouts/form.vue +203 -0
- package/components/forms/layouts/layout.vue +47 -0
- package/components/forms/layouts/modal.vue +96 -0
- package/components/forms/layouts/navigation.vue +48 -0
- package/components/forms/layouts/section.vue +118 -0
- package/components/forms/selects/select-collection-enum.vue +195 -0
- package/components/forms/selects/select-date.vue +245 -0
- package/components/forms/selects/select-enum.vue +186 -0
- package/components/forms/selects/select-record.vue +274 -0
- package/components/layout/headings/headings-collection.vue +29 -0
- package/components/layout/headings/headings-record.vue +33 -0
- package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
- package/components/layout/navigations/primary.vue +132 -0
- package/components/layout/notifications/notifications-list.vue +19 -0
- package/composables/core/domains/use-definitions.ts +57 -0
- package/composables/core/domains/use-entries.ts +62 -0
- package/composables/core/domains/use-media.ts +82 -0
- package/composables/core/use-core.ts +12 -0
- package/composables/use-ability.js +38 -0
- package/composables/use-models.js +27 -0
- package/composables/use-session.ts +80 -0
- package/composables/use-timer.js +62 -0
- package/composables/use-trpc.js +17 -0
- package/editor/index.ts +44 -0
- package/editor/views/CalloutView.vue +22 -0
- package/editor/views/DetailsContentView.vue +11 -0
- package/editor/views/DetailsSummaryView.vue +11 -0
- package/editor/views/DetailsView.vue +72 -0
- package/editor/views/EmbedView.vue +53 -0
- package/editor/views/FileView.vue +79 -0
- package/editor/views/ImageView.vue +56 -0
- package/editor/views/UploadView.vue +71 -0
- package/layouts/default.vue +21 -0
- package/layouts/entrance.vue +10 -0
- package/layouts/record.vue +21 -0
- package/middleware/00.auth.global.js +16 -0
- package/middleware/01.instance.global.js +21 -0
- package/middleware/03.verify-permissions.global.js +11 -0
- package/middleware/04.prefetch-page.global.js +18 -0
- package/middleware/05.headings-page.global.js +25 -0
- package/models/_commun.js +243 -0
- package/models/_factory.js +34 -0
- package/models/collection.js +87 -0
- package/models/entrance.js +42 -0
- package/models/media.js +82 -0
- package/models/organization.js +92 -0
- package/models/record.js +165 -0
- package/models/role.js +66 -0
- package/models/token.js +56 -0
- package/models/user.js +97 -0
- package/nuxt.config.ts +151 -0
- package/package.json +71 -0
- package/pages/[collection]/[record]/index.vue +88 -0
- package/pages/[collection]/index.vue +55 -0
- package/pages/_lab/editor.vue +149 -0
- package/pages/auth/callback.vue +31 -0
- package/pages/index/index.vue +85 -0
- package/pages/index/password/define/[token].vue +79 -0
- package/pages/index/password/recover/index.vue +72 -0
- package/pages/index/welcome/[token].vue +90 -0
- package/pages/index.vue +44 -0
- package/pages/management/index.vue +15 -0
- package/pages/management/members.vue +176 -0
- package/pages/management/organizations.vue +192 -0
- package/pages/management/roles.vue +183 -0
- package/pages/overview.vue +15 -0
- package/pages/settings.vue +197 -0
- package/plugins/01.session.client.ts +5 -0
- package/plugins/02.vue-query.ts +25 -0
- package/public/favicon.ico +0 -0
- package/public/logo-black.svg +1 -0
- package/public/logo-white.svg +1 -0
- package/server/api/trpc/[...trpc].ts +28 -0
- package/server/api/v1/content/deployment.get.ts +45 -0
- package/server/api/v1/content/records.get.ts +12 -0
- package/server/middleware/0.security.ts +19 -0
- package/server/middleware/1.cors.ts +31 -0
- package/server/middleware/2.session.ts +20 -0
- package/server/middleware/3.api-token.ts +20 -0
- package/server/plugins/core.ts +8 -0
- package/server/routes/_tasks/[name].post.ts +20 -0
- package/server/routes/health.get.ts +6 -0
- package/server/tasks/deploy.ts +26 -0
- package/server/types.d.ts +8 -0
- package/server/utils/core.ts +18 -0
- package/store/layout/headings.js +21 -0
- package/store/layout/modal.js +80 -0
- package/store/layout/notifications.js +52 -0
- 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>
|