@drax/media-vue 2.1.2 → 3.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.
- package/package.json +5 -3
- package/src/comboboxes/FileCombobox.vue +78 -0
- package/src/components/MediaFieldView.vue +303 -0
- package/src/components/MediaFullField.vue +4 -4
- package/src/components/cruds/FileCrud.vue +47 -0
- package/src/cruds/FileEntityCrud.ts +227 -0
- package/src/index.ts +13 -1
- package/src/pages/crud/FileCrudPage.vue +14 -0
- package/src/routes/FileCrudRoute.ts +18 -0
- package/src/routes/index.ts +8 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "3.0.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"format": "prettier --write src/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drax/
|
|
27
|
+
"@drax/crud-share": "^3.0.0",
|
|
28
|
+
"@drax/crud-vue": "^3.0.0",
|
|
29
|
+
"@drax/media-front": "^3.0.0"
|
|
28
30
|
},
|
|
29
31
|
"peerDependencies": {
|
|
30
32
|
"vue": "^3.5.28",
|
|
@@ -46,5 +48,5 @@
|
|
|
46
48
|
"vue-tsc": "^3.2.4",
|
|
47
49
|
"vuetify": "^3.11.8"
|
|
48
50
|
},
|
|
49
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "63ae718b24ea25ae80b1a9a5dfb84a3abbb95199"
|
|
50
52
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import FileEntityCrud from '../cruds/FileEntityCrud'
|
|
4
|
+
import {CrudAutocomplete} from "@drax/crud-vue";
|
|
5
|
+
import type {IEntityCrudField} from "@drax/crud-share";
|
|
6
|
+
import type {PropType} from "vue";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const valueModel = defineModel<any>({type: String})
|
|
10
|
+
|
|
11
|
+
const entityName = FileEntityCrud.instance.name
|
|
12
|
+
|
|
13
|
+
const {name, label} = defineProps({
|
|
14
|
+
name: {type: String as PropType<string>, required: false},
|
|
15
|
+
label: {type: String as PropType<string>, required: false},
|
|
16
|
+
itemTitle: {type: [String], default: 'name'},
|
|
17
|
+
itemValue: {type: [String], default: '_id'},
|
|
18
|
+
prependIcon: {type: String},
|
|
19
|
+
prependInnerIcon: {type: String},
|
|
20
|
+
appendIcon: {type: String},
|
|
21
|
+
appendInnerIcon: {type: String},
|
|
22
|
+
multiple: {type: Boolean, default: false},
|
|
23
|
+
chips: {type: Boolean, default: false},
|
|
24
|
+
closableChips: {type: Boolean, default: true},
|
|
25
|
+
readonly: {type: Boolean, default: false},
|
|
26
|
+
clearable: {type: Boolean, default: true},
|
|
27
|
+
hint: {type: String},
|
|
28
|
+
persistentHint: {type: Boolean, default: false},
|
|
29
|
+
rules: {type: Array as PropType<any>, default: () => []},
|
|
30
|
+
errorMessages: {type: Array as PropType<string[]>, default: () => []},
|
|
31
|
+
hideDetails: {type: Boolean, default: false},
|
|
32
|
+
singleLine: {type: Boolean, default: false},
|
|
33
|
+
addOnTheFly: {type: Boolean, default: false},
|
|
34
|
+
density: {type: String as PropType<'comfortable' | 'compact' | 'default'>, default: 'default'},
|
|
35
|
+
variant: {type: String as PropType<'underlined' | 'outlined' | 'filled' | 'solo' | 'solo-inverted' | 'solo-filled' | 'plain'>, default: 'filled'},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const field: IEntityCrudField = {
|
|
40
|
+
default: undefined,
|
|
41
|
+
label: label || entityName,
|
|
42
|
+
name: name || entityName,
|
|
43
|
+
type: 'ref'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
|
|
51
|
+
<crud-autocomplete
|
|
52
|
+
v-model="valueModel"
|
|
53
|
+
:field="field"
|
|
54
|
+
:entity="FileEntityCrud.instance"
|
|
55
|
+
:hint="hint"
|
|
56
|
+
:persistent-hint="persistentHint"
|
|
57
|
+
:error-messages="errorMessages"
|
|
58
|
+
:rules="rules"
|
|
59
|
+
:density="density"
|
|
60
|
+
:variant="variant"
|
|
61
|
+
:readonly="readonly"
|
|
62
|
+
:clearable="clearable"
|
|
63
|
+
:hide-details="hideDetails"
|
|
64
|
+
:single-line="singleLine"
|
|
65
|
+
:prepend-icon="prependIcon"
|
|
66
|
+
:append-icon="appendIcon"
|
|
67
|
+
:prepend-inner-icon="prependInnerIcon"
|
|
68
|
+
:append-inner-icon="appendInnerIcon"
|
|
69
|
+
:add-on-the-fly="addOnTheFly"
|
|
70
|
+
></crud-autocomplete>
|
|
71
|
+
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<style scoped>
|
|
75
|
+
|
|
76
|
+
</style>
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue';
|
|
3
|
+
import { formatDateTime } from '@drax/common-front';
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
modelValue: {
|
|
7
|
+
type: Object,
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const tab = ref('details');
|
|
13
|
+
|
|
14
|
+
const file = computed(() => props.modelValue);
|
|
15
|
+
|
|
16
|
+
function formatFileSize(bytes: number) {
|
|
17
|
+
if (!bytes) return '0 B';
|
|
18
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
19
|
+
let i = 0;
|
|
20
|
+
let size = bytes;
|
|
21
|
+
while (size >= 1024 && i < units.length - 1) {
|
|
22
|
+
size /= 1024;
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
return `${size.toFixed(2)} ${units[i]}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isImage = computed(() => file.value?.mimetype?.startsWith('image/'));
|
|
29
|
+
const isPdf = computed(() => file.value?.mimetype === 'application/pdf');
|
|
30
|
+
|
|
31
|
+
const fileIcon = computed(() => {
|
|
32
|
+
if (isImage.value) return 'mdi-image';
|
|
33
|
+
if (isPdf.value) return 'mdi-file-pdf-box';
|
|
34
|
+
if (file.value?.mimetype?.includes('video')) return 'mdi-video';
|
|
35
|
+
if (file.value?.mimetype?.includes('audio')) return 'mdi-music-box';
|
|
36
|
+
if (file.value?.mimetype?.includes('zip') || file.value?.mimetype?.includes('compressed')) return 'mdi-folder-zip';
|
|
37
|
+
return 'mdi-file';
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<v-card class="media-field-card mx-auto rounded-xl elevation-4" max-width="800">
|
|
43
|
+
<div class="glass-header bg-primary pa-6 d-flex align-center">
|
|
44
|
+
<v-avatar size="64" color="surface" class="mr-4 elevation-2 text-primary">
|
|
45
|
+
<v-icon size="36">{{ fileIcon }}</v-icon>
|
|
46
|
+
</v-avatar>
|
|
47
|
+
<div>
|
|
48
|
+
<h2 class="text-h5 font-weight-bold mb-1 header-title text-truncate" style="max-width: 600px;">
|
|
49
|
+
{{ file?.filename || 'Unknown File' }}
|
|
50
|
+
</h2>
|
|
51
|
+
<div class="text-subtitle-2 opacity-80 d-flex align-center">
|
|
52
|
+
<v-chip size="small" color="surface" variant="outlined" class="mr-2 font-weight-medium">
|
|
53
|
+
{{ file?.extension?.toUpperCase() || 'FILE' }}
|
|
54
|
+
</v-chip>
|
|
55
|
+
{{ formatFileSize(file?.size) }}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<v-tabs
|
|
61
|
+
v-model="tab"
|
|
62
|
+
color="primary"
|
|
63
|
+
align-tabs="center"
|
|
64
|
+
class="border-b"
|
|
65
|
+
>
|
|
66
|
+
<v-tab value="details">
|
|
67
|
+
<v-icon start>mdi-information-outline</v-icon>
|
|
68
|
+
Detalles
|
|
69
|
+
</v-tab>
|
|
70
|
+
<v-tab value="preview" :disabled="!isImage && !isPdf && !file?.url">
|
|
71
|
+
<v-icon start>mdi-eye-outline</v-icon>
|
|
72
|
+
Vista Previa
|
|
73
|
+
</v-tab>
|
|
74
|
+
</v-tabs>
|
|
75
|
+
|
|
76
|
+
<v-card-text class="pa-0">
|
|
77
|
+
<v-window v-model="tab">
|
|
78
|
+
<v-window-item value="details">
|
|
79
|
+
<v-container class="pa-6">
|
|
80
|
+
<v-row>
|
|
81
|
+
<v-col cols="12" md="6">
|
|
82
|
+
<v-list class="bg-transparent" lines="two">
|
|
83
|
+
<v-list-item>
|
|
84
|
+
<template v-slot:prepend>
|
|
85
|
+
<v-icon color="primary" class="mr-3">mdi-file-document-outline</v-icon>
|
|
86
|
+
</template>
|
|
87
|
+
<v-list-item-title class="font-weight-medium">Nombre de Archivo</v-list-item-title>
|
|
88
|
+
<v-list-item-subtitle class="text-wrap">{{ file?.filename }}</v-list-item-subtitle>
|
|
89
|
+
</v-list-item>
|
|
90
|
+
|
|
91
|
+
<v-divider inset></v-divider>
|
|
92
|
+
|
|
93
|
+
<v-list-item>
|
|
94
|
+
<template v-slot:prepend>
|
|
95
|
+
<v-icon color="primary" class="mr-3">mdi-shape-outline</v-icon>
|
|
96
|
+
</template>
|
|
97
|
+
<v-list-item-title class="font-weight-medium">Tipo MIME</v-list-item-title>
|
|
98
|
+
<v-list-item-subtitle>{{ file?.mimetype || 'Desconocido' }}</v-list-item-subtitle>
|
|
99
|
+
</v-list-item>
|
|
100
|
+
|
|
101
|
+
<v-divider inset></v-divider>
|
|
102
|
+
|
|
103
|
+
<v-list-item>
|
|
104
|
+
<template v-slot:prepend>
|
|
105
|
+
<v-icon color="primary" class="mr-3">mdi-weight</v-icon>
|
|
106
|
+
</template>
|
|
107
|
+
<v-list-item-title class="font-weight-medium">Tamaño</v-list-item-title>
|
|
108
|
+
<v-list-item-subtitle>{{ formatFileSize(file?.size) }}</v-list-item-subtitle>
|
|
109
|
+
</v-list-item>
|
|
110
|
+
|
|
111
|
+
<v-divider inset></v-divider>
|
|
112
|
+
|
|
113
|
+
<v-list-item>
|
|
114
|
+
<template v-slot:prepend>
|
|
115
|
+
<v-icon color="primary" class="mr-3">mdi-eye-circle-outline</v-icon>
|
|
116
|
+
</template>
|
|
117
|
+
<v-list-item-title class="font-weight-medium">Visualizaciones / Hits</v-list-item-title>
|
|
118
|
+
<v-list-item-subtitle>{{ file?.hits || 0 }}</v-list-item-subtitle>
|
|
119
|
+
</v-list-item>
|
|
120
|
+
</v-list>
|
|
121
|
+
</v-col>
|
|
122
|
+
|
|
123
|
+
<v-col cols="12" md="6">
|
|
124
|
+
<v-list class="bg-transparent" lines="two">
|
|
125
|
+
<v-list-item>
|
|
126
|
+
<template v-slot:prepend>
|
|
127
|
+
<v-icon color="primary" class="mr-3">mdi-calendar-plus</v-icon>
|
|
128
|
+
</template>
|
|
129
|
+
<v-list-item-title class="font-weight-medium">Fecha de Creación</v-list-item-title>
|
|
130
|
+
<v-list-item-subtitle>{{ file?.createdAt ? formatDateTime(file.createdAt) : '-' }}</v-list-item-subtitle>
|
|
131
|
+
</v-list-item>
|
|
132
|
+
|
|
133
|
+
<v-divider inset></v-divider>
|
|
134
|
+
|
|
135
|
+
<v-list-item>
|
|
136
|
+
<template v-slot:prepend>
|
|
137
|
+
<v-icon color="primary" class="mr-3">mdi-eye-check-outline</v-icon>
|
|
138
|
+
</template>
|
|
139
|
+
<v-list-item-title class="font-weight-medium">Último Acceso</v-list-item-title>
|
|
140
|
+
<v-list-item-subtitle>{{ file?.lastAccess ? formatDateTime(file.lastAccess) : '-' }}</v-list-item-subtitle>
|
|
141
|
+
</v-list-item>
|
|
142
|
+
|
|
143
|
+
<v-divider inset></v-divider>
|
|
144
|
+
|
|
145
|
+
<v-list-item>
|
|
146
|
+
<template v-slot:prepend>
|
|
147
|
+
<v-icon color="primary" class="mr-3">mdi-clock-alert-outline</v-icon>
|
|
148
|
+
</template>
|
|
149
|
+
<v-list-item-title class="font-weight-medium">Fecha de Expiración</v-list-item-title>
|
|
150
|
+
<v-list-item-subtitle :class="{'text-error': file?.expiresAt && new Date(file.expiresAt) < new Date(), 'text-warning': file?.expiresAt && new Date(file.expiresAt) >= new Date()}">
|
|
151
|
+
{{ file?.expiresAt ? formatDateTime(file.expiresAt) : 'Sin expiración' }}
|
|
152
|
+
</v-list-item-subtitle>
|
|
153
|
+
</v-list-item>
|
|
154
|
+
|
|
155
|
+
<v-divider inset></v-divider>
|
|
156
|
+
|
|
157
|
+
<v-list-item>
|
|
158
|
+
<template v-slot:prepend>
|
|
159
|
+
<v-icon color="primary" class="mr-3">mdi-tag-multiple-outline</v-icon>
|
|
160
|
+
</template>
|
|
161
|
+
<v-list-item-title class="font-weight-medium mb-1">Etiquetas</v-list-item-title>
|
|
162
|
+
<v-list-item-subtitle>
|
|
163
|
+
<template v-if="file?.tags && file.tags.length > 0">
|
|
164
|
+
<v-chip
|
|
165
|
+
v-for="(tag, index) in file.tags"
|
|
166
|
+
:key="index"
|
|
167
|
+
color="primary"
|
|
168
|
+
size="small"
|
|
169
|
+
class="mr-1 mb-1"
|
|
170
|
+
variant="tonal"
|
|
171
|
+
>
|
|
172
|
+
{{ tag }}
|
|
173
|
+
</v-chip>
|
|
174
|
+
</template>
|
|
175
|
+
<template v-else>
|
|
176
|
+
<span class="text-grey">Sin etiquetas</span>
|
|
177
|
+
</template>
|
|
178
|
+
</v-list-item-subtitle>
|
|
179
|
+
</v-list-item>
|
|
180
|
+
</v-list>
|
|
181
|
+
</v-col>
|
|
182
|
+
</v-row>
|
|
183
|
+
<v-row v-if="file?.description">
|
|
184
|
+
<v-col cols="12">
|
|
185
|
+
<v-card class="bg-surface-variant rounded-lg pa-4" variant="tonal">
|
|
186
|
+
<div class="text-subtitle-2 font-weight-bold mb-2 d-flex align-center text-primary">
|
|
187
|
+
<v-icon size="small" class="mr-2">mdi-text-box-outline</v-icon>
|
|
188
|
+
Descripción
|
|
189
|
+
</div>
|
|
190
|
+
<p class="text-body-2">{{ file.description }}</p>
|
|
191
|
+
</v-card>
|
|
192
|
+
</v-col>
|
|
193
|
+
</v-row>
|
|
194
|
+
</v-container>
|
|
195
|
+
</v-window-item>
|
|
196
|
+
|
|
197
|
+
<v-window-item value="preview">
|
|
198
|
+
<div class="preview-container bg-black d-flex align-center justify-center relative">
|
|
199
|
+
<template v-if="isImage">
|
|
200
|
+
<v-img
|
|
201
|
+
:src="file?.url"
|
|
202
|
+
max-height="600"
|
|
203
|
+
contain
|
|
204
|
+
>
|
|
205
|
+
<template v-slot:placeholder>
|
|
206
|
+
<div class="d-flex align-center justify-center fill-height">
|
|
207
|
+
<v-progress-circular color="primary" indeterminate></v-progress-circular>
|
|
208
|
+
</div>
|
|
209
|
+
</template>
|
|
210
|
+
</v-img>
|
|
211
|
+
</template>
|
|
212
|
+
<template v-else-if="isPdf">
|
|
213
|
+
<iframe
|
|
214
|
+
:src="file?.url"
|
|
215
|
+
width="100%"
|
|
216
|
+
height="600"
|
|
217
|
+
style="border: none;"
|
|
218
|
+
title="Vista previa del PDF"
|
|
219
|
+
></iframe>
|
|
220
|
+
</template>
|
|
221
|
+
<template v-else>
|
|
222
|
+
<div class="pa-10 text-center text-white">
|
|
223
|
+
<v-icon size="64" color="grey-lighten-1" class="mb-4">mdi-file-hidden</v-icon>
|
|
224
|
+
<div class="text-h6">Vista previa no disponible</div>
|
|
225
|
+
<div class="text-body-2 text-grey-lighten-1 mt-2">
|
|
226
|
+
No se puede generar una vista previa para este tipo de archivo.
|
|
227
|
+
</div>
|
|
228
|
+
<v-btn
|
|
229
|
+
v-if="file?.url"
|
|
230
|
+
:href="file?.url"
|
|
231
|
+
target="_blank"
|
|
232
|
+
color="primary"
|
|
233
|
+
variant="flat"
|
|
234
|
+
class="mt-6"
|
|
235
|
+
prepend-icon="mdi-download"
|
|
236
|
+
>
|
|
237
|
+
Descargar Archivo
|
|
238
|
+
</v-btn>
|
|
239
|
+
</div>
|
|
240
|
+
</template>
|
|
241
|
+
</div>
|
|
242
|
+
</v-window-item>
|
|
243
|
+
</v-window>
|
|
244
|
+
</v-card-text>
|
|
245
|
+
|
|
246
|
+
<v-divider></v-divider>
|
|
247
|
+
|
|
248
|
+
<v-card-actions class="pa-4 d-flex justify-space-between bg-surface">
|
|
249
|
+
<div class="d-flex align-center text-caption text-medium-emphasis">
|
|
250
|
+
<v-icon size="small" class="mr-1">mdi-account-outline</v-icon>
|
|
251
|
+
Creado por: <span class="font-weight-bold ml-1">{{ file?.createdBy?.username || 'Sistema' }}</span>
|
|
252
|
+
</div>
|
|
253
|
+
<div>
|
|
254
|
+
<v-btn
|
|
255
|
+
v-if="file?.url"
|
|
256
|
+
:href="file?.url"
|
|
257
|
+
target="_blank"
|
|
258
|
+
color="primary"
|
|
259
|
+
variant="tonal"
|
|
260
|
+
prepend-icon="mdi-open-in-new"
|
|
261
|
+
>
|
|
262
|
+
Abrir Externo
|
|
263
|
+
</v-btn>
|
|
264
|
+
</div>
|
|
265
|
+
</v-card-actions>
|
|
266
|
+
</v-card>
|
|
267
|
+
</template>
|
|
268
|
+
|
|
269
|
+
<style scoped>
|
|
270
|
+
.media-field-card {
|
|
271
|
+
overflow: hidden;
|
|
272
|
+
border: 1px solid rgba(0,0,0,0.05);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.glass-header {
|
|
276
|
+
position: relative;
|
|
277
|
+
overflow: hidden;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.glass-header::before {
|
|
281
|
+
content: '';
|
|
282
|
+
position: absolute;
|
|
283
|
+
top: 0;
|
|
284
|
+
left: 0;
|
|
285
|
+
right: 0;
|
|
286
|
+
bottom: 0;
|
|
287
|
+
background: radial-gradient(circle at top right, rgba(255,255,255,0.2) 0%, transparent 60%);
|
|
288
|
+
pointer-events: none;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.header-title {
|
|
292
|
+
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.preview-container {
|
|
296
|
+
min-height: 400px;
|
|
297
|
+
width: 100%;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.v-list-item {
|
|
301
|
+
padding-left: 0;
|
|
302
|
+
}
|
|
303
|
+
</style>
|
|
@@ -48,7 +48,7 @@ function onFileClick() {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const valueModelUrl = computed(() =>
|
|
51
|
-
valueModel.value
|
|
51
|
+
valueModel.value?.url || '')
|
|
52
52
|
|
|
53
53
|
async function onFileChanged(e: Event) {
|
|
54
54
|
if (e.target && (e.target as HTMLInputElement).files) {
|
|
@@ -101,12 +101,12 @@ defineEmits(['updateValue'])
|
|
|
101
101
|
|
|
102
102
|
const isImage = computed(() => {
|
|
103
103
|
|
|
104
|
-
if (typeof valueModel.value
|
|
104
|
+
if (valueModel.value && (typeof valueModel.value?.url !== 'string' || !valueModel.value?.url.trim())) return false;
|
|
105
105
|
|
|
106
106
|
// supports optional query/hash: ".../file.jpg?x=1#y"
|
|
107
107
|
const imageExtRegex = /\.(?:jpe?g|png|gif|webp|bmp|svg|tiff?|avif|ico)(?:[?#].*)?$/i;
|
|
108
108
|
|
|
109
|
-
return imageExtRegex.test(valueModel.value
|
|
109
|
+
return imageExtRegex.test(valueModel.value?.url);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
</script>
|
|
@@ -157,7 +157,7 @@ const isImage = computed(() => {
|
|
|
157
157
|
<v-btn @click="onFileClick" :loading="loading" density="compact" color="grey" variant="text">Click | Drag & Drop</v-btn>
|
|
158
158
|
|
|
159
159
|
<template v-if="preview && isImage">
|
|
160
|
-
<v-img :src="valueModel
|
|
160
|
+
<v-img :src="valueModel?.url" alt="Preview" :height="previewHeight" class="mt-4"></v-img>
|
|
161
161
|
</template>
|
|
162
162
|
|
|
163
163
|
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import FileEntityCrud from '../../cruds/FileEntityCrud'
|
|
4
|
+
import {Crud, useCrudStore} from "@drax/crud-vue";
|
|
5
|
+
import {formatDateTime} from "@drax/common-front"
|
|
6
|
+
import MediaFieldView from "../MediaFieldView.vue";
|
|
7
|
+
|
|
8
|
+
function formatFileSize(bytes: number) {
|
|
9
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
10
|
+
|
|
11
|
+
let i = 0;
|
|
12
|
+
let size = bytes;
|
|
13
|
+
|
|
14
|
+
while (size >= 1024 && i < units.length - 1) {
|
|
15
|
+
size /= 1024;
|
|
16
|
+
i++;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return `${size.toFixed(2)} ${units[i]}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const store = useCrudStore(FileEntityCrud.instance.name)
|
|
23
|
+
|
|
24
|
+
</script>formatDateTime
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<crud :entity="FileEntityCrud.instance">
|
|
28
|
+
<template v-slot:item.tags="{value}"><v-chip v-for="v in value">{{v}}</v-chip></template>
|
|
29
|
+
<template v-slot:item.size="{value}">{{formatFileSize(value)}}</template>
|
|
30
|
+
<template v-slot:item.lastAccess="{value}">{{formatDateTime(value)}}</template>
|
|
31
|
+
<template v-slot:item.createdAt="{value}">{{formatDateTime(value)}}</template>
|
|
32
|
+
<template v-slot:item.updatedAt="{value}">{{formatDateTime(value)}}</template>
|
|
33
|
+
<template v-slot:item.expiresAt="{value}">{{formatDateTime(value)}}</template>
|
|
34
|
+
|
|
35
|
+
<template v-if="store.operation === 'view' "
|
|
36
|
+
v-slot:form>
|
|
37
|
+
<media-field-view v-model="store.form" />
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
</crud>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
|
|
46
|
+
</style>
|
|
47
|
+
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {EntityCrud} from "@drax/crud-vue";
|
|
2
|
+
import type {
|
|
3
|
+
IDraxCrudProvider,
|
|
4
|
+
IEntityCrud,
|
|
5
|
+
IEntityCrudField,
|
|
6
|
+
IEntityCrudFilter,
|
|
7
|
+
IEntityCrudHeader,
|
|
8
|
+
IEntityCrudPermissions,
|
|
9
|
+
IEntityCrudRefs,
|
|
10
|
+
IEntityCrudRules
|
|
11
|
+
} from "@drax/crud-share";
|
|
12
|
+
import {FileSystemFactory} from "@drax/media-front";
|
|
13
|
+
|
|
14
|
+
//Import EntityCrud Refs
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileEntityCrud extends EntityCrud implements IEntityCrud {
|
|
18
|
+
|
|
19
|
+
static singleton: FileEntityCrud
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
this.name = 'File'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static get instance(): FileEntityCrud {
|
|
27
|
+
if (!FileEntityCrud.singleton) {
|
|
28
|
+
FileEntityCrud.singleton = new FileEntityCrud()
|
|
29
|
+
}
|
|
30
|
+
return FileEntityCrud.singleton
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get permissions(): IEntityCrudPermissions {
|
|
34
|
+
return {
|
|
35
|
+
manage: 'file:manage',
|
|
36
|
+
view: 'file:view',
|
|
37
|
+
create: 'file:create',
|
|
38
|
+
update: 'file:update',
|
|
39
|
+
delete: 'file:delete'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get headers(): IEntityCrudHeader[] {
|
|
44
|
+
return [
|
|
45
|
+
{title: 'filename', key: 'filename', align: 'start'},
|
|
46
|
+
{title: 'url', key: 'url', align: 'start'},
|
|
47
|
+
{title: 'mimetype', key: 'mimetype', align: 'start'},
|
|
48
|
+
{title: 'extension', key: 'extension', align: 'start'},
|
|
49
|
+
{title: 'size', key: 'size', align: 'start'},
|
|
50
|
+
{title: 'type', key: 'type', align: 'start'},
|
|
51
|
+
{title: 'createdAt', key: 'createdAt', align: 'start'},
|
|
52
|
+
{title: 'createdBy', key: 'createdBy.username', align: 'start'},
|
|
53
|
+
{title: 'lastAccess', key: 'lastAccess', align: 'start'},
|
|
54
|
+
{title: 'ttlSeconds', key: 'ttlSeconds', align: 'start'},
|
|
55
|
+
{title: 'expiresAt', key: 'expiresAt', align: 'start'},
|
|
56
|
+
// {title: 'isPublic', key: 'isPublic', align: 'start'},
|
|
57
|
+
{title: 'hits', key: 'hits', align: 'start'},
|
|
58
|
+
{title: 'updatedAt', key: 'updatedAt', align: 'start'},
|
|
59
|
+
{title: 'updatedBy', key: 'updatedBy.username', align: 'start'},
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get selectedHeaders(): string[] {
|
|
64
|
+
return ['filename', 'mimetype', 'extension', 'size', 'createdAt','createdBy.username','lastAccess','hits'];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get actionHeaders(): IEntityCrudHeader[] {
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
title: 'action.actions',
|
|
71
|
+
key: 'actions',
|
|
72
|
+
sortable: false,
|
|
73
|
+
align: 'center',
|
|
74
|
+
minWidth: '190px',
|
|
75
|
+
fixed: 'end'
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get provider(): IDraxCrudProvider<any, any, any> {
|
|
81
|
+
return FileSystemFactory.getInstance()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get refs(): IEntityCrudRefs {
|
|
85
|
+
return {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get rules(): IEntityCrudRules {
|
|
89
|
+
return {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get fields(): IEntityCrudField[] {
|
|
94
|
+
return [
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
{name: 'filename', type: 'string', label: 'filename', default: '', readonly: true},
|
|
100
|
+
{name: 'url', type: 'string', label: 'url', default: '', readonly: true},
|
|
101
|
+
{name: 'mimetype', type: 'string', label: 'mimetype', default: '', readonly: true},
|
|
102
|
+
{name: 'encoding', type: 'string', label: 'encoding', default: '', readonly: true},
|
|
103
|
+
{name: 'extension', type: 'string', label: 'extension', default: '', readonly: true},
|
|
104
|
+
{name: 'size', type: 'number', label: 'size', default: null, readonly: true},
|
|
105
|
+
{name: 'type', type: 'string', label: 'type', default: '', readonly: true},
|
|
106
|
+
|
|
107
|
+
{name: 'description', type: 'longString', label: 'description', default: ''},
|
|
108
|
+
{name: 'tags', type: 'array.string', label: 'tags', default: []},
|
|
109
|
+
{name: 'createdFor', type: 'string', label: 'createdFor', default: ''},
|
|
110
|
+
{name: 'isPublic', type: 'boolean', label: 'isPublic', default: true},
|
|
111
|
+
|
|
112
|
+
{name: 'ttlSeconds', type: 'number', label: 'ttlSeconds', default: null},
|
|
113
|
+
{name: 'expiresAt', type: 'date', label: 'expiresAt', default: null},
|
|
114
|
+
|
|
115
|
+
{name: 'lastAccess', type: 'date', label: 'lastAccess', default: null, readonly: true},
|
|
116
|
+
{name: 'createdAt', type: 'date', label: 'createdAt', default: null, readonly: true},
|
|
117
|
+
{name: 'updatedAt', type: 'date', label: 'updatedAt', default: null, readonly: true},
|
|
118
|
+
{
|
|
119
|
+
name: 'createdBy',
|
|
120
|
+
type: 'object',
|
|
121
|
+
label: 'createdBy',
|
|
122
|
+
default: {"id": "''", "username": "''"},
|
|
123
|
+
objectFields: [
|
|
124
|
+
{name: 'id', type: 'string', label: 'id', default: '', readonly: true},
|
|
125
|
+
{name: 'username', type: 'string', label: 'username', default: '', readonly: true}
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'updatedBy',
|
|
130
|
+
type: 'object',
|
|
131
|
+
label: 'updatedBy',
|
|
132
|
+
default: {"id": "''", "username": "''"},
|
|
133
|
+
objectFields: [
|
|
134
|
+
{name: 'id', type: 'string', label: 'id', default: '', readonly: true},
|
|
135
|
+
{name: 'username', type: 'string', label: 'username', default: '', readonly: true}
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
{name: 'hits', type: 'number', label: 'hits', default: 0, readonly: true}
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get filters(): IEntityCrudFilter[] {
|
|
146
|
+
return [
|
|
147
|
+
//{name: '_id', type: 'string', label: 'ID', default: '', operator: 'eq' },
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get isViewable() {
|
|
152
|
+
return true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get isEditable() {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get isCreatable() {
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
get isDeletable() {
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
get isExportable() {
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
get exportFormats() {
|
|
172
|
+
return ['CSV', 'JSON']
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
get exportHeaders() {
|
|
176
|
+
return ['filename', 'mimetype', 'extension', 'size', 'createdAt','createdBy.username','lastAccess','hits']
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
get isImportable() {
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
get isColumnSelectable() {
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
get isGroupable() {
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
get importFormats() {
|
|
192
|
+
return ['CSV', 'JSON']
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
get dialogFullscreen() {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get tabs() {
|
|
200
|
+
return []
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
get menus() {
|
|
204
|
+
return []
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
get searchEnable() {
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
get filtersEnable() {
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get dynamicFiltersEnable() {
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
get containerFluid(){
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export default FileEntityCrud
|
|
227
|
+
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import MediaField from "./components/MediaField.vue";
|
|
2
2
|
import MediaFullField from "./components/MediaFullField.vue";
|
|
3
|
+
import FileCombobox from "./comboboxes/FileCombobox.vue";
|
|
4
|
+
import FileCrud from "./components/cruds/FileCrud.vue";
|
|
5
|
+
import FileEntityCrud from "./cruds/FileEntityCrud";
|
|
6
|
+
import FileCrudPage from "./pages/crud/FileCrudPage.vue";
|
|
7
|
+
import { FileCrudRoute } from "./routes/FileCrudRoute";
|
|
8
|
+
import MediaRoutes from "./routes/index";
|
|
3
9
|
|
|
4
10
|
export {
|
|
5
11
|
MediaField,
|
|
6
|
-
MediaFullField
|
|
12
|
+
MediaFullField,
|
|
13
|
+
FileCombobox,
|
|
14
|
+
FileCrud,
|
|
15
|
+
FileEntityCrud,
|
|
16
|
+
FileCrudPage,
|
|
17
|
+
FileCrudRoute,
|
|
18
|
+
MediaRoutes
|
|
7
19
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import FileCrudPage from "../pages/crud/FileCrudPage.vue";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const FileCrudRoute = [
|
|
6
|
+
{
|
|
7
|
+
name: 'FileCrudPage',
|
|
8
|
+
path: '/crud/file',
|
|
9
|
+
component: FileCrudPage,
|
|
10
|
+
meta: {
|
|
11
|
+
auth: true,
|
|
12
|
+
permission: 'file:manage',
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export default FileCrudRoute
|
|
18
|
+
export { FileCrudRoute }
|