Browse Source

Feature: Mobile - Use inner Form value for rendering files

Vladimir Sheremet 2 years ago
parent
commit
472ba72a26

+ 1 - 0
.eslintrc.js

@@ -43,6 +43,7 @@ module.exports = {
       'WithStatement',
     ],
 
+    'no-underscore-dangle': 'off',
     'no-param-reassign': 'off',
 
     'func-style': ['error', 'expression'],

+ 1 - 1
app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleBubble.vue

@@ -183,7 +183,7 @@ const previewImage = (event: Event, attachment: TicketArticleAttachment) => {
           :key="attachment.internalId"
           :file="attachment"
           :download-url="attachment.downloadUrl"
-          :preview-url="attachment.content"
+          :preview-url="attachment.previewUrl"
           :no-preview="!$c.ui_ticket_zoom_attachments_preview"
           :wrapper-class="colorsClasses.file"
           :icon-class="colorsClasses.icon"

+ 1 - 1
app/frontend/apps/mobile/pages/ticket/composable/useArticleAttachments.ts

@@ -37,7 +37,7 @@ export const useArticleAttachments = (options: AttachmentsOptions) => {
 
       return {
         ...attachment,
-        content: previewUrl,
+        previewUrl,
         canDownload,
         downloadUrl,
       }

+ 0 - 1
app/frontend/apps/mobile/sw/sw.ts

@@ -1,6 +1,5 @@
 // Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
 /* eslint-disable no-restricted-globals */
-/* eslint-disable no-underscore-dangle */
 
 import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'
 import { clientsClaim } from 'workbox-core'

+ 0 - 1
app/frontend/shared/components/Form/composables/useValue.ts

@@ -4,7 +4,6 @@ import { computed, type Ref } from 'vue'
 import { type FormFieldContext } from '../types/field'
 
 const useValue = (context: Ref<FormFieldContext<{ multiple?: boolean }>>) => {
-  // eslint-disable-next-line no-underscore-dangle
   const currentValue = computed(() => context.value._value)
 
   const hasValue = computed(() => {

+ 31 - 30
app/frontend/shared/components/Form/fields/FieldFile/FieldFileInput.vue

@@ -1,35 +1,35 @@
 <!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
 <script setup lang="ts">
-import type { InputHTMLAttributes, Ref } from 'vue'
-import { computed, ref } from 'vue'
+import { toRef, computed, ref } from 'vue'
 import type { FormFieldContext } from '@shared/components/Form/types/field'
 import { MutationHandler } from '@shared/server/apollo/handler'
 import useImageViewer from '@shared/composables/useImageViewer'
-import type { Scalars, StoredFile } from '@shared/graphql/types'
+import type { Scalars } from '@shared/graphql/types'
 import { convertFileList } from '@shared/utils/files'
 import useConfirmation from '@mobile/components/CommonConfirmation/composable'
 import CommonFilePreview from '@mobile/components/CommonFilePreview/CommonFilePreview.vue'
 import { useFormUploadCacheAddMutation } from './graphql/mutations/uploadCache/add.api'
 import { useFormUploadCacheRemoveMutation } from './graphql/mutations/uploadCache/remove.api'
+import type { FieldFileProps, FileUploaded } from './types'
 
-// TODO: First proof of concept, this needs to be finalized during the first real usage.
 // TODO: Add a test + story for this component.
 
 export interface Props {
-  context: FormFieldContext<{
-    accept: InputHTMLAttributes['accept']
-    capture: InputHTMLAttributes['capture']
-    multiple: InputHTMLAttributes['multiple']
-  }>
+  context: FormFieldContext<FieldFileProps>
 }
 
 const props = defineProps<Props>()
 
-type FileUploaded = Pick<StoredFile, 'id' | 'name' | 'size' | 'type'> & {
-  content: string
-}
+const contextReactive = toRef(props, 'context')
 
-const uploadFiles: Ref<FileUploaded[]> = ref([])
+const uploadFiles = computed<FileUploaded[]>({
+  get() {
+    return contextReactive.value._value || []
+  },
+  set(value) {
+    props.context.node.input(value)
+  },
+})
 
 const addFileMutation = new MutationHandler(useFormUploadCacheAddMutation({}))
 const addFileLoading = addFileMutation.loading()
@@ -50,22 +50,23 @@ const onFileChanged = async ($event: Event) => {
   const { files } = input
   const uploads = await convertFileList(files)
 
-  addFileMutation
-    .send({ formId: props.context.formId, files: uploads })
-    .then((data) => {
-      if (data?.formUploadCacheAdd?.uploadedFiles) {
-        uploadFiles.value.push(
-          ...data.formUploadCacheAdd.uploadedFiles.map((file, index) => {
-            return {
-              ...file,
-              content: uploads[index].content,
-            }
-          }),
-        )
-        input.value = ''
-        input.files = null
-      }
-    })
+  const data = await addFileMutation.send({
+    formId: props.context.formId,
+    files: uploads,
+  })
+
+  const uploadedFiles = data?.formUploadCacheAdd?.uploadedFiles
+
+  if (!uploadedFiles) return
+
+  const previewableFile = uploadedFiles.map((file, index) => ({
+    ...file,
+    previewUrl: uploads[index].content,
+  }))
+
+  uploadFiles.value = [...uploadFiles.value, ...previewableFile]
+  input.value = ''
+  input.files = null
 }
 
 const { waitForConfirmation } = useConfirmation()
@@ -131,7 +132,7 @@ const { showImage } = useImageViewer(uploadFiles)
       v-for="uploadFile of uploadFiles"
       :key="uploadFile.id"
       :file="uploadFile"
-      :preview-url="uploadFile.content"
+      :preview-url="uploadFile.previewUrl"
       @preview="canInteract && showImage(uploadFile)"
       @remove="canInteract && removeFile(uploadFile.id)"
     />

+ 28 - 0
app/frontend/shared/components/Form/fields/FieldFile/__tests__/FieldFile.spec.ts

@@ -1,5 +1,6 @@
 // Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
 
+import { getNode } from '@formkit/core'
 import { FormKit } from '@formkit/vue'
 import type { ExtendedRenderResult } from '@tests/support/components'
 import { renderComponent } from '@tests/support/components'
@@ -12,6 +13,7 @@ import { FormUploadCacheRemoveDocument } from '../graphql/mutations/uploadCache/
 const renderFileInput = (props: Record<string, unknown> = {}) => {
   return renderComponent(FormKit, {
     props: {
+      id: 'file',
       type: 'file',
       name: 'file',
       label: 'File',
@@ -101,6 +103,32 @@ describe('Fields - FieldFile', () => {
     )
   })
 
+  it('exposes files to Form', async () => {
+    const file = new File([], 'foo.png', { type: 'image/png' })
+    const { view } = await uploadFiles([file])
+
+    const node = getNode('file')
+    expect(node).toBeDefined()
+    expect(node?._value).toEqual([
+      expect.objectContaining({ name: 'foo.png', type: 'image/png' }),
+    ])
+
+    node?.input([
+      {
+        name: 'bar.png',
+        type: 'image/png',
+        id: '1',
+        size: 300,
+        previewUrl: 'https://localhost/bar.png',
+      },
+    ])
+
+    const filePreview = await view.findByRole('button', {
+      name: 'Preview bar.png',
+    })
+    expect(filePreview).toBeInTheDocument()
+  })
+
   it('renders non-images', async () => {
     const file = new File([], 'foo.txt', { type: 'text/plain' })
     const { view } = await uploadFiles([file])

+ 14 - 0
app/frontend/shared/components/Form/fields/FieldFile/types.ts

@@ -0,0 +1,14 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import type { StoredFile } from '@shared/graphql/types'
+import type { InputHTMLAttributes } from 'vue'
+
+export interface FieldFileProps {
+  accept?: InputHTMLAttributes['accept']
+  capture?: InputHTMLAttributes['capture']
+  multiple?: InputHTMLAttributes['multiple']
+}
+
+export type FileUploaded = Pick<StoredFile, 'id' | 'name' | 'size' | 'type'> & {
+  previewUrl?: string
+}

+ 2 - 2
app/frontend/shared/composables/useImageViewer.ts

@@ -11,7 +11,7 @@ interface ImagePreview {
 
 interface CachedFile {
   name?: string
-  content?: string
+  previewUrl?: string
   type?: Maybe<string>
 }
 
@@ -40,7 +40,7 @@ const useImageViewer = (viewFiles: MaybeRef<CachedFile[]>) => {
         // be different from original files, if they had non-image uploads
         indexMap.set(image, index)
         return {
-          src: image.content,
+          src: image.previewUrl,
           title: image.name,
         }
       })

+ 0 - 1
app/frontend/shared/initializer/globalProperties.ts

@@ -10,6 +10,5 @@ export default function initializeGlobalProperties(app: App): void {
 
   app.use(applicationConfigPlugin)
 
-  // eslint-disable-next-line no-underscore-dangle
   app.config.globalProperties.__ = window.__
 }

Some files were not shown because too many files changed in this diff