diff --git a/.eslintrc b/.eslintrc index 17527587..6be3a63f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,7 +37,8 @@ "react/prefer-stateless-function": [2, { "ignorePureComponents": true }], - "class-methods-use-this": 0 + "class-methods-use-this": 0, + "react/jsx-no-bind": [2, {"allowBind": true, "allowArrowFunctions": true}] }, "plugins": [ "react", "jsx-a11y", "import" diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index 0a861e97..f19c6628 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -1,167 +1,463 @@ import React, { PropTypes } from 'react'; import { Link } from 'react-router'; +import InlineSVG from 'react-inlinesvg'; +import classNames from 'classnames'; + +import { + metaKeyName, +} from '../utils/metaKey'; + +const triangleUrl = require('../images/down-filled-triangle.svg'); +const logoUrl = require('../images/p5js-logo-small.svg'); class Nav extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + dropdownOpen: 'none' + }; + this.handleFocus = this.handleFocus.bind(this); + this.handleBlur = this.handleBlur.bind(this); + this.clearHideTimeout = this.clearHideTimeout.bind(this); + } + + setDropdown(dropdown) { + this.setState({ + dropdownOpen: dropdown + }); + } + + toggleDropdown(dropdown) { + if (this.state.dropdownOpen === 'none') { + this.setState({ + dropdownOpen: dropdown + }); + } else { + this.setState({ + dropdownOpen: 'none' + }); + } + } + + isUserOwner() { + return this.props.project.owner && this.props.project.owner.id === this.props.user.id; + } + + handleFocus(dropdown) { + this.clearHideTimeout(); + this.setDropdown(dropdown); + } + + clearHideTimeout() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + } + + handleBlur() { + this.timer = setTimeout(this.setDropdown.bind(this, 'none'), 10); + } + render() { + const navDropdownState = { + file: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'file' + }), + edit: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'edit' + }), + sketch: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'sketch' + }), + help: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'help' + }), + account: classNames({ + 'nav__item': true, + 'nav__item--open': this.state.dropdownOpen === 'account' + }) + }; return ( ); } @@ -196,14 +493,25 @@ Nav.propTypes = { showShareModal: PropTypes.func.isRequired, showErrorModal: PropTypes.func.isRequired, unsavedChanges: PropTypes.bool.isRequired, - warnIfUnsavedChanges: PropTypes.func.isRequired + warnIfUnsavedChanges: PropTypes.func.isRequired, + showKeyboardShortcutModal: PropTypes.func.isRequired, + cmController: PropTypes.shape({ + tidyCode: PropTypes.func, + showFind: PropTypes.func, + findNext: PropTypes.func, + findPrev: PropTypes.func + }), + startSketch: PropTypes.func.isRequired, + stopSketch: PropTypes.func.isRequired, + setAllAccessibleOutput: PropTypes.func.isRequired }; Nav.defaultProps = { project: { id: undefined, owner: undefined - } + }, + cmController: {} }; export default Nav; diff --git a/client/images/down-arrow-white.svg b/client/images/down-arrow-white.svg new file mode 100644 index 00000000..34efd84b --- /dev/null +++ b/client/images/down-arrow-white.svg @@ -0,0 +1,18 @@ + + + + arrow-shape-copy-2 + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/images/down-filled-triangle.svg b/client/images/down-filled-triangle.svg new file mode 100644 index 00000000..b673aafe --- /dev/null +++ b/client/images/down-filled-triangle.svg @@ -0,0 +1,10 @@ + + + + Triangle + Created with Sketch. + + + + + \ No newline at end of file diff --git a/client/images/p5js-logo-small.svg b/client/images/p5js-logo-small.svg new file mode 100644 index 00000000..164ef67f --- /dev/null +++ b/client/images/p5js-logo-small.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/client/images/right-arrow-white.svg b/client/images/right-arrow-white.svg new file mode 100644 index 00000000..b789f93d --- /dev/null +++ b/client/images/right-arrow-white.svg @@ -0,0 +1,16 @@ + + + + arrow-shape-copy + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/client/modules/App/components/Overlay.jsx b/client/modules/App/components/Overlay.jsx index 8951df64..aa016950 100644 --- a/client/modules/App/components/Overlay.jsx +++ b/client/modules/App/components/Overlay.jsx @@ -57,14 +57,15 @@ Overlay.propTypes = { closeOverlay: PropTypes.func, title: PropTypes.string, ariaLabel: PropTypes.string, - previousPath: PropTypes.string.isRequired + previousPath: PropTypes.string }; Overlay.defaultProps = { children: null, title: 'Modal', closeOverlay: null, - ariaLabel: 'modal' + ariaLabel: 'modal', + previousPath: '/' }; export default Overlay; diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js index 66146f00..64226b73 100644 --- a/client/modules/IDE/actions/ide.js +++ b/client/modules/IDE/actions/ide.js @@ -1,12 +1,13 @@ import * as ActionTypes from '../../../constants'; +import { clearConsole } from './console'; -export function startSketch() { +export function startVisualSketch() { return { type: ActionTypes.START_SKETCH }; } -export function stopSketch() { +export function stopVisualSketch() { return { type: ActionTypes.STOP_SKETCH }; @@ -20,7 +21,7 @@ export function startRefreshSketch() { export function startSketchAndRefresh() { return (dispatch) => { - dispatch(startSketch()); + dispatch(startVisualSketch()); dispatch(startRefreshSketch()); }; } @@ -245,3 +246,25 @@ export function showRuntimeErrorWarning() { type: ActionTypes.SHOW_RUNTIME_ERROR_WARNING }; } + +export function startSketch() { + return (dispatch) => { + dispatch(clearConsole()); + dispatch(startSketchAndRefresh()); + }; +} + +export function startAccessibleSketch() { + return (dispatch) => { + dispatch(clearConsole()); + dispatch(startAccessibleOutput()); + dispatch(startSketchAndRefresh()); + }; +} + +export function stopSketch() { + return (dispatch) => { + dispatch(stopAccessibleOutput()); + dispatch(stopVisualSketch()); + }; +} diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js index 4f735280..46182fa5 100644 --- a/client/modules/IDE/actions/preferences.js +++ b/client/modules/IDE/actions/preferences.js @@ -216,3 +216,12 @@ export function setAutorefresh(value) { } }; } + +export function setAllAccessibleOutput(value) { + return (dispatch) => { + dispatch(setTextOutput(value)); + dispatch(setGridOutput(value)); + dispatch(setSoundOutput(value)); + }; +} + diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index f97cbfa6..befb4efc 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -7,6 +7,11 @@ import 'codemirror/addon/lint/lint'; import 'codemirror/addon/lint/javascript-lint'; import 'codemirror/addon/lint/css-lint'; import 'codemirror/addon/lint/html-lint'; +import 'codemirror/addon/fold/brace-fold'; +import 'codemirror/addon/fold/comment-fold'; +import 'codemirror/addon/fold/foldcode'; +import 'codemirror/addon/fold/foldgutter'; +import 'codemirror/addon/fold/indent-fold'; import 'codemirror/addon/comment/comment'; import 'codemirror/keymap/sublime'; import 'codemirror/addon/search/searchcursor'; @@ -40,7 +45,6 @@ window.CSSLint = CSSLint; window.HTMLHint = HTMLHint; const beepUrl = require('../../../sounds/audioAlert.mp3'); -const downArrowUrl = require('../../../images/down-arrow.svg'); const unsavedChangesDotUrl = require('../../../images/unsaved-changes-dot.svg'); const rightArrowUrl = require('../../../images/right-arrow.svg'); const leftArrowUrl = require('../../../images/left-arrow.svg'); @@ -61,7 +65,11 @@ class Editor extends React.Component { this.beep.play(); } }, 2000); + this.showFind = this.showFind.bind(this); + this.findNext = this.findNext.bind(this); + this.findPrev = this.findPrev.bind(this); } + componentDidMount() { this.beep = new Audio(beepUrl); this.widgets = []; @@ -72,7 +80,9 @@ class Editor extends React.Component { inputStyle: 'contenteditable', lineWrapping: false, fixedGutter: false, - gutters: ['CodeMirror-lint-markers'], + foldGutter: true, + foldOptions: { widget: '\u2026' }, + gutters: ['CodeMirror-foldgutter'], keyMap: 'sublime', highlightSelectionMatches: true, // highlight current search match lint: { @@ -83,7 +93,8 @@ class Editor extends React.Component { options: { 'asi': true, 'eqeqeq': false, - '-W041': false + '-W041': false, + 'esversion': 6 } } }); @@ -123,6 +134,13 @@ class Editor extends React.Component { this._cm.getWrapperElement().style['font-size'] = `${this.props.fontSize}px`; this._cm.setOption('indentWithTabs', this.props.isTabIndent); this._cm.setOption('tabSize', this.props.indentationAmount); + + this.props.provideController({ + tidyCode: this.tidyCode, + showFind: this.showFind, + findNext: this.findNext, + findPrev: this.findPrev + }); } componentWillUpdate(nextProps) { @@ -178,6 +196,7 @@ class Editor extends React.Component { componentWillUnmount() { this._cm = null; + this.props.provideController(null); } getFileMode(fileName) { @@ -221,6 +240,20 @@ class Editor extends React.Component { } } + showFind() { + this._cm.execCommand('findPersistent'); + } + + findNext() { + this._cm.focus(); + this._cm.execCommand('findNext'); + } + + findPrev() { + this._cm.focus(); + this._cm.execCommand('findPrev'); + } + toggleEditorOptions() { if (this.props.editorOptionsVisible) { this.props.closeEditorOptions(); @@ -270,26 +303,6 @@ class Editor extends React.Component { isUserOwner={this.props.isUserOwner} /> - -
{ this.codemirrorContainer = element; }} className="editor-holder" tabIndex="0">
@@ -327,7 +340,6 @@ Editor.propTypes = { editorOptionsVisible: PropTypes.bool.isRequired, showEditorOptions: PropTypes.func.isRequired, closeEditorOptions: PropTypes.func.isRequired, - showKeyboardShortcutModal: PropTypes.func.isRequired, setUnsavedChanges: PropTypes.func.isRequired, startRefreshSketch: PropTypes.func.isRequired, autorefresh: PropTypes.bool.isRequired, @@ -348,6 +360,7 @@ Editor.propTypes = { showRuntimeErrorWarning: PropTypes.func.isRequired, hideRuntimeErrorWarning: PropTypes.func.isRequired, runtimeErrorWarningVisible: PropTypes.bool.isRequired, + provideController: PropTypes.func.isRequired }; Editor.defaultProps = { diff --git a/client/modules/IDE/components/GridOutput.jsx b/client/modules/IDE/components/GridOutput.jsx index f1e855d9..3f7dcd1b 100644 --- a/client/modules/IDE/components/GridOutput.jsx +++ b/client/modules/IDE/components/GridOutput.jsx @@ -10,24 +10,24 @@ class GridOutput extends React.Component { id="gridOutput-content" ref={(element) => { this.GridOutputModal = element; }} > -

Grid Output

+

table Output

diff --git a/client/modules/IDE/components/HTTPSModal.jsx b/client/modules/IDE/components/HTTPSModal.jsx new file mode 100644 index 00000000..6240dd51 --- /dev/null +++ b/client/modules/IDE/components/HTTPSModal.jsx @@ -0,0 +1,24 @@ +import React from 'react'; + +function HTTPSModal() { + return ( +
+
+
+

Use the checkbox to choose whether this sketch should be loaded using HTTPS or HTTP.

+

You should choose HTTPS if you need to:

+
    +
  • access a webcam or microphone
  • +
  • access an API served over HTTPS
  • +
+

Choose HTTP if you need to:

+
    +
  • access an API served over HTTP
  • +
+
+
+
+ ); +} + +export default HTTPSModal; diff --git a/client/modules/IDE/components/HelpModal.jsx b/client/modules/IDE/components/HelpModal.jsx deleted file mode 100644 index df0adf24..00000000 --- a/client/modules/IDE/components/HelpModal.jsx +++ /dev/null @@ -1,61 +0,0 @@ -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:

- -
- ) - } -}; - -const fallbackContent = { - title: 'No content for this topic', - body: null, -}; - -class HelpModal extends React.Component { - componentDidMount() { - this.shareModal.focus(); - } - render() { - const content = helpContent[this.props.type] == null ? - fallbackContent : - helpContent[this.props.type]; - - return ( -
{ this.shareModal = element; }} tabIndex="0"> -
-

{content.title}

- -
-
- {content.body} -
-
- ); - } -} - -HelpModal.propTypes = { - type: PropTypes.string.isRequired, - closeModal: PropTypes.func.isRequired, -}; - -export default HelpModal; diff --git a/client/modules/IDE/components/KeyboardShortcutModal.jsx b/client/modules/IDE/components/KeyboardShortcutModal.jsx index 0eb399ca..8b542efc 100644 --- a/client/modules/IDE/components/KeyboardShortcutModal.jsx +++ b/client/modules/IDE/components/KeyboardShortcutModal.jsx @@ -8,7 +8,7 @@ function KeyboardShortcutModal() { return ( ); diff --git a/client/modules/IDE/components/PreviewFrame.jsx b/client/modules/IDE/components/PreviewFrame.jsx index 314090c7..5627a7a9 100644 --- a/client/modules/IDE/components/PreviewFrame.jsx +++ b/client/modules/IDE/components/PreviewFrame.jsx @@ -88,6 +88,7 @@ class PreviewFrame extends React.Component { } window.addEventListener('message', (messageEvent) => { + console.log(messageEvent); messageEvent.data.forEach((message) => { const args = message.arguments; Object.keys(args).forEach((key) => { @@ -152,6 +153,14 @@ class PreviewFrame extends React.Component { doc.srcDoc = ''; } + addLoopProtect(sketchDoc) { + const scriptsInHTML = sketchDoc.getElementsByTagName('script'); + const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML); + scriptsInHTMLArray.forEach((script) => { + script.innerHTML = loopProtect(script.innerHTML); // eslint-disable-line + }); + } + injectLocalFiles() { const htmlFile = this.props.htmlFile.content; let scriptOffs = []; @@ -227,7 +236,7 @@ class PreviewFrame extends React.Component { scriptOffs = getAllScriptOffsets(sketchDocString); const consoleErrorsScript = sketchDoc.createElement('script'); consoleErrorsScript.innerHTML = hijackConsoleErrorsScript(JSON.stringify(scriptOffs)); - // sketchDoc.head.appendChild(consoleErrorsScript); + this.addLoopProtect(sketchDoc); sketchDoc.head.insertBefore(consoleErrorsScript, sketchDoc.head.firstElement); return `\n${sketchDoc.documentElement.outerHTML}`; @@ -280,7 +289,10 @@ class PreviewFrame extends React.Component { } } }); - newContent = decomment(newContent, { ignore: /noprotect/g }); + newContent = decomment(newContent, { + ignore: /noprotect/g, + space: true + }); newContent = loopProtect(newContent); return newContent; } diff --git a/client/modules/IDE/components/Toolbar.jsx b/client/modules/IDE/components/Toolbar.jsx index c23bf071..64cab3d5 100644 --- a/client/modules/IDE/components/Toolbar.jsx +++ b/client/modules/IDE/components/Toolbar.jsx @@ -4,7 +4,6 @@ import classNames from 'classnames'; import InlineSVG from 'react-inlinesvg'; const playUrl = require('../../../images/play.svg'); -const logoUrl = require('../../../images/p5js-logo.svg'); const stopUrl = require('../../../images/stop.svg'); const preferencesUrl = require('../../../images/preferences.svg'); const editProjectNameUrl = require('../../../images/pencil.svg'); @@ -59,14 +58,9 @@ class Toolbar extends React.Component { return (
-
- ); } } @@ -577,8 +576,6 @@ IDEView.propTypes = { runtimeErrorWarningVisible: PropTypes.bool.isRequired, }).isRequired, stopSketch: PropTypes.func.isRequired, - startAccessibleOutput: PropTypes.func.isRequired, - stopAccessibleOutput: PropTypes.func.isRequired, project: PropTypes.shape({ id: PropTypes.string, name: PropTypes.string.isRequired, @@ -619,6 +616,7 @@ IDEView.propTypes = { setTextOutput: PropTypes.func.isRequired, setGridOutput: PropTypes.func.isRequired, setSoundOutput: PropTypes.func.isRequired, + setAllAccessibleOutput: PropTypes.func.isRequired, files: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, @@ -677,7 +675,6 @@ IDEView.propTypes = { setUnsavedChanges: PropTypes.func.isRequired, setTheme: PropTypes.func.isRequired, setAutorefresh: PropTypes.func.isRequired, - startSketchAndRefresh: PropTypes.func.isRequired, endSketchRefresh: PropTypes.func.isRequired, startRefreshSketch: PropTypes.func.isRequired, setBlobUrl: PropTypes.func.isRequired, @@ -693,8 +690,13 @@ IDEView.propTypes = { persistState: PropTypes.func.isRequired, showHelpModal: PropTypes.func.isRequired, hideHelpModal: PropTypes.func.isRequired, +<<<<<<< HEAD showRuntimeErrorWarning: PropTypes.func.isRequired, hideRuntimeErrorWarning: PropTypes.func.isRequired +======= + startSketch: PropTypes.func.isRequired, + startAccessibleSketch: PropTypes.func.isRequired +>>>>>>> master }; function mapStateToProps(state) { diff --git a/client/modules/User/pages/LoginView.jsx b/client/modules/User/pages/LoginView.jsx index 66cdea1a..67ceff7d 100644 --- a/client/modules/User/pages/LoginView.jsx +++ b/client/modules/User/pages/LoginView.jsx @@ -27,6 +27,10 @@ class LoginView extends React.Component { } render() { + if (this.props.user.authenticated) { + this.gotoHomePage(); + return null; + } return (
@@ -70,7 +74,16 @@ function mapDispatchToProps() { } LoginView.propTypes = { - previousPath: PropTypes.string.isRequired + previousPath: PropTypes.string.isRequired, + user: { + authenticated: PropTypes.bool + } +}; + +LoginView.defaultProps = { + user: { + authenticated: false + } }; export default reduxForm({ diff --git a/client/modules/User/pages/SignupView.jsx b/client/modules/User/pages/SignupView.jsx index 47278271..d5fbaedd 100644 --- a/client/modules/User/pages/SignupView.jsx +++ b/client/modules/User/pages/SignupView.jsx @@ -27,6 +27,10 @@ class SignupView extends React.Component { } render() { + if (this.props.user.authenticated) { + this.gotoHomePage(); + return null; + } return (
@@ -84,7 +88,16 @@ function onSubmitFail(errors) { } SignupView.propTypes = { - previousPath: PropTypes.string.isRequired + previousPath: PropTypes.string.isRequired, + user: { + authenticated: PropTypes.bool + } +}; + +SignupView.defaultProps = { + user: { + authenticated: false + } }; export default reduxForm({ diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index 122c9df8..d3a62a22 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -15,7 +15,7 @@ $themes: ( primary-text-color: #333, modal-button-color: #333, heading-text-color: #333, - secondary-text-color: #6b6b6b, + secondary-text-color: #a8a8a8, inactive-text-color: #b5b5b5, background-color: #fbfbfb, preview-placeholder-color: #dcdcdc, @@ -30,7 +30,8 @@ $themes: ( button-active-color: $white, modal-background-color: #f4f4f4, modal-button-background-color: #e6e6e6, - modal-border-color: #B9D0E1, + modal-border-color: rgba(17, 17, 17, 0.3), + modal-boder-selected-color: #B9D0E1, icon-color: $icon-color, icon-hover-color: $icon-hover-color, icon-toast-hover-color: $white, @@ -44,14 +45,19 @@ $themes: ( input-text-color: #333, input-border-color: #b5b5b5, about-list-text-color: #4a4a4a, - search-background-color: #ebebeb + search-background-color: #ebebeb, + dropdown-color: #414141, + keyboard-shortcut-color: #757575, + nav-hover-color: $p5js-pink, + codefold-icon-open: url(../images/down-arrow.svg), + codefold-icon-closed: url(../images/right-arrow.svg) ), dark: ( logo-color: $p5js-pink, primary-text-color: $white, modal-button-color: $white, heading-text-color: $white, - secondary-text-color: #c2c2c2, + secondary-text-color: #DADADA, inactive-text-color: #b5b5b5, background-color: #333, preview-placeholder-color: #dcdcdc, @@ -80,7 +86,12 @@ $themes: ( input-text-color: #333, input-border-color: #b5b5b5, about-list-text-color: #f4f4f4, - search-background-color: #ebebeb + search-background-color: #ebebeb, + dropdown-color: #dadada, + keyboard-shortcut-color: #B5B5B5, + nav-hover-color: $p5js-pink, + codefold-icon-open: url(../images/down-arrow-white.svg), + codefold-icon-closed: url(../images/right-arrow-white.svg) ), contrast: ( logo-color: $yellow, @@ -115,7 +126,12 @@ $themes: ( input-text-color: #333, input-border-color: #b5b5b5, about-list-text-color: #f4f4f4, - search-background-color: $white + search-background-color: $white, + dropdown-color: #e1e1e1, + keyboard-shortcut-color: #e1e1e1, + nav-hover-color: $yellow, + codefold-icon-open: url(../images/down-arrow-white.svg), + codefold-icon-closed: url(../images/right-arrow-white.svg) ) ); diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 780b19a2..36c5fa07 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -13,17 +13,17 @@ .CodeMirror-linenumber { width: #{32 / $base-font-size}rem; - left: 0 !important; + left: #{-3 / $base-font-size}rem !important; @include themify() { color: getThemifyVariable('inactive-text-color'); } } .CodeMirror-lines { - padding-top: #{25 / $base-font-size}rem; + padding-top: #{25 / $base-font-size}rem; } -.CodeMirror-line { +pre.CodeMirror-line { padding-left: #{5 / $base-font-size}rem; } @@ -268,6 +268,40 @@ background: transparent url(../images/exit.svg) no-repeat; } +.CodeMirror-foldgutter-open:after { + @include themify() { + background-image: getThemifyVariable('codefold-icon-open'); + } +} + +.CodeMirror-foldgutter-folded:after { + @include themify() { + background-image: getThemifyVariable('codefold-icon-closed'); + } +} + +.CodeMirror-foldgutter-folded:after, +.CodeMirror-foldgutter-open:after { + background-size: 10px 10px; + content: ""; + padding-left: 15px; + background-repeat: no-repeat; + background-position: center center; +} + +.CodeMirror-foldmarker { + text-shadow: none; + border-radius: 5px; + opacity: 1; + font-weight: normal; + display: inline-block; + vertical-align: middle; + height: 0.85em; + line-height: 0.7; + padding: 0 #{5 / $base-font-size}rem; + font-family: serif; +} + .editor-holder { height: calc(100% - #{29 / $base-font-size}rem); width: 100%; diff --git a/client/styles/components/_nav.scss b/client/styles/components/_nav.scss index a8cd864d..5cf32462 100644 --- a/client/styles/components/_nav.scss +++ b/client/styles/components/_nav.scss @@ -1,34 +1,41 @@ .nav { - width: 100%; - padding: #{10 / $base-font-size}rem #{32 / $base-font-size}rem 0 #{32 / $base-font-size}rem; + width: calc(100% - #{10 / $base-font-size}rem); + height: #{42 / $base-font-size}rem; display: flex; flex-direction: row; justify-content: space-between; -} - -.nav__items-left, .nav__items-right { @include themify() { border-bottom: 1px dashed map-get($theme-map, 'inactive-text-color'); } + & button { + padding: 0; + } +} + +.nav__items-left, .nav__items-right { list-style: none; display: flex; flex-direction: row; justify-content: flex-end; - padding: #{3 / $base-font-size}rem 0; + height: 100%; + align-items: center; } .nav__items-left { & button { @include themify() { - color: getThemifyVariable('inactive-text-color'); + color: getThemifyVariable('secondary-text-color'); } } } .nav__item { position: relative; - padding: 0 #{24 / $base-font-size}rem; - text-align: center; + padding: 0 #{10 / $base-font-size}rem; + display: flex; + align-items: center; + justify-content: center; + height: 100%; } .nav__item:first-child { @@ -39,38 +46,57 @@ padding-right: #{15 / $base-font-size}rem; } +.nav__item-header { + margin-right: #{5 / $base-font-size}rem; +} + +.nav__item:hover { + .nav__item-header { + @include themify() { + color: getThemifyVariable('nav-hover-color'); + } + } + .nav__item-header-triangle polygon { + @include themify() { + fill: getThemifyVariable('nav-hover-color'); + } + } +} .nav__dropdown { @include themify() { background-color: map-get($theme-map, 'modal-background-color'); border: 1px solid map-get($theme-map, 'modal-border-color'); - box-shadow: 0 0 18px getThemifyVariable('shadow-color'); + box-shadow: 0 0 18px 0 getThemifyVariable('shadow-color'); + color: getThemifyVariable('dropdown-color'); } - @extend %hidden-element; + display: none; text-align: left; - width: #{140 / $base-font-size}rem; - .nav__item:hover & { + width: #{180 / $base-font-size}rem; + .nav__item--open & { display: flex; position: absolute; flex-direction: column; - top: #{-8 / $base-font-size}rem;; - left: #{-11 / $base-font-size}rem; + top: 4px; + left: 0; height: auto; } - padding-bottom: #{8 / $base-font-size}rem; - z-index: 1; + z-index: 9999; + border-radius: #{6 / $base-font-size}rem; +} + +.nav__items-right { + padding-right: #{20 / $base-font-size}rem; + & .nav__dropdown { + width: #{121 / $base-font-size}rem; + } } .nav__item-spacer { @include themify() { color: map-get($theme-map, 'inactive-text-color'); + margin: 0 #{8 / $base-font-size}rem; } - padding: 0 #{15 / $base-font-size}rem; -} - -.nav__dropdown li { - padding: #{4 / $base-font-size}rem #{16 / $base-font-size}rem; - width: 100%; } .nav__dropdown a, button { @@ -93,9 +119,24 @@ @include themify() { border-bottom: 1px dashed map-get($theme-map, 'inactive-text-color'); } - margin-top: #{3 / $base-font-size}rem; - text-align: center; - margin-bottom: #{4 / $base-font-size}rem; + height: #{(42 - 5) / $base-font-size}rem; + display: flex; + align-items: center; + justify-content: space-between; + margin: 0 #{16 / $base-font-size}rem; + cursor: pointer; + &:hover { + span { + @include themify() { + color: getThemifyVariable('nav-hover-color'); + } + } + polygon { + @include themify() { + fill: getThemifyVariable('nav-hover-color'); + } + } + } } .nav__dropdown-heading a, .nav__dropdown-heading a:hover { @@ -106,6 +147,48 @@ width: 100%; } +.nav__dropdown-heading svg { + transform-origin: 50% 50%; + transform: rotate(180deg); +} + +.nav__dropdown-item { + height: #{32 / $base-font-size}rem; + width: 100%; + display: flex; + align-items: center; + padding: 0 #{16 / $base-font-size}rem; + cursor: pointer; + & button, & a { + @include themify() { + color: getThemifyVariable('dropdown-color'); + } + } + &:hover { + @include themify() { + background-color: getThemifyVariable('button-background-hover-color'); + color: getThemifyVariable('button-hover-color') + } + & button, & a { + @include themify() { + color: getThemifyVariable('button-hover-color'); + } + } + } + + & button, & a { + width: 100%; + height: 100%; + display: flex; + justify-content: space-between; + align-items: center; + } +} + +.nav__dropdown-item:last-child { + border-radius: 0 0 #{6 / $base-font-size}rem #{6 / $base-font-size}rem; +} + .nav__announce { position: absolute; top: #{40 / $base-font-size}rem; @@ -121,3 +204,25 @@ z-index: 0; } +.nav__item-logo { + position: relative; + height: #{42 / $base-font-size}rem; + width: #{56 / $base-font-size}rem; + & span { + position: absolute; + } +} + +.nav__keyboard-shortcut { + font-size: #{12 / $base-font-size}rem; + font-family: Inconsololata, monospace; + @include themify() { + color: getThemifyVariable('keyboard-shortcut-color'); + } + .nav__dropdown-item:hover & { + @include themify() { + color: getThemifyVariable('button-hover-color'); + } + } +} + diff --git a/client/styles/components/_p5-contrast-codemirror-theme.scss b/client/styles/components/_p5-contrast-codemirror-theme.scss index d5a2a997..6ddef26a 100644 --- a/client/styles/components/_p5-contrast-codemirror-theme.scss +++ b/client/styles/components/_p5-contrast-codemirror-theme.scss @@ -125,3 +125,8 @@ $p5-contrast-activeline: #999999; color: $p5-contrast-pink; font-weight: bold; } + +.cm-s-p5-contrast .CodeMirror-foldmarker { + background-color: white; + color: #333; +} diff --git a/client/styles/components/_p5-dark-codemirror-theme.scss b/client/styles/components/_p5-dark-codemirror-theme.scss index 863855e3..275d57ff 100644 --- a/client/styles/components/_p5-dark-codemirror-theme.scss +++ b/client/styles/components/_p5-dark-codemirror-theme.scss @@ -21,6 +21,8 @@ $p5-dark-white: #FDFDFD; $p5-dark-orange: #EE9900; $p5-dark-lightgray: #E0D7D1; $p5-dark-darkgray: #666666; +$p5-dark-green: #58a10b; +$p5-dark-goldbrown: #b58317; $p5-dark-gutter: #f4f4f4; $p5-dark-number: #b5b5b5; @@ -41,7 +43,7 @@ $p5-dark-activeline: rgb(207, 207, 207); } .cm-s-p5-dark .cm-string { - color: $p5-dark-lightblue; + color: $p5-dark-green; } .cm-s-p5-dark .cm-string-2 { @@ -49,11 +51,11 @@ $p5-dark-activeline: rgb(207, 207, 207); } .cm-s-p5-dark .cm-number { - color: $p5-dark-pink; + color: $p5-dark-white; } .cm-s-p5-dark .cm-keyword { - color: $p5-light-green; + color: $p5-dark-goldbrown; } .cm-s-p5-dark .cm-variable { @@ -126,3 +128,8 @@ $p5-dark-activeline: rgb(207, 207, 207); color: $p5-dark-pink; font-weight: bold; } + +.cm-s-p5-dark .CodeMirror-foldmarker { + background-color: white; + color: #333; +} diff --git a/client/styles/components/_p5-light-codemirror-theme.scss b/client/styles/components/_p5-light-codemirror-theme.scss index b22edf26..dbb72dbd 100644 --- a/client/styles/components/_p5-light-codemirror-theme.scss +++ b/client/styles/components/_p5-light-codemirror-theme.scss @@ -12,7 +12,7 @@ $p5-light-lightbrown: #A67F59; $p5-light-brown: #704F21; -$p5-light-black: #333; +$p5-light-black: #333333; $p5-light-pink: #D9328F; $p5-light-gray: #A0A0A0; $p5-light-lightblue: #00A1D3; @@ -21,6 +21,7 @@ $p5-light-white: #FDFDFD; $p5-light-orange: #EE9900; $p5-light-lightgray: #E0D7D1; $p5-light-darkgray: #666666; +$p5-light-green: #58a10b; $p5-light-gutter: #f4f4f4; $p5-light-number: #b5b5b5; @@ -37,11 +38,11 @@ $p5-light-activeline: rgb(207, 207, 207); } .cm-s-p5-light .cm-def { - color: $p5-light-darkblue; + color: $p5-light-lightblue; } .cm-s-p5-light .cm-string { - color: $p5-light-lightblue; + color: $p5-light-green; } .cm-s-p5-light .cm-string-2 { @@ -49,7 +50,7 @@ $p5-light-activeline: rgb(207, 207, 207); } .cm-s-p5-light .cm-number { - color: $p5-light-pink; + color: $p5-light-black; } .cm-s-p5-light .cm-keyword { @@ -60,7 +61,7 @@ $p5-light-activeline: rgb(207, 207, 207); color: $p5-light-lightblue; } -.cm-s-p5-light .cm-variable-2 { +.cm-s-p5-light .cm-variable2 { color: $p5-light-black; } @@ -114,14 +115,19 @@ $p5-light-activeline: rgb(207, 207, 207); } .cm-s-p5-light .cm-attribute { - color: $p5-light-lightblue; + color: $p5-light-black; } .cm-s-p5-light .cm-p5-function { color: $p5-light-darkblue; + font-weight: bold; } .cm-s-p5-light .cm-p5-variable { color: $p5-light-pink; - font-weight: bold; +} + +.cm-s-p5-light .CodeMirror-foldmarker { + background-color: #333; + color: white; } diff --git a/client/styles/vendors/_codemirror.scss b/client/styles/vendors/_codemirror.scss index 1cf66a9f..8938568c 100644 --- a/client/styles/vendors/_codemirror.scss +++ b/client/styles/vendors/_codemirror.scss @@ -336,3 +336,33 @@ div.CodeMirror-dragcursors { /* Help users use markselection to safely style text background */ span.CodeMirror-selectedtext { background: none; } + +/* CODE FOLDING (FOLDGUTTER.JS) */ + +.CodeMirror-foldmarker { + text-shadow: -1px 0 #ed225d, 0 1px #ed225d, 1px 0 #ed225d, 0 -1px #ed225d; + color: #FFF; + /* background-color: rgba(237, 34, 93, 0.42); */ + /* border-radius: 3px; */ + font-weight: bold; + font-family: arial; + line-height: .3; + cursor: pointer; + opacity: 0.75; +} +.CodeMirror-foldgutter { + width: 2.7em; +} +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + cursor: pointer; + padding-bottom: 0.4em; + text-align: right; + line-height: 1.0; +} +.CodeMirror-foldgutter-open:after { + content: "\25BE"; +} +.CodeMirror-foldgutter-folded:after { + content: "\25B8"; +} diff --git a/client/utils/metaKey.js b/client/utils/metaKey.js index e88052e1..47899976 100644 --- a/client/utils/metaKey.js +++ b/client/utils/metaKey.js @@ -8,7 +8,7 @@ const metaKey = (() => { return 'Ctrl'; })(); -const metaKeyName = metaKey === 'Cmd' ? 'Command' : 'Control'; +const metaKeyName = metaKey === 'Cmd' ? '\u2318' : 'Ctrl'; export { metaKey, diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js index 911eab1e..0b7d84b4 100644 --- a/server/controllers/project.controller.js +++ b/server/controllers/project.controller.js @@ -136,6 +136,34 @@ export function getProjectsForUserId(userId) { }); } +export function getProjectAsset(req, res) { + Project.findById(req.params.project_id) + .populate('user', 'username') + .exec((err, project) => { + if (err) { + return res.status(404).send({ message: 'Project with that id does not exist' }); + } + + var assetURL = null; + var seekPath = req.params[0]; // req.params.asset_path; + var seekPathSplit = seekPath.split('/'); + var seekFilename = seekPathSplit[seekPathSplit.length-1]; + project.files.forEach((file) => { + if(file.name === seekFilename) { + assetURL = file.url; + } + }); + + if(!assetURL) { + return res.status(404).send({ message: 'Asset does not exist' }); + } else { + request({ method: 'GET', url: assetURL, encoding: null }, (err, response, body) => { + res.send(body); + }); + } + }); +} + export function getProjectsForUserName(username) { } diff --git a/server/routes/server.routes.js b/server/routes/server.routes.js index f4ec6bf2..87cbf3b4 100644 --- a/server/routes/server.routes.js +++ b/server/routes/server.routes.js @@ -2,6 +2,7 @@ import { Router } from 'express'; import { renderIndex } from '../views/index'; import { get404Sketch } from '../views/404Page'; import { userExists } from '../controllers/user.controller'; +import { getProjectAsset } from '../controllers/project.controller'; const router = new Router(); @@ -13,6 +14,9 @@ router.route('/').get((req, res) => { }); router.route('/signup').get((req, res) => { + if (req.user) { + return res.redirect('/'); + } res.send(renderIndex()); }); @@ -24,11 +28,18 @@ router.route('/:username/sketches/:project_id').get((req, res) => { res.send(renderIndex()); }); +router.route('/:username/sketches/:project_id/*').get((req, res) => { + getProjectAsset(req,res); +}); + // router.route('/full/:project_id').get((req, res) => { // res.send(renderIndex()); // }); router.route('/login').get((req, res) => { + if (req.user) { + return res.redirect('/'); + } res.send(renderIndex()); }); diff --git a/static/p5-interceptor b/static/p5-interceptor index 0958be54..51087283 160000 --- a/static/p5-interceptor +++ b/static/p5-interceptor @@ -1 +1 @@ -Subproject commit 0958be54482722821159cd3e07777988ee349f37 +Subproject commit 51087283c090ab1f1f0f733a01fdf46ef1382544