Merge branch 'develop' of https://github.com/processing/p5.js-web-editor into feature/mobile-canvas

This commit is contained in:
ghalestrilo 2020-06-15 14:20:28 -03:00
commit 96261db108
25 changed files with 165 additions and 126 deletions

3
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
custom: https://processingfoundation.org/support
github: processing
custom: https://processingfoundation.org/

View File

@ -10,14 +10,13 @@ import * as projectActions from '../modules/IDE/actions/project';
import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
import { logoutUser } from '../modules/User/actions';
import getConfig from '../utils/getConfig';
import { metaKeyName, } from '../utils/metaKey';
import CaretLeftIcon from '../images/left-arrow.svg';
import TriangleIcon from '../images/down-filled-triangle.svg';
import LogoIcon from '../images/p5js-logo-small.svg';
const __process = (typeof global !== 'undefined' ? global : window).process;
class Nav extends React.PureComponent {
constructor(props) {
super(props);
@ -272,7 +271,7 @@ class Nav extends React.PureComponent {
New
</button>
</li>
{ __process.env.LOGIN_ENABLED && (!this.props.project.owner || this.isUserOwner()) &&
{ getConfig('LOGIN_ENABLED') && (!this.props.project.owner || this.isUserOwner()) &&
<li className="nav__dropdown-item">
<button
onClick={this.handleSave}
@ -324,7 +323,7 @@ class Nav extends React.PureComponent {
Open
</Link>
</li> }
{__process.env.UI_COLLECTIONS_ENABLED &&
{getConfig('UI_COLLECTIONS_ENABLED') &&
this.props.user.authenticated &&
this.props.project.id &&
<li className="nav__dropdown-item">
@ -337,7 +336,7 @@ class Nav extends React.PureComponent {
Add to Collection
</Link>
</li>}
{ __process.env.EXAMPLES_ENABLED &&
{ getConfig('EXAMPLES_ENABLED') &&
<li className="nav__dropdown-item">
<Link
to="/p5/sketches"
@ -587,7 +586,7 @@ class Nav extends React.PureComponent {
My sketches
</Link>
</li>
{__process.env.UI_COLLECTIONS_ENABLED &&
{getConfig('UI_COLLECTIONS_ENABLED') &&
<li className="nav__dropdown-item">
<Link
to={`/${this.props.user.username}/collections`}
@ -635,7 +634,7 @@ class Nav extends React.PureComponent {
}
renderUserMenu(navDropdownState) {
const isLoginEnabled = __process.env.LOGIN_ENABLED;
const isLoginEnabled = getConfig('LOGIN_ENABLED');
const isAuthenticated = this.props.user.authenticated;
if (isLoginEnabled && isAuthenticated) {

View File

@ -1,11 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import getConfig from '../../utils/getConfig';
import DevTools from './components/DevTools';
import { setPreviousPath } from '../IDE/actions/ide';
const __process = (typeof global !== 'undefined' ? global : window).process;
class App extends React.Component {
constructor(props, context) {
super(props, context);
@ -35,7 +34,7 @@ class App extends React.Component {
render() {
return (
<div className="app">
{this.state.isMounted && !window.devToolsExtension && __process.env.NODE_ENV === 'development' && <DevTools />}
{this.state.isMounted && !window.devToolsExtension && getConfig('NODE_ENV') === 'development' && <DevTools />}
{this.props.children}
</div>
);

View File

@ -1,10 +1,7 @@
import axios from 'axios';
import apiClient from '../../../utils/apiClient';
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, totalSize) {
return {
type: ActionTypes.SET_ASSETS,
@ -16,7 +13,7 @@ function setAssets(assets, totalSize) {
export function getAssets() {
return (dispatch) => {
dispatch(startLoader());
axios.get(`${ROOT_URL}/S3/objects`, { withCredentials: true })
apiClient.get('/S3/objects')
.then((response) => {
dispatch(setAssets(response.data.assets, response.data.totalSize));
dispatch(stopLoader());
@ -39,7 +36,7 @@ export function deleteAsset(assetKey) {
export function deleteAssetRequest(assetKey) {
return (dispatch) => {
axios.delete(`${ROOT_URL}/S3/${assetKey}`, { withCredentials: true })
apiClient.delete(`/S3/${assetKey}`)
.then((response) => {
dispatch(deleteAsset(assetKey));
})

View File

@ -1,11 +1,9 @@
import axios from 'axios';
import { browserHistory } from 'react-router';
import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants';
import { startLoader, stopLoader } from './loader';
import { setToastText, showToast } from './toast';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
const TOAST_DISPLAY_TIME_MS = 1500;
@ -15,11 +13,11 @@ export function getCollections(username) {
dispatch(startLoader());
let url;
if (username) {
url = `${ROOT_URL}/${username}/collections`;
url = `/${username}/collections`;
} else {
url = `${ROOT_URL}/collections`;
url = '/collections';
}
axios.get(url, { withCredentials: true })
apiClient.get(url)
.then((response) => {
dispatch({
type: ActionTypes.SET_COLLECTIONS,
@ -41,8 +39,8 @@ export function getCollections(username) {
export function createCollection(collection) {
return (dispatch) => {
dispatch(startLoader());
const url = `${ROOT_URL}/collections`;
return axios.post(url, collection, { withCredentials: true })
const url = '/collections';
return apiClient.post(url, collection)
.then((response) => {
dispatch({
type: ActionTypes.CREATE_COLLECTION
@ -73,8 +71,8 @@ export function createCollection(collection) {
export function addToCollection(collectionId, projectId) {
return (dispatch) => {
dispatch(startLoader());
const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`;
return axios.post(url, { withCredentials: true })
const url = `/collections/${collectionId}/${projectId}`;
return apiClient.post(url)
.then((response) => {
dispatch({
type: ActionTypes.ADD_TO_COLLECTION,
@ -105,8 +103,8 @@ export function addToCollection(collectionId, projectId) {
export function removeFromCollection(collectionId, projectId) {
return (dispatch) => {
dispatch(startLoader());
const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`;
return axios.delete(url, { withCredentials: true })
const url = `/collections/${collectionId}/${projectId}`;
return apiClient.delete(url)
.then((response) => {
dispatch({
type: ActionTypes.REMOVE_FROM_COLLECTION,
@ -136,8 +134,8 @@ export function removeFromCollection(collectionId, projectId) {
export function editCollection(collectionId, { name, description }) {
return (dispatch) => {
const url = `${ROOT_URL}/collections/${collectionId}`;
return axios.patch(url, { name, description }, { withCredentials: true })
const url = `/collections/${collectionId}`;
return apiClient.patch(url, { name, description })
.then((response) => {
dispatch({
type: ActionTypes.EDIT_COLLECTION,
@ -159,8 +157,8 @@ export function editCollection(collectionId, { name, description }) {
export function deleteCollection(collectionId) {
return (dispatch) => {
const url = `${ROOT_URL}/collections/${collectionId}`;
return axios.delete(url, { withCredentials: true })
const url = `/collections/${collectionId}`;
return apiClient.delete(url)
.then((response) => {
dispatch({
type: ActionTypes.DELETE_COLLECTION,

View File

@ -1,13 +1,11 @@
import axios from 'axios';
import objectID from 'bson-objectid';
import blobUtil from 'blob-util';
import { reset } from 'redux-form';
import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants';
import { setUnsavedChanges, closeNewFolderModal, closeNewFileModal } from './ide';
import { setProjectSavedTime } from './project';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function appendToFilename(filename, string) {
const dotIndex = filename.lastIndexOf('.');
@ -50,7 +48,7 @@ export function createFile(formProps) {
parentId,
children: []
};
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true })
apiClient.post(`/projects/${state.project.id}/files`, postParams)
.then((response) => {
dispatch({
type: ActionTypes.CREATE_FILE,
@ -106,7 +104,7 @@ export function createFolder(formProps) {
parentId,
fileType: 'folder'
};
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true })
apiClient.post(`/projects/${state.project.id}/files`, postParams)
.then((response) => {
dispatch({
type: ActionTypes.CREATE_FILE,
@ -161,7 +159,7 @@ export function deleteFile(id, parentId) {
parentId
}
};
axios.delete(`${ROOT_URL}/projects/${state.project.id}/files/${id}`, deleteConfig, { withCredentials: true })
apiClient.delete(`/projects/${state.project.id}/files/${id}`, deleteConfig)
.then(() => {
dispatch({
type: ActionTypes.DELETE_FILE,

View File

@ -1,11 +1,8 @@
import axios from 'axios';
import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function updatePreferences(formParams, dispatch) {
axios.put(`${ROOT_URL}/preferences`, formParams, { withCredentials: true })
apiClient.put('/preferences', formParams)
.then(() => {
})
.catch((error) => {

View File

@ -1,8 +1,9 @@
import { browserHistory } from 'react-router';
import axios from 'axios';
import objectID from 'bson-objectid';
import each from 'async/each';
import isEqual from 'lodash/isEqual';
import apiClient from '../../../utils/apiClient';
import getConfig from '../../../utils/getConfig';
import * as ActionTypes from '../../../constants';
import { showToast, setToastText } from './toast';
import {
@ -14,8 +15,7 @@ import {
} from './ide';
import { clearState, saveState } from '../../../persistState';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
const ROOT_URL = getConfig('API_URL');
export function setProject(project) {
return {
@ -52,7 +52,7 @@ export function setNewProject(project) {
export function getProject(id, username) {
return (dispatch, getState) => {
dispatch(justOpenedProject());
axios.get(`${ROOT_URL}/${username}/projects/${id}`, { withCredentials: true })
apiClient.get(`/${username}/projects/${id}`)
.then((response) => {
dispatch(setProject(response.data));
dispatch(setUnsavedChanges(false));
@ -142,7 +142,7 @@ export function saveProject(selectedFile = null, autosave = false) {
fileToUpdate.content = selectedFile.content;
}
if (state.project.id) {
return axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
return apiClient.put(`/projects/${state.project.id}`, formParams)
.then((response) => {
dispatch(endSavingProject());
dispatch(setUnsavedChanges(false));
@ -177,7 +177,7 @@ export function saveProject(selectedFile = null, autosave = false) {
});
}
return axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
return apiClient.post('/projects', formParams)
.then((response) => {
dispatch(endSavingProject());
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
@ -260,7 +260,7 @@ export function cloneProject(id) {
if (!id) {
resolve(getState());
} else {
fetch(`${ROOT_URL}/projects/${id}`)
apiClient.get(`/projects/${id}`)
.then(res => res.json())
.then(data => resolve({
files: data.files,
@ -287,7 +287,7 @@ export function cloneProject(id) {
const formParams = {
url: file.url
};
axios.post(`${ROOT_URL}/S3/copy`, formParams, { withCredentials: true })
apiClient.post('/S3/copy', formParams)
.then((response) => {
file.url = response.data.url;
callback(null);
@ -298,7 +298,7 @@ export function cloneProject(id) {
}, (err) => {
// if not errors in duplicating the files on S3, then duplicate it
const formParams = Object.assign({}, { name: `${state.project.name} copy` }, { files: newFiles });
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
apiClient.post('/projects', formParams)
.then((response) => {
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
dispatch(setNewProject(response.data));
@ -337,7 +337,7 @@ export function setProjectSavedTime(updatedAt) {
export function changeProjectName(id, newName) {
return (dispatch, getState) => {
const state = getState();
axios.put(`${ROOT_URL}/projects/${id}`, { name: newName }, { withCredentials: true })
apiClient.put(`/projects/${id}`, { name: newName })
.then((response) => {
if (response.status === 200) {
dispatch({
@ -364,7 +364,7 @@ export function changeProjectName(id, newName) {
export function deleteProject(id) {
return (dispatch, getState) => {
axios.delete(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
apiClient.delete(`/projects/${id}`)
.then(() => {
const state = getState();
if (id === state.project.id) {

View File

@ -1,21 +1,18 @@
import axios from 'axios';
import apiClient from '../../../utils/apiClient';
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;
// eslint-disable-next-line
export function getProjects(username) {
return (dispatch) => {
dispatch(startLoader());
let url;
if (username) {
url = `${ROOT_URL}/${username}/projects`;
url = `/${username}/projects`;
} else {
url = `${ROOT_URL}/projects`;
url = '/projects';
}
axios.get(url, { withCredentials: true })
apiClient.get(url)
.then((response) => {
dispatch({
type: ActionTypes.SET_PROJECTS,

View File

@ -1,11 +1,10 @@
import axios from 'axios';
import apiClient from '../../../utils/apiClient';
import getConfig from '../../../utils/getConfig';
import { createFile } from './files';
import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils';
const __process = (typeof global !== 'undefined' ? global : window).process;
const s3BucketHttps = __process.env.S3_BUCKET_URL_BASE ||
`https://s3-${__process.env.AWS_REGION}.amazonaws.com/${__process.env.S3_BUCKET}/`;
const ROOT_URL = __process.env.API_URL;
const s3BucketHttps = getConfig('S3_BUCKET_URL_BASE') ||
`https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`;
const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB
function localIntercept(file, options = {}) {
@ -46,18 +45,13 @@ export function dropzoneAcceptCallback(userId, file, done) {
});
} else {
file.postData = []; // eslint-disable-line
axios.post(
`${ROOT_URL}/S3/sign`, {
name: file.name,
type: file.type,
size: file.size,
userId
apiClient.post('/S3/sign', {
name: file.name,
type: file.type,
size: file.size,
userId
// _csrf: document.getElementById('__createPostToken').value
},
{
withCredentials: true
}
)
})
.then((response) => {
file.custom_status = 'ready'; // eslint-disable-line
file.postData = response.data; // eslint-disable-line

View File

@ -3,8 +3,9 @@ import React from 'react';
import { connect } from 'react-redux';
import prettyBytes from 'pretty-bytes';
const __process = (typeof global !== 'undefined' ? global : window).process;
const limit = __process.env.UPLOAD_LIMIT || 250000000;
import getConfig from '../../../utils/getConfig';
const limit = getConfig('UPLOAD_LIMIT') || 250000000;
const MAX_SIZE_B = limit;
const formatPercent = (percent) => {

View File

@ -4,11 +4,11 @@ import Dropzone from 'dropzone';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as UploaderActions from '../actions/uploader';
import getConfig from '../../../utils/getConfig';
import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils';
const __process = (typeof global !== 'undefined' ? global : window).process;
const s3Bucket = __process.env.S3_BUCKET_URL_BASE ||
`https://s3-${__process.env.AWS_REGION}.amazonaws.com/${__process.env.S3_BUCKET}/`;
const s3Bucket = getConfig('S3_BUCKET_URL_BASE') ||
`https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`;
class FileUploader extends React.Component {
componentDidMount() {

View File

@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import prettyBytes from 'pretty-bytes';
import getConfig from '../../../utils/getConfig';
import FileUploader from './FileUploader';
import { getreachedTotalSizeLimit } from '../selectors/users';
import ExitIcon from '../../../images/exit.svg';
const __process = (typeof global !== 'undefined' ? global : window).process;
const limit = __process.env.UPLOAD_LIMIT || 250000000;
const limit = getConfig('UPLOAD_LIMIT') || 250000000;
const limitText = prettyBytes(limit);
class UploadFileModal extends React.Component {

View File

@ -1,10 +1,10 @@
import { createSelector } from 'reselect';
import getConfig from '../../../utils/getConfig';
const __process = (typeof global !== 'undefined' ? global : window).process;
const getAuthenticated = state => state.user.authenticated;
const getTotalSize = state => state.user.totalSize;
const getAssetsTotalSize = state => state.assets.totalSize;
const limit = __process.env.UPLOAD_LIMIT || 250000000;
const limit = getConfig('UPLOAD_LIMIT') || 250000000;
export const getCanUploadMedia = createSelector(
getAuthenticated,

View File

@ -1,13 +1,9 @@
import { browserHistory } from 'react-router';
import axios from 'axios';
import * as ActionTypes from '../../constants';
import apiClient from '../../utils/apiClient';
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
import { showToast, setToastText } from '../IDE/actions/toast';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
export function authError(error) {
return {
type: ActionTypes.AUTH_ERROR,
@ -17,7 +13,7 @@ export function authError(error) {
export function signUpUser(previousPath, formValues) {
return (dispatch) => {
axios.post(`${ROOT_URL}/signup`, formValues, { withCredentials: true })
apiClient.post('/signup', formValues)
.then((response) => {
dispatch({
type: ActionTypes.AUTH_USER,
@ -34,7 +30,7 @@ export function signUpUser(previousPath, formValues) {
}
export function loginUser(formValues) {
return axios.post(`${ROOT_URL}/login`, formValues, { withCredentials: true });
return apiClient.post('/login', formValues);
}
export function loginUserSuccess(user) {
@ -74,7 +70,7 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
export function getUser() {
return (dispatch) => {
axios.get(`${ROOT_URL}/session`, { withCredentials: true })
apiClient.get('/session')
.then((response) => {
dispatch({
type: ActionTypes.AUTH_USER,
@ -95,7 +91,7 @@ export function getUser() {
export function validateSession() {
return (dispatch, getState) => {
axios.get(`${ROOT_URL}/session`, { withCredentials: true })
apiClient.get('/session')
.then((response) => {
const state = getState();
if (state.user.username !== response.data.username) {
@ -113,7 +109,7 @@ export function validateSession() {
export function logoutUser() {
return (dispatch) => {
axios.get(`${ROOT_URL}/logout`, { withCredentials: true })
apiClient.get('/logout')
.then(() => {
dispatch({
type: ActionTypes.UNAUTH_USER
@ -131,7 +127,7 @@ export function initiateResetPassword(formValues) {
dispatch({
type: ActionTypes.RESET_PASSWORD_INITIATE
});
axios.post(`${ROOT_URL}/reset-password`, formValues, { withCredentials: true })
apiClient.post('/reset-password', formValues)
.then(() => {
// do nothing
})
@ -150,7 +146,7 @@ export function initiateVerification() {
dispatch({
type: ActionTypes.EMAIL_VERIFICATION_INITIATE
});
axios.post(`${ROOT_URL}/verify/send`, {}, { withCredentials: true })
apiClient.post('/verify/send', {})
.then(() => {
// do nothing
})
@ -170,7 +166,7 @@ export function verifyEmailConfirmation(token) {
type: ActionTypes.EMAIL_VERIFICATION_VERIFY,
state: 'checking',
});
return axios.get(`${ROOT_URL}/verify?t=${token}`, {}, { withCredentials: true })
return apiClient.get(`/verify?t=${token}`, {})
.then(response => dispatch({
type: ActionTypes.EMAIL_VERIFICATION_VERIFIED,
message: response.data,
@ -194,7 +190,7 @@ export function resetPasswordReset() {
export function validateResetPasswordToken(token) {
return (dispatch) => {
axios.get(`${ROOT_URL}/reset-password/${token}`)
apiClient.get(`/reset-password/${token}`)
.then(() => {
// do nothing if the token is valid
})
@ -206,7 +202,7 @@ export function validateResetPasswordToken(token) {
export function updatePassword(token, formValues) {
return (dispatch) => {
axios.post(`${ROOT_URL}/reset-password/${token}`, formValues)
apiClient.post(`/reset-password/${token}`, formValues)
.then((response) => {
dispatch(loginUserSuccess(response.data));
browserHistory.push('/');
@ -226,7 +222,7 @@ export function updateSettingsSuccess(user) {
export function updateSettings(formValues) {
return dispatch =>
axios.put(`${ROOT_URL}/account`, formValues, { withCredentials: true })
apiClient.put('/account', formValues)
.then((response) => {
dispatch(updateSettingsSuccess(response.data));
browserHistory.push('/');
@ -248,7 +244,7 @@ export function createApiKeySuccess(user) {
export function createApiKey(label) {
return dispatch =>
axios.post(`${ROOT_URL}/account/api-keys`, { label }, { withCredentials: true })
apiClient.post('/account/api-keys', { label })
.then((response) => {
dispatch(createApiKeySuccess(response.data));
})
@ -260,7 +256,7 @@ export function createApiKey(label) {
export function removeApiKey(keyId) {
return dispatch =>
axios.delete(`${ROOT_URL}/account/api-keys/${keyId}`, { withCredentials: true })
apiClient.delete(`/account/api-keys/${keyId}`)
.then((response) => {
dispatch({
type: ActionTypes.API_KEY_REMOVED,

View File

@ -3,18 +3,15 @@ import React from 'react';
import { reduxForm } from 'redux-form';
import { bindActionCreators } from 'redux';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import axios from 'axios';
import { Helmet } from 'react-helmet';
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
import AccountForm from '../components/AccountForm';
import apiClient from '../../../utils/apiClient';
import { validateSettings } from '../../../utils/reduxFormUtils';
import SocialAuthButton from '../components/SocialAuthButton';
import APIKeyForm from '../components/APIKeyForm';
import Nav from '../../../components/Nav';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function SocialLoginPanel(props) {
return (
<React.Fragment>
@ -96,7 +93,7 @@ function asyncValidate(formProps, dispatch, props) {
const queryParams = {};
queryParams[fieldToValidate] = formProps[fieldToValidate];
queryParams.check_type = fieldToValidate;
return axios.get(`${ROOT_URL}/signup/duplicate_check`, { params: queryParams })
return apiClient.get('/signup/duplicate_check', { params: queryParams })
.then((response) => {
if (response.data.exists) {
const error = {};

View File

@ -1,19 +1,16 @@
import PropTypes from 'prop-types';
import React from 'react';
import { bindActionCreators } from 'redux';
import axios from 'axios';
import { Link, browserHistory } from 'react-router';
import { Helmet } from 'react-helmet';
import { reduxForm } from 'redux-form';
import * as UserActions from '../actions';
import SignupForm from '../components/SignupForm';
import apiClient from '../../../utils/apiClient';
import { validateSignup } from '../../../utils/reduxFormUtils';
import SocialAuthButton from '../components/SocialAuthButton';
import Nav from '../../../components/Nav';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
class SignupView extends React.Component {
gotoHomePage = () => {
browserHistory.push('/');
@ -86,7 +83,7 @@ function asyncValidate(formProps, dispatch, props) {
const queryParams = {};
queryParams[fieldToValidate] = formProps[fieldToValidate];
queryParams.check_type = fieldToValidate;
return axios.get(`${ROOT_URL}/signup/duplicate_check`, { params: queryParams })
return apiClient.get('/signup/duplicate_check', { params: queryParams })
.then((response) => {
if (response.data.exists) {
errors[fieldToValidate] = response.data.message;

View File

@ -3,15 +3,14 @@ import thunk from 'redux-thunk';
import DevTools from './modules/App/components/DevTools';
import rootReducer from './reducers';
import { clearState, loadState } from './persistState';
const __process = (typeof global !== 'undefined' ? global : window).process;
import getConfig from './utils/getConfig';
export default function configureStore(initialState) {
const enhancers = [
applyMiddleware(thunk),
];
if (__process.env.CLIENT && __process.env.NODE_ENV === 'development') {
if (getConfig('CLIENT') && getConfig('NODE_ENV') === 'development') {
// Enable DevTools only when rendering on client and during development.
enhancers.push(window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument());
}

17
client/utils/apiClient.js Normal file
View File

@ -0,0 +1,17 @@
import axios from 'axios';
import getConfig from './getConfig';
const ROOT_URL = getConfig('API_URL');
/**
* Configures an Axios instance with the correct API URL
*/
function createClientInstance() {
return axios.create({
baseURL: ROOT_URL,
withCredentials: true
});
}
export default createClientInstance();

View File

@ -60,7 +60,7 @@ export const getAllScriptOffsets = (htmlFile) => {
if (ind === -1) {
foundJSScript = false;
} else {
endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 3);
endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 1);
filename = htmlFile.substring(ind + startTag.length, endFilenameInd);
lineOffset = htmlFile.substring(0, ind).split('\n').length + hijackConsoleErrorsScriptLength;
offs.push([lineOffset, filename]);

24
client/utils/getConfig.js Normal file
View File

@ -0,0 +1,24 @@
function isTestEnvironment() {
// eslint-disable-next-line no-use-before-define
return getConfig('NODE_ENV', { warn: false }) === 'test';
}
/**
* Returns config item from environment
*/
function getConfig(key, options = { warn: !isTestEnvironment() }) {
if (key == null) {
throw new Error('"key" must be provided to getConfig()');
}
const env = (typeof global !== 'undefined' ? global : window)?.process?.env || {};
const value = env[key];
if (value == null && options?.warn !== false) {
console.warn(`getConfig("${key}") returned null`);
}
return value;
}
export default getConfig;

View File

@ -0,0 +1,28 @@
import getConfig from './getConfig';
describe('utils/getConfig()', () => {
beforeEach(() => {
delete global.process.env.CONFIG_TEST_KEY_NAME;
delete window.process.env.CONFIG_TEST_KEY_NAME;
});
it('throws if key is not defined', () => {
expect(() => getConfig(/* key is missing */)).toThrow(/must be provided/);
});
it('fetches from global.process', () => {
global.process.env.CONFIG_TEST_KEY_NAME = 'editor.p5js.org';
expect(getConfig('CONFIG_TEST_KEY_NAME')).toBe('editor.p5js.org');
});
it('fetches from window.process', () => {
window.process.env.CONFIG_TEST_KEY_NAME = 'editor.p5js.org';
expect(getConfig('CONFIG_TEST_KEY_NAME')).toBe('editor.p5js.org');
});
it('warns but does not throw if no value found', () => {
expect(() => getConfig('CONFIG_TEST_KEY_NAME')).not.toThrow();
});
});

View File

@ -13,19 +13,19 @@ This project release guide is based on
1. `$ git checkout develop`
2. `$ git checkout -b release-<newversion>`
3. Do all of the release branch testing necessary. This could be as simple as running `npm test:ci`, or it could take user testing over a few days.
4. `$ git checkout release`
5. `$ git merge --no-ff release-<newversion>`
6. `$ npm version <newversion>` (see [npm-version](https://docs.npmjs.com/cli/version) for valid values of <newversion>).
4. `$ npm version <newversion>` (see [npm-version](https://docs.npmjs.com/cli/version) for valid values of <newversion>).
5. `$ git checkout release`
6. `$ git merge --no-ff release-<newversion>`
7. `$ git push && git push --tags`
8. `$ git checkout develop`
9. `$ git merge --no-ff release-<newversion>`
10. Create a release on GitHub. You can do this in one of two ways:
9. Create a release on GitHub. Make sure that you release from the `release` branch! You can do this in one of two ways:
1. (Preferred) Use the [`hub` command line tool](https://hub.github.com/). You can automate adding all commit messages since the last release with the following command:
```sh
$ hub release create -d -m "<newversion>" -m "$(git log `git describe --tags --abbrev=0 HEAD^`..HEAD --oneline)" <newversion>`
```
Note that this creates a draft release, which you can then edit on GitHub. This allows you to create release notes from the list of commit messages, but then edit these notes as you wish.
2. [Draft a new release on Github](https://github.com/processing/p5.js-web-editor/releases/new).
10. `$ git merge --no-ff release-<newversion>`
Travis CI will automatically deploy the release to production, as well as push a production tagged Docker image to DockerHub.

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "p5.js-web-editor",
"version": "0.0.1",
"version": "1.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "p5.js-web-editor",
"version": "0.0.1",
"version": "1.0.3",
"description": "The web editor for p5.js.",
"scripts": {
"clean": "rimraf dist",