diff --git a/client/constants.js b/client/constants.js index b3772398..3c22e79c 100644 --- a/client/constants.js +++ b/client/constants.js @@ -81,6 +81,7 @@ export const CLOSE_KEYBOARD_SHORTCUT_MODAL = 'CLOSE_KEYBOARD_SHORTCUT_MODAL'; export const SHOW_TOAST = 'SHOW_TOAST'; export const HIDE_TOAST = 'HIDE_TOAST'; export const SET_TOAST_TEXT = 'SET_TOAST_TEXT'; +export const SET_THEME = 'SET_THEME'; export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES'; diff --git a/client/images/play.svg b/client/images/play.svg index 98d2ae1d..aff6d461 100644 --- a/client/images/play.svg +++ b/client/images/play.svg @@ -1,5 +1,5 @@ - + play diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js index f2270905..6b3964ef 100644 --- a/client/modules/IDE/actions/preferences.js +++ b/client/modules/IDE/actions/preferences.js @@ -136,3 +136,11 @@ export function setTextOutput(value) { } }; } + +export function setTheme(value) { + return { + type: ActionTypes.SET_THEME, + value + }; +} + diff --git a/client/modules/IDE/components/Editor.js b/client/modules/IDE/components/Editor.js index 6b814067..8e6078ff 100644 --- a/client/modules/IDE/components/Editor.js +++ b/client/modules/IDE/components/Editor.js @@ -39,7 +39,7 @@ class Editor extends React.Component { this.beep = new Audio(beepUrl); this.widgets = []; this._cm = CodeMirror(this.refs.container, { // eslint-disable-line - theme: 'p5-widget', + theme: `p5-${this.props.theme}`, value: this.props.file.content, lineNumbers: true, styleActiveLine: true, @@ -115,6 +115,10 @@ class Editor extends React.Component { this._cm.setOption('mode', 'htmlmixed'); } } + + if (this.props.theme !== prevProps.theme) { + this._cm.setOption('theme', `p5-${this.props.theme}`); + } } componentWillUnmount() { @@ -140,6 +144,7 @@ class Editor extends React.Component { checkForInfiniteLoop(callback) { const prevIsplaying = this.props.isPlaying; let infiniteLoop = false; + let prevLine; this.props.stopSketch(); this.props.resetInfiniteLoops(); @@ -155,14 +160,17 @@ class Editor extends React.Component { loopProtect.alias = 'protect'; loopProtect.hit = (line) => { - this.props.detectInfiniteLoops(); - infiniteLoop = true; - callback(infiniteLoop, prevIsplaying); - const msg = document.createElement('div'); - const loopError = `line ${line}: This loop is taking too long to run.`; - msg.appendChild(document.createTextNode(loopError)); - msg.className = 'lint-error'; - this.widgets.push(this._cm.addLineWidget(line - 1, msg, { coverGutter: false, noHScroll: true })); + if (line !== prevLine) { + this.props.detectInfiniteLoops(); + infiniteLoop = true; + callback(infiniteLoop, prevIsplaying); + const msg = document.createElement('div'); + const loopError = `line ${line}: This loop is taking too long to run.`; + msg.appendChild(document.createTextNode(loopError)); + msg.className = 'lint-error'; + this.widgets.push(this._cm.addLineWidget(line - 1, msg, { coverGutter: false, noHScroll: true })); + prevLine = line; + } }; const processed = loopProtect(this.props.file.content); @@ -193,7 +201,7 @@ class Editor extends React.Component { `); doc.close(); - callback(infiniteLoop, prevIsplaying); + callback(infiniteLoop, prevIsplaying, prevLine); } _cm: CodeMirror.Editor @@ -265,7 +273,8 @@ Editor.propTypes = { resetInfiniteLoops: PropTypes.func.isRequired, stopSketch: PropTypes.func.isRequired, startSketch: PropTypes.func.isRequired, - isPlaying: PropTypes.bool.isRequired + isPlaying: PropTypes.bool.isRequired, + theme: PropTypes.string.isRequired }; export default Editor; diff --git a/client/modules/IDE/components/Preferences.js b/client/modules/IDE/components/Preferences.js index e9ba2fe3..b0baf885 100644 --- a/client/modules/IDE/components/Preferences.js +++ b/client/modules/IDE/components/Preferences.js @@ -165,6 +165,33 @@ class Preferences extends React.Component { +
+

Theme

+
+ this.props.setTheme('light')} + aria-label="light theme on" + name="light theme" + id="light-theme-on" + className="preference__radio-button" + value="light" + checked={this.props.theme === 'light'} + /> + + this.props.setTheme('dark')} + aria-label="dark theme on" + name="dark theme" + id="dark-theme-on" + className="preference__radio-button" + value="dark" + checked={this.props.theme === 'dark'} + /> + +
+

Lint Warning Sound

@@ -249,7 +276,9 @@ Preferences.propTypes = { textOutput: PropTypes.bool.isRequired, setTextOutput: PropTypes.func.isRequired, lintWarning: PropTypes.bool.isRequired, - setLintWarning: PropTypes.func.isRequired + setLintWarning: PropTypes.func.isRequired, + theme: PropTypes.string.isRequired, + setTheme: PropTypes.func.isRequired }; export default Preferences; diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js index 430273e7..d80a7944 100644 --- a/client/modules/IDE/pages/IDEView.js +++ b/client/modules/IDE/pages/IDEView.js @@ -61,6 +61,8 @@ class IDEView extends React.Component { this.props.router.setRouteLeaveHook(this.props.route, () => this.warnIfUnsavedChanges()); window.onbeforeunload = () => this.warnIfUnsavedChanges(); + + document.body.className = this.props.preferences.theme; } componentWillUpdate(nextProps) { @@ -75,6 +77,10 @@ class IDEView extends React.Component { if (nextProps.params.project_id && !this.props.params.project_id) { this.props.getProject(nextProps.params.project_id); } + + if (nextProps.preferences.theme !== this.props.preferences.theme) { + document.body.className = nextProps.preferences.theme; + } } componentDidUpdate(prevProps) { @@ -203,6 +209,8 @@ class IDEView extends React.Component { setLintWarning={this.props.setLintWarning} textOutput={this.props.preferences.textOutput} setTextOutput={this.props.setTextOutput} + theme={this.props.preferences.theme} + setTheme={this.props.setTheme} />
{ @@ -31,6 +32,8 @@ const preferences = (state = initialState, action) => { return Object.assign({}, state, { textOutput: action.value }); case ActionTypes.SET_PREFERENCES: return action.preferences; + case ActionTypes.SET_THEME: + return Object.assign({}, state, { theme: action.value }); default: return state; } diff --git a/client/styles/abstracts/_functions.scss b/client/styles/abstracts/_functions.scss new file mode 100644 index 00000000..a4756f08 --- /dev/null +++ b/client/styles/abstracts/_functions.scss @@ -0,0 +1,26 @@ +@function map-fetch($map, $keys) { + $key: nth($keys, 1); + $length: length($keys); + $value: map-get($map, $key); + + + @if $value != null { + @if $length > 1 { + $rest: (); + + @for $i from 2 through $length { + $rest: append($rest, nth($keys, $i)) + } + + @return map-fetch($value, $rest); + } @else { + @return $value; + } + } @else { + @return false; + } +} + +@function getThemifyVariable($key) { + @return map-get($theme-map, $key); +} \ No newline at end of file diff --git a/client/styles/abstracts/_mixins.scss b/client/styles/abstracts/_mixins.scss new file mode 100644 index 00000000..54b836ec --- /dev/null +++ b/client/styles/abstracts/_mixins.scss @@ -0,0 +1,19 @@ +@mixin themify ($themes: $themes) { + @each $theme, $map in $themes { + .#{$theme} & { + // Define theme color + $theme-map : ( + ) !global; + + @each $key, $submap in $map { + $value: map-fetch($themes, $theme '#{$key}'); + $theme-map: map-merge($theme-map, ($key: $value)) !global; + } + + @content; + + // reset theme color to null + $theme-map: null !global; + } + } +} \ No newline at end of file diff --git a/client/styles/abstracts/_placeholders.scss b/client/styles/abstracts/_placeholders.scss index dadf6267..4b4a66fb 100644 --- a/client/styles/abstracts/_placeholders.scss +++ b/client/styles/abstracts/_placeholders.scss @@ -1,116 +1,122 @@ %toolbar-button { - display: inline-block; - height: #{44 / $base-font-size}rem; - width: #{44 / $base-font-size}rem; - text-align: center; - border-radius: 100%; - line-height: #{46 / $base-font-size}rem; - cursor: pointer; - border: none; - outline: none; - background-color: $light-button-background-color; - color: $light-button-color; - & g { - fill: $light-toolbar-button-color; - } - &:hover { - background-color: $light-button-background-hover-color; - color: $light-button-hover-color; - + @include themify() { + display: inline-block; + height: #{44 / $base-font-size}rem; + width: #{44 / $base-font-size}rem; + text-align: center; + border-radius: 100%; + line-height: #{46 / $base-font-size}rem; + cursor: pointer; + border: none; + outline: none; + background-color: getThemifyVariable('toolbar-button-background-color'); + color: getThemifyVariable('toolbar-button-color'); & g { - fill: $light-button-hover-color; + fill: getThemifyVariable('toolbar-button-color'); } + &:hover { + background-color: getThemifyVariable('button-background-hover-color'); + color: getThemifyVariable('button-hover-color'); + + & g { + fill: getThemifyVariable('button-hover-color'); + } + } + &--selected { + background-color: getThemifyVariable('button-background-hover-color'); + & g { + fill: getThemifyVariable('button-hover-color'); + } + } } - &--selected { - background-color: $light-button-background-hover-color; - & g { - fill: $light-button-hover-color; - } - } } %icon { + @include themify() { + color: getThemifyVariable('icon-color'); + & g { + fill: getThemifyVariable('icon-color'); + } + &:hover { + color: getThemifyVariable('icon-hover-color'); + & g { + opacity: 1; + fill: getThemifyVariable('icon-hover-color'); + } + } + } background-color: transparent; border: none; cursor: pointer; padding: 0; - color: $light-icon-color; - & g { - fill: $light-icon-color; - } - &:hover { - color: $light-icon-hover-color; - & g { - opacity: 1; - fill: $light-icon-hover-color; - } - } } %button { - background-color: $light-button-background-color; - color: $light-button-color; - cursor: pointer; - border: 1px solid $light-button-border-color; - border-radius: 2px; - padding: #{10 / $base-font-size}rem #{30 / $base-font-size}rem; - &:hover { - border-color: $light-button-background-hover-color; - background-color: $light-button-background-hover-color; - color: $light-button-hover-color; - } - &:active { - border-color: $light-button-background-active-color; - background-color: $light-button-background-active-color; - color: $light-button-active-color; + @include themify() { + background-color: getThemifyVariable('button-background-color'); + color: getThemifyVariable('button-color'); + cursor: pointer; + border: 1px solid getThemifyVariable('button-border-color'); + border-radius: 2px; + padding: #{10 / $base-font-size}rem #{30 / $base-font-size}rem; + &:hover { + border-color: getThemifyVariable('button-background-hover-color'); + background-color: getThemifyVariable('button-background-hover-color'); + color: getThemifyVariable('button-hover-color'); + } + &:active { + border-color: getThemifyVariable('button-background-active-color'); + background-color: getThemifyVariable('button-background-active-color'); + color: getThemifyVariable('button-active-color'); + } } } %preferences-button { @extend %toolbar-button; - color: $light-primary-text-color; - background-color: $light-modal-button-background-color; - padding: 0; - margin-bottom: #{28 / $base-font-size}rem; - line-height: #{50 / $base-font-size}rem; - & g { - fill: $light-primary-text-color; - } - &:hover { - background-color: $light-button-background-hover-color; - color: $light-button-hover-color; + @include themify() { + color: getThemifyVariable('primary-text-color'); + background-color: getThemifyVariable('modal-button-background-color'); + padding: 0; + margin-bottom: #{28 / $base-font-size}rem; + line-height: #{50 / $base-font-size}rem; & g { - fill: $light-button-hover-color; + fill: getThemifyVariable('primary-text-color'); + } + &:hover { + background-color: getThemifyVariable('button-background-hover-color'); + color: getThemifyVariable('button-hover-color'); + & g { + fill: getThemifyVariable('button-hover-color'); + } } } } -%fake-link { - color: $light-inactive-text-color; - cursor: pointer; - &:hover { - color: $light-primary-text-color; - } -} - %preference-option { - background-color: $light-button-background-color; - color: $light-inactive-text-color; + @include themify() { + background-color: transparent; + color: getThemifyVariable('inactive-text-color'); + &:hover { + color: getThemifyVariable('primary-text-color'); + } + } font-size: #{12 / $base-font-size}rem; cursor: pointer; text-align: left; margin-bottom: #{5 / $base-font-size}rem; - border: 0px; - &:hover { - color: $light-primary-text-color; - } + border: 0; + padding: 0; + list-style-type: none; } %modal { - background-color: $light-modal-background-color; - border: 1px solid $light-modal-border-color; + @include themify() { + background-color: getThemifyVariable('modal-background-color'); + border: 1px solid getThemifyVariable('modal-border-color'); + box-shadow: 0 12px 12px getThemifyVariable('shadow-color'); + } border-radius: 2px; - box-shadow: 0 12px 12px $light-shadow-color; z-index: 20; } diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index 7ab71052..f4fc23c3 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -5,49 +5,70 @@ $p5js-pink: #ed225d; $white: #fff; $black: #000; -$light-primary-text-color: #333; -$light-secondary-text-color: #6b6b6b; -$light-inactive-text-color: #b5b5b5; -$light-background-color: #fdfdfd; +$themes: ( + light: ( + primary-text-color: #333, + secondary-text-color: #6b6b6b, + inactive-text-color: #b5b5b5, + background-color: #fdfdfd, + button-background-color: #f4f4f4, + button-color: $black, + button-border-color: #979797, + toolbar-button-color: $p5js-pink, + toolbar-button-background-color: #f4f4f4, + button-background-hover-color: $p5js-pink, + button-background-active-color: #f10046, + button-hover-color: $white, + button-active-color: $white, + modal-background-color: #f4f4f4, + modal-button-background-color: #e6e6e6, + modal-border-color: #B9D0E1, + icon-color: #8b8b8b, + icon-hover-color: #333, + shadow-color: rgba(0, 0, 0, 0.16), + console-background-color: #eee, + console-header-background-color: #d6d6d6, + console-header-color: #b1b1b1, + ide-border-color: #f4f4f4, + editor-gutter-color: #f7f7f7, + file-selected-color: #f4f4f4, + input-text-color: #333, + input-border-color: #979797, + ), + dark: ( + primary-text-color: $white, + secondary-text-color: #c2c2c2, + inactive-text-color: #7d7d7d, + background-color: #333, + button-background-color: $white, + button-color: $black, + button-border-color: #979797, + toolbar-button-color: $p5js-pink, + toolbar-button-background-color: #424242, + button-background-hover-color: $p5js-pink, + button-background-active-color: #f10046, + button-hover-color: $white, + button-active-color: $white, + modal-background-color: #444, + modal-button-background-color: #5f5f5f, + modal-border-color: #949494, + icon-color: #a9a9a9, + icon-hover-color: $white, + shadow-color: rgba(0, 0, 0, 0.16), + console-background-color: #4f4f4f, + console-header-background-color: #3f3f3f, + console-header-color: #b5b5b5, + ide-border-color: #949494, + editor-gutter-color: #363636, + file-selected-color: #404040, + input-text-color: #333, + input-border-color: #979797, + ) +); -$light-button-background-color: #f4f4f4; -$light-button-color: $black; -$light-button-border-color: #979797; -$light-toolbar-button-color: $p5js-pink; -$light-button-background-hover-color: $p5js-pink; -$light-button-background-active-color: #f10046; -$light-button-hover-color: $white; -$light-button-active-color: $white; -$light-modal-background-color: #f4f4f4; -$light-modal-button-background-color: #e6e6e6; -$light-modal-border-color: #B9D0E1; -$light-icon-color: #8b8b8b; -$light-icon-hover-color: $light-primary-text-color; -$light-shadow-color: rgba(0, 0, 0, 0.16); +$console-warn-color: #ffbe05; +$console-error-color: #ff5f52; $toast-background-color: #979797; $toast-text-color: $white; -$dark-primary-text-color: $white; -$dark-secondary-text-color: #c2c2c2; -$dark-inactive-color: #7d7d7d; -$dark-background-color: #333; - -$dark-button-background-color: $white; -$dark-button-color: $black; -$dark-toolbar-button-color: $p5js-pink; -$dark-button-background-hover-color: $p5js-pink; -$dark-button-background-active-color: #f10046; -$dark-button-hover-color: $white; -$dark-button-active-color: $white; - -$ide-border-color: #f4f4f4; -$editor-selected-line-color: #f3f3f3; -$input-border-color: #979797; - -$console-light-background-color: #eee; -$console-header-background-color: #d6d6d6; -$console-header-color: #b1b1b1; -$console-warn-color: #ffbe05; -$console-error-color: #ff5f52; - diff --git a/client/styles/base/_base.scss b/client/styles/base/_base.scss index dbf361c6..d96bfbf8 100644 --- a/client/styles/base/_base.scss +++ b/client/styles/base/_base.scss @@ -7,12 +7,10 @@ html, body { } body, input, button { + @include themify() { + color: getThemifyVariable('primary-text-color'); + } font-family: 'Avenir Next', Montserrat, sans-serif; - color: $light-primary-text-color; -} - -body { - background-color: $light-background-color; } .root-app, .app { @@ -21,12 +19,14 @@ body { } a { - text-decoration: none; - color: $light-inactive-text-color; - cursor: pointer; - &:hover { + @include themify() { text-decoration: none; - color: $light-primary-text-color; + color: getThemifyVariable('inactive-text-color'); + cursor: pointer; + &:hover { + text-decoration: none; + color: getThemifyVariable('primary-text-color'); + } } } @@ -36,13 +36,18 @@ input, button { input { padding: #{5 / $base-font-size}rem; - // border-radius: 2px; - border: 1px solid $input-border-color; + border: 1px solid; padding: #{10 / $base-font-size}rem; + @include themify() { + color: getThemifyVariable('input-text-color'); + border-color: getThemifyVariable('input-border-color'); + } } input[type="submit"] { - @extend %button; + @include themify() { + @extend %button; + } } h2 { diff --git a/client/styles/components/_about.scss b/client/styles/components/_about.scss index b04fc160..6a94ff25 100644 --- a/client/styles/components/_about.scss +++ b/client/styles/components/_about.scss @@ -12,7 +12,9 @@ } .about__exit-button { - @extend %icon; + @include themify() { + @extend %icon; + } } .about__copy { diff --git a/client/styles/components/_console.scss b/client/styles/components/_console.scss index d96dfd4c..0f382c77 100644 --- a/client/styles/components/_console.scss +++ b/client/styles/components/_console.scss @@ -1,7 +1,12 @@ .preview-console { + @include themify() { + background: getThemifyVariable('console-background-color'); + border-color: getThemifyVariable('ide-border-color'); + } + border-left: 1px solid; + border-right: 1px solid; width: 100%; height: 100%; - background: $console-light-background-color; z-index: 1000; overflow: hidden; display: flex; @@ -14,7 +19,9 @@ // assign styles to different types of console messages .preview-console__log { - color: $dark-secondary-text-color; + @include themify(){ + color: getThemifyVariable('secondary-text-color'); + } flex: 1 0 auto; } @@ -30,8 +37,10 @@ } .preview-console__header { - background-color: $console-header-background-color; - color: $console-header-color; + @include themify() { + background-color: getThemifyVariable('console-header-background-color'); + color: getThemifyVariable('console-header-color'); + } padding: #{5 / $base-font-size}rem; display: flex; justify-content: space-between; @@ -51,14 +60,18 @@ } .preview-console__collapse { - @extend %icon; + @include themify() { + @extend %icon; + } .preview-console--collapsed & { display: none; } } .preview-console__expand { - @extend %icon; + @include themify() { + @extend %icon; + } display: none; .preview-console--collapsed & { display: inline-block; diff --git a/client/styles/components/_editor.scss b/client/styles/components/_editor.scss index 0b40661e..1c3d739f 100644 --- a/client/styles/components/_editor.scss +++ b/client/styles/components/_editor.scss @@ -1,7 +1,9 @@ .CodeMirror { - font-family: Inconsolata, monospace; - height: 100%; - border: 1px solid $ide-border-color; + @include themify() { + border: 1px solid getThemifyVariable('ide-border-color'); + } + font-family: Inconsolata, monospace; + height: 100%; } .CodeMirror-linenumbers { @@ -54,15 +56,27 @@ } .CodeMirror-lint-tooltip { - background-color: $light-modal-background-color; - border: 1px solid $light-modal-border-color; + @include themify() { + background-color: getThemifyVariable('modal-background-color'); + border: 1px solid getThemifyVariable('modal-border-color'); + box-shadow: 0 12px 12px getThemifyVariable('shadow-color'); + color: getThemifyVariable('primary-text-color'); + } border-radius: 2px; - box-shadow: 0 12px 12px $light-shadow-color; - font-family: Montserrat, sans-serif; + font-family: 'Avenir Next', Montserrat, sans-serif; +} + +.CodeMirror-gutters { + @include themify() { + background-color: getThemifyVariable('editor-gutter-color'); + border-color: getThemifyVariable('ide-border-color'); + } } .editor__options-button { - @extend %icon; + @include themify() { + @extend %icon; + } position: absolute; top: #{5 / $base-font-size}rem; right: #{5 / $base-font-size}rem; @@ -85,7 +99,7 @@ .lint-error { font-family: Inconsolata, monospace; font-size: 100%; - background: #FFBEC1; - color: red; + background: rgba($console-error-color, 0.3); + color: $console-error-color; padding: 2px 5px 3px; } diff --git a/client/styles/components/_github-button.scss b/client/styles/components/_github-button.scss index 6013fd08..1adf85d0 100644 --- a/client/styles/components/_github-button.scss +++ b/client/styles/components/_github-button.scss @@ -1,19 +1,21 @@ .github-button { - @extend %button; + @include themify() { + @extend %button; + & path { + color: getThemifyVariable('primary-text-color'); + } + &:hover path, &:active path { + fill: $white; + } + &:hover, &:active { + background-color: getThemifyVariable('secondary-text-color'); + border-color: getThemifyVariable('secondary-text-color'); + } + } width: #{300 / $base-font-size}rem; display: flex; justify-content: center; align-items: center; - & path { - color: $light-primary-text-color; - } - &:hover path, &:active path { - fill: $white; - } - &:hover, &:active { - background-color: $light-secondary-text-color; - border-color: $light-secondary-text-color - } } .github-icon { diff --git a/client/styles/components/_modal.scss b/client/styles/components/_modal.scss index 44b8e80f..8f276100 100644 --- a/client/styles/components/_modal.scss +++ b/client/styles/components/_modal.scss @@ -21,7 +21,9 @@ } .modal__exit-button { - @extend %icon; + @include themify() { + @extend %icon; + } } .modal__header { @@ -88,7 +90,9 @@ } .keyboard-shortcuts__close { - @extend %icon; + @include themify() { + @extend %icon; + } } .keyboard-shortcut-item { diff --git a/client/styles/components/_nav.scss b/client/styles/components/_nav.scss index 940f6af0..d73fa441 100644 --- a/client/styles/components/_nav.scss +++ b/client/styles/components/_nav.scss @@ -8,16 +8,14 @@ } .nav__items-left, .nav__items-right { + @include themify() { + border-bottom: 2px dashed map-get($theme-map, 'inactive-text-color'); + } list-style: none; display: flex; flex-direction: row; justify-content: flex-end; padding: #{5 / $base-font-size}rem #{10 / $base-font-size}rem; - border-bottom: 2px dashed; -} - -.nav__save, .nav__new { - @extend %fake-link; } .nav__item { @@ -25,21 +23,24 @@ margin-left: #{20 / $base-font-size}rem; } position: relative; - &:hover .nav__dropdown { - display: flex; - } } .nav__dropdown { - display: none; - position: absolute; - flex-direction: column; - background-color: $light-background-color; - padding: #{10 / $base-font-size}rem; - left: #{-10 / $base-font-size}rem; - border: 1px solid $ide-border-color; + @include themify() { + background-color: map-get($theme-map, 'background-color'); + border: 1px solid map-get($theme-map, 'ide-border-color'); + } + @extend %hidden-element; & li + li { margin-top: #{10 / $base-font-size}rem; } width: #{140 / $base-font-size}rem; + .nav__item:hover & { + display: flex; + position: absolute; + flex-direction: column; + padding: #{10 / $base-font-size}rem; + left: #{-10 / $base-font-size}rem; + height: auto; + } } \ No newline at end of file diff --git a/client/styles/components/_p5-dark-codemirror-theme.scss b/client/styles/components/_p5-dark-codemirror-theme.scss new file mode 100644 index 00000000..600e659e --- /dev/null +++ b/client/styles/components/_p5-dark-codemirror-theme.scss @@ -0,0 +1,119 @@ +// brown: #6C4D13 +// black: #333 +// blue: #0F9DD7 +// pink: #D9328F +// gray: #999999 +// dark blue: #318094 +// white: #fdfdfd + +//numbers +//light gray: #f4f4f4 +//dark gray: #b5b5b5 + +$p5-dark-lightbrown: #A67F59; +$p5-dark-brown: #6C4D13; +$p5-dark-black: #333; +$p5-dark-pink: #D9328F; +$p5-dark-gray: #A0A0A0; +$p5-dark-lightblue: #00A1D3; +$p5-dark-darkblue: #2D7BB6; +$p5-dark-white: #FDFDFD; +$p5-dark-orange: #EE9900; +$p5-dark-lightgray: #E0D7D1; +$p5-dark-darkgray: #666666; + +$p5-dark-gutter: #f4f4f4; +$p5-dark-number: #b5b5b5; +$p5-dark-selected: rgba(45, 123, 182, 25); +$p5-dark-activeline: rgb(207, 207, 207); + +.cm-s-p5-dark { + background-color: $p5-dark-black; + color: $p5-dark-white; +} + +.cm-s-p5-dark .cm-comment { + color: $p5-dark-gray; +} + +.cm-s-p5-dark .cm-def { + color: $p5-dark-darkblue; +} + +.cm-s-p5-dark .cm-string { + color: $p5-dark-lightblue; +} + +.cm-s-p5-dark .cm-string-2 { + color: $p5-dark-orange; +} + +.cm-s-p5-dark .cm-number { + color: $p5-dark-pink; +} + +.cm-s-p5-dark .cm-keyword { + color: $p5-dark-brown; +} + +.cm-s-p5-dark .cm-variable { + color: $p5-dark-lightblue; +} + +.cm-s-p5-dark .cm-variable-2 { + color: $p5-dark-white; +} + +.cm-s-p5-dark .cm-property { + color: $p5-dark-white; +} + +.cm-s-p5-dark .cm-atom { + color: $p5-dark-pink; +} + +.cm-s-p5-dark .cm-operator { + color: $p5-dark-lightbrown; +} + +.cm-s-p5-dark .cm-linenumber { + color: $p5-dark-number; +} + +.cm-s-p5-dark .CodeMirror-selected { + background-color: $p5-dark-selected; +} + +.cm-s-p5-dark .CodeMirror-activeline-background { + background-color: #404040; +} + +.cm-s-p5-dark .CodeMirror-activeline-gutter { + background-color: #454545; + border-right: 1px solid #949494; +} + +.cm-s-p5-dark .cm-error { + color: #f00; +} + +.cm-s-p5-dark .CodeMirror-matchingbracket { + outline: 1px solid $p5-dark-darkgray; + color: black !important; +} + +.cm-s-p5-dark .cm-qualifier { + color: $p5-dark-lightblue; +} + +.cm-s-p5-dark .cm-tag { + color: $p5-dark-pink; +} + +.cm-s-p5-dark .cm-builtin { + color: $p5-dark-lightblue; +} + +.cm-s-p5-dark .cm-attribute { + color: $p5-dark-lightblue; +} \ No newline at end of file diff --git a/client/styles/components/_p5-light-codemirror-theme.scss b/client/styles/components/_p5-light-codemirror-theme.scss new file mode 100644 index 00000000..fb5bf759 --- /dev/null +++ b/client/styles/components/_p5-light-codemirror-theme.scss @@ -0,0 +1,118 @@ +// brown: #6C4D13 +// black: #333 +// blue: #0F9DD7 +// pink: #D9328F +// gray: #999999 +// dark blue: #318094 +// white: #fdfdfd + +//numbers +//light gray: #f4f4f4 +//dark gray: #b5b5b5 + +$p5-light-lightbrown: #A67F59; +$p5-light-brown: #704F21; +$p5-light-black: #333; +$p5-light-pink: #D9328F; +$p5-light-gray: #A0A0A0; +$p5-light-lightblue: #00A1D3; +$p5-light-darkblue: #2D7BB6; +$p5-light-white: #FDFDFD; +$p5-light-orange: #EE9900; +$p5-light-lightgray: #E0D7D1; +$p5-light-darkgray: #666666; + +$p5-light-gutter: #f4f4f4; +$p5-light-number: #b5b5b5; +$p5-light-selected: rgba(45, 123, 182, 25); +$p5-light-activeline: rgb(207, 207, 207); + +.cm-s-p5-light { + background-color: $p5-light-white; + color: $p5-light-black; +} + +.cm-s-p5-light .cm-comment { + color: $p5-light-gray; +} + +.cm-s-p5-light .cm-def { + color: $p5-light-darkblue; +} + +.cm-s-p5-light .cm-string { + color: $p5-light-lightblue; +} + +.cm-s-p5-light .cm-string-2 { + color: $p5-light-orange; +} + +.cm-s-p5-light .cm-number { + color: $p5-light-pink; +} + +.cm-s-p5-light .cm-keyword { + color: $p5-light-brown; +} + +.cm-s-p5-light .cm-variable { + color: $p5-light-lightblue; +} + +.cm-s-p5-light .cm-variable-2 { + color: $p5-light-black; +} + +.cm-s-p5-light .cm-property { + color: $p5-light-black; +} + +.cm-s-p5-light .cm-atom { + color: $p5-light-pink; +} + +.cm-s-p5-light .cm-operator { + color: $p5-light-lightbrown; +} + +.cm-s-p5-light .cm-linenumber { + color: $p5-light-number; +} + +.cm-s-p5-light .CodeMirror-selected { + background-color: $p5-light-selected; +} + +.cm-s-p5-light .CodeMirror-activeline-background { + background-color: #F3F3F3; +} + +.cm-s-p5-light .CodeMirror-activeline-gutter { + background-color: #ECECEC; +} + +.cm-s-p5-light .cm-error { + color: #f00; +} + +.cm-s-p5-light .CodeMirror-matchingbracket { + outline: 1px solid $p5-light-number; + color: black !important; +} + +.cm-s-p5-light .cm-qualifier { + color: $p5-light-lightblue; +} + +.cm-s-p5-light .cm-tag { + color: $p5-light-pink; +} + +.cm-s-p5-light .cm-builtin { + color: $p5-light-lightblue; +} + +.cm-s-p5-light .cm-attribute { + color: $p5-light-lightblue; +} diff --git a/client/styles/components/_p5-widget-codemirror-theme.scss b/client/styles/components/_p5-widget-codemirror-theme.scss deleted file mode 100644 index fe3be0b1..00000000 --- a/client/styles/components/_p5-widget-codemirror-theme.scss +++ /dev/null @@ -1,40 +0,0 @@ -:root { - --light-gray: #A0A0A0; - --dark-gray: #666; - --almost-black: #222; - --dark-brown: #704F21; - --light-brown: #a67f59; - --pinkish: #DC3787; /* not p5 pink, but related */ - --dark-blueish: #00A1D3; -} - -.cm-s-p5-widget span { color: var(--dark-gray); } - -.cm-s-p5-widget span.cm-meta { color: var(--dark-gray); } -.cm-s-p5-widget span.cm-keyword { line-height: 1em; color: var(--dark-brown); } -.cm-s-p5-widget span.cm-atom { color: var(--pinkish); } -.cm-s-p5-widget span.cm-number { color: var(--pinkish); } -.cm-s-p5-widget span.cm-def { color: var(--dark-blueish); } -.cm-s-p5-widget span.cm-variable { color: var(--dark-blueish); } -.cm-s-p5-widget span.cm-variable-2 { color: var(--almost-black); } -.cm-s-p5-widget span.cm-variable-3 { color: var(--almost-black); } -.cm-s-p5-widget span.cm-property { color: var(--almost-black); } -.cm-s-p5-widget span.cm-operator { color: var(--light-brown); } -.cm-s-p5-widget span.cm-comment { color: var(--light-gray); } -.cm-s-p5-widget span.cm-string { color: var(--dark-blueish); } -.cm-s-p5-widget span.cm-string-2 { color: var(--dark-blueish); } - -.cm-s-p5-widget span.cm-error { color: #f00; } - -.cm-s-p5-widget .CodeMirror-activeline-background { background-color: #e8f2ff; } -// .cm-s-p5-widget .CodeMirror-activeline-gutter { background-color: #e8f2ff; } -.cm-s-p5-widget .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } - -/* These styles don't seem to be set by CodeMirror's javascript mode. */ - -.cm-s-p5-widget span.cm-qualifier { color: #555; } -.cm-s-p5-widget span.cm-builtin { color: #30a; } -.cm-s-p5-widget span.cm-bracket { color: #cc7; } -.cm-s-p5-widget span.cm-tag { color: #170; } -.cm-s-p5-widget span.cm-attribute { color: #00c; } -.cm-s-p5-widget span.cm-link { color: #219; } diff --git a/client/styles/components/_preferences.scss b/client/styles/components/_preferences.scss index e9bfedf1..408fd2fb 100644 --- a/client/styles/components/_preferences.scss +++ b/client/styles/components/_preferences.scss @@ -13,7 +13,9 @@ } .preferences__exit-button { - @extend %icon; + @include themify() { + @extend %icon; + } padding-top: #{5 / $base-font-size}rem; } @@ -37,7 +39,9 @@ flex-wrap: wrap; padding-bottom: #{12 / $base-font-size}rem; & + & { - border-top: 2px dashed $light-button-border-color; + @include themify() { + border-top: 2px dashed getThemifyVariable('button-border-color'); + } } } @@ -47,31 +51,38 @@ } .preference__subtitle { + @include themify() { + color: getThemifyVariable('inactive-text-color'); + } width: 100%; margin-bottom: #{10 / $base-font-size}rem; margin-top: 0; - color: $light-inactive-text-color; } .preference__value { - border: 2px solid $light-button-border-color; + @include themify() { + border: 2px solid getThemifyVariable('button-border-color'); + background-color: getThemifyVariable('button-background-color'); + color: getThemifyVariable('input-text-color'); + } text-align: center; border-radius: 0%; width: #{48 / $base-font-size}rem; height: #{44 / $base-font-size}rem; margin: 0 #{28 / $base-font-size}rem; padding: 0; - background-color: $light-button-background-color; } .preference__label { + @include themify() { + color: getThemifyColor('inactive-text-color'); + &:hover { + color: getThemifyColor('inactive-text-color'); + } + } margin: 0; line-height: #{20 / $base-font-size}rem; - color: $light-inactive-text-color; font-size: #{9 / $base-font-size}rem; - &:hover { - color: $light-inactive-text-color; - } } .preference__vertical-list { @@ -86,17 +97,16 @@ } .preference__option { - @extend %preference-option; - list-style-type: none; - padding: 0; - color: $light-inactive-text-color; + @include themify() { + @extend %preference-option; + } } .preference__preview-button { - @extend %preference-option; - padding: 0; - padding-left: #{110 / $base-font-size}rem; - outline: none; + @include themify() { + @extend %preference-option; + padding-left: #{110 / $base-font-size}rem; + } } .preference__options { @@ -106,7 +116,10 @@ } .preference__radio-button:checked + .preference__option { - color: $light-primary-text-color; + @include themify() { + //for some reason this won't work for getThemifyVariable + color: map-get($theme-map, 'primary-text-color'); + } } .preference--hidden { diff --git a/client/styles/components/_resizer.scss b/client/styles/components/_resizer.scss index 2e30edfb..e3d71e00 100644 --- a/client/styles/components/_resizer.scss +++ b/client/styles/components/_resizer.scss @@ -1,10 +1,12 @@ .Resizer { + @include themify() { + background: getThemifyVariable('ide-border-color'); + } -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; - background: $ide-border-color; - opacity: .2; z-index: 1; + opacity: 0.02; -moz-background-clip: padding; -webkit-background-clip: padding; background-clip: padding-box; @@ -16,7 +18,7 @@ // } .Resizer.horizontal { - height: 11px; + height: 10px; margin: -5px 0; border-top: 5px solid rgba(255, 255, 255, 0); border-bottom: 5px solid rgba(255, 255, 255, 0); @@ -30,7 +32,7 @@ } .Resizer.vertical { - width: 11px; + width: 10px; margin: 0 -5px; border-left: 5px solid rgba(255, 255, 255, 0); border-right: 5px solid rgba(255, 255, 255, 0); diff --git a/client/styles/components/_sidebar.scss b/client/styles/components/_sidebar.scss index 2081f4a0..e9142ef0 100644 --- a/client/styles/components/_sidebar.scss +++ b/client/styles/components/_sidebar.scss @@ -1,8 +1,10 @@ .sidebar__header { + @include themify() { + border-top: 1px solid map-get($theme-map, 'ide-border-color'); + } padding: #{10 / $base-font-size}rem #{6 / $base-font-size}rem; display: flex; justify-content: space-between; - border-top: 1px solid $ide-border-color; align-items: center; height: #{47 / $base-font-size}rem; } @@ -18,18 +20,19 @@ } .sidebar__add { - @extend %icon; - cursor: pointer; - // height: #{26 / $base-font-size}rem; - // margin-right: #{16 / $base-font-size}rem; - // font-size: #{24 / $base-font-size}rem; + @include themify() { + @extend %icon; + } .sidebar--contracted & { display: none; } } .sidebar__file-list { - border-top: 1px solid $ide-border-color; + @include themify() { + border-color: getThemifyVariable('ide-border-color') + } + border-top: 1px solid; .sidebar--contracted & { display: none; } @@ -41,13 +44,15 @@ .sidebar__file-item { font-size: #{16 / $base-font-size}rem; - color: $light-inactive-text-color; cursor: pointer; - &:hover > .file-item__content .sidebar__file-item-name { - color: $light-primary-text-color; - } - &:hover > .file-item__content .sidebar__file-item-icon g { - fill: $light-primary-text-color; + @include themify() { + color: map-get($theme-map, 'inactive-text-color'); + &:hover > .file-item__content .sidebar__file-item-name { + color: map-get($theme-map, 'primary-text-color'); + } + &:hover > .file-item__content .sidebar__file-item-icon g { + fill: map-get($theme-map, 'primary-text-color'); + } } } @@ -75,9 +80,10 @@ .file-item__content { display: flex; position: relative; - padding: #{8 / $base-font-size}rem 0; .sidebar__file-item--selected > & { - background-color: $ide-border-color; + @include themify() { + background-color: map-get($theme-map, 'file-selected-color'); + } } .sidebar--contracted & { display: none; @@ -85,13 +91,17 @@ } .sidebar__file-item-name { + padding: #{8 / $base-font-size}rem 0; .sidebar__file-item--editing & { display: none; } } .sidebar__file-item-show-options { - @extend %icon; + @include themify() { + @extend %icon; + padding: #{8 / $base-font-size}rem 0; + } display: none; position: absolute; right: #{26 / $base-font-size}rem; @@ -102,6 +112,9 @@ .sidebar__file-item-options { @extend %modal; + @include themify() { + background-color: getThemeColor('modal-background-color'); + } position: absolute; top: 95%; left: 77%; @@ -109,7 +122,6 @@ display: none; z-index: 100; padding: #{8 / $base-font-size}rem #{16 / $base-font-size}rem; - background-color: $light-modal-background-color; z-index: 100; .sidebar__file-item--open > .file-item__content & { display: block; @@ -120,27 +132,16 @@ display: none; padding: 0; border: 0; - background-color: transparent; - max-width: 90%; + width: calc(100% - #{100 / $base-font-size}rem); .sidebar__file-item--editing & { display: inline-block; } } -.sidebar__contract { - @extend %icon; - margin-left: #{10 / $base-font-size}rem; - height: #{14 / $base-font-size}rem; - & svg { - height: #{14 / $base-font-size}rem; - } - .sidebar--contracted & { - display: none; - } -} - .sidebar__expand { - @extend %icon; + @include themify() { + @extend %icon; + } height: #{14 / $base-font-size}rem; & svg { height: #{14 / $base-font-size}rem; @@ -151,6 +152,20 @@ } } +.sidebar__contract { + @include themify() { + @extend %icon; + } + margin-left: #{10 / $base-font-size}rem; + height: #{14 / $base-font-size}rem; + & svg { + height: #{14 / $base-font-size}rem; + } + .sidebar--contracted & { + display: none; + } +} + .sidebar__icons { display: flex; align-items: center; @@ -158,14 +173,23 @@ } .sidebar__folder-icon { + padding: #{8 / $base-font-size}rem 0; margin-right: #{5 / $base-font-size}rem; & g { - fill: $light-primary-text-color; + @include themify() { + fill: map-get($theme-map, 'primary-text-color'); + } } } .sidebar__file-item-icon { + padding: #{8 / $base-font-size}rem 0; margin-right: #{5 / $base-font-size}rem; + & g { + @include themify() { + fill: getThemifyVariable('inactive-text-color'); + } + } } .sidebar__file-item-closed { @@ -178,6 +202,7 @@ .sidebar__file-item-open { @extend .sidebar__file-item-icon; + display: inline-block; .sidebar__file-item--closed & { display: none; } diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss index 2a3a5d5f..a630b148 100644 --- a/client/styles/components/_sketch-list.scss +++ b/client/styles/components/_sketch-list.scss @@ -30,5 +30,7 @@ } .sketch-list__exit-button { - @extend %icon; + @include themify() { + @extend %icon; + } } \ No newline at end of file diff --git a/client/styles/components/_toast.scss b/client/styles/components/_toast.scss index 5a321152..f94642ff 100644 --- a/client/styles/components/_toast.scss +++ b/client/styles/components/_toast.scss @@ -10,7 +10,10 @@ } .toast__close { - @extend %icon; + @include themify() { + @extend %icon; + } + color: $toast-text-color; & g { fill: $toast-text-color; } diff --git a/client/styles/components/_toolbar.scss b/client/styles/components/_toolbar.scss index 180d1bbe..cee5f6ad 100644 --- a/client/styles/components/_toolbar.scss +++ b/client/styles/components/_toolbar.scss @@ -1,9 +1,11 @@ .toolbar__play-button { - @extend %toolbar-button; - margin-right: #{15 / $base-font-size}rem; - &--selected { - @extend %toolbar-button--selected; + @include themify() { + @extend %toolbar-button; + &--selected { + @extend %toolbar-button--selected; + } } + margin-right: #{15 / $base-font-size}rem; & span { padding-left: #{2 / $base-font-size}rem; } @@ -14,23 +16,25 @@ } .toolbar__stop-button { - @extend %toolbar-button; - &--selected { - @extend %toolbar-button--selected; + @include themify() { + @extend %toolbar-button; + &--selected { + @extend %toolbar-button--selected; + } } } .toolbar__preferences-button { - @extend %toolbar-button; + @include themify() { + @extend %toolbar-button; + line-height: #{50 / $base-font-size}rem; + &--selected { + @extend %toolbar-button--selected; + line-height: #{50 / $base-font-size}rem; + } + } line-height: #{50 / $base-font-size}rem; margin-left: auto; - &--selected { - @extend %toolbar-button--selected; - } -} - -.toolbar__shortcut-button { - } .toolbar__logo { @@ -44,6 +48,9 @@ } .toolbar__project-name-container { + @include themify() { + border-color: getThemifyVariable('inactive-text-color'); + } border-left: 2px dashed; margin-left: #{10 / $base-font-size}rem; padding-left: #{10 / $base-font-size}rem; @@ -53,14 +60,13 @@ } .toolbar__project-name { - color: $light-inactive-text-color; + @include themify() { + color: getThemifyVariable('inactive-text-color'); + &:hover { + color: getThemifyVariable('primary-text-color'); + } + } cursor: pointer; - &:hover { - color: $light-primary-text-color; - } - &:focus { - color: $light-inactive-text-color; - } .toolbar__project-name-container--editing & { display: none; diff --git a/client/styles/layout/_ide.scss b/client/styles/layout/_ide.scss index 7312f78d..b2a5ceeb 100644 --- a/client/styles/layout/_ide.scss +++ b/client/styles/layout/_ide.scss @@ -1,8 +1,12 @@ .ide { display: flex; - flex-direction: column; - height: 100%; - flex-wrap: wrap; + flex-direction: column; + height: 100%; + flex-wrap: wrap; + @include themify() { + color: getThemifyVariable('primary-text-color'); + background-color: getThemifyVariable('background-color'); + } } .editor-preview-container { diff --git a/client/styles/main.scss b/client/styles/main.scss index c3aa6a0a..5b41b334 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -1,4 +1,6 @@ @import 'abstracts/variables'; +@import 'abstracts/functions'; +@import 'abstracts/mixins'; @import 'abstracts/placeholders'; @import 'base/reset'; @@ -9,7 +11,8 @@ @import 'vendors/lint'; @import 'vendors/dropzone'; -@import 'components/p5-widget-codemirror-theme'; +@import 'components/p5-light-codemirror-theme'; +@import 'components/p5-dark-codemirror-theme'; @import 'components/editor'; @import 'components/nav'; @import 'components/toolbar'; diff --git a/server/models/user.js b/server/models/user.js index b9fa4987..843fe929 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -15,7 +15,8 @@ const userSchema = new Schema({ isTabIndent: { type: Boolean, default: false }, autosave: { type: Boolean, default: true }, lintWarning: { type: Boolean, default: false }, - textOutput: { type: Boolean, default: false } + textOutput: { type: Boolean, default: false }, + theme: { type: String, default: 'light' } } }, { timestamps: true });