Merge branch 'feature/public-api' into feature/sketch-collections

This commit is contained in:
Cassie Tarakajian 2019-10-31 15:02:31 -04:00
commit 846d2bb7db
19 changed files with 93 additions and 57 deletions

17
.github/config.yml vendored Normal file
View file

@ -0,0 +1,17 @@
# Configuration for welcome - https://github.com/behaviorbot/welcome
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
🎉 Thanks for opening this pull request! Please check out our [contributing guidelines](https://github.com/processing/p5.js-web-editor/blob/master/CONTRIBUTING.md) if you haven't already.
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user

View file

@ -46,4 +46,4 @@ deploy:
env:
global:
- APP_IMAGE_NAME=p5jswebeditor_app
- APP_IMAGE_NAME=p5js-web-editor_app

View file

@ -131,12 +131,12 @@ class Nav extends React.PureComponent {
}
handleAddFile() {
this.props.newFile();
this.props.newFile(this.props.rootFile.id);
this.setDropdown('none');
}
handleAddFolder() {
this.props.newFolder();
this.props.newFolder(this.props.rootFile.id);
this.setDropdown('none');
}
@ -174,7 +174,7 @@ class Nav extends React.PureComponent {
handleDownload() {
this.props.autosaveProject();
this.props.exportProjectAsZip(this.props.project.id);
projectActions.exportProjectAsZip(this.props.project.id);
this.setDropdown('none');
}
@ -719,7 +719,6 @@ Nav.propTypes = {
setToastText: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
autosaveProject: PropTypes.func.isRequired,
exportProjectAsZip: PropTypes.func.isRequired,
cloneProject: PropTypes.func.isRequired,
user: PropTypes.shape({
authenticated: PropTypes.bool.isRequired,
@ -750,7 +749,10 @@ Nav.propTypes = {
setAllAccessibleOutput: PropTypes.func.isRequired,
newFile: PropTypes.func.isRequired,
newFolder: PropTypes.func.isRequired,
layout: PropTypes.oneOf(['dashboard', 'project'])
layout: PropTypes.oneOf(['dashboard', 'project']),
rootFile: PropTypes.shape({
id: PropTypes.string.isRequired
}).isRequired
};
Nav.defaultProps = {
@ -767,7 +769,8 @@ function mapStateToProps(state) {
return {
project: state.project,
user: state.user,
unsavedChanges: state.ide.unsavedChanges
unsavedChanges: state.ide.unsavedChanges,
rootFile: state.files.filter(file => file.name === 'root')[0]
};
}

View file

@ -39,7 +39,12 @@ describe('Nav', () => {
},
startSketch: jest.fn(),
stopSketch: jest.fn(),
setAllAccessibleOutput: jest.fn()
setAllAccessibleOutput: jest.fn(),
showToast: jest.fn(),
setToastText: jest.fn(),
rootFile: {
id: 'root-file'
}
};
const getWrapper = () => shallow(<NavComponent {...props} />);

View file

@ -41,14 +41,7 @@ export function updateFileContent(id, content) {
export function createFile(formProps) {
return (dispatch, getState) => {
const state = getState();
const selectedFile = state.files.find(file => file.isSelectedFile);
const rootFile = state.files.find(file => file.name === 'root');
let parentId;
if (selectedFile.fileType === 'folder') {
parentId = selectedFile.id;
} else {
parentId = rootFile.id;
}
const { parentId } = state.ide;
if (state.project.id) {
const postParams = {
name: createUniqueName(formProps.name, parentId, state.files),
@ -99,14 +92,7 @@ export function createFile(formProps) {
export function createFolder(formProps) {
return (dispatch, getState) => {
const state = getState();
const selectedFile = state.files.find(file => file.isSelectedFile);
const rootFile = state.files.find(file => file.name === 'root');
let parentId;
if (selectedFile.fileType === 'folder') {
parentId = selectedFile.id;
} else {
parentId = rootFile.id;
}
const { parentId } = state.ide;
if (state.project.id) {
const postParams = {
name: createUniqueName(formProps.name, parentId, state.files),

View file

@ -62,9 +62,10 @@ export function resetSelectedFile(previousId) {
};
}
export function newFile() {
export function newFile(parentId) {
return {
type: ActionTypes.SHOW_MODAL
type: ActionTypes.SHOW_MODAL,
parentId
};
}
@ -122,9 +123,10 @@ export function closeProjectOptions() {
};
}
export function newFolder() {
export function newFolder(parentId) {
return {
type: ActionTypes.SHOW_NEW_FOLDER_MODAL
type: ActionTypes.SHOW_NEW_FOLDER_MODAL,
parentId
};
}

View file

@ -82,7 +82,7 @@ export function dropzoneSendingCallback(file, xhr, formData) {
Object.keys(file.postData).forEach((key) => {
formData.append(key, file.postData[key]);
});
formData.append('Content-type', '');
formData.append('Content-type', file.type);
formData.append('Content-length', '');
formData.append('acl', 'public-read');
}

View file

@ -56,19 +56,21 @@ class AssetList extends React.Component {
<table className="asset-table">
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>View</th>
<th>Sketch</th>
<th scope="col">Name</th>
<th scope="col">Size</th>
<th scope="col">Sketch</th>
</tr>
</thead>
<tbody>
{assetList.map(asset =>
(
<tr className="asset-table__row" key={asset.key}>
<td>{asset.name}</td>
<th scope="row">
<Link to={asset.url} target="_blank">
{asset.name}
</Link>
</th>
<td>{prettyBytes(asset.size)}</td>
<td><Link to={asset.url} target="_blank">View</Link></td>
<td><Link to={`/${username}/sketches/${asset.sketchId}`}>{asset.sketchName}</Link></td>
</tr>
))}

View file

@ -108,7 +108,15 @@ class Editor extends React.Component {
delete this._cm.options.lint.options.errors;
this._cm.setOption('extraKeys', {
Tab: cm => cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT)),
Tab: (cm) => {
// might need to specify and indent more?
const selection = cm.doc.getSelection();
if (selection.length > 0) {
cm.execCommand('indentMore');
} else {
cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT));
}
},
[`${metaKey}-Enter`]: () => null,
[`Shift-${metaKey}-Enter`]: () => null,
[`${metaKey}-F`]: 'findPersistent',
@ -126,7 +134,7 @@ class Editor extends React.Component {
this.props.clearConsole();
this.props.startRefreshSketch();
}
}, 400));
}, 1000));
this._cm.on('keyup', () => {
const temp = `line ${parseInt((this._cm.getCursor().line) + 1, 10)}`;

View file

@ -188,7 +188,7 @@ export class FileNode extends React.Component {
<button
aria-label="add file"
onClick={() => {
this.props.newFile();
this.props.newFile(this.props.id);
setTimeout(() => this.hideFileOptions(), 0);
}}
onBlur={this.onBlurComponent}
@ -208,7 +208,7 @@ export class FileNode extends React.Component {
<button
aria-label="add folder"
onClick={() => {
this.props.newFolder();
this.props.newFolder(this.props.id);
setTimeout(() => this.hideFileOptions(), 0);
}}
onBlur={this.onBlurComponent}

View file

@ -5,6 +5,7 @@ import classNames from 'classnames';
import InlineSVG from 'react-inlinesvg';
import NewFileForm from './NewFileForm';
import FileUploader from './FileUploader';
import { CREATE_FILE_REGEX } from '../../../../server/utils/fileUtils';
const exitUrl = require('../../../images/exit.svg');
@ -71,8 +72,8 @@ function validate(formProps) {
if (!formProps.name) {
errors.name = 'Please enter a name';
} else if (!formProps.name.match(/(.+\.js$|.+\.css$|.+\.json$|.+\.txt$|.+\.csv$|.+\.tsv$)/i)) {
errors.name = 'File must be of type JavaScript, CSS, JSON, TXT, CSV, or TSV.';
} else if (!formProps.name.match(CREATE_FILE_REGEX)) {
errors.name = 'Invalid file type. Valid extensions are .js, .css, .json, .txt, .csv, .tsv, .frag, and .vert.';
}
return errors;

View file

@ -66,6 +66,7 @@ class Sidebar extends React.Component {
'sidebar--project-options': this.props.projectOptionsVisible,
'sidebar--cant-edit': !canEditProject
});
const rootFile = this.props.files.filter(file => file.name === 'root')[0];
return (
<nav className={sidebarClass} title="file-navigation" >
@ -90,7 +91,7 @@ class Sidebar extends React.Component {
<button
aria-label="add folder"
onClick={() => {
this.props.newFolder();
this.props.newFolder(rootFile.id);
setTimeout(this.props.closeProjectOptions, 0);
}}
onBlur={this.onBlurComponent}
@ -103,7 +104,7 @@ class Sidebar extends React.Component {
<button
aria-label="add file"
onClick={() => {
this.props.newFile();
this.props.newFile(rootFile.id);
setTimeout(this.props.closeProjectOptions, 0);
}}
onBlur={this.onBlurComponent}
@ -116,7 +117,7 @@ class Sidebar extends React.Component {
</div>
</div>
<ConnectedFileNode
id={this.props.files.filter(file => file.name === 'root')[0].id}
id={rootFile.id}
canEdit={canEditProject}
/>
</nav>

View file

@ -124,7 +124,7 @@ class Toolbar extends React.Component {
</a>
<input
type="text"
maxLength="256"
maxLength="128"
className="toolbar__project-name-input"
value={this.props.project.name}
onChange={this.handleProjectNameChange}

View file

@ -23,6 +23,7 @@ const initialState = {
previousPath: '/',
errorType: undefined,
runtimeErrorWarningVisible: true,
parentId: undefined
};
const ide = (state = initialState, action) => {
@ -38,7 +39,7 @@ const ide = (state = initialState, action) => {
case ActionTypes.CONSOLE_EVENT:
return Object.assign({}, state, { consoleEvent: action.event });
case ActionTypes.SHOW_MODAL:
return Object.assign({}, state, { modalIsVisible: true });
return Object.assign({}, state, { modalIsVisible: true, parentId: action.parentId });
case ActionTypes.HIDE_MODAL:
return Object.assign({}, state, { modalIsVisible: false });
case ActionTypes.COLLAPSE_SIDEBAR:
@ -60,7 +61,7 @@ const ide = (state = initialState, action) => {
case ActionTypes.CLOSE_PROJECT_OPTIONS:
return Object.assign({}, state, { projectOptionsVisible: false });
case ActionTypes.SHOW_NEW_FOLDER_MODAL:
return Object.assign({}, state, { newFolderModalVisible: true });
return Object.assign({}, state, { newFolderModalVisible: true, parentId: action.parentId });
case ActionTypes.CLOSE_NEW_FOLDER_MODAL:
return Object.assign({}, state, { newFolderModalVisible: false });
case ActionTypes.SHOW_SHARE_MODAL:

View file

@ -21,7 +21,11 @@
}
}
& th {
.asset-table__row>th:nth-child(1) {
padding-left: #{12 / $base-font-size}rem;
}
& thead th {
padding-top: #{10 / $base-font-size}rem;
padding-bottom: #{15 / $base-font-size}rem;
height: #{32 / $base-font-size}rem;
@ -32,6 +36,10 @@
background-color: getThemifyVariable('background-color');
}
}
& th {
font-weight: normal;
}
}
.asset-table__row {

View file

@ -44,7 +44,9 @@ export function validateSettings(formProps) {
if (formProps.currentPassword && !formProps.newPassword) {
errors.newPassword = 'Please enter a new password or leave the current password empty.';
}
if (formProps.newPassword && formProps.newPassword.length < 6) {
errors.newPassword = 'Password must be at least 6 characters';
}
return errors;
}
@ -67,11 +69,9 @@ export function validateSignup(formProps) {
if (!formProps.password) {
errors.password = 'Please enter a password';
}
if (formProps.password) {
if (formProps.password.length < 6) {
if (formProps.password && formProps.password.length < 6) {
errors.password = 'Password must be at least 6 characters';
}
}
if (!formProps.confirmPassword) {
errors.confirmPassword = 'Please enter a password confirmation';
}

View file

@ -130,7 +130,7 @@
"jest-express": "^1.10.1",
"js-beautify": "^1.8.9",
"jsdom": "^9.8.3",
"jshint": "^2.10.1",
"jshint": "^2.10.2",
"lodash": "^4.17.15",
"loop-protect": "github:catarak/loop-protect",
"mjml": "^3.3.2",

View file

@ -3,7 +3,7 @@
export const fileExtensionsArray = ['gif', 'jpg', 'jpeg', 'png', 'bmp', 'wav', 'flac', 'ogg',
'oga', 'mp4', 'm4p', 'mp3', 'm4a', 'aiff', 'aif', 'm4v', 'aac', 'webm', 'mpg', 'mp2',
'mpeg', 'mpe', 'mpv', 'js', 'jsx', 'html', 'htm', 'css', 'json', 'csv', 'obj', 'svg',
'otf', 'ttf', 'txt', 'mov', 'vert', 'frag'];
'otf', 'ttf', 'txt', 'mov', 'vert', 'frag', 'bin'];
export const mimeTypes = `image/*,audio/*,text/javascript,text/html,text/css,
application/json,application/x-font-ttf,application/x-font-truetype,text/plain,
@ -22,6 +22,8 @@ export const STRING_REGEX = /(['"])((\\\1|.)*?)\1/gm;
// these are files that have to be linked to with a blob url
export const PLAINTEXT_FILE_REGEX = /.+\.(json|txt|csv|vert|frag|tsv)$/i;
// these are files that users would want to edit as text (maybe svg should be here?)
export const TEXT_FILE_REGEX = /.+\.(json|txt|csv|vert|frag|js|css|html|htm|jsx)$/i;
export const TEXT_FILE_REGEX = /.+\.(json|txt|csv|tsv|vert|frag|js|css|html|htm|jsx)$/i;
export const NOT_EXTERNAL_LINK_REGEX = /^(?!(http:\/\/|https:\/\/))/;
export const EXTERNAL_LINK_REGEX = /^(http:\/\/|https:\/\/)/;
export const CREATE_FILE_REGEX = /.+\.(json|txt|csv|tsv|js|css|frag|vert)$/i;

View file

@ -4,7 +4,7 @@
header does not match `type`
*/
const requestsOfType = type => (req, res, next) => {
const hasContentType = req.get('content-type') !== null;
const hasContentType = req.get('content-type') !== undefined && req.get('content-type') !== null;
const isCorrectType = req.is(type) === null || req.is(type) === type;
if (hasContentType && !isCorrectType) {