rework logic

main
raboid 6 years ago committed by Michael Fix
parent 8c2ce87c45
commit 4dea566193

@ -12,7 +12,6 @@ import {
LANGUAGE_MODE_HASH, LANGUAGE_MODE_HASH,
LANGUAGE_NAME_HASH, LANGUAGE_NAME_HASH,
LANGUAGE_MIME_HASH, LANGUAGE_MIME_HASH,
THEMES_HASH,
DEFAULT_SETTINGS DEFAULT_SETTINGS
} from '../lib/constants' } from '../lib/constants'
@ -77,7 +76,7 @@ class Carbon extends React.PureComponent {
const options = { const options = {
lineNumbers: config.lineNumbers, lineNumbers: config.lineNumbers,
mode: languageMode || 'plaintext', mode: languageMode || 'plaintext',
theme: config.theme, theme: config.theme.id,
scrollBarStyle: null, scrollBarStyle: null,
viewportMargin: Infinity, viewportMargin: Infinity,
lineWrapping: true, lineWrapping: true,
@ -108,7 +107,7 @@ class Carbon extends React.PureComponent {
value={this.props.children} value={this.props.children}
options={options} options={options}
/> />
{config.watermark && <Watermark light={THEMES_HASH[config.theme].light} />} {config.watermark && <Watermark light={config.theme.light} />}
<div className="container-bg"> <div className="container-bg">
<div className="white eliminateOnRender" /> <div className="white eliminateOnRender" />
<div className="alpha eliminateOnRender" /> <div className="alpha eliminateOnRender" />

@ -25,10 +25,12 @@ import {
DEFAULT_CODE, DEFAULT_CODE,
DEFAULT_SETTINGS, DEFAULT_SETTINGS,
DEFAULT_LANGUAGE, DEFAULT_LANGUAGE,
DEFAULT_PRESET_ID DEFAULT_PRESET_ID,
DEFAULT_THEME,
THEMES
} from '../lib/constants' } from '../lib/constants'
import { serializeState, getRouteState } from '../lib/routing' import { serializeState, getRouteState } from '../lib/routing'
import { getSettings, unescapeHtml, formatCode, omit } from '../lib/util' import { getThemes, saveThemes, getSettings, unescapeHtml, formatCode, omit } from '../lib/util'
import LanguageIcon from './svg/Language' import LanguageIcon from './svg/Language'
const languageIcon = <LanguageIcon /> const languageIcon = <LanguageIcon />
@ -44,8 +46,8 @@ class Editor extends React.Component {
super(props) super(props)
this.state = { this.state = {
...DEFAULT_SETTINGS, ...DEFAULT_SETTINGS,
themes: THEMES,
preset: DEFAULT_PRESET_ID, preset: DEFAULT_PRESET_ID,
highlights: {},
loading: true loading: true
} }
@ -81,6 +83,25 @@ class Editor extends React.Component {
loading: false loading: false
} }
const storedThemes = getThemes(localStorage)
newState.themes = storedThemes
? [...storedThemes, ...this.state.themes]
: [...this.state.themes]
if (newState.theme) {
newState.theme = newState.themes.find(t => t.id === newState.theme) || DEFAULT_THEME
}
if (newState.highlights) {
newState.theme = {
...newState.theme,
highlights: newState.highlights
}
delete newState.highlights
}
// Makes sure the slash in 'application/X' is decoded // Makes sure the slash in 'application/X' is decoded
if (newState.language) { if (newState.language) {
newState.language = unescapeHtml(newState.language) newState.language = unescapeHtml(newState.language)
@ -99,7 +120,10 @@ class Editor extends React.Component {
updateState = updates => { updateState = updates => {
this.setState(updates, () => { this.setState(updates, () => {
if (!this.gist) { if (!this.gist) {
this.props.onUpdate(this.state) this.props.onUpdate({
...this.state,
theme: this.state.theme.id
})
} }
}) })
} }
@ -117,7 +141,16 @@ class Editor extends React.Component {
// if safari, get image from api // if safari, get image from api
const isPNG = format !== 'svg' const isPNG = format !== 'svg'
if (this.context.image && this.isSafari && isPNG) { if (this.context.image && this.isSafari && isPNG) {
const encodedState = serializeState(this.state) const state = {
...this.state,
theme: this.state.theme.id,
highlights: this.state.theme.highlights
}
delete state.themes
const encodedState = serializeState(state)
return this.context.image(encodedState) return this.context.image(encodedState)
} }
@ -260,6 +293,35 @@ class Editor extends React.Component {
} }
} }
updateTheme = theme => this.updateState({ theme })
createTheme = theme =>
this.setState(({ themes }) => {
const newThemes = [theme, ...themes]
saveThemes(localStorage, newThemes.filter(({ custom }) => custom))
return {
theme,
themes: newThemes
}
})
removeTheme = id =>
this.setState(({ themes, theme }) => {
const newState = {
themes: themes.filter(t => t.id !== id)
}
saveThemes(localStorage, newState.themes.filter(({ custom }) => custom))
if (theme.id === id) {
newState.theme = DEFAULT_THEME
}
return newState
})
format = () => format = () =>
formatCode(this.state.code) formatCode(this.state.code)
.then(this.updateCode) .then(this.updateCode)
@ -267,12 +329,10 @@ class Editor extends React.Component {
// create toast here in the future // create toast here in the future
}) })
applyPreset = ({ id: preset, ...settings }) => this.updateState({ preset, ...settings })
render() { render() {
const { const {
theme, theme,
highlights, themes,
language, language,
backgroundColor, backgroundColor,
backgroundImage, backgroundImage,
@ -286,7 +346,13 @@ class Editor extends React.Component {
return ( return (
<div className="editor"> <div className="editor">
<Toolbar> <Toolbar>
<Themes update={this.updateState} theme={theme} highlights={highlights} /> <Themes
update={this.updateTheme}
remove={this.removeTheme}
create={this.createTheme}
theme={theme}
themes={themes}
/>
<Dropdown <Dropdown
icon={languageIcon} icon={languageIcon}
selected={ selected={

@ -6,7 +6,7 @@ import ListSetting from '../ListSetting'
import Popout from '../Popout' import Popout from '../Popout'
import ColorPicker from '../ColorPicker' import ColorPicker from '../ColorPicker'
import { HIGHLIGHT_KEYS, COLORS } from '../../lib/constants' import { HIGHLIGHT_KEYS, COLORS } from '../../lib/constants'
import { capitalize } from '../../lib/util' import { capitalize, stringifyRGBA, generateId } from '../../lib/util'
const colorPickerStyle = { const colorPickerStyle = {
backgroundColor: COLORS.BLACK, backgroundColor: COLORS.BLACK,
@ -47,143 +47,152 @@ const HighlightPicker = ({ title, onChange, color }) => (
</div> </div>
) )
const ThemeCreate = ({ const getCustomName = themes =>
theme, `Custom Theme ${themes.filter(({ name }) => name.startsWith('Custom Theme')).length + 1}`
themes,
highlights, const ThemeCreate = ({ theme, themes, highlights, create, updateHighlights }) => {
name, const [preset, updatePreset] = React.useState(theme.id)
preset, const [highlight, selectHighlight] = React.useState()
selected, const [name, updateName] = React.useState(getCustomName(themes))
createTheme,
applyPreset, return (
updateName, <Popout pointerLeft="15px" style={{ display: 'flex' }}>
selectHighlight, <div className="theme-settings">
updateHighlight <div className="field name-field">
}) => ( <span>Name</span>
<Popout pointerLeft="15px" style={{ display: 'flex' }}> <Input
<div className="theme-settings"> title="name"
<div className="field name-field"> name="name"
<span>Name</span> placeholder="Custom Theme"
<Input value={name}
title="name" onChange={({ target: { value } }) => updateName(value)}
name="name" maxLength="32"
placeholder="Custom Theme" />
value={name} </div>
onChange={updateName} <div className="theme-select">
maxLength="32" <ListSetting
/> title="Preset"
</div> items={themes}
<div className="theme-select"> selected={preset}
<ListSetting onOpen={() => selectHighlight(null)}
title="Preset" onChange={id => {
items={themes} updatePreset(id)
selected={preset || theme} updateHighlights(themes.find(t => t.id === id).highlights)
onOpen={selectHighlight(null)} }}
onChange={applyPreset} >
{({ name }) => <span>{name}</span>}
</ListSetting>
</div>
<div className="theme-colors">
{HIGHLIGHT_KEYS.map(key => (
<div className="field" key={key}>
<Button
padding="4px 4px 4px 8px"
onClick={() => selectHighlight(key)}
background={highlight === key ? COLORS.HOVER : COLORS.BLACK}
>
<div className="row">
<span>{capitalize(key)}</span>
<span
className="color-square"
style={{
backgroundColor: highlights[key],
boxShadow: `inset 0px 0px 0px ${highlight === key ? 2 : 1}px ${
COLORS.SECONDARY
}`
}}
/>
</div>
</Button>
</div>
))}
</div>
<Button
center
disabled={!name}
className="create-button"
padding="8px 0"
background={COLORS.SECONDARY}
hoverBackground={COLORS.SECONDARY}
color={COLORS.BLACK}
onClick={() =>
create({
id: `theme:${generateId()}`,
name,
highlights,
custom: true
})
}
> >
{({ name }) => <span>{name}</span>} Create +
</ListSetting> </Button>
</div>
<div className="theme-colors">
{HIGHLIGHT_KEYS.map(key => (
<div className="field" key={key}>
<Button
padding="4px 4px 4px 8px"
onClick={selectHighlight(key)}
background={selected === key ? COLORS.HOVER : COLORS.BLACK}
>
<div className="row">
<span>{capitalize(key)}</span>
<span
className="color-square"
style={{
backgroundColor: highlights[key],
boxShadow: `inset 0px 0px 0px ${selected === key ? 2 : 1}px ${COLORS.SECONDARY}`
}}
/>
</div>
</Button>
</div>
))}
</div> </div>
<Button {highlight && (
center <HighlightPicker
disabled={!name} title={capitalize(highlight)}
className="create-button" color={highlights[highlight]}
padding="8px 0" onChange={({ rgb }) => updateHighlights({ [highlight]: stringifyRGBA(rgb) })}
background={COLORS.SECONDARY} />
hoverBackground={COLORS.SECONDARY} )}
color={COLORS.BLACK} <style jsx>
onClick={createTheme} {`
> .field {
Create + align-items: center;
</Button> border-bottom: solid 1px ${COLORS.SECONDARY};
</div> display: flex;
{selected && ( height: 35px;
<HighlightPicker justify-content: space-between;
title={capitalize(selected)} position: relative;
color={highlights[selected]} }
onChange={updateHighlight}
/> .field:nth-last-child(-n + 2) {
)} border-width: 2px;
<style jsx> }
{`
.field { .row {
align-items: center; display: flex;
border-bottom: solid 1px ${COLORS.SECONDARY}; flex: 1;
display: flex; align-items: center;
height: 35px; justify-content: space-between;
justify-content: space-between; }
position: relative;
} .name-field {
padding: 8px;
.field:nth-last-child(-n + 2) { }
border-width: 2px;
} .theme-select {
width: 100%;
.row { }
display: flex;
flex: 1; .theme-settings {
align-items: center; display: flex;
justify-content: space-between; flex-direction: column;
} width: 224px;
}
.name-field {
padding: 8px; .theme-colors {
} display: flex;
flex-wrap: wrap;
.theme-select { border-top: 2px solid ${COLORS.SECONDARY};
width: 100%; text-transform: capitalize;
} }
.theme-settings { .theme-colors .field {
display: flex; width: 50%;
flex-direction: column; }
width: 224px;
} .theme-colors .field:nth-child(odd) {
border-right: 1px solid ${COLORS.SECONDARY};
.theme-colors { }
display: flex;
flex-wrap: wrap; .color-square {
border-top: 2px solid ${COLORS.SECONDARY}; border-radius: 3px;
text-transform: capitalize; padding: 12px;
} }
`}
.theme-colors .field { </style>
width: 50%; </Popout>
} )
}
.theme-colors .field:nth-child(odd) {
border-right: 1px solid ${COLORS.SECONDARY};
}
.color-square {
border-radius: 3px;
padding: 12px;
}
`}
</style>
</Popout>
)
export default ThemeCreate export default ThemeCreate

@ -5,18 +5,25 @@ import Dropdown from '../Dropdown'
import { managePopout } from '../Popout' import { managePopout } from '../Popout'
import ThemeIcon from '../svg/Theme' import ThemeIcon from '../svg/Theme'
import RemoveIcon from '../svg/Remove' import RemoveIcon from '../svg/Remove'
import { THEMES, COLORS, DEFAULT_THEME } from '../../lib/constants' import { COLORS } from '../../lib/constants'
import { getThemes, saveThemes, stringifyRGBA, generateId } from '../../lib/util'
const ThemeCreate = dynamic(() => import('./ThemeCreate'), { const ThemeCreate = dynamic(() => import('./ThemeCreate'), {
loading: () => null loading: () => null
}) })
const ThemeItem = ({ children, item, isSelected, onClick }) => ( const ThemeItem = ({ children, item, isSelected, remove }) => (
<div className="theme-item"> <div className="theme-item">
{children} {children}
{item.custom && !isSelected && ( {item.custom && !isSelected && (
<div role="button" tabIndex={0} className="icon" onClick={onClick(item.id)}> <div
role="button"
tabIndex={0}
className="icon"
onClick={e => {
e.stopPropagation()
remove(item.id)
}}
>
<RemoveIcon color={COLORS.SECONDARY} /> <RemoveIcon color={COLORS.SECONDARY} />
</div> </div>
)} )}
@ -40,131 +47,58 @@ const ThemeItem = ({ children, item, isSelected, onClick }) => (
const themeIcon = <ThemeIcon /> const themeIcon = <ThemeIcon />
const getCustomName = themes =>
`Custom Theme ${themes.filter(({ name }) => name.startsWith('Custom Theme')).length + 1}`
class Themes extends React.PureComponent { class Themes extends React.PureComponent {
selectedTheme = DEFAULT_THEME
state = { state = {
themes: THEMES, highlights: {}
preset: this.props.theme,
input: 'Custom Theme',
selected: null
} }
dropdown = React.createRef() dropdown = React.createRef()
componentDidMount() {
const { update, theme, highlights } = this.props
const storedThemes = getThemes(localStorage) || []
this.setState(({ themes }) => {
const newThemes = [...storedThemes, ...themes]
this.selectedTheme = newThemes.find(({ id }) => id === theme) || DEFAULT_THEME
if (Object.keys(highlights).length === 0) {
update({ highlights: this.selectedTheme.highlights })
}
return {
themes: newThemes,
input: getCustomName(newThemes)
}
})
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { isVisible, theme, update } = this.props if (prevProps.isVisible && !this.props.isVisible) {
const { themes } = this.state this.setState({
highlights: {}
if (prevProps.isVisible && !isVisible) { })
this.setState({ input: getCustomName(themes) })
update({ highlights: themes.find(({ id }) => id === theme).highlights })
} }
} }
applyPreset = preset => { handleThemeSelected = theme => {
this.setState(({ themes }) => {
this.props.update({ highlights: themes.find(({ id }) => id === preset).highlights })
return {
preset
}
})
}
handleChange = ({ id }) => {
const { toggleVisibility, update } = this.props const { toggleVisibility, update } = this.props
const { themes } = this.state if (theme.id === 'create') {
if (id === 'create') {
toggleVisibility() toggleVisibility()
this.dropdown.current.closeMenu() this.dropdown.current.closeMenu()
} else { } else {
update({ theme: id, highlights: themes.find(theme => theme.id === id).highlights }) update(theme)
} }
} }
updateInput = ({ target: { value: input } }) => this.setState({ input }) selectHighlight = key => () =>
this.setState(({ selectedHighlight }) => ({
selectHighlight = highlight => () => selectedHighlight: selectedHighlight === key ? null : key
this.setState(({ selected }) => ({
selected: selected === highlight ? null : highlight
})) }))
updateHighlight = ({ rgb }) => updateHighlights = updates =>
this.props.update({ this.setState(({ highlights }) => ({
highlights: { highlights: {
...this.props.highlights, ...highlights,
[this.state.selected]: stringifyRGBA(rgb) ...updates
} }
}) }))
removeTheme = id => event => {
const { themes } = this.state
const { theme, update } = this.props
event.stopPropagation()
const newThemes = themes.filter(t => t.id !== id)
saveThemes(localStorage, newThemes.filter(({ custom }) => custom))
if (theme === id) {
update({ theme: DEFAULT_THEME.id, highlights: DEFAULT_THEME.highlights })
} else {
this.setState({ themes: newThemes })
}
}
createTheme = () => {
const { highlights, update } = this.props
const { themes, input: name } = this.state
const id = `theme:${generateId()}`
const newTheme = {
id,
name,
highlights,
custom: true
}
const customThemes = [newTheme, ...themes.filter(({ custom }) => custom)]
saveThemes(localStorage, customThemes)
update({ theme: id }) create = theme => {
this.props.toggleVisibility()
this.props.create(theme)
} }
itemWrapper = props => <ThemeItem {...props} onClick={this.removeTheme} /> itemWrapper = props => <ThemeItem {...props} remove={this.props.remove} />
render() { render() {
const { theme, isVisible, toggleVisibility, highlights } = this.props const { themes, theme, isVisible, toggleVisibility } = this.props
const { input, themes, selected, preset } = this.state const { input } = this.state
const highlights = { ...theme.highlights, ...this.state.highlights }
const dropdownValue = isVisible ? { name: input } : { id: theme, name: this.selectedTheme.name } const dropdownValue = isVisible ? { name: input } : { id: theme.id, name: theme.name }
const dropdownList = [ const dropdownList = [
{ {
@ -184,23 +118,16 @@ class Themes extends React.PureComponent {
selected={dropdownValue} selected={dropdownValue}
list={dropdownList} list={dropdownList}
itemWrapper={this.itemWrapper} itemWrapper={this.itemWrapper}
onChange={this.handleChange} onChange={this.handleThemeSelected}
onOpen={isVisible && toggleVisibility} onOpen={isVisible && toggleVisibility}
/> />
{isVisible && ( {isVisible && (
<ThemeCreate <ThemeCreate
key={theme}
preset={preset}
name={input}
theme={theme} theme={theme}
themes={themes} themes={themes}
highlights={highlights} highlights={highlights}
selected={selected} create={this.create}
applyPreset={this.applyPreset} updateHighlights={this.updateHighlights}
createTheme={this.createTheme}
updateName={this.updateInput}
selectHighlight={this.selectHighlight}
updateHighlight={this.updateHighlight}
/> />
)} )}
<style jsx> <style jsx>

@ -948,7 +948,7 @@ export const DEFAULT_SETTINGS = {
dropShadow: true, dropShadow: true,
dropShadowOffsetY: '20px', dropShadowOffsetY: '20px',
dropShadowBlurRadius: '68px', dropShadowBlurRadius: '68px',
theme: DEFAULT_THEME.id, theme: DEFAULT_THEME,
windowTheme: 'none', windowTheme: 'none',
language: DEFAULT_LANGUAGE, language: DEFAULT_LANGUAGE,
fontFamily: 'Hack', fontFamily: 'Hack',

@ -106,6 +106,8 @@ const getQueryStringObject = query => {
} }
const state = mapper.map(mappings, query) const state = mapper.map(mappings, query)
deserializeHighlights(state)
deserializeCode(state) deserializeCode(state)
Object.keys(state).forEach(key => { Object.keys(state).forEach(key => {
@ -135,3 +137,11 @@ function deserializeCode(state) {
// decoding errors should not crash the app // decoding errors should not crash the app
} }
} }
function deserializeHighlights(state) {
try {
if (state.highlights) state.highlights = JSON.parse(state.highlights)
} catch (e) {
// parsing errors should not crash the app
}
}

@ -23,16 +23,10 @@ class Index extends React.Component {
onEditorUpdate = debounce( onEditorUpdate = debounce(
state => { state => {
updateRouteState(this.props.router, omit(state, ['highlights'])) updateRouteState(this.props.router, omit(state, 'themes'))
saveSettings( saveSettings(
localStorage, localStorage,
omit(state, [ omit(state, ['code', 'backgroundImage', 'backgroundImageSelection', 'filename', 'themes'])
'code',
'backgroundImage',
'backgroundImageSelection',
'filename',
'highlights'
])
) )
}, },
750, 750,

Loading…
Cancel
Save