diff --git a/src/components/ContentWrapFiles.jsx b/src/components/ContentWrapFiles.jsx index ca7fd2a..2292d7c 100644 --- a/src/components/ContentWrapFiles.jsx +++ b/src/components/ContentWrapFiles.jsx @@ -58,15 +58,16 @@ export default class ContentWrapFiles extends Component { } componentDidUpdate() { const { currentItem } = this.props; + const linearFiles = this.linearizeFiles(currentItem.files); // Select a new file if nothing is selected already or the selected file exists no more. if ( currentItem && currentItem.files && (!this.state.selectedFile || - !currentItem.files.includes(this.state.selectedFile)) + !linearFiles.includes(this.state.selectedFile)) ) { - this.fileSelectHandler(this.props.currentItem.files[0]); + this.fileSelectHandler(linearFiles[0]); } // HACK: becuase its a DOM manipulation // window.logCountEl.textContent = this.logCount; @@ -78,6 +79,18 @@ export default class ContentWrapFiles extends Component { componentDidMount() { this.props.onRef(this); } + 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); + } getEditorOptions(fileName = '') { let options = { gutters: [ @@ -440,6 +453,10 @@ export default class ContentWrapFiles extends Component { this.props.onEditorFocus(editor); } fileSelectHandler(file) { + if (file.isFolder) { + this.props.onFolderSelect(file); + return; + } this.setState({ editorOptions: this.getEditorOptions(file.name), selectedFile: file diff --git a/src/components/SidePane.jsx b/src/components/SidePane.jsx index 162a6b5..e5c3619 100644 --- a/src/components/SidePane.jsx +++ b/src/components/SidePane.jsx @@ -2,10 +2,25 @@ import { h, Component } from 'preact'; const ENTER_KEY = 13; const ESCAPE_KEY = 27; -function FileIcon({ fileName }) { - if (!fileName) fileName = 'sd.sd'; - - const type = fileName.match(/.(\w+)$/)[1]; +function FileIcon({ file }) { + if (file.isFolder) { + return ( + + {file.isCollapsed ? ( + + ) : ( + + )} + + ); + } + const type = file.name.match(/.(\w+)$/)[1]; switch (type) { case 'html': return ( @@ -64,19 +79,95 @@ function FileIcon({ fileName }) { /> ); - case 'jsx': - case 'tsx': - return ( - - - - ); } } +function File({ + file, + selectedFile, + fileBeingRenamed, + onFileSelect, + onRenameBtnClick, + onRemoveBtnClick, + onNameInputBlur, + onNameInputKeyUp +}) { + function focusInput(el) { + el && + setTimeout(() => { + el.focus(); + }, 1); + } + return ( +
+ {file === fileBeingRenamed ? ( + + ) : ( + + + +
+ + )} + + ); +} +function Folder(props) { + return ( +
+ + +
+ ); +} + export class SidePane extends Component { addFileButtonClickHandler() { this.setState({ isEditing: true }); @@ -132,12 +223,12 @@ export class SidePane extends Component { this.props.onRemoveFile(file); } } - renameFile() { + renameFile(e) { // This gets called twice when enter is pressed, because blur also fires. - if (!this.renameFileNameInput) { + if (!e.target || !this.state.fileBeingRenamed) { return; } - const newFileName = this.renameFileNameInput.value; + const newFileName = e.target.value; if (!this.warnForExistingFileName(newFileName)) { return; } @@ -146,9 +237,9 @@ export class SidePane extends Component { } this.setState({ fileBeingRenamed: null }); } - renameFileNameInputKeyDownHandler(e) { + renameFileNameInputKeyUpHandler(e) { if (e.which === ENTER_KEY) { - this.renameFile(); + this.renameFile(e); } else if (e.which === ESCAPE_KEY) { this.setState({ fileBeingRenamed: null }); } @@ -158,12 +249,16 @@ export class SidePane extends Component { this.setState({ fileBeingRenamed: file }); - setTimeout(() => { - this.renameFileNameInput.focus(); - }, 1); } render() { const { files, onFileSelect, selectedFile, onRemoveFile } = this.props; + const moreProps = { + onRemoveBtnClick: this.removeFileClickHandler.bind(this), + onRenameBtnClick: this.renameFileClickHandler.bind(this), + onNameInputBlur: this.renameFile.bind(this), + onNameInputKeyUp: this.renameFileNameInputKeyUpHandler.bind(this), + fileBeingRenamed: this.state.fileBeingRenamed + }; return ( ) : null} - {files.map(file => ( -
- {file === this.state.fileBeingRenamed ? ( - (this.renameFileNameInput = el)} - value={file.name} - onBlur={this.renameFile.bind(this)} - onKeyUpCapture={this.renameFileNameInputKeyDownHandler.bind( - this - )} - /> + {files.map( + file => + file.isFolder ? ( + ) : ( - - - -
- - )} - - ))} + + ) + )} ); } diff --git a/src/components/app.jsx b/src/components/app.jsx index ffbc561..c52f7ee 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -285,7 +285,11 @@ export default class App extends Component { ...item, files: [ { name: 'index.html', content: '' }, - { name: 'style.css', content: '' }, + { + name: 'styles', + isFolder: true, + children: [{ name: 'style.css', content: '' }] + }, { name: 'script.js', content: '' } ] }; @@ -1193,6 +1197,7 @@ export default class App extends Component { ] } }); + console.log(11, this.state.currentItem); } removeFileHandler(fileToRemove) { this.setState({ @@ -1218,6 +1223,20 @@ export default class App extends Component { }); } + folderSelectHandler(folder) { + this.setState({ + currentItem: { + ...this.state.currentItem, + files: this.state.currentItem.files.map(file => { + if (file === folder) { + return { ...file, isCollapsed: !folder.isCollapsed }; + } + return file; + }) + } + }); + } + render() { return (
@@ -1252,6 +1271,7 @@ export default class App extends Component { onAddFile={this.addFileHandler.bind(this)} onRemoveFile={this.removeFileHandler.bind(this)} onRenameFile={this.renameFileHandler.bind(this)} + onFolderSelect={this.folderSelectHandler.bind(this)} /> ) : ( div > .sidebar__file { + padding-left: 1rem; +} + .sidebar__file:hover, .sidebar__file:focus { background-color: rgba(255, 255, 255, 0.05); @@ -1653,6 +1657,10 @@ body:not(.is-app) .show-when-app { visibility: visible; } +.sidebar__folder-wrap[data-collapsed='true'] { + display: none; +} + .is-project .hide-in-file-mode { display: none !important; }