mirror of https://github.com/sgoudham/carbon.git
Refactor state components (#338)
* WIP: Unstated works * WIP: extract into its own file * Container instance * Remove unused variables * Move toolbar children into Toolbar * Extract our Coder interface * Remove top level subscription * Bug fix * WIP * Remove old dependencies * Add import eslint plugin * Rename components - add createRef TODO [ ] * Bug fixes * Rename Coder -> CodeWindow * Address comments - Rename to variable - Rename onDrop from Editor containers perspective * More variable renamingmain
parent
f44f944d3a
commit
0a59db56c8
@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es6: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: ['eslint:recommended', 'plugin:react/recommended'],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
experimentalObjectRestSpread: true,
|
||||||
|
jsx: true
|
||||||
|
},
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
plugins: ['react', 'import'],
|
||||||
|
rules: {
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'react/display-name': 'off',
|
||||||
|
'react/jsx-uses-react': 'error',
|
||||||
|
'react/jsx-uses-vars': 'error',
|
||||||
|
'import/no-unresolved': 2
|
||||||
|
}
|
||||||
|
}
|
@ -1,279 +1,41 @@
|
|||||||
import React, { PureComponent } from 'react'
|
// Theirs
|
||||||
import * as hljs from 'highlight.js'
|
import React from 'react'
|
||||||
import Spinner from 'react-spinner'
|
import { Provider } from 'unstated'
|
||||||
import ResizeObserver from 'resize-observer-polyfill'
|
import HTML5Backend from 'react-dnd-html5-backend'
|
||||||
import debounce from 'lodash.debounce'
|
import { DragDropContext } from 'react-dnd'
|
||||||
import ms from 'ms'
|
|
||||||
|
// Ours
|
||||||
import WindowControls from '../components/WindowControls'
|
import EditorContainer from '../containers/Editor'
|
||||||
import Watermark from '../components/svg/Watermark'
|
import Editor from './Editor'
|
||||||
import CodeMirror from '../lib/react-codemirror'
|
import Toolbar from './Toolbar'
|
||||||
import { COLORS, LANGUAGE_MODE_HASH, LANGUAGE_NAME_HASH, DEFAULT_SETTINGS } from '../lib/constants'
|
import { COLORS } from '../lib/constants'
|
||||||
|
|
||||||
class Carbon extends PureComponent {
|
class Carbon extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
this.inject = [new EditorContainer(props)]
|
||||||
this.state = {
|
|
||||||
loading: true,
|
|
||||||
language: props.config.language
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleLanguageChange = this.handleLanguageChange.bind(this)
|
|
||||||
this.handleTitleBarChange = this.handleTitleBarChange.bind(this)
|
|
||||||
this.codeUpdated = this.codeUpdated.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.handleLanguageChange(this.props.children)
|
|
||||||
|
|
||||||
const ro = new ResizeObserver(entries => {
|
|
||||||
const cr = entries[0].contentRect
|
|
||||||
this.props.onAspectRatioChange(cr.width / cr.height)
|
|
||||||
})
|
|
||||||
ro.observe(this.exportContainerNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(newProps) {
|
|
||||||
// TODO use getDerivedStateFromProps() on React@16.3
|
|
||||||
this.handleLanguageChange(newProps.children, { customProps: newProps })
|
|
||||||
}
|
|
||||||
|
|
||||||
codeUpdated(newCode) {
|
|
||||||
this.handleLanguageChange(newCode)
|
|
||||||
this.props.updateCode(newCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTitleBarChange(newTitle) {
|
|
||||||
this.props.updateTitleBar(newTitle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLanguageChange = debounce(
|
|
||||||
(newCode, config) => {
|
|
||||||
const props = (config && config.customProps) || this.props
|
|
||||||
|
|
||||||
if (props.config.language === 'auto') {
|
|
||||||
// try to set the language
|
|
||||||
const detectedLanguage = hljs.highlightAuto(newCode).language
|
|
||||||
const languageMode =
|
|
||||||
LANGUAGE_MODE_HASH[detectedLanguage] || LANGUAGE_NAME_HASH[detectedLanguage]
|
|
||||||
|
|
||||||
if (languageMode) {
|
|
||||||
this.setState({ language: languageMode.mime || languageMode.mode })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setState({ language: props.config.language })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ms('300ms'),
|
|
||||||
{ trailing: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const config = { ...DEFAULT_SETTINGS, ...this.props.config }
|
|
||||||
const options = {
|
|
||||||
lineNumbers: config.lineNumbers,
|
|
||||||
mode: this.state.language || 'plaintext',
|
|
||||||
theme: config.theme,
|
|
||||||
scrollBarStyle: null,
|
|
||||||
viewportMargin: Infinity,
|
|
||||||
lineWrapping: true,
|
|
||||||
extraKeys: {
|
|
||||||
'Shift-Tab': 'indentLess'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const backgroundImage =
|
|
||||||
(this.props.config.backgroundImage && this.props.config.backgroundImageSelection) ||
|
|
||||||
this.props.config.backgroundImage
|
|
||||||
|
|
||||||
// set content to spinner if loading, else editor
|
|
||||||
let content = (
|
|
||||||
<div>
|
|
||||||
<Spinner />
|
|
||||||
<style jsx>
|
|
||||||
{`
|
|
||||||
div {
|
|
||||||
height: 352px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
if (this.state.loading === false) {
|
|
||||||
content = (
|
|
||||||
<div id="container">
|
|
||||||
{config.windowControls ? (
|
|
||||||
<WindowControls
|
|
||||||
titleBar={this.props.titleBar}
|
|
||||||
theme={config.windowTheme}
|
|
||||||
handleTitleBarChange={this.handleTitleBarChange}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<CodeMirror
|
|
||||||
className={`CodeMirror__container window-theme__${config.windowTheme}`}
|
|
||||||
onBeforeChange={(editor, meta, code) => this.codeUpdated(code)}
|
|
||||||
value={this.props.children}
|
|
||||||
options={options}
|
|
||||||
/>
|
|
||||||
{config.watermark && <Watermark />}
|
|
||||||
<div id="container-bg">
|
|
||||||
<div className="white eliminateOnRender" />
|
|
||||||
<div className="alpha eliminateOnRender" />
|
|
||||||
<div className="bg" />
|
|
||||||
</div>
|
|
||||||
<style jsx>
|
|
||||||
{`
|
|
||||||
#container {
|
|
||||||
position: relative;
|
|
||||||
min-width: ${config.widthAdjustment ? '90px' : '680px'};
|
|
||||||
max-width: 1024px; /* The Fallback */
|
|
||||||
max-width: 92vw;
|
|
||||||
padding: ${config.paddingVertical} ${config.paddingHorizontal};
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.watermark) {
|
|
||||||
fill-opacity: 0.3;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
bottom: calc(${config.paddingVertical} + 16px);
|
|
||||||
right: calc(${config.paddingHorizontal} + 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container #container-bg {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container .white {
|
|
||||||
background: #fff;
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container .bg {
|
|
||||||
${this.props.config.backgroundMode === 'image'
|
|
||||||
? `background: url(${backgroundImage});
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;`
|
|
||||||
: `background: ${this.props.config.backgroundColor || config.backgroundColor};
|
|
||||||
background-size: auto;
|
|
||||||
background-repeat: repeat;`} position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container .alpha {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.cm-s-dracula .CodeMirror-cursor) {
|
|
||||||
border-left: solid 2px #159588;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.cm-s-solarized) {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.cm-s-solarized.cm-s-light) {
|
|
||||||
text-shadow: #eee8d5 0 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.CodeMirror-gutters) {
|
|
||||||
background-color: unset;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.CodeMirror__container) {
|
|
||||||
min-width: inherit;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
border-radius: 5px;
|
|
||||||
${config.dropShadow
|
|
||||||
? `box-shadow: 0 ${config.dropShadowOffsetY} ${
|
|
||||||
config.dropShadowBlurRadius
|
|
||||||
} rgba(0, 0, 0, 0.55)`
|
|
||||||
: ''};
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.CodeMirror__container .CodeMirror) {
|
|
||||||
height: auto;
|
|
||||||
min-width: inherit;
|
|
||||||
padding: 18px 18px;
|
|
||||||
${config.lineNumbers ? 'padding-left: 12px;' : ''} border-radius: 5px;
|
|
||||||
font-family: ${config.fontFamily}, monospace !important;
|
|
||||||
font-size: ${config.fontSize};
|
|
||||||
font-variant-ligatures: contextual;
|
|
||||||
font-feature-settings: 'calt' 1;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.CodeMirror-scroll) {
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.window-theme__sharp > .CodeMirror) {
|
|
||||||
border-radius: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.window-theme__bw > .CodeMirror) {
|
|
||||||
border: 2px solid ${COLORS.SECONDARY};
|
|
||||||
}
|
|
||||||
|
|
||||||
#container :global(.window-controls + .CodeMirror__container > .CodeMirror) {
|
|
||||||
padding-top: 48px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="section">
|
<Provider inject={this.inject}>
|
||||||
<div id="export-container" ref={ele => (this.exportContainerNode = ele)}>
|
<div id="carbon">
|
||||||
{content}
|
<Toolbar />
|
||||||
<div id="twitter-png-fix" />
|
<Editor />
|
||||||
</div>
|
</div>
|
||||||
<style jsx>
|
<style jsx>
|
||||||
{`
|
{`
|
||||||
#section,
|
#carbon {
|
||||||
#export-container {
|
background: ${COLORS.BLACK};
|
||||||
height: 100%;
|
border: 3px solid ${COLORS.SECONDARY};
|
||||||
display: flex;
|
border-radius: 8px;
|
||||||
flex-direction: column;
|
padding: 16px;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* forces twitter to save images as png — https://github.com/dawnlabs/carbon/issues/86 */
|
|
||||||
#twitter-png-fix {
|
|
||||||
height: 1px;
|
|
||||||
width: 100%;
|
|
||||||
background: rgba(0, 0, 0, 0.01);
|
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
</div>
|
</Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Carbon
|
export default DragDropContext(HTML5Backend)(Carbon)
|
||||||
|
@ -0,0 +1,280 @@
|
|||||||
|
import React, { PureComponent } from 'react'
|
||||||
|
import * as hljs from 'highlight.js'
|
||||||
|
import Spinner from 'react-spinner'
|
||||||
|
import ResizeObserver from 'resize-observer-polyfill'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
|
import ms from 'ms'
|
||||||
|
|
||||||
|
import WindowControls from '../components/WindowControls'
|
||||||
|
import Watermark from '../components/svg/Watermark'
|
||||||
|
import CodeMirror from '../lib/react-codemirror'
|
||||||
|
import { COLORS, LANGUAGE_MODE_HASH, LANGUAGE_NAME_HASH, DEFAULT_SETTINGS } from '../lib/constants'
|
||||||
|
|
||||||
|
class CodeWindow extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
language: props.config.language
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleLanguageChange = this.handleLanguageChange.bind(this)
|
||||||
|
this.handleTitleBarChange = this.handleTitleBarChange.bind(this)
|
||||||
|
this.codeUpdated = this.codeUpdated.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
this.handleLanguageChange(this.props.children)
|
||||||
|
|
||||||
|
const ro = new ResizeObserver(entries => {
|
||||||
|
const cr = entries[0].contentRect
|
||||||
|
this.props.onAspectRatioChange(cr.width / cr.height)
|
||||||
|
})
|
||||||
|
ro.observe(this.exportContainerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(newProps) {
|
||||||
|
// TODO use getDerivedStateFromProps() on React@16.3
|
||||||
|
this.handleLanguageChange(newProps.children, { customProps: newProps })
|
||||||
|
}
|
||||||
|
|
||||||
|
codeUpdated(newCode) {
|
||||||
|
this.handleLanguageChange(newCode)
|
||||||
|
this.props.updateCode(newCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTitleBarChange(newTitle) {
|
||||||
|
this.props.updateTitleBar(newTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLanguageChange = debounce(
|
||||||
|
(newCode, config) => {
|
||||||
|
const props = (config && config.customProps) || this.props
|
||||||
|
|
||||||
|
if (props.config.language === 'auto') {
|
||||||
|
// try to set the language
|
||||||
|
const detectedLanguage = hljs.highlightAuto(newCode).language
|
||||||
|
const languageMode =
|
||||||
|
LANGUAGE_MODE_HASH[detectedLanguage] || LANGUAGE_NAME_HASH[detectedLanguage]
|
||||||
|
|
||||||
|
if (languageMode) {
|
||||||
|
this.setState({ language: languageMode.mime || languageMode.mode })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({ language: props.config.language })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ms('300ms'),
|
||||||
|
{ trailing: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const config = { ...DEFAULT_SETTINGS, ...this.props.config }
|
||||||
|
const options = {
|
||||||
|
lineNumbers: config.lineNumbers,
|
||||||
|
mode: this.state.language || 'plaintext',
|
||||||
|
theme: config.theme,
|
||||||
|
scrollBarStyle: null,
|
||||||
|
viewportMargin: Infinity,
|
||||||
|
lineWrapping: true,
|
||||||
|
extraKeys: {
|
||||||
|
'Shift-Tab': 'indentLess'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const backgroundImage =
|
||||||
|
(this.props.config.backgroundImage && this.props.config.backgroundImageSelection) ||
|
||||||
|
this.props.config.backgroundImage
|
||||||
|
|
||||||
|
// set content to spinner if loading, else editor
|
||||||
|
let content = (
|
||||||
|
<div>
|
||||||
|
<Spinner />
|
||||||
|
<style jsx>
|
||||||
|
{`
|
||||||
|
div {
|
||||||
|
height: 352px;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
if (this.state.loading === false) {
|
||||||
|
content = (
|
||||||
|
<div id="container">
|
||||||
|
{config.windowControls ? (
|
||||||
|
<WindowControls
|
||||||
|
titleBar={this.props.titleBar}
|
||||||
|
theme={config.windowTheme}
|
||||||
|
handleTitleBarChange={this.handleTitleBarChange}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<CodeMirror
|
||||||
|
className={`CodeMirror__container window-theme__${config.windowTheme}`}
|
||||||
|
onBeforeChange={(editor, meta, code) => this.codeUpdated(code)}
|
||||||
|
value={this.props.children}
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
{config.watermark && <Watermark />}
|
||||||
|
<div id="container-bg">
|
||||||
|
<div className="white eliminateOnRender" />
|
||||||
|
<div className="alpha eliminateOnRender" />
|
||||||
|
<div className="bg" />
|
||||||
|
</div>
|
||||||
|
<style jsx>
|
||||||
|
{`
|
||||||
|
#container {
|
||||||
|
position: relative;
|
||||||
|
min-width: ${config.widthAdjustment ? '90px' : '680px'};
|
||||||
|
max-width: 1024px; /* The Fallback */
|
||||||
|
max-width: 92vw;
|
||||||
|
padding: ${config.paddingVertical} ${config.paddingHorizontal};
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.watermark) {
|
||||||
|
fill-opacity: 0.3;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
bottom: calc(${config.paddingVertical} + 16px);
|
||||||
|
right: calc(${config.paddingHorizontal} + 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#container #container-bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container .white {
|
||||||
|
background: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container .bg {
|
||||||
|
${this.props.config.backgroundMode === 'image'
|
||||||
|
? `background: url(${backgroundImage});
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;`
|
||||||
|
: `background: ${this.props.config.backgroundColor || config.backgroundColor};
|
||||||
|
background-size: auto;
|
||||||
|
background-repeat: repeat;`} position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container .alpha {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==);
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.cm-s-dracula .CodeMirror-cursor) {
|
||||||
|
border-left: solid 2px #159588;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.cm-s-solarized) {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.cm-s-solarized.cm-s-light) {
|
||||||
|
text-shadow: #eee8d5 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.CodeMirror-gutters) {
|
||||||
|
background-color: unset;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.CodeMirror__container) {
|
||||||
|
min-width: inherit;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 5px;
|
||||||
|
${config.dropShadow
|
||||||
|
? `box-shadow: 0 ${config.dropShadowOffsetY} ${
|
||||||
|
config.dropShadowBlurRadius
|
||||||
|
} rgba(0, 0, 0, 0.55)`
|
||||||
|
: ''};
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.CodeMirror__container .CodeMirror) {
|
||||||
|
height: auto;
|
||||||
|
min-width: inherit;
|
||||||
|
padding: 18px 18px;
|
||||||
|
${config.lineNumbers ? 'padding-left: 12px;' : ''} border-radius: 5px;
|
||||||
|
font-family: ${config.fontFamily}, monospace !important;
|
||||||
|
font-size: ${config.fontSize};
|
||||||
|
font-variant-ligatures: contextual;
|
||||||
|
font-feature-settings: 'calt' 1;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.CodeMirror-scroll) {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.window-theme__sharp > .CodeMirror) {
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.window-theme__bw > .CodeMirror) {
|
||||||
|
border: 2px solid ${COLORS.SECONDARY};
|
||||||
|
}
|
||||||
|
|
||||||
|
#container :global(.window-controls + .CodeMirror__container > .CodeMirror) {
|
||||||
|
padding-top: 48px;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="section">
|
||||||
|
{/* TODO use createRef */}
|
||||||
|
<div id="export-container" ref={ele => (this.exportContainerNode = ele)}>
|
||||||
|
{content}
|
||||||
|
<div id="twitter-png-fix" />
|
||||||
|
</div>
|
||||||
|
<style jsx>
|
||||||
|
{`
|
||||||
|
#section,
|
||||||
|
#export-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* forces twitter to save images as png — https://github.com/dawnlabs/carbon/issues/86 */
|
||||||
|
#twitter-png-fix {
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.01);
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CodeWindow
|
@ -1,31 +1,105 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Subscribe } from 'unstated'
|
||||||
|
|
||||||
const Toolbar = props => (
|
import EditorContainer from '../containers/Editor'
|
||||||
<div id="toolbar">
|
import Button from './Button'
|
||||||
{props.children}
|
import Dropdown from './Dropdown'
|
||||||
<style jsx>
|
import BackgroundSelect from './BackgroundSelect'
|
||||||
{`
|
import Settings from './Settings'
|
||||||
#toolbar {
|
import {
|
||||||
width: 100%;
|
THEMES,
|
||||||
height: 40px; // TODO fix
|
THEMES_HASH,
|
||||||
margin-bottom: 16px;
|
LANGUAGES,
|
||||||
display: flex;
|
LANGUAGE_MIME_HASH,
|
||||||
position: relative;
|
LANGUAGE_MODE_HASH,
|
||||||
z-index: 3;
|
LANGUAGE_NAME_HASH,
|
||||||
font-size: 14px;
|
DEFAULT_THEME
|
||||||
color: #fff;
|
} from '../lib/constants'
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar > :global(div) {
|
const editorContainer = [EditorContainer]
|
||||||
margin-right: 8px;
|
const saveButtonOptions = {
|
||||||
}
|
button: true,
|
||||||
|
color: '#c198fb',
|
||||||
|
selected: { id: 'SAVE_IMAGE', name: 'Save Image' },
|
||||||
|
list: ['png', 'svg'].map(id => ({ id, name: id.toUpperCase() }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function Toolbar() {
|
||||||
|
return <Subscribe to={editorContainer}>{render}</Subscribe>
|
||||||
|
}
|
||||||
|
|
||||||
#toolbar > :global(div):last-child {
|
function render(editor) {
|
||||||
margin-right: 0px;
|
return (
|
||||||
|
<div id="toolbar">
|
||||||
|
<Dropdown
|
||||||
|
selected={THEMES_HASH[editor.state.theme] || DEFAULT_THEME}
|
||||||
|
list={THEMES}
|
||||||
|
onChange={editor.updateTheme}
|
||||||
|
/>
|
||||||
|
<Dropdown
|
||||||
|
selected={
|
||||||
|
LANGUAGE_NAME_HASH[editor.state.language] ||
|
||||||
|
LANGUAGE_MIME_HASH[editor.state.language] ||
|
||||||
|
LANGUAGE_MODE_HASH[editor.state.language] ||
|
||||||
|
'auto'
|
||||||
}
|
}
|
||||||
`}
|
list={LANGUAGES}
|
||||||
</style>
|
onChange={editor.updateLanguage}
|
||||||
</div>
|
/>
|
||||||
)
|
<BackgroundSelect
|
||||||
|
onChange={editor.updateBackground}
|
||||||
|
mode={editor.state.backgroundMode}
|
||||||
|
color={editor.state.backgroundColor}
|
||||||
|
image={editor.state.backgroundImage}
|
||||||
|
aspectRatio={editor.state.aspectRatio}
|
||||||
|
/>
|
||||||
|
<Settings
|
||||||
|
{...editor.state}
|
||||||
|
onChange={editor.updateSetting}
|
||||||
|
resetDefaultSettings={editor.resetDefaultSettings}
|
||||||
|
/>
|
||||||
|
<div className="buttons">
|
||||||
|
{/* TODO don't set container function if no prop */}
|
||||||
|
{editor.upload && (
|
||||||
|
<Button
|
||||||
|
className="tweetButton"
|
||||||
|
onClick={editor.upload}
|
||||||
|
title={editor.state.uploading ? 'Loading...' : 'Tweet Image'}
|
||||||
|
color="#57b5f9"
|
||||||
|
style={{ marginRight: '8px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Dropdown {...saveButtonOptions} onChange={editor.save} />
|
||||||
|
</div>
|
||||||
|
<style jsx>
|
||||||
|
{`
|
||||||
|
#toolbar {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px; // TODO fix
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar > :global(div) {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar > :global(div):last-child {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default Toolbar
|
export default Toolbar
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
// Theirs
|
||||||
|
import { Container } from 'unstated'
|
||||||
|
import domtoimage from 'dom-to-image'
|
||||||
|
|
||||||
|
// Ours
|
||||||
|
import api from '../lib/api'
|
||||||
|
import {
|
||||||
|
DEFAULT_EXPORT_SIZE,
|
||||||
|
EXPORT_SIZES_HASH,
|
||||||
|
DEFAULT_CODE,
|
||||||
|
DEFAULT_SETTINGS
|
||||||
|
} from '../lib/constants'
|
||||||
|
import { serializeState } from '../lib/routing'
|
||||||
|
import { getState, isImage } from '../lib/util'
|
||||||
|
|
||||||
|
class EditorContainer extends Container {
|
||||||
|
constructor(props = {}) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
...DEFAULT_SETTINGS,
|
||||||
|
uploading: false,
|
||||||
|
code: props.content
|
||||||
|
}
|
||||||
|
|
||||||
|
this.save = this.save.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)
|
||||||
|
this.resetDefaultSettings = this.resetDefaultSettings.bind(this)
|
||||||
|
this.getCarbonImage = this.getCarbonImage.bind(this)
|
||||||
|
this.handleDroppedFile = this.handleDroppedFile.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// Load from localStorage and then URL params
|
||||||
|
this.setState({
|
||||||
|
...getState(localStorage),
|
||||||
|
...this.props.initialState
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.props.onUpdate(this.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
getCarbonImage({ format } = { format: 'png' }) {
|
||||||
|
// if safari, get image from api
|
||||||
|
if (
|
||||||
|
navigator.userAgent.indexOf('Safari') !== -1 &&
|
||||||
|
navigator.userAgent.indexOf('Chrome') === -1 &&
|
||||||
|
format === 'png'
|
||||||
|
) {
|
||||||
|
const encodedState = serializeState(this.state)
|
||||||
|
return api.image(encodedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = document.getElementById('export-container')
|
||||||
|
|
||||||
|
const exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value
|
||||||
|
const width = node.offsetWidth * exportSize
|
||||||
|
const height = this.state.squaredImage
|
||||||
|
? node.offsetWidth * exportSize
|
||||||
|
: node.offsetHeight * exportSize
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
style: {
|
||||||
|
transform: `scale(${exportSize})`,
|
||||||
|
'transform-origin': 'center',
|
||||||
|
background: this.state.squaredImage ? this.state.backgroundColor : 'none'
|
||||||
|
},
|
||||||
|
filter: n => {
|
||||||
|
// %[00 -> 19] cause failures
|
||||||
|
if (n.innerText && n.innerText.match(/%[0-1][0-9]/)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (n.className) {
|
||||||
|
return String(n.className).indexOf('eliminateOnRender') < 0
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === 'svg') {
|
||||||
|
return domtoimage
|
||||||
|
.toSvg(node, config)
|
||||||
|
.then(dataUrl => dataUrl.split(' ').join(' '))
|
||||||
|
.then(uri => uri.slice(uri.indexOf(',') + 1))
|
||||||
|
.then(data => new Blob([data], { type: 'image/svg+xml' }))
|
||||||
|
.then(data => window.URL.createObjectURL(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return domtoimage.toBlob(node, config).then(blob => window.URL.createObjectURL(blob))
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSetting(key, value) {
|
||||||
|
this.setState({ [key]: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
save({ id: format = 'png' }) {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
|
||||||
|
return this.getCarbonImage({ format }).then(url => {
|
||||||
|
link.download = `carbon.${format}`
|
||||||
|
link.href = url
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
link.remove()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resetDefaultSettings() {
|
||||||
|
this.setState(DEFAULT_SETTINGS)
|
||||||
|
localStorage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
upload() {
|
||||||
|
this.setState({ uploading: true })
|
||||||
|
this.getCarbonImage({ format: 'png' })
|
||||||
|
.then(this.props.tweet)
|
||||||
|
// eslint-disable-next-line
|
||||||
|
.catch(console.error)
|
||||||
|
.then(() => this.setState({ uploading: false }))
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDroppedFile([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({ photographer, ...changes } = {}) {
|
||||||
|
if (photographer) {
|
||||||
|
this.setState(({ code = DEFAULT_CODE }) => ({
|
||||||
|
...changes,
|
||||||
|
code: code + `\n\n// Photo by ${photographer.name} on Unsplash`
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
this.setState(changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorContainer.defaultProps = {
|
||||||
|
onUpdate: () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditorContainer
|
@ -1,4 +1,5 @@
|
|||||||
|
/* eslint-disable */
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
|
||||||
// Require Codemirror elixir mode from npm modules and register it here
|
// Require Codemirror elixir mode from npm modules and register it here
|
||||||
import registerElixirMode from 'codemirror-mode-elixir'
|
export * from 'codemirror-mode-elixir'
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
|
/* eslint-disable */
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import 'codemirror-graphql/mode'
|
import 'codemirror-graphql/mode'
|
||||||
|
Loading…
Reference in New Issue