├── .gitignore ├── LICENCE ├── README.md ├── docs ├── index.html └── index.js ├── img.png ├── jsconfig.json ├── package.json ├── postcss.config.js ├── src ├── actions │ └── index.jsx ├── components │ ├── App.jsx │ ├── App.scss │ ├── IndentTextarea.jsx │ ├── Link.jsx │ ├── Pin.jsx │ └── PointLink.jsx ├── containers │ ├── Balloons.jsx │ ├── Balloons.scss │ ├── Block.jsx │ ├── Block.scss │ ├── BlockCreator.jsx │ ├── BlockCreator.scss │ ├── Chain.jsx │ ├── Chain.scss │ ├── HTMLEditor.jsx │ ├── HTMLRenderer.jsx │ ├── HTMLRenderer.scss │ └── PinLink.jsx ├── cosine-curve-points.jsx ├── index.jsx ├── index.pug ├── index.scss ├── models │ └── index.jsx ├── reducers │ ├── balloons.jsx │ ├── block-creator.jsx │ ├── blocks.jsx │ ├── html-editor.jsx │ ├── index.jsx │ ├── pin-links.jsx │ └── point-link.jsx └── shared │ ├── util.scss │ └── vars.scss ├── webpack.config.babel.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/4caa9bfad9c46254724a3ff3c1f6b1c4c2a687a0/node.gitignore 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directories 29 | node_modules 30 | jspm_packages 31 | 32 | # Optional npm cache directory 33 | .npm 34 | 35 | # Optional REPL history 36 | .node_repl_history 37 | 38 | 39 | ### https://raw.github.com/github/gitignore/4caa9bfad9c46254724a3ff3c1f6b1c4c2a687a0/Global/osx.gitignore 40 | 41 | .DS_Store 42 | .AppleDouble 43 | .LSOverride 44 | 45 | # Icon must end with two \r 46 | Icon 47 | 48 | # Thumbnails 49 | ._* 50 | 51 | # Files that might appear in the root of a volume 52 | .DocumentRevisions-V100 53 | .fseventsd 54 | .Spotlight-V100 55 | .TemporaryItems 56 | .Trashes 57 | .VolumeIcon.icns 58 | 59 | # Directories potentially created on remote AFP share 60 | .AppleDB 61 | .AppleDesktop 62 | Network Trash Folder 63 | Temporary Items 64 | .apdisk 65 | 66 | 67 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Hiroki Usuba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chain 2 | 3 | A New Visual Programming Environment to Build JavaScript By Linking Blocks. 4 | 5 | Also, you can coedit this by using [Cochain](https://github.com/mimorisuzuko/chain/tree/feature/co). 6 | 7 | ## Example: 8 | 9 | `'Hello, Chain!'` => `'!niahC ,olleH'` 10 | 11 | ![](img.png) 12 | 13 | ### JavaScript 14 | 15 | ```javascript 16 | Array.from('Hello, ' + 'World!').reverse().join('') 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Chain: A New Visual Programming Language to Build a Program Like JavaScript
-------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mimorisuzuko/chain/000cc7bca863da4411eb65f7f387ad1950a29ac7/img.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "module": "amd", 6 | "target": "ES6" 7 | }, 8 | "exclude": [ 9 | "node_modules", 10 | "docs" 11 | ] 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chain", 3 | "version": "0.0.1", 4 | "description": "A New Visual Programming Environment to Build JavaScript By Linking Blocks", 5 | "main": "docs/index.htnl", 6 | "scripts": { 7 | "webpack": "./node_modules/.bin/webpack --config webpack.config.babel.js", 8 | "build": "npm-run-all build:*", 9 | "build:pug": "./node_modules/.bin/pug --hierarchy -o docs/ src/", 10 | "build:js": "npm run webpack", 11 | "watch": "npm-run-all --parallel watch:*", 12 | "watch:pug": "npm run build:pug -- -w", 13 | "watch:js": "WATCH=true ./node_modules/.bin/webpack-dev-server --config webpack.config.babel.js" 14 | }, 15 | "keywords": [], 16 | "author": "Hiroki Usuba (http://mimorisuzuko.github.io/)", 17 | "license": "MIT", 18 | "dependencies": { 19 | "autobind-decorator": "^2.1.0", 20 | "immutable": "^3.8.1", 21 | "lodash": "^4.17.4", 22 | "react": "^15.6.1", 23 | "react-codemirror": "^1.0.0", 24 | "react-dom": "^15.6.1", 25 | "react-redux": "^5.0.6", 26 | "react-router-dom": "^4.1.2", 27 | "redux": "^3.7.2", 28 | "redux-actions": "^2.2.1", 29 | "redux-batched-actions": "^0.1.6" 30 | }, 31 | "devDependencies": { 32 | "autoprefixer": "^7.1.2", 33 | "babel-core": "^6.25.0", 34 | "babel-loader": "^7.1.1", 35 | "babel-plugin-react-css-modules": "^3.1.0", 36 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 37 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 38 | "babel-preset-es2015": "^6.24.1", 39 | "babel-preset-react": "^6.24.1", 40 | "clean-webpack-plugin": "^0.1.16", 41 | "css-loader": "^0.28.4", 42 | "node-sass": "^4.5.3", 43 | "npm-run-all": "^4.0.2", 44 | "postcss-loader": "^2.0.6", 45 | "postcss-scss": "^1.0.2", 46 | "pug-cli": "^1.0.0-alpha6", 47 | "react-hot-loader": "^1.3.1", 48 | "sass-loader": "^6.0.6", 49 | "style-loader": "^0.18.2", 50 | "webpack": "^3.5.1", 51 | "webpack-dev-server": "^2.7.1" 52 | }, 53 | "babel": { 54 | "plugins": [ 55 | "transform-es2015-modules-commonjs" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({}) 4 | ] 5 | }; -------------------------------------------------------------------------------- /src/actions/index.jsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { createActions } from 'redux-actions'; 3 | 4 | let blockId = -1; 5 | let balloonId = -1; 6 | 7 | export default createActions( 8 | { 9 | ADD_BLOCK: (block) => _.merge({ id: blockId += 1 }, block), 10 | UPDATE_BLOCK: (id, patch) => ({ id, patch }), 11 | DELTA_MOVE_BLOCK: (id, dx, dy) => ({ id, dx, dy }), 12 | TOGGLE_BLOCK_CREATOR: (x, y) => ({ x, y }), 13 | ADD_BALLOON: (balloon) => _.merge({ id: balloonId += 1 }, balloon) 14 | }, 15 | 'DELETE_BLOCK', 16 | 'UPDATE_BLOCK_CREATOR', 17 | 'ADD_PIN', 18 | 'DELETE_PIN', 19 | 'START_POINT_LINK', 20 | 'END_POINT_LINK', 21 | 'ADD_PIN_LINK', 22 | 'REMOVE_PIN_LINK_BY_QUERY', 23 | 'ON_CHANGE_HTML', 24 | 'CLEAR_VIEW_BLOCK', 25 | 'PUSH_VIEW_BLOCK', 26 | 'DECREMENT_BALLOONS' 27 | ); -------------------------------------------------------------------------------- /src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import Chain from '../containers/Chain'; 4 | import { createStore } from 'redux'; 5 | import state from '../reducers'; 6 | import { enableBatching } from 'redux-batched-actions'; 7 | import { HashRouter, Route, NavLink, Redirect } from 'react-router-dom'; 8 | import HTMLRenderer from '../containers/HTMLRenderer'; 9 | import HTMLEditor from '../containers/HTMLEditor'; 10 | import { BlockCreator } from '../models'; 11 | import actions from '../actions'; 12 | import Balloons from '../containers/Balloons'; 13 | import styles from './App.scss'; 14 | 15 | const store = createStore(enableBatching(state)); 16 | store.dispatch(actions.addBlock({ x: 100, y: 100, type: BlockCreator.VIEW_BLOCK })); 17 | 18 | const redirectRender = () => ; 19 | 20 | const App = () => ( 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 | Chain 33 | 34 | 35 | Editor 36 | 37 | 38 | View 39 | 40 |
41 | 42 |
43 |
44 |
45 | ); 46 | 47 | export default App; -------------------------------------------------------------------------------- /src/components/App.scss: -------------------------------------------------------------------------------- 1 | @import '../shared/vars.scss'; 2 | 3 | .wrap { 4 | width: 100%; 5 | height: 100%; 6 | background-color: $black0; 7 | 8 | footer { 9 | border-top: 1px solid $black1; 10 | color: $white0; 11 | } 12 | } 13 | 14 | .base { 15 | width: 100%; 16 | height: calc(100% - #{$footer-height}); 17 | } 18 | 19 | .link { 20 | font-weight: 100; 21 | padding: 5px 10px; 22 | color: $white1; 23 | display: inline-block; 24 | text-decoration: none; 25 | 26 | > span { 27 | padding: 3px 6px; 28 | display: inline-block; 29 | border-bottom: 1px transparent solid; 30 | } 31 | } 32 | 33 | .active { 34 | color: $white0; 35 | 36 | > span { 37 | border-bottom-color: $blue0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/IndentTextarea.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import _ from 'lodash'; 4 | import autobind from 'autobind-decorator'; 5 | 6 | class IndentTextarea extends Component { 7 | constructor() { 8 | super(); 9 | 10 | this.tab = false; 11 | this.selectionStart = -1; 12 | } 13 | 14 | render() { 15 | const { props: prev } = this; 16 | const props = _.cloneDeep(prev); 17 | delete props.onKeyDown; 18 | 19 | return