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-react"
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"plugins": [
|
||||
"react-hot-loader/babel"
|
||||
]
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# [p5.js Web Editor](https://editor.p5js.org)
|
||||
|
||||
Hello! The p5.js Web Editor is an in-browser editor for creative coding, specifically for writing [p5.js](https://p5js.org/) sketches. p5.js, a separate [open source project](https://github.com/processing/p5.js), is a JavaScript library with the goal of making coding accessible for artists, designers, educators, and beginners. The web editor shares the same spirit as p5.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.
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Link } from 'react-router';
|
|||
import InlineSVG from 'react-inlinesvg';
|
||||
import classNames from 'classnames';
|
||||
import * as IDEActions from '../modules/IDE/actions/ide';
|
||||
import * as toastActions from '../modules/IDE/actions/toast';
|
||||
import * as projectActions from '../modules/IDE/actions/project';
|
||||
import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
|
||||
import { logoutUser } from '../modules/User/actions';
|
||||
|
@ -93,8 +94,12 @@ class Nav extends React.PureComponent {
|
|||
|
||||
handleNew() {
|
||||
if (!this.props.unsavedChanges) {
|
||||
this.props.showToast(1500);
|
||||
this.props.setToastText('Opened new sketch.');
|
||||
this.props.newProject();
|
||||
} else if (this.props.warnIfUnsavedChanges()) {
|
||||
this.props.showToast(1500);
|
||||
this.props.setToastText('Opened new sketch.');
|
||||
this.props.newProject();
|
||||
}
|
||||
this.setDropdown('none');
|
||||
|
@ -682,6 +687,8 @@ class Nav extends React.PureComponent {
|
|||
|
||||
Nav.propTypes = {
|
||||
newProject: PropTypes.func.isRequired,
|
||||
showToast: PropTypes.func.isRequired,
|
||||
setToastText: PropTypes.func.isRequired,
|
||||
saveProject: PropTypes.func.isRequired,
|
||||
autosaveProject: PropTypes.func.isRequired,
|
||||
exportProjectAsZip: PropTypes.func.isRequired,
|
||||
|
@ -738,6 +745,7 @@ function mapStateToProps(state) {
|
|||
const mapDispatchToProps = {
|
||||
...IDEActions,
|
||||
...projectActions,
|
||||
...toastActions,
|
||||
logoutUser,
|
||||
setAllAccessibleOutput
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ export const STOP_ACCESSIBLE_OUTPUT = 'STOP_ACCESSIBLE_OUTPUT';
|
|||
export const OPEN_PREFERENCES = 'OPEN_PREFERENCES';
|
||||
export const CLOSE_PREFERENCES = 'CLOSE_PREFERENCES';
|
||||
export const SET_FONT_SIZE = 'SET_FONT_SIZE';
|
||||
export const SET_LINE_NUMBERS = 'SET_LINE_NUMBERS';
|
||||
|
||||
export const AUTH_USER = 'AUTH_USER';
|
||||
export const UNAUTH_USER = 'UNAUTH_USER';
|
||||
|
@ -124,6 +125,9 @@ export const SET_ASSETS = 'SET_ASSETS';
|
|||
|
||||
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
|
||||
export const SET_SORTING = 'SET_SORTING';
|
||||
export const SET_SORT_PARAMS = 'SET_SORT_PARAMS';
|
||||
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
|
||||
export const CLOSE_SKETCHLIST_MODAL = 'CLOSE_SKETCHLIST_MODAL';
|
||||
|
||||
export const START_LOADING = 'START_LOADING';
|
||||
export const STOP_LOADING = 'STOP_LOADING';
|
||||
|
|
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 { render } from 'react-dom';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router, browserHistory } from 'react-router';
|
||||
import configureStore from './store';
|
||||
|
@ -22,7 +22,7 @@ const App = () => (
|
|||
</Provider>
|
||||
);
|
||||
|
||||
const HotApp = hot(module)(App);
|
||||
const HotApp = hot(App);
|
||||
|
||||
render(
|
||||
<HotApp />,
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
import axios from 'axios';
|
||||
|
||||
import * as ActionTypes from '../../../constants';
|
||||
import { startLoader, stopLoader } from './loader';
|
||||
|
||||
const __process = (typeof global !== 'undefined' ? global : window).process;
|
||||
const ROOT_URL = __process.env.API_URL;
|
||||
|
||||
function setAssets(assets) {
|
||||
function setAssets(assets, totalSize) {
|
||||
return {
|
||||
type: ActionTypes.SET_ASSETS,
|
||||
assets
|
||||
assets,
|
||||
totalSize
|
||||
};
|
||||
}
|
||||
|
||||
export function getAssets() {
|
||||
return (dispatch, getState) => {
|
||||
return (dispatch) => {
|
||||
dispatch(startLoader());
|
||||
axios.get(`${ROOT_URL}/S3/objects`, { withCredentials: true })
|
||||
.then((response) => {
|
||||
dispatch(setAssets(response.data.assets));
|
||||
dispatch(setAssets(response.data.assets, response.data.totalSize));
|
||||
dispatch(stopLoader());
|
||||
})
|
||||
.catch(response => dispatch({
|
||||
type: ActionTypes.ERROR
|
||||
}));
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
type: ActionTypes.ERROR
|
||||
});
|
||||
dispatch(stopLoader());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,24 @@ export function setFontSize(value) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setLineNumbers(value) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.SET_LINE_NUMBERS,
|
||||
value
|
||||
});
|
||||
const state = getState();
|
||||
if (state.user.authenticated) {
|
||||
const formParams = {
|
||||
preferences: {
|
||||
lineNumbers: value
|
||||
}
|
||||
};
|
||||
updatePreferences(formParams, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setAutosave(value) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
|
|
@ -25,3 +25,14 @@ export function toggleDirectionForField(field) {
|
|||
field
|
||||
};
|
||||
}
|
||||
|
||||
export function setSearchTerm(searchTerm) {
|
||||
return {
|
||||
type: ActionTypes.SET_SEARCH_TERM,
|
||||
query: searchTerm
|
||||
};
|
||||
}
|
||||
|
||||
export function resetSearchTerm() {
|
||||
return setSearchTerm('');
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ import { connect } from 'react-redux';
|
|||
import { bindActionCreators } from 'redux';
|
||||
import { Link } from 'react-router';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
import Loader from '../../App/components/loader';
|
||||
import * as AssetActions from '../actions/assets';
|
||||
|
||||
|
||||
class AssetList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -23,17 +22,37 @@ class AssetList extends React.Component {
|
|||
return `p5.js Web Editor | ${this.props.username}'s assets`;
|
||||
}
|
||||
|
||||
hasAssets() {
|
||||
return !this.props.loading && this.props.assetList.length > 0;
|
||||
}
|
||||
|
||||
renderLoader() {
|
||||
if (this.props.loading) return <Loader />;
|
||||
return null;
|
||||
}
|
||||
|
||||
renderEmptyTable() {
|
||||
if (!this.props.loading && this.props.assetList.length === 0) {
|
||||
return (<p className="asset-table__empty">No uploaded assets.</p>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
|
||||
const { assetList, totalSize } = this.props;
|
||||
return (
|
||||
<div className="asset-table-container">
|
||||
{/* Eventually, this copy should be Total / 250 MB Used */}
|
||||
{this.hasAssets() &&
|
||||
<p className="asset-table__total">{`${prettyBytes(totalSize)} Total`}</p>
|
||||
}
|
||||
<Helmet>
|
||||
<title>{this.getAssetsTitle()}</title>
|
||||
</Helmet>
|
||||
{this.props.assets.length === 0 &&
|
||||
<p className="asset-table__empty">No uploaded assets.</p>
|
||||
}
|
||||
{this.props.assets.length > 0 &&
|
||||
{this.renderLoader()}
|
||||
{this.renderEmptyTable()}
|
||||
{this.hasAssets() &&
|
||||
<table className="asset-table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -44,7 +63,7 @@ class AssetList extends React.Component {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.assets.map(asset =>
|
||||
{assetList.map(asset =>
|
||||
(
|
||||
<tr className="asset-table__row" key={asset.key}>
|
||||
<td>{asset.name}</td>
|
||||
|
@ -65,20 +84,24 @@ AssetList.propTypes = {
|
|||
username: PropTypes.string
|
||||
}).isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
assets: PropTypes.arrayOf(PropTypes.shape({
|
||||
assetList: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
sketchName: PropTypes.string.isRequired,
|
||||
sketchId: PropTypes.string.isRequired
|
||||
})).isRequired,
|
||||
totalSize: PropTypes.number.isRequired,
|
||||
getAssets: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user,
|
||||
assets: state.assets
|
||||
assetList: state.assets.list,
|
||||
totalSize: state.assets.totalSize,
|
||||
loading: state.loading
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class Editor extends React.Component {
|
|||
this.widgets = [];
|
||||
this._cm = CodeMirror(this.codemirrorContainer, { // eslint-disable-line
|
||||
theme: `p5-${this.props.theme}`,
|
||||
lineNumbers: true,
|
||||
lineNumbers: this.props.lineNumbers,
|
||||
styleActiveLine: true,
|
||||
inputStyle: 'contenteditable',
|
||||
lineWrapping: this.props.linewrap,
|
||||
|
@ -181,6 +181,9 @@ class Editor extends React.Component {
|
|||
if (this.props.theme !== prevProps.theme) {
|
||||
this._cm.setOption('theme', `p5-${this.props.theme}`);
|
||||
}
|
||||
if (this.props.lineNumbers !== prevProps.lineNumbers) {
|
||||
this._cm.setOption('lineNumbers', this.props.lineNumbers);
|
||||
}
|
||||
|
||||
if (prevProps.consoleEvents !== this.props.consoleEvents) {
|
||||
this.props.showRuntimeErrorWarning();
|
||||
|
@ -188,7 +191,7 @@ class Editor extends React.Component {
|
|||
for (let i = 0; i < this._cm.lineCount(); i += 1) {
|
||||
this._cm.removeLineClass(i, 'background', 'line-runtime-error');
|
||||
}
|
||||
if (this.props.runtimeErrorWarningVisible && this._cm.getDoc().modeOption === 'javascript') {
|
||||
if (this.props.runtimeErrorWarningVisible) {
|
||||
this.props.consoleEvents.forEach((consoleEvent) => {
|
||||
if (consoleEvent.method === 'error') {
|
||||
if (consoleEvent.data &&
|
||||
|
@ -197,7 +200,11 @@ class Editor extends React.Component {
|
|||
consoleEvent.data[0].indexOf(')') > -1) {
|
||||
const n = consoleEvent.data[0].replace(')', '').split(' ');
|
||||
const lineNumber = parseInt(n[n.length - 1], 10) - 1;
|
||||
if (!Number.isNaN(lineNumber)) {
|
||||
const { source } = consoleEvent;
|
||||
const fileName = this.props.file.name;
|
||||
const errorFromJavaScriptFile = (`${source}.js` === fileName);
|
||||
const errorFromIndexHTML = ((source === fileName) && (fileName === 'index.html'));
|
||||
if (!Number.isNaN(lineNumber) && (errorFromJavaScriptFile || errorFromIndexHTML)) {
|
||||
this._cm.addLineClass(lineNumber, 'background', 'line-runtime-error');
|
||||
}
|
||||
}
|
||||
|
@ -338,6 +345,7 @@ class Editor extends React.Component {
|
|||
}
|
||||
|
||||
Editor.propTypes = {
|
||||
lineNumbers: PropTypes.bool.isRequired,
|
||||
lintWarning: PropTypes.bool.isRequired,
|
||||
linewrap: PropTypes.bool.isRequired,
|
||||
lintMessages: PropTypes.arrayOf(PropTypes.shape({
|
||||
|
@ -359,7 +367,7 @@ Editor.propTypes = {
|
|||
content: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
fileType: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired
|
||||
url: PropTypes.string
|
||||
}).isRequired,
|
||||
editorOptionsVisible: PropTypes.bool.isRequired,
|
||||
showEditorOptions: PropTypes.func.isRequired,
|
||||
|
|
|
@ -14,7 +14,7 @@ class NewFolderForm extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
fields: { name }, handleSubmit, submitting, pristine
|
||||
fields: { name }, handleSubmit
|
||||
} = this.props;
|
||||
return (
|
||||
<form
|
||||
|
@ -34,7 +34,7 @@ class NewFolderForm extends React.Component {
|
|||
ref={(element) => { this.fileName = element; }}
|
||||
{...domOnlyProps(name)}
|
||||
/>
|
||||
<input type="submit" value="Add Folder" disabled={submitting || pristine} aria-label="add folder" />
|
||||
<input type="submit" value="Add Folder" aria-label="add folder" />
|
||||
{name.touched && name.error && <span className="form-error">{name.error}</span>}
|
||||
</form>
|
||||
);
|
||||
|
|
|
@ -17,6 +17,7 @@ class Preferences extends React.Component {
|
|||
this.handleUpdateAutosave = this.handleUpdateAutosave.bind(this);
|
||||
this.handleUpdateLinewrap = this.handleUpdateLinewrap.bind(this);
|
||||
this.handleLintWarning = this.handleLintWarning.bind(this);
|
||||
this.handleLineNumbers = this.handleLineNumbers.bind(this);
|
||||
this.onFontInputChange = this.onFontInputChange.bind(this);
|
||||
this.onFontInputSubmit = this.onFontInputSubmit.bind(this);
|
||||
this.increaseFontSize = this.increaseFontSize.bind(this);
|
||||
|
@ -29,9 +30,12 @@ class Preferences extends React.Component {
|
|||
}
|
||||
|
||||
onFontInputChange(event) {
|
||||
this.setState({
|
||||
fontSize: event.target.value
|
||||
});
|
||||
const INTEGER_REGEX = /^[0-9\b]+$/;
|
||||
if (event.target.value === '' || INTEGER_REGEX.test(event.target.value)) {
|
||||
this.setState({
|
||||
fontSize: event.target.value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onFontInputSubmit(event) {
|
||||
|
@ -79,6 +83,11 @@ class Preferences extends React.Component {
|
|||
this.props.setLintWarning(value);
|
||||
}
|
||||
|
||||
handleLineNumbers(event) {
|
||||
const value = event.target.value === 'true';
|
||||
this.props.setLineNumbers(value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const beep = new Audio(beepUrl);
|
||||
|
||||
|
@ -151,10 +160,9 @@ class Preferences extends React.Component {
|
|||
aria-atomic="true"
|
||||
value={this.state.fontSize}
|
||||
onChange={this.onFontInputChange}
|
||||
type="text"
|
||||
ref={(element) => { this.fontSizeInput = element; }}
|
||||
onClick={() => {
|
||||
this.fontSizeInput.select();
|
||||
}}
|
||||
onClick={() => { this.fontSizeInput.select(); }}
|
||||
/>
|
||||
</form>
|
||||
<button
|
||||
|
@ -223,6 +231,33 @@ class Preferences extends React.Component {
|
|||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<div className="preference">
|
||||
<h4 className="preference__title">Line numbers</h4>
|
||||
<div className="preference__options">
|
||||
<input
|
||||
type="radio"
|
||||
onChange={() => this.props.setLineNumbers(true)}
|
||||
aria-label="line numbers on"
|
||||
name="line numbers"
|
||||
id="line-numbers-on"
|
||||
className="preference__radio-button"
|
||||
value="On"
|
||||
checked={this.props.lineNumbers}
|
||||
/>
|
||||
<label htmlFor="line-numbers-on" className="preference__option">On</label>
|
||||
<input
|
||||
type="radio"
|
||||
onChange={() => this.props.setLineNumbers(false)}
|
||||
aria-label="line numbers off"
|
||||
name="line numbers"
|
||||
id="line-numbers-off"
|
||||
className="preference__radio-button"
|
||||
value="Off"
|
||||
checked={!this.props.lineNumbers}
|
||||
/>
|
||||
<label htmlFor="line-numbers-off" className="preference__option">Off</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="preference">
|
||||
<h4 className="preference__title">Lint warning sound</h4>
|
||||
<div className="preference__options">
|
||||
|
@ -309,9 +344,11 @@ class Preferences extends React.Component {
|
|||
|
||||
Preferences.propTypes = {
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
lineNumbers: PropTypes.bool.isRequired,
|
||||
setFontSize: PropTypes.func.isRequired,
|
||||
autosave: PropTypes.bool.isRequired,
|
||||
linewrap: PropTypes.bool.isRequired,
|
||||
setLineNumbers: PropTypes.func.isRequired,
|
||||
setAutosave: PropTypes.func.isRequired,
|
||||
setLinewrap: PropTypes.func.isRequired,
|
||||
textOutput: PropTypes.bool.isRequired,
|
||||
|
|
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 { bindActionCreators } from 'redux';
|
||||
import classNames from 'classnames';
|
||||
import slugify from 'slugify';
|
||||
import * as ProjectActions from '../actions/project';
|
||||
import * as ProjectsActions from '../actions/projects';
|
||||
import * as ToastActions from '../actions/toast';
|
||||
|
@ -137,13 +138,17 @@ class SketchListRowBase extends React.Component {
|
|||
const { sketch, username } = this.props;
|
||||
const { renameOpen, optionsOpen, renameValue } = this.state;
|
||||
const userIsOwner = this.props.user.username === this.props.username;
|
||||
let url = `/${username}/sketches/${sketch.id}`;
|
||||
if (username === 'p5') {
|
||||
url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
|
||||
}
|
||||
return (
|
||||
<tr
|
||||
className="sketches-table__row"
|
||||
key={sketch.id}
|
||||
>
|
||||
<th scope="row">
|
||||
<Link to={`/${username}/sketches/${sketch.id}`}>
|
||||
<Link to={url}>
|
||||
{renameOpen ? '' : sketch.name}
|
||||
</Link>
|
||||
{renameOpen
|
||||
|
@ -359,10 +364,20 @@ SketchList.propTypes = {
|
|||
sorting: PropTypes.shape({
|
||||
field: PropTypes.string.isRequired,
|
||||
direction: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
project: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
owner: PropTypes.shape({
|
||||
id: PropTypes.string
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
SketchList.defaultProps = {
|
||||
project: {
|
||||
id: undefined,
|
||||
owner: undefined
|
||||
},
|
||||
username: undefined
|
||||
};
|
||||
|
||||
|
@ -371,7 +386,8 @@ function mapStateToProps(state) {
|
|||
user: state.user,
|
||||
sketches: getSortedSketches(state),
|
||||
sorting: state.sorting,
|
||||
loading: state.loading
|
||||
loading: state.loading,
|
||||
project: state.project
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ import * as ToastActions from '../actions/toast';
|
|||
import * as ConsoleActions from '../actions/console';
|
||||
import { getHTMLFile } from '../reducers/files';
|
||||
import Overlay from '../../App/components/Overlay';
|
||||
import SketchList from '../components/SketchList';
|
||||
import Searchbar from '../components/Searchbar';
|
||||
import AssetList from '../components/AssetList';
|
||||
import About from '../components/About';
|
||||
import Feedback from '../components/Feedback';
|
||||
|
||||
|
@ -203,6 +206,8 @@ class IDEView extends React.Component {
|
|||
setFontSize={this.props.setFontSize}
|
||||
autosave={this.props.preferences.autosave}
|
||||
linewrap={this.props.preferences.linewrap}
|
||||
lineNumbers={this.props.preferences.lineNumbers}
|
||||
setLineNumbers={this.props.setLineNumbers}
|
||||
setAutosave={this.props.setAutosave}
|
||||
setLinewrap={this.props.setLinewrap}
|
||||
lintWarning={this.props.preferences.lintWarning}
|
||||
|
@ -268,6 +273,7 @@ class IDEView extends React.Component {
|
|||
file={this.props.selectedFile}
|
||||
updateFileContent={this.props.updateFileContent}
|
||||
fontSize={this.props.preferences.fontSize}
|
||||
lineNumbers={this.props.preferences.lineNumbers}
|
||||
files={this.props.files}
|
||||
editorOptionsVisible={this.props.ide.editorOptionsVisible}
|
||||
showEditorOptions={this.props.showEditorOptions}
|
||||
|
@ -363,6 +369,31 @@ class IDEView extends React.Component {
|
|||
createFolder={this.props.createFolder}
|
||||
/>
|
||||
}
|
||||
{ this.props.location.pathname.match(/sketches$/) &&
|
||||
<Overlay
|
||||
ariaLabel="project list"
|
||||
title="Open a Sketch"
|
||||
previousPath={this.props.ide.previousPath}
|
||||
>
|
||||
<Searchbar />
|
||||
<SketchList
|
||||
username={this.props.params.username}
|
||||
user={this.props.user}
|
||||
/>
|
||||
</Overlay>
|
||||
}
|
||||
{ this.props.location.pathname.match(/assets$/) &&
|
||||
<Overlay
|
||||
title="Assets"
|
||||
ariaLabel="asset list"
|
||||
previousPath={this.props.ide.previousPath}
|
||||
>
|
||||
<AssetList
|
||||
username={this.props.params.username}
|
||||
user={this.props.user}
|
||||
/>
|
||||
</Overlay>
|
||||
}
|
||||
{ this.props.location.pathname === '/about' &&
|
||||
<Overlay
|
||||
previousPath={this.props.ide.previousPath}
|
||||
|
@ -490,6 +521,7 @@ IDEView.propTypes = {
|
|||
fontSize: PropTypes.number.isRequired,
|
||||
autosave: PropTypes.bool.isRequired,
|
||||
linewrap: PropTypes.bool.isRequired,
|
||||
lineNumbers: PropTypes.bool.isRequired,
|
||||
lintWarning: PropTypes.bool.isRequired,
|
||||
textOutput: PropTypes.bool.isRequired,
|
||||
gridOutput: PropTypes.bool.isRequired,
|
||||
|
@ -500,6 +532,7 @@ IDEView.propTypes = {
|
|||
closePreferences: PropTypes.func.isRequired,
|
||||
setFontSize: PropTypes.func.isRequired,
|
||||
setAutosave: PropTypes.func.isRequired,
|
||||
setLineNumbers: PropTypes.func.isRequired,
|
||||
setLinewrap: PropTypes.func.isRequired,
|
||||
setLintWarning: PropTypes.func.isRequired,
|
||||
setTextOutput: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import * as ActionTypes from '../../../constants';
|
||||
|
||||
const assets = (state = [], action) => {
|
||||
// 1,000,000 bytes in a MB. can't upload if totalSize is bigger than this.
|
||||
const initialState = {
|
||||
list: [],
|
||||
totalSize: 0
|
||||
};
|
||||
|
||||
const assets = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SET_ASSETS:
|
||||
return action.assets;
|
||||
return { list: action.assets, totalSize: action.totalSize };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@ const defaultHTML =
|
|||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.sound.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<meta charset="utf-8" />
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ const initialState = {
|
|||
projectOptionsVisible: false,
|
||||
newFolderModalVisible: false,
|
||||
shareModalVisible: false,
|
||||
shareModalProjectId: null,
|
||||
shareModalProjectName: null,
|
||||
shareModalProjectUsername: null,
|
||||
shareModalProjectId: 'abcd',
|
||||
shareModalProjectName: 'My Cute Sketch',
|
||||
shareModalProjectUsername: 'p5_user',
|
||||
editorOptionsVisible: false,
|
||||
keyboardShortcutVisible: false,
|
||||
unsavedChanges: false,
|
||||
|
|
|
@ -4,6 +4,7 @@ const initialState = {
|
|||
fontSize: 18,
|
||||
autosave: true,
|
||||
linewrap: true,
|
||||
lineNumbers: true,
|
||||
lintWarning: false,
|
||||
textOutput: false,
|
||||
gridOutput: false,
|
||||
|
@ -34,6 +35,8 @@ const preferences = (state = initialState, action) => {
|
|||
return Object.assign({}, state, { theme: action.value });
|
||||
case ActionTypes.SET_AUTOREFRESH:
|
||||
return Object.assign({}, state, { autorefresh: action.value });
|
||||
case ActionTypes.SET_LINE_NUMBERS:
|
||||
return Object.assign({}, state, { lineNumbers: action.value });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
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 getField = state => state.sorting.field;
|
||||
const getDirection = state => state.sorting.direction;
|
||||
const getSearchTerm = state => state.search.searchTerm;
|
||||
|
||||
const getFilteredSketches = createSelector(
|
||||
getSketches,
|
||||
getSearchTerm,
|
||||
(sketches, search) => {
|
||||
if (search) {
|
||||
const searchStrings = sketches.map((sketch) => {
|
||||
const smallSketch = {
|
||||
name: sketch.name
|
||||
};
|
||||
return { ...sketch, searchString: Object.values(smallSketch).join(' ').toLowerCase() };
|
||||
});
|
||||
return searchStrings.filter(sketch => sketch.searchString.includes(search.toLowerCase()));
|
||||
}
|
||||
return sketches;
|
||||
}
|
||||
);
|
||||
|
||||
const getSortedSketches = createSelector(
|
||||
getSketches,
|
||||
getFilteredSketches,
|
||||
getField,
|
||||
getDirection,
|
||||
(sketches, field, direction) => {
|
||||
|
|
|
@ -64,9 +64,8 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
|
|||
browserHistory.push(previousPath);
|
||||
resolve();
|
||||
})
|
||||
.catch((response) => {
|
||||
reject({ password: response.data.message, _error: 'Login failed!' }); // eslint-disable-line
|
||||
});
|
||||
.catch(error =>
|
||||
reject({ password: error.response.data.message, _error: 'Login failed!' })); // eslint-disable-line
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -84,7 +83,8 @@ export function getUser() {
|
|||
});
|
||||
})
|
||||
.catch((response) => {
|
||||
dispatch(authError(response.data.error));
|
||||
const message = response.message || response.data.error;
|
||||
dispatch(authError(message));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ function ResetPasswordForm(props) {
|
|||
id="email"
|
||||
{...domOnlyProps(email)}
|
||||
/>
|
||||
{email.touched && email.error && <span className="form-error">{email.error}</span>}
|
||||
</p>
|
||||
<input
|
||||
type="submit"
|
||||
|
|
|
@ -83,9 +83,9 @@ function mapDispatchToProps() {
|
|||
|
||||
LoginView.propTypes = {
|
||||
previousPath: PropTypes.string.isRequired,
|
||||
user: {
|
||||
user: PropTypes.shape({
|
||||
authenticated: PropTypes.bool
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
LoginView.defaultProps = {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link, browserHistory } from 'react-router';
|
||||
|
@ -8,6 +9,7 @@ import { reduxForm } from 'redux-form';
|
|||
import { Helmet } from 'react-helmet';
|
||||
import * as UserActions from '../actions';
|
||||
import ResetPasswordForm from '../components/ResetPasswordForm';
|
||||
import { validateResetPassword } from '../../../utils/reduxFormUtils';
|
||||
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
const logoUrl = require('../../../images/p5js-logo.svg');
|
||||
|
@ -83,16 +85,8 @@ function mapDispatchToProps(dispatch) {
|
|||
return bindActionCreators(UserActions, dispatch);
|
||||
}
|
||||
|
||||
function validate(formProps) {
|
||||
const errors = {};
|
||||
if (!formProps.email) {
|
||||
errors.email = 'Please enter an email';
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
export default reduxForm({
|
||||
form: 'reset-password',
|
||||
fields: ['email'],
|
||||
validate
|
||||
validate: validateResetPassword
|
||||
}, mapStateToProps, mapDispatchToProps)(ResetPasswordView);
|
||||
|
|
|
@ -10,6 +10,7 @@ import sketches from './modules/IDE/reducers/projects';
|
|||
import toast from './modules/IDE/reducers/toast';
|
||||
import console from './modules/IDE/reducers/console';
|
||||
import assets from './modules/IDE/reducers/assets';
|
||||
import search from './modules/IDE/reducers/search';
|
||||
import sorting from './modules/IDE/reducers/sorting';
|
||||
import loading from './modules/IDE/reducers/loading';
|
||||
|
||||
|
@ -21,6 +22,7 @@ const rootReducer = combineReducers({
|
|||
user,
|
||||
project,
|
||||
sketches,
|
||||
search,
|
||||
sorting,
|
||||
editorAccessibility,
|
||||
toast,
|
||||
|
|
|
@ -64,7 +64,10 @@ $themes: (
|
|||
input-text-color: #333,
|
||||
input-border-color: #b5b5b5,
|
||||
about-list-text-color: #4a4a4a,
|
||||
search-background-color: #ebebeb,
|
||||
search-background-color: #ffffff,
|
||||
search-clear-background-color: #e9e9e9,
|
||||
search-hover-text-color: #ffffff,
|
||||
search-hover-background-color: #4d4d4d,
|
||||
dropdown-color: #414141,
|
||||
keyboard-shortcut-color: #757575,
|
||||
nav-hover-color: $p5js-pink,
|
||||
|
@ -127,7 +130,10 @@ $themes: (
|
|||
input-text-color: #333,
|
||||
input-border-color: #b5b5b5,
|
||||
about-list-text-color: #f4f4f4,
|
||||
search-background-color: #ebebeb,
|
||||
search-background-color: #ffffff,
|
||||
search-clear-background-color: #4f4f4f,
|
||||
search-hover-text-color: #ffffff,
|
||||
search-hover-background-color: $p5js-pink,
|
||||
dropdown-color: #dadada,
|
||||
keyboard-shortcut-color: #B5B5B5,
|
||||
nav-hover-color: $p5js-pink,
|
||||
|
@ -187,6 +193,9 @@ $themes: (
|
|||
input-border-color: #b5b5b5,
|
||||
about-list-text-color: #f4f4f4,
|
||||
search-background-color: $white,
|
||||
search-clear-background-color: #444,
|
||||
search-hover-text-color: $black,
|
||||
search-hover-background-color: $yellow,
|
||||
dropdown-color: #e1e1e1,
|
||||
keyboard-shortcut-color: #e1e1e1,
|
||||
nav-hover-color: $yellow,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.asset-table-container {
|
||||
// flex: 1 1 0%;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
max-width: 100%;
|
||||
min-height: #{400 / $base-font-size}rem;
|
||||
}
|
||||
|
@ -54,3 +54,7 @@
|
|||
font-size: #{16 / $base-font-size}rem;
|
||||
padding: #{42 / $base-font-size}rem 0;
|
||||
}
|
||||
|
||||
.asset-table__total {
|
||||
padding: 0 #{20 / $base-font-size}rem;
|
||||
}
|
||||
|
|
|
@ -97,16 +97,6 @@
|
|||
margin-left: #{5 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.nav__dropdown {
|
||||
@include themify() {
|
||||
color: getThemifyVariable('nav-hover-color');
|
||||
}
|
||||
}
|
||||
|
||||
.nav__item-header-triangle {
|
||||
margin-left: #{5 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.nav__dropdown {
|
||||
@extend %dropdown-open-left;
|
||||
display: none;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
flex-flow: column;
|
||||
max-height: 80%;
|
||||
max-width: 65%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.overlay__header {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
min-height: #{460 / $base-font-size}rem;
|
||||
max-height: 100%;
|
||||
z-index: 9999;
|
||||
padding: 0 #{20 / $base-font-size}rem #{2 / $base-font-size}rem #{20 / $base-font-size}rem ;
|
||||
padding: 0 #{20 / $base-font-size}rem #{2 / $base-font-size}rem #{20 /
|
||||
$base-font-size}rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
|
@ -47,17 +48,26 @@
|
|||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
@include themify() {
|
||||
color: getThemifyVariable('heading-text-color');
|
||||
color: getThemifyVariable("heading-text-color");
|
||||
}
|
||||
}
|
||||
|
||||
.preferences__title{
|
||||
.preferences__title {
|
||||
width: #{90 / $base-font-size}rem;
|
||||
height: #{25 / $base-font-size}rem;
|
||||
font-family: Montserrat;
|
||||
font-size: #{21 / $base-font-size}rem;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
height: #{25 / $base-font-size}rem;
|
||||
font-family: Montserrat;
|
||||
font-size: #{21 / $base-font-size}rem;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.preference__subheadings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: #{0.1 / $base-font-size}rem;
|
||||
@include themify() {
|
||||
border-bottom: 1px solid getThemifyVariable("button-border-color");
|
||||
}
|
||||
}
|
||||
|
||||
.preference {
|
||||
|
@ -66,7 +76,7 @@
|
|||
padding-bottom: #{12 / $base-font-size}rem;
|
||||
& + & {
|
||||
@include themify() {
|
||||
border-top: 1px dashed getThemifyVariable('button-border-color');
|
||||
border-top: 1px dashed getThemifyVariable("button-border-color");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,13 +87,13 @@
|
|||
margin-top: #{13 / $base-font-size}rem;
|
||||
margin-bottom: #{7 / $base-font-size}rem;
|
||||
@include themify() {
|
||||
color: getThemifyVariable('heading-text-color');
|
||||
color: getThemifyVariable("heading-text-color");
|
||||
}
|
||||
}
|
||||
|
||||
.preference__subtitle {
|
||||
@include themify() {
|
||||
color: getThemifyVariable('inactive-text-color');
|
||||
color: getThemifyVariable("inactive-text-color");
|
||||
}
|
||||
width: 100%;
|
||||
margin-bottom: #{10 / $base-font-size}rem;
|
||||
|
@ -92,9 +102,10 @@
|
|||
|
||||
.preference__value {
|
||||
@include themify() {
|
||||
border: #{1 / $base-font-size}rem solid getThemifyVariable('button-border-color');
|
||||
background-color: getThemifyVariable('button-background-color');
|
||||
color: getThemifyVariable('input-text-color');
|
||||
border: #{1 / $base-font-size}rem solid
|
||||
getThemifyVariable("button-border-color");
|
||||
background-color: getThemifyVariable("button-background-color");
|
||||
color: getThemifyVariable("input-text-color");
|
||||
}
|
||||
text-align: center;
|
||||
border-radius: 0%;
|
||||
|
@ -109,17 +120,60 @@
|
|||
|
||||
.preference__label {
|
||||
@include themify() {
|
||||
color: getThemifyVariable('inactive-text-color');
|
||||
color: getThemifyVariable("inactive-text-color");
|
||||
&:hover {
|
||||
color: getThemifyVariable('primary-text-color');
|
||||
color: getThemifyVariable("primary-text-color");
|
||||
}
|
||||
|
||||
}
|
||||
margin: #{-15 / $base-font-size}rem 0 0 #{-5 / $base-font-size}rem;
|
||||
font-size: #{9 / $base-font-size}rem;
|
||||
width: #{44 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.react-tabs__tab--selected {
|
||||
@include themify() {
|
||||
border-bottom: #{4 / $base-font-size}rem solid
|
||||
getThemifyVariable("button-background-hover-color");
|
||||
}
|
||||
}
|
||||
|
||||
.react-tabs__tab--selected .preference__subheading {
|
||||
@include themify() {
|
||||
color: getThemifyVariable("primary-text-color");
|
||||
}
|
||||
}
|
||||
|
||||
.react-tabs__tab {
|
||||
text-align: center;
|
||||
color: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: #{4 / $base-font-size}rem solid transparent;
|
||||
& + & {
|
||||
margin-left: #{45 / $base-font-size}rem;
|
||||
}
|
||||
}
|
||||
|
||||
.preference__subheading {
|
||||
@include themify() {
|
||||
color: getThemifyVariable("inactive-text-color");
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: getThemifyVariable("primary-text-color");
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus {
|
||||
color: getThemifyVariable("primary-text-color");
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
font-size: #{12 / $base-font-size}rem;
|
||||
height: #{20 / $base-font-size}rem;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0 #{5 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.preference__vertical-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -135,8 +189,8 @@
|
|||
margin-top: #{40 / $base-font-size}rem;
|
||||
margin-bottom: #{10 / $base-font-size}rem;
|
||||
@include themify() {
|
||||
color: getThemifyVariable('inactive-text-color');
|
||||
border-bottom: 1px dashed getThemifyVariable('button-border-color');
|
||||
color: getThemifyVariable("inactive-text-color");
|
||||
border-bottom: 1px dashed getThemifyVariable("button-border-color");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,6 +198,12 @@
|
|||
@extend %hidden-element;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.preference__option {
|
||||
@include themify() {
|
||||
@extend %preference-option;
|
||||
|
@ -162,7 +222,7 @@
|
|||
@include themify() {
|
||||
@extend %preference-option;
|
||||
&:hover {
|
||||
color: getThemifyVariable('button-background-hover-color');
|
||||
color: getThemifyVariable("button-background-hover-color");
|
||||
}
|
||||
}
|
||||
margin-left: #{30 / $base-font-size}rem;
|
||||
|
@ -182,7 +242,7 @@
|
|||
.preference__radio-button:checked + .preference__option {
|
||||
@include themify() {
|
||||
//for some reason this won't work for getThemifyVariable
|
||||
color: map-get($theme-map, 'primary-text-color');
|
||||
color: map-get($theme-map, "primary-text-color");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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 {
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
max-width: 100%;
|
||||
min-height: #{400 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.sketches-table {
|
||||
width: 100%;
|
||||
|
||||
max-height: 100%;
|
||||
border-spacing: 0;
|
||||
& .sketch-list__dropdown-column {
|
||||
|
@ -69,7 +70,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.sketches-table thead {
|
||||
font-size: #{12 / $base-font-size}rem;
|
||||
@include themify() {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
@import 'components/preferences';
|
||||
@import 'components/reset-password';
|
||||
@import 'components/new-password';
|
||||
@import 'components/searchbar';
|
||||
@import 'components/sketch-list';
|
||||
@import 'components/sidebar';
|
||||
@import 'components/modal';
|
||||
|
|
|
@ -73,6 +73,8 @@ export default function(CodeMirror) {
|
|||
CodeMirror.on(searchField, "keyup", function (e) {
|
||||
if (e.keyCode !== 13 && searchField.value.length > 1) { // not enter and more than 1 character to search
|
||||
startSearch(cm, getSearchState(cm), searchField.value);
|
||||
} else if (searchField.value.length <= 1) {
|
||||
cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = '';
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -260,6 +262,7 @@ export default function(CodeMirror) {
|
|||
</button>
|
||||
</div>
|
||||
<div class="CodeMirror-search-nav">
|
||||
<button class="CodeMirror-search-results"></button>
|
||||
<button
|
||||
title="Previous"
|
||||
aria-label="Previous"
|
||||
|
@ -292,6 +295,9 @@ export default function(CodeMirror) {
|
|||
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
|
||||
state.annotate = cm.showMatchesOnScrollbar(state.query, state.caseInsensitive);
|
||||
}
|
||||
if (originalQuery) {
|
||||
return findNext(cm, false);
|
||||
}
|
||||
}
|
||||
|
||||
function doSearch(cm, rev, persistent, immediate, ignoreQuery) {
|
||||
|
@ -350,11 +356,19 @@ export default function(CodeMirror) {
|
|||
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
|
||||
if (!cursor.find(rev)) {
|
||||
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
|
||||
if (!cursor.find(rev)) return;
|
||||
if (!cursor.find(rev)) {
|
||||
cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
cm.setSelection(cursor.from(), cursor.to());
|
||||
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 60);
|
||||
state.posFrom = cursor.from(); state.posTo = cursor.to();
|
||||
var num_match = cm.state.search.annotate.matches.length;
|
||||
var next = cm.state.search.annotate.matches
|
||||
.findIndex(s => s.from.ch === cursor.from().ch && s.from.line === cursor.from().line) + 1;
|
||||
var text_match = next + '/' + num_match;
|
||||
cm.display.wrapper.querySelector('.CodeMirror-search-results').innerText = text_match;
|
||||
if (callback) callback(cursor.from(), cursor.to())
|
||||
});}
|
||||
|
||||
|
|
|
@ -46,21 +46,36 @@ export const startTag = '@fs-';
|
|||
|
||||
export const getAllScriptOffsets = (htmlFile) => {
|
||||
const offs = [];
|
||||
let found = true;
|
||||
const hijackConsoleErrorsScriptLength = 36;
|
||||
const embeddedJSStart = 'script crossorigin=""';
|
||||
let foundJSScript = true;
|
||||
let foundEmbeddedJS = true;
|
||||
let lastInd = 0;
|
||||
let ind = 0;
|
||||
let endFilenameInd = 0;
|
||||
let filename = '';
|
||||
let lineOffset = 0;
|
||||
while (found) {
|
||||
while (foundJSScript) {
|
||||
ind = htmlFile.indexOf(startTag, lastInd);
|
||||
if (ind === -1) {
|
||||
found = false;
|
||||
foundJSScript = false;
|
||||
} else {
|
||||
endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 3);
|
||||
filename = htmlFile.substring(ind + startTag.length, endFilenameInd);
|
||||
// the length of hijackConsoleErrorsScript is 36 lines
|
||||
lineOffset = htmlFile.substring(0, ind).split('\n').length + 36;
|
||||
lineOffset = htmlFile.substring(0, ind).split('\n').length + hijackConsoleErrorsScriptLength;
|
||||
offs.push([lineOffset, filename]);
|
||||
lastInd = ind + 1;
|
||||
}
|
||||
}
|
||||
lastInd = 0;
|
||||
while (foundEmbeddedJS) {
|
||||
ind = htmlFile.indexOf(embeddedJSStart, lastInd);
|
||||
if (ind === -1) {
|
||||
foundEmbeddedJS = false;
|
||||
} else {
|
||||
filename = 'index.html';
|
||||
// not sure where the offset of 25 comes from
|
||||
lineOffset = htmlFile.substring(0, ind).split('\n').length + 25;
|
||||
offs.push([lineOffset, filename]);
|
||||
lastInd = ind + 1;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ export const domOnlyProps = ({
|
|||
...domProps }) => domProps;
|
||||
/* eslint-enable */
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
|
||||
|
||||
function validateNameEmail(formProps, errors) {
|
||||
if (!formProps.username) {
|
||||
errors.username = 'Please enter a username.';
|
||||
|
@ -28,7 +31,7 @@ function validateNameEmail(formProps, errors) {
|
|||
errors.email = 'Please enter an email.';
|
||||
} else if (
|
||||
// eslint-disable-next-line max-len
|
||||
!formProps.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) {
|
||||
!formProps.email.match(EMAIL_REGEX)) {
|
||||
errors.email = 'Please enter a valid email address.';
|
||||
}
|
||||
}
|
||||
|
@ -79,3 +82,14 @@ export function validateSignup(formProps) {
|
|||
|
||||
return errors;
|
||||
}
|
||||
export function validateResetPassword(formProps) {
|
||||
const errors = {};
|
||||
if (!formProps.email) {
|
||||
errors.email = 'Please enter an email.';
|
||||
} else if (
|
||||
// eslint-disable-next-line max-len
|
||||
!formProps.email.match(EMAIL_REGEX)) {
|
||||
errors.email = 'Please enter a valid email address.';
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
|
1
index.js
1
index.js
|
@ -1,6 +1,5 @@
|
|||
if (process.env.NODE_ENV === 'production') {
|
||||
process.env.webpackAssets = JSON.stringify(require('./dist/static/manifest.json'));
|
||||
process.env.webpackChunkAssets = JSON.stringify(require('./dist/static/chunk-manifest.json'));
|
||||
require('./dist/server.bundle.js');
|
||||
} else {
|
||||
let parsed = require('dotenv').config();
|
||||
|
|
5347
package-lock.json
generated
5347
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-loader": "^8.0.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.2.12",
|
||||
"chunk-manifest-webpack-plugin": "github:catarak/chunk-manifest-webpack-plugin",
|
||||
"css-loader": "^0.23.1",
|
||||
"cssnano": "^3.10.0",
|
||||
"css-loader": "^3.2.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"enzyme": "^3.7.0",
|
||||
"enzyme-adapter-react-16": "^1.6.0",
|
||||
"eslint": "^4.19.1",
|
||||
|
@ -73,19 +72,22 @@
|
|||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.2",
|
||||
"eslint-plugin-react": "^7.12.3",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^2.0.0",
|
||||
"jest": "^24.8.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"nodemon": "^1.18.9",
|
||||
"postcss-cssnext": "^2.11.0",
|
||||
"postcss-focus": "^1.0.0",
|
||||
"postcss-loader": "^0.9.1",
|
||||
"postcss-reporter": "^1.4.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"postcss-cssnext": "^3.1.0",
|
||||
"postcss-focus": "^4.0.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-reporter": "^6.0.1",
|
||||
"react-test-renderer": "^16.6.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"sass-loader": "^6.0.7",
|
||||
"style-loader": "^0.13.2",
|
||||
"style-loader": "^1.0.0",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"webpack-cli": "^3.3.7",
|
||||
"webpack-manifest-plugin": "^2.0.4",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
|
@ -108,7 +110,7 @@
|
|||
"clipboard": "^1.7.1",
|
||||
"codemirror": "^5.42.2",
|
||||
"connect-mongo": "^1.3.2",
|
||||
"console-feed": "^2.8.8",
|
||||
"console-feed": "^2.8.10",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^5.2.0",
|
||||
|
@ -129,7 +131,7 @@
|
|||
"js-beautify": "^1.8.9",
|
||||
"jsdom": "^9.8.3",
|
||||
"jshint": "^2.10.1",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.15",
|
||||
"loop-protect": "github:catarak/loop-protect",
|
||||
"mjml": "^3.3.2",
|
||||
"mockingoose": "^2.13.0",
|
||||
|
@ -175,7 +177,7 @@
|
|||
"slugify": "^1.3.4",
|
||||
"srcdoc-polyfill": "^0.2.0",
|
||||
"url": "^0.11.0",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack": "^4.39.2",
|
||||
"webpack-dev-middleware": "^2.0.6",
|
||||
"webpack-hot-middleware": "^2.24.3",
|
||||
"xhr": "^2.5.0"
|
||||
|
|
|
@ -126,6 +126,7 @@ export function listObjectsInS3ForUser(req, res) {
|
|||
.on('end', () => {
|
||||
const projectAssets = [];
|
||||
getProjectsForUserId(userId).then((projects) => {
|
||||
let totalSize = 0;
|
||||
assets.forEach((asset) => {
|
||||
const name = asset.key.split('/').pop();
|
||||
const foundAsset = {
|
||||
|
@ -134,6 +135,7 @@ export function listObjectsInS3ForUser(req, res) {
|
|||
size: asset.size,
|
||||
url: `${process.env.S3_BUCKET_URL_BASE}${asset.key}`
|
||||
};
|
||||
totalSize += asset.size;
|
||||
projects.some((project) => {
|
||||
let found = false;
|
||||
project.files.some((file) => {
|
||||
|
@ -152,7 +154,7 @@ export function listObjectsInS3ForUser(req, res) {
|
|||
});
|
||||
projectAssets.push(foundAsset);
|
||||
});
|
||||
res.json({ assets: projectAssets });
|
||||
res.json({ assets: projectAssets, totalSize });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ export function createSession(req, res, next) {
|
|||
passport.authenticate('local', (err, user) => { // eslint-disable-line consistent-return
|
||||
if (err) { return next(err); }
|
||||
if (!user) {
|
||||
return res.status(401).send({ message: 'Invalid username or password.' });
|
||||
return res.status(401).json({ message: 'Invalid username or password.' });
|
||||
}
|
||||
|
||||
req.logIn(user, (innerErr) => {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
require('@babel/register');
|
||||
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] },
|
||||
preferences: {
|
||||
fontSize: { type: Number, default: 18 },
|
||||
lineNumbers: { type: Boolean, default: true },
|
||||
indentationAmount: { type: Number, default: 2 },
|
||||
isTabIndent: { type: Boolean, default: false },
|
||||
autosave: { type: Boolean, default: true },
|
||||
|
|
|
@ -11,9 +11,9 @@ const defaultHTML =
|
|||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.sound.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
@ -61,7 +61,7 @@ function getCategories() {
|
|||
for (let j = 1; j < metadata.name.split('_').length; j += 1) {
|
||||
category += `${metadata.name.split('_')[j]} `;
|
||||
}
|
||||
categories.push({ url: metadata.url, name: category });
|
||||
categories.push({ url: metadata.url, name: category.trim() });
|
||||
});
|
||||
|
||||
return categories;
|
||||
|
@ -114,12 +114,12 @@ function getSketchContent(projectsInAllCategories) {
|
|||
|
||||
return rp(options).then((res) => {
|
||||
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
|
||||
if (noNumberprojectName === 'Instance Mode : Instance Container ') {
|
||||
if (noNumberprojectName === 'Instance Mode: Instance Container ') {
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
const splitedRes = `${res.split('*/')[1].split('</html>')[i]}</html>\n`;
|
||||
project.sketchContent = splitedRes.replace(
|
||||
'p5.js',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.4/p5.min.js'
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -153,7 +153,7 @@ function createProjectsInP5user(projectsInAllCategories) {
|
|||
const c = objectID().toHexString();
|
||||
const r = objectID().toHexString();
|
||||
const noNumberprojectName = project.projectName.replace(/(\d+)/g, '');
|
||||
if (noNumberprojectName === 'Instance Mode : Instance Container ') {
|
||||
if (noNumberprojectName === 'Instance Mode: Instance Container ') {
|
||||
newProject = new Project({
|
||||
name: project.projectName,
|
||||
user: user._id,
|
||||
|
@ -167,7 +167,7 @@ function createProjectsInP5user(projectsInAllCategories) {
|
|||
},
|
||||
{
|
||||
name: 'sketch.js',
|
||||
content: '// Instance Mode : Instance Container, please check its index.html file',
|
||||
content: '// Instance Mode: Instance Container, please check its index.html file',
|
||||
id: a,
|
||||
_id: a,
|
||||
fileType: 'file',
|
||||
|
|
|
@ -52,7 +52,7 @@ const corsOriginsWhitelist = [
|
|||
// Run Webpack dev server in development mode
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const compiler = webpack(config);
|
||||
app.use(webpackDevMiddleware(compiler, { lazy: false, noInfo: true, publicPath: config[0].output.publicPath }));
|
||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
||||
app.use(webpackHotMiddleware(compiler));
|
||||
|
||||
corsOriginsWhitelist.push(/localhost/);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export function renderIndex() {
|
||||
const assetsManifest = process.env.webpackAssets && JSON.parse(process.env.webpackAssets);
|
||||
const chunkManifest = process.env.webpackChunkAssets && JSON.parse(process.env.webpackChunkAssets);
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -37,13 +36,6 @@ export function renderIndex() {
|
|||
<body>
|
||||
<div id="root" class="root-app">
|
||||
</div>
|
||||
<script>
|
||||
${process.env.NODE_ENV === 'production' ?
|
||||
`//<![CDATA[
|
||||
window.webpackManifest = ${JSON.stringify(chunkManifest)};
|
||||
//]]>` : ''}
|
||||
</script>
|
||||
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/vendor.js']}` : '/vendor.js'}'></script>
|
||||
<script src='${process.env.NODE_ENV === 'production' ? `${assetsManifest['/app.js']}` : '/app.js'}'></script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
|
|
|
@ -5,7 +5,10 @@ if (process.env.NODE_ENV === 'development') {
|
|||
require('dotenv').config();
|
||||
}
|
||||
|
||||
module.exports = [{
|
||||
|
||||
// react hmr being fucked up has to do with the multiple entries!!! cool.
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
entry: {
|
||||
app: [
|
||||
|
@ -15,14 +18,13 @@ module.exports = [{
|
|||
'react-hot-loader/patch',
|
||||
'./client/index.jsx',
|
||||
],
|
||||
vendor: [
|
||||
'react',
|
||||
'react-dom'
|
||||
previewScripts: [
|
||||
path.resolve(__dirname, '../client/utils/previewEntry.js')
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: `${__dirname}`,
|
||||
filename: 'app.js',
|
||||
filename: '[name].js',
|
||||
publicPath: '/'
|
||||
},
|
||||
resolve: {
|
||||
|
@ -34,11 +36,6 @@ module.exports = [{
|
|||
},
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks: Infinity,
|
||||
filename: 'vendor.js',
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify('development')
|
||||
|
@ -100,32 +97,4 @@ module.exports = [{
|
|||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
entry: path.resolve(__dirname, '../client/utils/previewEntry.js'),
|
||||
target: 'web',
|
||||
output: {
|
||||
path: `${__dirname}`,
|
||||
filename: 'previewScripts.js',
|
||||
publicPath: '/'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.jsx'],
|
||||
modules: [
|
||||
'client',
|
||||
'node_modules',
|
||||
],
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: true
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
}]
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ const nodeExternals = require('webpack-node-externals');
|
|||
|
||||
module.exports = [{
|
||||
entry: path.resolve(__dirname, '../server/scripts/fetch-examples.js'),
|
||||
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist/'),
|
||||
filename: 'fetch-examples.bundle.js'
|
||||
|
@ -22,7 +22,7 @@ module.exports = [{
|
|||
},
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
|
@ -36,7 +36,7 @@ module.exports = [{
|
|||
},
|
||||
{
|
||||
entry: path.resolve(__dirname, '../server/scripts/fetch-examples-gg.js'),
|
||||
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist/'),
|
||||
filename: 'fetch-examples-gg.bundle.js'
|
||||
|
@ -55,7 +55,7 @@ module.exports = [{
|
|||
},
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
|
@ -69,7 +69,7 @@ module.exports = [{
|
|||
},
|
||||
{
|
||||
entry: path.resolve(__dirname, '../server/scripts/fetch-examples-ml5.js'),
|
||||
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist/'),
|
||||
filename: 'fetch-examples-ml5.bundle.js'
|
||||
|
@ -88,7 +88,7 @@ module.exports = [{
|
|||
},
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
|
||||
const cssnext = require('postcss-cssnext');
|
||||
const postcssFocus = require('postcss-focus');
|
||||
const postcssReporter = require('postcss-reporter');
|
||||
|
@ -13,36 +14,18 @@ if (process.env.NODE_ENV === "development") {
|
|||
|
||||
module.exports = [{
|
||||
devtool: 'source-map',
|
||||
|
||||
mode: 'production',
|
||||
entry: {
|
||||
app: [
|
||||
'@babel/polyfill',
|
||||
'core-js/modules/es6.promise',
|
||||
'core-js/modules/es6.array.iterator',
|
||||
path.resolve(__dirname, '../client/index.jsx')
|
||||
],
|
||||
vendor: [
|
||||
'axios',
|
||||
'classnames',
|
||||
'codemirror',
|
||||
'csslint',
|
||||
'dropzone',
|
||||
'htmlhint',
|
||||
'js-beautify',
|
||||
'jshint',
|
||||
'react-dom',
|
||||
'react-inlinesvg',
|
||||
'react-redux',
|
||||
'react-router',
|
||||
'react',
|
||||
'redux-form',
|
||||
'redux-thunk',
|
||||
'redux',
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist/static'),
|
||||
filename: '[name].[chunkhash].js',
|
||||
filename: '[name].[hash].js',
|
||||
publicPath: '/'
|
||||
},
|
||||
|
||||
|
@ -53,16 +36,44 @@ module.exports = [{
|
|||
'node_modules',
|
||||
]
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
rules: [{
|
||||
test: /main\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ExtractTextPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
use: 'css-loader!sass-loader!postcss-loader'
|
||||
})
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 2,
|
||||
sourceMap: true
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
plugins: () => [
|
||||
postcssFocus(),
|
||||
cssnext({
|
||||
browsers: ['last 2 versions', 'IE > 9']
|
||||
}),
|
||||
cssnano({
|
||||
autoprefixer: false
|
||||
}),
|
||||
postcssReporter({
|
||||
clearMessages: true
|
||||
})
|
||||
],
|
||||
sourceMap: true
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
|
@ -76,11 +87,11 @@ module.exports = [{
|
|||
{
|
||||
test: /\.(png)$/,
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]',
|
||||
outputPath: 'images/'
|
||||
}
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]',
|
||||
outputPath: 'images/'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -92,58 +103,28 @@ module.exports = [{
|
|||
use: {
|
||||
loader: 'sass-extract-loader',
|
||||
options: {
|
||||
plugins: [{ plugin: 'sass-extract-js', options: { camelCase: false } }]
|
||||
plugins: [{
|
||||
plugin: 'sass-extract-js',
|
||||
options: {
|
||||
camelCase: false
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
optimization: {
|
||||
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify('production')
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks: Infinity,
|
||||
filename: '[name].[chunkhash].js',
|
||||
}),
|
||||
new ExtractTextPlugin({ filename: 'app.[chunkhash].css', allChunks: true }),
|
||||
new ManifestPlugin({
|
||||
basePath: '/',
|
||||
}),
|
||||
new ChunkManifestPlugin({
|
||||
filename: 'chunk-manifest.json',
|
||||
manifestVariable: 'webpackManifest',
|
||||
inlineManifest: false
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
warnings: false,
|
||||
drop_console :true
|
||||
}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
options: {
|
||||
postcss: () => [
|
||||
postcssFocus(),
|
||||
cssnext({
|
||||
browsers: ['last 2 versions', 'IE > 9']
|
||||
}),
|
||||
cssnano({
|
||||
autoprefixer: false
|
||||
}),
|
||||
postcssReporter({
|
||||
clearMessages: true
|
||||
})
|
||||
]
|
||||
}
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'app.[hash].css',
|
||||
})
|
||||
],
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
|
@ -152,6 +133,7 @@ module.exports = [{
|
|||
]
|
||||
},
|
||||
target: 'web',
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../dist/static'),
|
||||
filename: 'previewScripts.js',
|
||||
|
@ -165,7 +147,7 @@ module.exports = [{
|
|||
],
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
|
@ -175,12 +157,5 @@ module.exports = [{
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
target: 'node',
|
||||
mode: 'production',
|
||||
|
||||
node: {
|
||||
__filename: true,
|
||||
|
@ -28,7 +29,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
|
|
Loading…
Reference in a new issue