diff --git a/client/modules/IDE/actions/files.js b/client/modules/IDE/actions/files.js index e17e46c5..c8a656c8 100644 --- a/client/modules/IDE/actions/files.js +++ b/client/modules/IDE/actions/files.js @@ -160,7 +160,8 @@ export function deleteFile(id, parentId) { } }; apiClient.delete(`/projects/${state.project.id}/files/${id}`, deleteConfig) - .then(() => { + .then((response) => { + dispatch(setProjectSavedTime(response.data.project.updatedAt)); dispatch({ type: ActionTypes.DELETE_FILE, id, diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx index 57238cf8..e7869f26 100644 --- a/client/modules/IDE/components/FileNode.jsx +++ b/client/modules/IDE/components/FileNode.jsx @@ -10,6 +10,61 @@ import FolderRightIcon from '../../../images/triangle-arrow-right.svg'; import FolderDownIcon from '../../../images/triangle-arrow-down.svg'; import FileIcon from '../../../images/file.svg'; +function parseFileName(name) { + const nameArray = name.split('.'); + if (nameArray.length > 1) { + const extension = `.${nameArray[nameArray.length - 1]}`; + const baseName = nameArray.slice(0, -1).join(''); + const firstLetter = baseName[0]; + const lastLetter = baseName[baseName.length - 1]; + const middleText = baseName.slice(1, -1); + return { + baseName, + firstLetter, + lastLetter, + middleText, + extension + }; + } + const firstLetter = name[0]; + const lastLetter = name[name.length - 1]; + const middleText = name.slice(1, -1); + return { + baseName: name, + firstLetter, + lastLetter, + middleText + }; +} + +function FileName({ name }) { + const { + baseName, + firstLetter, + lastLetter, + middleText, + extension + } = parseFileName(name); + return ( + + {firstLetter} + {baseName.length > 2 && + {middleText} + } + {baseName.length > 1 && + {lastLetter} + } + {extension && + {extension} + } + + ); +} + +FileName.propTypes = { + name: PropTypes.string.isRequired +}; + export class FileNode extends React.Component { constructor(props) { super(props); @@ -206,11 +261,12 @@ export class FileNode extends React.Component { } ', () => { }; const expectFileNameToBe = async (expectedName) => { - const name = screen.getByLabelText(/Name/i); + const name = screen.getByTestId('file-name'); await waitFor(() => within(name).queryByText(expectedName)); }; diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index 956fd974..4979eaee 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -301,7 +301,7 @@ class IDEView extends React.Component { onChange={size => this.setState({ sidebarSize: size })} onDragFinished={this._handleSidebarPaneOnDragFinished} allowResize={this.props.ide.sidebarIsExpanded} - minSize={20} + minSize={125} > { name: 'root', id: r, _id: r, - children: [a, b, c], + children: [b, a, c], fileType: 'folder', content: '' }, @@ -110,6 +110,32 @@ function deleteMany(state, ids) { return newState; } +function sortedChildrenId(state, children) { + const childrenArray = state.filter(file => children.includes(file.id)); + childrenArray.sort((a, b) => (a.name > b.name ? 1 : -1)); + return childrenArray.map(child => child.id); +} + +function updateParent(state, action) { + return state.map((file) => { + if (file.id === action.parentId) { + const newFile = Object.assign({}, file); + newFile.children = [...newFile.children, action.id]; + return newFile; + } + return file; + }); +} + +function renameFile(state, action) { + return state.map((file) => { + if (file.id !== action.id) { + return file; + } + return Object.assign({}, file, { name: action.name }); + }); +} + const files = (state, action) => { if (state === undefined) { state = initialState(); // eslint-disable-line @@ -138,15 +164,8 @@ const files = (state, action) => { return initialState(); case ActionTypes.CREATE_FILE: // eslint-disable-line { - const newState = state.map((file) => { - if (file.id === action.parentId) { - const newFile = Object.assign({}, file); - newFile.children = [...newFile.children, action.id]; - return newFile; - } - return file; - }); - return [...newState, + const newState = [ + ...updateParent(state, action), { name: action.name, id: action.id, @@ -156,15 +175,23 @@ const files = (state, action) => { children: action.children, fileType: action.fileType || 'file' }]; + return newState.map((file) => { + if (file.id === action.parentId) { + file.children = sortedChildrenId(newState, file.children); + } + return file; + }); } case ActionTypes.UPDATE_FILE_NAME: - return state.map((file) => { - if (file.id !== action.id) { - return file; + { + const newState = renameFile(state, action); + return newState.map((file) => { + if (file.children.includes(action.id)) { + file.children = sortedChildrenId(newState, file.children); } - - return Object.assign({}, file, { name: action.name }); + return file; }); + } case ActionTypes.DELETE_FILE: { const newState = deleteMany(state, [action.id, ...getAllDescendantIds(state, action.id)]); @@ -200,7 +227,10 @@ const files = (state, action) => { return file; }); default: - return state; + return state.map((file) => { + file.children = sortedChildrenId(state, file.children); + return file; + }); } }; diff --git a/client/styles/components/_sidebar.scss b/client/styles/components/_sidebar.scss index ffafc439..761bda91 100644 --- a/client/styles/components/_sidebar.scss +++ b/client/styles/components/_sidebar.scss @@ -110,8 +110,28 @@ } } +.sidebar__file-item-name--ellipsis { + overflow-x: hidden; + white-space: nowrap; + text-overflow: ellipsis; + min-width: #{15 / $base-font-size}rem; +} + +.sidebar__file-item-name-text { + display: flex; + width: 100%; + overflow: hidden; + min-width: #{50 / $base-font-size}rem; + white-space: nowrap; + text-align: left; +} + .sidebar__file-item-name { padding: #{4 / $base-font-size}rem 0; + padding-right: #{25 / $base-font-size}rem; + font-family: Inconsolata, monospace; + font-size: #{14 / $base-font-size}rem; + overflow: hidden; .sidebar__file-item--editing & { display: none; } @@ -174,6 +194,8 @@ padding: 0; border: 0; width: calc(100% - #{63 / $base-font-size}rem); + font-family: Inconsolata, monospace; + font-size: #{14 / $base-font-size}rem; .sidebar__file-item--editing & { display: inline-block; } @@ -254,9 +276,6 @@ fill: getThemifyVariable('secondary-text-color'); } } - & svg { - height: #{10 / $base-font-size}rem; - } background-color: transparent; border: none; } diff --git a/package-lock.json b/package-lock.json index 04592b99..8abec584 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.6", + "version": "1.0.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ba2680a8..01f558f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.6", + "version": "1.0.7", "description": "The web editor for p5.js.", "scripts": { "clean": "rimraf dist", diff --git a/server/controllers/file.controller.js b/server/controllers/file.controller.js index 4ccb6db2..542a40fd 100644 --- a/server/controllers/file.controller.js +++ b/server/controllers/file.controller.js @@ -103,8 +103,8 @@ export function deleteFile(req, res) { const idsToDelete = getAllDescendantIds(project.files, req.params.file_id); deleteMany(project.files, [req.params.file_id, ...idsToDelete]); project.files = deleteChild(project.files, req.query.parentId, req.params.file_id); - project.save((innerErr) => { - res.json(project.files); + project.save((innerErr, savedProject) => { + res.json({ project: savedProject }); }); }); } diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js index 7f118c23..2a83aa6a 100644 --- a/server/controllers/project.controller.js +++ b/server/controllers/project.controller.js @@ -30,14 +30,15 @@ export function updateProject(req, res) { $set: req.body }, { - new: true + new: true, + runValidators: true } ) .populate('user', 'username') .exec((updateProjectErr, updatedProject) => { if (updateProjectErr) { console.log(updateProjectErr); - res.json({ success: false }); + res.status(400).json({ success: false }); return; } if (req.body.files && updatedProject.files.length !== req.body.files.length) { @@ -50,7 +51,7 @@ export function updateProject(req, res) { updatedProject.save((innerErr, savedProject) => { if (innerErr) { console.log(innerErr); - res.json({ success: false }); + res.status(400).json({ success: false }); return; } res.json(savedProject); diff --git a/server/controllers/project.controller/createProject.js b/server/controllers/project.controller/createProject.js index e4040268..74529c0d 100644 --- a/server/controllers/project.controller/createProject.js +++ b/server/controllers/project.controller/createProject.js @@ -9,7 +9,7 @@ export default function createProject(req, res) { projectValues = Object.assign(projectValues, req.body); function sendFailure() { - res.json({ success: false }); + res.status(400).json({ success: false }); } function populateUserData(newProject) { diff --git a/server/models/project.js b/server/models/project.js index bf8c992e..3581e998 100644 --- a/server/models/project.js +++ b/server/models/project.js @@ -29,7 +29,7 @@ fileSchema.set('toJSON', { const projectSchema = new Schema( { - name: { type: String, default: "Hello p5.js, it's the server" }, + name: { type: String, default: "Hello p5.js, it's the server", maxlength: 128 }, user: { type: Schema.Types.ObjectId, ref: 'User' }, serveSecure: { type: Boolean, default: false }, files: { type: [fileSchema] },