@testdracul/media-frontend 2.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 (93) hide show
  1. package/.env.development +4 -0
  2. package/.env.example +3 -0
  3. package/.eslintrc.json +25 -0
  4. package/babel.config.js +5 -0
  5. package/dist/dracul-media-frontend.es.js +16238 -0
  6. package/dist/dracul-media-frontend.umd.js +586 -0
  7. package/dist/media-frontend.css +1 -0
  8. package/docs-en.md +45 -0
  9. package/docs-es.md +45 -0
  10. package/package.json +56 -0
  11. package/readme.md +36 -0
  12. package/src/components/CsvWebViewer/CsvWebViewer.vue +81 -0
  13. package/src/components/CsvWebViewer/index.ts +4 -0
  14. package/src/components/FileUpload/FileUpload.vue +94 -0
  15. package/src/components/FileUpload/index.ts +4 -0
  16. package/src/components/FileUploadButton/FileUploadButton.vue +127 -0
  17. package/src/components/FileUploadButton/index.ts +4 -0
  18. package/src/components/FileUploadExpiration/FileUploadExpiration.vue +274 -0
  19. package/src/components/FileUploadExpiration/index.ts +4 -0
  20. package/src/components/FileUploadExpress/FileUploadExpress.vue +208 -0
  21. package/src/components/FileUploadExpress/index.ts +4 -0
  22. package/src/components/FileView/FileView.vue +336 -0
  23. package/src/components/FileView/index.ts +4 -0
  24. package/src/components/GroupsShow/GroupsShow.vue +40 -0
  25. package/src/components/GroupsShow/index.ts +4 -0
  26. package/src/components/MediaField/MediaField.vue +62 -0
  27. package/src/components/MediaField/index.ts +4 -0
  28. package/src/components/PdfWebViewer/PdfWebViewer.vue +81 -0
  29. package/src/components/PdfWebViewer/index.ts +4 -0
  30. package/src/components/UsersShow/UsersShow.vue +39 -0
  31. package/src/components/UsersShow/index.ts +4 -0
  32. package/src/components/XlsxWebViewer/XlsxWebViewer.vue +70 -0
  33. package/src/components/XlsxWebViewer/index.ts +4 -0
  34. package/src/helpers/redeableBytes.ts +9 -0
  35. package/src/i18n/index.ts +22 -0
  36. package/src/i18n/messages/DocMessages.ts +31 -0
  37. package/src/i18n/messages/ExtraMessages.ts +29 -0
  38. package/src/i18n/messages/FileMessages.ts +223 -0
  39. package/src/i18n/messages/UserStorageMessages.ts +145 -0
  40. package/src/i18n/permissions/FilePermissionMessages.ts +50 -0
  41. package/src/i18n/permissions/OldPermissionMessages.ts +59 -0
  42. package/src/i18n/permissions/UserStoragePermissionMessages.ts +40 -0
  43. package/src/index.ts +70 -0
  44. package/src/mixins/readableBytesMixin.ts +9 -0
  45. package/src/pages/FileManagementPage/FileCreate/FileCreate.vue +108 -0
  46. package/src/pages/FileManagementPage/FileCreate/index.ts +3 -0
  47. package/src/pages/FileManagementPage/FileCrud/FileCrud.vue +133 -0
  48. package/src/pages/FileManagementPage/FileCrud/index.ts +4 -0
  49. package/src/pages/FileManagementPage/FileDelete/FileDelete.vue +61 -0
  50. package/src/pages/FileManagementPage/FileDelete/index.ts +3 -0
  51. package/src/pages/FileManagementPage/FileFilters/FileFilters.vue +150 -0
  52. package/src/pages/FileManagementPage/FileFilters/index.ts +3 -0
  53. package/src/pages/FileManagementPage/FileForm/FileForm.vue +184 -0
  54. package/src/pages/FileManagementPage/FileForm/UserCombobox.vue +66 -0
  55. package/src/pages/FileManagementPage/FileForm/index.ts +3 -0
  56. package/src/pages/FileManagementPage/FileList/FileEditButton.vue +410 -0
  57. package/src/pages/FileManagementPage/FileList/FileList.vue +178 -0
  58. package/src/pages/FileManagementPage/FileList/index.ts +4 -0
  59. package/src/pages/FileManagementPage/FileShow/FileShow.vue +23 -0
  60. package/src/pages/FileManagementPage/FileShow/FileShowData.vue +35 -0
  61. package/src/pages/FileManagementPage/FileShow/index.ts +3 -0
  62. package/src/pages/FileManagementPage/FileUpdate/FileUpdate.vue +107 -0
  63. package/src/pages/FileManagementPage/FileUpdate/index.ts +4 -0
  64. package/src/pages/FileManagementPage/index.vue +20 -0
  65. package/src/pages/MediaDocPage/MediaDocCard.vue +35 -0
  66. package/src/pages/MediaDocPage/MediaDocPage.vue +78 -0
  67. package/src/pages/UserStoragePage/UserStorage.vue +311 -0
  68. package/src/pages/UserStoragePage/UserStorageForm/UserStorageForm.vue +172 -0
  69. package/src/pages/UserStoragePage/UserStorageUpdate/UserStorageUpdate.vue +91 -0
  70. package/src/pages/UserStoragePage/index.vue +14 -0
  71. package/src/providers/FileMetricsProvider.ts +47 -0
  72. package/src/providers/FileProvider.ts +60 -0
  73. package/src/providers/UploadProvider.ts +32 -0
  74. package/src/providers/UserStorageProvider.ts +47 -0
  75. package/src/providers/gql/almacenamientoPorUsuario.graphql +10 -0
  76. package/src/providers/gql/cantidadArchivosPorUsuario.graphql +10 -0
  77. package/src/providers/gql/fetchMediaVariables.graphql +6 -0
  78. package/src/providers/gql/fileCreate.graphql +27 -0
  79. package/src/providers/gql/fileDelete.graphql +7 -0
  80. package/src/providers/gql/fileFetch.graphql +29 -0
  81. package/src/providers/gql/fileFind.graphql +29 -0
  82. package/src/providers/gql/fileGlobalMetrics.graphql +6 -0
  83. package/src/providers/gql/filePaginate.graphql +38 -0
  84. package/src/providers/gql/fileUpdate.graphql +29 -0
  85. package/src/providers/gql/fileUpload.graphql +29 -0
  86. package/src/providers/gql/fileUploadAnonymous.graphql +25 -0
  87. package/src/providers/gql/fileUserMetrics.graphql +9 -0
  88. package/src/providers/gql/userStorageFetch.graphql +17 -0
  89. package/src/providers/gql/userStorageFindByUser.graphql +17 -0
  90. package/src/providers/gql/userStorageUpdate.graphql +31 -0
  91. package/src/routes/index.ts +32 -0
  92. package/vite.config.ts +65 -0
  93. package/vue.config.js +22 -0
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <v-col cols="12" sm="6">
3
+ <v-select
4
+ prepend-inner-icon="mdi-account-circle"
5
+ :items="items"
6
+ :item-title="'name'"
7
+ :item-value="'id'"
8
+ v-model="item"
9
+ :label="$t('media.file.createdBy')"
10
+ :loading="loading"
11
+ :error="hasInputErrors('createdBy')"
12
+ :error-messages="getInputErrors('createdBy')"
13
+ color="secondary"
14
+ item-color="secondary"
15
+
16
+ ></v-select>
17
+ </v-col>
18
+ </template>
19
+
20
+ <script setup>
21
+ import { ref, computed, onMounted } from 'vue'
22
+ import {InputErrorsByProps, RequiredRule} from '@testdracul/common-frontend'
23
+ import UserProvider from "../../../providers/UserProvider"
24
+
25
+ const props = defineProps({
26
+ modelValue: { type: String }
27
+ })
28
+
29
+ const emit = defineEmits(['update:modelValue'])
30
+
31
+ const items = ref([])
32
+ const loading = ref(false)
33
+ const formRef = ref(null)
34
+
35
+ const item = computed({
36
+ get() { return props.modelValue },
37
+ set(val) { emit('update:modelValue', val) }
38
+ })
39
+
40
+ const validate = async () => {
41
+ if (formRef.value) {
42
+ const { valid } = await formRef.value.validate()
43
+ return valid
44
+ }
45
+ return false
46
+ }
47
+
48
+ defineExpose({ validate })
49
+
50
+ const fetch = () => {
51
+ loading.value = true
52
+ UserProvider.fetchUsers().then(r => {
53
+ items.value = r.data.userFetch
54
+ }).catch(err => console.error(err))
55
+ .finally(() => loading.value = false)
56
+ }
57
+
58
+ onMounted(() => {
59
+ fetch()
60
+ })
61
+ </script>
62
+
63
+ <style scoped>
64
+
65
+ </style>
66
+
@@ -0,0 +1,3 @@
1
+ import FileForm from './FileForm'
2
+ export {FileForm}
3
+ export default FileForm
@@ -0,0 +1,410 @@
1
+ <template>
2
+ <v-tooltip location="bottom">
3
+ <template v-slot:activator="{ props }">
4
+ <v-btn
5
+ icon="mdi-note-edit"
6
+ size="x-small"
7
+ color="primary"
8
+ variant="text"
9
+ class="mx-1"
10
+ v-bind="props"
11
+ @click="dialog = true"
12
+ />
13
+ </template>
14
+ <span>Editar archivo</span>
15
+ </v-tooltip>
16
+
17
+ <v-dialog v-model="dialog" max-width="80vw" persistent>
18
+ <v-card style="max-height: 80vh; display: flex; flex-direction: column">
19
+ <v-toolbar color="primary" theme="dark">
20
+ <v-toolbar-title class="mt-3">
21
+ Editar archivo
22
+ <div class="text-subtitle-2 mt-n2 pa-0 text-white-50">{{ file.filename }}</div>
23
+ </v-toolbar-title>
24
+ </v-toolbar>
25
+
26
+ <v-card-text class="flex-grow-1 overflow-y-auto pa-4" style="min-height: 0">
27
+ <div class="d-flex mb-2 justify-end">
28
+
29
+ <v-tooltip location="bottom">
30
+ <template v-slot:activator="{ props }">
31
+ <v-btn
32
+ size="small"
33
+ icon="mdi-content-copy"
34
+ variant="text"
35
+ v-bind="props"
36
+ @click="copyToClipboard"
37
+ :disabled="!noteContent"
38
+ />
39
+ </template>
40
+ <span>Copiar</span>
41
+ </v-tooltip>
42
+
43
+ <v-tooltip location="bottom">
44
+ <template v-slot:activator="{ props }">
45
+ <v-btn
46
+ size="small"
47
+ icon="mdi-backspace-outline"
48
+ variant="text"
49
+ class="ml-2"
50
+ v-bind="props"
51
+ @click="clearContent"
52
+ :disabled="!noteContent"
53
+ />
54
+ </template>
55
+ <span>Limpiar</span>
56
+ </v-tooltip>
57
+
58
+ <v-tooltip location="bottom">
59
+ <template v-slot:activator="{ props }">
60
+ <v-btn
61
+ size="small"
62
+ icon="mdi-undo"
63
+ variant="text"
64
+ class="ml-2"
65
+ v-bind="props"
66
+ @click="undoAction"
67
+ :disabled="undoStack.length === 0"
68
+ />
69
+ </template>
70
+ <span>Deshacer</span>
71
+ </v-tooltip>
72
+
73
+ <v-tooltip location="bottom">
74
+ <template v-slot:activator="{ props }">
75
+ <v-btn
76
+ size="small"
77
+ icon="mdi-redo"
78
+ variant="text"
79
+ class="ml-2"
80
+ v-bind="props"
81
+ @click="redoAction"
82
+ :disabled="redoStack.length === 0"
83
+ />
84
+ </template>
85
+ <span>Rehacer</span>
86
+ </v-tooltip>
87
+
88
+ <v-spacer />
89
+
90
+ <v-tooltip location="bottom">
91
+ <template v-slot:activator="{ props }">
92
+ <v-btn
93
+ size="small"
94
+ :icon="isFormatted ? 'mdi-format-letter-case' : 'mdi-format-align-left'"
95
+ variant="text"
96
+ v-bind="props"
97
+ @click="toggleFormat"
98
+ :disabled="!isJson"
99
+ />
100
+ </template>
101
+ <span>{{ isFormatted ? 'Minificar JSON' : 'Formatear JSON' }}</span>
102
+ </v-tooltip>
103
+ </div>
104
+
105
+ <div
106
+ ref="editor"
107
+ class="dracul-json-editor"
108
+ :class="{ 'json-editor-error': hasErrors }"
109
+ contenteditable="true"
110
+ spellcheck="false"
111
+ autocorrect="off"
112
+ autocomplete="off"
113
+ autocapitalize="off"
114
+ @input="onEdit"
115
+ @blur="onBlur"
116
+ style="background: #2e2e2e !important; color: #ccc !important; white-space: pre-wrap !important; word-break: break-all !important; font-family: 'Fira Code', 'Consolas', monospace !important; outline: none !important; min-height: 300px !important; padding: 12px !important; border-radius: 4px !important; font-size: 14px !important; line-height: 1.5 !important; border: 1px solid #444 !important;"
117
+ ></div>
118
+
119
+ <div v-if="hasErrors" class="error-message">
120
+ {{ errorMessages[0] }}
121
+ </div>
122
+ </v-card-text>
123
+
124
+ <v-divider />
125
+
126
+ <v-card-actions>
127
+ <v-spacer />
128
+ <v-btn variant="text" @click="dialog = false" :disabled="loading">
129
+ {{ t('common.cancel') }}
130
+ </v-btn>
131
+ <v-btn :disabled="hasErrors || loading" color="primary" variant="flat" @click="saveNote" :loading="loading">
132
+ {{ t('common.update') }}
133
+ </v-btn>
134
+ </v-card-actions>
135
+ </v-card>
136
+ </v-dialog>
137
+ </template>
138
+
139
+ <script setup>
140
+ import { ref, computed, watch, nextTick } from 'vue'
141
+ import FileProvider from "../../../providers/FileProvider"
142
+ import { useStore } from 'vuex'
143
+ import { useI18n } from 'vue-i18n'
144
+
145
+ // Props y Emits
146
+ const props = defineProps({
147
+ file: {
148
+ type: Object,
149
+ required: true
150
+ }
151
+ })
152
+
153
+ const emit = defineEmits(['file-updated', 'itemUpdated'])
154
+
155
+ // Plugins
156
+ const store = useStore()
157
+ const { t } = useI18n()
158
+
159
+ // Estado Reactivo
160
+ const dialog = ref(false)
161
+ const noteContent = ref('')
162
+ const originalContent = ref('')
163
+ const isFormatted = ref(false)
164
+ const loading = ref(false)
165
+ const errorMessages = ref([])
166
+ const undoStack = ref([])
167
+ const redoStack = ref([])
168
+
169
+ // Referencia al div contenteditable
170
+ const editor = ref(null)
171
+
172
+ // Computados
173
+ const hasErrors = computed(() => errorMessages.value.length > 0)
174
+ const isJson = computed(() => {
175
+ if (!props.file?.extension) return false
176
+ const ext = props.file.extension.toLowerCase()
177
+ return (ext === '.json' || ext === 'json') && noteContent.value.trim() !== ''
178
+ })
179
+
180
+ // === MÉTODO MODIFICADO (Respetando tu nueva lógica) ===
181
+ const loadFileText = async (retries = 3) => {
182
+ try {
183
+ const authToken = store.state.user.access_token
184
+ if (!props.file.url) throw new Error("File URL is missing")
185
+
186
+ const cacheBuster = `t=${Date.now()}`
187
+ const finalUrl = props.file.url.includes('?')
188
+ ? `${props.file.url}&${cacheBuster}`
189
+ : `${props.file.url}?${cacheBuster}`
190
+
191
+ const requestOptions = {
192
+ method: 'GET',
193
+ headers: {}
194
+ }
195
+
196
+ if (authToken) {
197
+ requestOptions.headers['Authorization'] = `Bearer ${authToken}`
198
+ }
199
+
200
+ let response = await fetch(finalUrl, requestOptions)
201
+
202
+ if (!response.ok && authToken) {
203
+ delete requestOptions.headers['Authorization']
204
+ response = await fetch(finalUrl, requestOptions)
205
+ }
206
+
207
+ if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`)
208
+
209
+ noteContent.value = await response.text()
210
+ originalContent.value = noteContent.value
211
+ isFormatted.value = false
212
+ undoStack.value = []
213
+ redoStack.value = []
214
+
215
+ validateJson()
216
+ await nextTick()
217
+ setTimeout(() => renderEditor(), 150)
218
+ } catch (error) {
219
+ console.error(`Load attempt failed (${retries} retries left):`, error)
220
+ if (retries > 0) {
221
+ // Esperamos 1 segundo y reintentamos (el server puede estar reiniciandose)
222
+ setTimeout(() => loadFileText(retries - 1), 1000)
223
+ } else {
224
+ noteContent.value = ''
225
+ errorMessages.value = [`Error cargando el contenido: ${error.message}. Intenta de nuevo en unos segundos.`]
226
+ }
227
+ }
228
+ }
229
+
230
+ const validateJson = () => {
231
+ errorMessages.value = []
232
+ if (isJson.value) {
233
+ try {
234
+ JSON.parse(noteContent.value)
235
+ } catch {
236
+ errorMessages.value = ['Formato JSON inválido']
237
+ }
238
+ }
239
+ }
240
+
241
+ const toggleFormat = () => {
242
+ if (!isJson.value) return
243
+ try {
244
+ const obj = JSON.parse(noteContent.value)
245
+ pushUndo()
246
+ if (!isFormatted.value) {
247
+ noteContent.value = JSON.stringify(obj, null, 2)
248
+ isFormatted.value = true
249
+ } else {
250
+ noteContent.value = JSON.stringify(obj)
251
+ isFormatted.value = false
252
+ }
253
+ validateJson()
254
+ renderEditor()
255
+ } catch (e) {
256
+ console.error("Format error:", e)
257
+ errorMessages.value = ['No se pudo formatear: JSON inválido']
258
+ }
259
+ }
260
+
261
+ const onEdit = () => {
262
+ if (editor.value) {
263
+ const newContent = editor.value.innerText
264
+ if (newContent !== noteContent.value) {
265
+ // Guardamos el estado anterior para poder deshacer
266
+ undoStack.value.push(noteContent.value)
267
+ redoStack.value = []
268
+
269
+ noteContent.value = newContent
270
+ isFormatted.value = false
271
+ validateJson()
272
+ }
273
+ }
274
+ }
275
+
276
+ const onBlur = () => {
277
+ renderEditor()
278
+ }
279
+
280
+ const pushUndo = () => {
281
+ undoStack.value.push(noteContent.value)
282
+ redoStack.value = []
283
+ }
284
+
285
+ const undoAction = () => {
286
+ if (!undoStack.value.length) return
287
+ redoStack.value.push(noteContent.value)
288
+ noteContent.value = undoStack.value.pop()
289
+ validateJson()
290
+ renderEditor()
291
+ }
292
+
293
+ const redoAction = () => {
294
+ if (!redoStack.value.length) return
295
+ undoStack.value.push(noteContent.value)
296
+ noteContent.value = redoStack.value.pop()
297
+ validateJson()
298
+ renderEditor()
299
+ }
300
+
301
+ const copyToClipboard = async () => {
302
+ try {
303
+ await navigator.clipboard.writeText(noteContent.value)
304
+ } catch (err) {
305
+ console.error('Fallo al copiar: ', err)
306
+ }
307
+ }
308
+
309
+ const clearContent = () => {
310
+ pushUndo()
311
+ noteContent.value = ''
312
+ validateJson()
313
+ renderEditor()
314
+ }
315
+
316
+ const renderEditor = () => {
317
+ if (!editor.value) return
318
+ const text = noteContent.value || ''
319
+
320
+ let html = text
321
+ .replace(/&/g, '&amp;')
322
+ .replace(/</g, '&lt;')
323
+ .replace(/>/g, '&gt;')
324
+
325
+ if (isJson.value) {
326
+ html = html
327
+ .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"\s*?)(?=:)/g, '<span class="json-key" style="color: #c5a5c5 !important; font-weight: bold !important;">$1</span>')
328
+ .replace(/(:\s*)("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*")/g, '$1<span class="json-string" style="color: #8dc891 !important;">$2</span>')
329
+ .replace(/\b(true|false|null)\b/g, '<span class="json-boolean" style="color: #f99157 !important; font-weight: bold !important;">$1</span>')
330
+ .replace(/\b(-?\d+\.?\d*)\b(?![^<]*>)/g, '<span class="json-number" style="color: #f99157 !important;">$1</span>')
331
+ }
332
+
333
+ editor.value.innerHTML = html
334
+ }
335
+
336
+ const saveNote = async () => {
337
+ if (hasErrors.value) return
338
+ loading.value = true
339
+ errorMessages.value = []
340
+
341
+ try {
342
+ const mimeType = props.file.extension?.toLowerCase() === '.json' ? 'application/json' : 'text/plain'
343
+ const blob = new Blob([noteContent.value], { type: mimeType })
344
+ const filename = props.file.filename || 'file.txt'
345
+ const newFile = new File([blob], filename, { type: mimeType, lastModified: Date.now() })
346
+
347
+ const input = {
348
+ id: props.file.id,
349
+ description: props.file.description || '',
350
+ tags: props.file.tags || [],
351
+ expirationDate: props.file.expirationDate || null,
352
+ isPublic: props.file.isPublic || false,
353
+ groups: props.file.groups || [],
354
+ users: props.file.users || []
355
+ }
356
+
357
+ const result = await FileProvider.updateFile(input, newFile)
358
+
359
+ // Verificamos si hay errores de GraphQL en la respuesta
360
+ if (result?.errors && result.errors.length > 0) {
361
+ throw new Error(result.errors[0].message || 'Error en la respuesta del servidor')
362
+ }
363
+
364
+ if (result?.data?.fileUpdate) {
365
+ dialog.value = false
366
+ emit('file-updated', result.data.fileUpdate)
367
+
368
+ // Añadimos un pequeño delay antes de refrescar la lista para dar tiempo al backend
369
+ setTimeout(() => {
370
+ emit('itemUpdated')
371
+ }, 800)
372
+ } else {
373
+ throw new Error('La respuesta del servidor no fue exitosa')
374
+ }
375
+ } catch (error) {
376
+ console.error('Error guardando archivo:', error)
377
+ const msg = error.message.includes('fetch') ? 'Error de conexión con el servidor (posible reinicio)' : error.message
378
+ errorMessages.value = [msg]
379
+ } finally {
380
+ loading.value = false
381
+ }
382
+ }
383
+
384
+ // Watchers
385
+ watch(dialog, async (open) => {
386
+ if (open) {
387
+ errorMessages.value = []
388
+ await loadFileText()
389
+ }
390
+ })
391
+ </script>
392
+
393
+ <style>
394
+ /* Estilos globales para asegurar que funcionen con innerHTML */
395
+ .dracul-json-editor .json-key { color: #c5a5c5 !important; font-weight: bold !important; }
396
+ .dracul-json-editor .json-string { color: #8dc891 !important; }
397
+ .dracul-json-editor .json-number { color: #f99157 !important; }
398
+ .dracul-json-editor .json-boolean { color: #f99157 !important; font-weight: bold !important; }
399
+
400
+ .dracul-json-editor.json-editor-error {
401
+ border: 1px solid #ff5252 !important;
402
+ }
403
+
404
+ .error-message {
405
+ color: #ff5252 !important;
406
+ font-size: 0.875rem !important;
407
+ margin-top: 4px !important;
408
+ font-weight: bold !important;
409
+ }
410
+ </style>
@@ -0,0 +1,178 @@
1
+ <template>
2
+ <v-row row wrap>
3
+
4
+ <file-filters
5
+ @updateFilters="setFilters"
6
+ @clearFilter="clearFilters"
7
+ v-model="filters"
8
+ />
9
+
10
+ <v-col cols="12">
11
+
12
+ <v-data-table-server
13
+ class="mt-3"
14
+ :headers="headers"
15
+ :items="items"
16
+ :search="search"
17
+ :items-length="totalItems"
18
+ :loading="loading"
19
+ v-model:page="pageNumber"
20
+ v-model:items-per-page="itemsPerPage"
21
+ v-model:sort-by="sortBy"
22
+ :items-per-page-options="[5, 10, 25, 50, 100]"
23
+ :items-per-page-text="t('common.itemsPerPageText')"
24
+ @update:options="fetch"
25
+ density="compact"
26
+ hover
27
+ >
28
+
29
+ <template v-slot:[`item.isPublic`]="{ item }">
30
+ <div v-if="item.isPublic">
31
+ <v-icon color="success">mdi-check-circle</v-icon>
32
+ </div>
33
+ <div v-else>
34
+ <v-icon color="error">mdi-close-circle</v-icon>
35
+ </div>
36
+ </template>
37
+
38
+ <template v-slot:no-data>
39
+ <div class="text-center">{{ t('common.noData') }}</div>
40
+ </template>
41
+
42
+ <template v-slot:[`item.size`]="{item}">
43
+ {{ item.size.toFixed(2) }} Mb
44
+ </template>
45
+
46
+ <template v-slot:[`item.type`]="{item}">
47
+ {{ t(`media.file.${item.type}`) }}
48
+ </template>
49
+
50
+ <template v-slot:[`item.createdAt`]="{item}">
51
+ {{ getDateTimeFormat(item.createdAt, true) }}
52
+ </template>
53
+
54
+ <template v-slot:[`item.lastAccess`]="{item}">
55
+ {{ getDateTimeFormat(item.lastAccess, true) }}
56
+ </template>
57
+
58
+ <template v-slot:[`item.action`]="{ item }">
59
+ <show-button @click="$emit('show', item)"/>
60
+
61
+ <edit-button
62
+ v-if="hasPermission('FILE_UPDATE_ALL') ||
63
+ (hasPermission('FILE_UPDATE_OWN') && item.createdBy.user?.id === me?.id)"
64
+ @click="$emit('update', item)"
65
+ />
66
+
67
+ <file-edit-button v-if="editTextButtonMustBeRender(item.extension) && (hasPermission('FILE_UPDATE_ALL') || (hasPermission('FILE_UPDATE_OWN') && item.createdBy.user?.id === me?.id))"
68
+ :file="item"
69
+ @itemUpdated="$emit('itemUpdated')"
70
+ />
71
+
72
+ <delete-button
73
+ v-if="hasPermission('FILE_DELETE_ALL') ||
74
+ (hasPermission('FILE_DELETE_OWN') && item.createdBy.user?.id === me?.id)"
75
+ @click="$emit('delete', item)"
76
+ />
77
+ </template>
78
+
79
+ </v-data-table-server>
80
+ </v-col>
81
+ </v-row>
82
+ </template>
83
+
84
+ <script setup>
85
+ import { ref, computed, onBeforeMount } from 'vue'
86
+ import { useStore } from 'vuex'
87
+ import { useI18n } from 'vue-i18n'
88
+ import {DeleteButton, EditButton, ShowButton} from "@testdracul/common-frontend"
89
+ import {useDayjs} from "@testdracul/dayjs-frontend"
90
+ import FileEditButton from "./FileEditButton.vue"
91
+
92
+ // import redeableBytesMixin from "../../../mixins/readableBytesMixin";
93
+ import FileProvider from "../../../providers/FileProvider";
94
+ import FileFilters from "../FileFilters/FileFilters"
95
+
96
+ const emit = defineEmits(['update', 'delete', 'show', 'itemUpdated'])
97
+
98
+ const { t } = useI18n()
99
+ const store = useStore()
100
+ const { getDateTimeFormat } = useDayjs()
101
+
102
+ const items = ref([])
103
+ const totalItems = ref(0)
104
+ const loading = ref(false)
105
+ const sortBy = ref([])
106
+ const itemsPerPage = ref(5)
107
+ const pageNumber = ref(1)
108
+ const search = ref('')
109
+ const filters = ref([
110
+ { field: 'dateFrom', operator: '$gte', value: null },
111
+ { field: 'dateTo', operator: '$lte', value: null },
112
+ { field: 'filename', operator: '$regex', value: null },
113
+ { field: 'createdBy.user', operator: '$eq', value: null },
114
+ { field: 'type', operator: '$regex', value: null },
115
+ { field: 'minSize', operator: '$gte', value: null },
116
+ { field: 'maxSize', operator: '$lte', value: null },
117
+ { field: 'isPublic', operator: '$eq', value: null },
118
+ { field: 'groups', operator: '$eq', value: null },
119
+ { field: 'users', operator: '$eq', value: null }
120
+ ])
121
+
122
+ const hasPermission = (permission) => store.getters.hasPermission(permission)
123
+ const me = computed(() => store.getters.me)
124
+
125
+ const headers = computed(() => [
126
+ { title: t('media.file.filename'), key: 'filename' },
127
+ { title: t('media.file.type'), key: 'type' },
128
+ { title: t('media.file.size'), key: 'size' },
129
+ { title: t('media.file.createdAt'), key: 'createdAt' },
130
+ { title: t('media.file.lastAccess'), key: 'lastAccess' },
131
+ { title: t('media.file.createdBy'), key: 'createdBy.username' },
132
+ { title: t('media.file.isPublic'), key: 'isPublic' },
133
+ { title: t('media.file.hits'), key: 'hits' },
134
+ { title: t('common.actions'), key: 'action', sortable: false },
135
+ ])
136
+
137
+ const getOrderBy = computed(() => sortBy.value.length > 0 ? sortBy.value[0].key : null)
138
+ const getOrderDesc = computed(() => sortBy.value.length > 0 ? sortBy.value[0].order === 'desc' : false)
139
+
140
+ const fetch = () => {
141
+ loading.value = true
142
+ FileProvider.paginateFiles(
143
+ pageNumber.value,
144
+ itemsPerPage.value,
145
+ search.value,
146
+ filters.value,
147
+ getOrderBy.value,
148
+ getOrderDesc.value
149
+ ).then(r => {
150
+ items.value = r.data.filePaginate.items
151
+ totalItems.value = r.data.filePaginate.totalItems
152
+ }).catch(err => {
153
+ console.error(err)
154
+ }).finally(() => loading.value = false)
155
+ }
156
+
157
+ const setFilters = (fileFilters) => {
158
+ filters.value = fileFilters
159
+ fetch()
160
+ }
161
+
162
+ const clearFilters = () => {
163
+ filters.value.forEach(filter => {
164
+ filter.value = null
165
+ })
166
+ fetch()
167
+ }
168
+
169
+ const editTextButtonMustBeRender = (itemExtension) => {
170
+ return itemExtension === '.json' || itemExtension === '.md' || itemExtension === '.txt'
171
+ }
172
+
173
+ onBeforeMount(() => {
174
+ fetch()
175
+ })
176
+
177
+ defineExpose({ fetch })
178
+ </script>
@@ -0,0 +1,4 @@
1
+ import FileList from './FileList.vue'
2
+
3
+ export {FileList}
4
+ export default FileList
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <crud-show :title="title" :open="open" @close="$emit('close')">
3
+ <v-card-text>
4
+ <file-view :file="item" />
5
+ </v-card-text>
6
+ </crud-show>
7
+ </template>
8
+
9
+ <script setup>
10
+ import { ref } from 'vue'
11
+ import {CrudShow} from '@testdracul/common-frontend'
12
+ import FileView from "../../../components/FileView/FileView";
13
+
14
+ defineProps({
15
+ open: {type: Boolean, default: true},
16
+ item: {type: Object, required: true}
17
+ })
18
+
19
+ defineEmits(['close'])
20
+
21
+ const title = ref('media.file.showing')
22
+ </script>
23
+