refactor(upload): clsPrefix

This commit is contained in:
07akioni 2021-04-18 01:13:59 +08:00
parent 5a503e4802
commit c67ac4690c
6 changed files with 211 additions and 172 deletions

View File

@ -1,3 +1,5 @@
export { default as NUpload } from './src/Upload'
export { default as NUploadDragger } from './src/UploadDragger'
export type { UploadRef } from './src/interface'
export type { UploadProps } from './src/Upload'
export type { uploadDraggerKey } from './src/UploadDragger'
export type { UploadInst } from './src/interface'

View File

@ -5,37 +5,37 @@ import {
provide,
toRef,
ref,
reactive,
PropType,
CSSProperties
} from 'vue'
import { createId } from 'seemly'
import { useTheme } from '../../_mixins'
import { useConfig, useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { warn } from '../../_utils'
import { ExtractPublicPropTypes, getFirstSlotVNode, warn } from '../../_utils'
import { NFadeInExpandTransition } from '../../_internal'
import { uploadLight, UploadTheme } from '../styles'
import NUploadFile from './UploadFile'
import style from './styles/index.cssr'
import type {
import {
XhrHandlers,
FileInfo,
DoChange,
UploadInst,
UploadInternalInst,
FuncOrRecordOrUndef,
OnFinish,
UploadInjection,
OnRemove,
OnDownload,
OnChange
OnChange,
uploadInjectionKey
} from './interface'
import { useMergedState } from 'vooks'
import { uploadDraggerKey } from './UploadDragger'
/**
* fils status ['pending', 'uploading', 'finished', 'removed', 'error']
*/
function createXhrHandlers (
inst: UploadInst,
inst: UploadInternalInst,
file: FileInfo,
XHR: XMLHttpRequest
): XhrHandlers {
@ -86,7 +86,7 @@ function createXhrHandlers (
}
function registerHandler (
inst: UploadInst,
inst: UploadInternalInst,
file: FileInfo,
request: XMLHttpRequest
): void {
@ -135,7 +135,7 @@ function appendData (
}
function submitImpl (
inst: UploadInst,
inst: UploadInternalInst,
file: FileInfo,
formData: FormData,
{
@ -168,76 +168,90 @@ function submitImpl (
}
}
const uploadProps = {
...(useTheme.props as ThemeProps<UploadTheme>),
name: {
type: String,
default: 'file'
},
accept: String,
action: String,
// to be impl
// directory: {
// type: Boolean,
// default: false
// },
method: {
type: String,
default: 'POST'
},
multiple: {
type: Boolean,
default: false
},
data: [Object, Function] as PropType<FuncOrRecordOrUndef>,
headers: [Object, Function] as PropType<FuncOrRecordOrUndef>,
withCredentials: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
onChange: Function as PropType<OnChange>,
onRemove: Function as PropType<OnRemove>,
onFinish: Function as PropType<OnFinish>,
/** currently of no usage */
onDownload: Function as PropType<OnDownload>,
defaultUpload: {
type: Boolean,
default: true
},
fileList: Array as PropType<FileInfo[]>,
fileListStyle: [String, Object] as PropType<string | CSSProperties>,
defaultFileList: {
type: Array as PropType<FileInfo[]>,
default: () => []
},
showCancelButton: {
type: Boolean,
default: true
},
showRemoveButton: {
type: Boolean,
default: true
},
showDownloadButton: {
type: Boolean,
default: false
},
showRetryButton: {
type: Boolean,
default: true
}
} as const
export type UploadProps = ExtractPublicPropTypes<typeof uploadProps>
export default defineComponent({
name: 'Upload',
props: {
...(useTheme.props as ThemeProps<UploadTheme>),
name: {
type: String,
default: 'file'
},
accept: String,
action: String,
// to be impl
// directory: {
// type: Boolean,
// default: false
// },
method: {
type: String,
default: 'POST'
},
multiple: {
type: Boolean,
default: false
},
data: [Object, Function] as PropType<FuncOrRecordOrUndef>,
headers: [Object, Function] as PropType<FuncOrRecordOrUndef>,
withCredentials: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
onChange: Function as PropType<OnChange>,
onRemove: Function as PropType<OnRemove>,
onFinish: Function as PropType<OnFinish>,
/** currently of no usage */
onDownload: Function as PropType<OnDownload>,
defaultUpload: {
type: Boolean,
default: true
},
fileList: Array as PropType<FileInfo[]>,
fileListStyle: [String, Object] as PropType<string | CSSProperties>,
defaultFileList: {
type: Array as PropType<FileInfo[]>,
default: () => []
},
showCancelButton: {
type: Boolean,
default: true
},
showRemoveButton: {
type: Boolean,
default: true
},
showDownloadButton: {
type: Boolean,
default: false
},
showRetryButton: {
type: Boolean,
default: true
}
},
props: uploadProps,
setup (props) {
const themeRef = useTheme('Upload', 'Upload', style, uploadLight, props)
const { mergedClsPrefix } = useConfig(props)
const themeRef = useTheme(
'Upload',
'Upload',
style,
uploadLight,
props,
mergedClsPrefix
)
const uncontrolledFileListRef = ref(props.defaultFileList)
const inputElRef = ref<HTMLInputElement | null>(null)
const draggerInsideRef = ref(false)
const draggerInsideRef = {
value: false
}
const dragOverRef = ref(false)
const XhrMap = new Map<string, XMLHttpRequest>()
const mergedFileListRef = useMergedState(
@ -369,28 +383,26 @@ export default defineComponent({
warn('upload', 'File has no corresponding id in current file list.')
}
}
provide<UploadInjection>(
'NUpload',
reactive({
mergedTheme: themeRef,
showCancelButton: toRef(props, 'showCancelButton'),
showDownloadButton: toRef(props, 'showDownloadButton'),
showRemoveButton: toRef(props, 'showRemoveButton'),
showRetryButton: toRef(props, 'showRetryButton'),
draggerInside: draggerInsideRef,
mergedFileList: mergedFileListRef,
XhrMap,
onRemove: toRef(props, 'onRemove'),
onDownload: toRef(props, 'onDownload'),
submit,
doChange
})
)
provide(uploadInjectionKey, {
cPrefixRef: mergedClsPrefix,
mergedThemeRef: themeRef,
showCancelButtonRef: toRef(props, 'showCancelButton'),
showDownloadButtonRef: toRef(props, 'showDownloadButton'),
showRemoveButtonRef: toRef(props, 'showRemoveButton'),
showRetryButtonRef: toRef(props, 'showRetryButton'),
onRemoveRef: toRef(props, 'onRemove'),
onDownloadRef: toRef(props, 'onDownload'),
mergedFileListRef: mergedFileListRef,
XhrMap,
submit,
doChange
})
return {
cPrefix: mergedClsPrefix,
draggerInsideRef,
inputElRef,
mergedFileList: mergedFileListRef,
mergedTheme: themeRef,
draggerInside: draggerInsideRef,
dragOver: dragOverRef,
handleTriggerDrop,
handleTriggerDragLeave,
@ -439,14 +451,20 @@ export default defineComponent({
}
},
render () {
const { draggerInsideRef, cPrefix } = this
const firstChild = getFirstSlotVNode(this.$slots, 'default')
// @ts-expect-error
if (firstChild?.type?.[uploadDraggerKey]) {
draggerInsideRef.value = true
}
return (
<div
class={[
'n-upload',
`${cPrefix}-upload`,
{
'n-upload--dragger-inside': this.draggerInside,
'n-upload--drag-over': this.dragOver,
'n-upload--disabled': this.disabled
[`${cPrefix}-upload--dragger-inside`]: draggerInsideRef.value,
[`${cPrefix}-upload--drag-over`]: this.dragOver,
[`${cPrefix}-upload--disabled`]: this.disabled
}
]}
style={this.cssVars as CSSProperties}
@ -454,13 +472,13 @@ export default defineComponent({
<input
ref="inputElRef"
type="file"
class="n-upload__file-input"
class={`${cPrefix}-upload__file-input`}
accept={this.accept}
multiple={this.multiple}
onChange={this.handleFileInputChange}
/>
<div
class="n-upload__trigger"
class={`${cPrefix}-upload__trigger`}
onClick={this.handleTriggerClick}
onDrop={this.handleTriggerDrop}
onDragover={this.handleTriggerDragOver}
@ -469,12 +487,12 @@ export default defineComponent({
>
{this.$slots}
</div>
<div class="n-upload-file-list" style={this.fileListStyle}>
<div class={`${cPrefix}-upload-file-list`} style={this.fileListStyle}>
<NFadeInExpandTransition group>
{{
default: () =>
this.mergedFileList.map((file) => (
<NUploadFile key={file.id} file={file} />
<NUploadFile clsPrefix={cPrefix} key={file.id} file={file} />
))
}}
</NFadeInExpandTransition>

View File

@ -1,16 +1,22 @@
import { h, inject, defineComponent, onBeforeUnmount } from 'vue'
import { UploadInjection } from './interface'
import { h, defineComponent, inject } from 'vue'
import { throwError } from '../../_utils'
import { uploadInjectionKey } from './interface'
export const uploadDraggerKey = '__UPLOAD_DRAGGER__'
export default defineComponent({
name: 'UploadDragger',
setup () {
const NUpload = inject<UploadInjection>('NUpload')
NUpload && (NUpload.draggerInside = true)
onBeforeUnmount(() => {
NUpload && (NUpload.draggerInside = false)
})
},
render () {
return <div class="n-upload-dragger">{this.$slots}</div>
[uploadDraggerKey]: true,
setup (_, { slots }) {
const NUpload = inject(uploadInjectionKey, null)
if (!NUpload) {
throwError(
'upload-dragger',
'`n-upload-dragger` must be placed inside `n-upload`.'
)
}
return () => (
<div class={`${NUpload.cPrefixRef.value}-upload-dragger`}>{slots}</div>
)
}
})

View File

@ -10,18 +10,23 @@ import { NButton } from '../../button'
import { NIconSwitchTransition, NBaseIcon } from '../../_internal'
import { warn } from '../../_utils'
import NUploadProgress from './UploadProgress'
import type { FileInfo, UploadInjection } from './interface'
import { FileInfo, uploadInjectionKey } from './interface'
export default defineComponent({
name: 'UploadFile',
props: {
clsPrefix: {
type: String,
required: true
},
file: {
type: Object as PropType<FileInfo>,
required: true
}
},
setup (props) {
const NUpload = inject<UploadInjection>('NUpload') as UploadInjection
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const NUpload = inject(uploadInjectionKey)!
const progressStatusRef = computed(() => {
const { file } = props
if (file.status === 'finished') return 'success'
@ -38,22 +43,22 @@ export default defineComponent({
return file.status === 'uploading'
})
const showCancelButtonRef = computed(() => {
if (!NUpload.showCancelButton) return false
if (!NUpload.showCancelButtonRef.value) return false
const { file } = props
return ['uploading', 'pending', 'error'].includes(file.status)
})
const showRemoveButtonRef = computed(() => {
if (!NUpload.showRemoveButton) return false
if (!NUpload.showRemoveButtonRef.value) return false
const { file } = props
return ['finished'].includes(file.status)
})
const showDownloadButtonRef = computed(() => {
if (!NUpload.showDownloadButton) return false
if (!NUpload.showDownloadButtonRef.value) return false
const { file } = props
return ['finished'].includes(file.status)
})
const showRetryButtonRef = computed(() => {
if (!NUpload.showRetryButton) return false
if (!NUpload.showRetryButtonRef.value) return false
const { file } = props
return ['error'].includes(file.status)
})
@ -76,12 +81,17 @@ export default defineComponent({
handleDownload(props.file)
}
function handleRemove (file: FileInfo): void {
const { XhrMap, doChange } = NUpload
const {
XhrMap,
doChange,
onRemoveRef: { value: onRemove },
mergedFileListRef: { value: mergedFileList }
} = NUpload
void Promise.resolve(
NUpload.onRemove
? NUpload.onRemove({
onRemove
? onRemove({
file: Object.assign({}, file),
fileList: NUpload.mergedFileList
fileList: mergedFileList
})
: true
).then((result) => {
@ -96,8 +106,11 @@ export default defineComponent({
})
}
function handleDownload (file: FileInfo): void {
const {
onDownloadRef: { value: onDownload }
} = NUpload
void Promise.resolve(
NUpload.onDownload ? NUpload.onDownload(Object.assign({}, file)) : true
onDownload ? onDownload(Object.assign({}, file)) : true
).then((res) => {
/** I haven't figure out its usage, so just leave it here */
})
@ -109,7 +122,7 @@ export default defineComponent({
handleRemove(Object.assign({}, file))
}
return {
NUpload,
mergedTheme: NUpload.mergedThemeRef,
progressStatus: progressStatusRef,
buttonType: buttonTypeRef,
showProgress: showProgressRef,
@ -123,28 +136,31 @@ export default defineComponent({
}
},
render () {
const { clsPrefix, mergedTheme } = this
return (
<a
ref="noopener noreferer"
target="_blank"
href={this.file.url || undefined}
class={[
'n-upload-file',
`n-upload-file--${this.progressStatus}-status`,
this.file.url && 'n-upload-file--with-url'
`${clsPrefix}-upload-file`,
`${clsPrefix}-upload-file--${this.progressStatus}-status`,
this.file.url && `${clsPrefix}-upload-file--with-url`
]}
>
<div class="n-upload-file-info">
<div class="n-upload-file-info__name">
<NBaseIcon>{{ default: () => <AttachIcon /> }}</NBaseIcon>
<div class={`${clsPrefix}-upload-file-info`}>
<div class={`${clsPrefix}-upload-file-info__name`}>
<NBaseIcon clsPrefix={clsPrefix}>
{{ default: () => <AttachIcon /> }}
</NBaseIcon>
{this.file.name}
</div>
<div class="n-upload-file-info__action">
<div class={`${clsPrefix}-upload-file-info__action`}>
{this.showRemoveButton || this.showCancelButton ? (
<NButton
key="cancelOrTrash"
theme={this.NUpload.mergedTheme.peers.Button}
themeOverrides={this.NUpload.mergedTheme.peerOverrides.Button}
theme={mergedTheme.peers.Button}
themeOverrides={mergedTheme.peerOverrides.Button}
text
type={this.buttonType}
onClick={this.handleRemoveOrCancelClick}
@ -155,11 +171,11 @@ export default defineComponent({
{{
default: () =>
this.showRemoveButton ? (
<NBaseIcon key="trash">
<NBaseIcon clsPrefix={clsPrefix} key="trash">
{{ default: () => <TrashIcon /> }}
</NBaseIcon>
) : (
<NBaseIcon key="cancel">
<NBaseIcon clsPrefix={clsPrefix} key="cancel">
{{ default: () => <CancelIcon /> }}
</NBaseIcon>
)
@ -175,10 +191,14 @@ export default defineComponent({
text
type={this.buttonType}
onClick={this.handleRetryClick}
theme={mergedTheme.peers.Button}
themeOverrides={mergedTheme.peerOverrides.Button}
>
{{
icon: () => (
<NBaseIcon>{{ default: () => <RetryIcon /> }}</NBaseIcon>
<NBaseIcon clsPrefix={clsPrefix}>
{{ default: () => <RetryIcon /> }}
</NBaseIcon>
)
}}
</NButton>
@ -189,10 +209,14 @@ export default defineComponent({
text
type={this.buttonType}
onClick={this.handleDownloadClick}
theme={mergedTheme.peers.Button}
themeOverrides={mergedTheme.peerOverrides.Button}
>
{{
icon: () => (
<NBaseIcon>{{ default: () => <DownloadIcon /> }}</NBaseIcon>
<NBaseIcon clsPrefix={clsPrefix}>
{{ default: () => <DownloadIcon /> }}
</NBaseIcon>
)
}}
</NButton>

View File

@ -1,7 +1,7 @@
import { h, defineComponent, inject, PropType } from 'vue'
import { NFadeInExpandTransition } from '../../_internal'
import { NProgress } from '../../progress'
import type { UploadInjection } from './interface'
import { uploadInjectionKey } from './interface'
export default defineComponent({
name: 'UploadProgress',
@ -23,28 +23,11 @@ export default defineComponent({
default: 900
}
},
// data () {
// return {
// deferredShow: false
// }
// },
// watch: {
// show (value) {
// if (value) this.deferredShow = true
// else {
// window.setTimeout(() => {
// this.deferredShow = false
// }, this.delay)
// }
// }
// },
// created () {
// this.deferredShow = this.show
// },
setup (props) {
const NUpload = inject<UploadInjection>('NUpload') as UploadInjection
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const NUpload = inject(uploadInjectionKey)!
return {
NUpload
mergedTheme: NUpload.mergedThemeRef
}
},
render () {
@ -59,8 +42,8 @@ export default defineComponent({
percentage={this.percentage}
status={this.status}
height={2}
theme={this.NUpload.mergedTheme.peers.Progress}
themeOverrides={this.NUpload.mergedTheme.peerOverrides.Progress}
theme={this.mergedTheme.peers.Progress}
themeOverrides={this.mergedTheme.peerOverrides.Progress}
/>
) : null
}}

View File

@ -1,3 +1,5 @@
import { Ref } from '@vue/reactivity'
import { InjectionKey } from '@vue/runtime-core'
import type { MergedTheme } from '../../_mixins'
import type { UploadTheme } from '../styles'
@ -27,7 +29,7 @@ export type OnRemove = (data: {
}) => Promise<boolean> | boolean | any
export type OnDownload = (file: FileInfo) => Promise<boolean> | boolean | any
export interface UploadInst {
export interface UploadInternalInst {
doChange: DoChange
XhrMap: Map<string, XMLHttpRequest>
onFinish?: OnFinish
@ -43,20 +45,24 @@ export type DoChange = (
) => void
export interface UploadInjection {
readonly mergedTheme: MergedTheme<UploadTheme>
draggerInside: boolean
showCancelButton: boolean
showRemoveButton: boolean
showDownloadButton: boolean
showRetryButton: boolean
mergedFileList: FileInfo[]
cPrefixRef: Ref<string>
mergedThemeRef: Ref<MergedTheme<UploadTheme>>
showCancelButtonRef: Ref<boolean>
showRemoveButtonRef: Ref<boolean>
showDownloadButtonRef: Ref<boolean>
showRetryButtonRef: Ref<boolean>
mergedFileListRef: Ref<FileInfo[]>
onRemoveRef: Ref<OnRemove | undefined>
onDownloadRef: Ref<OnDownload | undefined>
XhrMap: Map<string, XMLHttpRequest>
submit: (fileId?: string) => void
doChange: DoChange
onRemove: OnRemove | undefined
onDownload: OnDownload | undefined
}
export const uploadInjectionKey: InjectionKey<UploadInjection> = Symbol(
'upload'
)
export interface XhrHandlers {
handleXHRLoad: (e: ProgressEvent) => void
handleXHRAbort: (e: ProgressEvent) => void
@ -64,6 +70,6 @@ export interface XhrHandlers {
handleXHRError: (e: ProgressEvent) => void
}
export interface UploadRef {
export interface UploadInst {
submit: () => void
}