import React from 'react'; import ReactDom from 'react-dom'; import BeakerService from './BeakerService'; import CloudExplorerView from './CloudExplorerView'; import * as ImageBankService from './ImageBankService'; import ImageBankView from './ImageBankView'; import Tabs from './Tabs'; const STORAGE_KEY_PATH = 'CloudExplorer.path'; // Titles const TITLE_OPEN_FILE = 'Select a file'; const TITLE_OPEN_FILES = 'Select file(s)'; const TITLE_OPEN_FOLDER = 'Select a folder'; const TITLE_SAVE_AS = 'Save as...'; /** * Class in charge of the history and init of the main CloudExplorer component */ class App extends React.Component { static focusInput static focus () { if (!App.focusInput) { App.focusInput = document.createElement('input'); // Hide the focus input and attach it to the DOM App.focusInput.style.left = '-1000px'; App.focusInput.style.position = 'absolute'; document.body.appendChild(App.focusInput); } /* * SetTimeout because we might need to wait for a click to finish bubbling * e.g. when edit text, the UI layer is hidden, click on the stage => focus on the stage iframe */ setTimeout(() => { App.focusInput.focus(); App.focusInput.blur(); document.getSelection().removeAllRanges(); }, 0); } createBlob (path, file) { if (file.url) { // Case of an absolute URL (probably an image bank? return {...file}; } return {...file, folder: this.state.unifile.getPath(path), path: this.state.unifile.getPath(path.concat(file.name)), service: path[0], url: this.state.unifile.getUrl(path.concat(file.name))}; } static fromBlobToPath (blob, folderPath = false) { const path = folderPath ? blob.folder : blob.path; return [ blob.service, ...path.split('/').filter((file) => file !== '') ]; } constructor () { super(); window.ce = this; // get the list of image banks this.getImageBanks().then((imageBanks) => { this.setState({imageBanks}); }); // start with the path in local storage const path = localStorage.getItem(STORAGE_KEY_PATH) || '[]'; this.state.path = JSON.parse(path); } state = { unifile: new BeakerService([]), imageBanks: [], extensions: [], onCancel: null, onError: null, onPick: null, path: [], inputName: false, pickFolder: false, selection: [], thumbnailMode: false, title: '', } onChange (path) { if (path !== this.state.path) { // store the old path in case of error const oldPath = this.state.path; // apply the new path this.setState({ path, selection: [] }, () => { this.state.unifile.cd(path) .then(() => { this.cloudExplorer.ls() }) .catch((e) => { this.state.onError(e); this.setState({ path: oldPath }); }); }) } localStorage.setItem(STORAGE_KEY_PATH, JSON.stringify(path)); } onSelection (selection) { this.setState({ defaultFileName: selection.length && !selection[0].isDir ? selection[0].name : this.state.defaultFileName, selection }); } /* * ////////////////// * Class methods * ////////////////// */ onCloudExplorerReady (cloudExplorer) { this.cloudExplorer = cloudExplorer || this.cloudExplorer; } read (blob) { return this.state.unifile.read(this.constructor.fromBlobToPath(blob)); } write (data, blob) { // Convert data to an Array of File const content = (Array.isArray(data) ? data : [data]) .map((c) => { if (c.constructor === String) return new File([c], blob.path.split('/').pop(), {type: 'text/plain'}); if (c instanceof File) return c; throw new Error('Invalid data. You must provide a String or a File'); }); return this.state.unifile.upload(this.constructor.fromBlobToPath(blob, true), content); } /* * ////////////////// * API * ////////////////// */ thumbnailMode (show) { this.setState({thumbnailMode: show}); } openFile (extensions = null) { return new Promise((resolve, reject) => { App.focus(); this.setState({ extensions, inputName: false, multiple: false, onCancel: () => resolve(null), onError: (e) => reject(e), onPick: (files) => resolve(this.createBlob(this.state.path, files[0])), onSave: null, pickFolder: false, selection: [], title: TITLE_OPEN_FILE, }, () => this.cloudExplorer.ls()); }); } openFiles (extensions = null) { return new Promise((resolve, reject) => { App.focus(); this.setState({ extensions, inputName: false, multiple: true, onCancel: () => resolve(null), onError: (e) => reject(e), onPick: (files) => resolve(files.map((file) => this.createBlob(this.state.path, file))), onSave: null, pickFolder: false, selection: [], title: TITLE_OPEN_FILES, }, () => this.cloudExplorer.ls()); }); } openFolder () { return new Promise((resolve, reject) => { App.focus(); this.setState({ extensions: [], inputName: false, multiple: false, onCancel: () => resolve(null), onError: (e) => reject(e), onPick: (files) => { if (files.length) { // Case of a selected folder in the current path resolve(this.createBlob(this.state.path, files[0])); } else if (this.state.path.length > 1) { // The user pressed "ok" to select the current folder const endOffset = -1; resolve(this.createBlob(this.state.path.slice(0, endOffset), { isDir: true, mime: 'application/octet-stream', name: this.state.path[this.state.path.length - 1] })); } else { // Same case but for the / folder (root) resolve(this.createBlob(this.state.path, { isDir: true, mime: 'application/octet-stream', name: '' })); } }, onSave: null, pickFolder: true, selection: [], title: TITLE_OPEN_FOLDER, }, () => this.cloudExplorer.ls()); }); } saveAs (defaultFileName, extensions = null) { return new Promise((resolve, reject) => { App.focus(); this.setState({ defaultFileName, extensions, inputName: true, multiple: false, onCancel: () => resolve(null), onError: (e) => reject(e), onPick: null, onSave: (fileName) => resolve(this.createBlob(this.state.path, {name: fileName})), pickFolder: false, selection: [], title: TITLE_SAVE_AS, }, () => this.cloudExplorer.ls()); }); } reload (extensions) { this.setState({extensions}, () => this.cloudExplorer.ls()); return Promise.resolve(); } getImageBanks () { return ImageBankService.list(); } getServices () { return this.state.unifile.getServices(); } // The auth method has to be called on a click or keydown in order not to be blocked by the browser auth (serviceName) { return this.state.unifile.auth(serviceName); } getHideTabs () { return this.state.inputName || this.state.pickFolder || !this.state.extensions || !this.state.extensions.find((ext) => ext === '.jpg'); } render () { // update extension filters (file ext) this.state.unifile.setExtensions(this.state.extensions); // render the UI return (

{ this.state.title }

{[ (this.state.onCancel ? this.state.onCancel() : '')} onCd={(path) => this.onChange(path)} onError={(e) => (this.state.onError ? this.state.onError(e) : '')} onPick={(selection) => (this.state.onPick ? this.state.onPick(selection) : '')} onSave={(fileName) => (this.state.onSave ? this.state.onSave(fileName) : '')} onSelection={(selection) => this.onSelection(selection)} path={this.state.path} pickFolder={this.state.pickFolder} ref={(c) => this.onCloudExplorerReady(c)} selection={this.state.selection} thumbnailMode={this.state.thumbnailMode} onThumbnailMode={(thumbnailMode) => this.setState({thumbnailMode})} /> ] .concat(this.state.imageBanks.map((bank) => this.onSelection(selection)} onCancel={() => (this.state.onCancel ? this.state.onCancel() : '')} onPick={(selection) => (this.state.onPick ? this.state.onPick(selection) : '')} onError={(e) => (this.state.onError ? this.state.onError(e) : '')} />)) }
); } } ReactDom.render( , document.getElementById('cloud-explorer') );