mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-28 17:27:17 +00:00

- Replace onClick arrow functions in all components that use them for efficiency reasons: 300-500% speed-up - Sort by folders and ignore case for better UX - Remove use of `rendition.Button` in files, leading to a 10-20% performance increase when browsing files - Proper sidebar width and spacing - Recents and favorites are now filtered by existence async for a tiny performance improvement - Make Breadcrumbs and Icon pure components to stop frequent re-rendering - Initial support for array constraints - Use first constraint as initial path instead of homedir if a constraint is set - Use correct design height on modal, `calc(100vh - 20px)` - Reset scroll position when browsing a new folder - Fuse Bluebird `.map()` and `.reduce()` in `files.getAllFilesMetadataAsync`. - Use `localeCompare`'s own case-insensitive option instead of calling `.toLowerCase()` twice on `n-2` files compared. - Use 16px font sizes in sidebar and files to match design. - Disable `$locationProvider.html5Mode.rewriteLinks`, which seemed to take 50ms of the directory changing time. - Leave file extension as-is in `files.getFileMetadataSync` and the async counterpart for a very minor performance improvement. Change-Type: patch
213 lines
5.4 KiB
JavaScript
213 lines
5.4 KiB
JavaScript
/*
|
|
* Copyright 2018 resin.io
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
'use strict'
|
|
|
|
const _ = require('lodash')
|
|
const path = require('path')
|
|
const Bluebird = require('bluebird')
|
|
const fs = Bluebird.promisifyAll(require('fs'))
|
|
|
|
/* eslint-disable lodash/prefer-lodash-method */
|
|
|
|
/**
|
|
* @summary Async exists function
|
|
* @function
|
|
* @public
|
|
*
|
|
* @description
|
|
* This is a promise for convenience, as it never fails with an exception/catch.
|
|
*
|
|
* @param {String} fullpath - full path
|
|
* @returns {Boolean}
|
|
*
|
|
* @example
|
|
* files.existsAsync('/home/user/file').then(console.log)
|
|
* > true
|
|
*/
|
|
exports.existsAsync = (fullpath) => {
|
|
return new Bluebird((resolve, reject) => {
|
|
return fs.accessAsync(fullpath, fs.constants.F_OK).then(() => {
|
|
resolve(true)
|
|
}).catch(() => {
|
|
resolve(false)
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @summary Get file metadata
|
|
* @function
|
|
* @private
|
|
*
|
|
* @param {String} dirname - directory name
|
|
* @param {String} [basename] - custom basename to append
|
|
* @returns {Object} file metadata
|
|
*
|
|
* @example
|
|
* try {
|
|
* const file = files.getFileMetadataSync('/home/user')
|
|
* console.log(`Is ${file.basename} a directory? ${file.isDirectory}`)
|
|
* } catch (error) {
|
|
* console.error(error)
|
|
* }
|
|
*/
|
|
exports.getFileMetadataSync = (dirname, basename = '') => {
|
|
// TODO(Shou): use path.parse object information here
|
|
const fullpath = path.join(dirname, basename)
|
|
const pathObj = path.parse(fullpath)
|
|
|
|
// TODO(Shou): this is not true for Windows, figure out Windows hidden files
|
|
const isHidden = pathObj.base.startsWith('.')
|
|
const stats = fs.statSync(fullpath)
|
|
|
|
return {
|
|
basename: pathObj.base,
|
|
dirname: pathObj.dir,
|
|
fullpath,
|
|
extension: pathObj.ext,
|
|
name: pathObj.name,
|
|
isDirectory: stats.isDirectory(),
|
|
isHidden,
|
|
size: stats.size
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @summary Get file metadata asynchronously
|
|
* @function
|
|
* @private
|
|
*
|
|
* @param {String} fullpath - full path
|
|
* @returns {Promise<Object>} promise of file metadata
|
|
*
|
|
* @example
|
|
* files.getFileMetadataAsync('/home/user').then((file) => {
|
|
* console.log(`Is ${file.basename} a directory? ${file.isDirectory}`)
|
|
* })
|
|
*/
|
|
exports.getFileMetadataAsync = (fullpath) => {
|
|
const pathObj = path.parse(fullpath)
|
|
|
|
// NOTE(Shou): this is not true for Windows
|
|
const isHidden = pathObj.base.startsWith('.')
|
|
|
|
return fs.statAsync(fullpath).then((stats) => {
|
|
return {
|
|
basename: pathObj.base,
|
|
dirname: pathObj.dir,
|
|
fullpath,
|
|
extension: pathObj.ext,
|
|
name: pathObj.name,
|
|
isDirectory: stats.isDirectory(),
|
|
isHidden,
|
|
size: stats.size
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @summary Get file metadata for a list of filenames
|
|
* @function
|
|
* @public
|
|
*
|
|
* @description Note that this omits any file that errors
|
|
*
|
|
* @param {String} dirname - directory path
|
|
* @param {Array<String>} basenames - file names
|
|
* @returns {Promise<Array<Object>>} promise of file objects
|
|
*
|
|
* @example
|
|
* files.getAllFilesMetadataAsync(os.homedir(), [ 'file1.txt', 'file2.txt' ])
|
|
*/
|
|
exports.getAllFilesMetadataAsync = (dirname, basenames) => {
|
|
return Bluebird.reduce(basenames, (fileMetas, basename) => {
|
|
return new Bluebird((resolve, reject) => {
|
|
exports.getFileMetadataAsync(path.join(dirname, basename)).then((metadata) => {
|
|
resolve(fileMetas.concat(metadata))
|
|
}).catch(() => {
|
|
resolve(fileMetas)
|
|
})
|
|
})
|
|
}, [])
|
|
}
|
|
|
|
/**
|
|
* @summary Split a path on it's separator(s)
|
|
* @function
|
|
* @public
|
|
*
|
|
* @param {String} fullpath - full path to split
|
|
* @param {Array<String>} [subpaths] - this param shouldn't normally be used
|
|
* @returns {Array<String>}
|
|
*
|
|
* @example
|
|
* console.log(splitPath(path.join(os.homedir(), 'Downloads'))
|
|
* // Linux
|
|
* > [ '/', 'home', 'user', 'Downloads' ]
|
|
* // Windows
|
|
* > [ 'C:', 'Users', 'user', 'Downloads' ]
|
|
*/
|
|
exports.splitPath = (fullpath, subpaths = []) => {
|
|
const {
|
|
base,
|
|
dir,
|
|
root
|
|
} = path.parse(fullpath)
|
|
const isAbsolute = path.isAbsolute(fullpath)
|
|
|
|
// Takes care of 'relative/path'
|
|
if (!isAbsolute && dir === '') {
|
|
return [ base ].concat(subpaths)
|
|
|
|
// Takes care of '/'
|
|
} else if (isAbsolute && base === '') {
|
|
return [ root ].concat(subpaths)
|
|
}
|
|
|
|
return exports.splitPath(dir, [ base ].concat(subpaths))
|
|
}
|
|
|
|
/**
|
|
* @summary Get all subpaths contained in a path
|
|
* @function
|
|
* @private
|
|
*
|
|
* @param {String} fullpath - path string
|
|
* @returns {Array<Object>} - all subpaths as file objects
|
|
*
|
|
* @example
|
|
* const subpaths = files.subpaths('/home/user/Downloads')
|
|
* console.log(subpaths.map(file => file.fullpath))
|
|
* // Linux/macOS
|
|
* > [ '/', '/home', '/home/user', '/home/user/Downloads' ]
|
|
* // Windows
|
|
* > [ 'C:', 'Users', 'user', 'Downloads' ]
|
|
*/
|
|
exports.subpaths = (fullpath) => {
|
|
if (!_.isString(fullpath)) {
|
|
return null
|
|
}
|
|
|
|
const dirs = exports.splitPath(fullpath)
|
|
|
|
return _.map(dirs, (dir, index) => {
|
|
// eslint-disable-next-line no-magic-numbers
|
|
const subdir = dirs.slice(0, index + 1)
|
|
return exports.getFileMetadataSync(path.join(...subdir))
|
|
})
|
|
}
|