├── postcss.config.js ├── dist ├── app.bundle.css.map ├── resources │ ├── favicon.ico │ └── logo.svg ├── index.html └── app.bundle.css ├── src ├── index.js ├── style │ ├── index.js │ ├── font.css │ ├── reset.css │ ├── codepen-export.svg │ └── app.scss └── app │ ├── component │ ├── index.js │ ├── gui │ │ ├── input │ │ │ ├── index.jsx │ │ │ ├── radio.jsx │ │ │ ├── Code.jsx │ │ │ ├── checkbox.jsx │ │ │ ├── text.jsx │ │ │ ├── collection.js │ │ │ └── number.jsx │ │ ├── icon │ │ │ ├── modify.jsx │ │ │ └── Clear.jsx │ │ ├── index.jsx │ │ ├── member.jsx │ │ └── property.jsx │ ├── chart │ │ ├── billboard │ │ │ ├── util.js │ │ │ ├── proptypes.js │ │ │ └── index.jsx │ │ └── index.jsx │ ├── tabs │ │ └── index.jsx │ ├── guide │ │ ├── index.jsx │ │ └── card.jsx │ ├── command │ │ ├── exportcode.js │ │ ├── index.jsx │ │ └── codemirror.js │ └── data │ │ └── index.jsx │ ├── configure │ ├── resources │ │ ├── template.js │ │ └── preset.js │ └── index.js │ ├── index.jsx │ ├── reducers │ ├── logic │ │ ├── convert.js │ │ └── index.js │ └── index.js │ ├── actions │ └── index.js │ └── util │ └── index.js ├── .gitignore ├── webpack.config.js ├── .eslintrc ├── LICENSE ├── README.md ├── package.json ├── NOTICE └── webpack.production.config.js /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /dist/app.bundle.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"app.bundle.css","sourceRoot":""} -------------------------------------------------------------------------------- /dist/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naver/billboard.js-playground/master/dist/resources/favicon.ico -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require("codemirror/mode/javascript/javascript"); 2 | require("./style"); 3 | require("./app/index.jsx"); 4 | -------------------------------------------------------------------------------- /src/style/index.js: -------------------------------------------------------------------------------- 1 | require("./reset.css"); 2 | require("./font.css"); 3 | require("codemirror/lib/codemirror.css"); 4 | require("billboard.js/dist/billboard.css"); 5 | require("./app.scss"); 6 | -------------------------------------------------------------------------------- /src/app/component/index.js: -------------------------------------------------------------------------------- 1 | import { Chart } from "./chart"; 2 | import { GUI } from "./gui"; 3 | import { TabViews } from "./tabs"; 4 | import { Guide } from "./guide"; 5 | 6 | export { 7 | Chart, GUI, TabViews, Guide 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm modules 2 | components 3 | build 4 | .sass-cache 5 | npm-debug.log 6 | npm-debug.log* 7 | 8 | # coverage report 9 | coverage/ 10 | .DS_Store 11 | #dist/ 12 | doc/ 13 | node_modules 14 | .idea/ 15 | *.log 16 | -------------------------------------------------------------------------------- /src/app/configure/resources/template.js: -------------------------------------------------------------------------------- 1 | const line1 = { 2 | data: { 3 | columns: [ 4 | ["data1", 30, 200, 100, 400, 150], 5 | ["data2", 50, 20, 10, 40, 15], 6 | ["data3", 50, 20, 10, 40, 15], 7 | ["data4", 30, 200, 130, 400, 30], 8 | ["data5", 50, 20, 10, 40, 415] 9 | ] 10 | } 11 | }; 12 | 13 | export { 14 | line1 15 | }; 16 | export default line1; 17 | -------------------------------------------------------------------------------- /src/app/component/gui/input/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import ConnectedCheckbox from "./checkbox"; 4 | import ConnectedNumber from "./number"; 5 | import ConnectedText from "./text"; 6 | import ConnectedCollection from "./collection"; 7 | import ConnectedCode from "./code"; 8 | 9 | export { 10 | ConnectedCollection, 11 | ConnectedCheckbox, 12 | ConnectedNumber, 13 | ConnectedText, 14 | ConnectedCode 15 | }; 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const portscanner = require("portscanner"); 2 | const productconfig = require("./webpack.production.config"); 3 | 4 | const config = productconfig; 5 | 6 | const host = process.env.HOST || "localhost"; 7 | const port = process.env.PORT || 3000; 8 | 9 | const portPromise = new Promise((resolve) => { 10 | portscanner.findAPortNotInUse(port, port + 10, host, (error, psport) => { 11 | config.devServer.port = psport; // Status is 'open' if currently in use or 'closed' if available 12 | config.devServer.host = host; 13 | resolve(config); 14 | }); 15 | }); 16 | 17 | module.exports = portPromise; 18 | 19 | -------------------------------------------------------------------------------- /src/app/component/chart/billboard/util.js: -------------------------------------------------------------------------------- 1 | // http://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge 2 | const deepCopy = (target, ...sources) => { 3 | const isObject = (item) =>{ 4 | return (item && typeof item === 'object' && !Array.isArray(item)); 5 | }; 6 | 7 | if (!sources.length) return target; 8 | const source = sources.shift(); 9 | 10 | if (isObject(target) && isObject(source)) { 11 | for (const key in source) { 12 | if (isObject(source[key])) { 13 | if (!target[key]) Object.assign(target, { [key]: {} }); 14 | deepCopy(target[key], source[key]); 15 | } else { 16 | Object.assign(target, { [key]: source[key] }); 17 | } 18 | } 19 | } 20 | 21 | return deepCopy(target, ...sources); 22 | }; 23 | 24 | const UTIL = { 25 | deepCopy: deepCopy 26 | }; 27 | 28 | export { 29 | deepCopy 30 | }; 31 | export default UTIL; -------------------------------------------------------------------------------- /src/app/component/chart/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { connect } from "react-redux"; 3 | import CONFIGURE_PROPTYPES from "./billboard/proptypes"; 4 | import { Billboard } from "./billboard/index"; 5 | 6 | class RawChart extends React.Component { 7 | render() { 8 | const chartConfigure = this.props.chartConfigure; 9 | 10 | return (
11 |
12 | 16 |
17 |
); 18 | } 19 | } 20 | 21 | RawChart.propTypes = { 22 | chartConfigure: PropTypes.shape(CONFIGURE_PROPTYPES).isRequired 23 | }; 24 | 25 | const mapStateToProps = state => ({ 26 | chartConfigure: state.command.original 27 | }); 28 | 29 | const Chart = connect(mapStateToProps)(RawChart); 30 | 31 | export { 32 | Chart, 33 | CONFIGURE_PROPTYPES 34 | }; 35 | export default Chart; 36 | 37 | -------------------------------------------------------------------------------- /src/style/font.css: -------------------------------------------------------------------------------- 1 | 2 | /* fallback */ 3 | @font-face { 4 | font-family: 'Material Icons'; 5 | font-style: normal; 6 | font-weight: 400; 7 | /*src: url(MaterialIcons-Regular.woff2) format('woff2'),*/ 8 | src: url(https://fonts.gstatic.com/s/materialicons/v29/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2) format('woff2'); 9 | } 10 | @import url('https://fonts.googleapis.com/css?family=Bungee'); 11 | @import url('https://fonts.googleapis.com/css?family=Roboto:400,500'); 12 | 13 | .material-icons { 14 | font-family: 'Material Icons'; 15 | font-weight: normal; 16 | font-style: normal; 17 | font-size: 24px; 18 | line-height: 1; 19 | letter-spacing: normal; 20 | text-transform: none; 21 | display: inline-block; 22 | white-space: nowrap; 23 | word-wrap: normal; 24 | direction: ltr; 25 | -webkit-font-feature-settings: 'liga'; 26 | -webkit-font-smoothing: antialiased; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { createStore } from "redux"; 5 | import { Chart } from "./component/chart"; 6 | import { GUI } from "./component/gui"; 7 | import { TabViews } from "./component/tabs"; 8 | import { Guide } from "./component/guide"; 9 | import playgroundApp from "./reducers"; 10 | 11 | const store = createStore(playgroundApp); 12 | const root = document.querySelector("#app"); 13 | 14 | ReactDOM.render( 15 | 16 |
17 |
18 | 🎨 Let’s play with 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 |
28 |
, 29 | root 30 | ); 31 | 32 | document.body.className = ""; 33 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | // .eslintrc 파일 2 | { 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "extends": "airbnb", 8 | "parserOptions" : { 9 | "sourceType": "module", 10 | "ecmaFeatures": { 11 | "jsx": true 12 | } 13 | }, 14 | "rules": { 15 | "indent": ["error", 16 | "tab", 17 | { 18 | "SwitchCase": 1, 19 | "MemberExpression": 1 20 | } 21 | ], 22 | "padded-blocks": ["error", "never"], 23 | "no-tabs": "off", 24 | "react/jsx-indent": ["error", 25 | "tab" 26 | ], 27 | "react/jsx-indent-props": ["error", "tab"], 28 | "quotes": ["error", "double", { 29 | "avoidEscape": true, 30 | "allowTemplateLiterals": true 31 | }], 32 | "jsx-closing-bracket-location" : [1, {"selfClosing": "props-aligned", "nonEmpty": "after-props"}], 33 | "react/forbid-prop-types": ["error", { "forbid": [] }], 34 | "react/prefer-stateless-function": [0, { "ignorePureComponents": true }], 35 | "comma-dangle" : ["error", { 36 | "functions": "ignore" 37 | }] 38 | } 39 | } -------------------------------------------------------------------------------- /src/app/component/gui/icon/modify.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import FontIcon from 'material-ui/FontIcon'; 4 | import { grey500 } from 'material-ui/styles/colors'; 5 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 6 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 7 | 8 | class EditIcon extends React.Component { 9 | render (){ 10 | return 11 | mode edit 18 | ; 19 | } 20 | 21 | onClickDelete(e) { 22 | //this.props.onClickDelete(e); 23 | } 24 | } 25 | 26 | const mapDispatchToProps = (dispatch, ownProps) => { 27 | return { 28 | //onChangeActive: e => dispatch(changeGuiActivate(ownProps.name.replace(/\:/g, "."), e.target.checked)), 29 | }; 30 | }; 31 | 32 | const Modify = connect( 33 | null, mapDispatchToProps 34 | )(EditIcon); 35 | 36 | export default Modify; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 NAVER Corp. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/app/component/chart/billboard/proptypes.js: -------------------------------------------------------------------------------- 1 | import { 2 | PropTypes 3 | } from "react"; 4 | 5 | const CONFIGURE_PROPTYPES = { 6 | area: PropTypes.object, 7 | axis: PropTypes.object, 8 | bar: PropTypes.object, 9 | bindto: PropTypes.string, 10 | color: PropTypes.object, 11 | data: PropTypes.object.isRequired, 12 | donut: PropTypes.object, 13 | gauge: PropTypes.object, 14 | grid: PropTypes.object, 15 | interaction: PropTypes.object, 16 | legend: PropTypes.object, 17 | line: PropTypes.object, 18 | oninit: PropTypes.func, 19 | onout: PropTypes.func, 20 | onover: PropTypes.func, 21 | onrendered: PropTypes.func, 22 | onresize: PropTypes.func, 23 | onresized: PropTypes.func, 24 | padding: PropTypes.object, 25 | pie: PropTypes.object, 26 | point: PropTypes.object, 27 | regions: PropTypes.array, 28 | resize: PropTypes.object, 29 | size: PropTypes.object, 30 | spline: PropTypes.object, 31 | subchart: PropTypes.object, 32 | svg: PropTypes.object, 33 | title: PropTypes.object, 34 | tooltip: PropTypes.object, 35 | transition: PropTypes.object, 36 | zoom: PropTypes.object, 37 | forceUpdate: PropTypes.bool // custom 38 | }; 39 | 40 | export default CONFIGURE_PROPTYPES; 41 | -------------------------------------------------------------------------------- /src/app/component/gui/icon/Clear.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import FontIcon from 'material-ui/FontIcon'; 4 | import { 5 | deepOrange300, 6 | grey500 7 | } from 'material-ui/styles/colors'; 8 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 9 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 10 | import { 11 | resetGui 12 | } from "../../../actions"; 13 | 14 | 15 | class DeleteIcon extends React.Component { 16 | render (){ 17 | const color = this.props.activated ? deepOrange300 : grey500; 18 | return 19 |
20 | this.onClickDelete(e)} 22 | className="material-icons" 23 | color={color}>remove_circle 24 |
25 |
; 26 | } 27 | 28 | onClickDelete(e) { 29 | this.props.onClickDelete(e); 30 | } 31 | } 32 | 33 | const mapDispatchToProps = (dispatch, ownProps) => { 34 | return { 35 | onChangeActive: e => dispatch(changeGuiActivate(ownProps.name.replace(/\:/g, "."), e.target.checked)), 36 | onClickDelete: () => dispatch(resetGui(ownProps.name.replace(/\:/g, "."), { 37 | root: ownProps.rootMemberName 38 | })) 39 | }; 40 | }; 41 | 42 | const Clear = connect( 43 | null, mapDispatchToProps 44 | )(DeleteIcon); 45 | 46 | export default Clear; 47 | -------------------------------------------------------------------------------- /src/app/component/tabs/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Tabs, Tab} from 'material-ui/Tabs'; 3 | import { Command } from "../command"; 4 | import { Data } from "../data"; 5 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 6 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 7 | import {grey100, grey400, deepOrange300} from "material-ui/styles/colors"; 8 | 9 | class TabViews extends React.Component { 10 | componentWillMount(props) { 11 | this.setState({ 12 | target: "command", 13 | }); 14 | } 15 | 16 | render(){ 17 | return (
18 | 25 | this.onChangeTab(e)}> 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
); 40 | } 41 | 42 | onChangeTab(target) { 43 | this.setState({ 44 | target 45 | }); 46 | } 47 | } 48 | 49 | export { 50 | TabViews 51 | } 52 | export default TabViews; 53 | -------------------------------------------------------------------------------- /src/app/component/guide/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Drawer from "material-ui/Drawer"; 3 | import getMuiTheme from "material-ui/styles/getMuiTheme"; 4 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 5 | import { connect } from "react-redux"; 6 | import { DocumentCard } from "./card"; 7 | 8 | class DockedGuide extends React.Component { 9 | constructor() { 10 | super(); 11 | 12 | this.state = { 13 | open: false 14 | }; 15 | } 16 | 17 | componentDidMount() { 18 | this.setState(this.props); 19 | } 20 | 21 | componentWillReceiveProps(nextProps) { 22 | this.setState(nextProps); 23 | } 24 | 25 | shouldComponentUpdate(nextProps, nextState) { 26 | if (this.state.open !== nextState.open) { 27 | this.setState({ 28 | open: nextState.open 29 | }); 30 | return true; 31 | } else { 32 | return false; 33 | } 34 | } 35 | 36 | render() { 37 | return ( 38 | 39 | { this.setState({ open }); }} 47 | width={600} 48 | openSecondary={true} 49 | open={this.state.open} 50 | > 51 | 52 | 53 | ); 54 | } 55 | } 56 | 57 | const mapStateToProps = state => ({ 58 | open: state.guide.open, 59 | style: state.guide.style, 60 | }); 61 | 62 | const Guide = connect( 63 | mapStateToProps, null 64 | )(DockedGuide); 65 | 66 | 67 | export { 68 | Guide 69 | } 70 | export default Guide; 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # billboard.js playground 2 | 3 | - Create chart easy with GUI & Command & Table UI 🎨 . 4 | 5 | ### Features 6 | - You can handle most configures supported in billboard.js [billboard.js API](https://naver.github.io/billboard.js/release/latest/doc/) 7 | - directly access to description, default value, example etc. 8 | - export to code pen and share 9 | 10 | ![demo](https://user-images.githubusercontent.com/3433156/32003963-a68fce54-b9db-11e7-8694-8241c2b06a20.gif) 11 | 12 | 13 | 14 | 15 | ### comming soon.. 16 | > import data file .csv, .json ... 17 | > color, data type template 18 | 19 | ### LICENSE 20 | 21 | ``` 22 | Copyright (c) 2017 NAVER Corp. 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy of 25 | this software and associated documentation files (the "Software"), to deal in 26 | the Software without restriction, including without limitation the rights to 27 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 28 | the Software, and to permit persons to whom the Software is furnished to do so, 29 | subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all 32 | copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 36 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 37 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 38 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 39 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 40 | ``` 41 | -------------------------------------------------------------------------------- /src/app/component/gui/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import * as _ from "lodash"; 3 | import { connect } from "react-redux"; 4 | import Property from "./property"; 5 | import Member from "./member"; 6 | import Subheader from 'material-ui/Subheader'; 7 | import Divider from 'material-ui/Divider'; 8 | import {List, ListItem} from 'material-ui/List'; 9 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 10 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 11 | import {yellow200, deepOrange400,deepOrange500, deepOrange700, grey900, grey50, grey200, yellow500, red600, deepOrange600, greenA200} from 'material-ui/styles/colors'; 12 | 13 | class Control extends React.Component { 14 | getMember() { 15 | const hasProperty = []; 16 | const noneProperty = []; 17 | const lastUpdateRoot = this.props.lastUpdateRoot; 18 | 19 | _.map(this.props, (option) => { 20 | if (option.kind === "member") { 21 | const properties = option.properties; 22 | 23 | if (properties) { 24 | 25 | hasProperty.push(); 26 | 27 | } else { 28 | if(option.attributes.name !== "regions"){ 29 | noneProperty.push(); 30 | } 31 | } 32 | } 33 | }); 34 | 35 | return _.map(noneProperty.concat(hasProperty), (pp) => (pp)); 36 | } 37 | 38 | render() { 39 | return (
40 |
41 | {this.getMember()} 42 |
43 |
); 44 | } 45 | } 46 | 47 | const mapStateToProps = (state) => { 48 | return state.gui; 49 | }; 50 | 51 | const GUI = connect(mapStateToProps)(Control); 52 | 53 | export { 54 | GUI, 55 | }; 56 | export default GUI; 57 | -------------------------------------------------------------------------------- /src/app/component/gui/input/radio.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { connect } from "react-redux"; 3 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 4 | import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton'; 5 | import { grey400, lightBlue300} from 'material-ui/styles/colors'; 6 | import { updateGui, resetGui } from "../../../actions"; 7 | 8 | const iconSelect = { 9 | fill: lightBlue300, 10 | }; 11 | 12 | const icon = { 13 | fill: grey400 14 | }; 15 | 16 | const labelSelect = { 17 | fontSize : "14px" 18 | } 19 | 20 | const label = { 21 | fontSize : "14px", 22 | color: grey400 23 | } 24 | 25 | class InputRadio extends React.Component { 26 | render() { 27 | const { valueoptions, name, value, onChangeRadio } = this.props; 28 | 29 | return 30 | 31 | {_.map(valueoptions, (v, i) => { 32 | const iconStyle = value === v ? iconSelect : icon; 33 | const labelStyle = value === v ? labelSelect : label; 34 | 35 | return (); 42 | })} 43 | 44 | ; 45 | } 46 | } 47 | 48 | const mapDispatchToProps = (dispatch, ownProps) => ({ 49 | onChangeRadio: (e, value) => { 50 | if (value === "") { 51 | dispatch(resetGui(ownProps.name.replace(/\:/g, "."), { 52 | root: ownProps.rootMemberName 53 | })); 54 | } else { 55 | (dispatch(updateGui(ownProps.name.replace(/\:/g, "."), { 56 | value: value, 57 | root: ownProps.rootMemberName 58 | }))); 59 | } 60 | } 61 | }); 62 | 63 | 64 | const ConnectedRadio = connect( 65 | null, mapDispatchToProps 66 | )(InputRadio); 67 | 68 | export default ConnectedRadio; 69 | -------------------------------------------------------------------------------- /src/app/component/gui/input/Code.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import TextField from 'material-ui/TextField'; 3 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 4 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 5 | import IconButton from 'material-ui/IconButton'; 6 | 7 | import { connect } from "react-redux"; 8 | import { 9 | updateCodeInput 10 | } from "../../../actions"; 11 | import {grey900, grey400, lightBlue300, grey800} from 'material-ui/styles/colors'; 12 | 13 | class CodeInput extends React.Component { 14 | render() { 15 | let { value } = this.props; 16 | value += ""; 17 | 18 | return 19 |
20 | 32 | edit} 40 | /> 41 |
42 | 43 |
; 44 | } 45 | } 46 | 47 | const mapDispatchToProps = (dispatch, ownProps) => ({ 48 | onClickCodeEdit: (e, value) => { 49 | dispatch(updateCodeInput(ownProps.name.replace(/\:/g, "."), { 50 | root: ownProps.rootMemberName || ownProps.name 51 | })); 52 | } 53 | }); 54 | 55 | const ConnectedCode = connect( 56 | null, mapDispatchToProps 57 | )(CodeInput); 58 | 59 | export default ConnectedCode; 60 | -------------------------------------------------------------------------------- /src/app/component/gui/input/checkbox.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { connect } from "react-redux"; 3 | import getMuiTheme from "material-ui/styles/getMuiTheme"; 4 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 5 | 6 | import Toggle from "material-ui/Toggle"; 7 | import * as color from "material-ui/styles/colors"; 8 | import { updateGui } from "../../../actions"; 9 | 10 | const styles = { 11 | block: { 12 | maxWidth: 250, 13 | }, 14 | toggle: { 15 | marginBottom: 16, 16 | }, 17 | thumbSwitched: { 18 | backgroundColor: color.lightBlue100, 19 | }, 20 | trackSwitched: { 21 | backgroundColor: color.lightBlue300, 22 | }, 23 | labelStyle: { 24 | color: 'red', 25 | }, 26 | }; 27 | 28 | class InputCheckbox extends React.Component { 29 | render() { 30 | const checked = this.props.value ? true : false; 31 | const labelStyle = this.props.activated ? { 32 | color: color.grey900, 33 | fontSize: "14px" 34 | } : { 35 | color: color.grey400, 36 | fontSize: "14px" 37 | }; 38 | 39 | return ( 40 |
41 | 42 | 51 | 52 |
53 |
); 54 | } 55 | } 56 | 57 | const mapDispatchToProps = (dispatch, ownProps) => ({ 58 | onChange: (e, checked) => { 59 | dispatch(updateGui(ownProps.name.replace(/\:/g, "."), { 60 | value: checked, 61 | root: ownProps.rootMemberName 62 | })); 63 | } 64 | }); 65 | 66 | const ConnectedCheckbox = connect( 67 | null, mapDispatchToProps 68 | )(InputCheckbox); 69 | 70 | export default ConnectedCheckbox; 71 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | billboard.js playground 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /src/app/component/command/exportcode.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { connect } from "react-redux"; 3 | const beautify = require('js-beautify').js_beautify; 4 | import IconButton from 'material-ui/IconButton'; 5 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 6 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 7 | const URL = { 8 | d3v4 : "https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.2/d3.js", 9 | bbjs : "https://cdnjs.cloudflare.com/ajax/libs/billboard.js/1.0.1/billboard.js", 10 | bbcss : "https://cdnjs.cloudflare.com/ajax/libs/billboard.js/1.0.1/billboard.css", 11 | }; 12 | 13 | const html = `
`; 14 | 15 | const css_external = `${URL.bbcss}`; 16 | const js_external = `${URL.d3v4};${URL.bbjs}` 17 | 18 | class CodePenExportCode extends React.Component { 19 | render() { 20 | const textConfig = beautify(this.props.text); 21 | const js = `bb.generate(${textConfig})`; 22 | const value = { 23 | title: "bb playground", 24 | css_external, 25 | js_external, 26 | html, 27 | js 28 | }; 29 | 30 | return (
{ this.form = input; }} 32 | > 33 | 34 | 35 | 36 | this.onClickSubmit(e)} 41 | style={{ 42 | position: "absolute", 43 | backgroundSize: "contain", 44 | border: "none", 45 | width: "60px", 46 | height: "40px", 47 | backgroundImage: `url(codepen-export.svg)` 48 | }} 49 | /> 50 | 51 |
); 52 | } 53 | 54 | onClickSubmit() { 55 | this.form.submit(); 56 | } 57 | } 58 | 59 | 60 | CodePenExportCode.propTypes = { 61 | text: PropTypes.string.isRequired 62 | }; 63 | 64 | const mapStateToProps = state => ({ 65 | text: state.command.text 66 | }); 67 | 68 | const ExportCode = connect(mapStateToProps, null)(CodePenExportCode); 69 | 70 | export default ExportCode; 71 | -------------------------------------------------------------------------------- /src/style/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | html5doctor.com Reset Stylesheet 3 | v1.6.1 4 | Last Updated: 2010-09-17 5 | Author: Richard Clark - http://richclarkdesign.com 6 | Twitter: @rich_clark 7 | License : http://html5doctor.com/html-5-reset-stylesheet/ 8 | */ 9 | 10 | html, body, div, span, object, iframe, 11 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 12 | abbr, address, cite, code, 13 | del, dfn, em, img, ins, kbd, q, samp, 14 | small, strong, sub, sup, var, 15 | b, i, 16 | dl, dt, dd, ol, ul, li, 17 | fieldset, form, label, legend, 18 | table, caption, tbody, tfoot, thead, tr, th, td, 19 | article, aside, canvas, details, figcaption, figure, 20 | footer, header, hgroup, menu, nav, section, summary, 21 | time, mark, audio, video { 22 | margin:0; 23 | padding:0; 24 | border:0; 25 | outline:0; 26 | font-size:100%; 27 | vertical-align:baseline; 28 | background:transparent; 29 | } 30 | 31 | body { 32 | line-height:1; 33 | } 34 | 35 | article,aside,details,figcaption,figure, 36 | footer,header,hgroup,menu,nav,section { 37 | display:block; 38 | } 39 | 40 | nav ul { 41 | list-style:none; 42 | } 43 | 44 | blockquote, q { 45 | quotes:none; 46 | } 47 | 48 | blockquote:before, blockquote:after, 49 | q:before, q:after { 50 | content:''; 51 | content:none; 52 | } 53 | 54 | a { 55 | margin:0; 56 | padding:0; 57 | font-size:100%; 58 | vertical-align:baseline; 59 | background:transparent; 60 | } 61 | 62 | /* change colours to suit your needs */ 63 | ins { 64 | background-color:#ff9; 65 | color:#000; 66 | text-decoration:none; 67 | } 68 | 69 | /* change colours to suit your needs */ 70 | mark { 71 | background-color:#ff9; 72 | color:#000; 73 | font-style:italic; 74 | font-weight:bold; 75 | } 76 | 77 | del { 78 | text-decoration: line-through; 79 | } 80 | 81 | abbr[title], dfn[title] { 82 | border-bottom:1px dotted; 83 | cursor:help; 84 | } 85 | 86 | table { 87 | border-collapse:collapse; 88 | border-spacing:0; 89 | } 90 | 91 | /* change border colour to suit your needs */ 92 | hr { 93 | display:block; 94 | height:1px; 95 | border:0; 96 | border-top:1px solid #cccccc; 97 | margin:1em 0; 98 | padding:0; 99 | } 100 | 101 | input, select { 102 | vertical-align:middle; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/app/component/gui/input/text.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import TextField from 'material-ui/TextField'; 3 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 4 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 5 | import { connect } from "react-redux"; 6 | import { updateGui, resetGui } from "../../../actions"; 7 | import { grey900, grey400, lightBlue300} from 'material-ui/styles/colors'; 8 | import ConnectedRadio from "./radio"; 9 | 10 | const underlineFocusStyle = { 11 | borderColor: lightBlue300 12 | }; 13 | 14 | const textFieldStyle = { 15 | width: "100%", 16 | display: "inline-block" 17 | }; 18 | 19 | class InputText extends React.Component { 20 | getTextToRadio() { 21 | return (); 22 | } 23 | 24 | getDefaultTextArea(options) { 25 | return ( 26 | this.props.onChangeText(e, v)} 29 | underlineFocusStyle={underlineFocusStyle} 30 | style={textFieldStyle} 31 | fullWidth={true} 32 | {...options} /> 33 | ); 34 | } 35 | 36 | render() { 37 | const { value, valueoptions } = this.props; 38 | let returnValue; 39 | 40 | if (valueoptions) { 41 | returnValue = this.getTextToRadio(); 42 | } else if (value === undefined || value === "undefined") { 43 | returnValue = this.getDefaultTextArea({ 44 | hintText: "undefined", 45 | value: "" 46 | }); 47 | } else { 48 | returnValue = this.getDefaultTextArea({ 49 | inputStyle: { 50 | color: this.props.activated ? grey900 : grey400 51 | }, 52 | hintText: "undefined", 53 | value 54 | }); 55 | } 56 | 57 | return returnValue; 58 | } 59 | } 60 | 61 | InputText.propTypes = { 62 | onChangeText: PropTypes.func, 63 | value: PropTypes.string, 64 | valueoptions: PropTypes.array 65 | }; 66 | 67 | 68 | const mapDispatchToProps = (dispatch, ownProps) => ({ 69 | onChangeText: (e, value) => { 70 | if (value === "") { 71 | dispatch(resetGui(ownProps.name.replace(/\:/g, "."), { 72 | root: ownProps.rootMemberName 73 | })); 74 | } else { 75 | (dispatch(updateGui(ownProps.name.replace(/\:/g, "."), { 76 | value, 77 | root: ownProps.rootMemberName 78 | }))); 79 | } 80 | } 81 | }); 82 | 83 | 84 | const ConnectedText = connect( 85 | null, mapDispatchToProps 86 | )(InputText); 87 | 88 | export default ConnectedText; 89 | -------------------------------------------------------------------------------- /src/app/component/gui/member.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { ListItem } from "material-ui/List"; 3 | import getMuiTheme from "material-ui/styles/getMuiTheme"; 4 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 5 | import { deepOrange300 } from "material-ui/styles/colors"; 6 | import * as _ from "lodash"; 7 | import Property from "./property"; 8 | 9 | class Member extends React.Component { 10 | componentWillMount() { 11 | this.setState(this.props); 12 | } 13 | 14 | componentWillReceiveProps(nextProps) { 15 | this.setState(nextProps); 16 | } 17 | 18 | shouldComponentUpdate(nextProps) { 19 | const roots = nextProps.lastUpdateRoot || []; 20 | 21 | if (roots.indexOf(this.props.attributes.name) > -1) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | child(properties) { 28 | let items = []; 29 | 30 | _.map(properties, (option) => { 31 | if (option.properties) { 32 | items = items.concat(this.child(option.properties)); 33 | } else if (option.attributes.type) { 34 | const name = option.attributes.type.names; 35 | 36 | if (name.indexOf("Array") > -1) { 37 | // items.push(); 43 | } else if (name.indexOf("Object") > -1) { 44 | // console.log(option.attributes.name); 45 | // console.log(option.attributes.defaultvalue); 46 | // console.log(option.attributes.examples); 47 | } else { 48 | items.push(); 54 | } 55 | } 56 | }); 57 | 58 | return items; 59 | } 60 | 61 | render() { 62 | const member = this.props.attributes; 63 | const properties = this.props.properties; 64 | 65 | return ( 66 | 81 | ); 82 | } 83 | } 84 | 85 | export default Member; 86 | -------------------------------------------------------------------------------- /src/app/component/gui/input/collection.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import * as _ from "lodash"; 3 | import TextField from 'material-ui/TextField'; 4 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 5 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 6 | import IconButton from 'material-ui/IconButton'; 7 | 8 | import { connect } from "react-redux"; 9 | import { 10 | updateGui, resetGui 11 | } from "../../../actions"; 12 | 13 | class ArrayInput extends React.Component { 14 | render() { 15 | const { value } = this.props; 16 | let returnValue; 17 | const edit = add 18 | 19 | if(Array.isArray(value)){ 20 | returnValue = ( 21 |
22 | 23 | {_.map(value, (v, i) => { 24 | return
25 | {i} 26 | 31 |
32 | })} 33 | 38 |
39 |
); 40 | } else { 41 | returnValue = ( 42 |
43 | 54 | 59 |
60 |
); 61 | } 62 | 63 | return returnValue; 64 | } 65 | } 66 | 67 | const mapDispatchToProps = (dispatch, ownProps) => ({ 68 | onChange: (e, value) => { 69 | if(value === ""){ 70 | dispatch(resetGui(ownProps.name.replace(/\:/g, "."), { 71 | root: ownProps.rootMemberName 72 | })); 73 | } else { 74 | (dispatch(updateGui(ownProps.name.replace(/\:/g, "."), { 75 | value: value, 76 | root: ownProps.rootMemberName 77 | }))); 78 | } 79 | } 80 | }); 81 | 82 | const ConnectedCollection = connect( 83 | null, mapDispatchToProps 84 | )(ArrayInput); 85 | 86 | export default ConnectedCollection; 87 | -------------------------------------------------------------------------------- /src/app/reducers/logic/convert.js: -------------------------------------------------------------------------------- 1 | const isUndefined = v => typeof v === "undefined"; 2 | const findValueInJson = (object, path) => { 3 | if (object[path] !== undefined) { 4 | return object[path]; 5 | } 6 | 7 | const convertedPath = path.replace(/\[(\w+)\]/g, ".$1"); // convert indexes to properties (replace [] with .) 8 | const pathArray = convertedPath.replace(/^\./, "").split("."); // strip a leading dot 9 | let target = object; 10 | 11 | for (const k of pathArray) { 12 | if (k in target) { 13 | target = target[k]; 14 | } else { 15 | target = undefined; 16 | break; 17 | } 18 | } 19 | return target; 20 | }; 21 | 22 | 23 | export const convertColumnsToData = (columns) => { 24 | const newRows = []; 25 | let i; 26 | let j; 27 | let key; 28 | 29 | for (i = 0; i < columns.length; i++) { 30 | key = columns[i][0]; 31 | for (j = 1; j < columns[i].length; j++) { 32 | if (isUndefined(newRows[j - 1])) { 33 | newRows[j - 1] = {}; 34 | } 35 | if (isUndefined(columns[i][j])) { 36 | throw new Error(`Source data is missing a component at (${i}, ${j})!`); 37 | } 38 | newRows[j - 1][key] = columns[i][j]; 39 | } 40 | } 41 | return newRows; 42 | } 43 | 44 | export const convertJsonToData = (json, keys) => { 45 | const newRows = []; 46 | let targetKeys; 47 | let data; 48 | 49 | if (keys) { // when keys specified, json would be an array that includes objects 50 | if (keys.x) { 51 | targetKeys = keys.value.concat(keys.x); 52 | this.config.data_x = keys.x; 53 | } else { 54 | targetKeys = keys.value; 55 | } 56 | newRows.push(targetKeys); 57 | 58 | json.forEach(o => { 59 | const newRow = []; 60 | let v; 61 | 62 | for (const key of targetKeys) { 63 | // convert undefined to null because undefined data will be removed in convertDataToTargets() 64 | v = findValueInJson(o, key); 65 | if (isUndefined(v)) { 66 | v = null; 67 | } 68 | newRow.push(v); 69 | } 70 | newRows.push(newRow); 71 | }); 72 | data = convertRowsToData(newRows); 73 | } else { 74 | Object.keys(json).forEach(key => { 75 | const tmp = json[key].concat(); 76 | 77 | tmp.unshift(key); 78 | newRows.push(tmp); 79 | }); 80 | data = convertColumnsToData(newRows); 81 | } 82 | return data; 83 | }; 84 | 85 | export const convertRowsToData = (rows) => { 86 | const keys = rows[0]; 87 | const newRows = []; 88 | let newRow = {}; 89 | let i; 90 | let j; 91 | 92 | for (i = 1; i < rows.length; i++) { 93 | newRow = {}; 94 | for (j = 0; j < rows[i].length; j++) { 95 | if (isUndefined(rows[i][j])) { 96 | throw new Error(`Source data is missing a component at (${i}, ${j})!`); 97 | } 98 | newRow[keys[j]] = rows[i][j]; 99 | } 100 | newRows.push(newRow); 101 | } 102 | 103 | return newRows; 104 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "billboardjs-playground", 3 | "version": "0.7.0", 4 | "description": "playground for billboard.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npm run extract:document && webpack-dev-server --open", 8 | "build": "npm run extract:document && webpack --env production --config ./webpack.production.config.js", 9 | "extract:document": "echo module.exports = > src/app/configure/resources/document.js && jsdoc2md --json node_modules/billboard.js/src/config/Options.js >> src/app/configure/resources/document.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/naver/billboard.js-playground" 14 | }, 15 | "keywords": [ 16 | "billboard.js", 17 | "chart", 18 | "generator", 19 | "playground" 20 | ], 21 | "author": "joohwee.kim", 22 | "license": "MIT", 23 | "dependencies": { 24 | "billboard.js": "^1.2.0", 25 | "classnames": "^2.2.5", 26 | "codemirror": "^5.30.0", 27 | "create-react-class": "^15.6.2", 28 | "js-beautify": "^1.6.14", 29 | "lodash": "^4.17.4", 30 | "material-ui": "^0.19.1", 31 | "react": "^16.0.0", 32 | "react-dom": "^16.0.0", 33 | "react-redux": "^5.0.6", 34 | "react-watcher": "0.0.1", 35 | "redux": "^3.7.2" 36 | }, 37 | "devDependencies": { 38 | "babel-core": "^6.24.1", 39 | "babel-loader": "^6.4.1", 40 | "babel-plugin-add-module-exports": "^0.2.1", 41 | "babel-preset-es2015": "^6.24.1", 42 | "babel-preset-react": "^6.24.1", 43 | "clean-webpack-plugin": "^0.1.16", 44 | "css-loader": "^0.28.7", 45 | "eslint": "^3.19.0", 46 | "eslint-config-naver": "^1.0.0", 47 | "eslint-loader": "^1.7.1", 48 | "eslint-plugin-import": "^2.2.0", 49 | "eslint-plugin-jsx-a11y": "^5.0.1", 50 | "eslint-plugin-react": "^7.4.0", 51 | "extract-text-webpack-plugin": "^2.1.2", 52 | "file-loader": "^0.11.2", 53 | "jsdoc-to-markdown": "^3.0.3", 54 | "node-sass": "^4.5.2", 55 | "optimize-css-assets-webpack-plugin": "^1.3.0", 56 | "portscanner": "^2.1.1", 57 | "postcss": "^6.0.11", 58 | "postcss-cssnext": "^3.0.2", 59 | "postcss-load-config": "^1.2.0", 60 | "postcss-loader": "^2.0.6", 61 | "react-billboardjs": "git://github.com/kjhwee91/react-billboardjs.git", 62 | "react-hot-loader": "^3.0.0-beta.7", 63 | "sass-loader": "^6.0.6", 64 | "scss-loader": "0.0.1", 65 | "string-replace-webpack-plugin": "0.1.3", 66 | "style-loader": "^0.18.2", 67 | "svg-inline-loader": "^0.8.0", 68 | "svg-url-loader": "^2.1.1", 69 | "uglify-js": "^2.8.22", 70 | "uglifyjs-webpack-plugin": "^0.4.1", 71 | "url-loader": "^0.5.9", 72 | "webpack": "^2.6.0", 73 | "webpack-cleanup-plugin": "^0.5.1", 74 | "webpack-dev-server": "^2.4.2", 75 | "webpack-merge": "^4.1.0", 76 | "write-file-webpack-plugin": "^4.0.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/app/component/command/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import CodeMirror from "./codemirror"; 3 | import { connect } from "react-redux"; 4 | import { updateCommand, reflectCommandToDatatable } from "../../actions"; 5 | import ExportCode from "./exportcode"; 6 | var beautify_js = require('js-beautify').js_beautify; 7 | 8 | class Controller extends React.Component { 9 | constructor() { 10 | super(); 11 | 12 | this.state = { 13 | original: {}, 14 | error: false, 15 | text: "" 16 | }; 17 | 18 | this.onChangeText = this.onChangeText.bind(this); 19 | } 20 | 21 | componentWillMount() { 22 | this.setState({ 23 | fromData: this.props.fromData, 24 | original: this.props.original, 25 | text: this.props.text, 26 | focus: this.props.focus 27 | }); 28 | } 29 | 30 | componentWillReceiveProps(nextProps) { 31 | this.setState({ 32 | fromData: nextProps.fromData, 33 | original: nextProps.original, 34 | focus: nextProps.focus, 35 | text: nextProps.text 36 | }); 37 | } 38 | 39 | render() { 40 | const value = `${this.state.text}`; 41 | const options = { 42 | lineNumbers: true, 43 | mode: "javascript" 44 | }; 45 | 46 | const newLine = beautify_js(value); 47 | const className = "textConfigure" + " " + (this.state.error ? "err" : ""); 48 | 49 | return (
50 | 51 | this.onChangeText(v)} options={options} /> 52 |
); 53 | } 54 | 55 | getParsed(value) { 56 | let parsed; 57 | try { 58 | eval(`parsed = ${value}`); 59 | } catch (e) { 60 | parsed = null; 61 | } 62 | 63 | return parsed; 64 | } 65 | 66 | onChangeText(value) { 67 | let parsed = this.getParsed(value); 68 | 69 | if(parsed === null){ 70 | this.setState({ 71 | error: true, 72 | text: value 73 | }); 74 | } else { 75 | this.setState({ 76 | error: false, 77 | text: value, 78 | }); 79 | this.props.reflectCode({ value: parsed }); 80 | this.props.onChange({ 81 | prev: this.state.original, 82 | value: parsed 83 | }); 84 | } 85 | } 86 | } 87 | 88 | Controller.propTypes = { 89 | text: PropTypes.string.isRequired, 90 | onChange: PropTypes.func.isRequired 91 | }; 92 | 93 | const mapDispatchToProps = dispatch => ({ 94 | reflectCode: (value) => { 95 | dispatch(reflectCommandToDatatable(value)); 96 | }, 97 | onChange: value => { 98 | dispatch(updateCommand(value)) 99 | }, 100 | }); 101 | 102 | const mapStateToProps = state => ({ 103 | original: state.command.original, 104 | text: state.command.text, 105 | focus: state.command.focus 106 | }); 107 | 108 | const Command = connect(mapStateToProps, mapDispatchToProps)(Controller); 109 | 110 | export { 111 | Command 112 | }; 113 | export default Command; 114 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | billboard.js playground 2 | Copyright 2017 NAVER Corp. 3 | 4 | This project contains subcomponents with separate copyright notices and license terms. 5 | Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses. 6 | 7 | ===== 8 | 9 | JedWatson/react-codemirror from https://github.com/JedWatson/react-codemirror 10 | 11 | ===== 12 | 13 | The MIT License (MIT) 14 | 15 | Copyright (c) 2016 Jed Watson 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | 35 | ===== 36 | 37 | richclark/HTML5resetCSS from http://html5doctor.com/html-5-reset-stylesheet/ 38 | 39 | ===== 40 | 41 | Permission is hereby granted, free of charge, to any person obtaining a copy of any version of this file on Google Code or this file on Github (the “Software”), to deal in the Software under the terms of the CC0 Public Domain Dedication, or,alternatively, to deal in the Software under the terms of the MIT License. 42 | 43 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 46 | 47 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/app/component/gui/input/number.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { connect } from "react-redux"; 3 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 4 | import getMuiTheme from "material-ui/styles/getMuiTheme"; 5 | import Slider from "material-ui/Slider"; 6 | import * as color from "material-ui/styles/colors"; 7 | 8 | import { 9 | updateGui, resetGui 10 | } from "../../../actions"; 11 | import { 12 | deepCopy 13 | } from "../../../util"; 14 | 15 | 16 | const SliderStyleTheme = { 17 | trackColor: color.grey400, 18 | trackColorSelected: color.grey400, 19 | selectionColor: color.lightBlue300, 20 | handleColorZero: color.grey400, 21 | handleFillColor: color.grey100, 22 | rippleColor: color.lightBlue100 23 | }; 24 | 25 | const SliderStyle = { 26 | padding: 0, 27 | margin: 0 28 | }; 29 | const Style = { 30 | width: "60%", 31 | display: "inline-block" 32 | }; 33 | const ValueTextStyle = { 34 | width: "40%", 35 | display: "inline-block" 36 | }; 37 | 38 | class InputNumber extends React.Component { 39 | render() { 40 | const props = deepCopy({}, this.props); 41 | const textStyle = deepCopy({}, ValueTextStyle); 42 | 43 | props.min = props.min || 0; 44 | props.max = props.max || 100; 45 | textStyle.color = props.activated ? color.grey900 : color.grey400; 46 | 47 | if (props.docid && isNaN(props.value)) { 48 | props.value = props.defaultvalue || undefined; 49 | 50 | return (
51 | 52 | 63 | 64 | undefined 65 |
); 66 | } 67 | props.value *= 1; 68 | 69 | return (
70 | 71 | 82 | 83 | {props.value} 84 |
); 85 | } 86 | } 87 | 88 | const mapDispatchToProps = (dispatch, ownProps) => ({ 89 | onChange: (e, value) => { 90 | const name = ownProps.name.replace(/\:/g, "."); 91 | if (value === "") { 92 | dispatch(resetGui(name, { 93 | root: ownProps.rootMemberName 94 | })); 95 | } else { 96 | dispatch(updateGui(name, { 97 | root: ownProps.rootMemberName, 98 | value: value * 1 99 | })); 100 | } 101 | } 102 | }); 103 | 104 | const ConnectedNumber = connect( 105 | null, mapDispatchToProps 106 | )(InputNumber); 107 | 108 | export default ConnectedNumber; 109 | -------------------------------------------------------------------------------- /src/app/component/chart/billboard/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import connect from "react-watcher"; 3 | import * as bb from "billboard.js"; 4 | import CONFIGURE_PROPTYPES from "./proptypes"; 5 | import { deepCopy } from "./util"; 6 | 7 | class Billboard extends React.Component { 8 | componentWillMount() { 9 | this.instance = null; 10 | this.updated = false; 11 | this.setState(this.props); 12 | } 13 | 14 | componentDidMount() { 15 | this.watchConfig(); 16 | this.generateChart(); 17 | } 18 | 19 | componentDidUpdate() { 20 | this.updated = false; 21 | this.generateChart(); 22 | } 23 | 24 | componentWillReceiveProps(props) { 25 | this.setState(props); 26 | } 27 | 28 | shouldComponentUpdate() { 29 | const willRender = !this.updated; 30 | 31 | if (willRender) { 32 | this.destroy(); 33 | } 34 | return willRender; 35 | } 36 | 37 | componentWillUnmount() { 38 | this.destroy(); 39 | } 40 | 41 | watchConfig() { 42 | const { watch } = this.state; 43 | 44 | //watch("axis.y.labels", data => this.instance.axis.label({ y: data })); 45 | //watch("axis.x.categories", data => this.instance.categories(data)); 46 | //watch("legend.show", data => this.instance.legend.show(data)); 47 | //watch("legend.hide", data => this.instance.legend.hide(data)); 48 | //watch("regions", data => this.instance.regions(data)); 49 | //watch("size.width", data => this.instance.resize({ width: data })); 50 | //watch("size.height", data => this.instance.resize({ height: data })); 51 | //watch("grid.x.lines", data => this.instance.xgrids(data)); 52 | //watch("grid.y.lines", data => this.instance.ygrids(data)); 53 | //watch("zoom.enable", data => this.instance.zoom.enable(data)); 54 | //watch("zoom.domain", (data) => { 55 | // if (data === null) { 56 | // this.instance.unzoom(); 57 | // } else { 58 | // this.instance.zoom(); 59 | // } 60 | //}); 61 | //watch("data.columns", (data) => { 62 | // this.instance.load({ columns: data }); 63 | //}); 64 | 65 | //Object.keys(CONFIGURE_PROPTYPES).forEach((key) => { 66 | // watch(key, (data) => { 67 | // console.log(data); 68 | // }); 69 | //}); 70 | } 71 | 72 | getDispatchedValue(namespace, props) { 73 | const keys = namespace.split("."); 74 | let newProps = props; 75 | 76 | keys.every(key => { 77 | newProps = newProps[key]; 78 | if(newProps === undefined){ 79 | return false; 80 | } 81 | }); 82 | 83 | return newProps; 84 | } 85 | 86 | destroy() { 87 | this.instance && this.instance.destroy(); 88 | this.instance = null; 89 | } 90 | 91 | generateChart(mountNode = this.wrapper, config = this.props) { 92 | // using react node 93 | 94 | const newConfig = deepCopy({}, config); 95 | newConfig.bindto = mountNode; 96 | 97 | this.instance = bb.generate(newConfig); 98 | window.chart = this.instance; 99 | } 100 | 101 | render() { 102 | return (
{ this.wrapper = d; }} 105 | >
); 106 | } 107 | } 108 | 109 | Billboard.propTypes = CONFIGURE_PROPTYPES; 110 | 111 | export { 112 | Billboard 113 | }; 114 | Billboard = connect(Billboard); 115 | export default Billboard; 116 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const path = require("path"); 3 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 4 | const UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; 5 | 6 | const config = { 7 | context: `${__dirname}/`, 8 | entry: { 9 | app: path.resolve(__dirname, "./src/index.js") 10 | }, 11 | output: { 12 | path: path.join(__dirname, "/dist"), // 파일이 저장될 경로 13 | filename: "[name].bundle.js", 14 | publicPath: "/dist/", // 서버상의 경로 (ex. express.static) 15 | libraryTarget: "umd" 16 | }, 17 | module: { 18 | rules: [{ 19 | test: [/(\.js)$/, /(\.jsx)$/], 20 | exclude: /(node_modules)/, 21 | include: path.join(__dirname, "src"), 22 | loader: "babel-loader", 23 | query: { 24 | presets: ["es2015", "react"] 25 | } 26 | }, { 27 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 28 | loader: "url-loader?name=[name].[ext]&limit=10000&minetype=application/font-woff" 29 | }, { 30 | test: /\.svg$/, 31 | loader: "svg-url-loader?name=[name].[ext]" 32 | }, { 33 | test: /\.scss$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: [{ 36 | loader: "css-loader" 37 | }, { 38 | loader: "sass-loader" 39 | }], 40 | fallback: "style-loader" 41 | }) 42 | }, { 43 | test: /\.css$/, 44 | loader: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) 45 | }] 46 | }, 47 | resolve: { 48 | modules: [ 49 | "node_modules", 50 | path.resolve("./src") 51 | ], 52 | extensions: [".js", ".jsx"] 53 | }, 54 | externals: { 55 | "prop-types": { 56 | commonjs2: "prop-types", 57 | commonjs: "prop-types", 58 | amd: "prop-types", 59 | umd: "prop-types", 60 | root: "PropTypes" 61 | }, 62 | lodash: { 63 | commonjs2: "lodash", 64 | commonjs: "lodash", 65 | amd: "lodash", 66 | umd: "lodash", 67 | root: "_" 68 | }, 69 | d3: { 70 | root: "d3", 71 | commonjs2: "d3", 72 | commonjs: "d3", 73 | amd: "d3", 74 | umd: "d3" 75 | }, 76 | "billboard.js": { 77 | root: "bb", 78 | commonjs2: "billboard.js", 79 | commonjs: "billboard.js", 80 | amd: "billboard.js", 81 | umd: "billboard.js" 82 | }, 83 | react: { 84 | root: "React", 85 | commonjs2: "react", 86 | commonjs: "react", 87 | amd: "react", 88 | umd: "react" 89 | }, 90 | redux: { 91 | root: "Redux", 92 | commonjs2: "redux", 93 | commonjs: "redux", 94 | amd: "redux", 95 | umd: "redux" 96 | }, 97 | "react-redux": { 98 | root: "ReactRedux", 99 | commonjs2: "react-redux", 100 | commonjs: "react-redux", 101 | amd: "react-redux", 102 | umd: "react-redux" 103 | }, 104 | "react-dom": { 105 | root: "ReactDOM", 106 | commonjs2: "react-dom", 107 | commonjs: "react-dom", 108 | amd: "react-dom", 109 | umd: "react-dom" 110 | } 111 | }, 112 | devtool: "cheap-eval-source-map", 113 | devServer: { 114 | contentBase: `${__dirname}/`, 115 | hot: true, 116 | inline: true 117 | }, 118 | plugins: [ 119 | new webpack.ProvidePlugin({ 120 | d3: "d3", 121 | $: "jquery", 122 | jQuery: "jquery" 123 | }), 124 | new ExtractTextPlugin("app.bundle.css"), 125 | new webpack.HotModuleReplacementPlugin(), 126 | new UglifyJsPlugin({ 127 | drop_console: true, 128 | sourceMap: true 129 | }) 130 | ] 131 | }; 132 | 133 | module.exports = config; 134 | -------------------------------------------------------------------------------- /src/app/actions/index.js: -------------------------------------------------------------------------------- 1 | export const UPDATE_GUI = "UPDATE_GUI"; 2 | export const UPDATE_COMMAND = "UPDATE_COMMAND"; 3 | export const RESET_GUI = "RESET_GUI"; 4 | export const CHANGE_GUI_ACTIVATE = "CHANGE_GUI_ACTIVATE"; 5 | export const UPDATE_DATA = "UPDATE_DATA"; 6 | export const UPDATE_CODE_INPUT = "UPDATE_CODE_INPUT"; 7 | export const RECENT_CONFIGURE = "RECENT_CONFIGURE"; 8 | export const ADD_KEY_FROM_TABLE = "ADD_KEY_FROM_TABLE"; 9 | export const ADD_VALUE_FROM_TABLE = "ADD_VALUE_FROM_TABLE"; 10 | export const REMOVE_VALUE_FROM_TABLE = "REMOVE_VALUE_FROM_TABLE"; 11 | export const REMOVE_KEY_FROM_TABLE = "REMOVE_KEY_FROM_DATA"; 12 | export const REFLECTED_DATA = "REFLECTED_DATA"; 13 | export const UPDATE_TABLE_KEY = "UPDATE_TABLE_KEY"; 14 | export const UPDATE_TABLE_VALUE = "UPDATE_TABLE_VALUE"; 15 | export const REFLECT_CODE_TO_DATATABLE = "REFLECT_CODE_TO_DATATABLE"; 16 | export const UPDATE_CONFIGURE_INFO = "UPDATE_CONFIGURE_INFO"; 17 | export const SHOW_GUIDE_CARD = "SHOW_GUIDE_CARD"; 18 | export const HIDE_GUIDE_CARD = "HIDE_GUIDE_CARD"; 19 | 20 | export const hideGuideCard = () => { 21 | return { 22 | type: HIDE_GUIDE_CARD 23 | } 24 | 25 | }; 26 | export const showGuideCard = (style) => { 27 | return { 28 | type: SHOW_GUIDE_CARD, 29 | style, 30 | } 31 | } 32 | 33 | export const updateConfigureInfo = (name) => { 34 | return { 35 | type: UPDATE_CONFIGURE_INFO, 36 | name 37 | } 38 | } 39 | 40 | 41 | export const updateHeader = (updateData) => { 42 | return { 43 | type: UPDATE_TABLE_KEY, 44 | data: updateData 45 | }; 46 | }; 47 | 48 | export const updateCell = (updateData) => { 49 | return { 50 | type: UPDATE_TABLE_VALUE, 51 | data: updateData 52 | }; 53 | }; 54 | 55 | export const reflectedDataToCommand = (latestData) => { 56 | return { 57 | type: REFLECTED_DATA, 58 | data: latestData 59 | } 60 | } 61 | 62 | export const removeValueFromTable = (index) => { 63 | return { 64 | type: REMOVE_VALUE_FROM_TABLE, 65 | data: index 66 | }; 67 | }; 68 | 69 | export const removeKeyFromTable = (key) => { 70 | return { 71 | type: REMOVE_KEY_FROM_TABLE, 72 | data: key 73 | }; 74 | }; 75 | 76 | export const addValueToData = (newData) => { 77 | return { 78 | type: ADD_VALUE_FROM_TABLE, 79 | data: newData 80 | }; 81 | } 82 | export const addKeyToData = (newData) => { 83 | return { 84 | type: ADD_KEY_FROM_TABLE, 85 | data: newData 86 | }; 87 | }; 88 | 89 | export const updateCodeInput = (name, state) => { 90 | return { 91 | type: UPDATE_CODE_INPUT, 92 | name, 93 | }; 94 | }; 95 | 96 | export const updateData = (name, value, info) => { 97 | return { 98 | type: UPDATE_DATA, 99 | name, 100 | value, 101 | info 102 | }; 103 | }; 104 | export const recentConfigureUpdate = (configure) => ({ 105 | type: RECENT_CONFIGURE, 106 | configure 107 | }) 108 | 109 | export const changeGuiActivate = (name, value) => ({ 110 | type: CHANGE_GUI_ACTIVATE, 111 | value: value.value, 112 | root: value.root, 113 | name 114 | }); 115 | 116 | 117 | export const resetGui = (name, updated) => { 118 | return { 119 | type: RESET_GUI, 120 | updated, 121 | name 122 | }; 123 | }; 124 | export const updateGui = (name, updated) => ({ 125 | type: UPDATE_GUI, 126 | name, 127 | updated 128 | }); 129 | 130 | export const updateCommand = updated => ({ 131 | type: UPDATE_COMMAND, 132 | updated 133 | }); 134 | 135 | export const reflectCommandToDatatable = (updated) => { 136 | return { 137 | type: REFLECT_CODE_TO_DATATABLE, 138 | updated 139 | }; 140 | }; 141 | -------------------------------------------------------------------------------- /src/app/configure/index.js: -------------------------------------------------------------------------------- 1 | import * as rawDocument from "./resources/document"; 2 | import * as presetDocument from "./resources/preset"; 3 | import { deepCopy, typeValid } from "../util"; 4 | import * as template from "./resources/template"; 5 | const keysFromDocument = []; 6 | 7 | const getDefaultAttributes = (name) => { 8 | return { 9 | description: "", 10 | optional: true, 11 | activated: false, 12 | name, 13 | value: undefined, 14 | defaultvalue: undefined, 15 | type: { 16 | names: ["Object"] 17 | }, 18 | }; 19 | }; 20 | 21 | const fillDefaultAttributes = (target, root) => { 22 | target.attributes = target.attributes || getDefaultAttributes(root); 23 | if(target.properties){ 24 | _.each(target.properties, (obj, key) => { 25 | target.properties[key] = fillDefaultAttributes(obj, `${root}.${key}`); 26 | }); 27 | } 28 | return target; 29 | }; 30 | 31 | const memberFlatten = (member) => { 32 | let newArray = [member]; 33 | if (member.properties) { 34 | _.each(member.properties, (item) => { 35 | if(item.properties){ 36 | newArray = newArray.concat(memberFlatten(item)); 37 | } else { 38 | const value = typeValid(item.type.names.map(type => (type.toLowerCase())), item.defaultvalue); 39 | newArray.push({ 40 | type: item.type, 41 | defaultvalue: value, 42 | value: value, 43 | name: item.name, 44 | description: item.description, 45 | optional: item.optional, 46 | activated: false, 47 | examples: item.examples, 48 | docid: item.name, 49 | }); 50 | } 51 | }); 52 | } 53 | 54 | return newArray; 55 | }; 56 | 57 | 58 | const documentToObject = (defaultDocumentOption) => { 59 | let fullProperties = []; 60 | 61 | _.each(defaultDocumentOption, ({ id, examples, name, type, kind, defaultvalue, description, properties, optional }) => { 62 | if (kind === "member") { 63 | if (name.indexOf("․") > -1) { 64 | const target = { 65 | type: type, 66 | defaultvalue: defaultvalue, 67 | value: defaultvalue, 68 | docid: name, 69 | name: name.replace(/\․/g, "."), 70 | description: description, 71 | optional: optional, 72 | activated: false, 73 | examples, 74 | }; 75 | 76 | fullProperties = fullProperties.concat([target]); 77 | } else { 78 | const ps = memberFlatten({ 79 | value: defaultvalue, 80 | defaultvalue, 81 | properties, 82 | type, 83 | name, 84 | description, 85 | kind, 86 | activated: false, 87 | examples, 88 | docid: name, 89 | }); 90 | fullProperties = fullProperties.concat(ps); 91 | } 92 | } 93 | }); 94 | 95 | let member = {}; 96 | 97 | _.each(fullProperties, (p) => { 98 | const keys = p.name.split("."); 99 | let copy = { 100 | attributes: p 101 | }; 102 | _.each(keys.reverse(), (key) => { 103 | const newCopy = { 104 | properties: {} 105 | }; 106 | 107 | newCopy.properties[key] = copy; 108 | copy = newCopy; 109 | }); 110 | member = deepCopy(member, copy); 111 | keysFromDocument.push(p.name); 112 | }); 113 | 114 | const newObj = {}; 115 | _.each(Object.keys(member.properties), (memberKey) => { 116 | let target = member.properties[memberKey]; 117 | target.kind = "member"; 118 | target = fillDefaultAttributes(target, memberKey); 119 | newObj[memberKey] = deepCopy({}, target, presetDocument[memberKey]); 120 | }); 121 | 122 | return newObj; 123 | }; 124 | 125 | const getFormattedDocument = () => { 126 | return documentToObject(rawDocument); 127 | }; 128 | 129 | const formattedDocument = getFormattedDocument(); 130 | const initConfigure = template.line1; 131 | 132 | export { 133 | rawDocument as documentFromJSDoc, 134 | presetDocument as formattedCustomDocumnet, 135 | formattedDocument, 136 | initConfigure 137 | }; 138 | -------------------------------------------------------------------------------- /src/app/util/index.js: -------------------------------------------------------------------------------- 1 | export const namespaceToObject = (ns, lastValue, obj = {}) => { 2 | const keys = ns.split("."); 3 | obj[keys.pop()] = lastValue; 4 | 5 | keys.reverse().forEach(key => { 6 | const newObj = {}; 7 | newObj[key] = obj; 8 | obj = newObj; 9 | }); 10 | 11 | return obj; 12 | }; 13 | 14 | export const deepCopy = (target, ...sources) => { 15 | const isObject = (item) =>{ 16 | return (item && typeof item === "object" && !Array.isArray(item)); 17 | }; 18 | 19 | if (!sources.length) return target; 20 | const source = sources.shift(); 21 | 22 | if (isObject(target) && isObject(source)) { 23 | for (const key in source) { 24 | if (isObject(source[key])) { 25 | if (!target[key]) Object.assign(target, { [key]: {} }); 26 | deepCopy(target[key], source[key]); 27 | } else { 28 | Object.assign(target, { [key]: source[key] }); 29 | } 30 | } 31 | } 32 | 33 | return deepCopy(target, ...sources); 34 | }; 35 | 36 | export const stringToFunction = (str) =>{ 37 | let code = str; 38 | eval(`code = ${code}`); 39 | return code; 40 | } 41 | 42 | export const objectFlatten = (obj) => { 43 | var flattenObject = function(ob) { 44 | var toReturn = {}; 45 | 46 | for (var i in ob) { 47 | if (!ob.hasOwnProperty(i)) continue; 48 | 49 | if ((typeof ob[i]) == 'object' && !Array.isArray(ob[i])) { 50 | var flatObject = flattenObject(ob[i]); 51 | for (var x in flatObject) { 52 | if (!flatObject.hasOwnProperty(x)) continue; 53 | 54 | toReturn[i + '.' + x] = flatObject[x]; 55 | } 56 | } else { 57 | toReturn[i] = ob[i]; 58 | } 59 | } 60 | return toReturn; 61 | }; 62 | 63 | 64 | return flattenObject(obj); 65 | }; 66 | 67 | 68 | export const hasProperty = (name) => { 69 | const keys = name.split("."); 70 | let configure = initDocumentConfigure; 71 | let attr = {}; 72 | 73 | _.each(keys, (key) => { 74 | if(configure !== undefined){ 75 | const cof = configure[key] || {}; 76 | 77 | attr = cof.attributes; 78 | configure = cof.properties; 79 | } else { 80 | attr = undefined; 81 | } 82 | }); 83 | 84 | return !!attr; 85 | }; 86 | 87 | 88 | export const objectToKeys = (obj, root = "") => { 89 | const keys = _.keys(obj); 90 | const arr = []; 91 | 92 | if(keys.length > 0){ 93 | _.each(keys, key => { 94 | const parent = root == "" ? key : root + "." + key; 95 | if(hasProperty(parent)){ 96 | arr.push(objectToKeys(obj[key], parent)); 97 | } else { 98 | arr.indexOf(root) < 0 && arr.push(root); 99 | } 100 | }); 101 | } else { 102 | arr.push(root); 103 | } 104 | 105 | return _.flatten(arr); 106 | }; 107 | 108 | 109 | export const strinifyContainsFunction = (obj) => { 110 | function replacer(key, value) { 111 | if(typeof value == "function"){ 112 | return `___codestart${value}codeend___`; 113 | } 114 | return value; 115 | } 116 | var text = JSON.stringify(obj, replacer); 117 | text = text.replace(/\"\_\_\_codestart/g, "").replace(/codeend\_\_\_\"/g, ""); 118 | 119 | return text; 120 | } 121 | 122 | 123 | export const typeValid = (types, value) => { 124 | if(value === undefined || value === "undefined"){ 125 | return undefined; 126 | } 127 | 128 | if(types.indexOf("number") > -1) { 129 | if(isNaN(value)){ 130 | //console.log("TODO => " + value); 131 | return value; 132 | } else { 133 | return value*1; 134 | } 135 | } 136 | 137 | if(types.indexOf("array") > -1){ 138 | const array = JSON.parse(value+=""); 139 | return array; 140 | } 141 | 142 | if(types.indexOf("function") > -1){ 143 | return value; 144 | } 145 | 146 | if(types.indexOf("boolean") > -1){ 147 | return value; 148 | } 149 | 150 | if(types.indexOf("string") > -1){ 151 | return value; 152 | } 153 | 154 | if(types.indexOf("object") > -1){ 155 | return value; 156 | } 157 | 158 | return value; 159 | 160 | }; -------------------------------------------------------------------------------- /src/app/component/gui/property.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { connect } from "react-redux"; 3 | import getMuiTheme from "material-ui/styles/getMuiTheme"; 4 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 5 | import { ListItem } from "material-ui/List"; 6 | import FontIcon from "material-ui/FontIcon"; 7 | import Clear from "./icon/clear"; 8 | import { 9 | ConnectedCollection, 10 | ConnectedCheckbox, 11 | ConnectedNumber, 12 | ConnectedText, 13 | ConnectedCode 14 | } from "./input/index"; 15 | import { 16 | resetGui, updateConfigureInfo, showGuideCard 17 | } from "../../actions"; 18 | import * as color from "material-ui/styles/colors"; 19 | 20 | 21 | class InputProperty extends React.Component { 22 | componentWillMount() { 23 | this.setState(this.props); 24 | } 25 | 26 | componentWillReceiveProps(nextProps) { 27 | this.setState(nextProps); 28 | } 29 | 30 | getName() { 31 | return (
36 |
37 |
{this.state.name} 38 | this.onClickTitle()} className="material-icons property_info">info_outline 39 |
40 |
{this.state.type.names.join(", ")}
41 |
42 |
); 43 | } 44 | 45 | getInput(type) { 46 | return (
51 |
52 | {InputProperty.getInputType(type, this.state)} 53 |
54 |
); 55 | } 56 | 57 | onClickTitle() { 58 | this.props.onClickTitle(this.state.name); 59 | } 60 | 61 | getClear() { 62 | return (
69 |
74 | 75 |
76 |
); 77 | } 78 | 79 | render() { 80 | const option = this.state; 81 | const type = option.type ? option.type.names.join(", ") : ""; 82 | 83 | const title = this.getName(); 84 | const input = this.getInput(type); 85 | const clear = this.getClear(); 86 | const className = `property ${(option.activated ? " activated" : "")}`; 87 | 88 | if(this.state.hidegui){ 89 | return ; 90 | }; 91 | 92 | 93 | return ( 94 | 100 | ); 101 | } 102 | 103 | onClickDelete(e) { 104 | this.props.onClickDelete(e); 105 | } 106 | 107 | static getInputType(type, option) { 108 | 109 | switch (type.toLocaleLowerCase()) { 110 | case "boolean" : 111 | return ; 112 | case "string" : 113 | return ; 114 | case "number" : 115 | return ; 116 | case "array" : 117 | return ""; //; 118 | case "function" : 119 | return ; 120 | default : 121 | return ""; 122 | } 123 | } 124 | } 125 | 126 | const mapDispatchToProps = (dispatch, ownProps) => ({ 127 | onClickTitle : (name) => { 128 | dispatch(updateConfigureInfo(name)); 129 | dispatch(showGuideCard(name)); 130 | }, 131 | onClickDelete: () => { 132 | dispatch(resetGui(ownProps.name.replace(/\:/g, "."), { 133 | root: ownProps.rootMemberName 134 | })) 135 | } 136 | }); 137 | 138 | const Property = connect( 139 | null, mapDispatchToProps 140 | )(InputProperty); 141 | 142 | export default Property; 143 | -------------------------------------------------------------------------------- /src/style/codepen-export.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/app/component/guide/card.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Card, CardActions, CardHeader, CardMedia, CardTitle, CardText} from 'material-ui/Card'; 3 | import FlatButton from 'material-ui/FlatButton'; 4 | import Toggle from 'material-ui/Toggle'; 5 | import Chip from 'material-ui/Chip'; 6 | import FontIcon from 'material-ui/FontIcon'; 7 | import { connect } from "react-redux"; 8 | import { 9 | showGuideCard, resetGui, updateConfigureInfo, hideGuideCard 10 | } from "../../actions"; 11 | import CodeMirror from "../command/codemirror"; 12 | import * as color from 'material-ui/styles/colors'; 13 | 14 | const styles = { 15 | chip: { 16 | display: "inline-block", 17 | paddingBottom: "8px", 18 | paddingRight: "8px" 19 | }, 20 | wrapper: { 21 | display: "flex", 22 | flexWrap: "wrap" 23 | }, 24 | 25 | cardHeader: { 26 | color: color.grey800, 27 | padding: 0 28 | }, 29 | 30 | cardText: { 31 | padding: "0", 32 | margin: "0 16px", 33 | marginBottom: "20px", 34 | borderTop : "1px solid #E0E0E0" 35 | } 36 | }; 37 | 38 | class PropertyCard extends React.Component { 39 | 40 | componentDidMount() { 41 | PR && PR.prettyPrint(); 42 | } 43 | 44 | componentDidUpdate() { 45 | if(this.pp){ 46 | this.pp.className = "prettyprint"; 47 | PR && PR.prettyPrint(); 48 | } 49 | } 50 | 51 | onClickOpenDoc() { 52 | const url = `https://naver.github.io/billboard.js/release/latest/doc/Options.html#.${this.props.attributes.docid}`; 53 | 54 | window.open(url); 55 | } 56 | 57 | getActiveOption() { 58 | const closeIcon = ( this.props.onHideCard()} className="material-icons">close); 59 | const chip = this.props.chip; 60 | return ( 61 |
66 | {chip.map(({name, optional}) => { 67 | if(optional){ 68 | return
69 | this.props.onClickChip(name)} onRequestDelete={() => this.props.onClickDelete(name)}>{name} 70 |
71 | } else { 72 | return
73 | this.props.onClickChip(name)}>{name} 74 |
75 | } 76 | 77 | })} 78 |
79 |
84 | {closeIcon} 85 |
86 |
); 87 | } 88 | 89 | getExample() { 90 | const example = this.props.attributes.examples; 91 | 92 | return example ? ( 93 |
example
94 |
 {this.pp = p;}} className="prettyprint">{example}
95 |
) : ""; 96 | } 97 | 98 | getDescription() { 99 | return ( 100 |
description
101 |
{this.props.attributes.description}
102 |
); 103 | } 104 | 105 | render() { 106 | const openDocIcon = ( this.onClickOpenDoc()} className="material-icons">open_in_new); 107 | const attr = this.props.attributes; 108 | const name = attr.name; 109 | const type = attr.type.names.join(" | "); 110 | const defaultvalue = attr.defaultvalue; 111 | 112 | return ( 113 |
{ this.wrapper = d; }}> 114 | 115 | {this.getActiveOption()} 116 | 122 | 123 | 124 | {this.getDescription()} 125 | {this.getExample()} 126 | 127 |
128 | ); 129 | } 130 | 131 | 132 | } 133 | 134 | const mapStateToProps = state => ({ 135 | chip: state.command.chip, 136 | name: state.guide.name, 137 | attributes: state.guide.attributes, 138 | }); 139 | 140 | const mapDispatchToProps = (dispatch, ownProps) => ({ 141 | onHideCard: () => { 142 | dispatch(hideGuideCard()); 143 | }, 144 | onClickChip : (name) => { 145 | dispatch(updateConfigureInfo(name)); 146 | }, 147 | onClickDelete: (name) => { 148 | dispatch(resetGui(name)); 149 | }, 150 | onUpdateCard : (heightStyle) => { 151 | dispatch(showGuideCard(heightStyle)); 152 | } 153 | }); 154 | 155 | const DocumentCard = connect( 156 | mapStateToProps, mapDispatchToProps 157 | )(PropertyCard); 158 | 159 | 160 | export { 161 | DocumentCard 162 | } 163 | export default DocumentCard; 164 | 165 | 166 | -------------------------------------------------------------------------------- /src/app/component/command/codemirror.js: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Jed Watson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | **/ 24 | 25 | 'use strict'; 26 | 27 | var React = require('react'); 28 | var ReactDOM = require('react-dom'); 29 | var PropTypes = require('prop-types'); 30 | var className = require('classnames'); 31 | var debounce = require('lodash').debounce; 32 | var isEqual = require('lodash').isEqual; 33 | var createReactClass = require('create-react-class'); 34 | 35 | function normalizeLineEndings(str) { 36 | if (!str) return str; 37 | return str.replace(/\r\n|\r/g, '\n'); 38 | } 39 | 40 | var CodeMirror = createReactClass({ 41 | propTypes: { 42 | autoFocus: PropTypes.bool, 43 | className: PropTypes.any, 44 | codeMirrorInstance: PropTypes.func, 45 | defaultValue: PropTypes.string, 46 | name: PropTypes.string, 47 | onChange: PropTypes.func, 48 | onCursorActivity: PropTypes.func, 49 | onFocusChange: PropTypes.func, 50 | onScroll: PropTypes.func, 51 | options: PropTypes.object, 52 | path: PropTypes.string, 53 | value: PropTypes.string, 54 | preserveScrollPosition: PropTypes.bool 55 | }, 56 | getDefaultProps: function getDefaultProps() { 57 | return { 58 | preserveScrollPosition: false 59 | }; 60 | }, 61 | getCodeMirrorInstance: function getCodeMirrorInstance() { 62 | return this.props.codeMirrorInstance || require('codemirror'); 63 | }, 64 | getInitialState: function getInitialState() { 65 | return { 66 | isFocused: false 67 | }; 68 | }, 69 | componentWillMount: function componentWillMount() { 70 | this.componentWillReceiveProps = debounce(this.componentWillReceiveProps, 0); 71 | if (this.props.path) { 72 | console.error('Warning: react-codemirror: the `path` prop has been changed to `name`'); 73 | } 74 | }, 75 | componentDidMount: function componentDidMount() { 76 | var codeMirrorInstance = this.getCodeMirrorInstance(); 77 | this.codeMirror = codeMirrorInstance.fromTextArea(this.textareaNode, this.props.options); 78 | 79 | this.codeMirror.on('change', this.codemirrorValueChanged); 80 | this.codeMirror.on('cursorActivity', this.cursorActivity); 81 | this.codeMirror.on('focus', this.focusChanged.bind(this, true)); 82 | this.codeMirror.on('blur', this.focusChanged.bind(this, false)); 83 | this.codeMirror.on('scroll', this.scrollChanged); 84 | this.codeMirror.setValue(this.props.defaultValue || this.props.value || ''); 85 | }, 86 | componentWillUnmount: function componentWillUnmount() { 87 | // is there a lighter-weight way to remove the cm instance? 88 | if (this.codeMirror) { 89 | this.codeMirror.toTextArea(); 90 | } 91 | }, 92 | componentWillReceiveProps: function componentWillReceiveProps(nextProps) { 93 | if (this.codeMirror && nextProps.value !== undefined && nextProps.value !== this.props.value && normalizeLineEndings(this.codeMirror.getValue()) !== normalizeLineEndings(nextProps.value)) { 94 | if (this.props.preserveScrollPosition) { 95 | var prevScrollPosition = this.codeMirror.getScrollInfo(); 96 | this.codeMirror.setValue(nextProps.value); 97 | this.codeMirror.scrollTo(prevScrollPosition.left, prevScrollPosition.top); 98 | } else { 99 | this.codeMirror.setValue(nextProps.value); 100 | } 101 | } else { 102 | if(this.state.origin === "setValue" || this.state.isFocused === false){ 103 | this.codeMirror.setValue(nextProps.value); 104 | } 105 | } 106 | if (typeof nextProps.options === 'object') { 107 | for (var optionName in nextProps.options) { 108 | if (nextProps.options.hasOwnProperty(optionName)) { 109 | this.setOptionIfChanged(optionName, nextProps.options[optionName]); 110 | } 111 | } 112 | } 113 | }, 114 | setOptionIfChanged: function setOptionIfChanged(optionName, newValue) { 115 | var oldValue = this.codeMirror.getOption(optionName); 116 | if (!isEqual(oldValue, newValue)) { 117 | this.codeMirror.setOption(optionName, newValue); 118 | } 119 | }, 120 | getCodeMirror: function getCodeMirror() { 121 | return this.codeMirror; 122 | }, 123 | focus: function focus() { 124 | if (this.codeMirror) { 125 | this.codeMirror.focus(); 126 | } 127 | }, 128 | focusChanged: function focusChanged(focused) { 129 | this.setState({ 130 | isFocused: focused 131 | }); 132 | this.props.onFocusChange && this.props.onFocusChange(focused); 133 | }, 134 | cursorActivity: function cursorActivity(cm) { 135 | this.props.onCursorActivity && this.props.onCursorActivity(cm); 136 | }, 137 | scrollChanged: function scrollChanged(cm) { 138 | this.props.onScroll && this.props.onScroll(cm.getScrollInfo()); 139 | }, 140 | codemirrorValueChanged: function codemirrorValueChanged(doc, change) { 141 | this.setState({ 142 | origin: change.origin 143 | }); 144 | if (this.props.onChange && change.origin !== 'setValue') { 145 | this.props.onChange(doc.getValue(), change); 146 | } 147 | }, 148 | render: function render() { 149 | var _this = this; 150 | 151 | var editorClassName = className('ReactCodeMirror', this.state.isFocused ? 'ReactCodeMirror--focused' : null, this.props.className); 152 | return React.createElement( 153 | 'div', 154 | { className: editorClassName }, 155 | React.createElement('textarea', { 156 | ref: function (ref) { 157 | return _this.textareaNode = ref; 158 | }, 159 | name: this.props.name || this.props.path, 160 | value: this.props.value, 161 | onChange: this.props.onChange, 162 | autoComplete: 'off', 163 | autoFocus: this.props.autoFocus 164 | }) 165 | ); 166 | } 167 | }); 168 | 169 | module.exports = CodeMirror; 170 | -------------------------------------------------------------------------------- /src/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import { 3 | namespaceToObject, 4 | deepCopy, 5 | objectFlatten 6 | } from "../util"; 7 | 8 | import { 9 | formattedDocument as initDocument, 10 | initConfigure 11 | } from "../configure"; 12 | 13 | import { 14 | HIDE_GUIDE_CARD, 15 | SHOW_GUIDE_CARD, 16 | UPDATE_GUI, 17 | RESET_GUI, 18 | REFLECTED_DATA, 19 | UPDATE_CODE_INPUT, 20 | UPDATE_COMMAND, 21 | UPDATE_CONFIGURE_INFO, 22 | UPDATE_TABLE_KEY, 23 | UPDATE_TABLE_VALUE, 24 | REMOVE_VALUE_FROM_TABLE, 25 | REMOVE_KEY_FROM_TABLE, 26 | ADD_VALUE_FROM_TABLE, 27 | ADD_KEY_FROM_TABLE 28 | } from "../actions"; 29 | 30 | import { 31 | getDefaultValue, 32 | updateGuiState, 33 | changeDefaultGuiState, 34 | updateGuiActivate, 35 | resetDataState, 36 | getChip, 37 | removeValueToDataState, 38 | removeKeyToDataState, 39 | addValueToDataTable, 40 | addKeyToDataTable, 41 | updateDataKeyState, 42 | updateDataValueState, 43 | updateCommandByGUI, 44 | getRemovedToDefault, 45 | updateCommandData, 46 | updateCommandCode, 47 | resetCommandState, 48 | updateCommandState, 49 | deleteTargetCommandState, 50 | getAttributesFromDocument, 51 | convertData 52 | } from "./logic"; 53 | 54 | 55 | /** state variable **/ 56 | let guiState = initDocument; 57 | let dataState = convertData(initConfigure.data); 58 | let commandState = { 59 | original: initConfigure, 60 | text: JSON.stringify(initConfigure), 61 | chip: getChip(Object.keys(objectFlatten(initConfigure))) 62 | }; 63 | let guideState = { 64 | open: false, 65 | name: null, 66 | attributes: getAttributesFromDocument("bindto") 67 | }; 68 | 69 | 70 | /** gui action handler **/ 71 | const gui = (state = guiState, action) => { 72 | let returnState = {}; 73 | 74 | switch (action.type) { 75 | case UPDATE_GUI : { 76 | const latest = namespaceToObject(action.name, action.updated.value); 77 | 78 | returnState = updateGuiState(state, latest); 79 | 80 | returnState.lastUpdateRoot = [action.name.split(".")[0]]; 81 | returnState.lastUpdate = (new Date()).getTime(); 82 | 83 | break; 84 | } 85 | case RESET_GUI : { 86 | const value = getDefaultValue(action.name); 87 | const latest = namespaceToObject(action.name, value); 88 | 89 | returnState = updateGuiState(state, latest); 90 | 91 | returnState.lastUpdateRoot = [action.name.split(".")[0]]; 92 | returnState.lastUpdate = (new Date()).getTime(); 93 | 94 | break; 95 | } 96 | 97 | case UPDATE_CODE_INPUT : { 98 | returnState = updateGuiActivate(state, action.name); 99 | 100 | returnState.lastUpdateRoot = [action.name.split(".")[0]]; 101 | returnState.lastUpdate = (new Date()).getTime(); 102 | 103 | break; 104 | } 105 | 106 | case UPDATE_COMMAND : { 107 | const prev = action.updated.prev; 108 | const current = action.updated.value; 109 | const resets = getRemovedToDefault(prev, current); 110 | 111 | returnState = updateGuiState(state, current); 112 | returnState = updateGuiState(returnState, resets); 113 | 114 | returnState.lastUpdateRoot = Object.keys(current).concat(Object.keys(resets)); 115 | returnState.lastUpdate = (new Date()).getTime(); 116 | 117 | break; 118 | } 119 | default : 120 | returnState = state; 121 | } 122 | 123 | guiState = returnState; 124 | return returnState; 125 | }; 126 | 127 | const command = (state = commandState, action) => { 128 | let returnState; 129 | 130 | switch (action.type) { 131 | case UPDATE_GUI : { 132 | returnState = updateCommandByGUI(state, action.name, action.updated.value); 133 | returnState.chip = getChip(Object.keys(objectFlatten(returnState.original))); 134 | returnState.lastUpdate = new Date(); 135 | break; 136 | } 137 | 138 | case RESET_GUI : { 139 | returnState = deleteTargetCommandState(state, action.name); 140 | returnState.chip = getChip(Object.keys(objectFlatten(returnState.original))); 141 | returnState.lastUpdate = new Date(); 142 | break; 143 | } 144 | 145 | case UPDATE_CODE_INPUT : { 146 | returnState = updateCommandCode(state, action.name); 147 | returnState.chip = getChip(Object.keys(objectFlatten(returnState.original))); 148 | returnState.lastUpdate = new Date(); 149 | break; 150 | } 151 | 152 | case UPDATE_COMMAND : { 153 | returnState = resetCommandState(action.updated.value); 154 | returnState.chip = getChip(Object.keys(objectFlatten(returnState.original))); 155 | returnState.lastUpdate = new Date(); 156 | break; 157 | } 158 | 159 | case REFLECTED_DATA : { 160 | const lastedData = deepCopy({}, action.data); 161 | returnState = updateCommandData(state, lastedData); 162 | returnState.chip = getChip(Object.keys(objectFlatten(returnState.original))); 163 | returnState.fromData = true; 164 | returnState.lastUpdate = new Date() 165 | break; 166 | } 167 | 168 | default : 169 | returnState = state; 170 | } 171 | 172 | 173 | commandState = returnState; 174 | 175 | return returnState; 176 | }; 177 | 178 | 179 | const data = (state = dataState, action) => { 180 | let returnState; 181 | 182 | switch (action.type) { 183 | case UPDATE_COMMAND : { 184 | const newData = convertData(action.updated.value.data); 185 | returnState = resetDataState(state, newData); 186 | returnState.lastUpdate = new Date(); 187 | 188 | break; 189 | } 190 | case UPDATE_TABLE_VALUE : { 191 | returnState = updateDataValueState(state, action.data); 192 | returnState.lastUpdate = new Date(); 193 | 194 | break; 195 | } 196 | case UPDATE_TABLE_KEY : { 197 | returnState = updateDataKeyState(state, action.data); 198 | returnState.lastUpdate = new Date(); 199 | 200 | break; 201 | } 202 | case REMOVE_VALUE_FROM_TABLE : { 203 | returnState = removeValueToDataState(state, action.data); 204 | returnState.lastUpdate = new Date(); 205 | 206 | break; 207 | } 208 | case REMOVE_KEY_FROM_TABLE : { 209 | returnState = removeKeyToDataState(state, action.data); 210 | returnState.lastUpdate = new Date(); 211 | 212 | break; 213 | } 214 | case ADD_VALUE_FROM_TABLE : { 215 | returnState = addValueToDataTable(state, action.data); 216 | returnState.lastUpdate = new Date(); 217 | 218 | break; 219 | } 220 | case ADD_KEY_FROM_TABLE : { 221 | returnState = addKeyToDataTable(state, action.data); 222 | returnState.lastUpdate = new Date(); 223 | 224 | break; 225 | } 226 | 227 | default : 228 | returnState = state; 229 | } 230 | 231 | dataState = returnState; 232 | 233 | return returnState; 234 | }; 235 | 236 | const guide = (state = guideState, action) => { 237 | let returnState = {}; 238 | 239 | switch (action.type) { 240 | case UPDATE_CONFIGURE_INFO : { 241 | const name = action.name; 242 | const attributes = getAttributesFromDocument(name); 243 | returnState = { 244 | name, 245 | attributes 246 | }; 247 | returnState.lastUpdate = (new Date()).getTime(); 248 | break; 249 | } 250 | case HIDE_GUIDE_CARD : { 251 | returnState.open = false; 252 | returnState.lastUpdate = (new Date()).getTime(); 253 | break; 254 | } 255 | case SHOW_GUIDE_CARD : { 256 | returnState.open = true; 257 | returnState.lastUpdate = (new Date()).getTime(); 258 | break; 259 | } 260 | default : 261 | returnState = state; 262 | } 263 | 264 | returnState = deepCopy({}, guideState, returnState); 265 | guideState = returnState; 266 | 267 | return returnState; 268 | }; 269 | 270 | const playgroundApp = combineReducers({ 271 | gui, command, data, guide 272 | }); 273 | 274 | export default playgroundApp; 275 | -------------------------------------------------------------------------------- /src/app/component/data/index.jsx: -------------------------------------------------------------------------------- 1 | import TextField from "material-ui/TextField"; 2 | import React, { PropTypes } from "react"; 3 | import FontIcon from "material-ui/FontIcon"; 4 | import { connect } from "react-redux"; 5 | import { 6 | Table, 7 | TableBody, 8 | TableHeader, 9 | TableHeaderColumn, 10 | TableRow, 11 | TableRowColumn 12 | } from "material-ui/Table"; 13 | import * as _ from "lodash"; 14 | import getMuiTheme from "material-ui/styles/getMuiTheme"; 15 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 16 | import { 17 | addKeyToData, 18 | addValueToData, 19 | removeValueFromTable, 20 | removeKeyFromTable, 21 | reflectedDataToCommand, 22 | updateHeader, 23 | updateCell 24 | } from "../../actions"; 25 | 26 | const cellStyle = { 27 | width: "50px", 28 | padding: "0 5" 29 | }; 30 | 31 | const tableProps = { 32 | bodyStyle: { 33 | overflowX: "initial", 34 | overflowY: "initial" 35 | }, 36 | selectable: false, 37 | multiSelectable: false 38 | }; 39 | 40 | const tableHeaderProps = { 41 | displaySelectAll: false, 42 | adjustForCheckbox: false 43 | }; 44 | 45 | const textFieldProps = { 46 | fullWidth: true, 47 | underlineStyle: { 48 | borderColor: "transparent" 49 | }, 50 | underlineFocusStyle: { 51 | borderColor: "transparent" 52 | }, 53 | inputStyle: { 54 | textAlign: "center" 55 | } 56 | }; 57 | 58 | class DataTableController extends React.Component { 59 | componentDidUpdate() { 60 | this.props.reflectedData(this.props.data); 61 | } 62 | 63 | onClickDelete(deleteType, targetInfo) { 64 | if (deleteType === "column") { 65 | this.props.deleteKey(targetInfo); 66 | } 67 | 68 | if (deleteType === "row") { 69 | this.props.deleteData(targetInfo); 70 | } 71 | } 72 | 73 | onClickAdd(addType) { 74 | const headers = this.props.data.header; 75 | const rows = this.props.data.body.concat([]); 76 | let data = {}; 77 | 78 | if (addType === "column") { 79 | const newLength = rows.length; 80 | const newKey = `data${headers.length + 1}`; 81 | const value = Array(...Array(newLength)).map(() => parseInt((Math.random() * 100), 10)); 82 | data = { 83 | name: newKey, 84 | value 85 | }; 86 | this.props.addKey(data); 87 | } else if (addType === "row") { 88 | headers.forEach((header) => { 89 | data[header] = parseInt((Math.random() * 100), 10); 90 | }); 91 | this.props.addData(data); 92 | } 93 | } 94 | 95 | getTextField(value, rowIndex, columnIndex) { 96 | const header = this.props.data.header[columnIndex - 1]; 97 | return ( { this.props.onChangeData(v, rowIndex, header); }} 101 | {...textFieldProps} 102 | />); 103 | } 104 | 105 | getRemoveButton(deleteType, targetInfo) { 106 | return (
107 | this.onClickDelete(deleteType, targetInfo)} > 110 | remove_circle 111 | 112 |
); 113 | } 114 | 115 | getAddButton(addType) { 116 | return (
117 | this.onClickAdd(addType)} className="material-icons"> 118 | add_circle 119 | 120 |
); 121 | } 122 | 123 | getHeaderTextField(headerKey, columnIndex) { 124 | const removeButton = this.getRemoveButton("column", headerKey); 125 | return (
126 | {removeButton} 127 | { this.props.onChangeHeader(value, columnIndex - 1); }} 131 | {...textFieldProps} 132 | /> 133 |
); 134 | } 135 | 136 | getHeader(keys) { 137 | const headers = keys.concat([]); 138 | headers.unshift(""); // for delete button column 139 | headers.push(""); // for add button column 140 | 141 | const tc = _.map(headers, (headerKey, columnIndex) => { 142 | let inner; 143 | if (columnIndex === 0) { 144 | inner = ""; 145 | } else if (columnIndex < headers.length - 1) { 146 | inner = this.getHeaderTextField(headerKey, columnIndex); 147 | } else { 148 | inner = this.getAddButton("column"); 149 | } 150 | return ({inner}); 151 | }); 152 | 153 | return ( 154 | 155 | {tc} 156 | 157 | ); 158 | } 159 | 160 | getBody(tableData) { 161 | return ( 162 | {tableData.map((row, rowIndex) => ( 163 | {this.getBodyRow(row, rowIndex)} 164 | ))} 165 | 166 | 167 | {this.getAddButton("row")} 168 | 169 | 170 | ); 171 | } 172 | 173 | getBodyRow(row, rowIndex) { 174 | const values = this.props.data.header.map(header => row[header]); 175 | values.unshift(""); 176 | values.push(""); 177 | 178 | return _.map(values, (value, columnIndex) => { 179 | let inner; 180 | if (columnIndex === 0) { 181 | inner = this.getRemoveButton("row", rowIndex); 182 | } else if (columnIndex < values.length - 1) { 183 | inner = this.getTextField(value, rowIndex, columnIndex); 184 | } else { 185 | inner = ""; 186 | } 187 | return ({inner}); 188 | }); 189 | } 190 | 191 | render() { 192 | const data = this.props.data; 193 | const header = this.getHeader(data.header); 194 | const body = this.getBody(data.body); 195 | 196 | return (
197 | 198 | 199 | {header} 200 | {body} 201 |
202 |
203 |
); 204 | } 205 | } 206 | 207 | 208 | const mapDispatchToProps = dispatch => ({ 209 | onChangeHeader: (value, columnIndex) => { 210 | dispatch(updateHeader({ 211 | value, 212 | column: columnIndex 213 | })); 214 | }, 215 | onChangeData: (value, rowIndex, header) => { 216 | dispatch(updateCell({ 217 | value, 218 | header, 219 | row: rowIndex 220 | })); 221 | }, 222 | reflectedData: (latest) => { 223 | dispatch(reflectedDataToCommand(latest)); 224 | }, 225 | deleteData: (rowIndex) => { 226 | dispatch(removeValueFromTable(rowIndex)); 227 | }, 228 | deleteKey: (columnKey) => { 229 | dispatch(removeKeyFromTable(columnKey)); 230 | }, 231 | addKey: (newDataObject) => { 232 | dispatch(addKeyToData(newDataObject)); 233 | }, 234 | addData: (newDataObject) => { 235 | dispatch(addValueToData(newDataObject)); 236 | } 237 | }); 238 | 239 | DataTableController.defaultProps = { 240 | data: { 241 | header: [], 242 | body: [] 243 | } 244 | }; 245 | 246 | DataTableController.propTypes = { 247 | onChangeHeader: PropTypes.func.isRequired, 248 | onChangeData: PropTypes.func.isRequired, 249 | addKey: PropTypes.func.isRequired, 250 | addData: PropTypes.func.isRequired, 251 | deleteKey: PropTypes.func.isRequired, 252 | deleteData: PropTypes.func.isRequired, 253 | reflectedData: PropTypes.func.isRequired, 254 | data: PropTypes.shape({ 255 | header: PropTypes.arrayOf(PropTypes.string), 256 | body: PropTypes.arrayOf(PropTypes.object) 257 | }) 258 | }; 259 | 260 | const mapStateToProps = state => ({ 261 | data: state.data 262 | }); 263 | 264 | const Data = connect(mapStateToProps, mapDispatchToProps)(DataTableController); 265 | 266 | export { 267 | Data 268 | }; 269 | export default Data; 270 | -------------------------------------------------------------------------------- /src/style/app.scss: -------------------------------------------------------------------------------- 1 | /** app.scss **/ 2 | html { 3 | padding : 10px; 4 | } 5 | 6 | #app { 7 | border: 1px solid #d7d7d7; 8 | position: relative; 9 | height: 100%; 10 | box-sizing: border-box; 11 | } 12 | 13 | #wrapper { 14 | height: 100%; 15 | } 16 | 17 | 18 | .ReactCodeMirror { 19 | width: 100%; 20 | height: auto; 21 | position : absolute; 22 | } 23 | 24 | .guide_card .ReactCodeMirror{ 25 | width: auto; 26 | height: auto; 27 | position: relative; 28 | } 29 | 30 | .muidocs-icon-custom-github:before { 31 | content: "github"; 32 | } 33 | 34 | 35 | .CodeMirror { 36 | width: 100%; 37 | height: 100%; 38 | } 39 | 40 | .type { 41 | color: #009688; 42 | } 43 | .err { 44 | background: #ffd9d9; 45 | } 46 | 47 | .bbtitle { 48 | width : 60%; 49 | height : 10%; 50 | margin : 0; 51 | left : 0; 52 | text-align: center; 53 | font-family: 'Roboto', sans-serif; 54 | font-size: 25px; 55 | } 56 | 57 | .bbtitle .logo { 58 | display: inline-block; 59 | width: 145px; 60 | height: 33px; 61 | margin-left : 10px; 62 | background-repeat :no-repeat; 63 | position: relative; 64 | top: 9px; 65 | box-sizing: content-box; 66 | background-color: #fff; 67 | border-style: none; 68 | } 69 | 70 | .chart { 71 | width : 60%; 72 | height : 40%; 73 | margin : 0; 74 | left : 0; 75 | } 76 | 77 | .tabviews { 78 | width : 60%; 79 | height : 50%; 80 | margin : 0; 81 | left : 0; 82 | } 83 | 84 | .textConfigure, .data-table { 85 | width : 100%; 86 | } 87 | 88 | .data-table .remove-data { 89 | text-align: center; 90 | opacity:0; 91 | } 92 | 93 | .data-table .add-data { 94 | position: relative; 95 | text-align: center; 96 | } 97 | 98 | .data-table .add-data.column{ 99 | top: 33px; 100 | } 101 | 102 | .data-table .add-data .row{ 103 | top: 3px; 104 | } 105 | 106 | .data-table .header-text:hover .remove-data{ 107 | opacity:1; 108 | } 109 | .data-table .table-row:hover .remove-data { 110 | opacity:1; 111 | } 112 | 113 | .layer_wrapper { 114 | margin: 10px; 115 | } 116 | 117 | .bb { 118 | height : 100%; 119 | } 120 | 121 | .inputConfigure { 122 | width : 40%; 123 | left : 60%; 124 | position : absolute; 125 | height : 100%; 126 | top : 0; 127 | border-left: 1px solid #d7d7d7; 128 | box-sizing: border-box; 129 | } 130 | .scroll { 131 | width : 100%; 132 | height : 100%; 133 | overflow-y: scroll; 134 | } 135 | 136 | .property_clear { 137 | opacity:0; 138 | } 139 | 140 | .property.activated .property_clear{ 141 | opacity: 1; 142 | } 143 | 144 | .property_content { 145 | display : inline-block; 146 | } 147 | 148 | 149 | .property:hover .edit_function { 150 | opacity: 1; 151 | } 152 | 153 | 154 | .property.updated { 155 | color : red; 156 | } 157 | 158 | .property.deactivated { 159 | opacity: 0.5; 160 | } 161 | 162 | .activate_check { 163 | //opacity:0; 164 | } 165 | 166 | .property.deactivated .activate_check{ 167 | opacity: 0; 168 | } 169 | 170 | .property:hover .activate_check{ 171 | opacity:1; 172 | } 173 | 174 | .react-toolbox_checkbox { 175 | display: inline-block; 176 | } 177 | 178 | .property_name { 179 | color : #424242; 180 | font-weight : 500; 181 | } 182 | 183 | .property .material-icons { 184 | font-size:20px; 185 | padding-left:10px; 186 | } 187 | 188 | .property .property_info { 189 | top: 6px; 190 | padding-left: 12px; 191 | } 192 | 193 | .property .property_info, .property .edit_code_property { 194 | opacity:0; 195 | } 196 | 197 | .property:hover .property_info, .property:hover .edit_code_property { 198 | opacity:1; 199 | } 200 | 201 | .property_type { 202 | font-size: 14px; 203 | line-height: 16px; 204 | height: 16px; 205 | margin: 0; 206 | margin-top: 4px; 207 | color: rgba(0, 0, 0, 0.54); 208 | overflow: hidden; 209 | text-overflow: ellipsis; 210 | white-space: nowrap; 211 | } 212 | 213 | .member svg { 214 | color : #ff8a65 !important; 215 | } 216 | 217 | .number_guide { 218 | font-size: 14px; 219 | line-height: 24px; 220 | font-family: Roboto, sans-serif; 221 | text-align : center; 222 | 223 | } 224 | 225 | .codepen-export { 226 | position: absolute; 227 | right : 60px; 228 | z-index: 1000; 229 | } 230 | .codepen-export input[type="submit"] { 231 | border : none; 232 | width: 60px; 233 | height : 40px; 234 | background: none; 235 | display: inline-block; 236 | } 237 | .codepen-export .button { 238 | background-size: contain !important; 239 | border : none ; 240 | width: 60px !important; 241 | height : 40px !important; 242 | background-image : url(codepen-export.svg) !important; 243 | } 244 | .drawer_close { 245 | transform : translate(-10px, 200px) !important; 246 | 247 | } 248 | .drawer_open { 249 | transform : translate(-10px, -10px) !important; 250 | } 251 | 252 | .table-row:hover .table-row-column{ 253 | opacity:1; 254 | } 255 | .table-row-column { 256 | opacity:0; 257 | } 258 | 259 | /** app.css **/ 260 | 261 | .guide_card .example { 262 | padding: 10px !important; 263 | background: #F5F5F5; 264 | font-size: 13px; 265 | } 266 | 267 | .guide_card { 268 | } 269 | 270 | .guide_card .text_title { 271 | //color : #424242; 272 | font-weight: 500; 273 | margin-bottom: 9px; 274 | margin-top: 22px; 275 | font-family: 'Roboto', sans-serif; 276 | } 277 | .text_content { 278 | //color : #424242; 279 | font-weight: 400; 280 | font-family: 'Roboto', sans-serif; 281 | } 282 | 283 | 284 | /*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */ 285 | .prettyprint { 286 | font-family: Menlo, "Bitstream Vera Sans Mono", "DejaVu Sans Mono", Monaco, Consolas, monospace; 287 | border: 0 !important; 288 | } 289 | 290 | .pln { 291 | color: #333; 292 | } 293 | 294 | /* Specify class=linenums on a pre to get line numbering */ 295 | ol.linenums { 296 | margin-top: 0; 297 | margin-bottom: 0; 298 | color: #cccccc; 299 | } 300 | 301 | li.L0, 302 | li.L1, 303 | li.L2, 304 | li.L3, 305 | li.L4, 306 | li.L5, 307 | li.L6, 308 | li.L7, 309 | li.L8, 310 | li.L9 { 311 | padding-left: 1em; 312 | background-color: #fff; 313 | list-style-type: decimal; 314 | } 315 | 316 | @media screen { 317 | /* string content */ 318 | 319 | .str { 320 | color: #d14; 321 | } 322 | 323 | /* keyword */ 324 | 325 | .kwd { 326 | color: #333; 327 | } 328 | 329 | /* comment */ 330 | 331 | .com { 332 | color: #998; 333 | } 334 | 335 | /* type name */ 336 | 337 | .typ { 338 | color: #458; 339 | } 340 | 341 | /* literal value */ 342 | 343 | .lit { 344 | color: #458; 345 | } 346 | 347 | /* punctuation */ 348 | 349 | .pun { 350 | color: #333; 351 | } 352 | 353 | /* lisp open bracket */ 354 | 355 | .opn { 356 | color: #333; 357 | } 358 | 359 | /* lisp close bracket */ 360 | 361 | .clo { 362 | color: #333; 363 | } 364 | 365 | /* markup tag name */ 366 | 367 | .tag { 368 | color: #000080; 369 | } 370 | 371 | /* markup attribute name */ 372 | 373 | .atn { 374 | color: #008080; 375 | } 376 | 377 | /* markup attribute value */ 378 | 379 | .atv { 380 | color: #d14; 381 | } 382 | 383 | /* declaration */ 384 | 385 | .dec { 386 | color: #333; 387 | } 388 | 389 | /* variable name */ 390 | 391 | .var { 392 | color: #008080; 393 | } 394 | 395 | /* function name */ 396 | 397 | .fun { 398 | color: #900; 399 | } 400 | } -------------------------------------------------------------------------------- /dist/resources/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/app/configure/resources/preset.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | regions: { 3 | attributes: { 4 | customkeys: [{ 5 | name: "axis", 6 | type: "string" 7 | }, { 8 | name: "start", 9 | type: "number" 10 | }, { 11 | name: "end", 12 | type: "number" 13 | }, { 14 | name: "class", 15 | type: "string" 16 | }] 17 | } 18 | }, 19 | size: { 20 | properties: { 21 | width: { 22 | attributes: { 23 | min: 0, 24 | max: 1000, 25 | step: 1 26 | } 27 | }, 28 | height: { 29 | attributes: { 30 | min: 0, 31 | max: 1000, 32 | step: 1 33 | } 34 | } 35 | } 36 | }, 37 | padding: { 38 | properties: { 39 | top: { 40 | attributes: { 41 | min: -1000, 42 | max: 1000, 43 | step: 1 44 | } 45 | }, 46 | right: { 47 | attributes: { 48 | min: -1000, 49 | max: 1000, 50 | step: 1 51 | } 52 | }, 53 | bottom: { 54 | attributes: { 55 | min: -1000, 56 | max: 1000, 57 | step: 1 58 | } 59 | }, 60 | left: { 61 | attributes: { 62 | min: -1000, 63 | max: 1000, 64 | step: 1 65 | } 66 | } 67 | } 68 | }, 69 | transition: { 70 | properties: { 71 | duration: { 72 | attributes: { 73 | min: 0, 74 | max: 1000, 75 | step: 1 76 | } 77 | } 78 | } 79 | }, 80 | data: { 81 | properties: { 82 | columns: { 83 | attributes: { 84 | hidegui: true 85 | } 86 | }, 87 | rows: { 88 | attributes: { 89 | hidegui: true 90 | } 91 | }, 92 | json: { 93 | attributes: { 94 | hidegui: true 95 | } 96 | }, 97 | url: { 98 | attributes: { 99 | hidegui: true 100 | } 101 | }, 102 | type: { 103 | attributes: { 104 | valueoptions: ["line", "spline", "step", "area", "area-spline", "area-step", "bar", "bubble", "scatter", "pie", "donut", "gauge"] 105 | } 106 | } 107 | } 108 | }, 109 | axis: { 110 | properties: { 111 | y: { 112 | properties: { 113 | tick: { 114 | properties: { 115 | count: { 116 | attributes: { 117 | min: -500, 118 | max: 500, 119 | step: 1 120 | } 121 | } 122 | } 123 | }, 124 | center: { 125 | attributes: { 126 | min: -500, 127 | max: 500, 128 | step: 1 129 | } 130 | }, 131 | max: { 132 | attributes: { 133 | min: -500, 134 | max: 500, 135 | step: 1 136 | } 137 | }, 138 | min: { 139 | attributes: { 140 | min: -500, 141 | max: 500, 142 | step: 1 143 | } 144 | } 145 | } 146 | }, 147 | y2: { 148 | properties: { 149 | tick: { 150 | properties: { 151 | count: { 152 | attributes: { 153 | min: -500, 154 | max: 500, 155 | step: 1 156 | } 157 | } 158 | } 159 | }, 160 | center: { 161 | attributes: { 162 | min: -500, 163 | max: 500, 164 | step: 1 165 | } 166 | }, 167 | max: { 168 | attributes: { 169 | min: -500, 170 | max: 500, 171 | step: 1 172 | } 173 | }, 174 | min: { 175 | attributes: { 176 | min: -500, 177 | max: 500, 178 | step: 1 179 | } 180 | } 181 | } 182 | }, 183 | x: { 184 | properties: { 185 | height: { 186 | attributes: { 187 | min: 0, 188 | max: 500, 189 | step: 1 190 | } 191 | }, 192 | tick: { 193 | properties: { 194 | width: { 195 | attributes: { 196 | min: 0, 197 | max: 100, 198 | step: 1 199 | } 200 | }, 201 | rotate: { 202 | attributes: { 203 | min: -100, 204 | max: 100, 205 | step: 1 206 | } 207 | }, 208 | count: { 209 | attributes: { 210 | min: 0, 211 | max: 100, 212 | step: 1 213 | } 214 | }, 215 | culling: { 216 | properties: { 217 | max: { 218 | attributes: { 219 | min: 0, 220 | max: 100, 221 | step: 1 222 | } 223 | } 224 | } 225 | } 226 | } 227 | }, 228 | type: { 229 | attributes: { 230 | valueoptions: ["timeseries", "category", "indexed"] 231 | } 232 | }, 233 | max: { 234 | attributes: { 235 | min: -500, 236 | max: 500, 237 | step: 1 238 | } 239 | }, 240 | min: { 241 | attributes: { 242 | min: -500, 243 | max: 500, 244 | step: 1 245 | } 246 | } 247 | } 248 | } 249 | } 250 | }, 251 | bubble: { 252 | properties: { 253 | maxR: { 254 | attributes: { 255 | min: 0, 256 | max: 100, 257 | step: 1 258 | } 259 | } 260 | } 261 | }, 262 | point: { 263 | properties: { 264 | select: { 265 | properties: { 266 | r: { 267 | attributes: { 268 | custominput: true 269 | } 270 | } 271 | } 272 | }, 273 | focus: { 274 | properties: { 275 | expand: { 276 | properties: { 277 | r: { 278 | attributes: { 279 | custominput: true 280 | } 281 | } 282 | } 283 | } 284 | } 285 | }, 286 | r: { 287 | attributes: { 288 | min: 0, 289 | max: 50, 290 | step: 0.1 291 | } 292 | } 293 | } 294 | }, 295 | gauge: { 296 | properties: { 297 | startingAngle: { 298 | attributes: { 299 | custominput: true 300 | } 301 | }, 302 | max: { 303 | attributes: { 304 | min: 0, 305 | max: 1000, 306 | step: 1 307 | } 308 | }, 309 | expand: { 310 | properties: { 311 | duration: { 312 | attributes: { 313 | min: 0, 314 | max: 100, 315 | step: 1 316 | } 317 | } 318 | } 319 | } 320 | } 321 | }, 322 | legend: { 323 | properties: { 324 | inset: { 325 | properties: { 326 | anchor: { 327 | attributes: { 328 | name: "legend.inset.anchor", 329 | valueoptions: ["top-left", "top-right", "bottom-left", "bottom-right"], 330 | type: { 331 | names: ["String"] 332 | }, 333 | optional: true, 334 | value: "top-left", 335 | defaultvalue: "top-left" 336 | } 337 | }, 338 | x: { 339 | attributes: { 340 | name: "legend.inset.x", 341 | type: { 342 | names: ["Number"] 343 | }, 344 | optional: true, 345 | value: 10, 346 | defaultvalue: 10, 347 | min: 0, 348 | max: 100, 349 | step: 1 350 | } 351 | }, 352 | y: { 353 | attributes: { 354 | name: "legend.inset.y", 355 | type: { 356 | names: ["Number"] 357 | }, 358 | optional: true, 359 | value: 0, 360 | defaultvalue: 0, 361 | min: 0, 362 | max: 100, 363 | step: 1 364 | } 365 | }, 366 | step: { 367 | attributes: { 368 | name: "legend.inset.step", 369 | type: { 370 | names: ["Number"] 371 | }, 372 | optional: true, 373 | value: "undefined", 374 | defaultvalue: "undefined", 375 | min: 0, 376 | max: 100, 377 | step: 1 378 | } 379 | } 380 | } 381 | }, 382 | position: { 383 | attributes: { 384 | valueoptions: ["bottom", "right", "inset"] 385 | } 386 | } 387 | } 388 | }, 389 | 390 | grid: { 391 | properties: { 392 | y: { 393 | properties: { 394 | ticks: { 395 | attributes: { 396 | min: 0, 397 | max: 100, 398 | step: 1 399 | } 400 | } 401 | } 402 | } 403 | } 404 | }, 405 | color: { 406 | properties: { 407 | threshold: { 408 | properties: { 409 | max: { 410 | attributes: { 411 | min: 0, 412 | max: 300, 413 | step: 1 414 | } 415 | } 416 | } 417 | } 418 | } 419 | }, 420 | title: { 421 | properties: { 422 | position: { 423 | attributes: { 424 | valueoptions: ["top-center", "top-right", "top-left"] 425 | } 426 | } 427 | } 428 | } 429 | }; 430 | -------------------------------------------------------------------------------- /src/app/reducers/logic/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | namespaceToObject, 3 | deepCopy, 4 | strinifyContainsFunction, 5 | stringToFunction, 6 | objectFlatten 7 | } from "../../util"; 8 | import { 9 | formattedDocument as initDocument 10 | } from "../../configure"; 11 | import { 12 | convertColumnsToData 13 | } from "./convert"; 14 | 15 | const dataToFormatedData = (format, data) => { 16 | if (format === "columns") { 17 | const mapper = {}; 18 | const columns = []; 19 | 20 | data.header.forEach(function(key, index){ 21 | mapper[key] = index; 22 | columns.push([key]); 23 | }); 24 | 25 | data.body.forEach(function(data, index){ 26 | _.each(data, (value, key) => { 27 | columns[mapper[key]][index+1] = value; 28 | }); 29 | }); 30 | 31 | return columns; 32 | } 33 | }; 34 | 35 | export const convertData = (data) => { 36 | let converted; 37 | if (data.columns) { 38 | converted = convertColumnsToData(data.columns); 39 | } else if (data.rows) { 40 | converted = convertRowsToData(data.rows); 41 | } else if (data.json){ 42 | converted = convertJsonToData(data.json, { 43 | value: _.keys(data.json[0]) 44 | }); 45 | } 46 | return { 47 | header: _.keys(converted[0]), 48 | body: converted 49 | }; 50 | }; 51 | 52 | 53 | 54 | export const resetDataState = (state, newState) => { 55 | return deepCopy({}, newState); 56 | }; 57 | 58 | export const removeValueToDataState = (state, newData) => { 59 | const newState = deepCopy({}, state); 60 | newState.body.splice(newData, 1); 61 | 62 | return newState; 63 | }; 64 | 65 | export const removeKeyToDataState = (state, newData) => { 66 | const newState = deepCopy({}, state); 67 | 68 | newState.body.forEach((target) => { 69 | delete target[newData]; 70 | }); 71 | newState.header = _.remove(newState.header, function(name) { 72 | return name !== newData; 73 | }); 74 | 75 | return newState; 76 | }; 77 | 78 | 79 | export const addValueToDataTable = (state, newData) => { 80 | const newState = deepCopy({}, state); 81 | newState.body.push(newData); 82 | 83 | return newState; 84 | }; 85 | 86 | export const addKeyToDataTable = (state, newData) => { 87 | const newState = deepCopy({}, state); 88 | 89 | newState.header.push(newData.name); 90 | newData.value.forEach((value, index) => { 91 | newState.body[index][newData.name] = value; 92 | }); 93 | return newState; 94 | }; 95 | 96 | export const updateDataKeyState = (state, { value, column }) => { 97 | const newState = deepCopy({}, state); 98 | const prevHeader = state.header[column]; 99 | const newBody = newState.body.map((data) => { 100 | data[value] = data[prevHeader]; 101 | delete data[prevHeader]; 102 | return data; 103 | }); 104 | newState.header[column] = value; 105 | newState.body = newBody; 106 | 107 | return newState; 108 | }; 109 | 110 | export const updateDataValueState = (state, {row, header, value}) => { 111 | const newState = deepCopy({}, state); 112 | const newValue = !isNaN(value) && value !== "" ? Number(value) : value; 113 | 114 | newState.body[row][header] = newValue; 115 | 116 | return newState; 117 | }; 118 | 119 | 120 | 121 | 122 | export const updateCommandData = (state, lastest) => { 123 | // columns, json, rows 124 | const formatList = ["columns", "json", "rows"]; 125 | const newState = deepCopy({}, state); 126 | const format = Object.keys(newState.original.data).filter((key) => { 127 | return formatList.indexOf(key) > -1; 128 | })[0]; 129 | 130 | newState.original.data[format] = dataToFormatedData(format, lastest); 131 | 132 | const text = strinifyContainsFunction(newState.original); 133 | 134 | 135 | return { 136 | original: newState.original, 137 | text 138 | }; 139 | }; 140 | 141 | 142 | 143 | export const updateCommandCode = (state, name) => { 144 | const textFunc = getDefaultValue(name); 145 | const objFunc = stringToFunction(textFunc); 146 | const updated = namespaceToObject(name, objFunc); 147 | 148 | const original = deepCopy({}, state.original, updated); 149 | const text = strinifyContainsFunction(original); 150 | 151 | return { 152 | original, text 153 | }; 154 | }; 155 | 156 | 157 | export const resetCommandState = (updated) => { 158 | const original = deepCopy({}, updated); 159 | const text = strinifyContainsFunction(original); 160 | 161 | return { 162 | original, text 163 | }; 164 | }; 165 | 166 | export const updateCommandState = (state, updated) => { 167 | const original = deepCopy({}, state.original, updated); 168 | const text = strinifyContainsFunction(original); 169 | 170 | return { 171 | original, text 172 | }; 173 | }; 174 | 175 | export const deleteTargetCommandState = (state, namespace) => { 176 | const original = deepCopy({}, state.original); 177 | const deleteTarget = deleteTargetKey(original, namespace); 178 | const text = strinifyContainsFunction(deleteTarget); 179 | 180 | return { 181 | original: deepCopy({}, deleteTarget), 182 | text: text 183 | }; 184 | }; 185 | 186 | export const updateCommandByGUI = (state, name, value) => { 187 | const latest = namespaceToObject(name, value); 188 | const defaultvalue = getDefaultValue(name); 189 | 190 | return (value === defaultvalue) ? deleteTargetCommandState(state, name) : 191 | updateCommandState(state, latest); 192 | } 193 | 194 | 195 | export const updateGuiState = (state, updated) => { 196 | const newObj = changeMemberProperty(state, deepCopy({}, updated)); 197 | return deepCopy({}, newObj); 198 | }; 199 | 200 | 201 | export const changeDefaultGuiState = (state, keys) => { 202 | let newObj = {}; 203 | _.each(keys, (name) => { 204 | const newValue = getDefaultValue(name); 205 | const obj = namespaceToObject(name, newValue); 206 | newObj = deepCopy(newObj, obj); 207 | }); 208 | 209 | const newState = changeMemberProperty(state, deepCopy({}, newObj)); 210 | return deepCopy({}, newState); 211 | }; 212 | 213 | 214 | export const updateGuiActivate = (state, name) => { 215 | const path = `${name.replace(/\./g, ".properties.")}.attributes.activated`; 216 | _.update(state, path, () => { 217 | return true; 218 | }); 219 | return deepCopy({}, state); 220 | }; 221 | 222 | 223 | 224 | const typeValid = (types, value) => { 225 | if(value === undefined || value === "undefined"){ 226 | return undefined; 227 | } 228 | 229 | if(types.indexOf("number") > -1) { 230 | if(isNaN(value)){ 231 | //console.log("TODO => " + value); 232 | return value; 233 | } else { 234 | return value*1; 235 | } 236 | } 237 | 238 | if(types.indexOf("array") > -1){ 239 | const array = JSON.parse(value+=""); 240 | return array; 241 | } 242 | 243 | if(types.indexOf("function") > -1){ 244 | return value; 245 | } 246 | 247 | if(types.indexOf("boolean") > -1){ 248 | return value; 249 | } 250 | 251 | if(types.indexOf("string") > -1){ 252 | return value; 253 | } 254 | 255 | if(types.indexOf("object") > -1){ 256 | return value; 257 | } 258 | 259 | return value; 260 | 261 | }; 262 | 263 | const memberFlatten = (member) => { 264 | let newArray = [member]; 265 | if (member.properties) { 266 | _.each(member.properties, (item) => { 267 | if(item.properties){ 268 | newArray = newArray.concat(memberFlatten(item)); 269 | } else { 270 | const value = typeValid(item.type.names.map(type => (type.toLowerCase())), item.defaultvalue); 271 | newArray.push({ 272 | type: item.type, 273 | defaultvalue: value, 274 | value: value, 275 | name: item.name, 276 | description: item.description, 277 | optional: item.optional, 278 | activated: false, 279 | examples: item.examples, 280 | docid: item.name, 281 | }); 282 | } 283 | }); 284 | } 285 | 286 | return newArray; 287 | }; 288 | 289 | 290 | 291 | export const getValueFromDocument = (doc, namespace, targetAttribute) => { 292 | const path = namespace.replace(/\./g, ".properties."); 293 | const attirbutes = _.get(doc, path).attributes; 294 | 295 | return attirbutes[targetAttribute]; 296 | }; 297 | 298 | export const getRemovedToDefault = (prev, cur) => { 299 | const pkeys = Object.keys(objectFlatten(prev)); 300 | const ckeys = Object.keys(objectFlatten(cur)); 301 | const nkeys = _.difference(pkeys, ckeys); 302 | 303 | let newObj = {}; 304 | _.each(nkeys, (name) => { 305 | const newValue = getDefaultValue(name); 306 | const obj = namespaceToObject(name, newValue); 307 | newObj = deepCopy(newObj, obj); 308 | }); 309 | 310 | return newObj; 311 | }; 312 | 313 | export const getDefaultValue = (namespace) => { 314 | const document = initDocument; 315 | const path = namespace.replace(/\./g, ".properties."); 316 | const attirbutes = _.get(document, path).attributes; 317 | 318 | return attirbutes.defaultvalue; 319 | }; 320 | 321 | const fillDefaultAttributes = (target, root) => { 322 | target.attributes = target.attributes || getDefaultAttributes(root); 323 | if(target.properties){ 324 | _.each(target.properties, (obj, key) => { 325 | target.properties[key] = fillDefaultAttributes(obj, `${root}.${key}`); 326 | }); 327 | } 328 | return target; 329 | }; 330 | 331 | const getDefaultAttributes = (name) => { 332 | return { 333 | description: "", 334 | optional: true, 335 | activated: false, 336 | name, 337 | value: undefined, 338 | defaultvalue: undefined, 339 | type: { 340 | names: ["Object"] 341 | }, 342 | }; 343 | }; 344 | 345 | 346 | export const changeMemberActivate = (original, name, value) => { 347 | const fkeys = [name]; 348 | 349 | _.each(fkeys, (keyPath) => { 350 | const targetPath = keyPath.replace(/\./g, ".properties.") + ".attributes"; 351 | const type = _.get(original, targetPath + ".type"); 352 | 353 | _.update(original, targetPath + ".activated", () => { 354 | return value; 355 | }); 356 | }); 357 | 358 | return original; 359 | }; 360 | 361 | 362 | const typeConverting = (types, value) => { 363 | return value; 364 | }; 365 | 366 | const isSameValue = (a, b) => { 367 | return a+"" == b+""; 368 | }; 369 | 370 | export const changeMemberProperty = (document, latest) => { 371 | const flatten = objectFlatten(latest); 372 | const keys = Object.keys(flatten); 373 | 374 | _.each(keys, (keyPath) => { 375 | const path = `${keyPath.replace(/\./g, ".properties.")}.attributes`; 376 | const attributes = _.get(document, path); 377 | const latestValue = flatten[keyPath]; 378 | const isUpdate = !isSameValue(latestValue, attributes.defaultvalue); 379 | 380 | _.update(document, `${path}.value`, () => { 381 | return typeConverting(attributes.type.names, latestValue); 382 | }); 383 | 384 | _.update(document, `${path}.activated`, () => { 385 | return isUpdate; 386 | }); 387 | }); 388 | 389 | return document; 390 | 391 | //_.each(fkeys, (keyPath) => { 392 | // const targetPath = keyPath.replace(/\./g, ".properties.") + ".attributes"; 393 | // const type = _.get(original, targetPath + ".type"); 394 | // let value = _.get(object, keyPath); 395 | // let changed = _.get(original, targetPath + ".defaultvalue") !== value; 396 | // 397 | // _.update(original, targetPath + ".activated", () => { 398 | // return changed; 399 | // }); 400 | // 401 | // if(type && type.names && type.names[0] && type.names[0].toLowerCase() === "number" && value !== undefined){ 402 | // value = value * 1; 403 | // } 404 | // 405 | // if(typeof value === "function") { 406 | // value = value.toString(); 407 | // } 408 | // 409 | // _.update(original, targetPath + ".value", () => { 410 | // return value; 411 | // }); 412 | //}); 413 | //return original; 414 | }; 415 | 416 | export const deleteTargetKey = (original, targetkey) => { 417 | const keys = targetkey.split(".").reverse(); 418 | let path = targetkey.replace("." + keys[0], ""); 419 | let prevPath = path; 420 | 421 | _.unset(original, targetkey); 422 | 423 | _.each(keys.slice(1), (key) => { 424 | const target = _.get(original, path); 425 | if(target && Object.keys(target).length < 1){ 426 | _.unset(original, path); 427 | } 428 | prevPath = path; 429 | path = path.replace("." + key, ""); 430 | }); 431 | 432 | return original; 433 | }; 434 | export const getAttributesFromDocument = (name) => { 435 | const init = initDocument; 436 | const path = name.replace(/\./g, ".properties.") + ".attributes"; 437 | return _.get(init, path); 438 | }; 439 | 440 | 441 | const canOptional = (keys, name) => { 442 | const datas = ["data.columns", "data.rows", "data.json", "data.url"]; 443 | if(datas.indexOf(name) < 0){ 444 | return true; 445 | } 446 | let flag = 0; 447 | _.each(keys, (key) => { 448 | datas.indexOf(key) > -1 ? flag++ : flag; 449 | }); 450 | 451 | }; 452 | 453 | const dataSetConfig = ["data.columns", "data.rows", "data.json", "data.url"]; 454 | const hasOneMoreDataset = (keys) => { 455 | return (dataSetConfig.length - _.difference(dataSetConfig, keys).length > 1); 456 | }; 457 | 458 | export const getChip = (keys) => { 459 | const collection = []; 460 | const hasMoreDataSet = hasOneMoreDataset(keys); 461 | 462 | _.each(keys, (name) => { 463 | const target = getAttributesFromDocument(name); 464 | if(dataSetConfig.indexOf(name) > -1 && !hasMoreDataSet){ 465 | collection.push({ 466 | name, 467 | optional: false 468 | }); 469 | } else { 470 | collection.push({ 471 | name, 472 | optional: target.optional === undefined ? true : target.optional, 473 | }); 474 | } 475 | 476 | }); 477 | 478 | return collection; 479 | }; 480 | -------------------------------------------------------------------------------- /dist/app.bundle.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Bungee);@import url(https://fonts.googleapis.com/css?family=Roboto:400,500);/* 2 | html5doctor.com Reset Stylesheet 3 | v1.6.1 4 | Last Updated: 2010-09-17 5 | Author: Richard Clark - http://richclarkdesign.com 6 | Twitter: @rich_clark 7 | License : http://html5doctor.com/html-5-reset-stylesheet/ 8 | */ 9 | 10 | html, body, div, span, object, iframe, 11 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 12 | abbr, address, cite, code, 13 | del, dfn, em, img, ins, kbd, q, samp, 14 | small, strong, sub, sup, var, 15 | b, i, 16 | dl, dt, dd, ol, ul, li, 17 | fieldset, form, label, legend, 18 | table, caption, tbody, tfoot, thead, tr, th, td, 19 | article, aside, canvas, details, figcaption, figure, 20 | footer, header, hgroup, menu, nav, section, summary, 21 | time, mark, audio, video { 22 | margin:0; 23 | padding:0; 24 | border:0; 25 | outline:0; 26 | font-size:100%; 27 | vertical-align:baseline; 28 | background:transparent; 29 | } 30 | 31 | body { 32 | line-height:1; 33 | } 34 | 35 | article,aside,details,figcaption,figure, 36 | footer,header,hgroup,menu,nav,section { 37 | display:block; 38 | } 39 | 40 | nav ul { 41 | list-style:none; 42 | } 43 | 44 | blockquote, q { 45 | quotes:none; 46 | } 47 | 48 | blockquote:before, blockquote:after, 49 | q:before, q:after { 50 | content:''; 51 | content:none; 52 | } 53 | 54 | a { 55 | margin:0; 56 | padding:0; 57 | font-size:100%; 58 | vertical-align:baseline; 59 | background:transparent; 60 | } 61 | 62 | /* change colours to suit your needs */ 63 | ins { 64 | background-color:#ff9; 65 | color:#000; 66 | text-decoration:none; 67 | } 68 | 69 | /* change colours to suit your needs */ 70 | mark { 71 | background-color:#ff9; 72 | color:#000; 73 | font-style:italic; 74 | font-weight:bold; 75 | } 76 | 77 | del { 78 | text-decoration: line-through; 79 | } 80 | 81 | abbr[title], dfn[title] { 82 | border-bottom:1px dotted; 83 | cursor:help; 84 | } 85 | 86 | table { 87 | border-collapse:collapse; 88 | border-spacing:0; 89 | } 90 | 91 | /* change border colour to suit your needs */ 92 | hr { 93 | display:block; 94 | height:1px; 95 | border:0; 96 | border-top:1px solid #cccccc; 97 | margin:1em 0; 98 | padding:0; 99 | } 100 | 101 | input, select { 102 | vertical-align:middle; 103 | } 104 | 105 | 106 | /* fallback */ 107 | @font-face { 108 | font-family: 'Material Icons'; 109 | font-style: normal; 110 | font-weight: 400; 111 | /*src: url(MaterialIcons-Regular.woff2) format('woff2'),*/ 112 | src: url(https://fonts.gstatic.com/s/materialicons/v29/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2) format('woff2'); 113 | } 114 | 115 | .material-icons { 116 | font-family: 'Material Icons'; 117 | font-weight: normal; 118 | font-style: normal; 119 | font-size: 24px; 120 | line-height: 1; 121 | letter-spacing: normal; 122 | text-transform: none; 123 | display: inline-block; 124 | white-space: nowrap; 125 | word-wrap: normal; 126 | direction: ltr; 127 | -webkit-font-feature-settings: 'liga'; 128 | -webkit-font-smoothing: antialiased; 129 | } 130 | 131 | /* BASICS */ 132 | 133 | .CodeMirror { 134 | /* Set height, width, borders, and global font properties here */ 135 | font-family: monospace; 136 | height: 300px; 137 | color: black; 138 | direction: ltr; 139 | } 140 | 141 | /* PADDING */ 142 | 143 | .CodeMirror-lines { 144 | padding: 4px 0; /* Vertical padding around content */ 145 | } 146 | .CodeMirror pre { 147 | padding: 0 4px; /* Horizontal padding of content */ 148 | } 149 | 150 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 151 | background-color: white; /* The little square between H and V scrollbars */ 152 | } 153 | 154 | /* GUTTER */ 155 | 156 | .CodeMirror-gutters { 157 | border-right: 1px solid #ddd; 158 | background-color: #f7f7f7; 159 | white-space: nowrap; 160 | } 161 | .CodeMirror-linenumbers {} 162 | .CodeMirror-linenumber { 163 | padding: 0 3px 0 5px; 164 | min-width: 20px; 165 | text-align: right; 166 | color: #999; 167 | white-space: nowrap; 168 | } 169 | 170 | .CodeMirror-guttermarker { color: black; } 171 | .CodeMirror-guttermarker-subtle { color: #999; } 172 | 173 | /* CURSOR */ 174 | 175 | .CodeMirror-cursor { 176 | border-left: 1px solid black; 177 | border-right: none; 178 | width: 0; 179 | } 180 | /* Shown when moving in bi-directional text */ 181 | .CodeMirror div.CodeMirror-secondarycursor { 182 | border-left: 1px solid silver; 183 | } 184 | .cm-fat-cursor .CodeMirror-cursor { 185 | width: auto; 186 | border: 0 !important; 187 | background: #7e7; 188 | } 189 | .cm-fat-cursor div.CodeMirror-cursors { 190 | z-index: 1; 191 | } 192 | .cm-fat-cursor-mark { 193 | background-color: rgba(20, 255, 20, 0.5); 194 | -webkit-animation: blink 1.06s steps(1) infinite; 195 | -moz-animation: blink 1.06s steps(1) infinite; 196 | animation: blink 1.06s steps(1) infinite; 197 | } 198 | .cm-animate-fat-cursor { 199 | width: auto; 200 | border: 0; 201 | -webkit-animation: blink 1.06s steps(1) infinite; 202 | -moz-animation: blink 1.06s steps(1) infinite; 203 | animation: blink 1.06s steps(1) infinite; 204 | background-color: #7e7; 205 | } 206 | @-moz-keyframes blink { 207 | 0% {} 208 | 50% { background-color: transparent; } 209 | 100% {} 210 | } 211 | @-webkit-keyframes blink { 212 | 0% {} 213 | 50% { background-color: transparent; } 214 | 100% {} 215 | } 216 | @keyframes blink { 217 | 0% {} 218 | 50% { background-color: transparent; } 219 | 100% {} 220 | } 221 | 222 | /* Can style cursor different in overwrite (non-insert) mode */ 223 | .CodeMirror-overwrite .CodeMirror-cursor {} 224 | 225 | .cm-tab { display: inline-block; text-decoration: inherit; } 226 | 227 | .CodeMirror-rulers { 228 | position: absolute; 229 | left: 0; right: 0; top: -50px; bottom: -20px; 230 | overflow: hidden; 231 | } 232 | .CodeMirror-ruler { 233 | border-left: 1px solid #ccc; 234 | top: 0; bottom: 0; 235 | position: absolute; 236 | } 237 | 238 | /* DEFAULT THEME */ 239 | 240 | .cm-s-default .cm-header {color: blue;} 241 | .cm-s-default .cm-quote {color: #090;} 242 | .cm-negative {color: #d44;} 243 | .cm-positive {color: #292;} 244 | .cm-header, .cm-strong {font-weight: bold;} 245 | .cm-em {font-style: italic;} 246 | .cm-link {text-decoration: underline;} 247 | .cm-strikethrough {text-decoration: line-through;} 248 | 249 | .cm-s-default .cm-keyword {color: #708;} 250 | .cm-s-default .cm-atom {color: #219;} 251 | .cm-s-default .cm-number {color: #164;} 252 | .cm-s-default .cm-def {color: #00f;} 253 | .cm-s-default .cm-variable, 254 | .cm-s-default .cm-punctuation, 255 | .cm-s-default .cm-property, 256 | .cm-s-default .cm-operator {} 257 | .cm-s-default .cm-variable-2 {color: #05a;} 258 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} 259 | .cm-s-default .cm-comment {color: #a50;} 260 | .cm-s-default .cm-string {color: #a11;} 261 | .cm-s-default .cm-string-2 {color: #f50;} 262 | .cm-s-default .cm-meta {color: #555;} 263 | .cm-s-default .cm-qualifier {color: #555;} 264 | .cm-s-default .cm-builtin {color: #30a;} 265 | .cm-s-default .cm-bracket {color: #997;} 266 | .cm-s-default .cm-tag {color: #170;} 267 | .cm-s-default .cm-attribute {color: #00c;} 268 | .cm-s-default .cm-hr {color: #999;} 269 | .cm-s-default .cm-link {color: #00c;} 270 | 271 | .cm-s-default .cm-error {color: #f00;} 272 | .cm-invalidchar {color: #f00;} 273 | 274 | .CodeMirror-composing { border-bottom: 2px solid; } 275 | 276 | /* Default styles for common addons */ 277 | 278 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} 279 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} 280 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 281 | .CodeMirror-activeline-background {background: #e8f2ff;} 282 | 283 | /* STOP */ 284 | 285 | /* The rest of this file contains styles related to the mechanics of 286 | the editor. You probably shouldn't touch them. */ 287 | 288 | .CodeMirror { 289 | position: relative; 290 | overflow: hidden; 291 | background: white; 292 | } 293 | 294 | .CodeMirror-scroll { 295 | overflow: scroll !important; /* Things will break if this is overridden */ 296 | /* 30px is the magic margin used to hide the element's real scrollbars */ 297 | /* See overflow: hidden in .CodeMirror */ 298 | margin-bottom: -30px; margin-right: -30px; 299 | padding-bottom: 30px; 300 | height: 100%; 301 | outline: none; /* Prevent dragging from highlighting the element */ 302 | position: relative; 303 | } 304 | .CodeMirror-sizer { 305 | position: relative; 306 | border-right: 30px solid transparent; 307 | } 308 | 309 | /* The fake, visible scrollbars. Used to force redraw during scrolling 310 | before actual scrolling happens, thus preventing shaking and 311 | flickering artifacts. */ 312 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 313 | position: absolute; 314 | z-index: 6; 315 | display: none; 316 | } 317 | .CodeMirror-vscrollbar { 318 | right: 0; top: 0; 319 | overflow-x: hidden; 320 | overflow-y: scroll; 321 | } 322 | .CodeMirror-hscrollbar { 323 | bottom: 0; left: 0; 324 | overflow-y: hidden; 325 | overflow-x: scroll; 326 | } 327 | .CodeMirror-scrollbar-filler { 328 | right: 0; bottom: 0; 329 | } 330 | .CodeMirror-gutter-filler { 331 | left: 0; bottom: 0; 332 | } 333 | 334 | .CodeMirror-gutters { 335 | position: absolute; left: 0; top: 0; 336 | min-height: 100%; 337 | z-index: 3; 338 | } 339 | .CodeMirror-gutter { 340 | white-space: normal; 341 | height: 100%; 342 | display: inline-block; 343 | vertical-align: top; 344 | margin-bottom: -30px; 345 | } 346 | .CodeMirror-gutter-wrapper { 347 | position: absolute; 348 | z-index: 4; 349 | background: none !important; 350 | border: none !important; 351 | } 352 | .CodeMirror-gutter-background { 353 | position: absolute; 354 | top: 0; bottom: 0; 355 | z-index: 4; 356 | } 357 | .CodeMirror-gutter-elt { 358 | position: absolute; 359 | cursor: default; 360 | z-index: 4; 361 | } 362 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent } 363 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } 364 | 365 | .CodeMirror-lines { 366 | cursor: text; 367 | min-height: 1px; /* prevents collapsing before first draw */ 368 | } 369 | .CodeMirror pre { 370 | /* Reset some styles that the rest of the page might have set */ 371 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 372 | border-width: 0; 373 | background: transparent; 374 | font-family: inherit; 375 | font-size: inherit; 376 | margin: 0; 377 | white-space: pre; 378 | word-wrap: normal; 379 | line-height: inherit; 380 | color: inherit; 381 | z-index: 2; 382 | position: relative; 383 | overflow: visible; 384 | -webkit-tap-highlight-color: transparent; 385 | -webkit-font-variant-ligatures: contextual; 386 | font-variant-ligatures: contextual; 387 | } 388 | .CodeMirror-wrap pre { 389 | word-wrap: break-word; 390 | white-space: pre-wrap; 391 | word-break: normal; 392 | } 393 | 394 | .CodeMirror-linebackground { 395 | position: absolute; 396 | left: 0; right: 0; top: 0; bottom: 0; 397 | z-index: 0; 398 | } 399 | 400 | .CodeMirror-linewidget { 401 | position: relative; 402 | z-index: 2; 403 | padding: 0.1px; /* Force widget margins to stay inside of the container */ 404 | } 405 | 406 | .CodeMirror-widget {} 407 | 408 | .CodeMirror-rtl pre { direction: rtl; } 409 | 410 | .CodeMirror-code { 411 | outline: none; 412 | } 413 | 414 | /* Force content-box sizing for the elements where we expect it */ 415 | .CodeMirror-scroll, 416 | .CodeMirror-sizer, 417 | .CodeMirror-gutter, 418 | .CodeMirror-gutters, 419 | .CodeMirror-linenumber { 420 | -moz-box-sizing: content-box; 421 | box-sizing: content-box; 422 | } 423 | 424 | .CodeMirror-measure { 425 | position: absolute; 426 | width: 100%; 427 | height: 0; 428 | overflow: hidden; 429 | visibility: hidden; 430 | } 431 | 432 | .CodeMirror-cursor { 433 | position: absolute; 434 | pointer-events: none; 435 | } 436 | .CodeMirror-measure pre { position: static; } 437 | 438 | div.CodeMirror-cursors { 439 | visibility: hidden; 440 | position: relative; 441 | z-index: 3; 442 | } 443 | div.CodeMirror-dragcursors { 444 | visibility: visible; 445 | } 446 | 447 | .CodeMirror-focused div.CodeMirror-cursors { 448 | visibility: visible; 449 | } 450 | 451 | .CodeMirror-selected { background: #d9d9d9; } 452 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 453 | .CodeMirror-crosshair { cursor: crosshair; } 454 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 455 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 456 | 457 | .cm-searching { 458 | background-color: #ffa; 459 | background-color: rgba(255, 255, 0, .4); 460 | } 461 | 462 | /* Used to force a border model for a node */ 463 | .cm-force-border { padding-right: .1px; } 464 | 465 | @media print { 466 | /* Hide the cursor when printing */ 467 | .CodeMirror div.CodeMirror-cursors { 468 | visibility: hidden; 469 | } 470 | } 471 | 472 | /* See issue #2901 */ 473 | .cm-tab-wrap-hack:after { content: ''; } 474 | 475 | /* Help users use markselection to safely style text background */ 476 | span.CodeMirror-selectedtext { background: none; } 477 | /*! 478 | * Copyright (c) 2017 NAVER Corp. 479 | * billboard.js project is licensed under the MIT license 480 | * 481 | * billboard.js, JavaScript chart library 482 | * http://naver.github.io/billboard.js/ 483 | * 484 | * @version 1.2.0 485 | */ 486 | /*-- Chart --*/ 487 | .bb svg { 488 | font: 10px sans-serif; 489 | -webkit-tap-highlight-color: transparent; } 490 | 491 | .bb path, .bb line { 492 | fill: none; 493 | stroke: #000; } 494 | 495 | .bb text { 496 | -webkit-user-select: none; 497 | -moz-user-select: none; 498 | user-select: none; } 499 | 500 | .bb-legend-item-tile, 501 | .bb-xgrid-focus, 502 | .bb-ygrid, 503 | .bb-event-rect, 504 | .bb-bars path { 505 | shape-rendering: crispEdges; } 506 | 507 | .bb-chart-arc path { 508 | stroke: #fff; } 509 | 510 | .bb-chart-arc text { 511 | fill: #fff; 512 | font-size: 13px; } 513 | 514 | /*-- Axis --*/ 515 | /*-- Grid --*/ 516 | .bb-grid line { 517 | stroke: #aaa; } 518 | 519 | .bb-grid text { 520 | fill: #aaa; } 521 | 522 | .bb-xgrid, .bb-ygrid { 523 | stroke-dasharray: 3 3; } 524 | 525 | /*-- Text on Chart --*/ 526 | .bb-text.bb-empty { 527 | fill: #808080; 528 | font-size: 2em; } 529 | 530 | /*-- Line --*/ 531 | .bb-line { 532 | stroke-width: 1px; } 533 | 534 | /*-- Point --*/ 535 | .bb-circle._expanded_ { 536 | stroke-width: 1px; 537 | stroke: white; } 538 | 539 | .bb-selected-circle { 540 | fill: white; 541 | stroke-width: 2px; } 542 | 543 | /*-- Bar --*/ 544 | .bb-bar { 545 | stroke-width: 0; } 546 | .bb-bar._expanded_ { 547 | fill-opacity: 0.75; } 548 | 549 | /*-- Focus --*/ 550 | .bb-target.bb-focused { 551 | opacity: 1; } 552 | 553 | .bb-target.bb-focused path.bb-line, .bb-target.bb-focused path.bb-step { 554 | stroke-width: 2px; } 555 | 556 | .bb-target.bb-defocused { 557 | opacity: 0.3 !important; } 558 | 559 | /*-- Region --*/ 560 | .bb-region { 561 | fill: steelblue; 562 | fill-opacity: .1; } 563 | 564 | /*-- Brush --*/ 565 | .bb-brush .extent { 566 | fill-opacity: .1; } 567 | 568 | /*-- Select - Drag --*/ 569 | /*-- Legend --*/ 570 | .bb-legend-item { 571 | font-size: 12px; } 572 | 573 | .bb-legend-item-hidden { 574 | opacity: 0.15; } 575 | 576 | .bb-legend-background { 577 | opacity: 0.75; 578 | fill: white; 579 | stroke: lightgray; 580 | stroke-width: 1; } 581 | 582 | /*-- Title --*/ 583 | .bb-title { 584 | font: 14px sans-serif; } 585 | 586 | /*-- Tooltip --*/ 587 | .bb-tooltip-container { 588 | z-index: 10; } 589 | 590 | .bb-tooltip { 591 | border-collapse: collapse; 592 | border-spacing: 0; 593 | background-color: #fff; 594 | empty-cells: show; 595 | opacity: 0.9; 596 | -webkit-box-shadow: 7px 7px 12px -9px #777777; 597 | -moz-box-shadow: 7px 7px 12px -9px #777777; 598 | box-shadow: 7px 7px 12px -9px #777777; } 599 | .bb-tooltip tr { 600 | border: 1px solid #CCC; } 601 | .bb-tooltip th { 602 | background-color: #aaa; 603 | font-size: 14px; 604 | padding: 2px 5px; 605 | text-align: left; 606 | color: #FFF; } 607 | .bb-tooltip td { 608 | font-size: 13px; 609 | padding: 3px 6px; 610 | background-color: #fff; 611 | border-left: 1px dotted #999; } 612 | .bb-tooltip td > span, .bb-tooltip td > svg { 613 | display: inline-block; 614 | width: 10px; 615 | height: 10px; 616 | margin-right: 6px; } 617 | .bb-tooltip td.value { 618 | text-align: right; } 619 | 620 | /*-- Area --*/ 621 | .bb-area { 622 | stroke-width: 0; 623 | opacity: 0.2; } 624 | 625 | /*-- Arc --*/ 626 | .bb-chart-arcs-title { 627 | dominant-baseline: middle; 628 | font-size: 1.3em; } 629 | 630 | .bb-chart-arcs .bb-chart-arcs-background { 631 | fill: #e0e0e0; 632 | stroke: none; } 633 | 634 | .bb-chart-arcs .bb-chart-arcs-gauge-unit { 635 | fill: #000; 636 | font-size: 16px; } 637 | 638 | .bb-chart-arcs .bb-chart-arcs-gauge-max { 639 | fill: #777; } 640 | 641 | .bb-chart-arcs .bb-chart-arcs-gauge-min { 642 | fill: #777; } 643 | 644 | .bb-chart-arc .bb-gauge-value { 645 | fill: #000; }/** app.scss **/ 646 | html { 647 | padding: 10px; } 648 | 649 | #app { 650 | border: 1px solid #d7d7d7; 651 | position: relative; 652 | height: 100%; 653 | box-sizing: border-box; } 654 | 655 | #wrapper { 656 | height: 100%; } 657 | 658 | .ReactCodeMirror { 659 | width: 100%; 660 | height: auto; 661 | position: absolute; } 662 | 663 | .guide_card .ReactCodeMirror { 664 | width: auto; 665 | height: auto; 666 | position: relative; } 667 | 668 | .muidocs-icon-custom-github:before { 669 | content: "github"; } 670 | 671 | .CodeMirror { 672 | width: 100%; 673 | height: 100%; } 674 | 675 | .type { 676 | color: #009688; } 677 | 678 | .err { 679 | background: #ffd9d9; } 680 | 681 | .bbtitle { 682 | width: 60%; 683 | height: 10%; 684 | margin: 0; 685 | left: 0; 686 | text-align: center; 687 | font-family: 'Roboto', sans-serif; 688 | font-size: 25px; } 689 | 690 | .bbtitle .logo { 691 | display: inline-block; 692 | width: 145px; 693 | height: 33px; 694 | margin-left: 10px; 695 | background-repeat: no-repeat; 696 | position: relative; 697 | top: 9px; 698 | box-sizing: content-box; 699 | background-color: #fff; 700 | border-style: none; } 701 | 702 | .chart { 703 | width: 60%; 704 | height: 40%; 705 | margin: 0; 706 | left: 0; } 707 | 708 | .tabviews { 709 | width: 60%; 710 | height: 50%; 711 | margin: 0; 712 | left: 0; } 713 | 714 | .textConfigure, .data-table { 715 | width: 100%; } 716 | 717 | .data-table .remove-data { 718 | text-align: center; 719 | opacity: 0; } 720 | 721 | .data-table .add-data { 722 | position: relative; 723 | text-align: center; } 724 | 725 | .data-table .add-data.column { 726 | top: 33px; } 727 | 728 | .data-table .add-data .row { 729 | top: 3px; } 730 | 731 | .data-table .header-text:hover .remove-data { 732 | opacity: 1; } 733 | 734 | .data-table .table-row:hover .remove-data { 735 | opacity: 1; } 736 | 737 | .layer_wrapper { 738 | margin: 10px; } 739 | 740 | .bb { 741 | height: 100%; } 742 | 743 | .inputConfigure { 744 | width: 40%; 745 | left: 60%; 746 | position: absolute; 747 | height: 100%; 748 | top: 0; 749 | border-left: 1px solid #d7d7d7; 750 | box-sizing: border-box; } 751 | 752 | .scroll { 753 | width: 100%; 754 | height: 100%; 755 | overflow-y: scroll; } 756 | 757 | .property_clear { 758 | opacity: 0; } 759 | 760 | .property.activated .property_clear { 761 | opacity: 1; } 762 | 763 | .property_content { 764 | display: inline-block; } 765 | 766 | .property:hover .edit_function { 767 | opacity: 1; } 768 | 769 | .property.updated { 770 | color: red; } 771 | 772 | .property.deactivated { 773 | opacity: 0.5; } 774 | 775 | .property.deactivated .activate_check { 776 | opacity: 0; } 777 | 778 | .property:hover .activate_check { 779 | opacity: 1; } 780 | 781 | .react-toolbox_checkbox { 782 | display: inline-block; } 783 | 784 | .property_name { 785 | color: #424242; 786 | font-weight: 500; } 787 | 788 | .property .material-icons { 789 | font-size: 20px; 790 | padding-left: 10px; } 791 | 792 | .property .property_info { 793 | top: 6px; 794 | padding-left: 12px; } 795 | 796 | .property .property_info, .property .edit_code_property { 797 | opacity: 0; } 798 | 799 | .property:hover .property_info, .property:hover .edit_code_property { 800 | opacity: 1; } 801 | 802 | .property_type { 803 | font-size: 14px; 804 | line-height: 16px; 805 | height: 16px; 806 | margin: 0; 807 | margin-top: 4px; 808 | color: rgba(0, 0, 0, 0.54); 809 | overflow: hidden; 810 | text-overflow: ellipsis; 811 | white-space: nowrap; } 812 | 813 | .member svg { 814 | color: #ff8a65 !important; } 815 | 816 | .number_guide { 817 | font-size: 14px; 818 | line-height: 24px; 819 | font-family: Roboto, sans-serif; 820 | text-align: center; } 821 | 822 | .codepen-export { 823 | position: absolute; 824 | right: 60px; 825 | z-index: 1000; } 826 | 827 | .codepen-export input[type="submit"] { 828 | border: none; 829 | width: 60px; 830 | height: 40px; 831 | background: none; 832 | display: inline-block; } 833 | 834 | .codepen-export .button { 835 | background-size: contain !important; 836 | border: none; 837 | width: 60px !important; 838 | height: 40px !important; 839 | background-image: url("data:image/svg+xml,%3C?xml version='1.0' encoding='utf-8'?%3E %3C!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E %3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E %3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='179.851px' height='120.095px' viewBox='0 0 179.851 120.095' enable-background='new 0 0 179.851 120.095' xml:space='preserve'%3E %3Cg%3E %3Cg%3E %3Crect x='104.055' y='54.547' fill='%23EFEFEF' width='48.38' height='11'/%3E %3C/g%3E %3Cg%3E %3Cg%3E %3Cpath fill='%23EFEFEF' d='M149.336,60.047c-8.046-8.046-11.881-23.957-12.196-34.996c11.031,14.821,25.367,27.747,42.711,34.996 c-17.344,6.941-31.048,20.649-42.711,34.996C138.087,83.223,140.975,68.875,149.336,60.047z'/%3E %3C/g%3E %3C/g%3E %3C/g%3E %3Cg%3E %3Cpolygon fill='%23EFEFEF' points='29.301,54.708 29.301,65.388 37.284,60.048 '/%3E %3Cpolygon fill='%23EFEFEF' points='56.856,46.956 56.856,32.073 31.855,48.739 43.023,56.21 '/%3E %3Cpolygon fill='%23EFEFEF' points='88.239,48.739 63.238,32.073 63.238,46.956 77.072,56.21 '/%3E %3Cpolygon fill='%23EFEFEF' points='31.855,71.356 56.856,88.022 56.856,73.14 43.023,63.888 '/%3E %3Cpolygon fill='%23EFEFEF' points='63.238,73.14 63.238,88.022 88.239,71.356 77.072,63.888 '/%3E %3Cpolygon fill='%23EFEFEF' points='60.047,52.499 48.762,60.048 60.047,67.597 71.333,60.048 '/%3E %3Cpath fill='%23EFEFEF' d='M60.047,0C26.885,0,0,26.884,0,60.048s26.885,60.047,60.047,60.047c33.164,0,60.048-26.883,60.048-60.047 S93.211,0,60.047,0z M97.175,71.36c0,0.141-0.01,0.279-0.028,0.418c-0.007,0.045-0.018,0.092-0.025,0.137 c-0.017,0.09-0.032,0.18-0.056,0.268c-0.014,0.053-0.033,0.104-0.05,0.154c-0.025,0.078-0.051,0.156-0.082,0.234 c-0.021,0.053-0.048,0.104-0.071,0.154c-0.034,0.072-0.069,0.143-0.108,0.213c-0.028,0.049-0.061,0.098-0.091,0.146 c-0.043,0.066-0.087,0.131-0.135,0.193c-0.035,0.049-0.071,0.094-0.11,0.139c-0.05,0.059-0.103,0.117-0.158,0.172 c-0.042,0.043-0.083,0.086-0.127,0.125c-0.058,0.053-0.119,0.104-0.182,0.152c-0.047,0.037-0.094,0.074-0.144,0.109 c-0.019,0.012-0.035,0.027-0.054,0.039L61.817,96.64c-0.536,0.357-1.152,0.537-1.771,0.537c-0.616,0-1.233-0.18-1.77-0.537 L24.34,74.015c-0.018-0.012-0.034-0.027-0.052-0.039c-0.05-0.035-0.098-0.072-0.145-0.109c-0.062-0.049-0.123-0.1-0.181-0.152 c-0.044-0.039-0.086-0.082-0.128-0.125c-0.056-0.055-0.107-0.113-0.159-0.172c-0.037-0.045-0.074-0.09-0.109-0.139 c-0.047-0.062-0.092-0.127-0.134-0.193c-0.032-0.049-0.062-0.098-0.092-0.146c-0.039-0.07-0.074-0.141-0.108-0.213 c-0.024-0.051-0.049-0.102-0.071-0.154c-0.031-0.078-0.058-0.156-0.082-0.234c-0.018-0.051-0.035-0.102-0.05-0.154 c-0.023-0.088-0.039-0.178-0.056-0.268c-0.008-0.045-0.02-0.092-0.025-0.137c-0.019-0.139-0.029-0.277-0.029-0.418V48.735 c0-0.141,0.011-0.279,0.029-0.416c0.006-0.047,0.018-0.092,0.025-0.139c0.017-0.09,0.032-0.18,0.056-0.268 c0.015-0.053,0.032-0.104,0.05-0.154c0.024-0.078,0.051-0.156,0.082-0.232c0.022-0.053,0.047-0.105,0.071-0.156 c0.034-0.072,0.069-0.143,0.108-0.211c0.029-0.051,0.06-0.1,0.092-0.148c0.042-0.066,0.087-0.131,0.134-0.193 c0.035-0.047,0.072-0.094,0.109-0.139c0.052-0.059,0.104-0.117,0.159-0.172c0.042-0.043,0.084-0.086,0.128-0.125 c0.058-0.053,0.118-0.104,0.181-0.152c0.047-0.037,0.095-0.074,0.145-0.109c0.018-0.012,0.034-0.027,0.052-0.039l33.938-22.625 c1.072-0.715,2.468-0.715,3.54,0l33.937,22.625c0.019,0.012,0.035,0.027,0.054,0.039c0.05,0.035,0.097,0.072,0.144,0.109 c0.062,0.049,0.124,0.1,0.182,0.152c0.044,0.039,0.085,0.082,0.127,0.125c0.056,0.055,0.108,0.113,0.158,0.172 c0.039,0.045,0.075,0.092,0.11,0.139c0.048,0.062,0.092,0.127,0.135,0.193c0.03,0.049,0.062,0.098,0.091,0.148 c0.039,0.068,0.074,0.139,0.108,0.211c0.023,0.051,0.05,0.104,0.071,0.156c0.031,0.076,0.057,0.154,0.082,0.232 c0.017,0.051,0.036,0.102,0.05,0.154c0.023,0.088,0.039,0.178,0.056,0.268c0.008,0.047,0.019,0.092,0.025,0.139 c0.019,0.137,0.028,0.275,0.028,0.416V71.36z'/%3E %3Cpolygon fill='%23EFEFEF' points='90.794,65.388 90.794,54.708 82.812,60.048 '/%3E %3C/g%3E %3C/svg%3E") !important; } 840 | 841 | .drawer_close { 842 | transform: translate(-10px, 200px) !important; } 843 | 844 | .drawer_open { 845 | transform: translate(-10px, -10px) !important; } 846 | 847 | .table-row:hover .table-row-column { 848 | opacity: 1; } 849 | 850 | .table-row-column { 851 | opacity: 0; } 852 | 853 | /** app.css **/ 854 | .guide_card .example { 855 | padding: 10px !important; 856 | background: #F5F5F5; 857 | font-size: 13px; } 858 | 859 | .guide_card .text_title { 860 | font-weight: 500; 861 | margin-bottom: 9px; 862 | margin-top: 22px; 863 | font-family: 'Roboto', sans-serif; } 864 | 865 | .text_content { 866 | font-weight: 400; 867 | font-family: 'Roboto', sans-serif; } 868 | 869 | /*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */ 870 | .prettyprint { 871 | font-family: Menlo, "Bitstream Vera Sans Mono", "DejaVu Sans Mono", Monaco, Consolas, monospace; 872 | border: 0 !important; } 873 | 874 | .pln { 875 | color: #333; } 876 | 877 | /* Specify class=linenums on a pre to get line numbering */ 878 | ol.linenums { 879 | margin-top: 0; 880 | margin-bottom: 0; 881 | color: #cccccc; } 882 | 883 | li.L0, 884 | li.L1, 885 | li.L2, 886 | li.L3, 887 | li.L4, 888 | li.L5, 889 | li.L6, 890 | li.L7, 891 | li.L8, 892 | li.L9 { 893 | padding-left: 1em; 894 | background-color: #fff; 895 | list-style-type: decimal; } 896 | 897 | @media screen { 898 | /* string content */ 899 | .str { 900 | color: #d14; } 901 | /* keyword */ 902 | .kwd { 903 | color: #333; } 904 | /* comment */ 905 | .com { 906 | color: #998; } 907 | /* type name */ 908 | .typ { 909 | color: #458; } 910 | /* literal value */ 911 | .lit { 912 | color: #458; } 913 | /* punctuation */ 914 | .pun { 915 | color: #333; } 916 | /* lisp open bracket */ 917 | .opn { 918 | color: #333; } 919 | /* lisp close bracket */ 920 | .clo { 921 | color: #333; } 922 | /* markup tag name */ 923 | .tag { 924 | color: #000080; } 925 | /* markup attribute name */ 926 | .atn { 927 | color: #008080; } 928 | /* markup attribute value */ 929 | .atv { 930 | color: #d14; } 931 | /* declaration */ 932 | .dec { 933 | color: #333; } 934 | /* variable name */ 935 | .var { 936 | color: #008080; } 937 | /* function name */ 938 | .fun { 939 | color: #900; } } 940 | --------------------------------------------------------------------------------