Update sketch list styling (#819)

* parent b3c3efcec9
author Laksh Singla <lakshsingla@gmail.com> 1549106083 +0530
committer Cassie Tarakajian <ctarakajian@gmail.com> 1560540243 -0400

parent b3c3efcec9
author Laksh Singla <lakshsingla@gmail.com> 1549106083 +0530
committer Cassie Tarakajian <ctarakajian@gmail.com> 1560540198 -0400

parent b3c3efcec9
author Laksh Singla <lakshsingla@gmail.com> 1549106083 +0530
committer Cassie Tarakajian <ctarakajian@gmail.com> 1560539667 -0400

Created initial html structure and styling for new SketchList design

Final styling of ActionDialogueBox commplete

Dropdown menu disappearing while clicking anywhere on the table

Fixed linting issues and renamed variables

Minor tweaks in the SketchList dropdown dialogue UI

Themifyed the dropdown

Made changes in the dropdown: Arrow positioned slightly updwards, Removed blank space and added box-shadow in dropdown, themifyed dropdowns dashed border color

Added Delete and Share functionality to Dialog box

Added Duplicate functionality to Dialog box

Added download functionality to Dialog box

SketchList does not open a sketch if dialogue box is opened

SketchList Rename initial UI completed

Enter key handled for rename project option

[WIP] Updating rename functionality

Download option now working for all the sketches

Duplicate functionality extended for non opened sketches too

Modified overlay behaviour to close only the last overlay

Share modal can now display different projects

Dropdown closes when Share and Delete are closing for a more natural UX

fix broken files from rebasing

Created initial html structure and styling for new SketchList design

Final styling of ActionDialogueBox commplete

Added Delete and Share functionality to Dialog box

Added Duplicate functionality to Dialog box

[WIP] Updating rename functionality

Duplicate functionality extended for non opened sketches too

Modified overlay behaviour to close only the last overlay

Share modal can now display different projects

Final styling of ActionDialogueBox commplete

Fixed linting issues and renamed variables

Minor tweaks in the SketchList dropdown dialogue UI

Themifyed the dropdown

Added Delete and Share functionality to Dialog box

[WIP] Updating rename functionality

Modified overlay behaviour to close only the last overlay

Share modal can now display different projects

Dropdown closes when Share and Delete are closing for a more natural UX

fix broken files from rebasing

Final styling of ActionDialogueBox commplete

Minor tweaks in the SketchList dropdown dialogue UI

Themifyed the dropdown

[WIP] Updating rename functionality

Duplicate functionality extended for non opened sketches too

Modified overlay behaviour to close only the last overlay

Share modal can now display different projects

Dropdown closes when Share and Delete are closing for a more natural UX

* fix bugs in merge commit

* move sketch list dialogue to ul/li

* update sketch option dropdown to use dropdown placeholder, remove unused css

* major refactor of sketchlist component, fix showShareModal action, minor updates ot icon sizing

* fix broken links on asset list

* remove unused image, fix options for different users in sketch list
This commit is contained in:
Laksh Singla 2019-06-20 01:51:25 +05:30 committed by Cassie Tarakajian
parent 4bd081b307
commit 735adcfa05
14 changed files with 448 additions and 145 deletions

View file

@ -20,6 +20,7 @@ export const AUTH_ERROR = 'AUTH_ERROR';
export const SETTINGS_UPDATED = 'SETTINGS_UPDATED'; export const SETTINGS_UPDATED = 'SETTINGS_UPDATED';
export const SET_PROJECT_NAME = 'SET_PROJECT_NAME'; export const SET_PROJECT_NAME = 'SET_PROJECT_NAME';
export const RENAME_PROJECT = 'RENAME_PROJECT';
export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS'; export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS';
export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL'; export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL';

View file

@ -33,7 +33,7 @@ class Overlay extends React.Component {
return; return;
} }
this.handleClickOutside(); this.handleClickOutside(e);
} }
handleClickOutside() { handleClickOutside() {
@ -49,6 +49,10 @@ class Overlay extends React.Component {
} }
close() { close() {
// Only close if it is the last (and therefore the topmost overlay)
const overlays = document.getElementsByClassName('overlay');
if (this.node.parentElement.parentElement !== overlays[overlays.length - 1]) return;
if (!this.props.closeOverlay) { if (!this.props.closeOverlay) {
browserHistory.push(this.props.previousPath); browserHistory.push(this.props.previousPath);
} else { } else {

View file

@ -134,9 +134,17 @@ export function closeNewFolderModal() {
}; };
} }
export function showShareModal() { export function showShareModal(projectId, projectName, ownerUsername) {
return { return (dispatch, getState) => {
type: ActionTypes.SHOW_SHARE_MODAL const { project, user } = getState();
dispatch({
type: ActionTypes.SHOW_SHARE_MODAL,
payload: {
shareModalProjectId: projectId || project.id,
shareModalProjectName: projectName || project.name,
shareModalProjectUsername: ownerUsername || user.username
}
});
}; };
} }

View file

@ -9,7 +9,8 @@ import {
setUnsavedChanges, setUnsavedChanges,
justOpenedProject, justOpenedProject,
resetJustOpenedProject, resetJustOpenedProject,
showErrorModal showErrorModal,
setPreviousPath
} from './ide'; } from './ide';
import { clearState, saveState } from '../../../persistState'; import { clearState, saveState } from '../../../persistState';
@ -246,47 +247,61 @@ function generateNewIdsForChildren(file, files) {
file.children = newChildren; // eslint-disable-line file.children = newChildren; // eslint-disable-line
} }
export function cloneProject() { export function cloneProject(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(setUnsavedChanges(false)); dispatch(setUnsavedChanges(false));
const state = getState(); new Promise((resolve, reject) => {
const newFiles = state.files.map((file) => { // eslint-disable-line if (!id) {
return { ...file }; resolve(getState());
});
// generate new IDS for all files
const rootFile = newFiles.find(file => file.name === 'root');
const newRootFileId = objectID().toHexString();
rootFile.id = newRootFileId;
rootFile._id = newRootFileId;
generateNewIdsForChildren(rootFile, newFiles);
// duplicate all files hosted on S3
each(newFiles, (file, callback) => {
if (file.url && file.url.includes('amazonaws')) {
const formParams = {
url: file.url
};
axios.post(`${ROOT_URL}/S3/copy`, formParams, { withCredentials: true })
.then((response) => {
file.url = response.data.url;
callback(null);
});
} else { } else {
callback(null); fetch(`${ROOT_URL}/projects/${id}`)
.then(res => res.json())
.then(data => resolve({
files: data.files,
project: {
name: data.name
}
}));
} }
}, (err) => { }).then((state) => {
// if not errors in duplicating the files on S3, then duplicate it const newFiles = state.files.map((file) => { // eslint-disable-line
const formParams = Object.assign({}, { name: `${state.project.name} copy` }, { files: newFiles }); return { ...file };
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true }) });
.then((response) => {
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`); // generate new IDS for all files
dispatch(setNewProject(response.data)); const rootFile = newFiles.find(file => file.name === 'root');
}) const newRootFileId = objectID().toHexString();
.catch(response => dispatch({ rootFile.id = newRootFileId;
type: ActionTypes.PROJECT_SAVE_FAIL, rootFile._id = newRootFileId;
error: response.data generateNewIdsForChildren(rootFile, newFiles);
}));
// duplicate all files hosted on S3
each(newFiles, (file, callback) => {
if (file.url && file.url.includes('amazonaws')) {
const formParams = {
url: file.url
};
axios.post(`${ROOT_URL}/S3/copy`, formParams, { withCredentials: true })
.then((response) => {
file.url = response.data.url;
callback(null);
});
} else {
callback(null);
}
}, (err) => {
// if not errors in duplicating the files on S3, then duplicate it
const formParams = Object.assign({}, { name: `${state.project.name} copy` }, { files: newFiles });
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
.then((response) => {
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
dispatch(setNewProject(response.data));
})
.catch(response => dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL,
error: response.data
}));
});
}); });
}; };
} }
@ -309,3 +324,58 @@ export function setProjectSavedTime(updatedAt) {
value: updatedAt value: updatedAt
}; };
} }
export function changeProjectName(id, newName) {
return (dispatch, getState) => {
const state = getState();
axios.put(`${ROOT_URL}/projects/${id}`, { name: newName }, { withCredentials: true })
.then((response) => {
if (response.status === 200) {
dispatch({
type: ActionTypes.RENAME_PROJECT,
payload: { id: response.data.id, name: response.data.name }
});
if (state.project.id === response.data.id) {
dispatch({
type: ActionTypes.SET_PROJECT_NAME,
name: response.data.name
});
}
}
})
.catch((response) => {
console.log(response);
dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL,
error: response.data
});
});
};
}
export function deleteProject(id) {
return (dispatch, getState) => {
axios.delete(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
.then(() => {
const state = getState();
if (id === state.project.id) {
dispatch(resetProject());
dispatch(setPreviousPath('/'));
}
dispatch({
type: ActionTypes.DELETE_PROJECT,
id
});
})
.catch((response) => {
if (response.status === 403) {
dispatch(showErrorModal('staleSession'));
} else {
dispatch({
type: ActionTypes.ERROR,
error: response.data
});
}
});
};
}

View file

@ -1,12 +1,11 @@
import axios from 'axios'; import axios from 'axios';
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { showErrorModal, setPreviousPath } from './ide';
import { resetProject } from './project';
import { startLoader, stopLoader } from './loader'; import { startLoader, stopLoader } from './loader';
const __process = (typeof global !== 'undefined' ? global : window).process; const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL; const ROOT_URL = __process.env.API_URL;
// eslint-disable-next-line
export function getProjects(username) { export function getProjects(username) {
return (dispatch) => { return (dispatch) => {
dispatch(startLoader()); dispatch(startLoader());
@ -33,30 +32,3 @@ export function getProjects(username) {
}); });
}; };
} }
export function deleteProject(id) {
return (dispatch, getState) => {
axios.delete(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
.then(() => {
const state = getState();
if (id === state.project.id) {
dispatch(resetProject());
dispatch(setPreviousPath('/'));
}
dispatch({
type: ActionTypes.DELETE_PROJECT,
id
});
})
.catch((response) => {
if (response.status === 403) {
dispatch(showErrorModal('staleSession'));
} else {
dispatch({
type: ActionTypes.ERROR,
error: response.data
});
}
});
};
}

View file

@ -17,13 +17,14 @@ class AssetList extends React.Component {
} }
getAssetsTitle() { getAssetsTitle() {
if (this.props.username === this.props.user.username) { if (!this.props.username || this.props.username === this.props.user.username) {
return 'p5.js Web Editor | My assets'; return 'p5.js Web Editor | My assets';
} }
return `p5.js Web Editor | ${this.props.username}'s assets`; return `p5.js Web Editor | ${this.props.username}'s assets`;
} }
render() { render() {
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
return ( return (
<div className="asset-table-container"> <div className="asset-table-container">
<Helmet> <Helmet>
@ -49,7 +50,7 @@ class AssetList extends React.Component {
<td>{asset.name}</td> <td>{asset.name}</td>
<td>{prettyBytes(asset.size)}</td> <td>{prettyBytes(asset.size)}</td>
<td><Link to={asset.url} target="_blank">View</Link></td> <td><Link to={asset.url} target="_blank">View</Link></td>
<td><Link to={`/${this.props.username}/sketches/${asset.sketchId}`}>{asset.sketchName}</Link></td> <td><Link to={`/${username}/sketches/${asset.sketchId}`}>{asset.sketchName}</Link></td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View file

@ -4,19 +4,255 @@ import React from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import InlineSVG from 'react-inlinesvg'; import InlineSVG from 'react-inlinesvg';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { browserHistory, Link } from 'react-router'; import { Link } from 'react-router';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import classNames from 'classnames'; import classNames from 'classnames';
import * as ProjectActions from '../actions/project'; import * as ProjectActions from '../actions/project';
import * as SketchActions from '../actions/projects'; import * as ProjectsActions from '../actions/projects';
import * as ToastActions from '../actions/toast'; import * as ToastActions from '../actions/toast';
import * as SortingActions from '../actions/sorting'; import * as SortingActions from '../actions/sorting';
import * as IdeActions from '../actions/ide';
import getSortedSketches from '../selectors/projects'; import getSortedSketches from '../selectors/projects';
import Loader from '../../App/components/loader'; import Loader from '../../App/components/loader';
const trashCan = require('../../../images/trash-can.svg');
const arrowUp = require('../../../images/sort-arrow-up.svg'); const arrowUp = require('../../../images/sort-arrow-up.svg');
const arrowDown = require('../../../images/sort-arrow-down.svg'); const arrowDown = require('../../../images/sort-arrow-down.svg');
const downFilledTriangle = require('../../../images/down-filled-triangle.svg');
class SketchListRowBase extends React.Component {
constructor(props) {
super(props);
this.state = {
optionsOpen: false,
renameOpen: false,
renameValue: props.sketch.name,
isFocused: false
};
}
onFocusComponent = () => {
this.setState({ isFocused: true });
}
onBlurComponent = () => {
this.setState({ isFocused: false });
setTimeout(() => {
if (!this.state.isFocused) {
this.closeAll();
}
}, 200);
}
openOptions = () => {
this.setState({
optionsOpen: true
});
}
closeOptions = () => {
this.setState({
optionsOpen: false
});
}
toggleOptions = () => {
if (this.state.optionsOpen) {
this.closeOptions();
} else {
this.openOptions();
}
}
openRename = () => {
this.setState({
renameOpen: true
});
}
closeRename = () => {
this.setState({
renameOpen: false
});
}
closeAll = () => {
this.setState({
renameOpen: false,
optionsOpen: false
});
}
handleRenameChange = (e) => {
this.setState({
renameValue: e.target.value
});
}
handleRenameEnter = (e) => {
if (e.key === 'Enter') {
// TODO pass this func
this.props.changeProjectName(this.props.sketch.id, this.state.renameValue);
this.closeAll();
}
}
resetSketchName = () => {
this.setState({
renameValue: this.props.sketch.name
});
}
handleDropdownOpen = () => {
this.closeAll();
this.openOptions();
}
handleRenameOpen = () => {
this.closeAll();
this.openRename();
}
handleSketchDownload = () => {
this.props.exportProjectAsZip(this.props.sketch.id);
}
handleSketchDuplicate = () => {
this.closeAll();
this.props.cloneProject(this.props.sketch.id);
}
handleSketchShare = () => {
this.closeAll();
this.props.showShareModal(this.props.sketch.id, this.props.sketch.name, this.props.username);
}
handleSketchDelete = () => {
this.closeAll();
if (window.confirm(`Are you sure you want to delete "${this.props.sketch.name}"?`)) {
this.props.deleteProject(this.props.sketch.id);
}
}
render() {
const { sketch, username } = this.props;
const { renameOpen, optionsOpen, renameValue } = this.state;
const userIsOwner = this.props.user.username === this.props.username;
return (
<tr
className="sketches-table__row"
key={sketch.id}
>
<th scope="row">
<Link to={`/${username}/sketches/${sketch.id}`}>
{renameOpen ? '' : sketch.name}
</Link>
{renameOpen
&&
<input
value={renameValue}
onChange={this.handleRenameChange}
onKeyUp={this.handleRenameEnter}
onBlur={this.resetSketchName}
onClick={e => e.stopPropagation()}
/>
}
</th>
<td>{format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')}</td>
<td>{format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')}</td>
<td className="sketch-list__dropdown-column">
<button
className="sketch-list__dropdown-button"
onClick={this.toggleOptions}
onBlur={this.onBlurComponent}
onFocus={this.onFocusComponent}
>
<InlineSVG src={downFilledTriangle} alt="Menu" />
</button>
{optionsOpen &&
<ul
className="sketch-list__action-dialogue"
>
{userIsOwner &&
<li>
<button
className="sketch-list__action-option"
onClick={this.handleRenameOpen}
onBlur={this.onBlurComponent}
onFocus={this.onFocusComponent}
>
Rename
</button>
</li>}
<li>
<button
className="sketch-list__action-option"
onClick={this.handleSketchDownload}
onBlur={this.onBlurComponent}
onFocus={this.onFocusComponent}
>
Download
</button>
</li>
{this.props.user.authenticated &&
<li>
<button
className="sketch-list__action-option"
onClick={this.handleSketchDuplicate}
onBlur={this.onBlurComponent}
onFocus={this.onFocusComponent}
>
Duplicate
</button>
</li>}
{ /* <li>
<button
className="sketch-list__action-option"
onClick={this.handleSketchShare}
onBlur={this.onBlurComponent}
onFocus={this.onFocusComponent}
>
Share
</button>
</li> */ }
{userIsOwner &&
<li>
<button
className="sketch-list__action-option"
onClick={this.handleSketchDelete}
onBlur={this.onBlurComponent}
onFocus={this.onFocusComponent}
>
Delete
</button>
</li>}
</ul>}
</td>
</tr>);
}
}
SketchListRowBase.propTypes = {
sketch: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired,
username: PropTypes.string.isRequired,
user: PropTypes.shape({
username: PropTypes.string,
authenticated: PropTypes.bool.isRequired
}).isRequired,
deleteProject: PropTypes.func.isRequired,
showShareModal: PropTypes.func.isRequired,
cloneProject: PropTypes.func.isRequired,
exportProjectAsZip: PropTypes.func.isRequired,
changeProjectName: PropTypes.func.isRequired
};
function mapDispatchToPropsSketchListRow(dispatch) {
return bindActionCreators(Object.assign({}, ProjectActions, IdeActions), dispatch);
}
const SketchListRow = connect(null, mapDispatchToPropsSketchListRow)(SketchListRowBase);
class SketchList extends React.Component { class SketchList extends React.Component {
constructor(props) { constructor(props) {
@ -83,43 +319,20 @@ class SketchList extends React.Component {
<table className="sketches-table" summary="table containing all saved projects"> <table className="sketches-table" summary="table containing all saved projects">
<thead> <thead>
<tr> <tr>
<th className="sketch-list__trash-column" scope="col"></th>
{this._renderFieldHeader('name', 'Sketch')} {this._renderFieldHeader('name', 'Sketch')}
{this._renderFieldHeader('createdAt', 'Date Created')} {this._renderFieldHeader('createdAt', 'Date Created')}
{this._renderFieldHeader('updatedAt', 'Date Updated')} {this._renderFieldHeader('updatedAt', 'Date Updated')}
<th scope="col"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{this.props.sketches.map(sketch => {this.props.sketches.map(sketch =>
// eslint-disable-next-line (<SketchListRow
<tr
className="sketches-table__row visibility-toggle"
key={sketch.id} key={sketch.id}
onClick={() => browserHistory.push(`/${username}/sketches/${sketch.id}`)} sketch={sketch}
> user={this.props.user}
<td className="sketch-list__trash-column"> username={username}
{(() => { // eslint-disable-line />))}
if (this.props.username === this.props.user.username || this.props.username === undefined) {
return (
<button
className="sketch-list__trash-button"
onClick={(e) => {
e.stopPropagation();
if (window.confirm(`Are you sure you want to delete "${sketch.name}"?`)) {
this.props.deleteProject(sketch.id);
}
}}
>
<InlineSVG src={trashCan} alt="Delete Project" />
</button>
);
}
})()}
</td>
<th scope="row"><Link to={`/${username}/sketches/${sketch.id}`}>{sketch.name}</Link></th>
<td>{format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')}</td>
<td>{format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')}</td>
</tr>)}
</tbody> </tbody>
</table>} </table>}
</div> </div>
@ -129,7 +342,8 @@ class SketchList extends React.Component {
SketchList.propTypes = { SketchList.propTypes = {
user: PropTypes.shape({ user: PropTypes.shape({
username: PropTypes.string username: PropTypes.string,
authenticated: PropTypes.bool.isRequired
}).isRequired, }).isRequired,
getProjects: PropTypes.func.isRequired, getProjects: PropTypes.func.isRequired,
sketches: PropTypes.arrayOf(PropTypes.shape({ sketches: PropTypes.arrayOf(PropTypes.shape({
@ -140,16 +354,25 @@ SketchList.propTypes = {
})).isRequired, })).isRequired,
username: PropTypes.string, username: PropTypes.string,
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
deleteProject: PropTypes.func.isRequired,
toggleDirectionForField: PropTypes.func.isRequired, toggleDirectionForField: PropTypes.func.isRequired,
resetSorting: PropTypes.func.isRequired, resetSorting: PropTypes.func.isRequired,
sorting: PropTypes.shape({ sorting: PropTypes.shape({
field: PropTypes.string.isRequired, field: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired direction: PropTypes.string.isRequired
}).isRequired, }).isRequired,
project: PropTypes.shape({
id: PropTypes.string,
owner: PropTypes.shape({
id: PropTypes.string
})
})
}; };
SketchList.defaultProps = { SketchList.defaultProps = {
project: {
id: undefined,
owner: undefined
},
username: undefined username: undefined
}; };
@ -158,12 +381,13 @@ function mapStateToProps(state) {
user: state.user, user: state.user,
sketches: getSortedSketches(state), sketches: getSortedSketches(state),
sorting: state.sorting, sorting: state.sorting,
loading: state.loading loading: state.loading,
project: state.project
}; };
} }
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions, ToastActions, SortingActions), dispatch); return bindActionCreators(Object.assign({}, ProjectsActions, ToastActions, SortingActions), dispatch);
} }
export default connect(mapStateToProps, mapDispatchToProps)(SketchList); export default connect(mapStateToProps, mapDispatchToProps)(SketchList);

View file

@ -414,9 +414,9 @@ class IDEView extends React.Component {
closeOverlay={this.props.closeShareModal} closeOverlay={this.props.closeShareModal}
> >
<ShareModal <ShareModal
projectId={this.props.project.id} projectId={this.props.ide.shareModalProjectId}
projectName={this.props.project.name} projectName={this.props.ide.shareModalProjectName}
ownerUsername={this.props.project.owner.username} ownerUsername={this.props.ide.shareModalProjectUsername}
/> />
</Overlay> </Overlay>
} }
@ -481,6 +481,9 @@ IDEView.propTypes = {
projectOptionsVisible: PropTypes.bool.isRequired, projectOptionsVisible: PropTypes.bool.isRequired,
newFolderModalVisible: PropTypes.bool.isRequired, newFolderModalVisible: PropTypes.bool.isRequired,
shareModalVisible: PropTypes.bool.isRequired, shareModalVisible: PropTypes.bool.isRequired,
shareModalProjectId: PropTypes.string.isRequired,
shareModalProjectName: PropTypes.string.isRequired,
shareModalProjectUsername: PropTypes.string.isRequired,
editorOptionsVisible: PropTypes.bool.isRequired, editorOptionsVisible: PropTypes.bool.isRequired,
keyboardShortcutVisible: PropTypes.bool.isRequired, keyboardShortcutVisible: PropTypes.bool.isRequired,
unsavedChanges: PropTypes.bool.isRequired, unsavedChanges: PropTypes.bool.isRequired,

View file

@ -10,6 +10,9 @@ const initialState = {
projectOptionsVisible: false, projectOptionsVisible: false,
newFolderModalVisible: false, newFolderModalVisible: false,
shareModalVisible: false, shareModalVisible: false,
shareModalProjectId: null,
shareModalProjectName: null,
shareModalProjectUsername: null,
editorOptionsVisible: false, editorOptionsVisible: false,
keyboardShortcutVisible: false, keyboardShortcutVisible: false,
unsavedChanges: false, unsavedChanges: false,
@ -61,7 +64,12 @@ const ide = (state = initialState, action) => {
case ActionTypes.CLOSE_NEW_FOLDER_MODAL: case ActionTypes.CLOSE_NEW_FOLDER_MODAL:
return Object.assign({}, state, { newFolderModalVisible: false }); return Object.assign({}, state, { newFolderModalVisible: false });
case ActionTypes.SHOW_SHARE_MODAL: case ActionTypes.SHOW_SHARE_MODAL:
return Object.assign({}, state, { shareModalVisible: true }); return Object.assign({}, state, {
shareModalVisible: true,
shareModalProjectId: action.payload.shareModalProjectId,
shareModalProjectName: action.payload.shareModalProjectName,
shareModalProjectUsername: action.payload.shareModalProjectUsername,
});
case ActionTypes.CLOSE_SHARE_MODAL: case ActionTypes.CLOSE_SHARE_MODAL:
return Object.assign({}, state, { shareModalVisible: false }); return Object.assign({}, state, { shareModalVisible: false });
case ActionTypes.SHOW_EDITOR_OPTIONS: case ActionTypes.SHOW_EDITOR_OPTIONS:

View file

@ -7,6 +7,14 @@ const sketches = (state = [], action) => {
case ActionTypes.DELETE_PROJECT: case ActionTypes.DELETE_PROJECT:
return state.filter(sketch => return state.filter(sketch =>
sketch.id !== action.id); sketch.id !== action.id);
case ActionTypes.RENAME_PROJECT: {
return state.map((sketch) => {
if (sketch.id === action.payload.id) {
return { ...sketch, name: action.payload.name };
}
return { ...sketch };
});
}
default: default:
return state; return state;
} }

View file

@ -214,6 +214,9 @@
height: auto; height: auto;
z-index: 9999; z-index: 9999;
border-radius: #{6 / $base-font-size}rem; border-radius: #{6 / $base-font-size}rem;
& li:first-child {
border-radius: #{5 / $base-font-size}rem #{5 / $base-font-size}rem 0 0;
}
& li:last-child { & li:last-child {
border-radius: 0 0 #{5 / $base-font-size}rem #{5 / $base-font-size}rem; border-radius: 0 0 #{5 / $base-font-size}rem #{5 / $base-font-size}rem;
} }
@ -248,17 +251,9 @@
%dropdown-open-left { %dropdown-open-left {
@extend %dropdown-open; @extend %dropdown-open;
left: 0; left: 0;
border-top-left-radius: 0px;
& li:first-child {
border-radius: 0 #{5 / $base-font-size}rem 0 0;
}
} }
%dropdown-open-right { %dropdown-open-right {
@extend %dropdown-open; @extend %dropdown-open;
right: 0; right: 0;
border-top-right-radius: 0px;
& li:first-child {
border-radius: #{5 / $base-font-size}rem 0 0 0;
}
} }

View file

@ -25,6 +25,8 @@
} }
.sidebar__add { .sidebar__add {
width: #{20 / $base-font-size}rem;
height: #{20 / $base-font-size}rem;
@include icon(); @include icon();
.sidebar--contracted & { .sidebar--contracted & {
display: none; display: none;
@ -121,10 +123,11 @@
} }
.sidebar__file-item-show-options { .sidebar__file-item-show-options {
width: #{20 / $base-font-size}rem;
height: #{20 / $base-font-size}rem;
@include icon(); @include icon();
@include themify() { @include themify() {
padding: #{4 / $base-font-size}rem 0; margin-right: #{5 / $base-font-size}rem;
padding-right: #{6 / $base-font-size}rem;
} }
display: none; display: none;
position: absolute; position: absolute;

View file

@ -10,8 +10,9 @@
padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem; padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem;
max-height: 100%; max-height: 100%;
border-spacing: 0; border-spacing: 0;
& .sketch-list__trash-column { & .sketch-list__dropdown-column {
width: #{23 / $base-font-size}rem; width: #{60 / $base-font-size}rem;
position: relative;
} }
} }
@ -44,11 +45,14 @@
} }
} }
.sketches-table thead th:nth-child(1){
padding-left: #{12 / $base-font-size}rem;
}
.sketches-table__row { .sketches-table__row {
margin: #{10 / $base-font-size}rem; margin: #{10 / $base-font-size}rem;
height: #{72 / $base-font-size}rem; height: #{72 / $base-font-size}rem;
font-size: #{16 / $base-font-size}rem; font-size: #{16 / $base-font-size}rem;
cursor: pointer;
} }
.sketches-table__row:nth-child(odd) { .sketches-table__row:nth-child(odd) {
@ -57,6 +61,10 @@
} }
} }
.sketches-table__row > th:nth-child(1) {
padding-left: #{12 / $base-font-size}rem;
}
.sketches-table__row a { .sketches-table__row a {
@include themify() { @include themify() {
color: getThemifyVariable('primary-text-color'); color: getThemifyVariable('primary-text-color');
@ -75,28 +83,26 @@
font-weight: normal; font-weight: normal;
} }
.visibility-toggle .sketch-list__trash-button { .sketch-list__dropdown-button {
@extend %hidden-element; width:#{25 / $base-font-size}rem;
width:#{20 / $base-font-size}rem; height:#{25 / $base-font-size}rem;
height:#{20 / $base-font-size}rem; @include themify() {
} & polygon {
fill: getThemifyVariable('dropdown-color');
.visibility-toggle:hover .sketch-list__trash-button {
@include themify() {
background-color: transparent;
border: none;
cursor: pointer;
padding: 0;
position: initial;
left: 0;
top: 0;
& g {
opacity: 1;
fill: getThemifyVariable('icon-hover-color');
} }
} }
} }
.sketch-list__action-dialogue {
@extend %dropdown-open-right;
top: 63%;
right: calc(100% - 26px);
}
.sketch-list__action-option {
}
.sketches-table__empty { .sketches-table__empty {
text-align: center; text-align: center;
font-size: #{16 / $base-font-size}rem; font-size: #{16 / $base-font-size}rem;

View file

@ -40,7 +40,7 @@ export function updateProject(req, res) {
res.json({ success: false }); res.json({ success: false });
return; return;
} }
if (updatedProject.files.length !== req.body.files.length) { if (req.body.files && updatedProject.files.length !== req.body.files.length) {
const oldFileIds = updatedProject.files.map(file => file.id); const oldFileIds = updatedProject.files.map(file => file.id);
const newFileIds = req.body.files.map(file => file.id); const newFileIds = req.body.files.map(file => file.id);
const staleIds = oldFileIds.filter(id => newFileIds.indexOf(id) === -1); const staleIds = oldFileIds.filter(id => newFileIds.indexOf(id) === -1);