Add preset feature (#595)

* Add preset feature without create

* fix lint errors

* Add presets to Editor state

* add remove, update -> apply, omit presets

* replace name with index, add undo functionality

* fix reduce function

* Tweaks:

- Make remove filter setState atomic
- Remove broken sCU in BackgroundSelect
- Touch up style of arrow functions a little
- Remove titleBar from default settings
- Don't expose SETTINGS_KEYS
- Use hasOwnProperty instead of includes()

* refactor preset state into Settings

* move format code into editor and make it work again

* omit custom in applyPreset

* move presets array state into Settings

* keep custom sCU in BackgroundSelect

* pull out inline objects

* revert pages/index

* increase Presets font-size, remove margin-top

* Add ability to create presets

* also enable passing exportSize as prop

* move selectedPreset back into Settings (my bad Sean)

* replace splice with filter, getSavedX -> getX

* Revert "move selectedPreset back into Settings (my bad Sean)"

This reverts commit ae5da4700ea36ad7c31e697e83a2724be4b448f4.

* make sure background updates remove selected preset

* selectedPreset -> preset

* use onChange instead of selectPreset

* use preset id's instead of indexes

* bug fixes

* use disabled instead of pointer-events

* make .settings-presets-applied flex 💪

* make .settings-presets-arrow flex 💪

* move getPresets outside of `setState`

* move inline styles to style tag

* refactor using omitBy and isFunction

* remove lodash.isfunction

* fix applyPreset to disclude preset field

* move omit to getSettingsFromProps

* replace lodash.omit with omitBy solution

* .includes -> .indexOf

* add default preset and presetApplied state

* fix lint error

* remove presetApplied

* add more default presets

* fix default preset functionality

* tweaks

* preserve preset list scrollLeft b/w updates with a hack

* Use ref for preset content

* remove forwardRef
main
Sean 6 years ago committed by Michael Fix
parent bf761d7d5b
commit f6f0adee6b

@ -22,10 +22,11 @@ class BackgroundSelect extends React.Component {
}
shouldComponentUpdate(prevProps, prevState) {
return (
prevState.isVisible !== this.state.isVisible ||
(prevState.isVisible && shallowCompare(this, prevProps, prevState))
)
return [
prevState.isVisible !== this.state.isVisible,
prevProps.color !== this.props.color,
prevState.isVisible && shallowCompare(this, prevProps, prevState)
].some(Boolean)
}
toggle() {

@ -7,7 +7,6 @@ import domtoimage from 'dom-to-image'
import ReadFileDropContainer, { DATA_URL, TEXT } from 'dropperx'
import Spinner from 'react-spinner'
import shallowCompare from 'react-addons-shallow-compare'
import omit from 'lodash.omit'
// Ours
import Button from './Button'
@ -31,13 +30,19 @@ import {
EXPORT_SIZES_HASH,
DEFAULT_CODE,
DEFAULT_SETTINGS,
DEFAULT_LANGUAGE
DEFAULT_LANGUAGE,
DEFAULT_PRESET_ID
} from '../lib/constants'
import { serializeState, getQueryStringState } from '../lib/routing'
import { getState, escapeHtml, unescapeHtml, formatCode } from '../lib/util'
import { getSettings, escapeHtml, unescapeHtml, formatCode, omit } from '../lib/util'
import LanguageIcon from './svg/Language'
import ThemeIcon from './svg/Theme'
const themeIcon = <ThemeIcon />
const languageIcon = <LanguageIcon />
const tweetButtonStyle = { marginRight: '8px' }
class Editor extends React.Component {
constructor(props) {
super(props)
@ -46,15 +51,13 @@ class Editor extends React.Component {
loading: true,
uploading: false,
code: props.content,
online: true
online: true,
preset: DEFAULT_PRESET_ID
}
this.export = this.export.bind(this)
this.upload = this.upload.bind(this)
this.updateSetting = this.updateSetting.bind(this)
this.updateCode = this.updateSetting.bind(this, 'code')
this.updateAspectRatio = this.updateSetting.bind(this, 'aspectRatio')
this.updateTitleBar = this.updateSetting.bind(this, 'titleBar')
this.updateTheme = this.updateTheme.bind(this)
this.updateLanguage = this.updateLanguage.bind(this)
this.updateBackground = this.updateBackground.bind(this)
@ -62,8 +65,6 @@ class Editor extends React.Component {
this.getCarbonImage = this.getCarbonImage.bind(this)
this.onDrop = this.onDrop.bind(this)
this.setOffline = () => this.setState({ online: false })
this.setOnline = () => this.setState({ online: true })
this.innerRef = node => (this.carbonNode = node)
}
@ -89,7 +90,7 @@ class Editor extends React.Component {
const newState = {
// Load from localStorage
...getState(localStorage),
...getSettings(localStorage),
// and then URL params
...initialState,
loading: false,
@ -119,7 +120,20 @@ class Editor extends React.Component {
}
}
async getCarbonImage({ format, type } = { format: 'png' }) {
updateCode = code => this.setState({ code })
updateAspectRatio = aspectRatio => this.setState({ aspectRatio })
updateTitleBar = titleBar => this.setState({ titleBar })
setOffline = () => this.setState({ online: false })
setOnline = () => this.setState({ online: true })
async getCarbonImage(
{
format,
type,
squared = this.state.squaredImage,
exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value
} = { format: 'png' }
) {
// if safari, get image from api
const isPNG = format !== 'svg'
if (
@ -134,8 +148,6 @@ class Editor extends React.Component {
const node = this.carbonNode
const exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value
const map = new Map()
const undoMap = value => {
map.forEach((value, node) => (node.innerText = value))
@ -152,15 +164,13 @@ class Editor extends React.Component {
}
const width = node.offsetWidth * exportSize
const height = this.state.squaredImage
? node.offsetWidth * exportSize
: node.offsetHeight * exportSize
const height = squared ? node.offsetWidth * exportSize : node.offsetHeight * exportSize
const config = {
style: {
transform: `scale(${exportSize})`,
'transform-origin': 'center',
background: this.state.squaredImage ? this.state.backgroundColor : 'none'
background: squared ? this.state.backgroundColor : 'none'
},
filter: n => {
if (n.className) {
@ -192,8 +202,6 @@ class Editor extends React.Component {
// Twitter needs regular dataurls
return await domtoimage.toPng(node, config)
} catch (error) {
throw error
} finally {
undoMap()
}
@ -201,6 +209,9 @@ class Editor extends React.Component {
updateSetting(key, value) {
this.setState({ [key]: value })
if (Object.prototype.hasOwnProperty.call(DEFAULT_SETTINGS, key)) {
this.setState({ preset: null })
}
}
export(format = 'png') {
@ -220,7 +231,7 @@ class Editor extends React.Component {
}
resetDefaultSettings() {
this.setState(DEFAULT_SETTINGS)
this.setState({ ...DEFAULT_SETTINGS, preset: DEFAULT_PRESET_ID })
this.props.onReset()
}
@ -238,7 +249,8 @@ class Editor extends React.Component {
this.setState({
backgroundImage: file.content,
backgroundImageSelection: null,
backgroundMode: 'image'
backgroundMode: 'image',
preset: null
})
} else {
this.setState({ code: file.content, language: 'auto' })
@ -257,10 +269,11 @@ class Editor extends React.Component {
if (photographer) {
this.setState(({ code = DEFAULT_CODE }) => ({
...changes,
code: code + `\n\n// Photo by ${photographer.name} on Unsplash`
code: code + `\n\n// Photo by ${photographer.name} on Unsplash`,
preset: null
}))
} else {
this.setState(changes)
this.setState({ ...changes, preset: null })
}
}
@ -271,6 +284,8 @@ class Editor extends React.Component {
// create toast here in the future
})
applyPreset = ({ id: preset, ...settings }) => this.setState({ preset, ...settings })
render() {
const {
loading,
@ -302,20 +317,20 @@ class Editor extends React.Component {
)
}
const config = omit(this.state, ['code', 'aspectRatio'])
const config = omit(this.state, ['code', 'aspectRatio', 'titleBar'])
return (
<React.Fragment>
<div className="editor">
<Toolbar>
<Dropdown
icon={<ThemeIcon />}
icon={themeIcon}
selected={THEMES_HASH[theme] || DEFAULT_THEME}
list={THEMES}
onChange={this.updateTheme}
/>
<Dropdown
icon={<LanguageIcon />}
icon={languageIcon}
selected={
LANGUAGE_NAME_HASH[language] ||
LANGUAGE_MIME_HASH[language] ||
@ -337,6 +352,8 @@ class Editor extends React.Component {
onChange={this.updateSetting}
resetDefaultSettings={this.resetDefaultSettings}
format={this.format}
applyPreset={this.applyPreset}
getCarbonImage={this.getCarbonImage}
/>
<div className="buttons">
{this.props.api.tweet &&
@ -346,7 +363,7 @@ class Editor extends React.Component {
onClick={this.upload}
title={uploading ? 'Loading...' : 'Tweet'}
color="#57b5f9"
style={{ marginRight: '8px' }}
style={tweetButtonStyle}
/>
)}
<ExportMenu

@ -1,16 +1,18 @@
import React from 'react'
import enhanceWithClickOutside from 'react-click-outside'
import omitBy from 'lodash.omitby'
import ThemeSelect from './ThemeSelect'
import FontSelect from './FontSelect'
import Slider from './Slider'
import Toggle from './Toggle'
import WindowPointer from './WindowPointer'
import { COLORS } from '../lib/constants'
import { COLORS, DEFAULT_PRESETS } from '../lib/constants'
import { getPresets, savePresets } from '../lib/util'
import { toggle } from '../lib/util'
import SettingsIcon from './svg/Settings'
import * as Arrows from './svg/Arrows'
//import Remove from './svg/Remove'
import Remove from './svg/Remove'
const WindowSettings = React.memo(
({
@ -202,31 +204,122 @@ const MenuButton = React.memo(({ name, select, selected }) => {
)
})
/*const Presets = React.memo(({ show, presets, toggle, create, remove }) => {
const Preset = React.memo(({ remove, apply, selected, preset }) => (
<div className="preset-container">
<button
className="preset-tile"
onClick={() => apply(preset)}
disabled={preset.id === selected}
/>
{preset.custom ? (
<button className="preset-remove" onClick={() => remove(preset.id)}>
<Remove />
</button>
) : null}
<style jsx>
{`
button {
outline: none;
border: none;
background: transparent;
cursor: pointer;
padding: 0;
}
.preset-container {
display: flex;
position: relative;
flex: 0 0 96px;
height: 96px;
margin-right: 8px;
}
.preset-tile {
flex: 1;
border-radius: 3px;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
cursor: ${preset.id === selected ? 'initial' : 'pointer'};
background-image: ${`url('${preset.icon}')`};
background-color: ${preset.icon ? 'initial' : preset.backgroundColor};
box-shadow: ${preset.id === selected
? `inset 0px 0px 0px 2px ${COLORS.SECONDARY}`
: 'initial'};
}
.preset-remove {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
padding: 0;
top: 6px;
right: 6px;
width: 11px;
height: 11px;
border-radius: 999px;
background-color: ${COLORS.SECONDARY};
}
`}
</style>
</div>
))
const Presets = React.memo(
({ show, create, toggle, undo, presets, selected, remove, apply, applied, contentRef }) => {
const customPresetsLength = presets.length - DEFAULT_PRESETS.length
const disabledCreate = selected != null
return (
<div className="settings-presets">
<div className="settings-presets-header">
<span>Presets</span>
{show && <button className="settings-presets-create" onClick={create}>create +</button>}
{show && (
<button className="settings-presets-create" onClick={create} disabled={disabledCreate}>
create +
</button>
)}
<button className="settings-presets-arrow" onClick={toggle}>
{show ? <Arrows.Up /> : <Arrows.Down />}
</button>
</div>
{show && (
<div className="settings-presets-content">
{presets.map(({ id, backgroundColor, userCreated }) => (
<div key={id} className="settings-presets-preset" style={{
backgroundColor
}}>
{
userCreated ? <button className="settings-presets-remove" onClick={() => remove(id)}><Remove /></button> : null
}
</div>
{show ? (
<div className="settings-presets-content" ref={contentRef}>
{presets.filter(p => p.custom).map(preset => (
<Preset
key={preset.id}
remove={remove}
apply={apply}
preset={preset}
selected={selected}
/>
))}
{customPresetsLength > 0 ? <div className="settings-presets-divider" /> : null}
{presets.filter(p => !p.custom).map(preset => (
<Preset key={preset.id} apply={apply} preset={preset} selected={selected} />
))}
</div>
)}
) : null}
{show && applied ? (
<div className="settings-presets-applied">
<span>Preset applied!</span>
<button onClick={undo}>
undo <span>&#x21A9;</span>
</button>
</div>
) : null}
<style jsx>
{`
button {
outline: none;
border: none;
background: transparent;
cursor: pointer;
padding: 0;
}
.settings-presets {
border-bottom: 1px solid ${COLORS.SECONDARY};
}
@ -240,7 +333,13 @@ const MenuButton = React.memo(({ name, select, selected }) => {
align-items: center;
}
.settings-presets-arrow, .settings-presets-create, .settings-presets-remove {
.settings-presets-header > span {
font-size: 14px;
}
.settings-presets-arrow,
.settings-presets-create,
.settings-presets-remove {
cursor: pointer;
background: transparent;
outline: none;
@ -251,9 +350,15 @@ const MenuButton = React.memo(({ name, select, selected }) => {
.settings-presets-create {
color: ${COLORS.GRAY};
padding: 0 8px;
cursor: ${disabledCreate ? 'not-allowed' : 'pointer'};
}
.settings-presets-create:enabled:hover {
color: ${COLORS.SECONDARY};
}
.settings-presets-arrow {
display: flex;
position: absolute;
right: 16px;
}
@ -261,41 +366,61 @@ const MenuButton = React.memo(({ name, select, selected }) => {
.settings-presets-content {
display: flex;
overflow-x: scroll;
margin: 12px 8px;
margin: 0 8px 12px 8px;
align-items: center;
/* https://iamsteve.me/blog/entry/using-flexbox-for-horizontal-scrolling-navigation */
flex-wrap: nowrap;
-webkit-overflow-scrolling: touch;
}
.settings-presets-preset {
.settings-presets-divider {
height: 72px;
padding: 1px;
border-radius: 3px;
height: 96px;
margin-right: 8px;
flex: 0 0 96px;
position: relative;
background-color: ${COLORS.DARK_GRAY};
}
.settings-presets-remove {
.settings-presets-applied {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
padding: 0;
top: 6px;
right: 6px;
width: 11px;
height: 11px;
border-radius: 999px;
justify-content: space-between;
background-color: ${COLORS.SECONDARY};
width: 100%;
color: ${COLORS.BLACK};
padding: 4px 8px;
}
.settings-presets-applied button {
float: right;
}
.settings-presets-applied button span {
float: right;
margin: 1px 0 0 2px;
}
`}
</style>
</div>
)
})*/
}
)
class Settings extends React.PureComponent {
state = {
presets: DEFAULT_PRESETS,
isVisible: false,
selectedMenu: 'Window',
showPresets: false
showPresets: false,
previousSettings: null
}
presetContentRef = React.createRef()
componentDidMount() {
const storedPresets = getPresets(localStorage) || []
this.setState(({ presets }) => ({
presets: [...storedPresets, ...presets]
}))
}
toggleVisible = () => this.setState(toggle('isVisible'))
@ -311,7 +436,7 @@ class Settings extends React.PureComponent {
case 'Window':
return (
<WindowSettings
onChange={this.props.onChange}
onChange={this.handleChange}
windowTheme={this.props.windowTheme}
paddingHorizontal={this.props.paddingHorizontal}
paddingVertical={this.props.paddingVertical}
@ -327,21 +452,90 @@ class Settings extends React.PureComponent {
case 'Type':
return (
<TypeSettings
onChange={this.props.onChange}
onChange={this.handleChange}
font={this.props.fontFamily}
size={this.props.fontSize}
lineHeight={this.props.lineHeight}
/>
)
case 'Misc':
return <MiscSettings format={this.props.format} reset={this.props.resetDefaultSettings} />
return <MiscSettings format={this.props.format} reset={this.handleReset} />
default:
return null
}
}
handleChange = (key, value) => {
this.props.onChange(key, value)
this.setState({ previousSettings: null })
}
handleReset = () => {
this.props.resetDefaultSettings()
this.setState({ previousSettings: null })
}
getSettingsFromProps = () =>
omitBy(this.props, (v, k) => typeof v === 'function' || k === 'preset')
applyPreset = preset => {
const previousSettings = this.getSettingsFromProps()
this.props.applyPreset(preset)
// TODO: this is a hack to prevent the scrollLeft position from changing when preset is applied
const { scrollLeft: previousScrollLeft } = this.presetContentRef.current
this.setState({ previousSettings }, () => {
this.presetContentRef.current.scrollLeft = previousScrollLeft
})
}
undoPreset = () => {
this.props.applyPreset({ ...this.state.previousSettings, id: null })
this.setState({ previousSettings: null })
}
removePreset = id => {
if (this.props.preset === id) {
this.props.onChange('preset', null)
this.setState({ previousSettings: null })
}
this.setState(
({ presets }) => ({ presets: presets.filter(p => p.id !== id) }),
this.savePresets
)
}
createPreset = async () => {
const newPreset = this.getSettingsFromProps()
newPreset.id = `preset:${Math.random()
.toString(36)
.slice(2)}`
newPreset.custom = true
newPreset.icon = await this.props.getCarbonImage({
format: 'png',
squared: true,
exportSize: 1
})
this.props.onChange('preset', newPreset.id)
this.setState(
({ presets }) => ({
previousSettings: null,
presets: [newPreset, ...presets]
}),
this.savePresets
)
}
savePresets = () => savePresets(localStorage, this.state.presets.filter(p => p.custom))
render() {
const { isVisible, selectedMenu } = this.state
const { isVisible, selectedMenu, showPresets, presets, previousSettings } = this.state
const { preset } = this.props
return (
<div className="settings-container">
@ -353,6 +547,18 @@ class Settings extends React.PureComponent {
</div>
<div className="settings-settings">
<WindowPointer fromLeft="15px" />
<Presets
show={showPresets}
presets={presets}
selected={preset}
toggle={this.togglePresets}
apply={this.applyPreset}
undo={this.undoPreset}
remove={this.removePreset}
create={this.createPreset}
applied={!!previousSettings}
contentRef={this.presetContentRef}
/>
<div className="settings-bottom">
<div className="settings-menu">
<MenuButton name="Window" select={this.selectMenu} selected={selectedMenu} />
@ -408,7 +614,7 @@ class Settings extends React.PureComponent {
top: 52px;
left: 0;
border: 2px solid ${COLORS.SECONDARY};
width: 320px;
width: 324px;
border-radius: 3px;
background: ${COLORS.BLACK};
}

@ -40,7 +40,7 @@ export default ({ titleBar, theme, handleTitleBarChange, copyable, code }) => (
<div className="window-title-container">
<input
aria-label="Image Title"
value={titleBar}
value={titleBar || ''}
type="text"
spellCheck="false"
onChange={e => handleTitleBarChange(e.target.value)}

@ -457,9 +457,9 @@ export const LANGUAGES = [
]
export const EXPORT_SIZES = [
{ id: '1x', name: '1x', value: '1' },
{ id: '2x', name: '2x', value: '2' },
{ id: '4x', name: '4x', value: '4' }
{ id: '1x', name: '1x', value: 1 },
{ id: '2x', name: '2x', value: 2 },
{ id: '4x', name: '4x', value: 4 }
]
export const EXPORT_SIZES_HASH = toHash(EXPORT_SIZES)
@ -512,8 +512,8 @@ if (typeof window !== 'undefined' && typeof window.navigator !== 'undefined') {
}
export const DEFAULT_SETTINGS = {
paddingVertical: '48px',
paddingHorizontal: '32px',
paddingVertical: '56px',
paddingHorizontal: '56px',
marginVertical: '45px',
marginHorizontal: '45px',
backgroundImage: null,
@ -533,7 +533,103 @@ export const DEFAULT_SETTINGS = {
widthAdjustment: true,
lineNumbers: false,
exportSize: '2x',
titleBar: '',
watermark: false,
squaredImage: false
}
export const DEFAULT_PRESET_ID = 'preset:4'
export const DEFAULT_PRESETS = [
{
...DEFAULT_SETTINGS,
icon: '/static/presets/4.png',
id: DEFAULT_PRESET_ID
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(74,144,226,1)',
dropShadow: false,
theme: 'material',
fontFamily: 'Fira Code',
lineHeight: '152%',
icon: '/static/presets/7.png',
id: 'preset:7'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(248,231,28,1)',
dropShadow: false,
theme: 'blackboard',
fontFamily: 'Fira Code',
lineHeight: '152%',
icon: '/static/presets/8.png',
id: 'preset:8'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(182,162,145,1)',
dropShadow: false,
theme: 'zenburn',
windowTheme: 'bw',
lineHeight: '152%',
icon: '/static/presets/9.png',
id: 'preset:9'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(121,72,185,1)',
dropShadow: false,
theme: 'verminal',
windowTheme: 'bw',
fontFamily: 'Fira Code',
fontSize: '14px',
lineHeight: '143%',
icon: '/static/presets/0.png',
id: 'preset:0'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(239,40,44,1)',
theme: 'one-light',
lineHeight: '143%',
icon: '/static/presets/1.png',
id: 'preset:1'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(31,129,109,1)',
dropShadow: false,
theme: 'night-owl',
lineHeight: '143%',
windowControls: false,
icon: '/static/presets/2.png',
id: 'preset:2'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(249,237,212,1)',
theme: 'twilight',
fontFamily: 'IBM Plex Mono',
lineHeight: '143%',
icon: '/static/presets/3.png',
id: 'preset:3'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(222,171,99,1)',
theme: 'duotone-dark',
icon: '/static/presets/5.png',
id: 'preset:5'
},
{
...DEFAULT_SETTINGS,
backgroundColor: 'rgba(187,187,187,1)',
dropShadowOffsetY: '3px',
dropShadowBlurRadius: '13px',
theme: 'solarized light',
windowTheme: 'sharp',
icon: '/static/presets/6.png',
id: 'preset:6'
}
]

@ -1,8 +1,11 @@
import morph from 'morphmorph'
import omitBy from 'lodash.omitby'
const KEY = 'CARBON_STATE'
const SETTINGS_KEY = 'CARBON_SETTINGS'
const PRESETS_KEY = 'CARBON_PRESETS'
const assign = morph.assign(KEY)
const assignSettings = morph.assign(SETTINGS_KEY)
const assignPresets = morph.assign(PRESETS_KEY)
const parse = v => {
try {
@ -36,12 +39,22 @@ export const unescapeHtml = s => {
export const parseRGBA = obj => `rgba(${obj.r},${obj.g},${obj.b},${obj.a})`
export const getState = morph.compose(
export const getSettings = morph.compose(
parse,
escapeHtml,
morph.get(KEY)
morph.get(SETTINGS_KEY)
)
export const saveState = (window, v) => assign(window, JSON.stringify(v))
export const getPresets = morph.compose(
parse,
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 clearSettings = () => localStorage.removeItem(SETTINGS_KEY)
export const capitalizeFirstLetter = s => s.charAt(0).toUpperCase() + s.slice(1)
@ -65,3 +78,5 @@ export const formatCode = async code => {
singleQuote: true
})
}
export const omit = (object, keys) => omitBy(object, (_, k) => keys.indexOf(k) > -1)

@ -28,7 +28,7 @@
"graphql": "^14.0.2",
"highlight.js": "^9.13.1",
"lodash.debounce": "^4.0.8",
"lodash.omit": "^4.5.0",
"lodash.omitby": "^4.6.0",
"match-sorter": "^2.3.0",
"morphmorph": "^0.1.0",
"ms": "^2.0.0",

@ -1,19 +1,18 @@
// Theirs
import React from 'react'
import { withRouter } from 'next/router'
import omit from 'lodash.omit'
// Ours
import Editor from '../components/Editor'
import Page from '../components/Page'
import api from '../lib/api'
import { updateQueryString } from '../lib/routing'
import { saveState } from '../lib/util'
import { saveSettings, clearSettings, omit } from '../lib/util'
class Index extends React.Component {
onEditorUpdate = state => {
updateQueryString(this.props.router, state)
saveState(
saveSettings(
localStorage,
omit(state, ['code', 'backgroundImage', 'backgroundImageSelection', 'filename'])
)
@ -34,7 +33,7 @@ class Index extends React.Component {
}
function onReset() {
localStorage.clear()
clearSettings()
if (window.navigator && navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations().then(registrations => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

@ -4310,10 +4310,10 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.omit@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=
lodash.omitby@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791"
integrity sha1-XBX/R1StVVAWtTwEExHo8HkgR5E=
lodash.once@^4.1.1:
version "4.1.1"

Loading…
Cancel
Save