├── .babelrc
├── .gitignore
├── README.md
├── imgs
├── 1.jpeg
├── 2.png
├── 3.png
├── 4.png
└── 5.jpeg
├── package.json
├── server-user.js
├── server.js
├── src
├── main
│ ├── index.js
│ └── services
│ │ ├── application.js
│ │ ├── file.js
│ │ ├── menu.js
│ │ ├── project.js
│ │ └── window.js
├── renderer
│ ├── components
│ │ ├── .gitkeep
│ │ ├── Editor
│ │ │ ├── EditorPane.js
│ │ │ ├── EditorSelectPane.css
│ │ │ └── EditorSelectPane.js
│ │ ├── FileTree
│ │ │ └── FileTree.js
│ │ ├── Layout
│ │ │ ├── Footer.js
│ │ │ ├── Layout.css
│ │ │ ├── Pane.js
│ │ │ ├── PaneGroup.js
│ │ │ ├── TabGroup.js
│ │ │ ├── TabItem.js
│ │ │ └── index.js
│ │ └── ProjFunc
│ │ │ ├── Console.css
│ │ │ ├── Console.js
│ │ │ ├── ProjMenu.css
│ │ │ └── ProjMenu.js
│ ├── constants.js
│ ├── index.js
│ ├── models
│ │ ├── .gitkeep
│ │ ├── cns.js
│ │ ├── editor.js
│ │ ├── files.js
│ │ ├── proj.js
│ │ └── win.js
│ ├── router.js
│ ├── routes
│ │ ├── IndexPage.css
│ │ └── IndexPage.js
│ ├── services
│ │ ├── .gitkeep
│ │ ├── cns.js
│ │ ├── file.js
│ │ ├── filter.js
│ │ └── proj.js
│ └── utils.js
└── vendor.js
├── static
├── assets
│ └── README.md
├── favicon.ico
├── index.html
└── template
│ └── test
│ ├── config.json
│ └── src
│ ├── components
│ ├── App.css
│ └── App.js
│ ├── index.js
│ └── vendor.js
└── webpack
├── base.config.js
├── base.config.prod.js
├── common.config.js
├── dev.config.js
└── prod.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 |
4 | ["es2015", {"modules": false}],
5 | //"es2015-webpack",
6 |
7 | // webpack understands the native import syntax, and uses it for tree shaking
8 |
9 | "stage-2",
10 | // Specifies what level of language features to activate.
11 | // Stage 2 is "draft", 4 is finished, 0 is strawman.
12 | // See https://tc39.github.io/process-document/
13 |
14 | "react"
15 | // Transpile React components to JavaScript
16 | ],
17 | "plugins": [
18 | "react-hot-loader/babel"
19 |
20 | // Enables React code to work with HMR.
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | user.webpack.config.js
3 | user.webpack.config.prod.js
4 | stop.bat
5 | old
6 | dist
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react editor
2 | 这是一个简单的 react 编辑器, 基于electron开发。
3 |
4 |
5 | ## Getting started
6 |
7 | ```bash
8 | git clone git@github.com:Jirapo/React-editor.git
9 | cd React-editor
10 | npm install
11 | npm start
12 | npm run server
13 | ```
14 |
15 | ## Features
16 |
17 | ### repl
18 |
19 | 单页repl, 含有小型console。用户在编辑器里输入代码, 可以编译运行es6和一小段react代码, 显示运行结果。
20 | 左侧框显示常规运行结果,右侧框显示react代码结果。
21 |
22 |
23 |
24 |
25 |
26 | #### 步骤
27 |
28 | 1. 点击 'Show Console' 按钮
29 | 2. 点击 'BABEL' 控制台显示babel编译后的代码
30 | 3. 点击 'EXEC' 执行babel编译后的代码
31 | 4. 点击 'CLEAR' 清空console
32 |
33 | ### react IDE
34 |
35 | 导入react项目,不需要用户配置复杂的webpack.config.js, 这个编辑器会使用默认的webpack.config配置。
36 | 同时编辑器可以运行和打包react项目。
37 |
38 |
39 |
40 | #### 步骤
41 |
42 | 1. 请用户把 `./static/template/test` 复制到其它文件夹中
43 | 2. 在编辑器菜单中选择 `File - Add React Project`, 导入刚才的复制出去的文件夹test
44 | 3. 点击 'start' 按钮, 在浏览器中输入 `http://localhost:3001/` (端口号为 `config.json` 中的 `port` 字段值)
45 | 4. 点击 'Stop' 按钮,编辑器停止该端口的监听,并停止该项目
46 | 5. 点击 'Build' 按钮,编辑器将会打包该项目,打包目录为 `config.json` 中的 `output` 字段值
47 |
48 | #### config.json
49 |
50 | ```javascript
51 |
52 | {
53 | "port": 3002, // listening port
54 | "entry": "src/index.js", // entry file
55 | "vendor": "src/vendor.js", // vendor file
56 | "output":"dist" // output folder
57 | }
58 |
59 | ```
60 |
61 | #### 打包目录文件
62 |
63 |
64 |
65 |
66 | ## Bugs
67 |
68 | - 文件夹没有监听。
69 |
70 | - 对于node_modules文件夹需要特殊处理。
71 |
72 | - 点击 'start', 'stop', 'build' 按钮,页面没有任何反馈。
73 |
74 | - 很多错误判断还没有写。
75 |
76 | - 样式使用postcss, 不支持less和sass。
77 |
78 | - 目前的ide流程逻辑很糟糕,需要更改。
79 |
80 | - ...
81 |
82 | ## End
83 |
84 | 请忽视文件中一大段一大段的残留代码注释……
85 |
86 |
--------------------------------------------------------------------------------
/imgs/1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/imgs/1.jpeg
--------------------------------------------------------------------------------
/imgs/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/imgs/2.png
--------------------------------------------------------------------------------
/imgs/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/imgs/3.png
--------------------------------------------------------------------------------
/imgs/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/imgs/4.png
--------------------------------------------------------------------------------
/imgs/5.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/imgs/5.jpeg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ele-demo",
3 | "version": "1.0.0",
4 | "description": "an simple react IDE, made by electron",
5 | "main": "./src/main/index.js",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=development electron ./src/main/index.js",
8 | "start-build": "cross-env NODE_ENV=production electron ./src/main/index.js",
9 | "server": "node server.js --port 3000 ",
10 | "server-user": "node server.js --port 3001",
11 | "build": "webpack --config webpack/prod.config.js --display-error-details --color",
12 | "build-user": "webpack --config user.webpack.config.prod.js --display-error-details --color",
13 | "watch": "webpack-dev-server --config webpack/dev.config.js --watch",
14 | "watch-user": "webpack-dev-server --config user.webpack.config.js --watch",
15 | "test": "echo \"Error: no test specified\" && exit 1"
16 | },
17 | "author": "jirapo",
18 | "license": "ISC",
19 | "devDependencies": {
20 | "autoprefixer": "^6.5.4",
21 | "babel": "^6.5.2",
22 | "babel-core": "^6.20.0",
23 | "babel-loader": "^6.2.9",
24 | "babel-plugin-add-module-exports": "^0.2.1",
25 | "babel-polyfill": "^6.20.0",
26 | "babel-preset-es2015-webpack": "^6.4.3",
27 | "babel-preset-latest": "^6.16.0",
28 | "babel-preset-react": "^6.16.0",
29 | "babel-preset-stage-2": "^6.16.0",
30 | "babel-register": "^6.18.0",
31 | "babel-standalone": "^6.21.1-0",
32 | "brace": "^0.9.0",
33 | "chokidar": "^1.6.1",
34 | "cross-env": "^3.1.3",
35 | "css-loader": "^0.26.1",
36 | "del": "^2.2.2",
37 | "dva": "^1.1.0",
38 | "electron": "^1.4.12",
39 | "electron-default-menu": "^1.0.0",
40 | "extract-text-webpack-plugin": "^2.0.0-beta.4",
41 | "file-loader": "^0.9.0",
42 | "html-webpack-inline-chunk-plugin": "^1.0.0",
43 | "html-webpack-plugin": "^2.24.1",
44 | "node-dir": "^0.1.16",
45 | "node-watch": "^0.4.1",
46 | "path": "^0.12.7",
47 | "postcss-loader": "^1.2.1",
48 | "react": "^15.4.1",
49 | "react-ace": "^4.1.0",
50 | "react-dom": "^15.4.1",
51 | "react-hot-loader": "^3.0.0-beta.6",
52 | "react-redux": "^4.4.6",
53 | "react-router": "^3.0.0",
54 | "react-treebeard": "^1.1.4",
55 | "redux": "^3.6.0",
56 | "style-loader": "^0.13.1",
57 | "webpack": "^2.2.0-rc.2",
58 | "webpack-dev-server": "^2.2.0-rc.0",
59 | "webpack-hot-middleware": "^2.14.0",
60 | "webpack-merge": "^1.1.2"
61 | },
62 | "dependencies": {
63 | "chokidar": "^1.6.1",
64 | "electron": "^1.4.13",
65 | "kill-by-port": "^0.0.1",
66 | "kp": "^1.1.0",
67 | "node-watch": "^0.4.1",
68 | "webpack": "^2.2.0-rc.2"
69 | },
70 | "repository": {
71 | "type": "git",
72 | "url": "git+https://github.com/Jirapo/React-editor.git"
73 | },
74 | "bugs": {
75 | "url": "https://github.com/Jirapo/React-editor/issues"
76 | },
77 | "homepage": "https://github.com/Jirapo/React-editor#readme"
78 | }
79 |
--------------------------------------------------------------------------------
/server-user.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var webpackDevMiddleware = require("webpack-dev-middleware");
3 | var webpack = require("webpack");
4 | var path = require("path")
5 | var http = require('http');
6 |
7 | var app = express();
8 | var port = process.argv[3]
9 | // var configDir = path.resolve('./', process.argv[5])
10 | // console.log(configDir)
11 | // var webpackConfig = require(configDir)
12 | var webpackConfig = require('./user.webpack.config');
13 | var compiler = webpack(webpackConfig);
14 |
15 | // var httpServer
16 | var httpServer = http.createServer(app)
17 | var webpackDevMiddlewareInstance = webpackDevMiddleware(compiler, {
18 | // lazy: true,
19 | hot: true,
20 | stats: { colors: true, chunks: false },
21 | publicPath: "/"
22 | })
23 |
24 | app.use(webpackDevMiddlewareInstance);
25 |
26 | app.use(require('webpack-hot-middleware')(compiler));
27 |
28 | app.use(function (req, res, next) {
29 | // console.log('Time: %d', Date.now());
30 | // console.log(req.hostname)
31 | console.log(req.originalUrl)
32 | // console.log(req.params)
33 | console.log(req.method)
34 |
35 | if(req.originalUrl == '/stopProj'){
36 | console.log(222)
37 | // httpServer.close(function(){
38 | httpServer.close()
39 | process.exit();
40 | // });
41 | }else{
42 | next();
43 | }
44 | });
45 |
46 | httpServer.listen(port);
47 | // httpServer = http.createServer(app).listen(port);
48 | // var server = app.listen(port, function () {
49 | // console.log(`Listening on port ${port}!`);
50 | // });
51 | // const webpack = require('webpack');
52 | // const WebpackDevServer = require('webpack-dev-server');
53 | // const webpackConfig = require('./webpack.config')
54 | //
55 | // var bundler = new WebpackDevServer(webpack(webpackConfig), {
56 | // publicPath: '/dist/',
57 | // inline: true,
58 | // hot: true,
59 | // quiet: false,
60 | // noInfo: true,
61 | // stats: {colors: true}
62 | // });
63 | //
64 | // bundler.listen(3001, 'localhost');
65 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var webpackDevMiddleware = require("webpack-dev-middleware");
3 | var webpack = require("webpack");
4 | var path = require("path")
5 |
6 | var app = express();
7 | var port = process.argv[3]
8 | // var configDir = path.resolve('./', process.argv[5])
9 | // console.log(configDir)
10 | // var webpackConfig = require(configDir)
11 | var webpackConfig = require('./webpack/dev.config');
12 |
13 | var compiler = webpack(webpackConfig);
14 |
15 | var webpackDevMiddlewareInstance = webpackDevMiddleware(compiler, {
16 | // lazy: true,
17 | hot: true,
18 | stats: { colors: true, chunks: false },
19 | publicPath: "/"
20 | })
21 |
22 | app.use(webpackDevMiddlewareInstance);
23 |
24 | app.use(require('webpack-hot-middleware')(compiler));
25 | app.listen(port, function () {
26 | console.log(`Listening on port ${port}!`);
27 | });
28 | // var httpServer = http.createServer(app)
29 | // httpServer.listen(port);
30 | /*
31 | app.use(function (req, res, next) {
32 | // console.log('Time: %d', Date.now());
33 | // console.log(req.hostname)
34 | console.log(req.originalUrl)
35 | // console.log(req.params)
36 | console.log(req.method)
37 |
38 | if(req.originalUrl == '/stopProj'){
39 | console.log(222)
40 | httpServer.close(function(){
41 | console.log(111)
42 | process.exit();
43 | });
44 | }else{
45 | next();
46 | }
47 | });*/
48 |
49 |
50 | // httpServer = http.createServer(app).listen(port);
51 | // var server = app.listen(port, function () {
52 | // console.log(`Listening on port ${port}!`);
53 | // });
54 | // const webpack = require('webpack');
55 | // const WebpackDevServer = require('webpack-dev-server');
56 | // const webpackConfig = require('./webpack.config')
57 | //
58 | // var bundler = new WebpackDevServer(webpack(webpackConfig), {
59 | // publicPath: '/dist/',
60 | // inline: true,
61 | // hot: true,
62 | // quiet: false,
63 | // noInfo: true,
64 | // stats: {colors: true}
65 | // });
66 | //
67 | // bundler.listen(3001, 'localhost');
68 |
--------------------------------------------------------------------------------
/src/main/index.js:
--------------------------------------------------------------------------------
1 | // import { app, BrowserWindow } from 'electron';
2 | // import { join } from 'path';
3 | // import * as application from './services/application';
4 | // import * as window from './services/window';
5 | // import * as menu from './services/menu';
6 | // import * as file from './services/file';
7 |
8 | const { app, BrowserWindow } = require('electron')
9 | const { join } = require('path')
10 | const application = require('./services/application')
11 | const window = require('./services/window')
12 | const menu = require('./services/menu')
13 | const file = require('./services/file')
14 | // const project = require('./services/project')
15 |
16 | console.log('(main/index) >>>>>>>>>>>>>>>>>>');
17 | console.log('(main/index) app start');
18 |
19 | // if (is.dev()) {
20 | // require('electron-debug')();
21 | // }
22 |
23 | app.on('ready', () => {
24 | console.log('(main/index) app ready');
25 | application.init();
26 | menu.init();
27 |
28 | });
29 |
30 | app.on('window-all-closed', () => {
31 | if (process.platform !== 'darwin') {
32 | app.quit();
33 | }
34 | });
35 |
36 | app.on('activate', () => {
37 | if (window.getCount() === 0) {
38 | application.init();
39 | }
40 | });
41 |
42 | app.on('quit', () => {
43 | console.log('(main/index) app quit');
44 | console.log('(main/index) <<<<<<<<<<<<<<<<<<<');
45 |
46 | if(process.platform == 'darwin'){
47 | const exec = require('child_process').exec;
48 | // exec()
49 | // spawn('npm run kill', ['--port', '3001'])
50 | }
51 |
52 | // if(process.platform == 'win32'){
53 | // const exec = require('child_process').exec;
54 | // exec('start stop.bat',{
55 | // // cwd: files.pathDir
56 | // }, (error, stdout, stderr) => {
57 | // if (error) {
58 | // console.error(`exec error: ${error}`);
59 | // return;
60 | // }
61 | // console.log(stdout);
62 | // console.log(stderr);
63 | // });
64 | // }
65 |
66 | });
67 |
68 | // Register to global, so renderer can access these with remote.getGlobal
69 | global.services = {
70 | application,
71 | window,
72 | file,
73 | // project
74 | };
75 |
--------------------------------------------------------------------------------
/src/main/services/application.js:
--------------------------------------------------------------------------------
1 | /* global $dirname */
2 | const { join } = require('path')
3 | const { create } = require('./window')
4 |
5 |
6 |
7 | var isProd = process.env.NODE_ENV === 'production'
8 |
9 | module.exports = {
10 | init: function () {
11 | const win = create({ width: 800, height: 600 });
12 | if(isProd){
13 | win.loadURL(`file://${join(__dirname, '../../../dist')}/index.html`);
14 | }else{
15 | win.loadURL('http://localhost:3000/index.html')
16 | }
17 | },
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/services/file.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 | const fs = require('fs')
4 | const dir = require('node-dir')
5 | const chokidar = require('chokidar')
6 | const {dialog, ipcMain} = require('electron')
7 |
8 | // Opens the file.
9 | const openFile = (focusWindow) => {
10 | dialog.showOpenDialog({ properties: ['openFile'] }, (fileNames) => {
11 | if (!fileNames) {
12 | return false;
13 | } else {
14 | const file = path.parse(fileNames[0])
15 | console.log(fileNames)
16 | fs.readFile(fileNames[0], 'utf-8',
17 | function (err, data) {
18 | if(err){
19 | return false;
20 | } else {
21 | return focusWindow
22 | .webContents
23 | .send(
24 | 'winOpenFile' , {
25 | // basename: file.base,
26 | // sep: path.sep,
27 | contents: data,
28 | file: file,
29 | path: fileNames[0]
30 | // dir: file.dir,
31 | // extension: file.ext
32 | });
33 | }
34 | });
35 | }
36 | });
37 | };
38 |
39 | // Opens a folder.
40 | const openFolder = (focusWindow) => {
41 | dialog.showOpenDialog({ properties: ['openDirectory'] }, (fileNames) => {
42 | if (!fileNames) {
43 | return false;
44 | } else {
45 | console.log('preload', fileNames)
46 |
47 |
48 |
49 | // var watcher = chokidar.watch(fileNames[0], {
50 | // // ignored: /[\/\\]\./,
51 | // persistent: true
52 | // });
53 |
54 | // watcher
55 | // .on('add', path => {
56 | // // console.log(path)
57 | // reading(fileNames[0])
58 | // })
59 | // .on('unlink', path => {
60 | // // console.log(path)
61 | // reading(fileNames[0])
62 | // })
63 |
64 |
65 | dir.paths(fileNames[0],
66 | function(err, paths) {
67 | if (err) throw err;
68 | console.log('files:\n',paths.files);
69 | console.log('subdirs:\n', paths.dirs);
70 | const files = paths.files.filter(p => path.basename(p) != '.DS_Store')
71 |
72 | return focusWindow
73 | .webContents
74 | .send(
75 | 'winOpenFolder' , {
76 | sep: path.sep,
77 | list: files,
78 | dirs: paths.dirs,
79 | folder: fileNames[0],
80 | });
81 | });
82 |
83 | }
84 | });
85 | };
86 |
87 |
88 |
89 | module.exports = {
90 | openFile,
91 | openFolder,
92 | }
93 |
94 | // Promise.all([System.import('chokidar')])
95 | // .then(([chokidar]) => {
96 | // var watcher = chokidar.watch(data.folder, {
97 | // // ignored: /[\/\\]\./,
98 | // persistent: true
99 | // });
100 |
101 | // watcher
102 | // .on('addDir', path => {
103 | // // console.log(path)
104 | // reading()
105 | // })
106 | // .on('unlinkDir', path => {
107 | // // console.log(path)
108 | // reading()
109 | // })
110 | // .on('add', path => {
111 | // // console.log(path)
112 | // reading()
113 | // })
114 | // .on('unlink', path => {
115 | // // console.log(path)
116 | // reading()
117 | // })
118 |
119 | // function reading(){
120 | // dir.paths(data.folder, (err, paths) => {
121 | // const d = {
122 | // sep: data.sep,
123 | // list: paths.files,
124 | // dirs: paths.dirs,
125 | // folder: data.folder,
126 | // }
127 | // // const newFd = {...formatFile(d), originTree};
128 | // const newFd = formatFile(d);
129 | // const newTree = addFileID(newFd, 0)
130 | // originTree = newTree;
131 |
132 | // // console.log(newTree)
133 |
134 | // dispatch({
135 | // type: 'changeStatus',
136 | // payload: {
137 | // // pathDir: data.pathDir,
138 | // // folder: data.folder,
139 | // treeList: newTree
140 | // }
141 | // });
142 | // })
143 | // }
144 |
145 | // });
146 |
147 | /*dir.readFiles(fileNames[0],
148 | {
149 | excludeDir: ['node_modules', 'bin'],
150 | exclude:['.DS_Store']
151 | },
152 | function(err, content, filename, next) {
153 | if (err) throw err;
154 | // console.log(filename, path.basename(filename), path.parse(filename))
155 | console.log('cure',filename)
156 | // console.log('content:', content);
157 | next();
158 | },
159 | function(err, files){
160 | if (err) throw err;
161 | console.log('finished reading forlder:', files);
162 | return focusWindow
163 | .webContents
164 | .send(
165 | 'folderOpened' , {
166 | // basename: file.base,
167 | sep: path.sep,
168 | list: files,
169 | path: fileNames[0]
170 | // dir: file.dir,
171 | // extension: file.ext
172 | });
173 | // return () => {}
174 | });*/
175 |
176 | // const _p = paths.dirs.map( p => path.basename(p))
177 |
178 | /*dir.subdirs(fileNames[0],
179 | function(err, subdirs) {
180 | if (err) throw err;
181 | console.log(subdirs);
182 | return () => {}
183 | });*/
--------------------------------------------------------------------------------
/src/main/services/menu.js:
--------------------------------------------------------------------------------
1 | // import defaultMenu from 'electron-default-menu';
2 | // import { Menu, app, shell, BrowserWindow, dialog } from 'electron';
3 | // import { openFile, openFolder} from './file';
4 | const defaultMenu = require('electron-default-menu')
5 | const { Menu, app, shell, BrowserWindow, dialog } = require('electron')
6 | const { openFile, openFolder} = require('./file')
7 |
8 | module.exports = {
9 | init () {
10 | const menu = defaultMenu(app, shell)
11 |
12 | menu.splice(1, 0, {
13 | label: 'File',
14 | submenu: [
15 | {
16 | label: 'New File',
17 | accelerator: 'CmdorCtrl+N',
18 | click: (item, focusedWindow) => {
19 | focusedWindow.webContents.send('winNewFile')
20 | // dialog.showOpenDialog({ properties: ['openFile'] }, (files) => {
21 | // console.log('file:', files)
22 | // focusedWindow.webContents.send('open-file', files[0])
23 | // })
24 | }
25 | },
26 | {
27 | label: 'Open File',
28 | accelerator: 'CmdorCtrl+O',
29 | click: (item, focusedWindow) => {
30 | return openFile(focusedWindow);
31 | // dialog.showOpenDialog({ properties: ['openFile'] }, (files) => {
32 | // console.log('file:', files)
33 | // focusedWindow.webContents.send('open-file', files[0])
34 | // })
35 | }
36 | },
37 | {
38 | label: 'Add React Project',
39 | // accelerator: 'CmdorCtrl+O',
40 | click: (item, focusedWindow) => {
41 | return openFolder(focusedWindow);
42 | // dialog.showOpenDialog({ properties: ['openDirectory'] }, (files) => {
43 | // console.log('forlder ' ,files)
44 | // focusedWindow.webContents.send('open-file', files[0])
45 | // })
46 | }
47 | },
48 | {
49 | label: 'Save File',
50 | accelerator: 'CmdorCtrl+S',
51 | click: (item, focusedWindow) => {
52 | focusedWindow.webContents.send('winSaveFile')
53 | }
54 | },
55 | {
56 | label: 'Close File',
57 | accelerator: 'CmdorCtrl+W',
58 | click: (item, focusedWindow) => {
59 | focusedWindow.webContents.send('winCloseFile')
60 | }
61 | }
62 | ]
63 | })
64 |
65 | Menu.setApplicationMenu(Menu.buildFromTemplate(menu))
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/services/project.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path')
2 | const exec = require('child_process').exec;
3 | const http = require('http')
4 |
5 | module.exports = {
6 | copy: function () {
7 |
8 | },
9 | stopProj:function (port){
10 | var options = {
11 | host: '127.0.0.1',
12 | port: port,
13 | path: '/stopProj'
14 | };
15 | // host: 'http://localhost:' + port,
16 |
17 | http.request(options, function(res) {
18 | res.on('data', (chunk) => {
19 | console.log(`BODY: ${chunk}`);
20 | });
21 | res.on('end', () => {
22 | console.log('No more data in response.');
23 | });
24 | }).end();
25 | // http.request(options);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/services/window.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow } = require('electron')
2 |
3 | let count = 0;
4 |
5 | module.exports = {
6 | getCount() {
7 | return count;
8 | },
9 | create(opts) {
10 | count += 1;
11 | let win = new BrowserWindow(opts);
12 | win.on('close', () => {
13 | count -= 1;
14 | win = null;
15 | });
16 | return win;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/renderer/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/src/renderer/components/.gitkeep
--------------------------------------------------------------------------------
/src/renderer/components/Editor/EditorPane.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import { render } from 'react-dom';
3 | import brace from 'brace';
4 | import AceEditor from 'react-ace';
5 | import { connect } from 'dva';
6 |
7 |
8 |
9 | // class EditorPane extends Component {
10 |
11 | // onChange(contents) {
12 | // // console.log('change',newValue);
13 | // const {dispatch} = this.props
14 | // dispatch({
15 | // type: 'editor/changeFile',
16 | // payload: { contents}
17 | // })
18 | // }
19 |
20 | // render(){
21 | // const {editor: { mode, theme, contents}, dispatch} = this.props
22 |
23 | // const paneProps = {
24 | // width: "100%",
25 | // height: "100%",
26 | // mode,
27 | // theme,
28 | // onChange: this.onChange.bind(this),
29 | // name: "UNIQUE_ID_OF_DIV",
30 | // editorProps: {$blockScrolling: true},
31 | // value: contents,
32 | // enableBasicAutocompletion: true
33 | // }
34 |
35 | // const AceEditorGen = () =>
36 |
37 | // return
38 | // }
39 | // }
40 |
41 | const EditorPane = (editor) => {
42 |
43 | const paneProps = {
44 | ...editor,
45 | width: "100%",
46 | height: "100%",
47 | name: "UNIQUE_ID_OF_DIV",
48 | editorProps: {$blockScrolling: true},
49 | enableBasicAutocompletion: true,
50 | enableLiveAutocompletion: true
51 | }
52 |
53 | return
54 | }
55 |
56 | EditorPane.propTypes = {};
57 |
58 | export default EditorPane;
59 |
60 | // export default connect(({ editor }) => ({ editor }))(EditorPane);
61 |
--------------------------------------------------------------------------------
/src/renderer/components/Editor/EditorSelectPane.css:
--------------------------------------------------------------------------------
1 | .select-grp{
2 |
3 | float: right;
4 | color: #fff;
5 | font-size: 12px;
6 | line-height: 22px;
7 |
8 | }
9 |
10 | .select-grp label{
11 | margin-left: 20px;
12 | }
13 |
--------------------------------------------------------------------------------
/src/renderer/components/Editor/EditorSelectPane.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'dva'
3 | import { EDITOR_THEMES, EDITOR_MODE } from '../../constants'
4 | import styles from './EditorSelectPane.css';
5 |
6 | const EditorSelectPane = ({ editor:{ mode, theme}, dispatch }) => {
7 |
8 | function changeThemeHandler(theme) {
9 | dispatch({
10 | type: 'editor/changeStatus',
11 | payload: { theme },
12 | });
13 | }
14 |
15 | function changeModeHandler(mode) {
16 | dispatch({
17 | type: 'editor/changeStatus',
18 | payload: { mode },
19 | });
20 | }
21 |
22 |
23 | return (
24 |
25 |
26 |
27 |
30 | {' '}
31 |
32 |
35 |
36 | );
37 | };
38 |
39 | EditorSelectPane.propTypes = {};
40 |
41 | export default connect(({ editor }) => ({ editor }))(EditorSelectPane);
42 |
43 |
--------------------------------------------------------------------------------
/src/renderer/components/FileTree/FileTree.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import { connect } from 'dva';
3 | import {Treebeard} from 'react-treebeard';
4 | import { remote } from 'electron'
5 | import { mapToMode } from '../../utils'
6 |
7 | class FileTree extends Component {
8 |
9 | constructor(props){
10 | super(props);
11 | this.state = {
12 | cursor: props.list
13 | };
14 | this.onToggle = this.onToggle.bind(this);
15 | }
16 |
17 | onToggle(node, toggled){
18 | const { dispatch } = this.props;
19 |
20 | if(this.state.cursor){
21 | this.state.cursor.active = false;
22 | }
23 |
24 | node.active = true;
25 |
26 | if(node.children){
27 | node.toggled = toggled;
28 | dispatch({
29 | type: 'files/toggledTree',
30 | payload: {
31 | node,
32 | }
33 | });
34 | }else{
35 |
36 | dispatch({
37 | type: 'files/openFile',
38 | payload: {
39 | path: node.path,
40 | }
41 | });
42 |
43 | }
44 |
45 | this.setState({ cursor: node });
46 | }
47 |
48 | render(){
49 | const {files: { treeList }, dispatch} = this.props
50 |
51 | return
52 | }
53 | }
54 |
55 | FileTree.propTypes = {};
56 |
57 | export default connect(({ files }) => ({ files }))(FileTree);
58 |
--------------------------------------------------------------------------------
/src/renderer/components/Layout/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import styles from './Layout.css'
4 |
5 | const Footer = (props) => {props.children}
6 |
7 | Footer.propTypes = {};
8 |
9 | export default Footer;
10 |
11 |
--------------------------------------------------------------------------------
/src/renderer/components/Layout/Layout.css:
--------------------------------------------------------------------------------
1 | html {
2 | width: 100%;
3 | height: 100%;
4 | }
5 |
6 | body{
7 | height: 100%;
8 | padding: 0;
9 | margin: 0;
10 | font-family: system,-apple-system,".SFNSDisplay-Regular","Helvetica Neue",Helvetica,"Segoe UI",sans-serif;
11 | font-size: 13px;
12 | line-height: 1.6;
13 | color: #333;
14 | background-color: transparent;
15 | }
16 |
17 | .window-content{
18 | position: relative;
19 | overflow-y: auto;
20 | display: flex;
21 | flex: 1;
22 | }
23 |
24 | .footer{
25 | padding: 0 10px;
26 | min-height: 22px;
27 | box-shadow: inset 0 1px 0 #d4d4d4;
28 | background-color: #e8e6e8;
29 | background-image: linear-gradient(to bottom,#868484 0,#616161 100%);
30 | }
31 |
32 | .panegroup{
33 | position: absolute;
34 | display: flex;
35 | bottom: 0;
36 | top: 0;
37 | left: 0;
38 | right: 0;
39 | }
40 |
41 | .pane{
42 | overflow-y: auto;
43 | flex: 1;
44 | border-left: 1px solid #4b4c48;
45 | position: relative;
46 |
47 | &:first-child{
48 | border-left: none;
49 | }
50 | }
51 |
52 | .pane.hide{
53 | display: none;
54 | }
55 |
56 | .sidebar{
57 | background-color: #21252b;
58 | min-width: 150px;
59 | max-width: 200px;
60 |
61 | }
62 |
63 | .tabgrp{
64 | margin-top: -1px;
65 | display: flex;
66 | /*border-top: 1px solid #989698;*/
67 | /*border-bottom: 1px solid #989698;*/
68 | width: 100%;
69 | height: 26px;
70 | background-color: #e8e6e6;
71 | }
72 |
73 | .tab-item{
74 | position: relative;
75 | flex: 1;
76 | padding: 3px;
77 | font-size: 12px;
78 | text-align: center;
79 | border-left: 1px solid #989698;
80 | background-color: #d4d2d4;
81 | height: 20px;
82 | cursor: pointer;
83 | max-width: 200px;
84 | }
85 |
86 | /*.tab-item:first-child {
87 | border-left: 0;
88 | }
89 | */
90 | .tab-item.active {
91 |
92 | /*background-color: red;*/
93 | background-color: #eee;
94 | /*background-image: linear-gradient(to bottom,#d4d2d4 0,#cccacc 100%);*/
95 | }
96 |
97 | .tab-item-fixed {
98 | flex: none;
99 | padding: 3px 10px;
100 | }
101 |
102 | .tab-item .ic-close-tab {
103 | position: absolute;
104 | top: 0px;
105 | left: 5px;
106 | width: 15px;
107 | height: 15px;
108 | font-size: 15px;
109 | color: #21252b;
110 | /*transition: opacity .1s linear,background-color .1s linear;*/
111 | /*border-radius: 3px;*/
112 | /*transform: translateY(-50%);*/
113 | z-index: 10;
114 | cursor: pointer;
115 | }
116 |
--------------------------------------------------------------------------------
/src/renderer/components/Layout/Pane.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from 'dva'
3 |
4 | import styles from './Layout.css'
5 |
6 | const Pane = (props) => {
7 | const newClz = styles.pane + ' ' + styles[props.className]
8 |
9 | return(
10 | {props.children}
11 | )
12 | }
13 |
14 | Pane.propTypes = {};
15 |
16 | export default connect()(Pane);
17 |
--------------------------------------------------------------------------------
/src/renderer/components/Layout/PaneGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './Layout.css'
4 |
5 | const Sidebar = (props) => {props.children}
6 |
7 | Sidebar.propTypes = {};
8 |
9 | export default Sidebar
10 |
11 |
--------------------------------------------------------------------------------
/src/renderer/components/Layout/TabGroup.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Pane from './Pane';
3 | import PaneGroup from './PaneGroup';
4 | import TabItem from './TabItem';
5 | import styles from './Layout.css';
6 |
7 | class TabGroup extends Component {
8 | constructor(props){
9 | super(props);
10 | }
11 |
12 | render(){
13 | const { tabComponents, activeIndex, onSelectItem, onCloseItem } = this.props;
14 | const tabItems = tabComponents.map((it, i) =>
15 |
23 | )
24 |
25 | const tabPanes = tabComponents.map((it, i) =>
26 | {it.comp}
29 | )
30 |
31 | return(
32 |
33 |
34 | {tabItems}
35 |
36 |
37 | {tabPanes}
38 |
39 |
40 | )
41 | }
42 | }
43 |
44 | TabGroup.propTypes = {};
45 |
46 | export default TabGroup
47 |
--------------------------------------------------------------------------------
/src/renderer/components/Layout/TabItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './Layout.css'
4 | const TabItem = ({ index, className, title, onSelectItem, onCloseItem }) => {
5 |
6 | const newClz = styles['tab-item'] + (className ? ' ' + styles[className] : '')
7 | return(
8 | {
10 | e.stopPropagation();
11 | onSelectItem(index)
12 | }}
13 | >
14 | {e.stopPropagation(); onCloseItem(index)}}>x
17 | { title }
18 |
19 | )
20 | }
21 |
22 | TabItem.propTypes = {};
23 |
24 | export default TabItem
25 |
--------------------------------------------------------------------------------
/src/renderer/components/Layout/index.js:
--------------------------------------------------------------------------------
1 | import Footer from './Footer';
2 | import Pane from './Pane';
3 | import PaneGroup from './PaneGroup';
4 | import TabGroup from './TabGroup';
5 |
6 |
7 | export default {
8 | Footer,
9 | Pane,
10 | PaneGroup,
11 | TabGroup,
12 | }
13 |
--------------------------------------------------------------------------------
/src/renderer/components/ProjFunc/Console.css:
--------------------------------------------------------------------------------
1 | .console{
2 | display: flex;
3 | background-color: #fff;
4 | height: 100px;
5 | box-shadow: inset 0 1px 0 #d4d4d4;
6 | padding: 5px 10px;
7 | overflow-y: auto;
8 | }
9 |
10 | .btn-grp{
11 | flex: 1;
12 | max-width: 60px;
13 | padding: 0 5px 0 0;
14 | }
15 |
16 | .btn{
17 | display: block;
18 | width: 60px;
19 | height: 20px;
20 | text-align: center;
21 | font-size: 12px;
22 | line-height: 12px;
23 | margin: 10px 0;
24 | /*line-height: 40px;*/
25 | }
26 |
27 | .result{
28 | flex: 1;
29 | word-break: break-all;
30 | line-height: 16px;
31 | border-left: 1px solid #ddd;
32 | padding: 10px;
33 | }
34 |
--------------------------------------------------------------------------------
/src/renderer/components/ProjFunc/Console.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDom, {findDOMNode, unmountComponentAtNode} from 'react-dom'
3 | import { connect } from 'dva'
4 |
5 | import styles from './Console.css'
6 |
7 | class Console extends Component {
8 |
9 | constructor(props){
10 | super(props);
11 | this.transCode = this.transCode.bind(this)
12 | this.clearConsole = this.clearConsole.bind(this)
13 | this.execCode = this.execCode.bind(this)
14 | }
15 |
16 | transCode() {
17 | const { files: { current, tabs }, dispatch } = this.props;
18 | const tab = tabs[current];
19 |
20 | dispatch({
21 | type: 'cns/transCode',
22 | payload: {
23 | contents: tab.contents
24 | },
25 | });
26 | }
27 |
28 | execCode(){
29 | const { files: { current, tabs }, dispatch } = this.props;
30 | const tab = tabs[current];
31 |
32 | dispatch({
33 | type: 'cns/execCode',
34 | payload: {
35 | mount: findDOMNode(this.refs.result),
36 | contents: tab.contents
37 | },
38 | });
39 | }
40 |
41 | clearConsole() {
42 | unmountComponentAtNode(this.refs.result)
43 | this.props.dispatch({
44 | type: 'cns/changeStatus',
45 | payload: { content: '', err: '' },
46 | });
47 | }
48 |
49 |
50 | render(){
51 | const { cns: {content, err, isExec}, proj } = this.props;
52 | let cnt = err || proj.err ? { err || proj.err }
53 | : { content };
54 |
55 | const showResult = isExec ? 'block' : 'none';
56 |
57 | return(
58 |
59 |
60 |
61 |
62 |
63 |
64 |
{ cnt }
65 |
66 |
67 | )
68 | }
69 | }
70 |
71 | Console.propTypes = {};
72 |
73 | export default connect(({ cns, files, proj }) => ({ cns, files, proj }))(Console);
74 |
75 |
--------------------------------------------------------------------------------
/src/renderer/components/ProjFunc/ProjMenu.css:
--------------------------------------------------------------------------------
1 | .proj-footer-grp{
2 | float: right;
3 | }
4 | .proj-footer-grp button{
5 | margin-left: 10px;
6 | height: 18px;
7 | margin-top: 3px;
8 | line-height: 12px;
9 | border-radius: 3px;
10 | border: 1px solid #717070;
11 | background-color: #fff;
12 | }
13 |
--------------------------------------------------------------------------------
/src/renderer/components/ProjFunc/ProjMenu.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'dva'
3 | import styles from './ProjMenu.css';
4 |
5 | const ProjMenu = ({ cns, files, proj, dispatch }) => {
6 |
7 | function showConsole(show){
8 |
9 | dispatch({
10 | type: 'cns/changeStatus',
11 | payload: { show },
12 | });
13 |
14 | }
15 |
16 | function startProj(){
17 | dispatch({
18 | type: 'proj/startProj',
19 | });
20 | }
21 |
22 | function stopProj(){
23 | dispatch({
24 | type: 'proj/stopProj',
25 | });
26 | }
27 |
28 | function buildProj(){
29 | dispatch({
30 | type: 'proj/buildProj',
31 | });
32 | }
33 |
34 | return (
35 |
36 | { files.folder && }
37 | { files.folder && }
38 | { files.folder && }
39 |
40 |
41 | );
42 | };
43 |
44 | ProjMenu.propTypes = {};
45 |
46 | export default connect(({ cns, files, proj }) => ({ cns, files, proj }))(ProjMenu);
47 |
48 |
--------------------------------------------------------------------------------
/src/renderer/constants.js:
--------------------------------------------------------------------------------
1 |
2 | export const IPC_CHANNEL = 'ipc';
3 |
4 | export const EDITOR_MODE= ['jsx', 'javascript', 'java', 'python', 'xml', 'html', 'css', 'markdown'];
5 |
6 | export const EDITOR_THEMES = ['monokai', 'github', 'twilight', 'xcode', 'terminal'];
7 |
8 |
--------------------------------------------------------------------------------
/src/renderer/index.js:
--------------------------------------------------------------------------------
1 | import dva from 'dva';
2 | import RouterConfig from './router';
3 | import files from './models/files'
4 | import editor from './models/editor'
5 | import cns from './models/cns'
6 | import proj from './models/proj'
7 |
8 | // 1. Initialize
9 | const app = dva();
10 |
11 | // 2. Plugins
12 |
13 | // 3. Model
14 |
15 | app.model(files);
16 | app.model(editor);
17 | app.model(cns);
18 | app.model(proj);
19 |
20 | // 4. Router
21 | // app.router(require('./router'));
22 | app.router(RouterConfig);
23 |
24 | // 5. Start
25 | app.start('#root');
26 |
--------------------------------------------------------------------------------
/src/renderer/models/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/src/renderer/models/.gitkeep
--------------------------------------------------------------------------------
/src/renderer/models/cns.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM, {findDOMNode} from 'react-dom'
3 | import _ from 'lodash'
4 | import CaptureConsole from '../services/cns'
5 | import { transformCode } from '../utils'
6 |
7 |
8 | export default {
9 |
10 | namespace: 'cns',
11 |
12 | state: {
13 | show: false,
14 | content: '',
15 | err: '',
16 | isExec: false
17 | },
18 |
19 | effects: {
20 | *transCode({ payload: { contents }}, {put, call, select }){
21 |
22 | const {code, err} = transformCode(contents);
23 |
24 | yield put({
25 | type: 'changeStatus',
26 | payload: {
27 | content: code,
28 | err
29 | }
30 | });
31 | },
32 | *execCode({ payload: { mount, contents }}, {put, call, select }){
33 |
34 | yield put({
35 | type: 'transCode',
36 | payload: {
37 | contents,
38 | }
39 | });
40 |
41 | const { content } = yield select(state => state.cns);
42 |
43 | let capturingConsole = CaptureConsole();
44 | var str = 'ReactDOM, React, mountNode, console'
45 | var strp = [ReactDOM, React, mount, capturingConsole]
46 |
47 | try{
48 | let f = (new Function(str, content))
49 | f.apply(null, strp)
50 | let buffer = capturingConsole.getBuffer();
51 |
52 | yield put({
53 | type: 'changeStatus',
54 | payload: {
55 | isExec: true,
56 | content: buffer
57 | }
58 | });
59 |
60 | }catch (err){
61 | yield put({
62 | type: 'changeStatus',
63 | payload: {
64 | err: err.toString(),
65 | isExec: false
66 | }
67 | });
68 | }
69 | }
70 | },
71 |
72 | reducers: {
73 | changeStatus(state, action){
74 | return { ...state, ...action.payload};
75 | },
76 |
77 |
78 | },
79 |
80 | };
81 |
--------------------------------------------------------------------------------
/src/renderer/models/editor.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 |
4 | namespace: 'editor',
5 |
6 | state: {
7 | theme: 'github',
8 | mode: 'jsx',
9 | },
10 |
11 | effects: {
12 |
13 | },
14 |
15 | reducers: {
16 | changeStatus(state, action){
17 | return { ...state, ...action.payload};
18 | },
19 |
20 |
21 | },
22 |
23 | };
24 |
--------------------------------------------------------------------------------
/src/renderer/models/files.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom'
2 | import fs from 'fs'
3 | import { remote, ipcRenderer } from 'electron'
4 | import { mapToMode, getFileName, formatFile, resolvePath, addFileID, coverTree } from '../utils'
5 | import { saveNewFile, readFile} from '../services/file'
6 | import { filterTree, expandFilteredNodes} from '../services/filter'
7 | // const fs = remote.require('fs')
8 |
9 | const baseTab = () => ({
10 | title: 'undefined',
11 | path: __dirname,
12 | mode: 'jsx',
13 | contents: '',
14 | })
15 |
16 | let originTree = []
17 |
18 | export default {
19 |
20 | namespace: 'files',
21 |
22 | state: {
23 | current: 0,
24 | treeList: [],
25 | folder: '',
26 | pathDir: '',
27 | tabs: [baseTab()]
28 | },
29 |
30 | subscriptions: {
31 | setup({ dispatch }) {
32 | ipcRenderer
33 | .on('winNewFile', (event) => {
34 |
35 | const tab = baseTab();
36 |
37 | dispatch({
38 | type: 'addFile',
39 | payload: {...tab}
40 | });
41 | })
42 | .on('winOpenFile', (event, data) => {
43 | const {ext, base} = data.file;
44 | const extension = ext.slice(1);
45 | const mode = mapToMode(extension)
46 |
47 |
48 | dispatch({
49 | type: 'addFile',
50 | payload: {
51 | path: data.path,
52 | contents: data.contents,
53 | mode,
54 | title: base,
55 | }
56 | });
57 | })
58 | .on('winOpenFolder', (event, data) => {
59 | const formatData = formatFile(data);
60 | const treeList = addFileID(formatData, 0);
61 | originTree = treeList;
62 |
63 | dispatch({
64 | type: 'changeStatus',
65 | payload: {
66 | pathDir: data.pathDir,
67 | folder: data.folder,
68 | treeList
69 | }
70 | });
71 |
72 | })
73 | .on('winSaveFile', function () {
74 | dispatch({
75 | type: 'saveFile',
76 | });
77 | })
78 | .on('winCloseFile', function () {
79 |
80 | dispatch({
81 | type: 'closeFile',
82 | payload: {}
83 | });
84 | })
85 |
86 | },
87 | },
88 |
89 | effects: {
90 | *addFile({ payload: { path, contents, mode, title} }, {put, call, select }){
91 |
92 | const { tabs, treeList } = yield select(state => state.files);
93 | let current = 0;
94 |
95 | const tab = {
96 | title,
97 | path,
98 | mode,
99 | contents,
100 | };
101 |
102 | const item = tabs.filter(i => i.path == path);
103 | tabs.filter((it, i) => {
104 | if(it.path == path){
105 | current = i;
106 | return true;
107 | }
108 | return false;
109 | });
110 |
111 | if(!item.length || path == __dirname){
112 | tabs.push(tab);
113 | current = tabs.length - 1;
114 | }
115 |
116 | yield put({
117 | type: 'changeStatus',
118 | payload: { tabs, current}
119 | });
120 |
121 | },
122 |
123 | *saveFile({}, {put, call, select }){
124 |
125 | const {current, tabs} = yield select(state => state.files);
126 | let tab = tabs[current];
127 |
128 | if(tab.path != '/'){
129 |
130 | // remote.require('fs').writeFile(tab.path, tab.contents, 'utf-8');
131 | fs.writeFile(tab.path, tab.contents, 'utf-8');
132 |
133 | }else{
134 |
135 | const filepath = yield saveNewFile(tab.contents);
136 | const title = getFileName(filepath)
137 |
138 | yield put({
139 | type: 'changeFile',
140 | payload: { current, file: { path: filepath, title} }
141 | });
142 |
143 | }
144 | },
145 |
146 | *openFile({ payload: { path } }, {put, call, select }){
147 |
148 | const contents = yield call(readFile, path);
149 | const spl = path.lastIndexOf('.')
150 | const mode = mapToMode(path.slice(spl + 1))
151 | const title = getFileName(path)
152 |
153 | yield put({
154 | type: 'addFile',
155 | payload: {
156 | path,
157 | contents,
158 | mode,
159 | title
160 | }
161 | });
162 | },
163 |
164 | *showResult({}, {put, call, select}){
165 | const { contents, mount } = yield select(state => state.editor);
166 |
167 | const str = 'ReactDOM.render(Hello, world!!
, mountNode)';
168 | const code = transformCode(str)
169 | (new Function('ReactDOM', 'React', 'mountNode', code))(ReactDOM, React, mount);
170 |
171 | yield put({
172 | type: 'changeStatus',
173 | payload: {
174 | showResult: true,
175 | results: code
176 | }
177 | });
178 | },
179 |
180 | *closeFile({ payload: { current } }, {put, call, select }){
181 | const files = yield select(state => state.files);
182 | const cur = current != null ? current : files.current;
183 |
184 | const tabs = files.tabs.filter((it, i) => i != cur);
185 | const newCur = cur == 0 ? 0 : cur - 1;
186 | yield put({
187 | type: 'changeStatus',
188 | payload: {
189 | tabs,
190 | current: newCur
191 | }
192 | })
193 | },
194 |
195 | *toggledTree({ payload: { node } }, {put, call, select }){
196 | coverTree(node, originTree, (node, newNode) => node.id == newNode.id )
197 | }
198 |
199 | },
200 |
201 | reducers: {
202 | changeStatus(state, action){
203 | return { ...state, ...action.payload};
204 | },
205 | changeFile(state, action){
206 | const { current, file} = action.payload
207 | let tabs = Object.assign([], state.tabs);
208 | const tab = {...tabs[current], ...file};
209 | tabs[current] = tab;
210 |
211 | return {...state, tabs};
212 |
213 | },
214 | },
215 |
216 | };
217 |
--------------------------------------------------------------------------------
/src/renderer/models/proj.js:
--------------------------------------------------------------------------------
1 | import { remote, ipcRenderer } from 'electron'
2 | import del from 'del'
3 | import fs from 'fs'
4 | import { exec } from 'child_process'
5 |
6 | import { mapToMode, getFileName, formatFile, resolvePath } from '../utils'
7 | import { execCode, editWbpkFile } from '../services/proj'
8 | import { saveNewFile, readFile} from '../services/file'
9 |
10 | // const fs = remote.require('fs')
11 | const APP_PATH = process.cwd()
12 | // const exec = remote.require('child_process').exec;
13 |
14 | export default {
15 |
16 | namespace: 'proj',
17 |
18 | state: {
19 | start: false,
20 | build: false,
21 | err: '',
22 | config:{
23 | "port": 3001,
24 | "entry": "src/index.js",
25 | "vendor": "src/vendor.js",
26 | "output":"dist"
27 | },
28 | user_path_entry: '',
29 | user_path_vendor: '',
30 | user_path_output: '',
31 | },
32 |
33 | subscriptions: {
34 | setup({ dispatch }) {
35 | ipcRenderer
36 | .on('winOpenFolder', (event, data) => {
37 |
38 | })
39 |
40 |
41 | },
42 | },
43 |
44 | effects: {
45 |
46 | *startProj({ }, {put, call, select }){
47 | const { folder} = yield select(state => state.files);
48 |
49 | let wbpk = yield call(readFile, resolvePath(APP_PATH, 'webpack/base.config.js'));
50 | let config = yield call(readFile, resolvePath(folder, 'config.json'));
51 |
52 | let baseConfig = JSON.parse(config)
53 | let newFoler = folder.replace(/\\/g, '/')
54 |
55 | let user_path_entry = resolvePath(newFoler, baseConfig.entry).replace(/\\/g, '/')
56 | let user_path_vendor = resolvePath(newFoler, baseConfig.vendor).replace(/\\/g, '/')
57 | let user_path_output = resolvePath(newFoler, baseConfig.output).replace(/\\/g, '/')
58 |
59 | wbpk = wbpk.replace(/user_path_entry/g, `'${user_path_entry}'`);
60 | wbpk = wbpk.replace(/user_path_vendor/g, `'${user_path_vendor}'`);
61 | wbpk = wbpk.replace(/user_path_output/g, `'${user_path_output}'`);
62 |
63 | let newWpPath = resolvePath(APP_PATH, 'user.webpack.config.js');
64 | // remote.require('fs').writeFile(newWpPath, wbpk, 'utf-8');
65 | fs.writeFile(newWpPath, wbpk, 'utf-8');
66 |
67 | yield put({
68 | type: 'changeStatus',
69 | payload: {
70 | start: true,
71 | config: baseConfig,
72 | user_path_entry,
73 | user_path_vendor,
74 | user_path_output
75 | }
76 | });
77 |
78 | try{
79 |
80 | // start server to listen
81 | // yield call(execCode, `node server.js --port ${baseConfig.port} --config ./user.webpack.config.js`);
82 | yield call(execCode, {env: 'development' }, `node server-user.js --port ${baseConfig.port}`);
83 |
84 | }catch (err){
85 | console.log(err)
86 |
87 | yield put({
88 | type: 'changeStatus',
89 | payload: {
90 | start: false,
91 | err: err.toString()
92 | }
93 | });
94 | }
95 | },
96 |
97 | *stopProj({ }, {put, call, select }){
98 | const { config } = yield select(state => state.proj)
99 |
100 | fetch(`http://localhost:${config.port}/stopProj`)
101 |
102 | yield put({
103 | type: 'changeStatus',
104 | payload: {
105 | start: false,
106 | }
107 | });
108 |
109 | },
110 |
111 | *buildProj({ }, {put, call, select }){
112 | const { start } = yield select(state => state.proj)
113 |
114 | let wbpk = yield call(readFile, resolvePath(APP_PATH, 'webpack/base.config.prod.js'));
115 | let a, b, c ;
116 |
117 | if(start){
118 | yield put({
119 | type: 'stopProj',
120 | payload: {}
121 | });
122 |
123 | const { config, user_path_entry, user_path_vendor, user_path_output } = yield select(state => state.proj)
124 | a = user_path_entry
125 | b = user_path_vendor
126 | c = user_path_output
127 |
128 | }else{
129 | const { folder} = yield select(state => state.files);
130 | let config = yield call(readFile, resolvePath(folder, 'config.json'));
131 |
132 | let baseConfig = JSON.parse(config)
133 | let newFoler = folder.replace(/\\/g, '/')
134 |
135 | a = resolvePath(newFoler, baseConfig.entry).replace(/\\/g, '/')
136 | b = resolvePath(newFoler, baseConfig.vendor).replace(/\\/g, '/')
137 | c = resolvePath(newFoler, baseConfig.output).replace(/\\/g, '/')
138 |
139 | }
140 |
141 | wbpk = wbpk.replace(/user_path_entry/g, `'${a}'`);
142 | wbpk = wbpk.replace(/user_path_vendor/g, `'${b}'`);
143 | wbpk = wbpk.replace(/user_path_output/g, `'${c}'`);
144 |
145 | let newWpPath = resolvePath(APP_PATH, 'user.webpack.config.prod.js');
146 | fs.writeFile(newWpPath, wbpk, 'utf-8');
147 |
148 | yield del.sync([c + '/**'], {
149 | force: true
150 | })
151 |
152 | yield call(execCode, {}, `npm run build-user`);
153 |
154 |
155 | }
156 | },
157 |
158 | reducers: {
159 | changeStatus(state, action){
160 | return { ...state, ...action.payload};
161 | },
162 | },
163 |
164 | };
165 |
166 |
--------------------------------------------------------------------------------
/src/renderer/models/win.js:
--------------------------------------------------------------------------------
1 | import { remote } from 'electron'
2 |
3 | export default {
4 |
5 | namespace: 'win',
6 |
7 | state: {
8 | instance: null,
9 | url: '',
10 | },
11 |
12 | effects: {
13 | *newWindow({ }, {put, call, select }){
14 | let win = remote.getGlobal('services').window.create({ width: 800, height: 600 })
15 | yield put({
16 | type: 'changeStatus',
17 | payload: {
18 | instance: win,
19 | }
20 | });
21 | },
22 | *closeWindow({ }, {put, call, select }){
23 | const { instance } = yield select(state => state.win)
24 | instance.close()
25 | yield put({
26 | type: 'changeStatus',
27 | payload: {
28 | instance: null,
29 | }
30 | });
31 | // remote.getGlobal('services').window.create({ width: 800, height: 600 })
32 |
33 | }
34 | },
35 |
36 | reducers: {
37 | changeStatus(state, action){
38 | return { ...state, ...action.payload};
39 | },
40 |
41 | },
42 |
43 | };
44 |
--------------------------------------------------------------------------------
/src/renderer/router.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, Route } from 'dva/router';
3 | import IndexPage from './routes/IndexPage';
4 |
5 | function RouterConfig({ history }) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default RouterConfig;
14 |
--------------------------------------------------------------------------------
/src/renderer/routes/IndexPage.css:
--------------------------------------------------------------------------------
1 | .window{
2 | position: absolute;
3 | bottom: 0;
4 | display: flex;
5 | flex-direction: column;
6 | background-color: #fff;
7 | top: 0;
8 | left: 0;
9 | right: 0;
10 | }
11 |
12 | .window-content{
13 | position: relative;
14 | overflow-y: auto;
15 | display: flex;
16 | flex: 1;
17 | }
18 |
19 | /*
20 | .normal {
21 | font-family: Georgia, sans-serif;
22 | margin-top: 3em;
23 | text-align: center;
24 | }
25 |
26 | .title {
27 | font-size: 2.5rem;
28 | font-weight: normal;
29 | letter-spacing: -1px;
30 | }
31 |
32 | .welcome {
33 | height: 328px;
34 | background: url(../assets/yay.jpg) no-repeat center 0;
35 | background-size: 388px 328px;
36 | }
37 |
38 | .list {
39 | font-size: 1.2em;
40 | margin-top: 1.8em;
41 | list-style: none;
42 | line-height: 1.5em;
43 | }
44 |
45 | .list code {
46 | background: #f7f7f7;
47 | }
48 | */
49 |
--------------------------------------------------------------------------------
/src/renderer/routes/IndexPage.js:
--------------------------------------------------------------------------------
1 |
2 | import React, {Component} from 'react';
3 | import ReactDOM from 'react-dom'
4 | import { connect } from 'dva';
5 | import { remote } from 'electron';
6 |
7 |
8 | import styles from './IndexPage.css';
9 |
10 | import EditorPane from '../components/Editor/EditorPane';
11 | import EditorSelectPane from '../components/Editor/EditorSelectPane';
12 | import FileTree from '../components/FileTree/FileTree';
13 | import Console from '../components/ProjFunc/Console';
14 | import ProjMenu from '../components/ProjFunc/ProjMenu';
15 | import Layout from '../components/Layout'
16 | const { Footer, PaneGroup, Pane, TabGroup } = Layout
17 |
18 | class IndexPage extends Component {
19 |
20 | constructor(props){
21 | super(props);
22 | }
23 |
24 | onChange( contents ){
25 | const {files: { current, tabs}, dispatch } = this.props;
26 | dispatch({
27 | type: 'files/changeFile',
28 | payload: { current, file: { contents }}
29 | });
30 | }
31 |
32 | componentWillReceiveProps(next){
33 | const {files: { current, tabs}, dispatch} = next;
34 |
35 | if(tabs.length){
36 | const file = tabs[current];
37 | if(file.mode != this.props.editor.mode){
38 | dispatch({
39 | type: 'editor/changeStatus',
40 | payload: { mode: file.mode },
41 | });
42 | }
43 | }
44 |
45 | }
46 |
47 | render(){
48 | const { editor:{ theme }, files, cns, dispatch } = this.props
49 |
50 | const tabComponents = files.tabs.map( (it, i) => {
51 | const {title, mode, contents} = it;
52 | const editorPaneProps = {
53 | theme,
54 | mode,
55 | value: contents,
56 | onChange: this.onChange.bind(this)
57 | }
58 | const comp =
59 | return {
60 | title,
61 | comp,
62 | }
63 | })
64 |
65 | const tabProps = {
66 | tabComponents,
67 | activeIndex: files.current,
68 | onSelectItem: (current) => {
69 | dispatch({
70 | type: 'files/changeStatus',
71 | payload: { current }
72 | })
73 | },
74 | onCloseItem: (current) => {
75 | dispatch({
76 | type: 'files/closeFile',
77 | payload: { current }
78 | })
79 | }
80 | }
81 |
82 | const cnsComp = cns.show ? : null;
83 |
84 | return (
85 |
86 |
92 | { cnsComp }
93 |
94 |
95 | );
96 | }
97 | }
98 |
99 | export default connect(({ editor, files, cns }) => ({ editor, files, cns }))(IndexPage);
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/renderer/services/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/src/renderer/services/.gitkeep
--------------------------------------------------------------------------------
/src/renderer/services/cns.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export default (capture) => {
5 | let capturingConsole = Object.create(console);
6 | let buffer = [];
7 |
8 | function capture() {
9 | if (this !== capturingConsole) { return; }
10 |
11 | let logs = _.map(arguments, log => JSON.stringify(log));
12 |
13 | buffer.push(logs.join(' '));
14 | }
15 |
16 |
17 | capturingConsole.clear = function() {
18 | buffer = [];
19 | console.clear();
20 | };
21 |
22 | ['error', 'log', 'info', 'debug'].forEach(function(key) {
23 | capturingConsole[key] = function() {
24 | Function.prototype.apply.call(console[key], console, arguments);
25 | capture.apply(this, arguments);
26 | };
27 | });
28 |
29 | capturingConsole.getBuffer = () => buffer.join('\n')
30 |
31 | return capturingConsole;
32 | }
33 |
--------------------------------------------------------------------------------
/src/renderer/services/file.js:
--------------------------------------------------------------------------------
1 | import { remote } from 'electron'
2 | import fs from 'fs'
3 | // const fs = remote.require('fs')
4 |
5 |
6 | export const saveNewFile = (contents) => {
7 | return new Promise((resolve, reject) => {
8 | remote.dialog.showSaveDialog(function (filepath) {
9 | if(filepath){
10 | fs.writeFile(filepath, contents, 'utf-8');
11 | resolve(filepath)
12 | }else{
13 | reject()
14 | }
15 | });
16 | })
17 | }
18 |
19 | export const readFile = (path) => {
20 | return new Promise((resolve, reject) => {
21 | fs.readFile(path, 'utf-8', function (err, data) {
22 | if (err) reject()
23 | resolve(data)
24 |
25 | });
26 | })
27 | }
--------------------------------------------------------------------------------
/src/renderer/services/filter.js:
--------------------------------------------------------------------------------
1 | export const defaultMatcher = (filterText, node) => {
2 | return node.name.toLowerCase().indexOf(filterText.toLowerCase()) !== -1;
3 | };
4 |
5 | export const findNode = (node, filter, matcher) => {
6 | return matcher(filter, node) || // i match
7 | (node.children && // or i have decendents and one of them match
8 | node.children.length &&
9 | !!node.children.find(child => findNode(child, filter, matcher)));
10 | };
11 |
12 | export const filterTree = (node, filter, matcher = defaultMatcher) => {
13 | // If im an exact match then all my children get to stay
14 | if(matcher(filter, node) || !node.children){ return node; }
15 | // If not then only keep the ones that match or have matching descendants
16 | const filtered = node.children
17 | .filter(child => findNode(child, filter, matcher))
18 | .map(child => filterTree(child, filter, matcher));
19 | return Object.assign({}, node, { children: filtered });
20 | };
21 |
22 | export const expandFilteredNodes = (node, filter, matcher = defaultMatcher) => {
23 | let children = node.children;
24 | if(!children || children.length === 0){
25 | return Object.assign({}, node, { toggled: false });
26 | }
27 | const childrenWithMatches = node.children.filter(child => findNode(child, filter, matcher));
28 | const shouldExpand = childrenWithMatches.length > 0;
29 | // If im going to expand, go through all the matches and see if thier children need to expand
30 | if(shouldExpand){
31 | children = childrenWithMatches.map(child => {
32 | return expandFilteredNodes(child, filter, matcher);
33 | });
34 | }
35 | return Object.assign({}, node, {
36 | children: children,
37 | toggled: shouldExpand
38 | });
39 | };
--------------------------------------------------------------------------------
/src/renderer/services/proj.js:
--------------------------------------------------------------------------------
1 | import { remote } from 'electron'
2 | // const exec = remote.require('child_process').exec;
3 | // const spawn = remote.require('child_process').spawn;
4 | import { saveNewFile, readFile} from './file'
5 | import { resolvePath } from '../utils'
6 | import {exec, spawn} from 'child_process'
7 |
8 | export const execCode = (opt, cmd) => {
9 | const options = {
10 | killSignal: 'SIGTERM',
11 | // env: 'development', // faild in statestr
12 | ...opt
13 | }
14 | // console.log(options)
15 | return new Promise((resolve, reject) => {
16 | const ex = exec(cmd, options, (error, stdout, stderr) => {
17 | if (error) {
18 | reject(error)
19 | }
20 | if(stderr){
21 | reject(stderr)
22 | }
23 | // console.log(stderr);
24 | resolve({
25 | stdout
26 | })
27 | });
28 | })
29 | }
30 |
31 |
32 | export const spwanCodeByPort = ( port ) => {
33 |
34 | return new Promise((resolve, reject) => {
35 |
36 | const ls = spawn('lsof',['-nP', `-i:${port}`])
37 | const grep = spawn('grep', ['LISTEN'])
38 |
39 | ls.stdout.on('data', (data) => {
40 | grep.stdin.write(data);
41 | });
42 |
43 | ls.stderr.on('data', (data) => {
44 | reject(data.toString())
45 | console.log(`ps stderr: ${data}`);
46 | });
47 |
48 | ls.on('close', (code) => {
49 | console.log('close ls')
50 | if (code !== 0) {
51 | console.log(`ps process exited with code ${code}`);
52 | }
53 | grep.stdin.end();
54 | });
55 |
56 | grep.stdout.on('data', (data) => {
57 | resolve(data.toString())
58 | });
59 |
60 | grep.stderr.on('data', (data) => {
61 | reject(data.toString())
62 | console.log(`grep stderr: ${data}`);
63 | });
64 |
65 | });
66 | }
67 |
--------------------------------------------------------------------------------
/src/renderer/utils.js:
--------------------------------------------------------------------------------
1 | import { remote } from 'electron'
2 | import { transform } from 'babel-standalone'
3 | const path = remote.require('path')
4 |
5 |
6 | export const mapToMode = (ext) => {
7 |
8 | const str = ext.toLowerCase();
9 |
10 | if(['jsx'].indexOf(str) != -1) return 'jsx';
11 | if(['js'].indexOf(str) != -1) return 'javascript';
12 | if(['java'].indexOf(str) != -1) return 'java';
13 | if(['xml'].indexOf(str) != -1) return 'xml';
14 | if(['html', 'ejs', 'htm'].indexOf(str) != -1) return 'html';
15 | if(['py'].indexOf(str) != -1) return 'python';
16 | if(['css', 'less', 'sass', 'scss', 'styl'].indexOf(str) != -1) return 'css';
17 |
18 | return 'markdown';
19 |
20 | }
21 |
22 | export const getFileName = (dir) => {
23 | return path.parse(dir).base
24 | }
25 |
26 |
27 | export const formatFile = ({list, dirs, sep, folder}) => {
28 | const sepLen = sep.length
29 | const a = list.map(i => i.slice(folder.length + sepLen))
30 | const b = dirs.map(i => i.slice(folder.length + sepLen))
31 |
32 | let d = [], e = Array.from({length: b.length}, i => []);
33 |
34 | let files = {
35 | active: true,
36 | children: [],
37 | toggled: true,
38 | name: getFileName(folder, sep)
39 | }
40 |
41 | function inter(p, obj, sp, file){
42 | const arr = p.split(sep)
43 | const name = arr[0]
44 |
45 | if(arr.length > 1){
46 | const k = obj.filter(o => o.name == name)
47 | inter(p.slice(name.length + sepLen), k[0].children, sp, file)
48 | }else{
49 | const k = obj.filter(o => o.name == name)
50 |
51 | if(k.length == 0 ){
52 | obj.push({
53 | name: name,
54 | active: false,
55 | children: file
56 | })
57 | obj.sort((a, b) => a.name.localeCompare(b.name))
58 | }
59 |
60 | return obj
61 | }
62 |
63 | // if(arr.length > 1){
64 | // inter(p.slice(name.length + sepLen), o[name], sp, file)
65 | // }else{
66 | // o[name] = {path: sp, children: file}
67 | // return o
68 | // }
69 | }
70 |
71 |
72 | list.forEach((l, i) => {
73 |
74 | const index = a[i].lastIndexOf(sep)
75 |
76 | if(index == -1){
77 | files.children.push({
78 | name: a[i],
79 | active: false,
80 | path: l
81 | })
82 |
83 | }else{
84 | dirs.forEach((d, j) => {
85 |
86 | const str = l.slice(0, l.lastIndexOf(sep))
87 |
88 | if(str === d){
89 | e[j].push({
90 | name: getFileName(l, sep),
91 | path: l,
92 | active: false
93 | })
94 | }
95 | })
96 | }
97 |
98 | })
99 |
100 | b.forEach((i, j) => inter(i, d, i, e[j]))
101 |
102 |
103 | files.children = [...files.children, ...d].sort((a, b) => a.name.localeCompare(b.name))
104 |
105 | return files
106 |
107 | }
108 |
109 | export const addFileID = (node, i) => {
110 | let children = node.children
111 | if(!children || children.length === 0){
112 | return Object.assign({}, node, { id: i});
113 | }
114 | let child = children.map((n, j) => addFileID(n, i + '-' + j))
115 | return Object.assign({}, node, { children: child, id: i })
116 | }
117 |
118 | export const coverTree = (filter, node, matcher) => {
119 | if(matcher(filter, node)){
120 | return Object.assign({}, node, filter);
121 | }
122 |
123 | if(!node.children) return node;
124 |
125 | const filtered = node.children.map(child => coverTree(filter, child, matcher))
126 |
127 | return Object.assign({}, node, { children: filtered });
128 | }
129 |
130 | export const delTree = (filter, node, matcher) => {
131 | if(matcher(filter, node)){
132 | return false
133 | }
134 |
135 | if(!node.children) return true;
136 |
137 | const filtered = node.children.filter(child => delTree(filter, child, matcher))
138 |
139 | return Object.assign({}, node, { children: filtered });
140 | }
141 |
142 |
143 | export const transformCode = (contents) => {
144 |
145 | try{
146 | const { code } = transform(contents.trim(), {
147 | presets: ['es2015', 'react', 'stage-0'],
148 | compact: true,
149 | });
150 |
151 | return { code }
152 |
153 | }catch(err){
154 |
155 | return { err: err.toString() }
156 | }
157 |
158 | }
159 |
160 | export const resolvePath = (root, dir) => path.resolve(root, dir)
161 |
--------------------------------------------------------------------------------
/src/vendor.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 |
3 | // polyfills and vendors
4 |
5 | require('babel-polyfill');
6 | require('babel-standalone');
7 | require('react');
8 | require('react-dom');
9 | require('react-redux');
10 | require('react-router');
11 | require('dva');
12 | require('react-ace');
13 | require('brace');
14 | require('react-treebeard');
15 | require('electron');
16 | require('lodash');
17 | require('path');
18 | // require('node-watch')
19 |
20 |
21 | require('brace/mode/jsx');
22 | require('brace/mode/javascript');
23 | require('brace/mode/java');
24 | require('brace/mode/python');
25 | require('brace/mode/xml');
26 | require('brace/mode/ruby');
27 | require('brace/mode/sass');
28 | require('brace/mode/markdown');
29 | require('brace/mode/json');
30 | require('brace/mode/html');
31 | require('brace/mode/handlebars');
32 | require('brace/mode/coffee');
33 | require('brace/mode/css');
34 | require('brace/theme/monokai');
35 | require('brace/theme/github');
36 | require('brace/theme/twilight');
37 | require('brace/theme/xcode');
38 | require('brace/theme/terminal');
39 |
--------------------------------------------------------------------------------
/static/assets/README.md:
--------------------------------------------------------------------------------
1 | ### put your assets here
2 |
3 | 1. images
4 | 2. other standalone libraries(no npm support)
5 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jirapo/React-editor/490c7c59887910f09af21983f551de070861e4ff/static/favicon.ico
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/static/template/test/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": 3001,
3 | "entry": "src/index.js",
4 | "vendor": "src/vendor.js",
5 | "output":"dist"
6 | }
--------------------------------------------------------------------------------
/static/template/test/src/components/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | text-size-adjust: none;
3 | font-family: helvetica, arial, sans-serif;
4 | line-height: 200%;
5 | padding: 6px 20px 30px;
6 | }
--------------------------------------------------------------------------------
/static/template/test/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './App.css';
3 |
4 | const App = () => (
5 |
6 |
Hello,world
7 |
8 | );
9 |
10 | export default App;
--------------------------------------------------------------------------------
/static/template/test/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import { AppContainer } from 'react-hot-loader';
5 | // AppContainer is a necessary wrapper component for HMR
6 |
7 | import App from './components/App';
8 |
9 | const render = () => {
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('root')
15 | );
16 | };
17 |
18 | render();
19 |
20 | // Hot Module Replacement API
21 | if (module.hot) {
22 | module.hot.accept('./components/App', render);
23 | }
24 |
--------------------------------------------------------------------------------
/static/template/test/src/vendor.js:
--------------------------------------------------------------------------------
1 | require('babel-polyfill');
2 | require('react');
3 | require('react-dom');
4 | require('lodash')
5 |
--------------------------------------------------------------------------------
/webpack/base.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 |
4 | const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 |
7 |
8 | module.exports = {
9 |
10 | context: __dirname,
11 |
12 | devtool: 'inline-source-map',
13 |
14 | resolve: {
15 |
16 | extensions: ['.js', '.jsx', '.scss', '.css'],
17 |
18 | modules: [ path.resolve(__dirname), path.resolve(__dirname, 'node_modules')]
19 |
20 | },
21 |
22 | resolveLoader:{
23 |
24 | // modules: [ path.resolve(__dirname), 'node_modules']
25 | modules: [ path.resolve(__dirname), path.resolve(__dirname, 'node_modules')]
26 | },
27 |
28 | entry: {
29 | 'app': ['react-hot-loader/patch',
30 | user_path_entry
31 | // path.resolve(user_proj_dir, 'src/index.js')
32 | // path.resolve(user_proj_dir, user_proj_entry)
33 | ],
34 | vendor: user_path_vendor
35 | // 'vendor': path.resolve(user_proj_dir, 'src/vendor.js')
36 | // 'vendor': path.resolve(user_proj_dir, 'src/vendor.js')
37 | },
38 |
39 | output: {
40 |
41 | // path: path.resolve(user_proj_dir, 'dist'),
42 | path: user_path_output,
43 |
44 | filename: '[name].js',
45 |
46 | publicPath: '/'
47 |
48 | },
49 |
50 | module: {
51 |
52 | rules: [
53 | {
54 | test: /\.js$/,
55 | exclude: /node_modules/,
56 | loader: 'babel-loader',
57 | query: {
58 | cacheDirectory: true,
59 | presets: ['babel-preset-es2015', 'babel-preset-stage-2', 'babel-preset-react'].map(require.resolve),
60 | }
61 | },
62 | {
63 | test: /\.css$/,
64 | use: [
65 | {
66 | loader: 'style-loader'
67 | },
68 | {
69 | loader: 'css-loader',
70 | options: {
71 | importLoaders: 2,
72 | modules: true,
73 | localIdentName: '[name]__[local]'
74 | }
75 | },
76 | {
77 | loader: 'postcss-loader'
78 | },
79 | ]
80 | },
81 | {
82 | test: /\.(mp4|webm)$/,
83 | loader: 'url-loader?limit=10000'
84 | },
85 | { test: /\.woff2{0,1}$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
86 | { test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file-loader' },
87 | { test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192'}
88 |
89 | ]
90 |
91 | },
92 |
93 | plugins: [
94 |
95 | new CommonsChunkPlugin({
96 | name: ['app', 'vendor', 'manifest'],
97 | minChunks: Infinity
98 | }),
99 | new HtmlWebpackPlugin({
100 | inject: true,
101 | template: path.resolve(__dirname, 'static/index.html'),
102 | }),
103 | new webpack.HotModuleReplacementPlugin()
104 | ],
105 | // devServer: {
106 | // host: 'localhost',
107 | // port: 3001,
108 | // open: true,
109 | // hot: true,
110 | // stats: { chunks: false },
111 | // historyApiFallback: true,
112 | // watchOptions: {
113 | // aggregateTimeout: 300,
114 | // poll: 1000
115 | // },
116 | // }
117 |
118 | };
119 |
--------------------------------------------------------------------------------
/webpack/base.config.prod.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const autoprefixer = require('autoprefixer');
4 | const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin');
7 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
8 | const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
9 | const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
10 |
11 |
12 | module.exports = {
13 |
14 | context: __dirname,
15 |
16 | devtool: 'source-map',
17 |
18 | resolve: {
19 |
20 | extensions: ['.js', '.jsx', '.scss', '.css'],
21 |
22 | modules: [ path.resolve(__dirname), path.resolve(__dirname, 'node_modules')]
23 |
24 | },
25 |
26 | resolveLoader:{
27 |
28 | // modules: [ path.resolve(__dirname), 'node_modules']
29 | modules: [ path.resolve(__dirname), path.resolve(__dirname, 'node_modules')]
30 | },
31 |
32 | entry: {
33 | 'app': user_path_entry,
34 | 'vendor': user_path_vendor
35 | },
36 |
37 | output: {
38 |
39 | path: user_path_output,
40 |
41 | filename: '[name]-[chunkhash].min.js',
42 |
43 | chunkFilename: '[chunkhash].js'
44 |
45 | },
46 |
47 | module: {
48 |
49 | rules: [
50 | {
51 | test: /\.js$/,
52 | exclude: /node_modules/,
53 | loader: 'babel-loader',
54 | query: {
55 | cacheDirectory: true,
56 | presets: [
57 | 'babel-preset-es2015',
58 | // 'babel-preset-es2015-webpack',
59 | // 'babel-plugin-transform-es2015-template-literals',
60 | // 'transform-es2015-literals',
61 | // 'transform-es2015-function-name',
62 | // 'transform-es2015-arrow-functions',
63 | // 'transform-es2015-block-scoped-functions',
64 | // 'transform-es2015-classes',
65 | // 'transform-es2015-object-super',
66 | // 'transform-es2015-shorthand-properties',
67 | // 'transform-es2015-computed-properties',
68 | // 'transform-es2015-for-of',
69 | // 'transform-es2015-sticky-regex',
70 | // 'transform-es2015-unicode-regex',
71 | // 'check-es2015-constants',
72 | // 'transform-es2015-spread',
73 | // 'transform-es2015-parameters',
74 | // 'transform-es2015-destructuring',
75 | // 'transform-es2015-block-scoping',
76 | // 'transform-es2015-typeof-symbol',
77 | // ['transform-regenerator', { async: false, asyncGenerators: false }],
78 | 'babel-preset-stage-2',
79 | 'babel-preset-react'].map(require.resolve),
80 | }
81 | },
82 | {
83 | test: /\.css$/,
84 | loader: ExtractTextPlugin.extract({
85 | fallbackLoader: 'style-loader',
86 | loader: [
87 | 'css-loader?modules&localIdentName=[name]__[local]&minimize&sourceMap&importLoaders=2',
88 | 'postcss-loader',
89 | ]
90 | })
91 | },
92 | {
93 | test: /\.(mp4|webm)$/,
94 | loader: 'url-loader?limit=10000'
95 | },
96 | { test: /\.woff2{0,1}$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
97 | { test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file-loader' },
98 | { test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192'}
99 |
100 | ]
101 |
102 | },
103 |
104 | plugins: [
105 |
106 | new CommonsChunkPlugin({
107 | name: 'manifest',
108 | minChunks: Infinity,
109 | }),
110 |
111 | new HtmlWebpackPlugin({
112 | inject: true,
113 | template: path.resolve(__dirname, 'static/index.html'),
114 | // favicon: path.resolve(__dirname, '../static/favicon.ico'),
115 | minify: {
116 | removeComments: true,
117 | collapseWhitespace: true,
118 | removeRedundantAttributes: true,
119 | useShortDoctype: true,
120 | removeEmptyAttributes: true,
121 | removeStyleLinkTypeAttributes: true,
122 | keepClosingSlash: true,
123 | minifyJS: true,
124 | minifyCSS: true,
125 | minifyURLs: true
126 | }
127 | }),
128 | new InlineChunkWebpackPlugin({
129 | inlineChunks: ['manifest']
130 | }),
131 | new ExtractTextPlugin('[name].css'),
132 | new UglifyJsPlugin({
133 | compressor: {
134 | screw_ie8: true,
135 | warnings: false
136 | },
137 | mangle: {
138 | screw_ie8: true
139 | },
140 | output: {
141 | comments: false,
142 | screw_ie8: true
143 | },
144 | // sourceMap: true
145 | }),
146 | new LoaderOptionsPlugin({
147 | options: {
148 | context: '/',
149 | postcss: function () {
150 | return [autoprefixer];
151 | }
152 | }
153 | })
154 | ]
155 | };
156 |
--------------------------------------------------------------------------------
/webpack/common.config.js:
--------------------------------------------------------------------------------
1 | // webpack plugins
2 | const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
3 | // const HappyPack = require('happypack');
4 | // const happyThreadPool = HappyPack.ThreadPool({ size: 4});
5 |
6 |
7 | module.exports = {
8 | target: "electron-renderer",
9 |
10 | resolve: {
11 |
12 | extensions: ['.js', '.jsx', '.scss', '.css'],
13 |
14 | modules: ['node_modules']
15 |
16 | },
17 |
18 | module: {
19 |
20 | rules: [
21 |
22 | {
23 | test: /\.js$/,
24 | // include: './src/',
25 | exclude: /node_modules/,
26 | loader: 'babel-loader',
27 | query: {
28 | cacheDirectory: true,
29 |
30 | },
31 | // use: ['babel-loader']
32 | // use: ['happypack/loader']
33 | },
34 | // {
35 | // test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)$/,
36 | // loader: 'file-loader',
37 | // },
38 | {
39 | test: /\.(mp4|webm)$/,
40 | loader: 'url-loader?limit=10000'
41 | },
42 | { test: /\.woff2{0,1}$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
43 | { test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file-loader' },
44 | { test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192'}
45 |
46 | ]
47 |
48 | },
49 |
50 | plugins: [
51 | // new HappyPack({
52 | // // cache: true,
53 | // loaders: [
54 | // {
55 | // path: 'babel-loader',
56 | // query: {
57 | // plugins: [
58 | // 'react-hot-loader/babel',
59 | // ],
60 | // presets: ['es2015', 'stage-2', 'react'],
61 | // cacheDirectory: true
62 | // }
63 | // }
64 | // ],
65 | // threads: 2
66 | // }),
67 | // new HappyPack({
68 | // id: 'js',
69 | // loaders: [ 'babel-loader' ],
70 | // threadPool: happyThreadPool,
71 | // }),
72 | new CommonsChunkPlugin({
73 | name: ['app', 'vendor', 'manifest'],
74 | minChunks: Infinity
75 | })
76 | ]
77 |
78 | };
79 |
80 | // options:{
81 | // presets:[
82 | // 'transform-es2015-template-literals',
83 | // // 'babel-plugin-transform-es2015-template-literals',
84 | // 'transform-es2015-literals',
85 | // 'transform-es2015-function-name',
86 | // 'transform-es2015-arrow-functions',
87 | // 'transform-es2015-block-scoped-functions',
88 | // 'transform-es2015-classes',
89 | // 'transform-es2015-object-super',
90 | // 'transform-es2015-shorthand-properties',
91 | // 'transform-es2015-computed-properties',
92 | // 'transform-es2015-for-of',
93 | // 'transform-es2015-sticky-regex',
94 | // 'transform-es2015-unicode-regex',
95 | // 'check-es2015-constants',
96 | // 'transform-es2015-spread',
97 | // 'transform-es2015-parameters',
98 | // 'transform-es2015-destructuring',
99 | // 'transform-es2015-block-scoping',
100 | // 'transform-es2015-typeof-symbol',
101 | // ['transform-regenerator', { async: false, asyncGenerators: false }],
102 | // ]
103 | // }
104 |
--------------------------------------------------------------------------------
/webpack/dev.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const webpackMerge = require('webpack-merge');
4 | const webpackCommon = require('./common.config');
5 |
6 |
7 | // webpack plugins
8 | const HtmlWebpackPlugin = require('html-webpack-plugin');
9 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
10 |
11 | module.exports = webpackMerge(webpackCommon, {
12 |
13 | devtool: 'inline-source-map',
14 |
15 | entry: {
16 | 'app': ['react-hot-loader/patch',
17 | './src/renderer/index.js'
18 | ],
19 | 'vendor': './src/vendor.js'
20 | },
21 |
22 | output: {
23 |
24 | path: path.resolve(__dirname, '../static/dist'),
25 |
26 | filename: '[name].js',
27 |
28 | // sourceMapFilename: '[name].map',
29 |
30 | // chunkFilename: '[id]-chunk.js',
31 |
32 | publicPath: '/'
33 |
34 | },
35 |
36 | module: {
37 |
38 | rules: [
39 | {
40 | test: /\.css$/,
41 | loader: ExtractTextPlugin.extract({
42 | fallbackLoader: 'style-loader',
43 | loader: [
44 | 'css-loader?modules&localIdentName=[name]__[local]&minimize&sourceMap&importLoaders=2',
45 | 'postcss-loader',
46 | // 'sass-loader?outputStyle=expanded&sourceMap&sourceMapContents'
47 | ]
48 | })
49 | }
50 | // {
51 | // test: /\.js$/,
52 | // exclude: /node_modules/,
53 | // use: ['babel-loader']
54 | // },
55 | // {
56 | // test: /\.css$/,
57 | // use: [
58 | // {
59 | // loader: 'style-loader'
60 | // },
61 | // {
62 | // loader: 'css-loader',
63 | // options: {
64 | // importLoaders: 2,
65 | // modules: true,
66 | // localIdentName: '[name]__[local]'
67 | // }
68 | // },
69 | // {
70 | // loader: 'postcss-loader'
71 | // },
72 | // ]
73 | // }
74 | ]
75 |
76 | },
77 |
78 | plugins: [
79 |
80 | new HtmlWebpackPlugin({
81 | inject: true,
82 | template: path.resolve(__dirname, '../static/index.html'),
83 | // favicon: path.resolve(__dirname, '../static/favicon.ico')
84 | }),
85 | new ExtractTextPlugin('[name].css'),
86 | new webpack.HotModuleReplacementPlugin()
87 | ],
88 |
89 | devServer: {
90 | host: 'localhost',
91 | port: 3000,
92 | // open: true,
93 | hot: true,
94 | stats: { chunks: false },
95 | historyApiFallback: true,
96 | watchOptions: {
97 | aggregateTimeout: 300,
98 | poll: 1000
99 | },
100 | },
101 | performance: {
102 | hints: false
103 | }
104 |
105 | });
106 |
--------------------------------------------------------------------------------
/webpack/prod.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpackMerge = require('webpack-merge');
3 | const autoprefixer = require('autoprefixer');
4 | const webpackCommon = require('./common.config');
5 |
6 | // webpack plugins
7 | const HtmlWebpackPlugin = require('html-webpack-plugin');
8 | const DefinePlugin = require('webpack/lib/DefinePlugin');
9 | const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
10 | // const CleanWebpackPlugin = require('clean-webpack-plugin');
11 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
12 | const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
13 | const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
14 | const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin');
15 |
16 | module.exports = webpackMerge(webpackCommon, {
17 |
18 | bail: true,
19 |
20 | devtool: 'source-map',
21 |
22 | entry: {
23 | 'app': [
24 | './src/renderer/index.js'
25 | ],
26 | 'vendor': './src/vendor.js'
27 | },
28 |
29 | output: {
30 |
31 | path: path.resolve(__dirname, '../dist'),
32 |
33 | filename: '[name]-[chunkhash].min.js',
34 | // filename: '[name].js',
35 | // sourceMapFilename: '[name]-[hash].map',
36 |
37 | // chunkFilename: '[id]-[chunkhash].js'
38 | chunkFilename: '[chunkhash].js'
39 |
40 | },
41 |
42 | module: {
43 |
44 | // TODO: use webpack old syntax to compatible with ExtractTextPlugin
45 | // https://github.com/webpack/extract-text-webpack-plugin/issues/275
46 | rules: [
47 | {
48 | test: /\.css$/,
49 | loader: ExtractTextPlugin.extract({
50 | fallbackLoader: 'style-loader',
51 | loader: [
52 | 'css-loader?modules&localIdentName=[name]__[local]&minimize&sourceMap&importLoaders=2',
53 | 'postcss-loader',
54 | // 'sass-loader?outputStyle=expanded&sourceMap&sourceMapContents'
55 | ]
56 | })
57 | }
58 | ]
59 |
60 | },
61 |
62 | plugins: [
63 | new CommonsChunkPlugin({
64 | name: 'manifest',
65 | // filename: 'manifest.js',
66 | minChunks: Infinity,
67 | }),
68 |
69 | new HtmlWebpackPlugin({
70 | inject: true,
71 | template: path.resolve(__dirname, '../static/index.html'),
72 | // favicon: path.resolve(__dirname, '../static/favicon.ico'),
73 | // excludeChunks: ['vendors'],
74 | minify: {
75 | removeComments: true,
76 | collapseWhitespace: true,
77 | removeRedundantAttributes: true,
78 | useShortDoctype: true,
79 | removeEmptyAttributes: true,
80 | removeStyleLinkTypeAttributes: true,
81 | keepClosingSlash: true,
82 | minifyJS: true,
83 | minifyCSS: true,
84 | minifyURLs: true
85 | }
86 | }),
87 | new InlineChunkWebpackPlugin({
88 | inlineChunks: ['manifest']
89 | }),
90 | // new DedupePlugin(),
91 | // new CleanWebpackPlugin(['dist'], {
92 | // root: path.resolve(__dirname, '..'),
93 | // exclude: '.gitignore'
94 | // }),
95 | // new DefinePlugin({
96 | // 'process.env': {
97 | // NODE_ENV: '"production"'
98 | // }
99 | // }),
100 | new ExtractTextPlugin('[name].css'),
101 | new UglifyJsPlugin({
102 | compressor: {
103 | screw_ie8: true,
104 | warnings: false
105 | },
106 | mangle: {
107 | screw_ie8: true
108 | },
109 | output: {
110 | comments: false,
111 | screw_ie8: true
112 | },
113 | // sourceMap: true
114 | }),
115 | new LoaderOptionsPlugin({
116 | options: {
117 | context: '/',
118 | postcss: function () {
119 | return [autoprefixer];
120 | }
121 | }
122 | })
123 | ]
124 |
125 | });
126 |
--------------------------------------------------------------------------------