p5.js-web-editor/client/modules/IDE/actions/project.js
Laksh Singla 735adcfa05 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
2019-06-19 16:21:25 -04:00

381 lines
11 KiB
JavaScript

import { browserHistory } from 'react-router';
import axios from 'axios';
import objectID from 'bson-objectid';
import each from 'async/each';
import isEqual from 'lodash/isEqual';
import * as ActionTypes from '../../../constants';
import { showToast, setToastText } from './toast';
import {
setUnsavedChanges,
justOpenedProject,
resetJustOpenedProject,
showErrorModal,
setPreviousPath
} from './ide';
import { clearState, saveState } from '../../../persistState';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
export function setProject(project) {
return {
type: ActionTypes.SET_PROJECT,
project,
files: project.files,
owner: project.user
};
}
export function setProjectName(name) {
return {
type: ActionTypes.SET_PROJECT_NAME,
name
};
}
export function projectSaveFail(error) {
return {
type: ActionTypes.PROJECT_SAVE_FAIL,
error
};
}
export function setNewProject(project) {
return {
type: ActionTypes.NEW_PROJECT,
project,
owner: project.user,
files: project.files
};
}
export function getProject(id) {
return (dispatch, getState) => {
dispatch(justOpenedProject());
axios.get(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
.then((response) => {
dispatch(setProject(response.data));
dispatch(setUnsavedChanges(false));
})
.catch(response => dispatch({
type: ActionTypes.ERROR,
error: response.data
}));
};
}
export function persistState() {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.PERSIST_STATE,
});
const state = getState();
saveState(state);
};
}
export function clearPersistedState() {
return (dispatch) => {
dispatch({
type: ActionTypes.CLEAR_PERSISTED_STATE,
});
clearState();
};
}
export function startSavingProject() {
return {
type: ActionTypes.START_SAVING_PROJECT
};
}
export function endSavingProject() {
return {
type: ActionTypes.END_SAVING_PROJECT
};
}
export function projectSaveSuccess() {
return {
type: ActionTypes.PROJECT_SAVE_SUCCESS
};
}
// want a function that will check for changes on the front end
function getSynchedProject(currentState, responseProject) {
let hasChanges = false;
const synchedProject = Object.assign({}, responseProject);
const currentFiles = currentState.files.map(({ name, children, content }) => ({ name, children, content }));
const responseFiles = responseProject.files.map(({ name, children, content }) => ({ name, children, content }));
if (!isEqual(currentFiles, responseFiles)) {
synchedProject.files = currentState.files;
hasChanges = true;
}
if (currentState.project.name !== responseProject.name) {
synchedProject.name = currentState.project.name;
hasChanges = true;
}
return {
synchedProject,
hasChanges
};
}
export function saveProject(selectedFile = null, autosave = false) {
return (dispatch, getState) => {
const state = getState();
if (state.project.isSaving) {
return Promise.resolve();
}
dispatch(startSavingProject());
if (state.user.id && state.project.owner && state.project.owner.id !== state.user.id) {
return Promise.reject();
}
const formParams = Object.assign({}, state.project);
formParams.files = [...state.files];
if (selectedFile) {
const fileToUpdate = formParams.files.find(file => file.id === selectedFile.id);
fileToUpdate.content = selectedFile.content;
}
if (state.project.id) {
return axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
.then((response) => {
dispatch(endSavingProject());
dispatch(setUnsavedChanges(false));
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
if (hasChanges) {
dispatch(setUnsavedChanges(true));
}
dispatch(setProject(synchedProject));
dispatch(projectSaveSuccess());
if (!autosave) {
if (state.ide.justOpenedProject && state.preferences.autosave) {
dispatch(showToast(5500));
dispatch(setToastText('Project saved.'));
setTimeout(() => dispatch(setToastText('Autosave enabled.')), 1500);
dispatch(resetJustOpenedProject());
} else {
dispatch(showToast(1500));
dispatch(setToastText('Project saved.'));
}
}
})
.catch((response) => {
dispatch(endSavingProject());
if (response.status === 403) {
dispatch(showErrorModal('staleSession'));
} else if (response.status === 409) {
dispatch(showErrorModal('staleProject'));
} else {
dispatch(projectSaveFail(response.data));
}
});
}
return axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
.then((response) => {
dispatch(endSavingProject());
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
if (hasChanges) {
dispatch(setNewProject(synchedProject));
dispatch(setUnsavedChanges(false));
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
dispatch(setUnsavedChanges(true));
} else {
dispatch(setNewProject(synchedProject));
dispatch(setUnsavedChanges(false));
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
}
dispatch(projectSaveSuccess());
if (!autosave) {
if (state.preferences.autosave) {
dispatch(showToast(5500));
dispatch(setToastText('Project saved.'));
setTimeout(() => dispatch(setToastText('Autosave enabled.')), 1500);
dispatch(resetJustOpenedProject());
} else {
dispatch(showToast(1500));
dispatch(setToastText('Project saved.'));
}
}
})
.catch((response) => {
dispatch(endSavingProject());
if (response.status === 403) {
dispatch(showErrorModal('staleSession'));
} else {
dispatch(projectSaveFail(response.data));
}
});
};
}
export function autosaveProject() {
return (dispatch, getState) => {
saveProject(null, true)(dispatch, getState);
};
}
export function exportProjectAsZip(projectId) {
const win = window.open(`${ROOT_URL}/projects/${projectId}/zip`, '_blank');
win.focus();
}
export function resetProject() {
return {
type: ActionTypes.RESET_PROJECT
};
}
export function newProject() {
setTimeout(() => {
browserHistory.push('/');
}, 0);
return resetProject();
}
function generateNewIdsForChildren(file, files) {
const newChildren = [];
file.children.forEach((childId) => {
const child = files.find(childFile => childFile.id === childId);
const newId = objectID().toHexString();
child.id = newId;
child._id = newId;
newChildren.push(newId);
generateNewIdsForChildren(child, files);
});
file.children = newChildren; // eslint-disable-line
}
export function cloneProject(id) {
return (dispatch, getState) => {
dispatch(setUnsavedChanges(false));
new Promise((resolve, reject) => {
if (!id) {
resolve(getState());
} else {
fetch(`${ROOT_URL}/projects/${id}`)
.then(res => res.json())
.then(data => resolve({
files: data.files,
project: {
name: data.name
}
}));
}
}).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
}));
});
});
};
}
export function showEditProjectName() {
return {
type: ActionTypes.SHOW_EDIT_PROJECT_NAME
};
}
export function hideEditProjectName() {
return {
type: ActionTypes.HIDE_EDIT_PROJECT_NAME
};
}
export function setProjectSavedTime(updatedAt) {
return {
type: ActionTypes.SET_PROJECT_SAVED_TIME,
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
});
}
});
};
}