merge master
This commit is contained in:
commit
7f2529a973
53 changed files with 4560 additions and 1778 deletions
5
.babelrc
5
.babelrc
|
@ -45,6 +45,11 @@
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
"@babel/preset-react"
|
"@babel/preset-react"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"plugins": [
|
||||||
|
"react-hot-loader/babel"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
|
|
@ -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.js–it 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 editor’s 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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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';
|
||||||
|
|
13
client/images/magnifyingglass.svg
Normal file
13
client/images/magnifyingglass.svg
Normal 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 |
|
@ -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 />,
|
||||||
|
|
|
@ -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());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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('');
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
91
client/modules/IDE/components/Searchbar.jsx
Normal file
91
client/modules/IDE/components/Searchbar.jsx
Normal 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);
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
14
client/modules/IDE/reducers/search.js
Normal file
14
client/modules/IDE/reducers/search.js
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -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) => {
|
||||||
|
|
|
@ -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));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,11 +48,11 @@
|
||||||
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preferences__title{
|
.preferences__title {
|
||||||
width: #{90 / $base-font-size}rem;
|
width: #{90 / $base-font-size}rem;
|
||||||
height: #{25 / $base-font-size}rem;
|
height: #{25 / $base-font-size}rem;
|
||||||
font-family: Montserrat;
|
font-family: Montserrat;
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
61
client/styles/components/_searchbar.scss
Normal file
61
client/styles/components/_searchbar.scss
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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())
|
||||||
});}
|
});}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
1
index.js
1
index.js
|
@ -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
5329
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
@ -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"
|
||||||
|
|
|
@ -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 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
require('@babel/register');
|
require('@babel/register');
|
||||||
require('@babel/polyfill');
|
require('@babel/polyfill');
|
||||||
require('./moveBucket');
|
require('./truncate');
|
||||||
|
|
36
server/migrations/truncate.js
Normal file
36
server/migrations/truncate.js
Normal 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);
|
||||||
|
});
|
|
@ -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 },
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -114,12 +114,12 @@ function getSketchContent(projectsInAllCategories) {
|
||||||
|
|
||||||
return rp(options).then((res) => {
|
return rp(options).then((res) => {
|
||||||
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
|
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) {
|
for (let i = 0; i < 4; i += 1) {
|
||||||
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 {
|
||||||
|
@ -153,7 +153,7 @@ function createProjectsInP5user(projectsInAllCategories) {
|
||||||
const c = objectID().toHexString();
|
const c = objectID().toHexString();
|
||||||
const r = objectID().toHexString();
|
const r = objectID().toHexString();
|
||||||
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
|
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
|
||||||
if (noNumberprojectName === 'Instance Mode : Instance Container ') {
|
if (noNumberprojectName === 'Instance Mode: Instance Container ') {
|
||||||
newProject = new Project({
|
newProject = new Project({
|
||||||
name: project.projectName,
|
name: project.projectName,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
|
@ -167,7 +167,7 @@ function createProjectsInP5user(projectsInAllCategories) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sketch.js',
|
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,
|
||||||
_id: a,
|
_id: a,
|
||||||
fileType: 'file',
|
fileType: '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/);
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
|
@ -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/,
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
]
|
|
||||||
}];
|
}];
|
||||||
|
|
|
@ -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/,
|
||||||
|
|
Loading…
Reference in a new issue