mirror of
https://github.com/chinchang/web-maker.git
synced 2025-07-17 03:51:13 +02:00
add drag n drop to files explorer 🔥
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { h, Component } from 'preact';
|
||||
import UserCodeMirror from './UserCodeMirror';
|
||||
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
|
||||
import { log, loadJS, linearizeFiles } from '../utils';
|
||||
import { log, loadJS } from '../utils';
|
||||
|
||||
import { linearizeFiles, assignFilePaths } from '../fileUtils';
|
||||
|
||||
import { SplitPane } from './SplitPane';
|
||||
import { trackEvent } from '../analytics';
|
||||
import CodeMirror from '../CodeMirror';
|
||||
@@ -161,17 +164,6 @@ export default class ContentWrapFiles extends Component {
|
||||
}, this.updateDelay);
|
||||
}
|
||||
|
||||
constructFilePaths(files, parentPath = '/user') {
|
||||
files.forEach(file => {
|
||||
if (file.isFolder) {
|
||||
this.constructFilePaths(file.children, `${parentPath}/${file.name}`);
|
||||
} else {
|
||||
file.path = `${parentPath}/${file.name}`;
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
createPreviewFile(html, css, js) {
|
||||
// Track if people have written code.
|
||||
if (!trackEvent.hasTrackedCode && (html || css || js)) {
|
||||
@@ -183,7 +175,7 @@ export default class ContentWrapFiles extends Component {
|
||||
const duplicateFiles = JSON.parse(
|
||||
JSON.stringify(this.props.currentItem.files)
|
||||
);
|
||||
const files = linearizeFiles(this.constructFilePaths(duplicateFiles));
|
||||
const files = linearizeFiles(assignFilePaths(duplicateFiles, '/user'));
|
||||
|
||||
files.forEach(file => {
|
||||
obj[file.path] = file.content || '';
|
||||
@@ -589,6 +581,7 @@ export default class ContentWrapFiles extends Component {
|
||||
onAddFile={this.props.onAddFile}
|
||||
onRemoveFile={this.props.onRemoveFile}
|
||||
onRenameFile={this.props.onRenameFile}
|
||||
onFileDrop={this.props.onFileDrop}
|
||||
/>
|
||||
</div>
|
||||
<div class="code-side" id="js-code-side">
|
||||
|
@@ -3,83 +3,86 @@ const ENTER_KEY = 13;
|
||||
const ESCAPE_KEY = 27;
|
||||
|
||||
function FileIcon({ file }) {
|
||||
let path;
|
||||
if (file.isFolder) {
|
||||
return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
{file.isCollapsed ? (
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
const type = file.name.match(/.(\w+)$/)[1];
|
||||
switch (type) {
|
||||
case 'html':
|
||||
return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
if (!file.children.length) {
|
||||
path = (
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
path = file.isCollapsed ? (
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z"
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const type = file.name.match(/.(\w+)$/)[1];
|
||||
switch (type) {
|
||||
case 'html':
|
||||
path = (
|
||||
<path
|
||||
fill="rgb(225, 187, 21)"
|
||||
d="M14.6,16.6L19.2,12L14.6,7.4L16,6L22,12L16,18L14.6,16.6M9.4,16.6L4.8,12L9.4,7.4L8,6L2,12L8,18L9.4,16.6Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
/* return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="rgb(225, 187, 21)"
|
||||
d="M12,17.56L16.07,16.43L16.62,10.33H9.38L9.2,8.3H16.8L17,6.31H7L7.56,12.32H14.45L14.22,14.9L12,15.5L9.78,14.9L9.64,13.24H7.64L7.93,16.43L12,17.56M4.07,3H19.93L18.5,19.2L12,21L5.5,19.2L4.07,3Z"
|
||||
/>
|
||||
</svg>
|
||||
); */
|
||||
case 'js':
|
||||
return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
);
|
||||
break;
|
||||
|
||||
case 'js':
|
||||
path = (
|
||||
<path
|
||||
fill="rgb(255, 165, 0)"
|
||||
d="M3,3H21V21H3V3M7.73,18.04C8.13,18.89 8.92,19.59 10.27,19.59C11.77,19.59 12.8,18.79 12.8,17.04V11.26H11.1V17C11.1,17.86 10.75,18.08 10.2,18.08C9.62,18.08 9.38,17.68 9.11,17.21L7.73,18.04M13.71,17.86C14.21,18.84 15.22,19.59 16.8,19.59C18.4,19.59 19.6,18.76 19.6,17.23C19.6,15.82 18.79,15.19 17.35,14.57L16.93,14.39C16.2,14.08 15.89,13.87 15.89,13.37C15.89,12.96 16.2,12.64 16.7,12.64C17.18,12.64 17.5,12.85 17.79,13.37L19.1,12.5C18.55,11.54 17.77,11.17 16.7,11.17C15.19,11.17 14.22,12.13 14.22,13.4C14.22,14.78 15.03,15.43 16.25,15.95L16.67,16.13C17.45,16.47 17.91,16.68 17.91,17.26C17.91,17.74 17.46,18.09 16.76,18.09C15.93,18.09 15.45,17.66 15.09,17.06L13.71,17.86Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case 'css':
|
||||
return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
);
|
||||
break;
|
||||
|
||||
case 'css':
|
||||
path = (
|
||||
<path
|
||||
fill="rgb(95, 158, 160)"
|
||||
d="M5,3L4.35,6.34H17.94L17.5,8.5H3.92L3.26,11.83H16.85L16.09,15.64L10.61,17.45L5.86,15.64L6.19,14H2.85L2.06,18L9.91,21L18.96,18L20.16,11.97L20.4,10.76L21.94,3H5Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case 'md':
|
||||
case 'markdown':
|
||||
return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
);
|
||||
break;
|
||||
|
||||
case 'md':
|
||||
case 'markdown':
|
||||
path = (
|
||||
<path
|
||||
fill="skyblue"
|
||||
d="M2,16V8H4L7,11L10,8H12V16H10V10.83L7,13.83L4,10.83V16H2M16,8H19V12H21.5L17.5,16.5L13.5,12H16V8Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'svg':
|
||||
case 'png':
|
||||
return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
);
|
||||
break;
|
||||
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'svg':
|
||||
case 'png':
|
||||
path = (
|
||||
<path
|
||||
fill="crimson"
|
||||
d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M13.96,12.29L11.21,15.83L9.25,13.47L6.5,17H17.5L13.96,12.29Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<svg class="sidebar__file-icon" viewBox="0 0 24 24">
|
||||
{path}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function File({
|
||||
@@ -90,7 +93,8 @@ function File({
|
||||
onRenameBtnClick,
|
||||
onRemoveBtnClick,
|
||||
onNameInputBlur,
|
||||
onNameInputKeyUp
|
||||
onNameInputKeyUp,
|
||||
onFileDrop
|
||||
}) {
|
||||
function focusInput(el) {
|
||||
el &&
|
||||
@@ -98,8 +102,36 @@ function File({
|
||||
el.focus();
|
||||
}, 1);
|
||||
}
|
||||
function dragStartHandler(e) {
|
||||
console.log(file.path);
|
||||
e.dataTransfer.setData('text/plain', file.path);
|
||||
}
|
||||
function dragOverHandler(e) {
|
||||
if (file.isFolder) {
|
||||
e.preventDefault();
|
||||
e.target.classList.add('is-being-dragged-over');
|
||||
e.target.style.outline = '1px dashed';
|
||||
}
|
||||
}
|
||||
function dragLeaveHandler(e) {
|
||||
if (file.isFolder) {
|
||||
e.preventDefault();
|
||||
e.target.style.outline = null;
|
||||
}
|
||||
}
|
||||
function dropHandler(e) {
|
||||
if (file.isFolder) {
|
||||
e.preventDefault();
|
||||
onFileDrop(e.dataTransfer.getData('text/plain'), file);
|
||||
e.target.style.outline = null;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
onDragOver={dragOverHandler}
|
||||
onDragLeave={dragLeaveHandler}
|
||||
onDrop={dropHandler}
|
||||
>
|
||||
{file === fileBeingRenamed ? (
|
||||
<input
|
||||
type="text"
|
||||
@@ -112,6 +144,8 @@ function File({
|
||||
<button
|
||||
class={`sidebar__file ${selectedFile === file ? 'selected' : ''}`}
|
||||
type="button"
|
||||
draggable={file.name !== 'index.html'}
|
||||
onDragStart={dragStartHandler}
|
||||
onClick={onFileSelect.bind(null, file)}
|
||||
>
|
||||
<div class="flex flex-v-center">
|
||||
|
@@ -74,7 +74,6 @@ export default class UserCodeMirror extends Component {
|
||||
|
||||
initEditor() {
|
||||
const { options, prefs } = this.props;
|
||||
console.log(100, options);
|
||||
this.cm = CodeMirror.fromTextArea(this.textarea, {
|
||||
mode: options.mode,
|
||||
lineNumbers: true,
|
||||
|
@@ -20,9 +20,14 @@ import {
|
||||
handleDownloadsPermission,
|
||||
downloadFile,
|
||||
getCompleteHtml,
|
||||
getFilenameFromUrl,
|
||||
linearizeFiles
|
||||
getFilenameFromUrl
|
||||
} from '../utils';
|
||||
import {
|
||||
linearizeFiles,
|
||||
assignFilePaths,
|
||||
getFileFromPath,
|
||||
removeFileAtPath
|
||||
} from '../fileUtils';
|
||||
import { itemService } from '../itemService';
|
||||
import '../db';
|
||||
import { Notifications } from './Notifications';
|
||||
@@ -284,15 +289,20 @@ export default class App extends Component {
|
||||
if (isFileMode) {
|
||||
item = {
|
||||
...item,
|
||||
files: [
|
||||
files: assignFilePaths([
|
||||
{ name: 'index.html', content: '' },
|
||||
{
|
||||
name: 'styles',
|
||||
isFolder: true,
|
||||
children: [{ name: 'style.css', content: '' }]
|
||||
},
|
||||
{ name: 'script.js', content: '' }
|
||||
]
|
||||
{ name: 'script.js', content: '' },
|
||||
{
|
||||
name: 'tempo',
|
||||
isFolder: true,
|
||||
children: [{ name: 'main.css', content: '' }]
|
||||
}
|
||||
])
|
||||
};
|
||||
} else {
|
||||
item = {
|
||||
@@ -1198,13 +1208,12 @@ export default class App extends Component {
|
||||
isCollapsed: true
|
||||
};
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentItem: {
|
||||
...this.state.currentItem,
|
||||
files: [...this.state.currentItem.files, newEntry]
|
||||
}
|
||||
});
|
||||
let currentItem = {
|
||||
...this.state.currentItem,
|
||||
files: [...this.state.currentItem.files, newEntry]
|
||||
};
|
||||
assignFilePaths(currentItem.files);
|
||||
this.setState({ currentItem });
|
||||
}
|
||||
removeFileHandler(fileToRemove) {
|
||||
this.setState({
|
||||
@@ -1217,17 +1226,34 @@ export default class App extends Component {
|
||||
});
|
||||
}
|
||||
renameFileHandler(oldFileName, newFileName) {
|
||||
this.setState({
|
||||
currentItem: {
|
||||
...this.state.currentItem,
|
||||
files: this.state.currentItem.files.map(file => {
|
||||
if (file.name === oldFileName) {
|
||||
return { ...file, name: newFileName };
|
||||
}
|
||||
return file;
|
||||
})
|
||||
}
|
||||
});
|
||||
let currentItem = {
|
||||
...this.state.currentItem,
|
||||
files: this.state.currentItem.files.map(file => {
|
||||
if (file.name === oldFileName) {
|
||||
return { ...file, name: newFileName };
|
||||
}
|
||||
return file;
|
||||
})
|
||||
};
|
||||
assignFilePaths(currentItem.files);
|
||||
|
||||
this.setState({ currentItem });
|
||||
}
|
||||
fileDropHandler(sourceFilePath, destinationFolder) {
|
||||
let { currentItem } = this.state;
|
||||
const { file } = getFileFromPath(currentItem.files, sourceFilePath);
|
||||
|
||||
if (file) {
|
||||
destinationFolder.children.push(file);
|
||||
removeFileAtPath(currentItem.files, sourceFilePath);
|
||||
|
||||
currentItem = {
|
||||
...currentItem,
|
||||
files: [...currentItem.files]
|
||||
};
|
||||
assignFilePaths(currentItem.files);
|
||||
this.setState({ currentItem });
|
||||
}
|
||||
}
|
||||
|
||||
folderSelectHandler(folder) {
|
||||
@@ -1278,6 +1304,7 @@ export default class App extends Component {
|
||||
onAddFile={this.addFileHandler.bind(this)}
|
||||
onRemoveFile={this.removeFileHandler.bind(this)}
|
||||
onRenameFile={this.renameFileHandler.bind(this)}
|
||||
onFileDrop={this.fileDropHandler.bind(this)}
|
||||
onFolderSelect={this.folderSelectHandler.bind(this)}
|
||||
/>
|
||||
) : (
|
||||
|
87
src/fileUtils.js
Normal file
87
src/fileUtils.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { deferred } from './deferred';
|
||||
const esprima = require('esprima');
|
||||
|
||||
/**
|
||||
* Returns a linear file list from a nested file strcuture.
|
||||
* It excludes the folders from the returned list.
|
||||
* @param {array} files Nested file structure
|
||||
*/
|
||||
export function linearizeFiles(files) {
|
||||
function reduceToLinearFiles(files) {
|
||||
return files.reduce((list, currentFile) => {
|
||||
if (currentFile.isFolder) {
|
||||
return [...list, ...reduceToLinearFiles(currentFile.children)];
|
||||
} else {
|
||||
return [...list, currentFile];
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
return reduceToLinearFiles(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively iterates and assigns the `path` property to the files in passed files
|
||||
* array.
|
||||
* @param {array} files files structure for an item
|
||||
* @param {string} parentPath Parent path to prefix with all processed files
|
||||
*/
|
||||
export function assignFilePaths(files, parentPath = '') {
|
||||
files.forEach(file => {
|
||||
file.path = parentPath ? `${parentPath}/${file.name}` : file.name;
|
||||
if (file.isFolder) {
|
||||
assignFilePaths(
|
||||
file.children,
|
||||
parentPath ? `${parentPath}/${file.name}` : file.name
|
||||
);
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file object and it's index that is direct child of passed files array with name as passed fileName.
|
||||
* If not found, returns -1
|
||||
* @param {array} files files structure for an item
|
||||
* @param {string} fileName File/folder name
|
||||
*/
|
||||
export function getChildFileFromName(files, fileName) {
|
||||
const index = files.findIndex(file => file.name === fileName);
|
||||
return { index, file: files[index] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file object and it's index in its parent for the passed path.
|
||||
* If not found, returns {index:-1}
|
||||
* @param {array} files files structure for an item
|
||||
* @param {string} path Path of file to search
|
||||
*/
|
||||
export function getFileFromPath(files, path) {
|
||||
let currentFolder = files;
|
||||
const pathPieces = path.split('/');
|
||||
while (pathPieces.length > 1) {
|
||||
let folderName = pathPieces.shift();
|
||||
currentFolder = getChildFileFromName(currentFolder, folderName).file
|
||||
.children;
|
||||
}
|
||||
// now we should be left with just one value in the pathPieces array - the actual file name
|
||||
return getChildFileFromName(currentFolder, pathPieces[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file object and it's index in its parent for the passed path.
|
||||
* If not found, returns {index:-1}
|
||||
* @param {array} files files structure for an item
|
||||
* @param {string} path Path of file to search
|
||||
*/
|
||||
export function removeFileAtPath(files, path) {
|
||||
let currentFolder = files;
|
||||
const pathPieces = path.split('/');
|
||||
while (pathPieces.length > 1) {
|
||||
let folderName = pathPieces.shift();
|
||||
currentFolder = getChildFileFromName(currentFolder, folderName).file
|
||||
.children;
|
||||
}
|
||||
// now we should be left with just one value in the pathPieces array - the actual file name
|
||||
const { index } = getChildFileFromName(currentFolder, pathPieces[0]);
|
||||
currentFolder.splice(index, 1);
|
||||
}
|
@@ -1622,15 +1622,28 @@ body:not(.is-app) .show-when-app {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
padding: 5px 4px;
|
||||
padding: 5px 5px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sidebar__folder-wrap > div > .sidebar__file {
|
||||
/* 1st level nesting */
|
||||
.sidebar__folder-wrap > div .sidebar__file {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
/* 2nd level nesting */
|
||||
.sidebar__folder-wrap .sidebar__folder-wrap > div > .sidebar__file {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
/* 3rd level nesting */
|
||||
.sidebar__folder-wrap
|
||||
.sidebar__folder-wrap
|
||||
.sidebar__folder-wrap
|
||||
> div
|
||||
> .sidebar__file {
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
.sidebar__file:hover,
|
||||
.sidebar__file:focus {
|
||||
|
83
src/tests/fileUtils.test.js
Normal file
83
src/tests/fileUtils.test.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// See: https://github.com/mzgoddard/preact-render-spy
|
||||
import { shallow, deep } from 'preact-render-spy';
|
||||
import {
|
||||
assignFilePaths,
|
||||
getFileFromPath,
|
||||
removeFileAtPath
|
||||
} from '../fileUtils';
|
||||
|
||||
function getNestedFiles() {
|
||||
return [
|
||||
{ name: 'index.html' },
|
||||
{
|
||||
name: 'styles',
|
||||
isFolder: true,
|
||||
children: [{ name: 'main.css' }, { name: 'style.css' }]
|
||||
},
|
||||
{ name: 'script.js' }
|
||||
];
|
||||
}
|
||||
describe('assignFilePaths', () => {
|
||||
test('should assign path on linear file system', () => {
|
||||
const files = [{ name: 'index.html' }, { name: 'main.css' }];
|
||||
assignFilePaths(files);
|
||||
expect(files[0].path).toBe('index.html');
|
||||
expect(files[1].path).toBe('main.css');
|
||||
});
|
||||
test('should assign path on nested file system', () => {
|
||||
const files = getNestedFiles();
|
||||
assignFilePaths(files);
|
||||
expect(files[0].path).toBe('index.html');
|
||||
expect(files[1].children[0].path).toBe('styles/main.css');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFileFromPath', () => {
|
||||
test('should return file and correct index', () => {
|
||||
const files = getNestedFiles();
|
||||
assignFilePaths(files);
|
||||
const { index, file } = getFileFromPath(files, 'index.html');
|
||||
expect(index).toBe(0);
|
||||
expect(file).toBe(files[index]);
|
||||
});
|
||||
test('should return empty object for non-existent path', () => {
|
||||
const files = getNestedFiles();
|
||||
assignFilePaths(files);
|
||||
const { index, file } = getFileFromPath(files, 'style.css');
|
||||
expect(index).toBe(-1);
|
||||
expect(file).toBe(undefined);
|
||||
});
|
||||
test('should return file and correct index for a nested file', () => {
|
||||
const files = getNestedFiles();
|
||||
assignFilePaths(files);
|
||||
const { index, file } = getFileFromPath(files, 'styles/style.css');
|
||||
expect(index).toBe(1);
|
||||
expect(file).toBe(files[1].children[index]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeFileAtPath', () => {
|
||||
test('should remove direct child file', () => {
|
||||
const files = getNestedFiles();
|
||||
assignFilePaths(files);
|
||||
|
||||
expect(files.length).toBe(3);
|
||||
|
||||
removeFileAtPath(files, 'index.html');
|
||||
expect(files.length).toBe(2);
|
||||
expect(files[0].name).toBe('styles');
|
||||
});
|
||||
|
||||
test('should remove a nested file', () => {
|
||||
const files = getNestedFiles();
|
||||
assignFilePaths(files);
|
||||
|
||||
expect(files.length).toBe(3);
|
||||
|
||||
removeFileAtPath(files, 'styles/style.css');
|
||||
expect(files.length).toBe(3);
|
||||
expect(files[1].children.length).toBe(1);
|
||||
expect(files[0].name).toBe('index.html');
|
||||
expect(files[2].name).toBe('script.js');
|
||||
});
|
||||
});
|
18
src/utils.js
18
src/utils.js
@@ -465,21 +465,3 @@ if (window.IS_EXTENSION) {
|
||||
} else {
|
||||
document.body.classList.add('is-app');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a linear file list from a nested file strcuture.
|
||||
* It excludes the folders from the returned list.
|
||||
* @param {array} files Nested file structure
|
||||
*/
|
||||
export function linearizeFiles(files) {
|
||||
function reduceToLinearFiles(files) {
|
||||
return files.reduce((list, currentFile) => {
|
||||
if (currentFile.isFolder) {
|
||||
return [...list, ...reduceToLinearFiles(currentFile.children)];
|
||||
} else {
|
||||
return [...list, currentFile];
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
return reduceToLinearFiles(files);
|
||||
}
|
||||
|
Reference in New Issue
Block a user