add file deletion

This commit is contained in:
catarak 2016-08-03 15:11:59 -04:00
parent 7fb77772f7
commit da98dcd47e
7 changed files with 245 additions and 59 deletions

View file

@ -42,5 +42,11 @@ export const CONSOLE_EVENT = 'CONSOLE_EVENT';
export const EXPAND_CONSOLE = 'EXPAND_CONSOLE'; export const EXPAND_CONSOLE = 'EXPAND_CONSOLE';
export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE'; export const COLLAPSE_CONSOLE = 'COLLAPSE_CONSOLE';
export const SHOW_FILE_OPTIONS = 'SHOW_FILE_OPTIONS';
export const HIDE_FILE_OPTIONS = 'HIDE_FILE_OPTIONS';
export const UPDATE_FILE_NAME = 'UPDATE_FILE_NAME';
export const DELETE_FILE = 'DELETE_FILE';
// eventually, handle errors more specifically and better // eventually, handle errors more specifically and better
export const ERROR = 'ERROR'; export const ERROR = 'ERROR';

View file

@ -112,3 +112,32 @@ export function createFile(formProps) {
} }
}; };
} }
export function showFileOptions(fileId) {
return {
type: ActionTypes.SHOW_FILE_OPTIONS,
id: fileId
};
}
export function hideFileOptions(fileId) {
return {
type: ActionTypes.HIDE_FILE_OPTIONS,
id: fileId
};
}
export function updateFileName(id, name) {
return {
type: ActionTypes.UPDATE_FILE_NAME,
id,
name
};
}
export function deleteFile(id) {
return {
type: ActionTypes.DELETE_FILE,
id
};
}

View file

@ -1,71 +1,84 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import InlineSVG from 'react-inlinesvg'; import InlineSVG from 'react-inlinesvg';
import SidebarItem from './SidebarItem';
const rightArrowUrl = require('../../../images/right-arrow.svg'); const rightArrowUrl = require('../../../images/right-arrow.svg');
const leftArrowUrl = require('../../../images/left-arrow.svg'); const leftArrowUrl = require('../../../images/left-arrow.svg');
function Sidebar(props) { class Sidebar extends React.Component {
const sidebarClass = classNames({ constructor(props) {
sidebar: true, super(props);
'sidebar--contracted': !props.isExpanded this.resetSelectedFile = this.resetSelectedFile.bind(this);
}); }
return ( resetSelectedFile() {
<nav className={sidebarClass} title="file-navigation" role="navigation"> debugger; // eslint-disable-line
<div className="sidebar__header"> this.props.setSelectedFile(this.props.files[0].id);
<h3 className="sidebar__title">Sketch Files</h3> }
<div className="sidebar__icons">
<button render() {
aria-label="add file" const sidebarClass = classNames({
className="sidebar__add" sidebar: true,
onClick={props.newFile} 'sidebar--contracted': !this.props.isExpanded
> });
+
</button> return (
<button <nav className={sidebarClass} title="file-navigation" role="navigation">
aria-label="collapse file navigation" <div className="sidebar__header">
className="sidebar__contract" <h3 className="sidebar__title">Sketch Files</h3>
onClick={props.collapseSidebar} <div className="sidebar__icons">
> <button
<InlineSVG src={leftArrowUrl} /> aria-label="add file"
</button> className="sidebar__add"
<button onClick={props.newFile}
aria-label="expand file navigation"
className="sidebar__expand"
onClick={props.expandSidebar}
>
<InlineSVG src={rightArrowUrl} />
</button>
</div>
</div>
<ul className="sidebar__file-list">
{props.files.map(file => {
let itemClass = classNames({
'sidebar__file-item': true,
'sidebar__file-item--selected': file.id === props.selectedFile.id
});
return (
<li
className={itemClass}
key={file.id}
> >
<a +
onClick={() => props.setSelectedFile(file.id)} </button>
>{file.name}</a> <button
</li> aria-label="collapse file navigation"
); className="sidebar__contract"
})} onClick={props.collapseSidebar}
</ul> >
</nav> <InlineSVG src={leftArrowUrl} />
); </button>
<button
aria-label="expand file navigation"
className="sidebar__expand"
onClick={props.expandSidebar}
>
<InlineSVG src={rightArrowUrl} />
</button>
</div>
</div>
<ul className="sidebar__file-list">
{this.props.files.map((file, fileIndex) =>
<SidebarItem
key={file.id}
file={file}
setSelectedFile={this.props.setSelectedFile}
fileIndex={fileIndex}
showFileOptions={this.props.showFileOptions}
hideFileOptions={this.props.hideFileOptions}
deleteFile={this.props.deleteFile}
resetSelectedFile={this.resetSelectedFile}
/>
)}
</ul>
</nav>
);
}
} }
Sidebar.propTypes = { Sidebar.propTypes = {
files: PropTypes.array.isRequired, files: PropTypes.array.isRequired,
selectedFile: PropTypes.shape({ setSelectedFile: PropTypes.func.isRequired,
id: PropTypes.string.isRequired isExpanded: PropTypes.bool.isRequired,
}), newFile: PropTypes.func.isRequired,
setSelectedFile: PropTypes.func.isRequired collapseSidebar: PropTypes.func.isRequired,
expandSidebar: PropTypes.func.isRequired,
showFileOptions: PropTypes.func.isRequired,
hideFileOptions: PropTypes.func.isRequired,
deleteFile: PropTypes.func.isRequired
}; };
export default Sidebar; export default Sidebar;

View file

@ -0,0 +1,73 @@
import React, { PropTypes } from 'react';
import InlineSVG from 'react-inlinesvg';
import classNames from 'classnames';
const downArrowUrl = require('../../../images/down-arrow.svg');
class SidebarItem extends React.Component {
onFocus() {
}
render() {
let itemClass = classNames({
'sidebar__file-item': true,
'sidebar__file-item--selected': this.props.file.isSelected,
'sidebar__file-item--open': this.props.file.isOptionsOpen
});
return (
<li
className={itemClass}
onBlur={() => this.props.hideFileOptions(this.props.file.id)}
tabIndex={this.props.fileIndex}
>
<a
onClick={() => this.props.setSelectedFile(this.props.file.id)}
>{this.props.file.name}</a>
<a
className="sidebar__file-item-show-options"
onClick={() => this.props.showFileOptions(this.props.file.id)}
>
<InlineSVG src={downArrowUrl} />
</a>
<div ref="fileOptions" className="sidebar__file-item-options">
<ul>
<li>
<a>
Rename
</a>
</li>
<li>
<a
onClick={() => {
if (window.confirm(`Are you sure you want to delete ${this.props.file.name}?`)) {
this.props.deleteFile(this.props.file.id);
this.props.resetSelectedFile();
}
}}
>
Delete
</a>
</li>
</ul>
</div>
</li>
);
}
}
SidebarItem.propTypes = {
file: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
isSelected: PropTypes.bool,
isOptionsOpen: PropTypes.bool
}).isRequired,
setSelectedFile: PropTypes.func.isRequired,
fileIndex: PropTypes.number.isRequired,
showFileOptions: PropTypes.func.isRequired,
hideFileOptions: PropTypes.func.isRequired,
deleteFile: PropTypes.func.isRequired,
resetSelectedFile: PropTypes.func.isRequired
};
export default SidebarItem;

View file

@ -12,7 +12,7 @@ import { connect } from 'react-redux';
import * as FileActions from '../actions/files'; import * as FileActions from '../actions/files';
import * as IDEActions from '../actions/ide'; import * as IDEActions from '../actions/ide';
import * as ProjectActions from '../actions/project'; import * as ProjectActions from '../actions/project';
import { getFile, getHTMLFile, getJSFiles, getCSSFiles } from '../reducers/files'; import { getFile, getHTMLFile, getJSFiles, getCSSFiles, setSelectedFile } from '../reducers/files';
class IDEView extends React.Component { class IDEView extends React.Component {
componentDidMount() { componentDidMount() {
@ -50,12 +50,14 @@ class IDEView extends React.Component {
<div className="editor-preview-container"> <div className="editor-preview-container">
<Sidebar <Sidebar
files={this.props.files} files={this.props.files}
selectedFile={this.props.selectedFile}
setSelectedFile={this.props.setSelectedFile} setSelectedFile={this.props.setSelectedFile}
newFile={this.props.newFile} newFile={this.props.newFile}
isExpanded={this.props.ide.sidebarIsExpanded} isExpanded={this.props.ide.sidebarIsExpanded}
expandSidebar={this.props.expandSidebar} expandSidebar={this.props.expandSidebar}
collapseSidebar={this.props.collapseSidebar} collapseSidebar={this.props.collapseSidebar}
showFileOptions={this.props.showFileOptions}
hideFileOptions={this.props.hideFileOptions}
deleteFile={this.props.deleteFile}
/> />
<div className="editor-console-container"> <div className="editor-console-container">
<Editor <Editor
@ -157,11 +159,14 @@ IDEView.propTypes = {
cloneProject: PropTypes.func.isRequired, cloneProject: PropTypes.func.isRequired,
expandConsole: PropTypes.func.isRequired, expandConsole: PropTypes.func.isRequired,
collapseConsole: PropTypes.func.isRequired, collapseConsole: PropTypes.func.isRequired,
showFileOptions: PropTypes.func.isRequired,
hideFileOptions: PropTypes.func.isRequired,
deleteFile: PropTypes.func.isRequired
}; };
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
files: state.files, files: setSelectedFile(state.files, state.ide.selectedFile),
selectedFile: getFile(state.files, state.ide.selectedFile), selectedFile: getFile(state.files, state.ide.selectedFile),
htmlFile: getHTMLFile(state.files), htmlFile: getHTMLFile(state.files),
jsFiles: getJSFiles(state.files), jsFiles: getJSFiles(state.files),

View file

@ -73,11 +73,45 @@ const files = (state = initialState, action) => {
return [...action.files]; return [...action.files];
case ActionTypes.CREATE_FILE: case ActionTypes.CREATE_FILE:
return [...state, { name: action.name, id: action.id, content: '', url: action.url }]; return [...state, { name: action.name, id: action.id, content: '', url: action.url }];
case ActionTypes.SHOW_FILE_OPTIONS:
return state.map(file => {
if (file.id !== action.id) {
return file;
}
return Object.assign({}, file, { isOptionsOpen: true });
});
case ActionTypes.HIDE_FILE_OPTIONS:
return state.map(file => {
if (file.id !== action.id) {
return file;
}
return Object.assign({}, file, { isOptionsOpen: false });
});
case ActionTypes.UPDATE_FILE_NAME:
return state.map(file => {
if (file.id !== action.id) {
return file;
}
return Object.assign({}, file, { name: action.name });
});
case ActionTypes.DELETE_FILE:
return state.filter(file => file.id !== action.id);
default: default:
return state; return state;
} }
}; };
export const setSelectedFile = (state, id) =>
state.map(file => {
if (file.id === id) {
return Object.assign({}, file, { isSelected: true });
}
return file;
});
export const getFile = (state, id) => state.filter(file => file.id === id)[0]; export const getFile = (state, id) => state.filter(file => file.id === id)[0];
export const getHTMLFile = (state) => state.filter(file => file.name.match(/.*\.html$/))[0]; export const getHTMLFile = (state) => state.filter(file => file.name.match(/.*\.html$/))[0];
export const getJSFiles = (state) => state.filter(file => file.name.match(/.*\.js$/)); export const getJSFiles = (state) => state.filter(file => file.name.match(/.*\.js$/));

View file

@ -38,11 +38,37 @@
padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem; padding: #{8 / $base-font-size}rem #{20 / $base-font-size}rem;
color: $light-inactive-text-color; color: $light-inactive-text-color;
cursor: pointer; cursor: pointer;
display: flex;
justify-content: space-between;
position: relative;
&--selected { &--selected {
background-color: $ide-border-color; background-color: $ide-border-color;
} }
} }
.sidebar__file-item-show-options {
@extend %icon;
display: none;
.sidebar__file-item--selected & {
display: inline-block;
}
}
.sidebar__file-item-options {
@extend %modal;
position: absolute;
top: 95%;
left: 77%;
display: none;
z-index: 100;
padding: #{8 / $base-font-size}rem #{16 / $base-font-size}rem;
background-color: $light-modal-background-color;
z-index: 100;
.sidebar__file-item--open & {
display: block;
}
}
.sidebar__contract { .sidebar__contract {
@extend %icon; @extend %icon;
height: #{14 / $base-font-size}rem; height: #{14 / $base-font-size}rem;