Performance improvements (#287)

* Perf improvements. Limit rerender on keystrokes

* Spread settings props, make dropdowns and carbon pure

* Remove last TODO

* Pass state into value of titleBar input

* Clean up
main
Michael Fix 7 years ago committed by Jake Dexheimer
parent 7f40e9b469
commit a6324586eb

@ -7,9 +7,9 @@ import { COLORS, DEFAULT_BG_COLOR } from '../lib/constants'
import { validateColor } from '../lib/colors'
import { parseRGBA, capitalizeFirstLetter } from '../lib/util'
class BackgroundSelect extends React.Component {
constructor() {
super()
class BackgroundSelect extends React.PureComponent {
constructor(props) {
super(props)
this.state = { isVisible: false, selectedTab: 'color', mounted: false }
this.toggle = this.toggle.bind(this)
this.selectTab = this.selectTab.bind(this)
@ -25,7 +25,7 @@ class BackgroundSelect extends React.Component {
}
selectTab(name) {
if (this.props.config.backgroundMode !== name) {
if (this.props.mode !== name) {
this.props.onChange({ backgroundMode: name })
}
}
@ -39,7 +39,7 @@ class BackgroundSelect extends React.Component {
}
render() {
let background = this.props.config.backgroundColor || config.backgroundColor
let background = this.props.color
background =
typeof background === 'string'
? background
@ -72,7 +72,7 @@ class BackgroundSelect extends React.Component {
{['color', 'image'].map((tab, i) => (
<div
key={i}
className={`picker-tab ${this.props.config.backgroundMode === tab ? 'active' : ''}`}
className={`picker-tab ${this.props.mode === tab ? 'active' : ''}`}
onClick={this.selectTab.bind(null, tab)}
>
{capitalizeFirstLetter(tab)}
@ -80,19 +80,16 @@ class BackgroundSelect extends React.Component {
))}
</div>
<div className="picker-tabs-contents">
<div style={this.props.config.backgroundMode === 'color' ? {} : { display: 'none' }}>
<div style={this.props.mode === 'color' ? {} : { display: 'none' }}>
{this.state.mounted && (
<SketchPicker
color={this.props.config.backgroundColor}
onChangeComplete={this.handlePickColor}
/>
<SketchPicker color={this.props.color} onChangeComplete={this.handlePickColor} />
)}
</div>
<div style={this.props.config.backgroundMode === 'image' ? {} : { display: 'none' }}>
<div style={this.props.mode === 'image' ? {} : { display: 'none' }}>
<ImagePicker
onChange={this.props.onChange}
imageDataURL={this.props.config.backgroundImage}
aspectRatio={this.props.config.aspectRatio}
imageDataURL={this.props.image}
aspectRatio={this.props.aspectRatio}
/>
</div>
</div>
@ -135,8 +132,8 @@ class BackgroundSelect extends React.Component {
right: 0px;
bottom: 0px;
left: 0px;
${this.props.config.backgroundMode === 'image'
? `background: url(${this.props.config.backgroundImage});
${this.props.mode === 'image'
? `background: url(${this.props.image});
background-size: cover;
background-repeat: no-repeat;`
: `background: ${background};`};

@ -1,6 +1,6 @@
import { EOL } from 'os'
import * as hljs from 'highlight.js'
import React from 'react'
import React, { PureComponent } from 'react'
import Spinner from 'react-spinner'
import ResizeObserver from 'resize-observer-polyfill'
import toHash from 'tohash'
@ -19,7 +19,7 @@ import {
DEFAULT_SETTINGS
} from '../lib/constants'
class Carbon extends React.Component {
class Carbon extends PureComponent {
constructor(props) {
super(props)
@ -113,6 +113,7 @@ class Carbon extends React.Component {
<div id="container">
{config.windowControls ? (
<WindowControls
titleBar={this.props.titleBar}
theme={config.windowTheme}
handleTitleBarChange={this.handleTitleBarChange}
/>

@ -1,11 +1,11 @@
import React, { Component } from 'react'
import React, { PureComponent } from 'react'
import Downshift from 'downshift'
import matchSorter from 'match-sorter'
import ArrowDown from './svg/Arrowdown'
import CheckMark from './svg/Checkmark'
import { COLORS } from '../lib/constants'
class Dropdown extends Component {
class Dropdown extends PureComponent {
state = {
inputValue: this.props.selected.name,
itemsToShow: this.props.list

@ -5,7 +5,7 @@ import { EXPORT_SIZES } from '../lib/constants'
export default class extends React.Component {
constructor(props) {
super()
super(props)
this.state = { isVisible: false }
this.select = this.select.bind(this)
this.toggle = this.toggle.bind(this)

@ -4,7 +4,7 @@ import { COLORS, FONTS } from '../lib/constants'
export default class extends React.Component {
constructor(props) {
super()
super(props)
this.state = { isVisible: false }
this.select = this.select.bind(this)
this.toggle = this.toggle.bind(this)

@ -2,7 +2,7 @@ import React from 'react'
import Link from 'next/link'
import { COLORS } from '../lib/constants'
const Footer = props => (
const Footer = () => (
<footer role="contentinfo" className="mt3">
<nav role="navigation" className="mt3">
<Link href="/about">

@ -36,8 +36,8 @@ const getCroppedImg = (imageDataURL, pixelCrop) => {
const INITIAL_STATE = { crop: null, imageAspectRatio: null, pixelCrop: null }
export default class extends React.Component {
constructor() {
super()
constructor(props) {
super(props)
this.state = INITIAL_STATE
this.selectImage = this.selectImage.bind(this)
this.removeImage = this.removeImage.bind(this)

@ -13,8 +13,8 @@ const largerImage = url => url.replace(/w=\d+/, 'w=1920').replace(/&h=\d+/, '')
const smallerImage = url => url.replace(/w=\d+/, 'w=240')
export default class extends React.Component {
constructor() {
super()
constructor(props) {
super(props)
this.state = { cacheIndex: 0, loading: false }
this.selectImage = this.selectImage.bind(this)
this.updateCache = this.updateCache.bind(this)

@ -12,7 +12,7 @@ import { COLORS } from '../lib/constants'
class Settings extends React.Component {
constructor(props) {
super()
super(props)
this.state = {
isVisible: false
}
@ -39,74 +39,74 @@ class Settings extends React.Component {
<div className="settings-settings">
<WindowPointer fromLeft="15px" />
<ThemeSelect
selected={this.props.enabled.windowTheme || 'none'}
selected={this.props.windowTheme || 'none'}
onChange={this.props.onChange.bind(null, 'windowTheme')}
/>
<FontSelect
selected={this.props.enabled.fontFamily || 'Hack'}
selected={this.props.fontFamily || 'Hack'}
onChange={this.props.onChange.bind(null, 'fontFamily')}
/>
<Slider
label="Font size"
value={this.props.enabled.fontSize || 13}
value={this.props.fontSize || 13}
minValue={10}
maxValue={18}
onChange={this.props.onChange.bind(null, 'fontSize')}
/>
<Toggle
label="Window controls"
enabled={this.props.enabled.windowControls}
enabled={this.props.windowControls}
onChange={this.props.onChange.bind(null, 'windowControls')}
/>
<Toggle
label="Line numbers"
enabled={this.props.enabled.lineNumbers}
enabled={this.props.lineNumbers}
onChange={this.props.onChange.bind(null, 'lineNumbers')}
/>
<Toggle
label="Auto-adjust width"
enabled={this.props.enabled.widthAdjustment}
enabled={this.props.widthAdjustment}
onChange={this.props.onChange.bind(null, 'widthAdjustment')}
/>
<Collapse label="Advanced">
<Slider
label="Padding (vertical)"
value={this.props.enabled.paddingVertical || 16}
value={this.props.paddingVertical || 16}
maxValue={200}
onChange={this.props.onChange.bind(null, 'paddingVertical')}
/>
<Slider
label="Padding (horizontal)"
value={this.props.enabled.paddingHorizontal || 32}
value={this.props.paddingHorizontal || 32}
onChange={this.props.onChange.bind(null, 'paddingHorizontal')}
/>
<Toggle
label="Drop shadow"
enabled={this.props.enabled.dropShadow}
enabled={this.props.dropShadow}
onChange={this.props.onChange.bind(null, 'dropShadow')}
/>
<Slider
label="Drop shadow (offset-y)"
value={this.props.enabled.dropShadowOffsetY || 20}
value={this.props.dropShadowOffsetY || 20}
onChange={this.props.onChange.bind(null, 'dropShadowOffsetY')}
/>
<Slider
label="Drop shadow (blur-radius)"
value={this.props.enabled.dropShadowBlurRadius || 68}
value={this.props.dropShadowBlurRadius || 68}
onChange={this.props.onChange.bind(null, 'dropShadowBlurRadius')}
/>
<Toggle
label="Squared image"
enabled={this.props.enabled.squaredImage}
enabled={this.props.squaredImage}
onChange={this.props.onChange.bind(null, 'squaredImage')}
/>
<Toggle
label="Watermark"
enabled={this.props.enabled.watermark}
enabled={this.props.watermark}
onChange={this.props.onChange.bind(null, 'watermark')}
/>
<ExportSizeSelect
selected={this.props.enabled.exportSize || '2x'}
selected={this.props.exportSize || '2x'}
onChange={this.props.onChange.bind(null, 'exportSize')}
/>
<Toggle

@ -2,7 +2,7 @@ import React from 'react'
export default class extends React.Component {
constructor(props) {
super()
super(props)
this.handleChange = this.handleChange.bind(this)
}

@ -7,7 +7,7 @@ export const WINDOW_THEMES = Object.keys(WINDOW_THEMES_MAP)
export default class extends React.Component {
constructor(props) {
super()
super(props)
this.select = this.select.bind(this)
}

@ -3,7 +3,7 @@ import Checkmark from './svg/Checkmark'
export default class extends React.Component {
constructor(props) {
super()
super(props)
this.toggle = this.toggle.bind(this)
}

@ -1,11 +1,16 @@
import React from 'react'
import { Controls, ControlsBW } from './svg/Controls'
export default ({ theme, handleTitleBarChange }) => (
export default ({ titleBar, theme, handleTitleBarChange }) => (
<div className="window-controls">
{theme === 'bw' ? <ControlsBW /> : <Controls />}
<div className="window-title-container">
<input type="text" spellCheck="false" onChange={e => handleTitleBarChange(e.target.value)} />
<input
value={titleBar}
type="text"
spellCheck="false"
onChange={e => handleTitleBarChange(e.target.value)}
/>
</div>
<style jsx>
{`

@ -86,12 +86,15 @@ export const getQueryStringState = query => {
}
export const updateQueryString = state => {
let mappedState = mapper.map(reverseMappings, state)
serializeCode(mappedState)
// If react_perf is set as a queryParm, don't update
if (history.location.search.indexOf('react_perf') < 0) {
let mappedState = mapper.map(reverseMappings, state)
serializeCode(mappedState)
history.replace({
search: encodeURI(keysToQuery(mappedState))
})
history.replace({
search: encodeURI(keysToQuery(mappedState))
})
}
}
// private

@ -36,12 +36,11 @@ import {
import { getQueryStringState, updateQueryString, serializeState } from '../lib/routing'
import { getState, saveState } from '../lib/util'
const removeQueryString = str => {
const qI = str.indexOf('?')
return (qI >= 0 ? str.substr(0, qI) : str)
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2F;')
const saveButtonOptions = {
button: true,
color: '#c198fb',
selected: { id: 'SAVE_IMAGE', name: 'Save Image' },
list: ['png', 'svg'].map(id => ({ id, name: id.toUpperCase() }))
}
class Editor extends React.Component {
@ -75,11 +74,16 @@ class Editor extends React.Component {
this.save = this.save.bind(this)
this.upload = this.upload.bind(this)
this.updateCode = this.updateCode.bind(this)
this.updateTitleBar = this.updateTitleBar.bind(this)
this.updateAspectRatio = this.updateAspectRatio.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)
this.resetDefaultSettings = this.resetDefaultSettings.bind(this)
this.getCarbonImage = this.getCarbonImage.bind(this)
this.onDrop = this.onDrop.bind(this)
}
componentDidMount() {
@ -133,19 +137,11 @@ class Editor extends React.Component {
: domtoimage.toPng(node, config)
}
updateCode(code) {
this.setState({ code })
}
updateAspectRatio(aspectRatio) {
this.setState({ aspectRatio })
}
updateTitleBar(titleBar) {
this.setState({ titleBar })
updateSetting(key, value) {
this.setState({ [key]: value })
}
save({ format } = { format: 'png' }) {
save({ id: format = 'png' }) {
this.getCarbonImage({ format }).then(dataUrl => {
if (format === 'svg') {
dataUrl = dataUrl.split('&nbsp;').join('&#160;')
@ -176,19 +172,39 @@ class Editor extends React.Component {
})
}
isImage(file) {
return file.type.split('/')[0] === 'image'
onDrop([file]) {
if (isImage(file)) {
this.setState({
backgroundImage: file.content,
backgroundImageSelection: null,
backgroundMode: 'image'
})
} else {
this.setState({ code: file.content, language: 'auto' })
}
}
updateTheme(theme) {
this.updateSetting('theme', theme.id)
}
updateLanguage(language) {
this.updateSetting('language', language.mime || language.mode)
}
updateBackground(changes, cb) {
this.setState(changes, cb)
}
render() {
return (
<Page enableHeroText>
<Page enableHeroText={true}>
<div id="editor">
<Toolbar>
<Dropdown
selected={THEMES_HASH[this.state.theme] || DEFAULT_THEME}
list={THEMES}
onChange={theme => this.setState({ theme: theme.id })}
onChange={this.updateTheme}
/>
<Dropdown
selected={
@ -197,12 +213,18 @@ class Editor extends React.Component {
LANGUAGE_MODE_HASH[this.state.language]
}
list={LANGUAGES}
onChange={language => this.setState({ language: language.mime || language.mode })}
onChange={this.updateLanguage}
/>
<BackgroundSelect
onChange={this.updateBackground}
mode={this.state.backgroundMode}
color={this.state.backgroundColor}
image={this.state.backgroundImage}
aspectRatio={this.state.aspectRatio}
/>
<BackgroundSelect onChange={changes => this.setState(changes)} config={this.state} />
<Settings
onChange={(key, value) => this.setState({ [key]: value })}
enabled={this.state}
{...this.state}
onChange={this.updateSetting}
resetDefaultSettings={this.resetDefaultSettings}
/>
<div className="buttons">
@ -213,35 +235,11 @@ class Editor extends React.Component {
color="#57b5f9"
style={{ marginRight: '8px' }}
/>
<Dropdown
button
color="#c198fb"
selected={{ id: 'SAVE_IMAGE', name: 'Save Image' }}
list={['png', 'svg'].map(id => ({ id, name: id.toUpperCase() }))}
onChange={saveAs => this.save({ format: saveAs.id })}
/>
<Dropdown {...saveButtonOptions} onChange={this.save} />
</div>
</Toolbar>
<ReadFileDropContainer
readAs={file => {
if (this.isImage(file)) {
return DATA_URL
}
return TEXT
}}
onDrop={([file]) => {
if (this.isImage(file)) {
this.setState({
backgroundImage: file.content,
backgroundImageSelection: null,
backgroundMode: 'image'
})
} else {
this.setState({ code: file.content, language: 'auto' })
}
}}
>
<ReadFileDropContainer readAs={readAs} onDrop={this.onDrop}>
{({ isOver, canDrop }) => (
<Overlay
isOver={isOver || canDrop}
@ -249,8 +247,9 @@ class Editor extends React.Component {
>
<Carbon
config={this.state}
updateCode={code => this.updateCode(code)}
updateCode={this.updateCode}
onAspectRatioChange={this.updateAspectRatio}
titleBar={this.state.titleBar}
updateTitleBar={this.updateTitleBar}
>
{this.state.code != null ? this.state.code : DEFAULT_CODE}
@ -277,4 +276,23 @@ class Editor extends React.Component {
}
}
function removeQueryString(str) {
const qI = str.indexOf('?')
return (qI >= 0 ? str.substr(0, qI) : str)
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2F;')
}
function isImage(file) {
return file.type.split('/')[0] === 'image'
}
function readAs(file) {
if (isImage(file)) {
return DATA_URL
}
return TEXT
}
export default DragDropContext(HTML5Backend)(Editor)

Loading…
Cancel
Save