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-env",
"@babel/preset-react" "@babel/preset-react"
] ]
},
"development": {
"plugins": [
"react-hot-loader/babel"
]
} }
}, },
"plugins": [ "plugins": [

View file

@ -1,6 +1,8 @@
# [p5.js Web Editor](https://editor.p5js.org) # [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. 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 InlineSVG from 'react-inlinesvg';
import classNames from 'classnames'; import classNames from 'classnames';
import * as IDEActions from '../modules/IDE/actions/ide'; 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 * as projectActions from '../modules/IDE/actions/project';
import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences'; import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
import { logoutUser } from '../modules/User/actions'; import { logoutUser } from '../modules/User/actions';
@ -93,8 +94,12 @@ class Nav extends React.PureComponent {
handleNew() { handleNew() {
if (!this.props.unsavedChanges) { if (!this.props.unsavedChanges) {
this.props.showToast(1500);
this.props.setToastText('Opened new sketch.');
this.props.newProject(); this.props.newProject();
} else if (this.props.warnIfUnsavedChanges()) { } else if (this.props.warnIfUnsavedChanges()) {
this.props.showToast(1500);
this.props.setToastText('Opened new sketch.');
this.props.newProject(); this.props.newProject();
} }
this.setDropdown('none'); this.setDropdown('none');
@ -682,6 +687,8 @@ class Nav extends React.PureComponent {
Nav.propTypes = { Nav.propTypes = {
newProject: PropTypes.func.isRequired, newProject: PropTypes.func.isRequired,
showToast: PropTypes.func.isRequired,
setToastText: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired, saveProject: PropTypes.func.isRequired,
autosaveProject: PropTypes.func.isRequired, autosaveProject: PropTypes.func.isRequired,
exportProjectAsZip: PropTypes.func.isRequired, exportProjectAsZip: PropTypes.func.isRequired,
@ -738,6 +745,7 @@ function mapStateToProps(state) {
const mapDispatchToProps = { const mapDispatchToProps = {
...IDEActions, ...IDEActions,
...projectActions, ...projectActions,
...toastActions,
logoutUser, logoutUser,
setAllAccessibleOutput setAllAccessibleOutput
}; };

View file

@ -12,6 +12,7 @@ export const STOP_ACCESSIBLE_OUTPUT = 'STOP_ACCESSIBLE_OUTPUT';
export const OPEN_PREFERENCES = 'OPEN_PREFERENCES'; export const OPEN_PREFERENCES = 'OPEN_PREFERENCES';
export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES'; export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES';
export const SET_FONT_SIZE = 'SET_FONT_SIZE'; export const SET_FONT_SIZE = 'SET_FONT_SIZE';
export const SET_LINE_NUMBERS = 'SET_LINE_NUMBERS';
export const AUTH_USER = 'AUTH_USER'; export const AUTH_USER = 'AUTH_USER';
export const UNAUTH_USER = 'UNAUTH_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 TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
export const SET_SORTING = 'SET_SORTING'; 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 START_LOADING = 'START_LOADING';
export const STOP_LOADING = 'STOP_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 KiB

View file

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

View file

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

View file

@ -25,3 +25,14 @@ export function toggleDirectionForField(field) {
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 { bindActionCreators } from 'redux';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
import Loader from '../../App/components/loader';
import * as AssetActions from '../actions/assets'; import * as AssetActions from '../actions/assets';
class AssetList extends React.Component { class AssetList extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -23,17 +22,37 @@ class AssetList extends React.Component {
return `p5.js Web Editor | ${this.props.username}'s assets`; 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() { render() {
const username = this.props.username !== undefined ? this.props.username : this.props.user.username; const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
const { assetList, totalSize } = this.props;
return ( return (
<div className="asset-table-container"> <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> <Helmet>
<title>{this.getAssetsTitle()}</title> <title>{this.getAssetsTitle()}</title>
</Helmet> </Helmet>
{this.props.assets.length === 0 && {this.renderLoader()}
<p className="asset-table__empty">No uploaded assets.</p> {this.renderEmptyTable()}
} {this.hasAssets() &&
{this.props.assets.length > 0 &&
<table className="asset-table"> <table className="asset-table">
<thead> <thead>
<tr> <tr>
@ -44,7 +63,7 @@ class AssetList extends React.Component {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{this.props.assets.map(asset => {assetList.map(asset =>
( (
<tr className="asset-table__row" key={asset.key}> <tr className="asset-table__row" key={asset.key}>
<td>{asset.name}</td> <td>{asset.name}</td>
@ -65,20 +84,24 @@ AssetList.propTypes = {
username: PropTypes.string username: PropTypes.string
}).isRequired, }).isRequired,
username: PropTypes.string.isRequired, username: PropTypes.string.isRequired,
assets: PropTypes.arrayOf(PropTypes.shape({ assetList: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string.isRequired, key: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
url: PropTypes.string.isRequired, url: PropTypes.string.isRequired,
sketchName: PropTypes.string.isRequired, sketchName: PropTypes.string.isRequired,
sketchId: PropTypes.string.isRequired sketchId: PropTypes.string.isRequired
})).isRequired, })).isRequired,
totalSize: PropTypes.number.isRequired,
getAssets: PropTypes.func.isRequired, getAssets: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired
}; };
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
user: state.user, 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.widgets = [];
this._cm = CodeMirror(this.codemirrorContainer, { // eslint-disable-line this._cm = CodeMirror(this.codemirrorContainer, { // eslint-disable-line
theme: `p5-${this.props.theme}`, theme: `p5-${this.props.theme}`,
lineNumbers: true, lineNumbers: this.props.lineNumbers,
styleActiveLine: true, styleActiveLine: true,
inputStyle: 'contenteditable', inputStyle: 'contenteditable',
lineWrapping: this.props.linewrap, lineWrapping: this.props.linewrap,
@ -181,6 +181,9 @@ class Editor extends React.Component {
if (this.props.theme !== prevProps.theme) { if (this.props.theme !== prevProps.theme) {
this._cm.setOption('theme', `p5-${this.props.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) { if (prevProps.consoleEvents !== this.props.consoleEvents) {
this.props.showRuntimeErrorWarning(); this.props.showRuntimeErrorWarning();
@ -188,7 +191,7 @@ class Editor extends React.Component {
for (let i = 0; i < this._cm.lineCount(); i += 1) { for (let i = 0; i < this._cm.lineCount(); i += 1) {
this._cm.removeLineClass(i, 'background', 'line-runtime-error'); 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) => { this.props.consoleEvents.forEach((consoleEvent) => {
if (consoleEvent.method === 'error') { if (consoleEvent.method === 'error') {
if (consoleEvent.data && if (consoleEvent.data &&
@ -197,7 +200,11 @@ class Editor extends React.Component {
consoleEvent.data[0].indexOf(')') > -1) { consoleEvent.data[0].indexOf(')') > -1) {
const n = consoleEvent.data[0].replace(')', '').split(' '); const n = consoleEvent.data[0].replace(')', '').split(' ');
const lineNumber = parseInt(n[n.length - 1], 10) - 1; 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'); this._cm.addLineClass(lineNumber, 'background', 'line-runtime-error');
} }
} }
@ -338,6 +345,7 @@ class Editor extends React.Component {
} }
Editor.propTypes = { Editor.propTypes = {
lineNumbers: PropTypes.bool.isRequired,
lintWarning: PropTypes.bool.isRequired, lintWarning: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired, linewrap: PropTypes.bool.isRequired,
lintMessages: PropTypes.arrayOf(PropTypes.shape({ lintMessages: PropTypes.arrayOf(PropTypes.shape({
@ -359,7 +367,7 @@ Editor.propTypes = {
content: PropTypes.string.isRequired, content: PropTypes.string.isRequired,
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
fileType: PropTypes.string.isRequired, fileType: PropTypes.string.isRequired,
url: PropTypes.string.isRequired url: PropTypes.string
}).isRequired, }).isRequired,
editorOptionsVisible: PropTypes.bool.isRequired, editorOptionsVisible: PropTypes.bool.isRequired,
showEditorOptions: PropTypes.func.isRequired, showEditorOptions: PropTypes.func.isRequired,

View file

@ -14,7 +14,7 @@ class NewFolderForm extends React.Component {
render() { render() {
const { const {
fields: { name }, handleSubmit, submitting, pristine fields: { name }, handleSubmit
} = this.props; } = this.props;
return ( return (
<form <form
@ -34,7 +34,7 @@ class NewFolderForm extends React.Component {
ref={(element) => { this.fileName = element; }} ref={(element) => { this.fileName = element; }}
{...domOnlyProps(name)} {...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>} {name.touched && name.error && <span className="form-error">{name.error}</span>}
</form> </form>
); );

View file

@ -17,6 +17,7 @@ class Preferences extends React.Component {
this.handleUpdateAutosave = this.handleUpdateAutosave.bind(this); this.handleUpdateAutosave = this.handleUpdateAutosave.bind(this);
this.handleUpdateLinewrap = this.handleUpdateLinewrap.bind(this); this.handleUpdateLinewrap = this.handleUpdateLinewrap.bind(this);
this.handleLintWarning = this.handleLintWarning.bind(this); this.handleLintWarning = this.handleLintWarning.bind(this);
this.handleLineNumbers = this.handleLineNumbers.bind(this);
this.onFontInputChange = this.onFontInputChange.bind(this); this.onFontInputChange = this.onFontInputChange.bind(this);
this.onFontInputSubmit = this.onFontInputSubmit.bind(this); this.onFontInputSubmit = this.onFontInputSubmit.bind(this);
this.increaseFontSize = this.increaseFontSize.bind(this); this.increaseFontSize = this.increaseFontSize.bind(this);
@ -29,10 +30,13 @@ class Preferences extends React.Component {
} }
onFontInputChange(event) { onFontInputChange(event) {
const INTEGER_REGEX = /^[0-9\b]+$/;
if (event.target.value === '' || INTEGER_REGEX.test(event.target.value)) {
this.setState({ this.setState({
fontSize: event.target.value fontSize: event.target.value
}); });
} }
}
onFontInputSubmit(event) { onFontInputSubmit(event) {
event.preventDefault(); event.preventDefault();
@ -79,6 +83,11 @@ class Preferences extends React.Component {
this.props.setLintWarning(value); this.props.setLintWarning(value);
} }
handleLineNumbers(event) {
const value = event.target.value === 'true';
this.props.setLineNumbers(value);
}
render() { render() {
const beep = new Audio(beepUrl); const beep = new Audio(beepUrl);
@ -151,10 +160,9 @@ class Preferences extends React.Component {
aria-atomic="true" aria-atomic="true"
value={this.state.fontSize} value={this.state.fontSize}
onChange={this.onFontInputChange} onChange={this.onFontInputChange}
type="text"
ref={(element) => { this.fontSizeInput = element; }} ref={(element) => { this.fontSizeInput = element; }}
onClick={() => { onClick={() => { this.fontSizeInput.select(); }}
this.fontSizeInput.select();
}}
/> />
</form> </form>
<button <button
@ -223,6 +231,33 @@ class Preferences extends React.Component {
</div> </div>
</TabPanel> </TabPanel>
<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"> <div className="preference">
<h4 className="preference__title">Lint warning sound</h4> <h4 className="preference__title">Lint warning sound</h4>
<div className="preference__options"> <div className="preference__options">
@ -309,9 +344,11 @@ class Preferences extends React.Component {
Preferences.propTypes = { Preferences.propTypes = {
fontSize: PropTypes.number.isRequired, fontSize: PropTypes.number.isRequired,
lineNumbers: PropTypes.bool.isRequired,
setFontSize: PropTypes.func.isRequired, setFontSize: PropTypes.func.isRequired,
autosave: PropTypes.bool.isRequired, autosave: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired, linewrap: PropTypes.bool.isRequired,
setLineNumbers: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired, setAutosave: PropTypes.func.isRequired,
setLinewrap: PropTypes.func.isRequired, setLinewrap: PropTypes.func.isRequired,
textOutput: PropTypes.bool.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 { Link } from 'react-router';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import classNames from 'classnames'; import classNames from 'classnames';
import slugify from 'slugify';
import * as ProjectActions from '../actions/project'; import * as ProjectActions from '../actions/project';
import * as ProjectsActions from '../actions/projects'; import * as ProjectsActions from '../actions/projects';
import * as ToastActions from '../actions/toast'; import * as ToastActions from '../actions/toast';
@ -137,13 +138,17 @@ class SketchListRowBase extends React.Component {
const { sketch, username } = this.props; const { sketch, username } = this.props;
const { renameOpen, optionsOpen, renameValue } = this.state; const { renameOpen, optionsOpen, renameValue } = this.state;
const userIsOwner = this.props.user.username === this.props.username; 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 ( return (
<tr <tr
className="sketches-table__row" className="sketches-table__row"
key={sketch.id} key={sketch.id}
> >
<th scope="row"> <th scope="row">
<Link to={`/${username}/sketches/${sketch.id}`}> <Link to={url}>
{renameOpen ? '' : sketch.name} {renameOpen ? '' : sketch.name}
</Link> </Link>
{renameOpen {renameOpen
@ -359,10 +364,20 @@ SketchList.propTypes = {
sorting: PropTypes.shape({ sorting: PropTypes.shape({
field: PropTypes.string.isRequired, field: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired direction: PropTypes.string.isRequired
}).isRequired }).isRequired,
project: PropTypes.shape({
id: PropTypes.string,
owner: PropTypes.shape({
id: PropTypes.string
})
})
}; };
SketchList.defaultProps = { SketchList.defaultProps = {
project: {
id: undefined,
owner: undefined
},
username: undefined username: undefined
}; };
@ -371,7 +386,8 @@ function mapStateToProps(state) {
user: state.user, user: state.user,
sketches: getSortedSketches(state), sketches: getSortedSketches(state),
sorting: state.sorting, 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 * as ConsoleActions from '../actions/console';
import { getHTMLFile } from '../reducers/files'; import { getHTMLFile } from '../reducers/files';
import Overlay from '../../App/components/Overlay'; 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 About from '../components/About';
import Feedback from '../components/Feedback'; import Feedback from '../components/Feedback';
@ -203,6 +206,8 @@ class IDEView extends React.Component {
setFontSize={this.props.setFontSize} setFontSize={this.props.setFontSize}
autosave={this.props.preferences.autosave} autosave={this.props.preferences.autosave}
linewrap={this.props.preferences.linewrap} linewrap={this.props.preferences.linewrap}
lineNumbers={this.props.preferences.lineNumbers}
setLineNumbers={this.props.setLineNumbers}
setAutosave={this.props.setAutosave} setAutosave={this.props.setAutosave}
setLinewrap={this.props.setLinewrap} setLinewrap={this.props.setLinewrap}
lintWarning={this.props.preferences.lintWarning} lintWarning={this.props.preferences.lintWarning}
@ -268,6 +273,7 @@ class IDEView extends React.Component {
file={this.props.selectedFile} file={this.props.selectedFile}
updateFileContent={this.props.updateFileContent} updateFileContent={this.props.updateFileContent}
fontSize={this.props.preferences.fontSize} fontSize={this.props.preferences.fontSize}
lineNumbers={this.props.preferences.lineNumbers}
files={this.props.files} files={this.props.files}
editorOptionsVisible={this.props.ide.editorOptionsVisible} editorOptionsVisible={this.props.ide.editorOptionsVisible}
showEditorOptions={this.props.showEditorOptions} showEditorOptions={this.props.showEditorOptions}
@ -363,6 +369,31 @@ class IDEView extends React.Component {
createFolder={this.props.createFolder} 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' && { this.props.location.pathname === '/about' &&
<Overlay <Overlay
previousPath={this.props.ide.previousPath} previousPath={this.props.ide.previousPath}
@ -490,6 +521,7 @@ IDEView.propTypes = {
fontSize: PropTypes.number.isRequired, fontSize: PropTypes.number.isRequired,
autosave: PropTypes.bool.isRequired, autosave: PropTypes.bool.isRequired,
linewrap: PropTypes.bool.isRequired, linewrap: PropTypes.bool.isRequired,
lineNumbers: PropTypes.bool.isRequired,
lintWarning: PropTypes.bool.isRequired, lintWarning: PropTypes.bool.isRequired,
textOutput: PropTypes.bool.isRequired, textOutput: PropTypes.bool.isRequired,
gridOutput: PropTypes.bool.isRequired, gridOutput: PropTypes.bool.isRequired,
@ -500,6 +532,7 @@ IDEView.propTypes = {
closePreferences: PropTypes.func.isRequired, closePreferences: PropTypes.func.isRequired,
setFontSize: PropTypes.func.isRequired, setFontSize: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired, setAutosave: PropTypes.func.isRequired,
setLineNumbers: PropTypes.func.isRequired,
setLinewrap: PropTypes.func.isRequired, setLinewrap: PropTypes.func.isRequired,
setLintWarning: PropTypes.func.isRequired, setLintWarning: PropTypes.func.isRequired,
setTextOutput: PropTypes.func.isRequired, setTextOutput: PropTypes.func.isRequired,

View file

@ -1,9 +1,15 @@
import * as ActionTypes from '../../../constants'; 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) { switch (action.type) {
case ActionTypes.SET_ASSETS: case ActionTypes.SET_ASSETS:
return action.assets; return { list: action.assets, totalSize: action.totalSize };
default: default:
return state; return state;
} }

View file

@ -13,9 +13,9 @@ const defaultHTML =
`<!DOCTYPE html> `<!DOCTYPE html>
<html> <html>
<head> <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.9.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.9.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/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" /> <meta charset="utf-8" />

View file

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

View file

@ -4,6 +4,7 @@ const initialState = {
fontSize: 18, fontSize: 18,
autosave: true, autosave: true,
linewrap: true, linewrap: true,
lineNumbers: true,
lintWarning: false, lintWarning: false,
textOutput: false, textOutput: false,
gridOutput: false, gridOutput: false,
@ -34,6 +35,8 @@ const preferences = (state = initialState, action) => {
return Object.assign({}, state, { theme: action.value }); return Object.assign({}, state, { theme: action.value });
case ActionTypes.SET_AUTOREFRESH: case ActionTypes.SET_AUTOREFRESH:
return Object.assign({}, state, { autorefresh: action.value }); return Object.assign({}, state, { autorefresh: action.value });
case ActionTypes.SET_LINE_NUMBERS:
return Object.assign({}, state, { lineNumbers: action.value });
default: default:
return state; 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 getSketches = state => state.sketches;
const getField = state => state.sorting.field; const getField = state => state.sorting.field;
const getDirection = state => state.sorting.direction; 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( const getSortedSketches = createSelector(
getSketches, getFilteredSketches,
getField, getField,
getDirection, getDirection,
(sketches, field, direction) => { (sketches, field, direction) => {

View file

@ -64,9 +64,8 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
browserHistory.push(previousPath); browserHistory.push(previousPath);
resolve(); resolve();
}) })
.catch((response) => { .catch(error =>
reject({ password: response.data.message, _error: 'Login failed!' }); // eslint-disable-line reject({ password: error.response.data.message, _error: 'Login failed!' })); // eslint-disable-line
});
}); });
} }
@ -84,7 +83,8 @@ export function getUser() {
}); });
}) })
.catch((response) => { .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" id="email"
{...domOnlyProps(email)} {...domOnlyProps(email)}
/> />
{email.touched && email.error && <span className="form-error">{email.error}</span>}
</p> </p>
<input <input
type="submit" type="submit"

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
.asset-table-container { .asset-table-container {
// flex: 1 1 0%; // flex: 1 1 0%;
overflow-y: scroll; overflow-y: auto;
max-width: 100%; max-width: 100%;
min-height: #{400 / $base-font-size}rem; min-height: #{400 / $base-font-size}rem;
} }
@ -54,3 +54,7 @@
font-size: #{16 / $base-font-size}rem; font-size: #{16 / $base-font-size}rem;
padding: #{42 / $base-font-size}rem 0; 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; 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 { .nav__dropdown {
@extend %dropdown-open-left; @extend %dropdown-open-left;
display: none; display: none;

View file

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

View file

@ -5,7 +5,8 @@
min-height: #{460 / $base-font-size}rem; min-height: #{460 / $base-font-size}rem;
max-height: 100%; max-height: 100%;
z-index: 9999; 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; display: flex;
flex-direction: column; flex-direction: column;
outline: none; outline: none;
@ -47,7 +48,7 @@
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
@include themify() { @include themify() {
color: getThemifyVariable('heading-text-color'); color: getThemifyVariable("heading-text-color");
} }
} }
@ -60,13 +61,22 @@
text-align: left; 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 { .preference {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
padding-bottom: #{12 / $base-font-size}rem; padding-bottom: #{12 / $base-font-size}rem;
& + & { & + & {
@include themify() { @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-top: #{13 / $base-font-size}rem;
margin-bottom: #{7 / $base-font-size}rem; margin-bottom: #{7 / $base-font-size}rem;
@include themify() { @include themify() {
color: getThemifyVariable('heading-text-color'); color: getThemifyVariable("heading-text-color");
} }
} }
.preference__subtitle { .preference__subtitle {
@include themify() { @include themify() {
color: getThemifyVariable('inactive-text-color'); color: getThemifyVariable("inactive-text-color");
} }
width: 100%; width: 100%;
margin-bottom: #{10 / $base-font-size}rem; margin-bottom: #{10 / $base-font-size}rem;
@ -92,9 +102,10 @@
.preference__value { .preference__value {
@include themify() { @include themify() {
border: #{1 / $base-font-size}rem solid getThemifyVariable('button-border-color'); border: #{1 / $base-font-size}rem solid
background-color: getThemifyVariable('button-background-color'); getThemifyVariable("button-border-color");
color: getThemifyVariable('input-text-color'); background-color: getThemifyVariable("button-background-color");
color: getThemifyVariable("input-text-color");
} }
text-align: center; text-align: center;
border-radius: 0%; border-radius: 0%;
@ -109,17 +120,60 @@
.preference__label { .preference__label {
@include themify() { @include themify() {
color: getThemifyVariable('inactive-text-color'); color: getThemifyVariable("inactive-text-color");
&:hover { &:hover {
color: getThemifyVariable('primary-text-color'); color: getThemifyVariable("primary-text-color");
} }
} }
margin: #{-15 / $base-font-size}rem 0 0 #{-5 / $base-font-size}rem; margin: #{-15 / $base-font-size}rem 0 0 #{-5 / $base-font-size}rem;
font-size: #{9 / $base-font-size}rem; font-size: #{9 / $base-font-size}rem;
width: #{44 / $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 { .preference__vertical-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -135,8 +189,8 @@
margin-top: #{40 / $base-font-size}rem; margin-top: #{40 / $base-font-size}rem;
margin-bottom: #{10 / $base-font-size}rem; margin-bottom: #{10 / $base-font-size}rem;
@include themify() { @include themify() {
color: getThemifyVariable('inactive-text-color'); color: getThemifyVariable("inactive-text-color");
border-bottom: 1px dashed getThemifyVariable('button-border-color'); border-bottom: 1px dashed getThemifyVariable("button-border-color");
} }
} }
@ -144,6 +198,12 @@
@extend %hidden-element; @extend %hidden-element;
} }
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.preference__option { .preference__option {
@include themify() { @include themify() {
@extend %preference-option; @extend %preference-option;
@ -162,7 +222,7 @@
@include themify() { @include themify() {
@extend %preference-option; @extend %preference-option;
&:hover { &:hover {
color: getThemifyVariable('button-background-hover-color'); color: getThemifyVariable("button-background-hover-color");
} }
} }
margin-left: #{30 / $base-font-size}rem; margin-left: #{30 / $base-font-size}rem;
@ -182,7 +242,7 @@
.preference__radio-button:checked + .preference__option { .preference__radio-button:checked + .preference__option {
@include themify() { @include themify() {
//for some reason this won't work for getThemifyVariable //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 { .sketches-table-container {
overflow-y: scroll; overflow-y: auto;
max-width: 100%; max-width: 100%;
min-height: #{400 / $base-font-size}rem; min-height: #{400 / $base-font-size}rem;
} }
.sketches-table { .sketches-table {
width: 100%; width: 100%;
max-height: 100%; max-height: 100%;
border-spacing: 0; border-spacing: 0;
& .sketch-list__dropdown-column { & .sketch-list__dropdown-column {
@ -69,7 +70,6 @@
} }
} }
.sketches-table thead { .sketches-table thead {
font-size: #{12 / $base-font-size}rem; font-size: #{12 / $base-font-size}rem;
@include themify() { @include themify() {

View file

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

View file

@ -73,6 +73,8 @@ export default function(CodeMirror) {
CodeMirror.on(searchField, "keyup", function (e) { CodeMirror.on(searchField, "keyup", function (e) {
if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search
startSearch(cm, getSearchState(cm), searchField.value); 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> </button>
</div> </div>
<div class="CodeMirror-search-nav"> <div class="CodeMirror-search-nav">
<button class="CodeMirror-search-results"></button>
<button <button
title="Previous" title="Previous"
aria-label="Previous" aria-label="Previous"
@ -292,6 +295,9 @@ export default function(CodeMirror) {
if (state.annotate) { state.annotate.clear(); state.annotate = null; } if (state.annotate) { state.annotate.clear(); state.annotate = null; }
state.annotate = cm.showMatchesOnScrollbar(state.query, state.caseInsensitive); state.annotate = cm.showMatchesOnScrollbar(state.query, state.caseInsensitive);
} }
if (originalQuery) {
return findNext(cm, false);
}
} }
function doSearch(cm, rev, persistent, immediate, ignoreQuery) { 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); var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) { if (!cursor.find(rev)) {
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); 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.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60); cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60);
state.posFrom = cursor.from(); state.posTo = cursor.to(); 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()) if (callback) callback(cursor.from(), cursor.to())
});} });}

View file

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

View file

@ -15,6 +15,9 @@ export const domOnlyProps = ({
...domProps }) => domProps; ...domProps }) => domProps;
/* eslint-enable */ /* 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) { function validateNameEmail(formProps, errors) {
if (!formProps.username) { if (!formProps.username) {
errors.username = 'Please enter a username.'; errors.username = 'Please enter a username.';
@ -28,7 +31,7 @@ function validateNameEmail(formProps, errors) {
errors.email = 'Please enter an email.'; errors.email = 'Please enter an email.';
} else if ( } else if (
// eslint-disable-next-line max-len // 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.'; errors.email = 'Please enter a valid email address.';
} }
} }
@ -79,3 +82,14 @@ export function validateSignup(formProps) {
return errors; 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') { if (process.env.NODE_ENV === 'production') {
process.env.webpackAssets = JSON.stringify(require('./dist/static/manifest.json')); 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'); require('./dist/server.bundle.js');
} else { } else {
let parsed = require('dotenv').config(); let parsed = require('dotenv').config();

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

View file

@ -126,6 +126,7 @@ export function listObjectsInS3ForUser(req, res) {
.on('end', () => { .on('end', () => {
const projectAssets = []; const projectAssets = [];
getProjectsForUserId(userId).then((projects) => { getProjectsForUserId(userId).then((projects) => {
let totalSize = 0;
assets.forEach((asset) => { assets.forEach((asset) => {
const name = asset.key.split('/').pop(); const name = asset.key.split('/').pop();
const foundAsset = { const foundAsset = {
@ -134,6 +135,7 @@ export function listObjectsInS3ForUser(req, res) {
size: asset.size, size: asset.size,
url: `${process.env.S3_BUCKET_URL_BASE}${asset.key}` url: `${process.env.S3_BUCKET_URL_BASE}${asset.key}`
}; };
totalSize += asset.size;
projects.some((project) => { projects.some((project) => {
let found = false; let found = false;
project.files.some((file) => { project.files.some((file) => {
@ -152,7 +154,7 @@ export function listObjectsInS3ForUser(req, res) {
}); });
projectAssets.push(foundAsset); 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 passport.authenticate('local', (err, user) => { // eslint-disable-line consistent-return
if (err) { return next(err); } if (err) { return next(err); }
if (!user) { 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) => { req.logIn(user, (innerErr) => {

View file

@ -1,3 +1,3 @@
require('@babel/register'); require('@babel/register');
require('@babel/polyfill'); 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] }, apiKeys: { type: [apiKeySchema] },
preferences: { preferences: {
fontSize: { type: Number, default: 18 }, fontSize: { type: Number, default: 18 },
lineNumbers: { type: Boolean, default: true },
indentationAmount: { type: Number, default: 2 }, indentationAmount: { type: Number, default: 2 },
isTabIndent: { type: Boolean, default: false }, isTabIndent: { type: Boolean, default: false },
autosave: { type: Boolean, default: true }, autosave: { type: Boolean, default: true },

View file

@ -11,9 +11,9 @@ const defaultHTML =
`<!DOCTYPE html> `<!DOCTYPE html>
<html> <html>
<head> <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.9.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.9.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/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css"> <link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
@ -61,7 +61,7 @@ function getCategories() {
for (let j = 1; j < metadata.name.split('_').length; j += 1) { for (let j = 1; j < metadata.name.split('_').length; j += 1) {
category += `${metadata.name.split('_')[j]} `; category += `${metadata.name.split('_')[j]} `;
} }
categories.push({ url: metadata.url, name: category }); categories.push({ url: metadata.url, name: category.trim() });
}); });
return categories; return categories;
@ -119,7 +119,7 @@ function getSketchContent(projectsInAllCategories) {
const splitedRes = `${res.split('*/')[1].split('</html>')[i]}</html>\n`; const splitedRes = `${res.split('*/')[1].split('</html>')[i]}</html>\n`;
project.sketchContent = splitedRes.replace( project.sketchContent = splitedRes.replace(
'p5.js', '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 { } else {

View file

@ -52,7 +52,7 @@ const corsOriginsWhitelist = [
// Run Webpack dev server in development mode // Run Webpack dev server in development mode
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
const compiler = webpack(config); 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)); app.use(webpackHotMiddleware(compiler));
corsOriginsWhitelist.push(/localhost/); corsOriginsWhitelist.push(/localhost/);

View file

@ -1,6 +1,5 @@
export function renderIndex() { export function renderIndex() {
const assetsManifest = process.env.webpackAssets && JSON.parse(process.env.webpackAssets); const assetsManifest = process.env.webpackAssets && JSON.parse(process.env.webpackAssets);
const chunkManifest = process.env.webpackChunkAssets && JSON.parse(process.env.webpackChunkAssets);
return ` return `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -37,13 +36,6 @@ export function renderIndex() {
<body> <body>
<div id="root" class="root-app"> <div id="root" class="root-app">
</div> </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 src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/app.js']}` : '/app.js'}'></script>
<script> <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (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(); 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', devtool: 'cheap-module-eval-source-map',
entry: { entry: {
app: [ app: [
@ -15,14 +18,13 @@ module.exports = [{
'react-hot-loader/patch', 'react-hot-loader/patch',
'./client/index.jsx', './client/index.jsx',
], ],
vendor: [ previewScripts: [
'react', path.resolve(__dirname, '../client/utils/previewEntry.js')
'react-dom'
] ]
}, },
output: { output: {
path: `${__dirname}`, path: `${__dirname}`,
filename: 'app.js', filename: '[name].js',
publicPath: '/' publicPath: '/'
}, },
resolve: { resolve: {
@ -34,11 +36,6 @@ module.exports = [{
}, },
plugins: [ plugins: [
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
filename: 'vendor.js',
}),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify('development') 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 = [{ module.exports = [{
entry: path.resolve(__dirname, '../server/scripts/fetch-examples.js'), entry: path.resolve(__dirname, '../server/scripts/fetch-examples.js'),
mode: 'production',
output: { output: {
path: path.resolve(__dirname, '../dist/'), path: path.resolve(__dirname, '../dist/'),
filename: 'fetch-examples.bundle.js' filename: 'fetch-examples.bundle.js'
@ -22,7 +22,7 @@ module.exports = [{
}, },
module: { module: {
loaders: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,
@ -36,7 +36,7 @@ module.exports = [{
}, },
{ {
entry: path.resolve(__dirname, '../server/scripts/fetch-examples-gg.js'), entry: path.resolve(__dirname, '../server/scripts/fetch-examples-gg.js'),
mode: 'production',
output: { output: {
path: path.resolve(__dirname, '../dist/'), path: path.resolve(__dirname, '../dist/'),
filename: 'fetch-examples-gg.bundle.js' filename: 'fetch-examples-gg.bundle.js'
@ -55,7 +55,7 @@ module.exports = [{
}, },
module: { module: {
loaders: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,
@ -69,7 +69,7 @@ module.exports = [{
}, },
{ {
entry: path.resolve(__dirname, '../server/scripts/fetch-examples-ml5.js'), entry: path.resolve(__dirname, '../server/scripts/fetch-examples-ml5.js'),
mode: 'production',
output: { output: {
path: path.resolve(__dirname, '../dist/'), path: path.resolve(__dirname, '../dist/'),
filename: 'fetch-examples-ml5.bundle.js' filename: 'fetch-examples-ml5.bundle.js'
@ -88,7 +88,7 @@ module.exports = [{
}, },
module: { module: {
loaders: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,

View file

@ -1,8 +1,9 @@
const webpack = require('webpack'); const webpack = require('webpack');
const path = require('path'); 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 ManifestPlugin = require('webpack-manifest-plugin');
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
const cssnext = require('postcss-cssnext'); const cssnext = require('postcss-cssnext');
const postcssFocus = require('postcss-focus'); const postcssFocus = require('postcss-focus');
const postcssReporter = require('postcss-reporter'); const postcssReporter = require('postcss-reporter');
@ -13,36 +14,18 @@ if (process.env.NODE_ENV === "development") {
module.exports = [{ module.exports = [{
devtool: 'source-map', devtool: 'source-map',
mode: 'production',
entry: { entry: {
app: [ app: [
'@babel/polyfill', '@babel/polyfill',
'core-js/modules/es6.promise', 'core-js/modules/es6.promise',
'core-js/modules/es6.array.iterator', 'core-js/modules/es6.array.iterator',
path.resolve(__dirname, '../client/index.jsx') 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: { output: {
path: path.resolve(__dirname, '../dist/static'), path: path.resolve(__dirname, '../dist/static'),
filename: '[name].[chunkhash].js', filename: '[name].[hash].js',
publicPath: '/' publicPath: '/'
}, },
@ -53,16 +36,44 @@ module.exports = [{
'node_modules', 'node_modules',
] ]
}, },
module: { module: {
rules: [ rules: [{
{
test: /main\.scss$/, test: /main\.scss$/,
exclude: /node_modules/, exclude: /node_modules/,
loader: ExtractTextPlugin.extract({ use: [
fallback: 'style-loader', MiniCssExtractPlugin.loader,
use: 'css-loader!sass-loader!postcss-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?$/, test: /\.jsx?$/,
@ -92,58 +103,28 @@ module.exports = [{
use: { use: {
loader: 'sass-extract-loader', loader: 'sass-extract-loader',
options: { options: {
plugins: [{ plugin: 'sass-extract-js', options: { camelCase: false } }] plugins: [{
plugin: 'sass-extract-js',
options: {
camelCase: false
}
}]
} }
} }
} }
] ]
}, },
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
plugins: [ 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({ new ManifestPlugin({
basePath: '/', basePath: '/',
}), }),
new ChunkManifestPlugin({ new MiniCssExtractPlugin({
filename: 'chunk-manifest.json', filename: 'app.[hash].css',
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
}) })
] ]
}
})
],
}, },
{ {
entry: { entry: {
@ -152,6 +133,7 @@ module.exports = [{
] ]
}, },
target: 'web', target: 'web',
mode: 'production',
output: { output: {
path: path.resolve(__dirname, '../dist/static'), path: path.resolve(__dirname, '../dist/static'),
filename: 'previewScripts.js', filename: 'previewScripts.js',
@ -165,7 +147,7 @@ module.exports = [{
], ],
}, },
module: { module: {
loaders: [ rules: [
{ {
test: /\.jsx?$/, test: /\.jsx?$/,
exclude: /node_modules/, 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', target: 'node',
mode: 'production',
node: { node: {
__filename: true, __filename: true,
@ -28,7 +29,7 @@ module.exports = {
}, },
module: { module: {
loaders: [ rules: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,