Merge pull request #2405 from resin-io/file-selection-constraint

feat(gui): Enable device specific constraints for file selection
This commit is contained in:
Jonas Hermsmeier 2018-07-03 17:08:39 +02:00 committed by GitHub
commit 2a9e9962e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 89 additions and 21 deletions

View File

@ -48,11 +48,10 @@ module.exports = function (
* @example * @example
* FileSelectorController.getFolderConstraint() * FileSelectorController.getFolderConstraint()
*/ */
this.getFolderConstraints = utils.memoize(() => { this.getFolderConstraint = utils.memoize(() => {
// TODO(Shou): get this dynamically from the mountpoint of a specific port in Etcher Pro
return settings.has('fileBrowserConstraintPath') return settings.has('fileBrowserConstraintPath')
? settings.get('fileBrowserConstraintPath').split(',') ? settings.get('fileBrowserConstraintPath')
: [] : ''
}, angular.equals) }, angular.equals)
/** /**
@ -66,7 +65,6 @@ module.exports = function (
* <file-selector path="FileSelectorController.getPath()"></file-selector> * <file-selector path="FileSelectorController.getPath()"></file-selector>
*/ */
this.getPath = () => { this.getPath = () => {
const [ constraint ] = this.getFolderConstraints() return this.getFolderConstraint() ? '/' : os.homedir()
return constraint || os.homedir()
} }
} }

View File

@ -228,15 +228,38 @@ const File = styled(UnstyledFile)`
class FileList extends React.Component { class FileList extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {
path: props.path, path: props.path,
highlighted: null, highlighted: null,
files: [], files: [],
} }
debug('FileList', props)
} }
readdir (dirname) { readdir (dirname) {
debug('FileList:readdir', dirname) debug('FileList:readdir', dirname)
if (this.props.constraintPath && dirname === '/') {
if (this.props.constraint) {
const mountpoints = this.props.constraint.mountpoints.map(( mount ) => {
const entry = new files.FileEntry(mount.path, {
size: 0,
isFile: () => false,
isDirectory: () => true
})
entry.name = mount.label
return entry
})
debug('FileList:readdir', mountpoints)
window.requestAnimationFrame(() => {
this.setState({ files: mountpoints })
})
}
return
}
files.readdirAsync(dirname).then((files) => { files.readdirAsync(dirname).then((files) => {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
this.setState({ files: files }) this.setState({ files: files })
@ -263,8 +286,10 @@ class FileList extends React.Component {
shouldComponentUpdate (nextProps, nextState) { shouldComponentUpdate (nextProps, nextState) {
const shouldUpdate = (this.state.files !== nextState.files) const shouldUpdate = (this.state.files !== nextState.files)
debug('FileList:shouldComponentUpdate', shouldUpdate) debug('FileList:shouldComponentUpdate', shouldUpdate)
if (this.props.path !== nextProps.path) { if (this.props.path !== nextProps.path || this.props.constraint !== nextProps.constraint) {
this.readdir(nextProps.path) process.nextTick(() => {
this.readdir(nextProps.path)
})
} }
return shouldUpdate return shouldUpdate
} }
@ -293,11 +318,4 @@ class FileList extends React.Component {
} }
} }
FileList.propTypes = {
path: propTypes.string,
onNavigate: propTypes.func,
onSelect: propTypes.func,
constraints: propTypes.arrayOf(propTypes.string)
}
module.exports = FileList module.exports = FileList

View File

@ -28,6 +28,7 @@ const colors = require('./colors')
const Breadcrumbs = require('./path-breadcrumbs') const Breadcrumbs = require('./path-breadcrumbs')
const FileList = require('./file-list') const FileList = require('./file-list')
const RecentFiles = require('./recent-files') const RecentFiles = require('./recent-files')
const files = require('../../../models/files')
const selectionState = require('../../../models/selection-state') const selectionState = require('../../../models/selection-state')
const osDialog = require('../../../os/dialog') const osDialog = require('../../../os/dialog')
@ -113,11 +114,20 @@ const FilePath = styled(UnstyledFilePath)`
class FileSelector extends React.PureComponent { class FileSelector extends React.PureComponent {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {
path: props.path, path: props.path,
highlighted: null, highlighted: null,
constraint: null,
files: [], files: [],
} }
if (props.constraintpath) {
files.getConstraintDevice(props.constraintpath, (error, device) => {
debug('FileSelector:getConstraintDevice', error || device)
this.setState({ constraint: device })
})
}
} }
confirmSelection () { confirmSelection () {
@ -134,13 +144,25 @@ class FileSelector extends React.PureComponent {
debug('FileSelector:componentDidUpdate') debug('FileSelector:componentDidUpdate')
} }
containPath (newPath) {
if (this.state.constraint) {
const isContained = this.state.constraint.mountpoints.some((mount) => {
return !path.relative(mount.path, newPath).startsWith('..')
})
if (!isContained) {
return '/'
}
}
return newPath
}
navigate (newPath) { navigate (newPath) {
debug('FileSelector:navigate', newPath) debug('FileSelector:navigate', newPath)
this.setState({ path: newPath }) this.setState({ path: this.containPath(newPath) })
} }
navigateUp () { navigateUp () {
const newPath = path.join( this.state.path, '..' ) let newPath = this.containPath(path.join(this.state.path, '..'))
debug('FileSelector:navigateUp', this.state.path, '->', newPath) debug('FileSelector:navigateUp', this.state.path, '->', newPath)
this.setState({ path: newPath }) this.setState({ path: newPath })
} }
@ -254,12 +276,15 @@ class FileSelector extends React.PureComponent {
<Breadcrumbs <Breadcrumbs
path={ this.state.path } path={ this.state.path }
navigate={ ::this.navigate } navigate={ ::this.navigate }
constraints={ this.props.constraints } constraintPath={ this.props.constraintpath }
constraint={ this.state.constraint }
/> />
</Header> </Header>
<Main flex="1"> <Main flex="1">
<Flex direction="column" grow="1"> <Flex direction="column" grow="1">
<FileList path={ this.state.path } <FileList path={ this.state.path }
constraintPath={ this.props.constraintpath }
constraint={ this.state.constraint }
onHighlight={ ::this.onHighlight } onHighlight={ ::this.onHighlight }
onSelect={ ::this.selectFile }></FileList> onSelect={ ::this.selectFile }></FileList>
</Flex> </Flex>
@ -282,7 +307,7 @@ class FileSelector extends React.PureComponent {
FileSelector.propTypes = { FileSelector.propTypes = {
path: propTypes.string, path: propTypes.string,
close: propTypes.func, close: propTypes.func,
constraints: propTypes.arrayOf(propTypes.string) constraintpath: propTypes.string,
} }
module.exports = FileSelector module.exports = FileSelector

View File

@ -1,4 +1,4 @@
<file-selector <file-selector
constraints="selector.getFolderConstraints()" constraintpath="selector.getFolderConstraint()"
path="selector.getPath()" path="selector.getPath()"
close="selector.close"></file-selector> close="selector.close"></file-selector>

View File

@ -18,6 +18,7 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const drivelist = require('drivelist')
/* eslint-disable lodash/prefer-lodash-method */ /* eslint-disable lodash/prefer-lodash-method */
/* eslint-disable no-undefined */ /* eslint-disable no-undefined */
@ -70,7 +71,6 @@ class FileEntry {
this.isFile = stats.isFile() this.isFile = stats.isFile()
this.isDirectory = stats.isDirectory() this.isDirectory = stats.isDirectory()
this.size = stats.size this.size = stats.size
this.stats = stats
} }
} }
@ -138,3 +138,30 @@ exports.splitPath = (fullpath, subpaths = []) => {
return exports.splitPath(dir, [ base ].concat(subpaths)) return exports.splitPath(dir, [ base ].concat(subpaths))
} }
/**
* @summary Get constraint path device
* @param {String} pathname - device path
* @param {Function} callback - callback(error, constraintDevice)
* @example
* files.getConstraintDevice('/dev/disk2', (error, device) => {
* // ...
* })
*/
exports.getConstraintDevice = (pathname, callback) => {
drivelist.list((error, devices) => {
if (error) {
callback()
return
}
const constraintDevice = devices.find((device) => {
return device.device === pathname ||
device.devicePath === pathname
})
callback(null, constraintDevice)
})
}
exports.FileEntry = FileEntry