diff --git a/client/constants.js b/client/constants.js
index 4c5d1bf1..aaf7637c 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -20,6 +20,7 @@ export const AUTH_ERROR = 'AUTH_ERROR';
export const SETTINGS_UPDATED = 'SETTINGS_UPDATED';
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_FAIL = 'PROJECT_SAVE_FAIL';
diff --git a/client/modules/App/components/Overlay.jsx b/client/modules/App/components/Overlay.jsx
index 486225f4..3a70242d 100644
--- a/client/modules/App/components/Overlay.jsx
+++ b/client/modules/App/components/Overlay.jsx
@@ -33,7 +33,7 @@ class Overlay extends React.Component {
return;
}
- this.handleClickOutside();
+ this.handleClickOutside(e);
}
handleClickOutside() {
@@ -49,6 +49,10 @@ class Overlay extends React.Component {
}
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) {
browserHistory.push(this.props.previousPath);
} else {
diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js
index 64226b73..1d7c2998 100644
--- a/client/modules/IDE/actions/ide.js
+++ b/client/modules/IDE/actions/ide.js
@@ -134,9 +134,17 @@ export function closeNewFolderModal() {
};
}
-export function showShareModal() {
- return {
- type: ActionTypes.SHOW_SHARE_MODAL
+export function showShareModal(projectId, projectName, ownerUsername) {
+ return (dispatch, getState) => {
+ const { project, user } = getState();
+ dispatch({
+ type: ActionTypes.SHOW_SHARE_MODAL,
+ payload: {
+ shareModalProjectId: projectId || project.id,
+ shareModalProjectName: projectName || project.name,
+ shareModalProjectUsername: ownerUsername || user.username
+ }
+ });
};
}
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index 0fa2d344..e4abb9d5 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -9,7 +9,8 @@ import {
setUnsavedChanges,
justOpenedProject,
resetJustOpenedProject,
- showErrorModal
+ showErrorModal,
+ setPreviousPath
} from './ide';
import { clearState, saveState } from '../../../persistState';
@@ -246,47 +247,61 @@ function generateNewIdsForChildren(file, files) {
file.children = newChildren; // eslint-disable-line
}
-export function cloneProject() {
+export function cloneProject(id) {
return (dispatch, getState) => {
dispatch(setUnsavedChanges(false));
- const state = getState();
- const newFiles = state.files.map((file) => { // eslint-disable-line
- return { ...file };
- });
-
- // 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);
- });
+ new Promise((resolve, reject) => {
+ if (!id) {
+ resolve(getState());
} else {
- callback(null);
+ fetch(`${ROOT_URL}/projects/${id}`)
+ .then(res => res.json())
+ .then(data => resolve({
+ files: data.files,
+ project: {
+ name: data.name
+ }
+ }));
}
- }, (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
- }));
+ }).then((state) => {
+ const newFiles = state.files.map((file) => { // eslint-disable-line
+ return { ...file };
+ });
+
+ // 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 {
+ 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
};
}
+
+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
+ });
+ }
+ });
+ };
+}
diff --git a/client/modules/IDE/actions/projects.js b/client/modules/IDE/actions/projects.js
index 77dfbe52..446c50cc 100644
--- a/client/modules/IDE/actions/projects.js
+++ b/client/modules/IDE/actions/projects.js
@@ -1,12 +1,11 @@
import axios from 'axios';
import * as ActionTypes from '../../../constants';
-import { showErrorModal, setPreviousPath } from './ide';
-import { resetProject } from './project';
import { startLoader, stopLoader } from './loader';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
+// eslint-disable-next-line
export function getProjects(username) {
return (dispatch) => {
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
- });
- }
- });
- };
-}
diff --git a/client/modules/IDE/components/AssetList.jsx b/client/modules/IDE/components/AssetList.jsx
index 42513fba..ab548322 100644
--- a/client/modules/IDE/components/AssetList.jsx
+++ b/client/modules/IDE/components/AssetList.jsx
@@ -17,13 +17,14 @@ class AssetList extends React.Component {
}
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 | ${this.props.username}'s assets`;
}
render() {
+ const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
return (
@@ -49,7 +50,7 @@ class AssetList extends React.Component {
{asset.name} |
{prettyBytes(asset.size)} |
View |
- {asset.sketchName} |
+ {asset.sketchName} |
))}
diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx
index f51a815a..bcfdf15f 100644
--- a/client/modules/IDE/components/SketchList.jsx
+++ b/client/modules/IDE/components/SketchList.jsx
@@ -4,19 +4,255 @@ import React from 'react';
import { Helmet } from 'react-helmet';
import InlineSVG from 'react-inlinesvg';
import { connect } from 'react-redux';
-import { browserHistory, Link } from 'react-router';
+import { Link } from 'react-router';
import { bindActionCreators } from 'redux';
import classNames from 'classnames';
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 SortingActions from '../actions/sorting';
+import * as IdeActions from '../actions/ide';
import getSortedSketches from '../selectors/projects';
import Loader from '../../App/components/loader';
-const trashCan = require('../../../images/trash-can.svg');
const arrowUp = require('../../../images/sort-arrow-up.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 (
+
+
+
+ {renameOpen ? '' : sketch.name}
+
+ {renameOpen
+ &&
+ e.stopPropagation()}
+ />
+ }
+ |
+ {format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')} |
+ {format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')} |
+
+
+ {optionsOpen &&
+
+ {userIsOwner &&
+ -
+
+
}
+ -
+
+
+ {this.props.user.authenticated &&
+ -
+
+
}
+ { /* -
+
+
*/ }
+ {userIsOwner &&
+ -
+
+
}
+ }
+ |
+
);
+ }
+}
+
+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 {
constructor(props) {
@@ -83,43 +319,20 @@ class SketchList extends React.Component {
- |
{this._renderFieldHeader('name', 'Sketch')}
{this._renderFieldHeader('createdAt', 'Date Created')}
{this._renderFieldHeader('updatedAt', 'Date Updated')}
+ |
{this.props.sketches.map(sketch =>
- // eslint-disable-next-line
- browserHistory.push(`/${username}/sketches/${sketch.id}`)}
- >
-
- {(() => { // eslint-disable-line
- if (this.props.username === this.props.user.username || this.props.username === undefined) {
- return (
-
- );
- }
- })()}
- |
- {sketch.name} |
- {format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')} |
- {format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')} |
-
)}
+ sketch={sketch}
+ user={this.props.user}
+ username={username}
+ />))}
}
@@ -129,7 +342,8 @@ class SketchList extends React.Component {
SketchList.propTypes = {
user: PropTypes.shape({
- username: PropTypes.string
+ username: PropTypes.string,
+ authenticated: PropTypes.bool.isRequired
}).isRequired,
getProjects: PropTypes.func.isRequired,
sketches: PropTypes.arrayOf(PropTypes.shape({
@@ -140,16 +354,25 @@ SketchList.propTypes = {
})).isRequired,
username: PropTypes.string,
loading: PropTypes.bool.isRequired,
- deleteProject: PropTypes.func.isRequired,
toggleDirectionForField: PropTypes.func.isRequired,
resetSorting: PropTypes.func.isRequired,
sorting: PropTypes.shape({
field: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired
}).isRequired,
+ project: PropTypes.shape({
+ id: PropTypes.string,
+ owner: PropTypes.shape({
+ id: PropTypes.string
+ })
+ })
};
SketchList.defaultProps = {
+ project: {
+ id: undefined,
+ owner: undefined
+ },
username: undefined
};
@@ -158,12 +381,13 @@ function mapStateToProps(state) {
user: state.user,
sketches: getSortedSketches(state),
sorting: state.sorting,
- loading: state.loading
+ loading: state.loading,
+ project: state.project
};
}
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);
diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx
index ed19bb8b..b2ae1ebb 100644
--- a/client/modules/IDE/pages/IDEView.jsx
+++ b/client/modules/IDE/pages/IDEView.jsx
@@ -414,9 +414,9 @@ class IDEView extends React.Component {
closeOverlay={this.props.closeShareModal}
>
}
@@ -481,6 +481,9 @@ IDEView.propTypes = {
projectOptionsVisible: PropTypes.bool.isRequired,
newFolderModalVisible: PropTypes.bool.isRequired,
shareModalVisible: PropTypes.bool.isRequired,
+ shareModalProjectId: PropTypes.string.isRequired,
+ shareModalProjectName: PropTypes.string.isRequired,
+ shareModalProjectUsername: PropTypes.string.isRequired,
editorOptionsVisible: PropTypes.bool.isRequired,
keyboardShortcutVisible: PropTypes.bool.isRequired,
unsavedChanges: PropTypes.bool.isRequired,
diff --git a/client/modules/IDE/reducers/ide.js b/client/modules/IDE/reducers/ide.js
index 379237bc..f9559234 100644
--- a/client/modules/IDE/reducers/ide.js
+++ b/client/modules/IDE/reducers/ide.js
@@ -10,6 +10,9 @@ const initialState = {
projectOptionsVisible: false,
newFolderModalVisible: false,
shareModalVisible: false,
+ shareModalProjectId: null,
+ shareModalProjectName: null,
+ shareModalProjectUsername: null,
editorOptionsVisible: false,
keyboardShortcutVisible: false,
unsavedChanges: false,
@@ -61,7 +64,12 @@ const ide = (state = initialState, action) => {
case ActionTypes.CLOSE_NEW_FOLDER_MODAL:
return Object.assign({}, state, { newFolderModalVisible: false });
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:
return Object.assign({}, state, { shareModalVisible: false });
case ActionTypes.SHOW_EDITOR_OPTIONS:
diff --git a/client/modules/IDE/reducers/projects.js b/client/modules/IDE/reducers/projects.js
index ba3bb4c9..ff06c5c7 100644
--- a/client/modules/IDE/reducers/projects.js
+++ b/client/modules/IDE/reducers/projects.js
@@ -7,6 +7,14 @@ const sketches = (state = [], action) => {
case ActionTypes.DELETE_PROJECT:
return state.filter(sketch =>
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:
return state;
}
diff --git a/client/styles/abstracts/_placeholders.scss b/client/styles/abstracts/_placeholders.scss
index d529ab55..d252a08c 100644
--- a/client/styles/abstracts/_placeholders.scss
+++ b/client/styles/abstracts/_placeholders.scss
@@ -214,6 +214,9 @@
height: auto;
z-index: 9999;
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 {
border-radius: 0 0 #{5 / $base-font-size}rem #{5 / $base-font-size}rem;
}
@@ -248,17 +251,9 @@
%dropdown-open-left {
@extend %dropdown-open;
left: 0;
- border-top-left-radius: 0px;
- & li:first-child {
- border-radius: 0 #{5 / $base-font-size}rem 0 0;
- }
}
%dropdown-open-right {
@extend %dropdown-open;
right: 0;
- border-top-right-radius: 0px;
- & li:first-child {
- border-radius: #{5 / $base-font-size}rem 0 0 0;
- }
}
diff --git a/client/styles/components/_sidebar.scss b/client/styles/components/_sidebar.scss
index 9c852e9d..345d0b40 100644
--- a/client/styles/components/_sidebar.scss
+++ b/client/styles/components/_sidebar.scss
@@ -25,6 +25,8 @@
}
.sidebar__add {
+ width: #{20 / $base-font-size}rem;
+ height: #{20 / $base-font-size}rem;
@include icon();
.sidebar--contracted & {
display: none;
@@ -121,10 +123,11 @@
}
.sidebar__file-item-show-options {
+ width: #{20 / $base-font-size}rem;
+ height: #{20 / $base-font-size}rem;
@include icon();
@include themify() {
- padding: #{4 / $base-font-size}rem 0;
- padding-right: #{6 / $base-font-size}rem;
+ margin-right: #{5 / $base-font-size}rem;
}
display: none;
position: absolute;
diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss
index 496a2b1c..f49bf828 100644
--- a/client/styles/components/_sketch-list.scss
+++ b/client/styles/components/_sketch-list.scss
@@ -10,8 +10,9 @@
padding: #{10 / $base-font-size}rem #{20 / $base-font-size}rem;
max-height: 100%;
border-spacing: 0;
- & .sketch-list__trash-column {
- width: #{23 / $base-font-size}rem;
+ & .sketch-list__dropdown-column {
+ 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 {
margin: #{10 / $base-font-size}rem;
height: #{72 / $base-font-size}rem;
font-size: #{16 / $base-font-size}rem;
- cursor: pointer;
}
.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 {
@include themify() {
color: getThemifyVariable('primary-text-color');
@@ -75,28 +83,26 @@
font-weight: normal;
}
-.visibility-toggle .sketch-list__trash-button {
- @extend %hidden-element;
- width:#{20 / $base-font-size}rem;
- height:#{20 / $base-font-size}rem;
-}
-
-.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__dropdown-button {
+ width:#{25 / $base-font-size}rem;
+ height:#{25 / $base-font-size}rem;
+ @include themify() {
+ & polygon {
+ fill: getThemifyVariable('dropdown-color');
}
}
}
+.sketch-list__action-dialogue {
+ @extend %dropdown-open-right;
+ top: 63%;
+ right: calc(100% - 26px);
+}
+
+.sketch-list__action-option {
+
+}
+
.sketches-table__empty {
text-align: center;
font-size: #{16 / $base-font-size}rem;
diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js
index 527ac64a..ae029918 100644
--- a/server/controllers/project.controller.js
+++ b/server/controllers/project.controller.js
@@ -40,7 +40,7 @@ export function updateProject(req, res) {
res.json({ success: false });
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 newFileIds = req.body.files.map(file => file.id);
const staleIds = oldFileIds.filter(id => newFileIds.indexOf(id) === -1);