diff --git a/components/Button.js b/components/Button.js index 49a5053..366b2ff 100644 --- a/components/Button.js +++ b/components/Button.js @@ -11,7 +11,6 @@ const Button = ({ hoverBackground = COLORS.HOVER, hoverColor, disabled, - notAllowed, selected, children, border, @@ -22,13 +21,7 @@ const Button = ({ padding = 0, margin = 0 }) => ( - + + ))} + + + + {selected && ( + + )} + + +) + +const ThemeItem = ({ children, item, isSelected, onClick }) => ( +
+ {children} + {item.custom && !isSelected && ( +
+ +
+ )} + +
+) + +const themeIcon = + +class Themes extends React.PureComponent { + selectedTheme = DEFAULT_THEME + + state = { + themes: THEMES, + preset: this.props.theme, + highlights: {}, + name: 'Custom Theme', + selected: null + } + + componentDidMount() { + const storedThemes = getThemes(localStorage) || [] + + this.setState(({ themes }) => { + const newThemes = [...storedThemes, ...themes] + + const name = `Custom Theme ${newThemes.filter(({ name }) => name.startsWith('Custom Theme')) + .length + 1}` + + this.selectedTheme = newThemes.find(({ id }) => id === this.props.theme) || DEFAULT_THEME + + return { + themes: newThemes, + highlights: this.selectedTheme.highlights, + name + } + }) + } + + applyPreset = preset => + this.setState(({ themes }) => ({ + preset, + highlights: themes.find(({ id }) => id === preset).highlights + })) + + handleDropdown = ({ id }) => { + if (id === 'create') { + this.props.toggleVisibility() + } else { + this.props.updateTheme(id) + } + } + + updateName = ({ target: { value: name } }) => this.setState({ name }) + + selectHighlight = highlight => () => + this.setState(({ selected }) => ({ + selected: selected === highlight ? null : highlight + })) + + updateHighlight = ({ rgb }) => + this.setState({ + highlights: { + ...this.state.highlights, + [this.state.selected]: stringifyRGBA(rgb) + } + }) + + removeTheme = id => event => { + event.stopPropagation() + + const { themes } = this.state + + const newThemes = themes.filter(t => t.id !== id) + + saveThemes(localStorage, newThemes.filter(({ custom }) => custom)) + + if (this.props.theme === id) { + this.props.updateTheme(DEFAULT_THEME.id) + } else { + this.setState({ themes: newThemes }) + } + } + + createTheme = () => { + const { themes, name, highlights } = this.state + + const id = `theme:${generateId()}` + + const newTheme = { + id, + name, + highlights, + custom: true + } + + const customThemes = [newTheme, ...themes.filter(({ custom }) => custom)] + + saveThemes(localStorage, customThemes) + + this.props.updateTheme(id) + } + + itemWrapper = props => + + render() { + const { theme, isVisible, toggleVisibility } = this.props + const { name, themes, highlights, selected, preset } = this.state + + const dropdownValue = isVisible ? { name } : { id: theme, name: this.selectedTheme.name } + + const dropdownList = [ + { + id: 'create', + name: 'Create +' + }, + ...themes + ] + + return ( +
+ + {isVisible && ( + + )} + +
+ ) + } +} + +export default managePopout(Themes) diff --git a/lib/constants.js b/lib/constants.js index 19fdcd7..2696675 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -15,116 +15,519 @@ export const FONTS = [ { id: 'Ubuntu Mono', name: 'Ubuntu Mono' } ] +export const HIGHLIGHT_KEYS = [ + 'background', + 'text', + 'variable', + 'attribute', + 'definition', + 'keyword', + 'operator', + 'property', + 'number', + 'string', + 'comment', + 'meta' +] + export const THEMES = [ { id: '3024-night', - name: '3024 Night' + name: '3024 Night', + highlights: { + background: '#090300', + text: '#d6d5d4', + variable: '#01a0e4', + attribute: '#00c', + definition: '#e8bbd0', + keyword: '#db2d20', + operator: '#fff', + property: '#01a252', + number: '#a16a94', + string: '#fded02', + comment: '#cdab53', + meta: '#555' + } }, { id: 'blackboard', - name: 'Blackboard' + name: 'Blackboard', + highlights: { + background: '#0C1021', + text: '#F8F8F8', + variable: '#FF6400', + attribute: '#8DA6CE', + definition: '#8DA6CE', + keyword: '#FBDE2D', + operator: '#fff', + property: '#fff', + number: '#D8FA3C', + string: '#61CE3C', + comment: '#AEAEAE', + meta: '#D8FA3C', + tag: '#8DA6CE' + } }, { id: 'base16-dark', - name: 'Base 16 (Dark)' + name: 'Base 16 (Dark)', + highlights: { + background: '#151515', + text: '#e0e0e0', + variable: '#6a9fb5', + attribute: '#00c', + definition: '#d28445', + keyword: '#ac4142', + operator: '#fff', + property: '#90a959', + number: '#aa759f', + string: '#f4bf75', + comment: '#8f5536', + meta: '#555', + tag: '#ac4142' + } }, { id: 'base16-light', name: 'Base 16 (Light)', - light: true + light: true, + highlights: { + background: '#f5f5f5', + text: '#202020', + variable: '#90a959', + attribute: '#90a959', + definition: '#d28445', + keyword: '#ac4142', + operator: '#fff', + property: '#90a959', + number: '#aa759f', + string: '#f4bf75', + comment: '#8f5536', + meta: '#555', + tag: '#ac4142' + } }, { id: 'cobalt', - name: 'Cobalt' + name: 'Cobalt', + highlights: { + background: '#002240', + text: '#fff', + variable: '#9effff', + attribute: '#ff80e1', + definition: '#fff', + keyword: '#ffee80', + operator: '#fff', + property: '#fff', + number: '#ff80e1', + string: '#3ad900', + comment: '#08f', + meta: '#ff9d00', + tag: '#9effff' + } }, { id: 'dracula', - name: 'Dracula' + name: 'Dracula', + highlights: { + background: '#282a36', + text: '#f8f8f2', + variable: '#fff', + attribute: '#50fa7b', + definition: '#50fa7b', + keyword: '#ff79c6', + operator: '#ff79c6', + property: '#66d9ef', + number: '#bd93f9', + string: '#f1fa8c', + comment: '#6272a4', + meta: '#f8f8f2', + tag: '#ff79c6' + } }, { id: 'duotone-dark', - name: 'Duotone' + name: 'Duotone', + highlights: { + background: '#2a2734', + text: '#6c6783', + variable: '#ffcc99', + attribute: '#ffcc99', + definition: '#eeebff', + keyword: '#ffcc99', + operator: '#ffad5c', + property: '#9a86fd', + number: '#ffcc99', + string: '#ffb870', + comment: '#6c6783', + meta: '#555', + tag: '#eeebff' + } }, { id: 'hopscotch', - name: 'Hopscotch' + name: 'Hopscotch', + highlights: { + background: '#322931', + text: '#d5d3d5', + variable: '#0066ff', + attribute: '#8fc13e', + definition: '#fd8b19', + keyword: '#dd464c', + operator: '#fff', + property: '#8fc13e', + number: '#c85e7c', + string: '#fdcc59', + comment: '#b33508', + meta: '#555', + tag: '#dd464c' + } }, { id: 'lucario', - name: 'Lucario' + name: 'Lucario', + highlights: { + background: '#2b3e50', + text: '#f8f8f2', + variable: '#f8f8f2', + attribute: '#66D9EF', + definition: '#72C05D', + keyword: '#ff6541', + operator: '#66D9EF', + property: '#f8f8f2', + number: '#ca94ff', + string: '#E6DB74', + comment: '#5c98cd', + meta: '#f8f8f2', + tag: '#ff6541' + } }, { id: 'material', - name: 'Material' + name: 'Material', + highlights: { + background: '#263238', + text: 'rgba(233, 237, 237, 1)', + variable: '#80CBC4', + attribute: '#FFCB6B', + definition: 'rgba(233, 237, 237, 1)', + keyword: 'rgba(199, 146, 234, 1)', + operator: 'rgba(233, 237, 237, 1)', + property: '#80CBAE', + number: '#F77669', + string: '#C3E88D', + comment: '#546E7A', + meta: '#80CBC4', + tag: 'rgba(255, 83, 112, 1)' + } }, { id: 'monokai', - name: 'Monokai' + name: 'Monokai', + highlights: { + background: '#272822', + text: '#f8f8f2', + variable: '#9effff', + attribute: '#a6e22e', + definition: '#fd971f', + keyword: '#f92672', + operator: '#fff', + property: '#a6e22e', + number: '#ae81ff', + string: '#e6db74', + comment: '#75715e', + meta: '#555', + tag: '#bc6283' + } }, { id: 'night-owl', - name: 'Night Owl' + name: 'Night Owl', + highlights: { + background: '#011627', + text: '#abb2bf', + variable: '#82AAFF', + attribute: '#F78C6C', + definition: '#82AAFF', + keyword: '#c792ea', + operator: '#c792ea', + property: '#fff', + number: '#F78C6C', + string: '#ecc48d', + comment: '#5c6370', + meta: '#7fdbca' + } }, { id: 'nord', - name: 'Nord' + name: 'Nord', + highlights: { + background: '#2e3440', + text: '#d8dee9', + variable: '#88C0D0', + attribute: '#8FBCBB', + definition: '#D8DEE9', + keyword: '#81A1C1', + operator: '#81A1C1', + property: '#D8DEE9', + number: '#B48EAD', + string: '#A3BE8C', + comment: '#4C566A', + meta: '#81A1C1', + tag: '#81A1C1' + } }, { id: 'oceanic-next', - name: 'Oceanic Next' + name: 'Oceanic Next', + highlights: { + background: '#304148', + text: '#f8f8f2', + variable: '#f8f8f2', + attribute: '#C594C5', + definition: '#6699CC', + keyword: '#C594C5', + operator: '#fff', + property: '#99C794', + number: '#F99157', + string: '#99C794', + comment: '#65737E', + meta: '#555', + tag: '#C594C5' + } }, { id: 'one-light', name: 'One Light', - light: true + light: true, + highlights: { + background: '#fafafa', + text: '#383a42', + variable: '#e06c75', + attribute: '#d19a66', + definition: '#4078f2', + keyword: '#a626a4', + operator: '#0184bc', + property: '#4078f2', + number: '#986801', + string: '#50a14f', + comment: '#a0a1a7', + meta: '#383a42', + tag: '#e45649' + } }, { id: 'one-dark', - name: 'One Dark' + name: 'One Dark', + highlights: { + background: '#282c34', + text: '#abb2bf', + variable: '#e06c75', + attribute: '#d19a66', + definition: '#e5c07b', + keyword: '#c678dd', + operator: '#56b6c2', + property: '#56b6c2', + number: '#d19a66', + string: '#98c379', + comment: '#5c6370', + meta: '#abb2bf', + tag: '#e06c75' + } }, { id: 'panda-syntax', - name: 'Panda' + name: 'Panda', + highlights: { + background: '#292A2B', + text: '#E6E6E6', + variable: '#ff9ac1', + attribute: '#ffb86c', + definition: '#e6e6e6', + keyword: '#FF75B5', + operator: '#f3f3f3', + property: '#f3f3f3', + number: '#FFB86C', + string: '#19F9D8', + comment: '#676B79', + meta: '#b084eb', + tag: '#ff2c6d' + } }, { id: 'paraiso-dark', - name: 'Paraiso' + name: 'Paraiso', + highlights: { + background: '#2f1e2e', + text: '#b9b6b0', + variable: '#06b6ef', + attribute: '#48b685', + definition: '#f99b15', + keyword: '#ef6155;', + operator: '#fff', + property: '#48b685', + number: '#815ba4', + string: '#fec418', + comment: '#e96ba8', + meta: '#555', + tag: '#ef6155' + } }, { id: 'seti', - name: 'Seti' + name: 'Seti', + highlights: { + background: '#151718', + text: '#CFD2D1', + variable: '#a074c4', + attribute: '#9fca56', + definition: '#55b5db', + keyword: '#e6cd69', + operator: '#9fca56', + property: '#a074c4', + number: '#cd3f45', + string: '#55b5db', + comment: '#41535b', + meta: '#55b5db', + tag: '#55b5db' + } }, { id: 'solarized dark', name: 'Solarized (Dark)', - link: 'solarized' + link: 'solarized', + highlights: { + background: '#002b36', + text: '#839496', + variable: '#b58900', + attribute: '#2aa198', + definition: '#2aa198', + keyword: '#cb4b16', + operator: '#6c71c4', + property: '#2aa198', + number: '#d33682', + string: '#859900', + comment: '#586e75', + meta: '#859900', + tag: '#93a1a1' + } }, { id: 'solarized light', name: 'Solarized (Light)', link: 'solarized', - light: true + light: true, + highlights: { + background: '#fdf6e3', + text: '#657b83', + variable: '#839496', + attribute: '#2aa198', + definition: '#2aa198', + keyword: '#cb4b16', + operator: '#6c71c4', + property: '#2aa198', + number: '#d33682', + string: '#859900', + comment: '#586e75', + meta: '#859900', + tag: '#93a1a1' + } }, { id: 'tomorrow-night-bright', - name: 'Tomorrow Night' + name: 'Tomorrow Night', + highlights: { + background: '#000000', + text: '#eaeaea', + variable: '#7aa6da', + attribute: '#99cc99', + definition: '#e78c45', + keyword: '#d54e53', + operator: '#fff', + property: '#99cc99', + number: '#a16a94', + string: '#e7c547', + comment: '#d27b53', + meta: '#555', + tag: '#d54e53' + } }, { id: 'twilight', - name: 'Twilight' + name: 'Twilight', + highlights: { + background: '#141414', + text: '#f7f7f7', + variable: '#607392', + attribute: '#d6bb6d', + definition: '#607392', + keyword: '#f9ee98', + operator: '#cda869', + property: '#fff', + number: '#ca7841', + string: '#8f9d6a', + comment: '#777', + meta: '#f7f7f7', + tag: '#997643' + } }, { id: 'verminal', - name: 'Verminal' + name: 'Verminal', + highlights: { + background: 'rgba(0, 0, 0, 0.85)', + text: '#fff', + variable: '#ff9ba3', + attribute: '#d19a66', + definition: '#34B7FF', + keyword: '#9AE1FF', + operator: '#FA78C3', + property: '#0af', + number: '#d19a66', + string: '#98c379', + comment: '#5c6370', + meta: '#abb2bf', + tag: '#e06c75' + } }, { id: 'yeti', name: 'Yeti', - light: true + light: true, + highlights: { + background: '#ECEAE8', + text: '#d1c9c0', + variable: '#a074c4', + attribute: '#9fb96e', + definition: '#55b5db', + keyword: '#9fb96e', + operator: '#9fb96e', + property: '#a074c4', + number: '#a074c4', + string: '#96c0d8', + comment: '#d4c8be', + meta: '#96c0d8', + tag: '#96c0d8' + } }, { id: 'zenburn', - name: 'Zenburn' + name: 'Zenburn', + highlights: { + background: '#3f3f3f', + text: '#dcdccc', + variable: '#dcdccc', + attribute: '#dfaf8f', + definition: '#dcdccc', + keyword: '#f0dfaf', + operator: '#f0efd0', + property: '#dfaf8f', + number: '#dcdccc', + string: '#cc9393', + comment: '#7f9f7f', + meta: '#f0dfaf', + tag: '#93e0e3' + } } ] diff --git a/lib/util.js b/lib/util.js index 0e39260..92e4364 100644 --- a/lib/util.js +++ b/lib/util.js @@ -4,9 +4,17 @@ import { unescape } from 'escape-goat' const SETTINGS_KEY = 'CARBON_STATE' const PRESETS_KEY = 'CARBON_PRESETS' +const THEMES_KEY = 'CARBON_THEMES' -const assignSettings = morph.assign(SETTINGS_KEY) -const assignPresets = morph.assign(PRESETS_KEY) +const createAssigner = key => { + const assign = morph.assign(key) + + return (window, v) => assign(window, JSON.stringify(v)) +} + +export const saveSettings = createAssigner(SETTINGS_KEY) +export const savePresets = createAssigner(PRESETS_KEY) +export const saveThemes = createAssigner(THEMES_KEY) const parse = v => { try { @@ -46,9 +54,10 @@ export const getPresets = morph.compose( morph.get(PRESETS_KEY) ) -export const saveSettings = (window, v) => assignSettings(window, JSON.stringify(v)) - -export const savePresets = (window, v) => assignPresets(window, JSON.stringify(v)) +export const getThemes = morph.compose( + parse, + morph.get(THEMES_KEY) +) export const clearSettings = () => localStorage.removeItem(SETTINGS_KEY) @@ -76,3 +85,8 @@ export const omit = (object, keys) => omitBy(object, (_, k) => keys.indexOf(k) > export const stringifyRGBA = obj => `rgba(${obj.r},${obj.g},${obj.b},${obj.a})` export const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1) + +export const generateId = () => + Math.random() + .toString(36) + .slice(2)