Browse Source

Dashboard: Single file mode (#4188)

* progress pause/resume should be white

* add uppy-c-btn to remaining buttons

* Single file mode: large preview thumbnail for single file

* tweak icon and FileItem sizes

* check for single file mode when files are added or removed

* Don't shorted file names in single file mode

* Refactor a bit

* remove events on uninstall
Artur Paikin 2 years ago
parent
commit
a2a265af02

+ 24 - 0
packages/@uppy/dashboard/src/Dashboard.jsx

@@ -705,6 +705,24 @@ export default class Dashboard extends UIPlugin {
     this.uppy.emit('restore-canceled')
   }
 
+  #generateLargeThumbnailIfSingleFile = () => {
+    if (this.opts.disableThumbnailGenerator) {
+      return
+    }
+
+    const LARGE_THUMBNAIL = 600
+    const files = this.uppy.getFiles()
+
+    if (files.length === 1) {
+      const thumbnailGenerator = this.uppy.getPlugin(`${this.id}:ThumbnailGenerator`)
+      thumbnailGenerator?.setOptions({ thumbnailWidth: LARGE_THUMBNAIL })
+      const fileForThumbnail = { ...files[0], preview: undefined }
+      thumbnailGenerator.requestThumbnail(fileForThumbnail).then(() => {
+        thumbnailGenerator?.setOptions({ thumbnailWidth: this.opts.thumbnailWidth })
+      })
+    }
+  }
+
   #openFileEditorWhenFilesAdded = (files) => {
     const firstFile = files[0]
     if (this.canEditFile(firstFile)) {
@@ -732,6 +750,9 @@ export default class Dashboard extends UIPlugin {
     this.uppy.on('file-editor:complete', this.hideAllPanels)
     this.uppy.on('complete', this.handleComplete)
 
+    this.uppy.on('files-added', this.#generateLargeThumbnailIfSingleFile)
+    this.uppy.on('file-removed', this.#generateLargeThumbnailIfSingleFile)
+
     // ___Why fire on capture?
     //    Because this.ifFocusedOnUppyRecently needs to change before onUpdate() fires.
     document.addEventListener('focus', this.recordIfFocusedOnUppyRecently, true)
@@ -762,6 +783,9 @@ export default class Dashboard extends UIPlugin {
     this.uppy.off('file-editor:complete', this.hideAllPanels)
     this.uppy.off('complete', this.handleComplete)
 
+    this.uppy.off('files-added', this.#generateLargeThumbnailIfSingleFile)
+    this.uppy.off('file-removed', this.#generateLargeThumbnailIfSingleFile)
+
     document.removeEventListener('focus', this.recordIfFocusedOnUppyRecently)
     document.removeEventListener('click', this.recordIfFocusedOnUppyRecently)
 

+ 4 - 0
packages/@uppy/dashboard/src/components/Dashboard.jsx

@@ -20,6 +20,8 @@ const HEIGHT_MD = 400
 
 export default function Dashboard (props) {
   const noFiles = props.totalFileCount === 0
+  const singleFile = props.totalFileCount === 1
+
   const isSizeMD = props.containerWidth > WIDTH_MD
 
   const dashboardClassName = classNames({
@@ -35,6 +37,7 @@ export default function Dashboard (props) {
     'uppy-size--height-md': props.containerHeight > HEIGHT_MD,
     'uppy-Dashboard--isAddFilesPanelVisible': props.showAddFilesPanel,
     'uppy-Dashboard--isInnerWrapVisible': props.areInsidesReadyToBeVisible,
+    'uppy-Dashboard--singleFile': singleFile,
   })
 
   // Important: keep these in sync with the percent width values in `src/components/FileItem/index.scss`.
@@ -135,6 +138,7 @@ export default function Dashboard (props) {
             <FileList
               // eslint-disable-next-line react/jsx-props-no-spreading
               {...props}
+              singleFile={singleFile}
               itemsPerRow={itemsPerRow}
             />
           ) : (

+ 1 - 1
packages/@uppy/dashboard/src/components/FileItem/Buttons/index.jsx

@@ -15,7 +15,7 @@ function EditButton ({
   ) {
     return (
       <button
-        className="uppy-u-reset uppy-Dashboard-Item-action uppy-Dashboard-Item-action--edit"
+        className="uppy-u-reset uppy-c-btn uppy-Dashboard-Item-action uppy-Dashboard-Item-action--edit"
         type="button"
         aria-label={i18n('editFileWithFilename', { file: file.meta.name })}
         title={i18n('editFileWithFilename', { file: file.meta.name })}

+ 16 - 15
packages/@uppy/dashboard/src/components/FileItem/Buttons/index.scss

@@ -30,6 +30,21 @@
     opacity: 1;
   }
 
+  .uppy-size--md &,
+  .uppy-Dashboard--singleFile & {
+    position: absolute;
+    top: -8px;
+    z-index: $zIndex-3;
+    width: 18px;
+    height: 18px;
+    padding: 0;
+    inset-inline-end: -8px;
+
+    &:focus {
+      border-radius: 50%;
+    }
+  }
+
   [data-uppy-theme="dark"] & {
     color: $gray-700;
   }
@@ -40,7 +55,7 @@
 }
 
 // Only for mobile screens
-.uppy-Dashboard:not(.uppy-size--md) {
+.uppy-Dashboard:not(.uppy-size--md):not(.uppy-Dashboard--singleFile) {
   // Vertically center Edit&Remove buttons on mobile
   .uppy-Dashboard-Item-actionWrapper {
     display: flex;
@@ -71,18 +86,4 @@
       border-radius: 3px;
     }
   }
-  // Remove button is in the top right corner
-  .uppy-Dashboard-Item-action--remove {
-    position: absolute;
-    top: -8px;
-    z-index: $zIndex-3;
-    width: 18px;
-    height: 18px;
-    padding: 0;
-    inset-inline-end: -8px;
-
-    &:focus {
-      border-radius: 50%;
-    }
-  }
 }

+ 4 - 1
packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.jsx

@@ -7,6 +7,9 @@ const renderFileName = (props) => {
   const { author, name } = props.file.meta
 
   function getMaxNameLength () {
+    if (props.singleFile) {
+      return 200
+    }
     if (props.containerWidth <= 352) {
       return 35
     }
@@ -78,7 +81,7 @@ const ErrorButton = ({ file, onClick }) => {
   if (file.error) {
     return (
       <button
-        className="uppy-u-reset uppy-Dashboard-Item-errorDetails"
+        className="uppy-u-reset uppy-c-btn uppy-Dashboard-Item-errorDetails"
         aria-label={file.error}
         data-microtip-position="bottom"
         data-microtip-size="medium"

+ 13 - 0
packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss

@@ -1,5 +1,13 @@
 .uppy-Dashboard-Item-fileInfo {
   padding-inline-end: 5px;
+
+  .uppy-Dashboard--singleFile & {
+    padding-inline-end: 10px;
+  }
+
+  .uppy-size--md.uppy-Dashboard--singleFile & {
+    padding-inline-end: 15px;
+  }
 }
 
 .uppy-Dashboard-Item-name {
@@ -15,6 +23,11 @@
   [data-uppy-theme="dark"] & {
     color: $gray-200;
   }
+
+  .uppy-size--md.uppy-Dashboard--singleFile & {
+    font-size: 14px;
+    line-height: 1.4;
+  }
 }
 
 .uppy-Dashboard-Item-fileName {

+ 1 - 1
packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.jsx

@@ -40,7 +40,7 @@ function ProgressIndicatorButton (props) {
   return (
     <div className="uppy-Dashboard-Item-progress">
       <button
-        className="uppy-u-reset uppy-Dashboard-Item-progressIndicator"
+        className="uppy-u-reset uppy-c-btn uppy-Dashboard-Item-progressIndicator"
         type="button"
         aria-label={progressIndicatorTitle(props)}
         title={progressIndicatorTitle(props)}

+ 1 - 0
packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.scss

@@ -15,6 +15,7 @@
   width: 38px;
   height: 38px;
   opacity: 0.9;
+  color: $white;
 
   .uppy-size--md & {
     width: 55px;

+ 1 - 0
packages/@uppy/dashboard/src/components/FileItem/index.jsx

@@ -106,6 +106,7 @@ export default class FileItem extends Component {
             toggleAddFilesPanel={this.props.toggleAddFilesPanel}
             toggleFileCard={this.props.toggleFileCard}
             metaFields={this.props.metaFields}
+            singleFile={this.props.singleFile}
           />
           <Buttons
             file={file}

+ 26 - 3
packages/@uppy/dashboard/src/components/FileItem/index.scss

@@ -8,7 +8,10 @@
   align-items: center;
   padding: 10px;
   border-bottom: 1px solid $gray-200;
-  padding-inline-end: 0;
+
+  .uppy-Dashboard:not(.uppy-Dashboard--singleFile) & {
+    padding-inline-end: 0;
+  }
 
   [data-uppy-theme="dark"] & {
     border-bottom: 1px solid $gray-800;
@@ -36,12 +39,25 @@
     width: calc(25% - #{$rl-margin} - #{$rl-margin});
     height: 190px;
     margin: 5px $rl-margin;
+    padding: 0;
   }
 
   .uppy-size--xl & {
     /* When changing width: also update `itemsPerRow` values in `src/components/Dashboard.js`. */
     width: calc(20% - #{$rl-margin} - #{$rl-margin});
     height: 210px;
+    padding: 0;
+  }
+
+  .uppy-Dashboard--singleFile & {
+    display: block;
+    width: 100%;
+    max-width: 400px;
+    height: auto;
+    border-bottom: 0;
+    position: relative;
+    padding: 0;
+    margin: 10px;
   }
 }
 
@@ -78,12 +94,13 @@
   position: relative;
 
   // @media only mobile
-  .uppy-Dashboard:not(.uppy-size--md) & {
+  .uppy-Dashboard:not(.uppy-size--md):not(.uppy-Dashboard--singleFile) & {
     flex-grow: 0;
     flex-shrink: 0;
     width: 50px;
     height: 50px;
   }
+
   // @media bigger than .md
   .uppy-size--md & {
     width: 100%;
@@ -97,6 +114,11 @@
   .uppy-size--xl & {
     height: 140px;
   }
+
+  .uppy-Dashboard--singleFile & {
+    width: 100%;
+    height: 270px;
+  }
 }
 
 .uppy-Dashboard-Item-fileInfoAndButtons {
@@ -107,7 +129,8 @@
   padding-inline-end: 8px;
   padding-inline-start: 12px;
 
-  .uppy-size--md & {
+  .uppy-size--md &,
+  .uppy-Dashboard--singleFile & {
     align-items: flex-start;
     width: 100%;
     padding: 0;

+ 11 - 9
packages/@uppy/dashboard/src/components/FileList.jsx

@@ -1,4 +1,3 @@
-import classNames from 'classnames'
 import { h } from 'preact'
 import FileItem from './FileItem/index.jsx'
 import VirtualList from './VirtualList.jsx'
@@ -19,12 +18,6 @@ function chunks (list, size) {
 }
 
 export default (props) => {
-  const noFiles = props.totalFileCount === 0
-  const dashboardFilesClass = classNames(
-    'uppy-Dashboard-files',
-    { 'uppy-Dashboard-files--noFiles': noFiles },
-  )
-
   // It's not great that this is hardcoded!
   // It's ESPECIALLY not great that this is checking against `itemsPerRow`!
   const rowHeight = props.itemsPerRow === 1
@@ -53,6 +46,7 @@ export default (props) => {
     isWide: props.isWide,
     metaFields: props.metaFields,
     recoveredState: props.recoveredState,
+    singleFile: props.singleFile,
     // callbacks
     toggleFileCard: props.toggleFileCard,
     handleRequestThumbnail: props.handleRequestThumbnail,
@@ -72,7 +66,7 @@ export default (props) => {
     // The `role="presentation` attribute ensures that the list items are properly
     // associated with the `VirtualList` element.
     // We use the first file ID as the key—this should not change across scroll rerenders
-    <div role="presentation" key={row[0]}>
+    <div class="uppy-Dashboard-filesInner" role="presentation" key={row[0]}>
       {row.map((fileID) => (
         <FileItem
           key={fileID}
@@ -88,9 +82,17 @@ export default (props) => {
     </div>
   )
 
+  if (props.singleFile) {
+    return (
+      <div class="uppy-Dashboard-files">
+        {renderRow(rows[0])}
+      </div>
+    )
+  }
+
   return (
     <VirtualList
-      class={dashboardFilesClass}
+      class="uppy-Dashboard-files"
       role="list"
       data={rows}
       renderRow={renderRow}

+ 16 - 0
packages/@uppy/dashboard/src/style.scss

@@ -727,6 +727,13 @@
   }
 }
 
+.uppy-Dashboard--singleFile .uppy-Dashboard-filesInner  {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+}
+
 .uppy-Dashboard-dropFilesHereHint {
   position: absolute;
   top: 7px;
@@ -867,12 +874,21 @@ a.uppy-Dashboard-poweredBy {
     width: 100%;
     height: 100%;
   }
+
+  .uppy-Dashboard--singleFile & {
+    width: 90px;
+    height: 90px;
+  }
 }
 
 .uppy-Dashboard-Item-previewIconWrap {
   position: relative;
   height: 76px;
   max-height: 75%;
+
+  .uppy-Dashboard--singleFile & {
+    height: 176px;
+  }
 }
 
 .uppy-Dashboard-Item-previewIconBg {