merge master

This commit is contained in:
Cassie Tarakajian 2019-09-11 19:05:15 -04:00
commit 7f2529a973
53 changed files with 4560 additions and 1778 deletions

View File

@ -45,6 +45,11 @@
"@babel/preset-env",
"@babel/preset-react"
]
},
"development": {
"plugins": [
"react-hot-loader/babel"
]
}
},
"plugins": [

View File

@ -1,6 +1,8 @@
# [p5.js Web Editor](https://editor.p5js.org)
Hello! The p5.js Web Editor is an in-browser editor for creative coding, specifically for writing [p5.js](https://p5js.org/) sketches. p5.js, a separate [open source project](https://github.com/processing/p5.js), is a JavaScript library with the goal of making coding accessible for artists, designers, educators, and beginners. The web editor shares the same spirit as p5.jsit is designed with the beginner in mind. When using the web editor, you don't need to download or configure anything, you can simply open the website, and start writing code. You can also host your work online and share it with others.
The p5.js Web Editor is a platform for creative coding, with a focus on making coding accessible for as many people as possible, including artists, designers, educators, beginners, and anyone who wants to learn. Simply by opening the website you can get started writing p5.js sketches without downloading or configuring anything. The editor is designed with simplicity in mind by limiting features and frills. We strive to listen to the community to drive the editors development, and to be intentional with every change. The editor is free and open-source.
We also strive to give the community as much ownership and control as possible. You can download your sketches so that you can edit them locally or host them elsewhere. You can also host your own version of the editor, giving you control over its data.
The p5.js Web Editor is currently in active development, and looking for contributions of any type! Please check out the [contribution guide](https://github.com/processing/p5.js-web-editor/blob/master/.github/CONTRIBUTING.md) for more details.

View File

@ -6,6 +6,7 @@ import { Link } from 'react-router';
import InlineSVG from 'react-inlinesvg';
import classNames from 'classnames';
import * as IDEActions from '../modules/IDE/actions/ide';
import * as toastActions from '../modules/IDE/actions/toast';
import * as projectActions from '../modules/IDE/actions/project';
import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
import { logoutUser } from '../modules/User/actions';
@ -93,8 +94,12 @@ class Nav extends React.PureComponent {
handleNew() {
if (!this.props.unsavedChanges) {
this.props.showToast(1500);
this.props.setToastText('Opened new sketch.');
this.props.newProject();
} else if (this.props.warnIfUnsavedChanges()) {
this.props.showToast(1500);
this.props.setToastText('Opened new sketch.');
this.props.newProject();
}
this.setDropdown('none');
@ -682,6 +687,8 @@ class Nav extends React.PureComponent {
Nav.propTypes = {
newProject: PropTypes.func.isRequired,
showToast: PropTypes.func.isRequired,
setToastText: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
autosaveProject: PropTypes.func.isRequired,
exportProjectAsZip: PropTypes.func.isRequired,
@ -738,6 +745,7 @@ function mapStateToProps(state) {
const mapDispatchToProps = {
...IDEActions,
...projectActions,
...toastActions,
logoutUser,
setAllAccessibleOutput
};

View File

@ -12,6 +12,7 @@ export const STOP_ACCESSIBLE_OUTPUT = 'STOP_ACCESSIBLE_OUTPUT';
export const OPEN_PREFERENCES = 'OPEN_PREFERENCES';
export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES';
export const SET_FONT_SIZE = 'SET_FONT_SIZE';
export const SET_LINE_NUMBERS = 'SET_LINE_NUMBERS';
export const AUTH_USER = 'AUTH_USER';
export const UNAUTH_USER = 'UNAUTH_USER';
@ -124,6 +125,9 @@ export const SET_ASSETS = 'SET_ASSETS';
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
export const SET_SORTING = 'SET_SORTING';
export const SET_SORT_PARAMS = 'SET_SORT_PARAMS';
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
export const CLOSE_SKETCHLIST_MODAL = 'CLOSE_SKETCHLIST_MODAL';
export const START_LOADING = 'START_LOADING';
export const STOP_LOADING = 'STOP_LOADING';

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<g>
<path d="M57.281,62.129c-4.16,0.4-8.32-0.721-11.92-3.2l-2.56,8.24l-8.24,9.841c-0.72,0.879-1.76,1.119-2.8,1.199
c-2.56,0.24-4.96-1.92-5.2-4.4c-0.08-1.039,0.08-2.16,0.72-2.959l8.16-9.841l7.76-4c-3.12-3.04-4.88-7.121-5.28-11.201
c-0.8-9.68,6.64-18.32,16.24-19.2c9.601-0.8,18.4,6.56,19.28,16.16c0.4,4.64-0.88,9.281-4,13.041
C66.242,59.568,61.842,61.729,57.281,62.129z M56.722,55.328c5.84-0.56,10.4-5.92,9.84-12c-0.479-5.84-6-10.4-11.76-9.84
c-6,0.48-10.48,5.92-10,11.76c0.24,2.88,1.52,5.6,3.84,7.52C51.041,54.688,53.922,55.568,56.722,55.328z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,6 +1,6 @@
import React from 'react';
import { render } from 'react-dom';
import { hot } from 'react-hot-loader';
import { hot } from 'react-hot-loader/root';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import configureStore from './store';
@ -22,7 +22,7 @@ const App = () => (
</Provider>
);
const HotApp = hot(module)(App);
const HotApp = hot(App);
render(
<HotApp />,

View File

@ -1,26 +1,32 @@
import axios from 'axios';
import * as ActionTypes from '../../../constants';
import { startLoader, stopLoader } from './loader';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function setAssets(assets) {
function setAssets(assets, totalSize) {
return {
type: ActionTypes.SET_ASSETS,
assets
assets,
totalSize
};
}
export function getAssets() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(startLoader());
axios.get(`${ROOT_URL}/S3/objects`, { withCredentials: true })
.then((response) => {
dispatch(setAssets(response.data.assets));
dispatch(setAssets(response.data.assets, response.data.totalSize));
dispatch(stopLoader());
})
.catch(response => dispatch({
type: ActionTypes.ERROR
}));
.catch(() => {
dispatch({
type: ActionTypes.ERROR
});
dispatch(stopLoader());
});
};
}

View File

@ -32,6 +32,24 @@ export function setFontSize(value) {
};
}
export function setLineNumbers(value) {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.SET_LINE_NUMBERS,
value
});
const state = getState();
if (state.user.authenticated) {
const formParams = {
preferences: {
lineNumbers: value
}
};
updatePreferences(formParams, dispatch);
}
};
}
export function setAutosave(value) {
return (dispatch, getState) => {
dispatch({

View File

@ -25,3 +25,14 @@ export function toggleDirectionForField(field) {
field
};
}
export function setSearchTerm(searchTerm) {
return {
type: ActionTypes.SET_SEARCH_TERM,
query: searchTerm
};
}
export function resetSearchTerm() {
return setSearchTerm('');
}

View File

@ -4,12 +4,11 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Link } from 'react-router';
import { Helmet } from 'react-helmet';
import prettyBytes from 'pretty-bytes';
import Loader from '../../App/components/loader';
import * as AssetActions from '../actions/assets';
class AssetList extends React.Component {
constructor(props) {
super(props);
@ -23,17 +22,37 @@ class AssetList extends React.Component {
return `p5.js Web Editor | ${this.props.username}'s assets`;
}
hasAssets() {
return !this.props.loading && this.props.assetList.length > 0;
}
renderLoader() {
if (this.props.loading) return <Loader />;
return null;
}
renderEmptyTable() {
if (!this.props.loading && this.props.assetList.length === 0) {
return (<p className="asset-table__empty">No uploaded assets.</p>);
}
return null;
}
render() {
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
const { assetList, totalSize } = this.props;
return (
<div className="asset-table-container">
{/* Eventually, this copy should be Total / 250 MB Used */}
{this.hasAssets() &&
<p className="asset-table__total">{`${prettyBytes(totalSize)} Total`}</p>
}
<Helmet>
<title>{this.getAssetsTitle()}</title>
</Helmet>
{this.props.assets.length === 0 &&
<p className="asset-table__empty">No uploaded assets.</p>
}
{this.props.assets.length > 0 &&
{this.renderLoader()}
{this.renderEmptyTable()}
{this.hasAssets() &&
<table className="asset-table">
<thead>
<tr>
@ -44,7 +63,7 @@ class AssetList extends React.Component {
</tr>
</thead>
<tbody>
{this.props.assets.map(asset =>
{assetList.map(asset =>
(
<tr className="asset-table__row" key={asset.key}>
<td>{asset.name}</td>
@ -65,20 +84,24 @@ AssetList.propTypes = {
username: PropTypes.string
}).isRequired,
username: PropTypes.string.isRequired,
assets: PropTypes.arrayOf(PropTypes.shape({
assetList: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
sketchName: PropTypes.string.isRequired,
sketchId: PropTypes.string.isRequired
})).isRequired,
totalSize: PropTypes.number.isRequired,
getAssets: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired
};
function mapStateToProps(state) {
return {
user: state.user,
assets: state.assets
assetList: state.assets.list,
totalSize: state.assets.totalSize,
loading: state.loading
};
}

View File

@ -80,7 +80,7 @@ class Editor extends React.Component {
this.widgets = [];
this._cm = CodeMirror(this.codemirrorContainer, { // eslint-disable-line
theme: `p5-${this.props.theme}`,
lineNumbers: true,
lineNumbers: this.props.lineNumbers,
styleActiveLine: true,
inputStyle: 'contenteditable',
lineWrapping: this.props.linewrap,
@ -181,6 +181,9 @@ class Editor extends React.Component {
if (this.props.theme !== prevProps.theme) {
this._cm.setOption('theme', `p5-${this.props.theme}`);
}
if (this.props.lineNumbers !== prevProps.lineNumbers) {
this._cm.setOption('lineNumbers', this.props.lineNumbers);
}
if (prevProps.consoleEvents !== this.props.consoleEvents) {
this.props.showRuntimeErrorWarning();
@ -188,7 +191,7 @@ class Editor extends React.Component {
for (let i = 0; i < this._cm.lineCount(); i += 1) {
this._cm.removeLineClass(i, 'background', 'line-runtime-error');
}
if (this.props.runtimeErrorWarningVisible && this._cm.getDoc().modeOption === 'javascript') {
if (this.props.runtimeErrorWarningVisible) {
this.props.consoleEvents.forEach((consoleEvent) => {
if (consoleEvent.method === 'error') {
if (consoleEvent.data &&
@ -197,7 +200,11 @@ class Editor extends React.Component {
consoleEvent.data[0].indexOf(')') > -1) {
const n = consoleEvent.data[0].replace(')', '').split(' ');
const lineNumber = parseInt(n[n.length - 1], 10) - 1;
if (!Number.isNaN(lineNumber)) {
const { source } = consoleEvent;
const fileName = this.props.file.name;
const errorFromJavaScriptFile = (`${source}.js` === fileName);
const errorFromIndexHTML = ((source === fileName) && (fileName === 'index.html'));
if (!Number.isNaN(lineNumber) && (errorFromJavaScriptFile || errorFromIndexHTML)) {
this._cm.addLineClass(lineNumber, 'background', 'line-runtime-error');
}
}
@ -338,6 +345,7 @@ class Editor extends React.Component {
}
Editor.propTypes = {
lineNumbers: PropTypes.bool.isRequired,
lintWarning: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired,
lintMessages: PropTypes.arrayOf(PropTypes.shape({
@ -359,7 +367,7 @@ Editor.propTypes = {
content: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
fileType: PropTypes.string.isRequired,
url: PropTypes.string.isRequired
url: PropTypes.string
}).isRequired,
editorOptionsVisible: PropTypes.bool.isRequired,
showEditorOptions: PropTypes.func.isRequired,

View File

@ -14,7 +14,7 @@ class NewFolderForm extends React.Component {
render() {
const {
fields: { name }, handleSubmit, submitting, pristine
fields: { name }, handleSubmit
} = this.props;
return (
<form
@ -34,7 +34,7 @@ class NewFolderForm extends React.Component {
ref={(element) => { this.fileName = element; }}
{...domOnlyProps(name)}
/>
<input type="submit" value="Add Folder" disabled={submitting || pristine} aria-label="add folder" />
<input type="submit" value="Add Folder" aria-label="add folder" />
{name.touched && name.error && <span className="form-error">{name.error}</span>}
</form>
);

View File

@ -17,6 +17,7 @@ class Preferences extends React.Component {
this.handleUpdateAutosave = this.handleUpdateAutosave.bind(this);
this.handleUpdateLinewrap = this.handleUpdateLinewrap.bind(this);
this.handleLintWarning = this.handleLintWarning.bind(this);
this.handleLineNumbers = this.handleLineNumbers.bind(this);
this.onFontInputChange = this.onFontInputChange.bind(this);
this.onFontInputSubmit = this.onFontInputSubmit.bind(this);
this.increaseFontSize = this.increaseFontSize.bind(this);
@ -29,9 +30,12 @@ class Preferences extends React.Component {
}
onFontInputChange(event) {
this.setState({
fontSize: event.target.value
});
const INTEGER_REGEX = /^[0-9\b]+$/;
if (event.target.value === '' || INTEGER_REGEX.test(event.target.value)) {
this.setState({
fontSize: event.target.value
});
}
}
onFontInputSubmit(event) {
@ -79,6 +83,11 @@ class Preferences extends React.Component {
this.props.setLintWarning(value);
}
handleLineNumbers(event) {
const value = event.target.value === 'true';
this.props.setLineNumbers(value);
}
render() {
const beep = new Audio(beepUrl);
@ -151,10 +160,9 @@ class Preferences extends React.Component {
aria-atomic="true"
value={this.state.fontSize}
onChange={this.onFontInputChange}
type="text"
ref={(element) => { this.fontSizeInput = element; }}
onClick={() => {
this.fontSizeInput.select();
}}
onClick={() => { this.fontSizeInput.select(); }}
/>
</form>
<button
@ -223,6 +231,33 @@ class Preferences extends React.Component {
</div>
</TabPanel>
<TabPanel>
<div className="preference">
<h4 className="preference__title">Line numbers</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => this.props.setLineNumbers(true)}
aria-label="line numbers on"
name="line numbers"
id="line-numbers-on"
className="preference__radio-button"
value="On"
checked={this.props.lineNumbers}
/>
<label htmlFor="line-numbers-on" className="preference__option">On</label>
<input
type="radio"
onChange={() => this.props.setLineNumbers(false)}
aria-label="line numbers off"
name="line numbers"
id="line-numbers-off"
className="preference__radio-button"
value="Off"
checked={!this.props.lineNumbers}
/>
<label htmlFor="line-numbers-off" className="preference__option">Off</label>
</div>
</div>
<div className="preference">
<h4 className="preference__title">Lint warning sound</h4>
<div className="preference__options">
@ -309,9 +344,11 @@ class Preferences extends React.Component {
Preferences.propTypes = {
fontSize: PropTypes.number.isRequired,
lineNumbers: PropTypes.bool.isRequired,
setFontSize: PropTypes.func.isRequired,
autosave: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired,
setLineNumbers: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired,
setLinewrap: PropTypes.func.isRequired,
textOutput: PropTypes.bool.isRequired,

View File

@ -0,0 +1,91 @@
import PropTypes from 'prop-types';
import React from 'react';
import InlineSVG from 'react-inlinesvg';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { throttle } from 'lodash';
import * as SortingActions from '../actions/sorting';
const searchIcon = require('../../../images/magnifyingglass.svg');
class Searchbar extends React.Component {
constructor(props) {
super(props);
this.state = {
searchValue: this.props.searchTerm
};
this.throttledSearchChange = throttle(this.searchChange, 500);
}
componentWillUnmount() {
this.props.resetSearchTerm();
}
handleResetSearch = () => {
this.setState({ searchValue: '' }, () => {
this.props.resetSearchTerm();
});
}
handleSearchEnter = (e) => {
if (e.key === 'Enter') {
this.props.setSearchTerm(this.state.searchValue);
}
}
searchChange = (value) => {
this.props.setSearchTerm(this.state.searchValue);
};
handleSearchChange = (e) => {
this.setState({ searchValue: e.target.value }, () => {
this.throttledSearchChange(this.state.searchValue);
});
}
render() {
const { searchValue } = this.state;
return (
<div className="searchbar">
<button
type="submit"
className="searchbar__button"
onClick={this.handleSearchEnter}
>
<InlineSVG className="searchbar__icon" src={searchIcon} />
</button>
<input
className="searchbar__input"
type="text"
value={searchValue}
placeholder="Search files..."
onChange={this.handleSearchChange}
onKeyUp={this.handleSearchEnter}
/>
<button
className="searchbar__clear-button"
onClick={this.handleResetSearch}
>clear
</button>
</div>
);
}
}
Searchbar.propTypes = {
searchTerm: PropTypes.string.isRequired,
setSearchTerm: PropTypes.func.isRequired,
resetSearchTerm: PropTypes.func.isRequired
};
function mapStateToProps(state) {
return {
searchTerm: state.search.searchTerm
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Object.assign({}, SortingActions), dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Searchbar);

View File

@ -7,6 +7,7 @@ import { connect } from 'react-redux';
import { Link } from 'react-router';
import { bindActionCreators } from 'redux';
import classNames from 'classnames';
import slugify from 'slugify';
import * as ProjectActions from '../actions/project';
import * as ProjectsActions from '../actions/projects';
import * as ToastActions from '../actions/toast';
@ -137,13 +138,17 @@ class SketchListRowBase extends React.Component {
const { sketch, username } = this.props;
const { renameOpen, optionsOpen, renameValue } = this.state;
const userIsOwner = this.props.user.username === this.props.username;
let url = `/${username}/sketches/${sketch.id}`;
if (username === 'p5') {
url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
}
return (
<tr
className="sketches-table__row"
key={sketch.id}
>
<th scope="row">
<Link to={`/${username}/sketches/${sketch.id}`}>
<Link to={url}>
{renameOpen ? '' : sketch.name}
</Link>
{renameOpen
@ -359,10 +364,20 @@ SketchList.propTypes = {
sorting: PropTypes.shape({
field: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired
}).isRequired
}).isRequired,
project: PropTypes.shape({
id: PropTypes.string,
owner: PropTypes.shape({
id: PropTypes.string
})
})
};
SketchList.defaultProps = {
project: {
id: undefined,
owner: undefined
},
username: undefined
};
@ -371,7 +386,8 @@ function mapStateToProps(state) {
user: state.user,
sketches: getSortedSketches(state),
sorting: state.sorting,
loading: state.loading
loading: state.loading,
project: state.project
};
}

View File

@ -29,6 +29,9 @@ import * as ToastActions from '../actions/toast';
import * as ConsoleActions from '../actions/console';
import { getHTMLFile } from '../reducers/files';
import Overlay from '../../App/components/Overlay';
import SketchList from '../components/SketchList';
import Searchbar from '../components/Searchbar';
import AssetList from '../components/AssetList';
import About from '../components/About';
import Feedback from '../components/Feedback';
@ -203,6 +206,8 @@ class IDEView extends React.Component {
setFontSize={this.props.setFontSize}
autosave={this.props.preferences.autosave}
linewrap={this.props.preferences.linewrap}
lineNumbers={this.props.preferences.lineNumbers}
setLineNumbers={this.props.setLineNumbers}
setAutosave={this.props.setAutosave}
setLinewrap={this.props.setLinewrap}
lintWarning={this.props.preferences.lintWarning}
@ -268,6 +273,7 @@ class IDEView extends React.Component {
file={this.props.selectedFile}
updateFileContent={this.props.updateFileContent}
fontSize={this.props.preferences.fontSize}
lineNumbers={this.props.preferences.lineNumbers}
files={this.props.files}
editorOptionsVisible={this.props.ide.editorOptionsVisible}
showEditorOptions={this.props.showEditorOptions}
@ -363,6 +369,31 @@ class IDEView extends React.Component {
createFolder={this.props.createFolder}
/>
}
{ this.props.location.pathname.match(/sketches$/) &&
<Overlay
ariaLabel="project list"
title="Open a Sketch"
previousPath={this.props.ide.previousPath}
>
<Searchbar />
<SketchList
username={this.props.params.username}
user={this.props.user}
/>
</Overlay>
}
{ this.props.location.pathname.match(/assets$/) &&
<Overlay
title="Assets"
ariaLabel="asset list"
previousPath={this.props.ide.previousPath}
>
<AssetList
username={this.props.params.username}
user={this.props.user}
/>
</Overlay>
}
{ this.props.location.pathname === '/about' &&
<Overlay
previousPath={this.props.ide.previousPath}
@ -490,6 +521,7 @@ IDEView.propTypes = {
fontSize: PropTypes.number.isRequired,
autosave: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired,
lineNumbers: PropTypes.bool.isRequired,
lintWarning: PropTypes.bool.isRequired,
textOutput: PropTypes.bool.isRequired,
gridOutput: PropTypes.bool.isRequired,
@ -500,6 +532,7 @@ IDEView.propTypes = {
closePreferences: PropTypes.func.isRequired,
setFontSize: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired,
setLineNumbers: PropTypes.func.isRequired,
setLinewrap: PropTypes.func.isRequired,
setLintWarning: PropTypes.func.isRequired,
setTextOutput: PropTypes.func.isRequired,

View File

@ -1,9 +1,15 @@
import * as ActionTypes from '../../../constants';
const assets = (state = [], action) => {
// 1,000,000 bytes in a MB. can't upload if totalSize is bigger than this.
const initialState = {
list: [],
totalSize: 0
};
const assets = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.SET_ASSETS:
return action.assets;
return { list: action.assets, totalSize: action.totalSize };
default:
return state;
}

View File

@ -13,9 +13,9 @@ const defaultHTML =
`<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />

View File

@ -10,9 +10,9 @@ const initialState = {
projectOptionsVisible: false,
newFolderModalVisible: false,
shareModalVisible: false,
shareModalProjectId: null,
shareModalProjectName: null,
shareModalProjectUsername: null,
shareModalProjectId: 'abcd',
shareModalProjectName: 'My Cute Sketch',
shareModalProjectUsername: 'p5_user',
editorOptionsVisible: false,
keyboardShortcutVisible: false,
unsavedChanges: false,

View File

@ -4,6 +4,7 @@ const initialState = {
fontSize: 18,
autosave: true,
linewrap: true,
lineNumbers: true,
lintWarning: false,
textOutput: false,
gridOutput: false,
@ -34,6 +35,8 @@ const preferences = (state = initialState, action) => {
return Object.assign({}, state, { theme: action.value });
case ActionTypes.SET_AUTOREFRESH:
return Object.assign({}, state, { autorefresh: action.value });
case ActionTypes.SET_LINE_NUMBERS:
return Object.assign({}, state, { lineNumbers: action.value });
default:
return state;
}

View File

@ -0,0 +1,14 @@
import * as ActionTypes from '../../../constants';
const initialState = {
searchTerm: ''
};
export default (state = initialState, action) => {
switch (action.type) {
case ActionTypes.SET_SEARCH_TERM:
return { ...state, searchTerm: action.query };
default:
return state;
}
};

View File

@ -6,9 +6,27 @@ import { DIRECTION } from '../actions/sorting';
const getSketches = state => state.sketches;
const getField = state => state.sorting.field;
const getDirection = state => state.sorting.direction;
const getSearchTerm = state => state.search.searchTerm;
const getFilteredSketches = createSelector(
getSketches,
getSearchTerm,
(sketches, search) => {
if (search) {
const searchStrings = sketches.map((sketch) => {
const smallSketch = {
name: sketch.name
};
return { ...sketch, searchString: Object.values(smallSketch).join(' ').toLowerCase() };
});
return searchStrings.filter(sketch => sketch.searchString.includes(search.toLowerCase()));
}
return sketches;
}
);
const getSortedSketches = createSelector(
getSketches,
getFilteredSketches,
getField,
getDirection,
(sketches, field, direction) => {

View File

@ -64,9 +64,8 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
browserHistory.push(previousPath);
resolve();
})
.catch((response) => {
reject({ password: response.data.message, _error: 'Login failed!' }); // eslint-disable-line
});
.catch(error =>
reject({ password: error.response.data.message, _error: 'Login failed!' })); // eslint-disable-line
});
}
@ -84,7 +83,8 @@ export function getUser() {
});
})
.catch((response) => {
dispatch(authError(response.data.error));
const message = response.message || response.data.error;
dispatch(authError(message));
});
};
}

View File

@ -17,6 +17,7 @@ function ResetPasswordForm(props) {
id="email"
{...domOnlyProps(email)}
/>
{email.touched && email.error && <span className="form-error">{email.error}</span>}
</p>
<input
type="submit"

View File

@ -83,9 +83,9 @@ function mapDispatchToProps() {
LoginView.propTypes = {
previousPath: PropTypes.string.isRequired,
user: {
user: PropTypes.shape({
authenticated: PropTypes.bool
}
})
};
LoginView.defaultProps = {

View File

@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Link, browserHistory } from 'react-router';
@ -8,6 +9,7 @@ import { reduxForm } from 'redux-form';
import { Helmet } from 'react-helmet';
import * as UserActions from '../actions';
import ResetPasswordForm from '../components/ResetPasswordForm';
import { validateResetPassword } from '../../../utils/reduxFormUtils';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
@ -83,16 +85,8 @@ function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
function validate(formProps) {
const errors = {};
if (!formProps.email) {
errors.email = 'Please enter an email';
}
return errors;
}
export default reduxForm({
form: 'reset-password',
fields: ['email'],
validate
validate: validateResetPassword
}, mapStateToProps, mapDispatchToProps)(ResetPasswordView);

View File

@ -10,6 +10,7 @@ import sketches from './modules/IDE/reducers/projects';
import toast from './modules/IDE/reducers/toast';
import console from './modules/IDE/reducers/console';
import assets from './modules/IDE/reducers/assets';
import search from './modules/IDE/reducers/search';
import sorting from './modules/IDE/reducers/sorting';
import loading from './modules/IDE/reducers/loading';
@ -21,6 +22,7 @@ const rootReducer = combineReducers({
user,
project,
sketches,
search,
sorting,
editorAccessibility,
toast,

View File

@ -64,7 +64,10 @@ $themes: (
input-text-color: #333,
input-border-color: #b5b5b5,
about-list-text-color: #4a4a4a,
search-background-color: #ebebeb,
search-background-color: #ffffff,
search-clear-background-color: #e9e9e9,
search-hover-text-color: #ffffff,
search-hover-background-color: #4d4d4d,
dropdown-color: #414141,
keyboard-shortcut-color: #757575,
nav-hover-color: $p5js-pink,
@ -127,7 +130,10 @@ $themes: (
input-text-color: #333,
input-border-color: #b5b5b5,
about-list-text-color: #f4f4f4,
search-background-color: #ebebeb,
search-background-color: #ffffff,
search-clear-background-color: #4f4f4f,
search-hover-text-color: #ffffff,
search-hover-background-color: $p5js-pink,
dropdown-color: #dadada,
keyboard-shortcut-color: #B5B5B5,
nav-hover-color: $p5js-pink,
@ -187,6 +193,9 @@ $themes: (
input-border-color: #b5b5b5,
about-list-text-color: #f4f4f4,
search-background-color: $white,
search-clear-background-color: #444,
search-hover-text-color: $black,
search-hover-background-color: $yellow,
dropdown-color: #e1e1e1,
keyboard-shortcut-color: #e1e1e1,
nav-hover-color: $yellow,

View File

@ -1,6 +1,6 @@
.asset-table-container {
// flex: 1 1 0%;
overflow-y: scroll;
overflow-y: auto;
max-width: 100%;
min-height: #{400 / $base-font-size}rem;
}
@ -54,3 +54,7 @@
font-size: #{16 / $base-font-size}rem;
padding: #{42 / $base-font-size}rem 0;
}
.asset-table__total {
padding: 0 #{20 / $base-font-size}rem;
}

View File

@ -97,16 +97,6 @@
margin-left: #{5 / $base-font-size}rem;
}
.nav__dropdown {
@include themify() {
color: getThemifyVariable('nav-hover-color');
}
}
.nav__item-header-triangle {
margin-left: #{5 / $base-font-size}rem;
}
.nav__dropdown {
@extend %dropdown-open-left;
display: none;

View File

@ -24,6 +24,7 @@
flex-flow: column;
max-height: 80%;
max-width: 65%;
position: relative;
}
.overlay__header {

View File

@ -5,7 +5,8 @@
min-height: #{460 / $base-font-size}rem;
max-height: 100%;
z-index: 9999;
padding: 0 #{20 / $base-font-size}rem #{2 / $base-font-size}rem #{20 / $base-font-size}rem ;
padding: 0 #{20 / $base-font-size}rem #{2 / $base-font-size}rem #{20 /
$base-font-size}rem;
display: flex;
flex-direction: column;
outline: none;
@ -47,17 +48,26 @@
flex-direction: row;
justify-content: space-between;
@include themify() {
color: getThemifyVariable('heading-text-color');
color: getThemifyVariable("heading-text-color");
}
}
.preferences__title{
.preferences__title {
width: #{90 / $base-font-size}rem;
height: #{25 / $base-font-size}rem;
font-family: Montserrat;
font-size: #{21 / $base-font-size}rem;
font-weight: bold;
text-align: left;
height: #{25 / $base-font-size}rem;
font-family: Montserrat;
font-size: #{21 / $base-font-size}rem;
font-weight: bold;
text-align: left;
}
.preference__subheadings {
display: flex;
flex-wrap: wrap;
padding-bottom: #{0.1 / $base-font-size}rem;
@include themify() {
border-bottom: 1px solid getThemifyVariable("button-border-color");
}
}
.preference {
@ -66,7 +76,7 @@
padding-bottom: #{12 / $base-font-size}rem;
& + & {
@include themify() {
border-top: 1px dashed getThemifyVariable('button-border-color');
border-top: 1px dashed getThemifyVariable("button-border-color");
}
}
}
@ -77,13 +87,13 @@
margin-top: #{13 / $base-font-size}rem;
margin-bottom: #{7 / $base-font-size}rem;
@include themify() {
color: getThemifyVariable('heading-text-color');
color: getThemifyVariable("heading-text-color");
}
}
.preference__subtitle {
@include themify() {
color: getThemifyVariable('inactive-text-color');
color: getThemifyVariable("inactive-text-color");
}
width: 100%;
margin-bottom: #{10 / $base-font-size}rem;
@ -92,9 +102,10 @@
.preference__value {
@include themify() {
border: #{1 / $base-font-size}rem solid getThemifyVariable('button-border-color');
background-color: getThemifyVariable('button-background-color');
color: getThemifyVariable('input-text-color');
border: #{1 / $base-font-size}rem solid
getThemifyVariable("button-border-color");
background-color: getThemifyVariable("button-background-color");
color: getThemifyVariable("input-text-color");
}
text-align: center;
border-radius: 0%;
@ -109,17 +120,60 @@
.preference__label {
@include themify() {
color: getThemifyVariable('inactive-text-color');
color: getThemifyVariable("inactive-text-color");
&:hover {
color: getThemifyVariable('primary-text-color');
color: getThemifyVariable("primary-text-color");
}
}
margin: #{-15 / $base-font-size}rem 0 0 #{-5 / $base-font-size}rem;
font-size: #{9 / $base-font-size}rem;
width: #{44 / $base-font-size}rem;
}
.react-tabs__tab--selected {
@include themify() {
border-bottom: #{4 / $base-font-size}rem solid
getThemifyVariable("button-background-hover-color");
}
}
.react-tabs__tab--selected .preference__subheading {
@include themify() {
color: getThemifyVariable("primary-text-color");
}
}
.react-tabs__tab {
text-align: center;
color: black;
display: flex;
align-items: center;
border-bottom: #{4 / $base-font-size}rem solid transparent;
& + & {
margin-left: #{45 / $base-font-size}rem;
}
}
.preference__subheading {
@include themify() {
color: getThemifyVariable("inactive-text-color");
&:hover,
&:focus {
color: getThemifyVariable("primary-text-color");
cursor: pointer;
}
&:focus {
color: getThemifyVariable("primary-text-color");
cursor: pointer;
}
}
font-size: #{12 / $base-font-size}rem;
height: #{20 / $base-font-size}rem;
width: 100%;
margin: 0;
padding: 0 #{5 / $base-font-size}rem;
}
.preference__vertical-list {
display: flex;
flex-direction: column;
@ -135,8 +189,8 @@
margin-top: #{40 / $base-font-size}rem;
margin-bottom: #{10 / $base-font-size}rem;
@include themify() {
color: getThemifyVariable('inactive-text-color');
border-bottom: 1px dashed getThemifyVariable('button-border-color');
color: getThemifyVariable("inactive-text-color");
border-bottom: 1px dashed getThemifyVariable("button-border-color");
}
}
@ -144,6 +198,12 @@
@extend %hidden-element;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.preference__option {
@include themify() {
@extend %preference-option;
@ -162,7 +222,7 @@
@include themify() {
@extend %preference-option;
&:hover {
color: getThemifyVariable('button-background-hover-color');
color: getThemifyVariable("button-background-hover-color");
}
}
margin-left: #{30 / $base-font-size}rem;
@ -182,7 +242,7 @@
.preference__radio-button:checked + .preference__option {
@include themify() {
//for some reason this won't work for getThemifyVariable
color: map-get($theme-map, 'primary-text-color');
color: map-get($theme-map, "primary-text-color");
}
}

View File

@ -0,0 +1,61 @@
.searchbar {
position: absolute;
display: flex;
padding-left: #{17 / $base-font-size}rem;
right: #{50 / $base-font-size}rem;
top: #{14 / $base-font-size}rem;
}
.searchbar__input {
width: #{240 / $base-font-size}rem;
height: #{36 / $base-font-size}rem;
border: solid 0.5px;
padding-left: #{36 / $base-font-size}rem;
padding-right: #{38 / $base-font-size}rem;
@include themify() {
border-color: getThemifyVariable('input-border-color');
background-color: getThemifyVariable('search-background-color');
}
}
.searchbar__button {
width: #{31 / $base-font-size}rem;
height: #{36 / $base-font-size}rem;
position: absolute;
padding: 0;
border-right: solid 1px;
@include themify() {
border-right-color: getThemifyVariable('input-border-color');
}
}
.searchbar__icon {
display: inline-block;
& svg {
width: #{22 / $base-font-size}rem;
height: #{27 / $base-font-size}rem;
transform: scaleX(-1);
@include themify() {
fill: getThemifyVariable('input-text-color');
}
}
}
.searchbar__clear-button {
font-weight: bold;
font-size: #{10 / $base-font-size}rem;
text-align: center;
border-radius: 2px;
align-self: center;
position: absolute;
padding: #{3 / $base-font-size}rem #{4 / $base-font-size}rem;
left: #{216 / $base-font-size}rem;;
@include themify() {
color: getThemifyVariable('primary-text-color');
background-color: getThemifyVariable('search-clear-background-color');
&:hover, &:focus {
color: getThemifyVariable('search-hover-text-color');
background-color: getThemifyVariable('search-hover-background-color');
}
}
}

View File

@ -1,11 +1,12 @@
.sketches-table-container {
overflow-y: scroll;
overflow-y: auto;
max-width: 100%;
min-height: #{400 / $base-font-size}rem;
}
.sketches-table {
width: 100%;
max-height: 100%;
border-spacing: 0;
& .sketch-list__dropdown-column {
@ -69,7 +70,6 @@
}
}
.sketches-table thead {
font-size: #{12 / $base-font-size}rem;
@include themify() {

View File

@ -23,6 +23,7 @@
@import 'components/preferences';
@import 'components/reset-password';
@import 'components/new-password';
@import 'components/searchbar';
@import 'components/sketch-list';
@import 'components/sidebar';
@import 'components/modal';

View File

@ -73,6 +73,8 @@ export default function(CodeMirror) {
CodeMirror.on(searchField, "keyup", function (e) {
if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search
startSearch(cm, getSearchState(cm), searchField.value);
} else if (searchField.value.length <= 1) {
cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = '';
}
});
@ -260,6 +262,7 @@ export default function(CodeMirror) {
</button>
</div>
<div class="CodeMirror-search-nav">
<button class="CodeMirror-search-results"></button>
<button
title="Previous"
aria-label="Previous"
@ -292,6 +295,9 @@ export default function(CodeMirror) {
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
state.annotate = cm.showMatchesOnScrollbar(state.query, state.caseInsensitive);
}
if (originalQuery) {
return findNext(cm, false);
}
}
function doSearch(cm, rev, persistent, immediate, ignoreQuery) {
@ -350,11 +356,19 @@ export default function(CodeMirror) {
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) {
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
if (!cursor.find(rev)) return;
if (!cursor.find(rev)) {
cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = '';
return;
}
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60);
state.posFrom = cursor.from(); state.posTo = cursor.to();
var num_match = cm.state.search.annotate.matches.length;
var next = cm.state.search.annotate.matches
.findIndex(s => s.from.ch === cursor.from().ch && s.from.line === cursor.from().line) + 1;
var text_match = next + '/' + num_match;
cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = text_match;
if (callback) callback(cursor.from(), cursor.to())
});}

View File

@ -46,21 +46,36 @@ export const startTag = '@fs-';
export const getAllScriptOffsets = (htmlFile) => {
const offs = [];
let found = true;
const hijackConsoleErrorsScriptLength = 36;
const embeddedJSStart = 'script crossorigin=""';
let foundJSScript = true;
let foundEmbeddedJS = true;
let lastInd = 0;
let ind = 0;
let endFilenameInd = 0;
let filename = '';
let lineOffset = 0;
while (found) {
while (foundJSScript) {
ind = htmlFile.indexOf(startTag, lastInd);
if (ind === -1) {
found = false;
foundJSScript = false;
} else {
endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 3);
filename = htmlFile.substring(ind + startTag.length, endFilenameInd);
// the length of hijackConsoleErrorsScript is 36 lines
lineOffset = htmlFile.substring(0, ind).split('\n').length + 36;
lineOffset = htmlFile.substring(0, ind).split('\n').length + hijackConsoleErrorsScriptLength;
offs.push([lineOffset, filename]);
lastInd = ind + 1;
}
}
lastInd = 0;
while (foundEmbeddedJS) {
ind = htmlFile.indexOf(embeddedJSStart, lastInd);
if (ind === -1) {
foundEmbeddedJS = false;
} else {
filename = 'index.html';
// not sure where the offset of 25 comes from
lineOffset = htmlFile.substring(0, ind).split('\n').length + 25;
offs.push([lineOffset, filename]);
lastInd = ind + 1;
}

View File

@ -15,6 +15,9 @@ export const domOnlyProps = ({
...domProps }) => domProps;
/* eslint-enable */
// eslint-disable-next-line max-len
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
function validateNameEmail(formProps, errors) {
if (!formProps.username) {
errors.username = 'Please enter a username.';
@ -28,7 +31,7 @@ function validateNameEmail(formProps, errors) {
errors.email = 'Please enter an email.';
} else if (
// eslint-disable-next-line max-len
!formProps.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) {
!formProps.email.match(EMAIL_REGEX)) {
errors.email = 'Please enter a valid email address.';
}
}
@ -79,3 +82,14 @@ export function validateSignup(formProps) {
return errors;
}
export function validateResetPassword(formProps) {
const errors = {};
if (!formProps.email) {
errors.email = 'Please enter an email.';
} else if (
// eslint-disable-next-line max-len
!formProps.email.match(EMAIL_REGEX)) {
errors.email = 'Please enter a valid email address.';
}
return errors;
}

View File

@ -1,6 +1,5 @@
if (process.env.NODE_ENV === 'production') {
process.env.webpackAssets = JSON.stringify(require('./dist/static/manifest.json'));
process.env.webpackChunkAssets = JSON.stringify(require('./dist/static/chunk-manifest.json'));
require('./dist/server.bundle.js');
} else {
let parsed = require('dotenv').config();

5347
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -63,9 +63,8 @@
"babel-jest": "^24.8.0",
"babel-loader": "^8.0.0",
"babel-plugin-transform-react-remove-prop-types": "^0.2.12",
"chunk-manifest-webpack-plugin": "github:catarak/chunk-manifest-webpack-plugin",
"css-loader": "^0.23.1",
"cssnano": "^3.10.0",
"css-loader": "^3.2.0",
"cssnano": "^4.1.10",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.6.0",
"eslint": "^4.19.1",
@ -73,19 +72,22 @@
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.2",
"eslint-plugin-react": "^7.12.3",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^2.0.0",
"jest": "^24.8.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.11.0",
"nodemon": "^1.18.9",
"postcss-cssnext": "^2.11.0",
"postcss-focus": "^1.0.0",
"postcss-loader": "^0.9.1",
"postcss-reporter": "^1.4.1",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-cssnext": "^3.1.0",
"postcss-focus": "^4.0.0",
"postcss-loader": "^3.0.0",
"postcss-reporter": "^6.0.1",
"react-test-renderer": "^16.6.0",
"rimraf": "^2.6.3",
"sass-loader": "^6.0.7",
"style-loader": "^0.13.2",
"style-loader": "^1.0.0",
"terser-webpack-plugin": "^1.4.1",
"webpack-cli": "^3.3.7",
"webpack-manifest-plugin": "^2.0.4",
"webpack-node-externals": "^1.7.2"
},
@ -108,7 +110,7 @@
"clipboard": "^1.7.1",
"codemirror": "^5.42.2",
"connect-mongo": "^1.3.2",
"console-feed": "^2.8.8",
"console-feed": "^2.8.10",
"cookie-parser": "^1.4.3",
"cors": "^2.8.5",
"cross-env": "^5.2.0",
@ -129,7 +131,7 @@
"js-beautify": "^1.8.9",
"jsdom": "^9.8.3",
"jshint": "^2.10.1",
"lodash": "^4.17.11",
"lodash": "^4.17.15",
"loop-protect": "github:catarak/loop-protect",
"mjml": "^3.3.2",
"mockingoose": "^2.13.0",
@ -175,7 +177,7 @@
"slugify": "^1.3.4",
"srcdoc-polyfill": "^0.2.0",
"url": "^0.11.0",
"webpack": "^3.12.0",
"webpack": "^4.39.2",
"webpack-dev-middleware": "^2.0.6",
"webpack-hot-middleware": "^2.24.3",
"xhr": "^2.5.0"

View File

@ -126,6 +126,7 @@ export function listObjectsInS3ForUser(req, res) {
.on('end', () => {
const projectAssets = [];
getProjectsForUserId(userId).then((projects) => {
let totalSize = 0;
assets.forEach((asset) => {
const name = asset.key.split('/').pop();
const foundAsset = {
@ -134,6 +135,7 @@ export function listObjectsInS3ForUser(req, res) {
size: asset.size,
url: `${process.env.S3_BUCKET_URL_BASE}${asset.key}`
};
totalSize += asset.size;
projects.some((project) => {
let found = false;
project.files.some((file) => {
@ -152,7 +154,7 @@ export function listObjectsInS3ForUser(req, res) {
});
projectAssets.push(foundAsset);
});
res.json({ assets: projectAssets });
res.json({ assets: projectAssets, totalSize });
});
});
});

View File

@ -6,7 +6,7 @@ export function createSession(req, res, next) {
passport.authenticate('local', (err, user) => { // eslint-disable-line consistent-return
if (err) { return next(err); }
if (!user) {
return res.status(401).send({ message: 'Invalid username or password.' });
return res.status(401).json({ message: 'Invalid username or password.' });
}
req.logIn(user, (innerErr) => {

View File

@ -1,3 +1,3 @@
require('@babel/register');
require('@babel/polyfill');
require('./moveBucket');
require('./truncate');

View File

@ -0,0 +1,36 @@
/* eslint-disable */
import mongoose from 'mongoose';
import slugify from 'slugify';
const dotenv = require('dotenv');
dotenv.config();
import Project from '../models/project';
// Connect to MongoDB
mongoose.Promise = global.Promise;
mongoose.connect(process.env.MONGO_URL, {
useMongoClient: true
});
mongoose.connection.on('error', () => {
console.error('MongoDB Connection Error. Please make sure that MongoDB is running.');
process.exit(1);
});
Project.find({}, {}, {
timeout: true
}).cursor().eachAsync((project) => {
console.log(project.name);
if (project.name.length < 256) {
console.log('Project name is okay.');
return Promise.resolve();
}
project.name = project.name.substr(0, 255);
project.slug = slugify(project.name, '_');
return project.save().then(() => {
console.log('Updated sketch slug to: ' + project.slug);
});
}).then(() => {
console.log('Done iterating over every sketch.');
process.exit(0);
});

View File

@ -55,6 +55,7 @@ const userSchema = new Schema({
apiKeys: { type: [apiKeySchema] },
preferences: {
fontSize: { type: Number, default: 18 },
lineNumbers: { type: Boolean, default: true },
indentationAmount: { type: Number, default: 2 },
isTabIndent: { type: Boolean, default: false },
autosave: { type: Boolean, default: true },

View File

@ -11,9 +11,9 @@ const defaultHTML =
`<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
@ -61,7 +61,7 @@ function getCategories() {
for (let j = 1; j < metadata.name.split('_').length; j += 1) {
category += `${metadata.name.split('_')[j]} `;
}
categories.push({ url: metadata.url, name: category });
categories.push({ url: metadata.url, name: category.trim() });
});
return categories;
@ -114,12 +114,12 @@ function getSketchContent(projectsInAllCategories) {
return rp(options).then((res) => {
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
if (noNumberprojectName === 'Instance Mode : Instance Container ') {
if (noNumberprojectName === 'Instance Mode: Instance Container ') {
for (let i = 0; i < 4; i += 1) {
const splitedRes = `${res.split('*/')[1].split('</html>')[i]}</html>\n`;
project.sketchContent = splitedRes.replace(
'p5.js',
'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.4/p5.min.js'
'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js'
);
}
} else {
@ -153,7 +153,7 @@ function createProjectsInP5user(projectsInAllCategories) {
const c = objectID().toHexString();
const r = objectID().toHexString();
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
if (noNumberprojectName === 'Instance Mode : Instance Container ') {
if (noNumberprojectName === 'Instance Mode: Instance Container ') {
newProject = new Project({
name: project.projectName,
user: user._id,
@ -167,7 +167,7 @@ function createProjectsInP5user(projectsInAllCategories) {
},
{
name: 'sketch.js',
content: '// Instance Mode : Instance Container, please check its index.html file',
content: '// Instance Mode: Instance Container, please check its index.html file',
id: a,
_id: a,
fileType: 'file',

View File

@ -52,7 +52,7 @@ const corsOriginsWhitelist = [
// Run Webpack dev server in development mode
if (process.env.NODE_ENV === 'development') {
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, { lazy: false, noInfo: true, publicPath: config[0].output.publicPath }));
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
app.use(webpackHotMiddleware(compiler));
corsOriginsWhitelist.push(/localhost/);

View File

@ -1,6 +1,5 @@
export function renderIndex() {
const assetsManifest = process.env.webpackAssets && JSON.parse(process.env.webpackAssets);
const chunkManifest = process.env.webpackChunkAssets && JSON.parse(process.env.webpackChunkAssets);
return `
<!DOCTYPE html>
<html>
@ -37,13 +36,6 @@ export function renderIndex() {
<body>
<div id="root" class="root-app">
</div>
<script>
${process.env.NODE_ENV === 'production' ?
`//<![CDATA[
window.webpackManifest = ${JSON.stringify(chunkManifest)};
//]]>` : ''}
</script>
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/vendor.js']}` : '/vendor.js'}'></script>
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/app.js']}` : '/app.js'}'></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

View File

@ -5,7 +5,10 @@ if (process.env.NODE_ENV === 'development') {
require('dotenv').config();
}
module.exports = [{
// react hmr being fucked up has to do with the multiple entries!!! cool.
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
app: [
@ -15,14 +18,13 @@ module.exports = [{
'react-hot-loader/patch',
'./client/index.jsx',
],
vendor: [
'react',
'react-dom'
previewScripts: [
path.resolve(__dirname, '../client/utils/previewEntry.js')
]
},
output: {
path: `${__dirname}`,
filename: 'app.js',
filename: '[name].js',
publicPath: '/'
},
resolve: {
@ -34,11 +36,6 @@ module.exports = [{
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
filename: 'vendor.js',
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development')
@ -100,32 +97,4 @@ module.exports = [{
}
],
},
},
{
entry: path.resolve(__dirname, '../client/utils/previewEntry.js'),
target: 'web',
output: {
path: `${__dirname}`,
filename: 'previewScripts.js',
publicPath: '/'
},
resolve: {
extensions: ['*', '.js', '.jsx'],
modules: [
'client',
'node_modules',
],
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
babelrc: true
}
}
],
},
}]
};

View File

@ -3,7 +3,7 @@ const nodeExternals = require('webpack-node-externals');
module.exports = [{
entry: path.resolve(__dirname, '../server/scripts/fetch-examples.js'),
mode: 'production',
output: {
path: path.resolve(__dirname, '../dist/'),
filename: 'fetch-examples.bundle.js'
@ -22,7 +22,7 @@ module.exports = [{
},
module: {
loaders: [
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
@ -36,7 +36,7 @@ module.exports = [{
},
{
entry: path.resolve(__dirname, '../server/scripts/fetch-examples-gg.js'),
mode: 'production',
output: {
path: path.resolve(__dirname, '../dist/'),
filename: 'fetch-examples-gg.bundle.js'
@ -55,7 +55,7 @@ module.exports = [{
},
module: {
loaders: [
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
@ -69,7 +69,7 @@ module.exports = [{
},
{
entry: path.resolve(__dirname, '../server/scripts/fetch-examples-ml5.js'),
mode: 'production',
output: {
path: path.resolve(__dirname, '../dist/'),
filename: 'fetch-examples-ml5.bundle.js'
@ -88,7 +88,7 @@ module.exports = [{
},
module: {
loaders: [
rules: [
{
test: /\.js$/,
exclude: /node_modules/,

View File

@ -1,8 +1,9 @@
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
const cssnext = require('postcss-cssnext');
const postcssFocus = require('postcss-focus');
const postcssReporter = require('postcss-reporter');
@ -13,36 +14,18 @@ if (process.env.NODE_ENV === "development") {
module.exports = [{
devtool: 'source-map',
mode: 'production',
entry: {
app: [
'@babel/polyfill',
'core-js/modules/es6.promise',
'core-js/modules/es6.array.iterator',
path.resolve(__dirname, '../client/index.jsx')
],
vendor: [
'axios',
'classnames',
'codemirror',
'csslint',
'dropzone',
'htmlhint',
'js-beautify',
'jshint',
'react-dom',
'react-inlinesvg',
'react-redux',
'react-router',
'react',
'redux-form',
'redux-thunk',
'redux',
]
},
output: {
path: path.resolve(__dirname, '../dist/static'),
filename: '[name].[chunkhash].js',
filename: '[name].[hash].js',
publicPath: '/'
},
@ -53,16 +36,44 @@ module.exports = [{
'node_modules',
]
},
module: {
rules: [
{
rules: [{
test: /main\.scss$/,
exclude: /node_modules/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader!sass-loader!postcss-loader'
})
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2,
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
postcssFocus(),
cssnext({
browsers: ['last 2 versions', 'IE > 9']
}),
cssnano({
autoprefixer: false
}),
postcssReporter({
clearMessages: true
})
],
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /\.jsx?$/,
@ -76,11 +87,11 @@ module.exports = [{
{
test: /\.(png)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/'
}
}
},
{
@ -92,58 +103,28 @@ module.exports = [{
use: {
loader: 'sass-extract-loader',
options: {
plugins: [{ plugin: 'sass-extract-js', options: { camelCase: false } }]
plugins: [{
plugin: 'sass-extract-js',
options: {
camelCase: false
}
}]
}
}
}
]
},
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
filename: '[name].[chunkhash].js',
}),
new ExtractTextPlugin({ filename: 'app.[chunkhash].css', allChunks: true }),
new ManifestPlugin({
basePath: '/',
}),
new ChunkManifestPlugin({
filename: 'chunk-manifest.json',
manifestVariable: 'webpackManifest',
inlineManifest: false
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false,
drop_console :true
}
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: () => [
postcssFocus(),
cssnext({
browsers: ['last 2 versions', 'IE > 9']
}),
cssnano({
autoprefixer: false
}),
postcssReporter({
clearMessages: true
})
]
}
new MiniCssExtractPlugin({
filename: 'app.[hash].css',
})
],
]
},
{
entry: {
@ -152,6 +133,7 @@ module.exports = [{
]
},
target: 'web',
mode: 'production',
output: {
path: path.resolve(__dirname, '../dist/static'),
filename: 'previewScripts.js',
@ -165,7 +147,7 @@ module.exports = [{
],
},
module: {
loaders: [
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
@ -175,12 +157,5 @@ module.exports = [{
}
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}
}];

View File

@ -12,6 +12,7 @@ module.exports = {
},
target: 'node',
mode: 'production',
node: {
__filename: true,
@ -28,7 +29,7 @@ module.exports = {
},
module: {
loaders: [
rules: [
{
test: /\.js$/,
exclude: /node_modules/,