rework logic

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

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

@ -25,10 +25,12 @@ import {
DEFAULT_CODE,
DEFAULT_SETTINGS,
DEFAULT_LANGUAGE,
DEFAULT_PRESET_ID
DEFAULT_PRESET_ID,
DEFAULT_THEME,
THEMES
} from '../lib/constants'
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'
const languageIcon = <LanguageIcon />
@ -44,8 +46,8 @@ class Editor extends React.Component {
super(props)
this.state = {
...DEFAULT_SETTINGS,
themes: THEMES,
preset: DEFAULT_PRESET_ID,
highlights: {},
loading: true
}
@ -81,6 +83,25 @@ class Editor extends React.Component {
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
if (newState.language) {
newState.language = unescapeHtml(newState.language)
@ -99,7 +120,10 @@ class Editor extends React.Component {
updateState = updates => {
this.setState(updates, () => {
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
const isPNG = format !== 'svg'
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)
}
@ -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 = () =>
formatCode(this.state.code)
.then(this.updateCode)
@ -267,12 +329,10 @@ class Editor extends React.Component {
// create toast here in the future
})
applyPreset = ({ id: preset, ...settings }) => this.updateState({ preset, ...settings })
render() {
const {
theme,
highlights,
themes,
language,
backgroundColor,
backgroundImage,
@ -286,7 +346,13 @@ class Editor extends React.Component {
return (
<div className="editor">
<Toolbar>
<Themes update={this.updateState} theme={theme} highlights={highlights} />
<Themes
update={this.updateTheme}
remove={this.removeTheme}
create={this.createTheme}
theme={theme}
themes={themes}
/>
<Dropdown
icon={languageIcon}
selected={

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

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

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

@ -106,6 +106,8 @@ const getQueryStringObject = query => {
}
const state = mapper.map(mappings, query)
deserializeHighlights(state)
deserializeCode(state)
Object.keys(state).forEach(key => {
@ -135,3 +137,11 @@ function deserializeCode(state) {
// 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(
state => {
updateRouteState(this.props.router, omit(state, ['highlights']))
updateRouteState(this.props.router, omit(state, 'themes'))
saveSettings(
localStorage,
omit(state, [
'code',
'backgroundImage',
'backgroundImageSelection',
'filename',
'highlights'
])
omit(state, ['code', 'backgroundImage', 'backgroundImageSelection', 'filename', 'themes'])
)
},
750,

Loading…
Cancel
Save