diff --git a/client/components/forceProtocol.jsx b/client/components/forceProtocol.jsx index dda5451c..3009d085 100644 --- a/client/components/forceProtocol.jsx +++ b/client/components/forceProtocol.jsx @@ -1,6 +1,27 @@ -import React, { PropTypes } from 'react'; +import React from 'react'; import { format, parse } from 'url'; +const findCurrentProtocol = () => ( + parse(window.location.href).protocol +); + +const redirectToProtocol = (protocol, { appendSource, disable = false } = {}) => { + const currentProtocol = findCurrentProtocol(); + + if (protocol !== currentProtocol) { + if (disable === true) { + console.info(`forceProtocol: would have redirected from "${currentProtocol}" to "${protocol}"`); + } else { + const url = parse(window.location.href, true /* parse query string */); + url.protocol = protocol; + if (appendSource === true) { + url.query.source = currentProtocol; + } + window.location = format(url); + } + } +}; + /** * A Higher Order Component that forces the protocol to change on mount * @@ -14,29 +35,12 @@ const forceProtocol = ({ targetProtocol = 'https', sourceProtocol, disable = fal static propTypes = {} componentDidMount() { - this.redirectToProtocol(targetProtocol, { appendSource: true }); + redirectToProtocol(targetProtocol, { appendSource: true, disable }); } componentWillUnmount() { if (sourceProtocol != null) { - this.redirectToProtocol(sourceProtocol, { appendSource: false }); - } - } - - redirectToProtocol(protocol, { appendSource }) { - const currentProtocol = parse(window.location.href).protocol; - - if (protocol !== currentProtocol) { - if (disable === true) { - console.info(`forceProtocol: would have redirected from "${currentProtocol}" to "${protocol}"`); - } else { - const url = parse(window.location.href, true /* parse query string */); - url.protocol = protocol; - if (appendSource === true) { - url.query.source = currentProtocol; - } - window.location = format(url); - } + redirectToProtocol(sourceProtocol, { appendSource: false, disable }); } } @@ -65,6 +69,8 @@ const findSourceProtocol = (state, location) => { export default forceProtocol; export { + findCurrentProtocol, findSourceProtocol, + redirectToProtocol, protocols, }; diff --git a/client/constants.js b/client/constants.js index e3060237..149dc79c 100644 --- a/client/constants.js +++ b/client/constants.js @@ -26,6 +26,7 @@ export const AUTH_ERROR = 'AUTH_ERROR'; export const SETTINGS_UPDATED = 'SETTINGS_UPDATED'; export const SET_PROJECT_NAME = 'SET_PROJECT_NAME'; +export const SET_SERVE_SECURE = 'SET_SERVE_SECURE'; export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS'; export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL'; @@ -113,3 +114,6 @@ export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL'; export const PERSIST_STATE = 'PERSIST_STATE'; export const CLEAR_PERSISTED_STATE = 'CLEAR_PERSISTED_STATE'; + +export const SHOW_HELP_MODAL = 'SHOW_HELP_MODAL'; +export const HIDE_HELP_MODAL = 'HIDE_HELP_MODAL'; diff --git a/client/images/help.svg b/client/images/help.svg new file mode 100644 index 00000000..b812feb7 --- /dev/null +++ b/client/images/help.svg @@ -0,0 +1,7 @@ + diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js index d277ea18..4f5c0662 100644 --- a/client/modules/IDE/actions/ide.js +++ b/client/modules/IDE/actions/ide.js @@ -220,3 +220,16 @@ export function hideErrorModal() { type: ActionTypes.HIDE_ERROR_MODAL }; } + +export function showHelpModal(helpType) { + return { + type: ActionTypes.SHOW_HELP_MODAL, + helpType + }; +} + +export function hideHelpModal() { + return { + type: ActionTypes.HIDE_HELP_MODAL + }; +} diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js index c5abc528..4a9ac140 100644 --- a/client/modules/IDE/actions/project.js +++ b/client/modules/IDE/actions/project.js @@ -9,10 +9,18 @@ import { setUnsavedChanges, resetJustOpenedProject, showErrorModal } from './ide'; import { clearState, saveState } from '../../../persistState'; +import { redirectToProtocol, protocols } from '../../../components/forceProtocol'; const ROOT_URL = process.env.API_URL; export function setProject(project) { + const targetProtocol = project.serveSecure === true ? + protocols.https : + protocols.http; + + // This will not reload if on same protocol + redirectToProtocol(targetProtocol); + return { type: ActionTypes.SET_PROJECT, project, @@ -66,12 +74,12 @@ export function saveProject(autosave = false) { return (dispatch, getState) => { const state = getState(); if (state.user.id && state.project.owner && state.project.owner.id !== state.user.id) { - return; + return Promise.reject(); } const formParams = Object.assign({}, state.project); formParams.files = [...state.files]; if (state.project.id) { - axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true }) + return axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true }) .then((response) => { dispatch(setUnsavedChanges(false)); console.log(response.data); @@ -103,41 +111,41 @@ export function saveProject(autosave = false) { }); } }); - } else { - axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true }) - .then((response) => { - dispatch(setUnsavedChanges(false)); - dispatch(setProject(response.data)); - browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`); - dispatch({ - type: ActionTypes.NEW_PROJECT, - project: response.data, - owner: response.data.user, - files: response.data.files - }); - if (!autosave) { - if (state.preferences.autosave) { - dispatch(showToast(5500)); - dispatch(setToastText('Project saved.')); - setTimeout(() => dispatch(setToastText('Autosave enabled.')), 1500); - dispatch(resetJustOpenedProject()); - } else { - dispatch(showToast(1500)); - dispatch(setToastText('Project saved.')); - } - } - }) - .catch((response) => { - if (response.status === 403) { - dispatch(showErrorModal('staleSession')); - } else { - dispatch({ - type: ActionTypes.PROJECT_SAVE_FAIL, - error: response.data - }); - } - }); } + + return axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true }) + .then((response) => { + dispatch(setUnsavedChanges(false)); + dispatch(setProject(response.data)); + browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`); + dispatch({ + type: ActionTypes.NEW_PROJECT, + project: response.data, + owner: response.data.user, + files: response.data.files + }); + if (!autosave) { + if (state.preferences.autosave) { + dispatch(showToast(5500)); + dispatch(setToastText('Project saved.')); + setTimeout(() => dispatch(setToastText('Autosave enabled.')), 1500); + dispatch(resetJustOpenedProject()); + } else { + dispatch(showToast(1500)); + dispatch(setToastText('Project saved.')); + } + } + }) + .catch((response) => { + if (response.status === 403) { + dispatch(showErrorModal('staleSession')); + } else { + dispatch({ + type: ActionTypes.PROJECT_SAVE_FAIL, + error: response.data + }); + } + }); }; } @@ -249,6 +257,24 @@ export function cloneProject() { }; } +export function setServeSecure(serveSecure, { redirect = true } = {}) { + return (dispatch, getState) => { + dispatch({ + type: ActionTypes.SET_SERVE_SECURE, + serveSecure + }); + + if (redirect === true) { + dispatch(saveProject(false /* autosave */)) + .then( + () => redirectToProtocol(serveSecure === true ? protocols.https : protocols.http) + ); + } + + return null; + }; +} + export function showEditProjectName() { return { type: ActionTypes.SHOW_EDIT_PROJECT_NAME diff --git a/client/modules/IDE/components/HelpModal.jsx b/client/modules/IDE/components/HelpModal.jsx new file mode 100644 index 00000000..df0adf24 --- /dev/null +++ b/client/modules/IDE/components/HelpModal.jsx @@ -0,0 +1,61 @@ +import React, { PropTypes } from 'react'; +import InlineSVG from 'react-inlinesvg'; + +const exitUrl = require('../../../images/exit.svg'); + +const helpContent = { + serveSecure: { + title: 'Serve over HTTPS', + body: ( +
Use the checkbox to choose whether this sketch should be loaded using HTTPS or HTTP.
+You should choose HTTPS if you need to:
+Choose HTTP if you need to:
+