123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- const Plugin = require('../../core/Plugin')
- const Translator = require('../../core/Translator')
- const dragDrop = require('drag-drop')
- const DashboardUI = require('./Dashboard')
- const StatusBar = require('../StatusBar')
- const Informer = require('../Informer')
- const ThumbnailGenerator = require('../ThumbnailGenerator')
- const { findAllDOMElements, toArray } = require('../../core/Utils')
- const prettyBytes = require('prettier-bytes')
- const { defaultTabIcon } = require('./icons')
- // Some code for managing focus was adopted from https://github.com/ghosh/micromodal
- // MIT licence, https://github.com/ghosh/micromodal/blob/master/LICENSE.md
- // Copyright (c) 2017 Indrashish Ghosh
- const FOCUSABLE_ELEMENTS = [
- 'a[href]',
- 'area[href]',
- 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',
- 'select:not([disabled]):not([aria-hidden])',
- 'textarea:not([disabled]):not([aria-hidden])',
- 'button:not([disabled]):not([aria-hidden])',
- 'iframe',
- 'object',
- 'embed',
- '[contenteditable]',
- '[tabindex]:not([tabindex^="-"])'
- ]
- /**
- * Dashboard UI with previews, metadata editing, tabs for various services and more
- */
- module.exports = class Dashboard extends Plugin {
- constructor (uppy, opts) {
- super(uppy, opts)
- this.id = this.opts.id || 'Dashboard'
- this.title = 'Dashboard'
- this.type = 'orchestrator'
- const defaultLocale = {
- strings: {
- selectToUpload: 'Select files to upload',
- closeModal: 'Close Modal',
- upload: 'Upload',
- importFrom: 'Import from',
- dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
- dashboardTitle: 'Uppy Dashboard',
- copyLinkToClipboardSuccess: 'Link copied to clipboard',
- copyLinkToClipboardFallback: 'Copy the URL below',
- copyLink: 'Copy link',
- fileSource: 'File source',
- done: 'Done',
- name: 'Name',
- removeFile: 'Remove file',
- editFile: 'Edit file',
- editing: 'Editing',
- finishEditingFile: 'Finish editing file',
- saveChanges: 'Save changes',
- cancel: 'Cancel',
- localDisk: 'Local Disk',
- myDevice: 'My Device',
- dropPasteImport: 'Drop files here, paste, import from one of the locations above or',
- dropPaste: 'Drop files here, paste or',
- browse: 'browse',
- fileProgress: 'File progress: upload speed and ETA',
- numberOfSelectedFiles: 'Number of selected files',
- uploadAllNewFiles: 'Upload all new files',
- emptyFolderAdded: 'No files were added from empty folder',
- uploadComplete: 'Upload complete',
- resumeUpload: 'Resume upload',
- pauseUpload: 'Pause upload',
- retryUpload: 'Retry upload',
- uploadXFiles: {
- 0: 'Upload %{smart_count} file',
- 1: 'Upload %{smart_count} files'
- },
- uploadXNewFiles: {
- 0: 'Upload +%{smart_count} file',
- 1: 'Upload +%{smart_count} files'
- },
- folderAdded: {
- 0: 'Added %{smart_count} file from %{folder}',
- 1: 'Added %{smart_count} files from %{folder}'
- }
- }
- }
- // set default options
- const defaultOptions = {
- target: 'body',
- metaFields: [],
- trigger: '#uppy-select-files',
- inline: false,
- width: 750,
- height: 550,
- thumbnailWidth: 280,
- defaultTabIcon: defaultTabIcon,
- showLinkToFileUploadResult: true,
- showProgressDetails: false,
- hideUploadButton: false,
- hideProgressAfterFinish: false,
- note: null,
- closeModalOnClickOutside: false,
- disableStatusBar: false,
- disableInformer: false,
- disableThumbnailGenerator: false,
- disablePageScrollWhenModalOpen: true,
- proudlyDisplayPoweredByUppy: true,
- onRequestCloseModal: () => this.closeModal(),
- locale: defaultLocale
- }
- // merge default options with the ones set by user
- this.opts = Object.assign({}, defaultOptions, opts)
- this.locale = Object.assign({}, defaultLocale, this.opts.locale)
- this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)
- this.translator = new Translator({locale: this.locale})
- this.i18n = this.translator.translate.bind(this.translator)
- this.openModal = this.openModal.bind(this)
- this.closeModal = this.closeModal.bind(this)
- this.requestCloseModal = this.requestCloseModal.bind(this)
- this.isModalOpen = this.isModalOpen.bind(this)
- this.addTarget = this.addTarget.bind(this)
- this.hideAllPanels = this.hideAllPanels.bind(this)
- this.showPanel = this.showPanel.bind(this)
- this.getFocusableNodes = this.getFocusableNodes.bind(this)
- this.setFocusToFirstNode = this.setFocusToFirstNode.bind(this)
- this.maintainFocus = this.maintainFocus.bind(this)
- this.initEvents = this.initEvents.bind(this)
- this.onKeydown = this.onKeydown.bind(this)
- this.handleClickOutside = this.handleClickOutside.bind(this)
- this.toggleFileCard = this.toggleFileCard.bind(this)
- this.handleDrop = this.handleDrop.bind(this)
- this.handlePaste = this.handlePaste.bind(this)
- this.handleInputChange = this.handleInputChange.bind(this)
- this.updateDashboardElWidth = this.updateDashboardElWidth.bind(this)
- this.render = this.render.bind(this)
- this.install = this.install.bind(this)
- }
- addTarget (plugin) {
- const callerPluginId = plugin.id || plugin.constructor.name
- const callerPluginName = plugin.title || callerPluginId
- const callerPluginType = plugin.type
- if (callerPluginType !== 'acquirer' &&
- callerPluginType !== 'progressindicator' &&
- callerPluginType !== 'presenter') {
- let msg = 'Dashboard: Modal can only be used by plugins of types: acquirer, progressindicator, presenter'
- this.uppy.log(msg)
- return
- }
- const target = {
- id: callerPluginId,
- name: callerPluginName,
- type: callerPluginType
- }
- const state = this.getPluginState()
- const newTargets = state.targets.slice()
- newTargets.push(target)
- this.setPluginState({
- targets: newTargets
- })
- return this.el
- }
- hideAllPanels () {
- this.setPluginState({
- activePanel: false
- })
- }
- showPanel (id) {
- const { targets } = this.getPluginState()
- const activePanel = targets.filter((target) => {
- return target.type === 'acquirer' && target.id === id
- })[0]
- this.setPluginState({
- activePanel: activePanel
- })
- }
- requestCloseModal () {
- if (this.opts.onRequestCloseModal) {
- return this.opts.onRequestCloseModal()
- } else {
- this.closeModal()
- }
- }
- getFocusableNodes () {
- const nodes = this.el.querySelectorAll(FOCUSABLE_ELEMENTS)
- return Object.keys(nodes).map((key) => nodes[key])
- }
- setFocusToFirstNode () {
- const focusableNodes = this.getFocusableNodes()
- if (focusableNodes.length) focusableNodes[0].focus()
- }
- setFocusToBrowse () {
- const browseBtn = this.el.querySelector('.uppy-Dashboard-browse')
- if (browseBtn) browseBtn.focus()
- }
- maintainFocus (event) {
- var focusableNodes = this.getFocusableNodes()
- var focusedItemIndex = focusableNodes.indexOf(document.activeElement)
- if (event.shiftKey && focusedItemIndex === 0) {
- focusableNodes[focusableNodes.length - 1].focus()
- event.preventDefault()
- }
- if (!event.shiftKey && focusedItemIndex === focusableNodes.length - 1) {
- focusableNodes[0].focus()
- event.preventDefault()
- }
- }
- openModal () {
- this.setPluginState({
- isHidden: false
- })
- // save scroll position
- this.savedScrollPosition = window.scrollY
- // save active element, so we can restore focus when modal is closed
- this.savedActiveElement = document.activeElement
- if (this.opts.disablePageScrollWhenModalOpen) {
- document.body.classList.add('uppy-Dashboard-isOpen')
- }
- this.rerender()
- this.updateDashboardElWidth()
- this.setFocusToBrowse()
- }
- closeModal () {
- this.setPluginState({
- isHidden: true
- })
- if (this.opts.disablePageScrollWhenModalOpen) {
- document.body.classList.remove('uppy-Dashboard-isOpen')
- }
- this.savedActiveElement.focus()
- }
- isModalOpen () {
- return !this.getPluginState().isHidden || false
- }
- onKeydown (event) {
- // close modal on esc key press
- if (event.keyCode === 27) this.requestCloseModal(event)
- // maintainFocus on tab key press
- if (event.keyCode === 9) this.maintainFocus(event)
- }
- handleClickOutside () {
- if (this.opts.closeModalOnClickOutside) this.requestCloseModal()
- }
- handlePaste (ev) {
- const files = toArray(ev.clipboardData.items)
- files.forEach((file) => {
- if (file.kind !== 'file') return
- const blob = file.getAsFile()
- if (!blob) {
- this.uppy.log('[Dashboard] File pasted, but the file blob is empty')
- this.uppy.info('Error pasting file', 'error')
- return
- }
- this.uppy.log('[Dashboard] File pasted')
- this.uppy.addFile({
- source: this.id,
- name: file.name,
- type: file.type,
- data: blob
- })
- })
- }
- handleInputChange (ev) {
- ev.preventDefault()
- const files = toArray(ev.target.files)
- files.forEach((file) => {
- this.uppy.addFile({
- source: this.id,
- name: file.name,
- type: file.type,
- data: file
- })
- })
- }
- initEvents () {
- // Modal open button
- const showModalTrigger = findAllDOMElements(this.opts.trigger)
- if (!this.opts.inline && showModalTrigger) {
- showModalTrigger.forEach(trigger => trigger.addEventListener('click', this.openModal))
- }
- if (!this.opts.inline && !showModalTrigger) {
- this.uppy.log('Dashboard modal trigger not found. Make sure `trigger` is set in Dashboard options unless you are planning to call openModal() method yourself')
- }
- if (!this.opts.inline) {
- document.addEventListener('keydown', this.onKeydown)
- }
- // Drag Drop
- this.removeDragDropListener = dragDrop(this.el, (files) => {
- this.handleDrop(files)
- })
- this.updateDashboardElWidth()
- window.addEventListener('resize', this.updateDashboardElWidth)
- }
- removeEvents () {
- const showModalTrigger = findAllDOMElements(this.opts.trigger)
- if (!this.opts.inline && showModalTrigger) {
- showModalTrigger.forEach(trigger => trigger.removeEventListener('click', this.openModal))
- }
- if (!this.opts.inline) {
- document.removeEventListener('keydown', this.onKeydown)
- }
- this.removeDragDropListener()
- window.removeEventListener('resize', this.updateDashboardElWidth)
- }
- updateDashboardElWidth () {
- const dashboardEl = this.el.querySelector('.uppy-Dashboard-inner')
- this.uppy.log(`Dashboard width: ${dashboardEl.offsetWidth}`)
- this.setPluginState({
- containerWidth: dashboardEl.offsetWidth
- })
- }
- toggleFileCard (fileId) {
- this.setPluginState({
- fileCardFor: fileId || false
- })
- }
- handleDrop (files) {
- this.uppy.log('[Dashboard] Files were dropped')
- console.log(files)
- files.forEach((file) => {
- this.uppy.addFile({
- source: this.id,
- name: file.name,
- type: file.type,
- data: file
- })
- })
- }
- render (state) {
- const pluginState = this.getPluginState()
- const files = state.files
- const newFiles = Object.keys(files).filter((file) => {
- return !files[file].progress.uploadStarted
- })
- const inProgressFiles = Object.keys(files).filter((file) => {
- return !files[file].progress.uploadComplete &&
- files[file].progress.uploadStarted &&
- !files[file].isPaused
- })
- let inProgressFilesArray = []
- inProgressFiles.forEach((file) => {
- inProgressFilesArray.push(files[file])
- })
- let totalSize = 0
- let totalUploadedSize = 0
- inProgressFilesArray.forEach((file) => {
- totalSize = totalSize + (file.progress.bytesTotal || 0)
- totalUploadedSize = totalUploadedSize + (file.progress.bytesUploaded || 0)
- })
- totalSize = prettyBytes(totalSize)
- totalUploadedSize = prettyBytes(totalUploadedSize)
- const attachRenderFunctionToTarget = (target) => {
- const plugin = this.uppy.getPlugin(target.id)
- return Object.assign({}, target, {
- icon: plugin.icon || this.opts.defaultTabIcon,
- render: plugin.render
- })
- }
- const isSupported = (target) => {
- const plugin = this.uppy.getPlugin(target.id)
- // If the plugin does not provide a `supported` check, assume the plugin works everywhere.
- if (typeof plugin.isSupported !== 'function') {
- return true
- }
- return plugin.isSupported()
- }
- const acquirers = pluginState.targets
- .filter(target => target.type === 'acquirer' && isSupported(target))
- .map(attachRenderFunctionToTarget)
- const progressindicators = pluginState.targets
- .filter(target => target.type === 'progressindicator')
- .map(attachRenderFunctionToTarget)
- const startUpload = (ev) => {
- this.uppy.upload().catch((err) => {
- // Log error.
- this.uppy.log(err.stack || err.message || err)
- })
- }
- const cancelUpload = (fileID) => {
- this.uppy.emit('upload-cancel', fileID)
- this.uppy.removeFile(fileID)
- }
- const saveFileCard = (meta, fileID) => {
- this.uppy.setFileMeta(fileID, meta)
- this.toggleFileCard()
- }
- return DashboardUI({
- state: state,
- modal: pluginState,
- newFiles: newFiles,
- files: files,
- totalFileCount: Object.keys(files).length,
- totalProgress: state.totalProgress,
- acquirers: acquirers,
- activePanel: pluginState.activePanel,
- getPlugin: this.uppy.getPlugin,
- progressindicators: progressindicators,
- autoProceed: this.uppy.opts.autoProceed,
- hideUploadButton: this.opts.hideUploadButton,
- id: this.id,
- closeModal: this.requestCloseModal,
- handleClickOutside: this.handleClickOutside,
- handleInputChange: this.handleInputChange,
- handlePaste: this.handlePaste,
- inline: this.opts.inline,
- showPanel: this.showPanel,
- hideAllPanels: this.hideAllPanels,
- log: this.uppy.log,
- i18n: this.i18n,
- addFile: this.uppy.addFile,
- removeFile: this.uppy.removeFile,
- info: this.uppy.info,
- note: this.opts.note,
- metaFields: pluginState.metaFields,
- resumableUploads: this.uppy.state.capabilities.resumableUploads || false,
- startUpload: startUpload,
- pauseUpload: this.uppy.pauseResume,
- retryUpload: this.uppy.retryUpload,
- cancelUpload: cancelUpload,
- fileCardFor: pluginState.fileCardFor,
- toggleFileCard: this.toggleFileCard,
- saveFileCard: saveFileCard,
- updateDashboardElWidth: this.updateDashboardElWidth,
- width: this.opts.width,
- height: this.opts.height,
- showLinkToFileUploadResult: this.opts.showLinkToFileUploadResult,
- proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy,
- currentWidth: pluginState.containerWidth,
- isWide: pluginState.containerWidth > 400,
- isTargetDOMEl: this.isTargetDOMEl,
- allowedFileTypes: this.uppy.opts.restrictions.allowedFileTypes,
- maxNumberOfFiles: this.uppy.opts.restrictions.maxNumberOfFiles
- })
- }
- discoverProviderPlugins () {
- this.uppy.iteratePlugins((plugin) => {
- if (plugin && !plugin.target && plugin.opts && plugin.opts.target === this.constructor) {
- this.addTarget(plugin)
- }
- })
- }
- install () {
- // Set default state for Dashboard
- this.setPluginState({
- isHidden: true,
- showFileCard: false,
- activePanel: false,
- metaFields: this.opts.metaFields,
- targets: []
- })
- const target = this.opts.target
- if (target) {
- this.mount(target, this)
- }
- const plugins = this.opts.plugins || []
- plugins.forEach((pluginID) => {
- const plugin = this.uppy.getPlugin(pluginID)
- if (plugin) plugin.mount(this, plugin)
- })
- if (!this.opts.disableStatusBar) {
- this.uppy.use(StatusBar, {
- id: `${this.id}:StatusBar`,
- target: this,
- hideUploadButton: this.opts.hideUploadButton,
- showProgressDetails: this.opts.showProgressDetails,
- hideAfterFinish: this.opts.hideProgressAfterFinish,
- locale: this.opts.locale
- })
- }
- if (!this.opts.disableInformer) {
- this.uppy.use(Informer, {
- id: `${this.id}:Informer`,
- target: this
- })
- }
- if (!this.opts.disableThumbnailGenerator) {
- this.uppy.use(ThumbnailGenerator, {
- id: `${this.id}:ThumbnailGenerator`,
- thumbnailWidth: this.opts.thumbnailWidth
- })
- }
- this.discoverProviderPlugins()
- this.initEvents()
- }
- uninstall () {
- if (!this.opts.disableInformer) {
- const informer = this.uppy.getPlugin('Informer')
- // Checking if this plugin exists, in case it was removed by uppy-core
- // before the Dashboard was.
- if (informer) this.uppy.removePlugin(informer)
- }
- if (!this.opts.disableStatusBar) {
- const statusBar = this.uppy.getPlugin('StatusBar')
- if (statusBar) this.uppy.removePlugin(statusBar)
- }
- if (!this.opts.disableThumbnailGenerator) {
- const thumbnail = this.uppy.getPlugin('ThumbnailGenerator')
- if (thumbnail) this.uppy.removePlugin(thumbnail)
- }
- const plugins = this.opts.plugins || []
- plugins.forEach((pluginID) => {
- const plugin = this.uppy.getPlugin(pluginID)
- if (plugin) plugin.unmount()
- })
- this.unmount()
- this.removeEvents()
- }
- }
|