naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
686 lines • 21.1 kB
JavaScript
var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function (resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { createId } from 'seemly';
import { useMergedState } from 'vooks';
import { computed, defineComponent, Fragment, h, nextTick, provide, ref, Teleport, toRef } from 'vue';
import { useConfig, useFormItem, useTheme, useThemeClass } from "../../_mixins/index.mjs";
import { call, throwError, warn } from "../../_utils/index.mjs";
import { uploadLight } from "../styles/index.mjs";
import { uploadInjectionKey } from "./interface.mjs";
import style from "./styles/index.cssr.mjs";
import { uploadDraggerKey } from "./UploadDragger.mjs";
import NUploadFileList from "./UploadFileList.mjs";
import NUploadTrigger from "./UploadTrigger.mjs";
import { createImageDataUrl, createSettledFileInfo, environmentSupportFile, isImageFile, matchType } from "./utils.mjs";
/**
* fils status ['pending', 'uploading', 'finished', 'removed', 'error']
*/
function createXhrHandlers(inst, file, xhr) {
const {
doChange,
xhrMap
} = inst;
let percentage = 0;
function handleXHRError(e) {
var _a;
let fileAfterChange = Object.assign({}, file, {
status: 'error',
percentage
});
xhrMap.delete(file.id);
fileAfterChange = createSettledFileInfo(((_a = inst.onError) === null || _a === void 0 ? void 0 : _a.call(inst, {
file: fileAfterChange,
event: e
})) || fileAfterChange);
doChange(fileAfterChange, e);
}
function handleXHRLoad(e) {
var _a;
if (inst.isErrorState) {
if (inst.isErrorState(xhr)) {
handleXHRError(e);
return;
}
} else {
if (xhr.status < 200 || xhr.status >= 300) {
handleXHRError(e);
return;
}
}
let fileAfterChange = Object.assign({}, file, {
status: 'finished',
percentage
});
xhrMap.delete(file.id);
fileAfterChange = createSettledFileInfo(((_a = inst.onFinish) === null || _a === void 0 ? void 0 : _a.call(inst, {
file: fileAfterChange,
event: e
})) || fileAfterChange);
doChange(fileAfterChange, e);
}
return {
handleXHRLoad,
handleXHRError,
handleXHRAbort(e) {
const fileAfterChange = Object.assign({}, file, {
status: 'removed',
file: null,
percentage
});
xhrMap.delete(file.id);
doChange(fileAfterChange, e);
},
handleXHRProgress(e) {
const fileAfterChange = Object.assign({}, file, {
status: 'uploading'
});
if (e.lengthComputable) {
const progress = Math.ceil(e.loaded / e.total * 100);
fileAfterChange.percentage = progress;
percentage = progress;
}
doChange(fileAfterChange, e);
}
};
}
function customSubmitImpl(options) {
const {
inst,
file,
data,
headers,
withCredentials,
action,
customRequest
} = options;
const {
doChange
} = options.inst;
let percentage = 0;
customRequest({
file,
data,
headers,
withCredentials,
action,
onProgress(event) {
const fileAfterChange = Object.assign({}, file, {
status: 'uploading'
});
const progress = event.percent;
fileAfterChange.percentage = progress;
percentage = progress;
doChange(fileAfterChange);
},
onFinish() {
var _a;
let fileAfterChange = Object.assign({}, file, {
status: 'finished',
percentage
});
fileAfterChange = createSettledFileInfo(((_a = inst.onFinish) === null || _a === void 0 ? void 0 : _a.call(inst, {
file: fileAfterChange
})) || fileAfterChange);
doChange(fileAfterChange);
},
onError() {
var _a;
let fileAfterChange = Object.assign({}, file, {
status: 'error',
percentage
});
fileAfterChange = createSettledFileInfo(((_a = inst.onError) === null || _a === void 0 ? void 0 : _a.call(inst, {
file: fileAfterChange
})) || fileAfterChange);
doChange(fileAfterChange);
}
});
}
function registerHandler(inst, file, request) {
const handlers = createXhrHandlers(inst, file, request);
request.onabort = handlers.handleXHRAbort;
request.onerror = handlers.handleXHRError;
request.onload = handlers.handleXHRLoad;
if (request.upload) {
request.upload.onprogress = handlers.handleXHRProgress;
}
}
function unwrapFunctionValue(data, file) {
if (typeof data === 'function') {
return data({
file
});
}
if (data) return data;
return {};
}
function setHeaders(request, headers, file) {
const headersObject = unwrapFunctionValue(headers, file);
if (!headersObject) return;
Object.keys(headersObject).forEach(key => {
request.setRequestHeader(key, headersObject[key]);
});
}
function appendData(formData, data, file) {
const dataObject = unwrapFunctionValue(data, file);
if (!dataObject) return;
Object.keys(dataObject).forEach(key => {
formData.append(key, dataObject[key]);
});
}
function submitImpl(inst, fieldName, file, {
method,
action,
withCredentials,
responseType,
headers,
data
}) {
const request = new XMLHttpRequest();
request.responseType = responseType;
inst.xhrMap.set(file.id, request);
request.withCredentials = withCredentials;
const formData = new FormData();
appendData(formData, data, file);
if (file.file !== null) {
formData.append(fieldName, file.file);
}
registerHandler(inst, file, request);
if (action !== undefined) {
request.open(method.toUpperCase(), action);
setHeaders(request, headers, file);
request.send(formData);
const fileAfterChange = Object.assign({}, file, {
status: 'uploading'
});
inst.doChange(fileAfterChange);
}
}
export const uploadProps = Object.assign(Object.assign({}, useTheme.props), {
name: {
type: String,
default: 'file'
},
accept: String,
action: String,
customRequest: Function,
directory: Boolean,
directoryDnd: {
type: Boolean,
default: undefined
},
method: {
type: String,
default: 'POST'
},
multiple: Boolean,
showFileList: {
type: Boolean,
default: true
},
data: [Object, Function],
headers: [Object, Function],
withCredentials: Boolean,
responseType: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: undefined
},
onChange: Function,
onRemove: Function,
onFinish: Function,
onError: Function,
onRetry: Function,
onBeforeUpload: Function,
isErrorState: Function,
/** currently not used */
onDownload: Function,
customDownload: Function,
defaultUpload: {
type: Boolean,
default: true
},
fileList: Array,
'onUpdate:fileList': [Function, Array],
onUpdateFileList: [Function, Array],
fileListClass: String,
fileListStyle: [String, Object],
defaultFileList: {
type: Array,
default: () => []
},
showCancelButton: {
type: Boolean,
default: true
},
showRemoveButton: {
type: Boolean,
default: true
},
showDownloadButton: Boolean,
showRetryButton: {
type: Boolean,
default: true
},
showPreviewButton: {
type: Boolean,
default: true
},
listType: {
type: String,
default: 'text'
},
onPreview: Function,
shouldUseThumbnailUrl: {
type: Function,
default: file => {
if (!environmentSupportFile) return false;
return isImageFile(file);
}
},
createThumbnailUrl: Function,
abstract: Boolean,
max: Number,
showTrigger: {
type: Boolean,
default: true
},
imageGroupProps: Object,
inputProps: Object,
triggerClass: String,
triggerStyle: [String, Object],
renderIcon: Function
});
export default defineComponent({
name: 'Upload',
props: uploadProps,
setup(props) {
if (props.abstract && props.listType === 'image-card') {
throwError('upload', 'when the list-type is image-card, abstract is not supported.');
}
const {
mergedClsPrefixRef,
inlineThemeDisabled
} = useConfig(props);
const themeRef = useTheme('Upload', '-upload', style, uploadLight, props, mergedClsPrefixRef);
const formItem = useFormItem(props);
const uncontrolledFileListRef = ref(props.defaultFileList);
const controlledFileListRef = toRef(props, 'fileList');
const inputElRef = ref(null);
const draggerInsideRef = {
value: false
};
const dragOverRef = ref(false);
const xhrMap = new Map();
const _mergedFileListRef = useMergedState(controlledFileListRef, uncontrolledFileListRef);
const mergedFileListRef = computed(() => _mergedFileListRef.value.map(createSettledFileInfo));
const maxReachedRef = computed(() => {
const {
max
} = props;
if (max !== undefined) {
return mergedFileListRef.value.length >= max;
}
return false;
});
function openOpenFileDialog() {
var _a;
(_a = inputElRef.value) === null || _a === void 0 ? void 0 : _a.click();
}
function handleFileInputChange(e) {
const target = e.target;
handleFileAddition(target.files ? Array.from(target.files).map(file => ({
file,
entry: null,
source: 'input'
})) : null, e);
// May have bug! set to null?
target.value = '';
}
function doUpdateFileList(files) {
const {
'onUpdate:fileList': _onUpdateFileList,
onUpdateFileList
} = props;
if (_onUpdateFileList) call(_onUpdateFileList, files);
if (onUpdateFileList) call(onUpdateFileList, files);
uncontrolledFileListRef.value = files;
}
const mergedMultipleRef = computed(() => props.multiple || props.directory);
const doChange = (fileAfterChange, event, options = {
append: false,
remove: false
}) => {
const {
append,
remove
} = options;
const fileListAfterChange = Array.from(mergedFileListRef.value);
const fileIndex = fileListAfterChange.findIndex(file => file.id === fileAfterChange.id);
if (append || remove || ~fileIndex) {
if (append) {
fileListAfterChange.push(fileAfterChange);
} else if (remove) {
fileListAfterChange.splice(fileIndex, 1);
} else {
fileListAfterChange.splice(fileIndex, 1, fileAfterChange);
}
const {
onChange
} = props;
if (onChange) {
onChange({
file: fileAfterChange,
fileList: fileListAfterChange,
event
});
}
doUpdateFileList(fileListAfterChange);
} else if (process.env.NODE_ENV !== 'production') {
warn('upload', 'File has no corresponding id in current file list.');
}
};
function handleFileAddition(fileAndEntries, e) {
if (!fileAndEntries || fileAndEntries.length === 0) return;
const {
onBeforeUpload
} = props;
fileAndEntries = mergedMultipleRef.value ? fileAndEntries : [fileAndEntries[0]];
const {
max,
accept
} = props;
fileAndEntries = fileAndEntries.filter(({
file,
source
}) => {
if (source === 'dnd' && (accept === null || accept === void 0 ? void 0 : accept.trim())) {
return matchType(file.name, file.type, accept);
} else {
return true;
}
});
if (max) {
fileAndEntries = fileAndEntries.slice(0, max - mergedFileListRef.value.length);
}
const batchId = createId();
void Promise.all(fileAndEntries.map(_a => __awaiter(this, [_a], void 0, function* ({
file,
entry
}) {
var _b;
const fileInfo = {
id: createId(),
batchId,
name: file.name,
status: 'pending',
percentage: 0,
file,
url: null,
type: file.type,
thumbnailUrl: null,
fullPath: (_b = entry === null || entry === void 0 ? void 0 : entry.fullPath) !== null && _b !== void 0 ? _b : `/${file.webkitRelativePath || file.name}`
};
if (!onBeforeUpload || (yield onBeforeUpload({
file: fileInfo,
fileList: mergedFileListRef.value
})) !== false) {
return fileInfo;
}
return null;
}))).then(fileInfos => __awaiter(this, void 0, void 0, function* () {
let nextTickChain = Promise.resolve();
fileInfos.forEach(fileInfo => {
nextTickChain = nextTickChain.then(nextTick).then(() => {
if (fileInfo) {
doChange(fileInfo, e, {
append: true
});
}
});
});
yield nextTickChain;
})).then(() => {
if (props.defaultUpload) {
submit();
}
});
}
function submit(fileId) {
const {
method,
action,
withCredentials,
headers,
data,
name: fieldName
} = props;
const filesToUpload = fileId !== undefined ? mergedFileListRef.value.filter(file => file.id === fileId) : mergedFileListRef.value;
const shouldReupload = fileId !== undefined;
filesToUpload.forEach(file => {
const {
status
} = file;
if (status === 'pending' || status === 'error' && shouldReupload) {
if (props.customRequest) {
customSubmitImpl({
inst: {
doChange,
xhrMap,
onFinish: props.onFinish,
onError: props.onError
},
file,
action,
withCredentials,
headers,
data,
customRequest: props.customRequest
});
} else {
submitImpl({
doChange,
xhrMap,
onFinish: props.onFinish,
onError: props.onError,
isErrorState: props.isErrorState
}, fieldName, file, {
method,
action,
withCredentials,
responseType: props.responseType,
headers,
data
});
}
}
});
}
function getFileThumbnailUrlResolver(file) {
var _a;
if (file.thumbnailUrl) return file.thumbnailUrl;
const {
createThumbnailUrl
} = props;
if (createThumbnailUrl) {
return (_a = createThumbnailUrl(file.file, file)) !== null && _a !== void 0 ? _a : file.url || '';
}
if (file.url) {
return file.url;
} else if (file.file) {
return createImageDataUrl(file.file);
}
return '';
}
const cssVarsRef = computed(() => {
const {
common: {
cubicBezierEaseInOut
},
self: {
draggerColor,
draggerBorder,
draggerBorderHover,
itemColorHover,
itemColorHoverError,
itemTextColorError,
itemTextColorSuccess,
itemTextColor,
itemIconColor,
itemDisabledOpacity,
lineHeight,
borderRadius,
fontSize,
itemBorderImageCardError,
itemBorderImageCard
}
} = themeRef.value;
return {
'--n-bezier': cubicBezierEaseInOut,
'--n-border-radius': borderRadius,
'--n-dragger-border': draggerBorder,
'--n-dragger-border-hover': draggerBorderHover,
'--n-dragger-color': draggerColor,
'--n-font-size': fontSize,
'--n-item-color-hover': itemColorHover,
'--n-item-color-hover-error': itemColorHoverError,
'--n-item-disabled-opacity': itemDisabledOpacity,
'--n-item-icon-color': itemIconColor,
'--n-item-text-color': itemTextColor,
'--n-item-text-color-error': itemTextColorError,
'--n-item-text-color-success': itemTextColorSuccess,
'--n-line-height': lineHeight,
'--n-item-border-image-card-error': itemBorderImageCardError,
'--n-item-border-image-card': itemBorderImageCard
};
});
const themeClassHandle = inlineThemeDisabled ? useThemeClass('upload', undefined, cssVarsRef, props) : undefined;
provide(uploadInjectionKey, {
mergedClsPrefixRef,
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'),
customDownloadRef: toRef(props, 'customDownload'),
mergedFileListRef,
triggerClassRef: toRef(props, 'triggerClass'),
triggerStyleRef: toRef(props, 'triggerStyle'),
shouldUseThumbnailUrlRef: toRef(props, 'shouldUseThumbnailUrl'),
renderIconRef: toRef(props, 'renderIcon'),
xhrMap,
submit,
doChange,
showPreviewButtonRef: toRef(props, 'showPreviewButton'),
onPreviewRef: toRef(props, 'onPreview'),
getFileThumbnailUrlResolver,
listTypeRef: toRef(props, 'listType'),
dragOverRef,
openOpenFileDialog,
draggerInsideRef,
handleFileAddition,
mergedDisabledRef: formItem.mergedDisabledRef,
maxReachedRef,
fileListClassRef: toRef(props, 'fileListClass'),
fileListStyleRef: toRef(props, 'fileListStyle'),
abstractRef: toRef(props, 'abstract'),
acceptRef: toRef(props, 'accept'),
cssVarsRef: inlineThemeDisabled ? undefined : cssVarsRef,
themeClassRef: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender,
showTriggerRef: toRef(props, 'showTrigger'),
imageGroupPropsRef: toRef(props, 'imageGroupProps'),
mergedDirectoryDndRef: computed(() => {
var _a;
return (_a = props.directoryDnd) !== null && _a !== void 0 ? _a : props.directory;
}),
onRetryRef: toRef(props, 'onRetry')
});
const exposedMethods = {
clear: () => {
uncontrolledFileListRef.value = [];
},
submit,
openOpenFileDialog
};
return Object.assign({
mergedClsPrefix: mergedClsPrefixRef,
draggerInsideRef,
inputElRef,
mergedTheme: themeRef,
dragOver: dragOverRef,
mergedMultiple: mergedMultipleRef,
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender,
handleFileInputChange
}, exposedMethods);
},
render() {
var _a, _b;
const {
draggerInsideRef,
mergedClsPrefix,
$slots,
directory,
onRender
} = this;
if ($slots.default && !this.abstract) {
const firstChild = $slots.default()[0];
if ((_a = firstChild === null || firstChild === void 0 ? void 0 : firstChild.type) === null || _a === void 0 ? void 0 : _a[uploadDraggerKey]) {
draggerInsideRef.value = true;
}
}
const inputNode = h("input", Object.assign({}, this.inputProps, {
ref: "inputElRef",
type: "file",
class: `${mergedClsPrefix}-upload-file-input`,
accept: this.accept,
multiple: this.mergedMultiple,
onChange: this.handleFileInputChange,
// @ts-expect-error // seems vue-tsc will add the prop, so we can't use expect-error
webkitdirectory: directory || undefined,
directory: directory || undefined
}));
if (this.abstract) {
return h(Fragment, null, (_b = $slots.default) === null || _b === void 0 ? void 0 : _b.call($slots), h(Teleport, {
to: "body"
}, inputNode));
}
onRender === null || onRender === void 0 ? void 0 : onRender();
return h("div", {
class: [`${mergedClsPrefix}-upload`, draggerInsideRef.value && `${mergedClsPrefix}-upload--dragger-inside`, this.dragOver && `${mergedClsPrefix}-upload--drag-over`, this.themeClass],
style: this.cssVars
}, inputNode, this.showTrigger && this.listType !== 'image-card' && h(NUploadTrigger, null, $slots), this.showFileList && h(NUploadFileList, null, $slots));
}
});