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')
);