Merge branch 'develop' of https://github.com/processing/p5.js-web-editor into feature/mobile-examples
This commit is contained in:
commit
8ddd7a76e2
12 changed files with 140 additions and 33 deletions
|
@ -160,7 +160,8 @@ export function deleteFile(id, parentId) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
apiClient.delete(`/projects/${state.project.id}/files/${id}`, deleteConfig)
|
apiClient.delete(`/projects/${state.project.id}/files/${id}`, deleteConfig)
|
||||||
.then(() => {
|
.then((response) => {
|
||||||
|
dispatch(setProjectSavedTime(response.data.project.updatedAt));
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionTypes.DELETE_FILE,
|
type: ActionTypes.DELETE_FILE,
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -10,6 +10,61 @@ import FolderRightIcon from '../../../images/triangle-arrow-right.svg';
|
||||||
import FolderDownIcon from '../../../images/triangle-arrow-down.svg';
|
import FolderDownIcon from '../../../images/triangle-arrow-down.svg';
|
||||||
import FileIcon from '../../../images/file.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 (
|
||||||
|
<span className="sidebar__file-item-name-text">
|
||||||
|
<span>{firstLetter}</span>
|
||||||
|
{baseName.length > 2 &&
|
||||||
|
<span className="sidebar__file-item-name--ellipsis">{middleText}</span>
|
||||||
|
}
|
||||||
|
{baseName.length > 1 &&
|
||||||
|
<span>{lastLetter}</span>
|
||||||
|
}
|
||||||
|
{extension &&
|
||||||
|
<span>{extension}</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileName.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
export class FileNode extends React.Component {
|
export class FileNode extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -206,11 +261,12 @@ export class FileNode extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<button
|
<button
|
||||||
aria-label="Name"
|
aria-label={this.state.updatedName}
|
||||||
className="sidebar__file-item-name"
|
className="sidebar__file-item-name"
|
||||||
onClick={this.handleFileClick}
|
onClick={this.handleFileClick}
|
||||||
|
data-testid="file-name"
|
||||||
>
|
>
|
||||||
{this.state.updatedName}
|
<FileName name={this.state.updatedName} />
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe('<FileNode />', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectFileNameToBe = async (expectedName) => {
|
const expectFileNameToBe = async (expectedName) => {
|
||||||
const name = screen.getByLabelText(/Name/i);
|
const name = screen.getByTestId('file-name');
|
||||||
await waitFor(() => within(name).queryByText(expectedName));
|
await waitFor(() => within(name).queryByText(expectedName));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -301,7 +301,7 @@ class IDEView extends React.Component {
|
||||||
onChange={size => this.setState({ sidebarSize: size })}
|
onChange={size => this.setState({ sidebarSize: size })}
|
||||||
onDragFinished={this._handleSidebarPaneOnDragFinished}
|
onDragFinished={this._handleSidebarPaneOnDragFinished}
|
||||||
allowResize={this.props.ide.sidebarIsExpanded}
|
allowResize={this.props.ide.sidebarIsExpanded}
|
||||||
minSize={20}
|
minSize={125}
|
||||||
>
|
>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
files={this.props.files}
|
files={this.props.files}
|
||||||
|
|
|
@ -45,7 +45,7 @@ const initialState = () => {
|
||||||
name: 'root',
|
name: 'root',
|
||||||
id: r,
|
id: r,
|
||||||
_id: r,
|
_id: r,
|
||||||
children: [a, b, c],
|
children: [b, a, c],
|
||||||
fileType: 'folder',
|
fileType: 'folder',
|
||||||
content: ''
|
content: ''
|
||||||
},
|
},
|
||||||
|
@ -110,6 +110,32 @@ function deleteMany(state, ids) {
|
||||||
return newState;
|
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) => {
|
const files = (state, action) => {
|
||||||
if (state === undefined) {
|
if (state === undefined) {
|
||||||
state = initialState(); // eslint-disable-line
|
state = initialState(); // eslint-disable-line
|
||||||
|
@ -138,15 +164,8 @@ const files = (state, action) => {
|
||||||
return initialState();
|
return initialState();
|
||||||
case ActionTypes.CREATE_FILE: // eslint-disable-line
|
case ActionTypes.CREATE_FILE: // eslint-disable-line
|
||||||
{
|
{
|
||||||
const newState = state.map((file) => {
|
const newState = [
|
||||||
if (file.id === action.parentId) {
|
...updateParent(state, action),
|
||||||
const newFile = Object.assign({}, file);
|
|
||||||
newFile.children = [...newFile.children, action.id];
|
|
||||||
return newFile;
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
});
|
|
||||||
return [...newState,
|
|
||||||
{
|
{
|
||||||
name: action.name,
|
name: action.name,
|
||||||
id: action.id,
|
id: action.id,
|
||||||
|
@ -156,15 +175,23 @@ const files = (state, action) => {
|
||||||
children: action.children,
|
children: action.children,
|
||||||
fileType: action.fileType || 'file'
|
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:
|
case ActionTypes.UPDATE_FILE_NAME:
|
||||||
return state.map((file) => {
|
{
|
||||||
if (file.id !== action.id) {
|
const newState = renameFile(state, action);
|
||||||
return file;
|
return newState.map((file) => {
|
||||||
|
if (file.children.includes(action.id)) {
|
||||||
|
file.children = sortedChildrenId(newState, file.children);
|
||||||
}
|
}
|
||||||
|
return file;
|
||||||
return Object.assign({}, file, { name: action.name });
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
case ActionTypes.DELETE_FILE:
|
case ActionTypes.DELETE_FILE:
|
||||||
{
|
{
|
||||||
const newState = deleteMany(state, [action.id, ...getAllDescendantIds(state, action.id)]);
|
const newState = deleteMany(state, [action.id, ...getAllDescendantIds(state, action.id)]);
|
||||||
|
@ -200,7 +227,10 @@ const files = (state, action) => {
|
||||||
return file;
|
return file;
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return state;
|
return state.map((file) => {
|
||||||
|
file.children = sortedChildrenId(state, file.children);
|
||||||
|
return file;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
.sidebar__file-item-name {
|
||||||
padding: #{4 / $base-font-size}rem 0;
|
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 & {
|
.sidebar__file-item--editing & {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -174,6 +194,8 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
width: calc(100% - #{63 / $base-font-size}rem);
|
width: calc(100% - #{63 / $base-font-size}rem);
|
||||||
|
font-family: Inconsolata, monospace;
|
||||||
|
font-size: #{14 / $base-font-size}rem;
|
||||||
.sidebar__file-item--editing & {
|
.sidebar__file-item--editing & {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
@ -254,9 +276,6 @@
|
||||||
fill: getThemifyVariable('secondary-text-color');
|
fill: getThemifyVariable('secondary-text-color');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& svg {
|
|
||||||
height: #{10 / $base-font-size}rem;
|
|
||||||
}
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "p5.js-web-editor",
|
"name": "p5.js-web-editor",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "p5.js-web-editor",
|
"name": "p5.js-web-editor",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"description": "The web editor for p5.js.",
|
"description": "The web editor for p5.js.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
|
|
|
@ -103,8 +103,8 @@ export function deleteFile(req, res) {
|
||||||
const idsToDelete = getAllDescendantIds(project.files, req.params.file_id);
|
const idsToDelete = getAllDescendantIds(project.files, req.params.file_id);
|
||||||
deleteMany(project.files, [req.params.file_id, ...idsToDelete]);
|
deleteMany(project.files, [req.params.file_id, ...idsToDelete]);
|
||||||
project.files = deleteChild(project.files, req.query.parentId, req.params.file_id);
|
project.files = deleteChild(project.files, req.query.parentId, req.params.file_id);
|
||||||
project.save((innerErr) => {
|
project.save((innerErr, savedProject) => {
|
||||||
res.json(project.files);
|
res.json({ project: savedProject });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,14 +30,15 @@ export function updateProject(req, res) {
|
||||||
$set: req.body
|
$set: req.body
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true,
|
||||||
|
runValidators: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.populate('user', 'username')
|
.populate('user', 'username')
|
||||||
.exec((updateProjectErr, updatedProject) => {
|
.exec((updateProjectErr, updatedProject) => {
|
||||||
if (updateProjectErr) {
|
if (updateProjectErr) {
|
||||||
console.log(updateProjectErr);
|
console.log(updateProjectErr);
|
||||||
res.json({ success: false });
|
res.status(400).json({ success: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (req.body.files && updatedProject.files.length !== req.body.files.length) {
|
if (req.body.files && updatedProject.files.length !== req.body.files.length) {
|
||||||
|
@ -50,7 +51,7 @@ export function updateProject(req, res) {
|
||||||
updatedProject.save((innerErr, savedProject) => {
|
updatedProject.save((innerErr, savedProject) => {
|
||||||
if (innerErr) {
|
if (innerErr) {
|
||||||
console.log(innerErr);
|
console.log(innerErr);
|
||||||
res.json({ success: false });
|
res.status(400).json({ success: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.json(savedProject);
|
res.json(savedProject);
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default function createProject(req, res) {
|
||||||
projectValues = Object.assign(projectValues, req.body);
|
projectValues = Object.assign(projectValues, req.body);
|
||||||
|
|
||||||
function sendFailure() {
|
function sendFailure() {
|
||||||
res.json({ success: false });
|
res.status(400).json({ success: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateUserData(newProject) {
|
function populateUserData(newProject) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ fileSchema.set('toJSON', {
|
||||||
|
|
||||||
const projectSchema = new Schema(
|
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' },
|
user: { type: Schema.Types.ObjectId, ref: 'User' },
|
||||||
serveSecure: { type: Boolean, default: false },
|
serveSecure: { type: Boolean, default: false },
|
||||||
files: { type: [fileSchema] },
|
files: { type: [fileSchema] },
|
||||||
|
|
Loading…
Reference in a new issue