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 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
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 classNames from 'classnames';
import InlineSVG from 'react-inlinesvg';
import SidebarItem from './SidebarItem';
const rightArrowUrl = require('../../../images/right-arrow.svg');
const leftArrowUrl = require('../../../images/left-arrow.svg');
function Sidebar(props) {
const sidebarClass = classNames({
sidebar: true,
'sidebar--contracted': !props.isExpanded
});
class Sidebar extends React.Component {
constructor(props) {
super(props);
this.resetSelectedFile = this.resetSelectedFile.bind(this);
}
return (
<nav className={sidebarClass} title="file-navigation" role="navigation">
<div className="sidebar__header">
<h3 className="sidebar__title">Sketch Files</h3>
<div className="sidebar__icons">
<button
aria-label="add file"
className="sidebar__add"
onClick={props.newFile}
>
+
</button>
<button
aria-label="collapse file navigation"
className="sidebar__contract"
onClick={props.collapseSidebar}
>
<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">
{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}
resetSelectedFile() {
debugger; // eslint-disable-line
this.props.setSelectedFile(this.props.files[0].id);
}
render() {
const sidebarClass = classNames({
sidebar: true,
'sidebar--contracted': !this.props.isExpanded
});
return (
<nav className={sidebarClass} title="file-navigation" role="navigation">
<div className="sidebar__header">
<h3 className="sidebar__title">Sketch Files</h3>
<div className="sidebar__icons">
<button
aria-label="add file"
className="sidebar__add"
onClick={props.newFile}
>
<a
onClick={() => props.setSelectedFile(file.id)}
>{file.name}</a>
</li>
);
})}
</ul>
</nav>
);
+
</button>
<button
aria-label="collapse file navigation"
className="sidebar__contract"
onClick={props.collapseSidebar}
>
<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 = {
files: PropTypes.array.isRequired,
selectedFile: PropTypes.shape({
id: PropTypes.string.isRequired
}),
setSelectedFile: PropTypes.func.isRequired
setSelectedFile: PropTypes.func.isRequired,
isExpanded: PropTypes.bool.isRequired,
newFile: 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;

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

View File

@ -73,11 +73,45 @@ const files = (state = initialState, action) => {
return [...action.files];
case ActionTypes.CREATE_FILE:
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:
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 getHTMLFile = (state) => state.filter(file => file.name.match(/.*\.html$/))[0];
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;
color: $light-inactive-text-color;
cursor: pointer;
display: flex;
justify-content: space-between;
position: relative;
&--selected {
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 {
@extend %icon;
height: #{14 / $base-font-size}rem;