From c3856480b8166255cf1c510c3c0a2fcb6329442b Mon Sep 17 00:00:00 2001 From: Laksh Singla <30999375+LakshSingla@users.noreply.github.com> Date: Thu, 20 Jun 2019 01:51:25 +0530 Subject: [PATCH] Update sketch list styling (#819) * parent b3c3efcec96b5e5bb4e00be742e8f17a025db409 author Laksh Singla 1549106083 +0530 committer Cassie Tarakajian 1560540243 -0400 parent b3c3efcec96b5e5bb4e00be742e8f17a025db409 author Laksh Singla 1549106083 +0530 committer Cassie Tarakajian 1560540198 -0400 parent b3c3efcec96b5e5bb4e00be742e8f17a025db409 author Laksh Singla 1549106083 +0530 committer Cassie Tarakajian 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 --- client/constants.js | 1 + client/modules/App/components/Overlay.jsx | 6 +- client/modules/IDE/actions/ide.js | 14 +- client/modules/IDE/actions/project.js | 146 ++++++--- client/modules/IDE/actions/projects.js | 30 +- client/modules/IDE/components/AssetList.jsx | 5 +- client/modules/IDE/components/SketchList.jsx | 296 ++++++++++++++++--- client/modules/IDE/pages/IDEView.jsx | 9 +- client/modules/IDE/reducers/ide.js | 10 +- client/modules/IDE/reducers/projects.js | 8 + client/styles/abstracts/_placeholders.scss | 11 +- client/styles/components/_sidebar.scss | 7 +- client/styles/components/_sketch-list.scss | 48 +-- server/controllers/project.controller.js | 2 +- 14 files changed, 448 insertions(+), 145 deletions(-) diff --git a/client/constants.js b/client/constants.js index c4dc80ae..4dd57213 100644 --- a/client/constants.js +++ b/client/constants.js @@ -23,6 +23,7 @@ export const API_KEY_CREATED = 'API_KEY_CREATED'; export const API_KEY_REMOVED = 'API_KEY_REMOVED'; 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}`)} - > - - - - - )} + sketch={sketch} + user={this.props.user} + username={username} + />))}
- {(() => { // 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')}
}
@@ -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 d6bc73f4..23b03f0e 100644 --- a/client/styles/abstracts/_placeholders.scss +++ b/client/styles/abstracts/_placeholders.scss @@ -193,6 +193,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; } @@ -227,17 +230,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 328b07b2..dca1d759 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);