From ec40a4f8ab3ec65287dd2c9dcfa6661082c20d20 Mon Sep 17 00:00:00 2001 From: btzr-io Date: Tue, 12 May 2020 23:05:37 -0500 Subject: [PATCH] add file selector refactor web file system utils update styles --- ui/component/common/icon-custom.jsx | 6 +++ ui/component/fileDrop/view.jsx | 79 ++++++++++++++++++++++++----- ui/constants/icons.js | 1 + ui/scss/component/_file-drop.scss | 57 +++++++++++++++++++++ ui/util/web-file-system.js | 66 +++++++++++------------- 5 files changed, 160 insertions(+), 49 deletions(-) diff --git a/ui/component/common/icon-custom.jsx b/ui/component/common/icon-custom.jsx index 59fc4cac0..0423b2aff 100644 --- a/ui/component/common/icon-custom.jsx +++ b/ui/component/common/icon-custom.jsx @@ -623,4 +623,10 @@ export const icons = { ), + [ICONS.COMPLETED]: buildIcon( + + + + + ), }; diff --git a/ui/component/fileDrop/view.jsx b/ui/component/fileDrop/view.jsx index dd7fb986f..cadfe068a 100644 --- a/ui/component/fileDrop/view.jsx +++ b/ui/component/fileDrop/view.jsx @@ -1,11 +1,14 @@ // @flow import React from 'react'; +import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; import * as PUBLISH_TYPES from 'constants/publish_types'; -import useDragDrop from 'effects/use-drag-drop'; +import Icon from 'component/common/icon'; import classnames from 'classnames'; +import useDragDrop from 'effects/use-drag-drop'; import { getTree } from 'util/web-file-system'; import { withRouter } from 'react-router'; +import { useRadioState, Radio, RadioGroup } from 'reakit/Radio'; type Props = { // Lazy fix for flow errors: @@ -25,8 +28,45 @@ type Props = { }, }; +type FileListProps = { + files: Array, + onSelected: string => void, +}; + const PUBLISH_URL = `/$/${PAGES.PUBLISH}`; +function FileList(props: FileListProps) { + const { files, onSelected } = props; + const radio = useRadioState(); + + React.useEffect(() => { + if (!radio.currentId) { + radio.first(); + } + + if (radio.state && radio.state !== '') { + onSelected(radio.state); + } + }, [radio, onSelected]); + + return ( + + {files.map((entry, index) => { + const item = radio.stops[index]; + const selected = item && item.id === radio.currentId; + + return ( + + ); + })} + + ); +} + function FileDrop(props: Props) { const { history, filePath, updatePublishForm } = props; const { drag, dropData } = useDragDrop(); @@ -43,12 +83,23 @@ function FileDrop(props: Props) { } }, [history]); + const handleFileSelected = name => { + if (files && files.length) { + const selected = files.find(file => file.name === name); + if (selected && selected.name !== (selectedFile && selectedFile.name)) { + setSelectedFile(selected); + } + } + }; + React.useEffect(() => { // Handle drop... if (dropData) { getTree(dropData) .then(entries => { - setFiles(entries); + if (entries && entries.length) { + setFiles(entries); + } }) .catch(error => { // Invalid entry / entries @@ -70,14 +121,12 @@ function FileDrop(props: Props) { if (!drag && files.length) { if (files.length === 1) { // Handle single file publish - files[0].entry.file(webFile => { - setSelectedFile(webFile); - updatePublishForm({ filePath: { publish: PUBLISH_TYPES.DROP, webFile } }); - }); + setSelectedFile(files[0]); + updatePublishForm({ filePath: { publish: PUBLISH_TYPES.DROP, webFile: files[0] } }); } } // Handle files - }, [drag, files, error]); + }, [drag, files, error, updatePublishForm, setSelectedFile]); // Wait for publish state update: React.useEffect(() => { @@ -91,14 +140,20 @@ function FileDrop(props: Props) { navigateToPublish(); } } - }, [filePath, selectedFile, navigateToPublish]); + }, [filePath, selectedFile, navigateToPublish, setFiles]); + const multipleFiles = files.length > 1; return (
-

Drop your files here!

- {files.map(({ entry }) => ( -
{entry.name}
- ))} +
+ +

{multipleFiles ? `Only one file is allowed choose wisely` : `Drop here to publish!`}

+ {files && files.length > 0 && ( +
+ +
+ )} +
); } diff --git a/ui/constants/icons.js b/ui/constants/icons.js index 8ab697305..71cc20b03 100644 --- a/ui/constants/icons.js +++ b/ui/constants/icons.js @@ -100,3 +100,4 @@ export const SLIDERS = 'Sliders'; export const SCIENCE = 'Science'; export const ANALYTICS = 'BarChart2'; export const PURCHASED = 'Key'; +export const CIRCLE = 'Circle'; diff --git a/ui/scss/component/_file-drop.scss b/ui/scss/component/_file-drop.scss index 22b307187..db87efdf5 100644 --- a/ui/scss/component/_file-drop.scss +++ b/ui/scss/component/_file-drop.scss @@ -11,9 +11,66 @@ z-index: 5; transition: opacity 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + &.file-drop--show { opacity: 1; pointer-events: all; transition: opacity 0.3s ease; } + + .file-drop__area { + display: flex; + align-items: center; + flex-direction: column; + padding: var(--spacing-large); + min-width: 400px; + + .file-drop__list { + max-height: 200px; + overflow: auto; + width: 100%; + + fieldset { + display: flex; + align-items: center; + flex-direction: column; + background: var(--color-menu-background); + + label { + margin: 0; + display: flex; + align-items: center; + padding: var(--spacing-miniscule) var(--spacing-small); + + &.selected { + background: rgba(0, 0, 0, 0.2); + box-shadow: inset 0 0 0 3px var(--color-focus); + } + + .icon { + margin-right: var(--spacing-small); + opacity: 0.64; + } + } + + input { + width: 0; + height: 0; + margin: 0; + padding: 0; + opacity: 0; + } + } + } + + .main-icon { + margin: var(--spacing-small); + } + p { + margin-bottom: var(--spacing-large); + } + } } diff --git a/ui/util/web-file-system.js b/ui/util/web-file-system.js index fe73c6ac4..d22b41b57 100644 --- a/ui/util/web-file-system.js +++ b/ui/util/web-file-system.js @@ -6,72 +6,64 @@ const getAsEntry = item => { if (item.kind === 'file' && item.webkitGetAsEntry) { return item.webkitGetAsEntry(); } - return null; }; +// Get file object from fileEntry +const getFile = fileEntry => new Promise((resolve, reject) => fileEntry.file(resolve, reject)); + // Read entries from directory const readDirectory = directory => { - let dirReader = directory.createReader(); - - return new Promise((resolve, reject) => { - dirReader.readEntries( - results => { - if (results.length) { - resolve(results); - } else { - reject(); - } - }, - error => reject(error) - ); - }); + // Some browsers don't support this + if (directory.createReader !== undefined) { + let dirReader = directory.createReader(); + return new Promise((resolve, reject) => dirReader.readEntries(resolve, reject)); + } }; // Get file system entries from the dataTransfer items list: -export const getFiles = dataTransfer => { + +const getFiles = (items, directoryEntries) => { let entries = []; - const { items } = dataTransfer; - for (let i = 0; i < items.length; i++) { - const entry = getAsEntry(items[i]); - if (entry !== null && entry.isFile) { - entries.push({ entry }); + + for (let item of items) { + const entry = directoryEntries ? item : getAsEntry(item); + if (entry && entry.isFile) { + const file = getFile(entry); + entries.push(file); } } - return entries; + + return Promise.all(entries); }; // Generate a valid file tree from dataTransfer: // - Ignores directory entries // - Ignores recursive search export const getTree = async dataTransfer => { - let tree = []; if (dataTransfer) { const { items, files } = dataTransfer; // Handle single item drop if (files.length === 1) { - const entry = getAsEntry(items[0]); + const root = getAsEntry(items[0]); // Handle entry - if (entry) { - const root = { entry }; + if (root) { // Handle directory - if (entry.isDirectory) { - const directoryEntries = await readDirectory(entry); - directoryEntries.forEach(item => { - if (item.isFile) { - tree.push({ entry: item, rootPath: root.path }); - } - }); + if (root.isDirectory) { + const directoryEntries = await readDirectory(root); + // Get each file from the list + return getFiles(directoryEntries, true); } // Hanlde file - if (entry.isFile) { - tree.push(root); + if (root.isFile) { + const file = await getFile(root); + return [file]; } } } // Handle multiple items drop if (files.length > 1) { - tree = tree.concat(getFiles(dataTransfer)); + // Convert items to fileEntry and get each file + return getFiles(items); } } - return tree; };