import { loadScript, loadStyle } from '@jswork/loadkit'; import { UploadFile } from 'antd'; import { DraggerProps } from 'antd/es/upload'; import { UploadChangeParam } from 'antd/es/upload/interface'; import React, { ReactNode } from 'react'; import { flushSync } from 'react-dom'; import Sortable from 'sortablejs'; import type { StdCallback } from './types'; const styleOpts = { id: 'viewer-style' }; const scriptOpts = { id: 'viewerjs' }; const styleURL = 'https://unpkg.com/viewerjs@1.11.1/dist/viewer.min.css'; const scriptURL = 'https://unpkg.com/viewerjs@1.11.1/dist/viewer.min.js'; export type AcAbstractUploadProps = { className?: string; value?: any[] | []; onChange?: StdCallback; transformResponse?: (inResponse: any) => any; transformURL?: (inPid) => string; } & DraggerProps; type State = { fileList: any[]; }; export class AcAbstractUpload extends React.Component { protected rootRef = React.createRef(); protected sortable: any = null; protected viewer: any = null; toFileList = (inUrls: any[] | any) => { const { transformURL } = this.props; const urls = Array.isArray(inUrls) ? inUrls : [inUrls].filter(Boolean); return urls.map((item) => { if (typeof item !== 'string') return item; return { uid: nx.gpid(item), url: transformURL!(item) }; }); }; constructor(inProps: AcAbstractUploadProps) { super(inProps); this.state = { fileList: this.toFileList(inProps.value), }; } async componentDidMount() { const { rootRef } = this; const root = rootRef.current as HTMLDivElement; const el = root.querySelector('.ant-upload-list'); this.mountSortable(el); await this.mountViewer(el); } shouldComponentUpdate(nextProps: Readonly): boolean { const { value } = nextProps; if (value !== this.props.value) { this.setState({ fileList: this.toFileList(value as any[]) }); } return true; } mountSortable(el) { this.sortable = new Sortable(el, { animation: 150, draggable: '.ant-upload-list-item-container', ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', onEnd: this.handleSortEnd, }); } async mountViewer(el) { await Promise.all([loadStyle(styleURL, styleOpts), loadScript(scriptURL, scriptOpts)]); this.viewer = new window['Viewer'](el); } componentWillUnmount() { this.sortable?.destroy(); this.viewer?.destroy(); } handlePreview = (file: UploadFile) => { const { fileList } = this.state; const idx = fileList.indexOf(file); this.viewer?.update(); this.viewer.view(idx); }; handleChange = (inEvent: UploadChangeParam>) => { const { fileList } = inEvent; const isDone = (file) => !file.status || file.status === 'done'; const done = fileList.every(isDone); flushSync(() => this.setState({ fileList })); if (done) this.doChange(fileList); }; handleSortEnd = (inEvent) => { const { oldIndex, newIndex } = inEvent; const { fileList } = this.state; const newFileList = fileList.slice(); newFileList.splice(newIndex, 0, newFileList.splice(oldIndex, 1)[0]); this.doChange(newFileList); }; doChange = (inValue) => { const { onChange, transformResponse } = this.props; const value = inValue.map((item) => item.response ?? item); onChange?.({ target: { value: transformResponse!(value) } }); }; previewFile = (file): Promise => { const blobURL = window.URL.createObjectURL(file); return new Promise((resolve) => { resolve(blobURL); }); }; render(): ReactNode { // warning: if not implement, will throw error. console.warn('Please implement render method.'); return null; } }