Skip to content

Commit

Permalink
feat(comp:upload): add onMaxCountExceeded (#1673)
Browse files Browse the repository at this point in the history
refactor upload component
  • Loading branch information
sallerli1 committed Sep 8, 2023
1 parent 5da8900 commit 8faddc1
Show file tree
Hide file tree
Showing 20 changed files with 356 additions and 259 deletions.
11 changes: 11 additions & 0 deletions packages/components/upload/demo/MaxCount.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
v-model:files="files"
action="https://run.mocky.io/v3/7564bc4f-780e-43f7-bc58-467959ae3354"
:maxCount="maxCount"
@select="onSelect"
@maxCountExceeded="onMaxCountExceeded"
>
<IxButton>Upload</IxButton>
<template #list>
Expand All @@ -13,8 +15,17 @@
</template>

<script setup lang="ts">
import type { FilteredFile } from '@idux/components/upload'
import { ref } from 'vue'
const maxCount = ref(1)
const files = ref([])
const onSelect = (files: File[], filteredFiles: FilteredFile[]) => {
console.log(files, filteredFiles)
}
const onMaxCountExceeded = () => {
console.log('maxCountExceeded')
}
</script>
1 change: 1 addition & 0 deletions packages/components/upload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export type {
UploadFilesInstance,
UploadFilesComponent,
UploadFilesPublicProps as UploadFilesProps,
FilteredFile,
} from './src/types'
26 changes: 17 additions & 9 deletions packages/components/upload/src/Upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@

import { type Ref, defineComponent, provide, ref, shallowRef } from 'vue'

import { useControlledProp } from '@idux/cdk/utils'
import { useGlobalConfig } from '@idux/components/config'
import { IxImageViewer } from '@idux/components/image'

import FileSelector from './component/Selector'
import { useCmpClasses } from './composables/useDisplay'
import { useDrag } from './composables/useDrag'
import { useFileSelect } from './composables/useFileSelect'
import { useFilesData } from './composables/useFilesData'
import { useRequest } from './composables/useRequest'
import { useUploadControl } from './composables/useUploadControl'
import { uploadToken } from './token'
import { uploadProps } from './types'

Expand All @@ -24,20 +27,25 @@ export default defineComponent({
const locale = useGlobalConfig('locale')
const cpmClasses = useCmpClasses()
const [showSelector, setSelectorVisible] = useShowSelector()
const [files, onUpdateFiles] = useControlledProp(props, 'files', [])
const { fileUploading, abort, startUpload, upload } = useRequest(props, files)
const filesDataContext = useFilesData(props)
const selectFiles = useFileSelect(props, filesDataContext)
const dragContext = useDrag(props, selectFiles)

const uploadRequest = useRequest(props, filesDataContext)
const { abort, upload } = uploadRequest
const { viewerVisible, images, setViewerVisible } = useImageViewer()

useUploadControl(filesDataContext.fileList, uploadRequest)

provide(uploadToken, {
props,
locale,
files,
fileUploading,
onUpdateFiles,
abort,
startUpload,
upload,
selectFiles,
setViewerVisible,
setSelectorVisible,
...uploadRequest,
...filesDataContext,
...dragContext,
})

return () => (
Expand Down
20 changes: 9 additions & 11 deletions packages/components/upload/src/component/ImageCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,32 @@ import {
import { type FileOperation, useOperation } from '../composables/useOperation'
import { uploadToken } from '../token'
import { UploadFile, type UploadFileStatus, type UploadProps, uploadFilesProps } from '../types'
import { type IconsMap, renderOprIcon } from '../util/icon'
import { showDownload, showErrorTip, showPreview, showProgress, showRetry } from '../util/visible'
import { type IconsMap, renderOprIcon } from '../utils/icon'
import { showDownload, showErrorTip, showPreview, showProgress, showRetry } from '../utils/visible'

export default defineComponent({
name: 'IxUploadImageCardList',
props: uploadFilesProps,
setup(listProps) {
const { props: uploadProps, locale, files, upload, abort, onUpdateFiles, setViewerVisible } = inject(uploadToken)!
const uploadContext = inject(uploadToken)!
const { props: uploadProps, locale, fileList } = uploadContext
const icons = useIcon(listProps)
const cpmClasses = useCmpClasses()
const listClasses = useListClasses(uploadProps, 'imageCard')
const [, imageCardVisible] = useSelectorVisible(uploadProps, 'imageCard')
const showSelector = useShowSelector(uploadProps, files, imageCardVisible)
const showSelector = useShowSelector(uploadProps, fileList, imageCardVisible)
const { getThumbNode, revokeAll } = useThumb()
const fileOperation = useOperation(files, listProps, uploadProps, {
abort,
upload,
onUpdateFiles,
setViewerVisible,
})
const fileOperation = useOperation(listProps, uploadContext)
const selectorNode = renderSelector(cpmClasses)

onBeforeUnmount(revokeAll)

return () => (
<ul class={listClasses.value}>
{showSelector.value && selectorNode}
{files.value.map(file => renderItem(uploadProps, file, icons, cpmClasses, fileOperation, locale, getThumbNode))}
{fileList.value.map(file =>
renderItem(uploadProps, file, icons, cpmClasses, fileOperation, locale, getThumbNode),
)}
</ul>
)
},
Expand Down
20 changes: 8 additions & 12 deletions packages/components/upload/src/component/ImageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import type { UseThumb } from '../composables/useDisplay'
import type { FileOperation } from '../composables/useOperation'
import type { UploadFile, UploadProps } from '../types'
import type { IconsMap } from '../util/icon'
import type { IconsMap } from '../utils/icon'
import type { Locale } from '@idux/components/locales'
import type { ComputedRef } from 'vue'

Expand All @@ -21,31 +21,27 @@ import { useCmpClasses, useIcon, useListClasses, useThumb } from '../composables
import { useOperation } from '../composables/useOperation'
import { uploadToken } from '../token'
import { uploadFilesProps } from '../types'
import { renderIcon, renderOprIcon } from '../util/icon'
import { showDownload, showErrorTip, showPreview, showProgress, showRetry } from '../util/visible'
import { renderIcon, renderOprIcon } from '../utils/icon'
import { showDownload, showErrorTip, showPreview, showProgress, showRetry } from '../utils/visible'

export default defineComponent({
name: 'IxUploadImageList',
props: uploadFilesProps,
setup(listProps) {
const { props: uploadProps, locale, files, upload, abort, onUpdateFiles, setViewerVisible } = inject(uploadToken)!
const uploadContext = inject(uploadToken)!
const { props: uploadProps, locale, fileList } = uploadContext
const icons = useIcon(listProps)
const cpmClasses = useCmpClasses()
const listClasses = useListClasses(uploadProps, 'image')
const { getThumbNode, revokeAll } = useThumb()
const fileOperation = useOperation(files, listProps, uploadProps, {
abort,
upload,
onUpdateFiles,
setViewerVisible,
})
const fileOperation = useOperation(listProps, uploadContext)

onBeforeUnmount(revokeAll)

return () =>
files.value.length > 0 && (
fileList.value.length > 0 && (
<ul class={listClasses.value}>
{files.value.map(file =>
{fileList.value.map(file =>
renderItem(uploadProps, file, icons, cpmClasses, fileOperation, locale, getThumbNode),
)}
</ul>
Expand Down
159 changes: 21 additions & 138 deletions packages/components/upload/src/component/Selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,48 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import type { UploadDrag } from '../composables/useDrag'
import type { UploadToken } from '../token'
import type { UploadFile, UploadFileStatus, UploadProps } from '../types'
import type { UploadProps } from '../types'
import type { UploadConfig } from '@idux/components/config'
import type { ComputedRef, Ref, ShallowRef } from 'vue'
import type { ComputedRef, Ref } from 'vue'

import { computed, defineComponent, inject, nextTick, normalizeClass, ref, shallowRef, watch } from 'vue'
import { computed, defineComponent, inject, normalizeClass, ref } from 'vue'

import { callEmit } from '@idux/cdk/utils'
import { useGlobalConfig } from '@idux/components/config'

import { useCmpClasses } from '../composables/useDisplay'
import { useDrag } from '../composables/useDrag'
import { uploadToken } from '../token'
import { getFileInfo, getFilesAcceptAllow, getFilesCountAllow } from '../util/fileHandle'

export default defineComponent({
name: 'IxUploadSelector',
setup(props, { slots }) {
const { props: uploadProps, files, onUpdateFiles, abort, startUpload } = inject(uploadToken)!
setup(_, { slots }) {
const { props: uploadProps, selectFiles, onDrop, onDragOver, onDragLeave, isDraggingOver } = inject(uploadToken)!
const cpmClasses = useCmpClasses()
const config = useGlobalConfig('upload')
const dir = useDir(uploadProps, config)
const multiple = useMultiple(uploadProps, config)
const dragable = useDragable(uploadProps, config)
const accept = useAccept(uploadProps)
const maxCount = computed(() => uploadProps.maxCount ?? 0)
const {
allowDrag,
dragOver,
filesSelected: dragFilesSelected,
onDrop,
onDragOver,
onDragLeave,
} = useDrag(uploadProps)
const [filesSelected, updateFilesSelected] = useFilesSelected(dragFilesSelected, allowDrag)
const filesReady = useFilesAllowed(files, filesSelected, accept, maxCount)

const fileInputRef: Ref<HTMLInputElement | null> = ref(null)
const inputClasses = computed(() => `${cpmClasses.value}-input`)
const selectorClasses = useSelectorClasses(uploadProps, cpmClasses, dragable, dragOver)

syncUploadHandle(uploadProps, files, filesReady, onUpdateFiles, abort, startUpload)
const selectorClasses = useSelectorClasses(uploadProps, cpmClasses, dragable, isDraggingOver)

const onClick = () => {
if (uploadProps.disabled || !fileInputRef.value) {
return
}
fileInputRef.value.value = ''
fileInputRef.value.click()
}
const onFileChange = () => {
const files = Array.prototype.slice.call(fileInputRef.value?.files ?? []) as File[]
selectFiles(files)
}

return () => {
return (
<div
class={selectorClasses.value}
onClick={() => onClick(fileInputRef, uploadProps)}
onClick={onClick}
onDragover={onDragOver}
onDrop={onDrop}
onDragleave={onDragLeave}
Expand All @@ -65,7 +59,7 @@ export default defineComponent({
accept={uploadProps.accept}
multiple={multiple.value}
onClick={e => e.stopPropagation()}
onChange={() => onSelect(fileInputRef, updateFilesSelected)}
onChange={onFileChange}
/>
{slots.default?.()}
</div>
Expand Down Expand Up @@ -95,121 +89,10 @@ function useDir(props: UploadProps, config: UploadConfig) {
return computed(() => (props.directory ?? config.directory ? directoryCfg : {}))
}

function useAccept(props: UploadProps) {
return computed(
() =>
props.accept
?.split(',')
.map(type => type.trim())
.filter(type => type),
)
}

function useMultiple(props: UploadProps, config: UploadConfig) {
return computed(() => props.multiple ?? config.multiple)
}

function useDragable(props: UploadProps, config: UploadConfig) {
return computed(() => props.dragable ?? config.dragable)
}

function useFilesSelected(
dragFilesSelected: UploadDrag['filesSelected'],
allowDrag: UploadDrag['allowDrag'],
): [ShallowRef<File[]>, (files: File[]) => void] {
const filesSelected: ShallowRef<File[]> = shallowRef([])

watch(dragFilesSelected, files => {
if (allowDrag.value) {
filesSelected.value = files
}
})

function updateFilesSelected(files: File[]) {
filesSelected.value = files
}

return [filesSelected, updateFilesSelected]
}

function useFilesAllowed(
files: ComputedRef<UploadFile[]>,
filesSelected: ShallowRef<File[]>,
accept: ComputedRef<string[] | undefined>,
maxCount: ComputedRef<number>,
) {
const filesAllowed: ShallowRef<File[]> = shallowRef([])

watch(filesSelected, filesSelected$$ => {
const filesCheckAccept = getFilesAcceptAllow(filesSelected$$, accept.value)
filesAllowed.value = getFilesCountAllow(filesCheckAccept, files.value.length, maxCount.value)
})

return filesAllowed
}

// 选中文件变化就处理上传
function syncUploadHandle(
uploadProps: UploadProps,
files: ComputedRef<UploadFile[]>,
filesReady: ShallowRef<File[]>,
onUpdateFiles: UploadToken['onUpdateFiles'],
abort: UploadToken['abort'],
startUpload: UploadToken['startUpload'],
) {
watch(filesReady, async filesReady$$ => {
if (filesReady$$.length === 0) {
return
}
const filesAfterHandle = uploadProps.onSelect ? await callEmit(uploadProps.onSelect, filesReady$$) : filesReady$$
const filesReadyUpload = getFilesHandled(filesAfterHandle!, filesReady$$)
const filesFormat = getFormatFiles(filesReadyUpload, uploadProps, 'selected')
const filesIds = filesFormat.map(file => file.key)
if (uploadProps.maxCount === 1) {
files.value.forEach(file => abort(file))
callEmit(onUpdateFiles, filesFormat)
} else {
callEmit(onUpdateFiles, files.value.concat(filesFormat))
}

await nextTick(() => {
files.value
.filter(item => filesIds.includes(item.key))
.forEach(file => {
startUpload(file)
})
})
})
}

function onClick(fileInputRef: Ref<HTMLInputElement | null>, props: UploadProps) {
if (props.disabled || !fileInputRef.value) {
return
}
fileInputRef.value.value = ''
fileInputRef.value.click()
}

function onSelect(fileInputRef: Ref<HTMLInputElement | null>, updateFilesSelected: (files: File[]) => void) {
const files = Array.prototype.slice.call(fileInputRef.value?.files ?? []) as File[]
updateFilesSelected(files)
}

// 文件对象初始化
function getFormatFiles(files: File[], props: UploadProps, status: UploadFileStatus) {
return files.map(item => {
const fileInfo = getFileInfo(item, { status })
callEmit(props.onFileStatusChange, fileInfo)
return fileInfo
})
}

function getFilesHandled(handleResult: boolean | File[], allowedFiles: File[]) {
if (handleResult === true) {
return allowedFiles
}
if (handleResult === false) {
return []
}
return handleResult
}
Loading

0 comments on commit 8faddc1

Please sign in to comment.