├── .vscode └── settings.json ├── src ├── features │ ├── common │ │ ├── redux │ │ │ ├── actions.js │ │ │ ├── constants.js │ │ │ ├── initialState.js │ │ │ └── reducer.js │ │ ├── OpenLink.less │ │ ├── FolderPicker.less │ │ ├── PageNotFound.less │ │ ├── style.less │ │ ├── WebView.less │ │ ├── route.js │ │ ├── index.js │ │ ├── PageNotFound.js │ │ ├── OpenLink.js │ │ ├── FormBuilder.less │ │ ├── WebView.js │ │ └── FolderPicker.js │ ├── home │ │ ├── DialogPlace.less │ │ ├── App.less │ │ ├── style.less │ │ ├── route.js │ │ ├── index.js │ │ ├── redux │ │ │ ├── actions.js │ │ │ ├── initialState.js │ │ │ ├── showWelcomePage.js │ │ │ ├── hideNewProjectDialog.js │ │ │ ├── showNewProjectDialog.js │ │ │ ├── reducer.js │ │ │ ├── constants.js │ │ │ ├── closeProject.js │ │ │ ├── openProject.js │ │ │ ├── getInitialState.js │ │ │ └── getMainState.js │ │ ├── DialogPlace.js │ │ ├── NewProjectDialog.less │ │ ├── RekitStudioPage.less │ │ ├── RecentProjects.less │ │ ├── WelcomePage.less │ │ ├── RecentProjects.js │ │ ├── App.js │ │ ├── NewProjectDialog.js │ │ ├── utils.js │ │ └── WelcomePage.js │ ├── plugin-manager │ │ ├── PluginIcon.less │ │ ├── PluginDetail.less │ │ ├── style.less │ │ ├── route.js │ │ ├── index.js │ │ ├── redux │ │ │ ├── actions.js │ │ │ ├── initialState.js │ │ │ ├── reducer.js │ │ │ ├── constants.js │ │ │ ├── enablePlugin.js │ │ │ ├── disablePlugin.js │ │ │ ├── fetchOnlinePlugins.js │ │ │ ├── fetchInstalledPlugins.js │ │ │ └── uninstallPlugin.js │ │ ├── PluginIcon.js │ │ ├── PluginHeader.js │ │ ├── InstallButton.less │ │ ├── MainPage.less │ │ ├── PluginHeader.less │ │ ├── PluginList.less │ │ ├── PluginDetail.js │ │ ├── MainPage.js │ │ └── InstallButton.js │ └── new-project │ │ ├── NewProjectForm.less │ │ ├── ProjectProperties.less │ │ ├── CreatingStatusView.less │ │ ├── style.less │ │ ├── redux │ │ ├── actions.js │ │ ├── constants.js │ │ ├── clearCreateAppStatus.js │ │ ├── initialState.js │ │ ├── reducer.js │ │ ├── createApp.js │ │ └── fetchAppTypes.js │ │ ├── route.js │ │ ├── NewProjectDialog.less │ │ ├── index.js │ │ ├── ProjectProperties.js │ │ ├── AppTypeSelect.less │ │ ├── NewProjectForm.js │ │ ├── CreatingStatusView.js │ │ └── AppTypeSelect.js ├── styles │ ├── index.scss │ ├── index.less │ ├── mixins.less │ └── global.less ├── favicon.png ├── images │ ├── logo.png │ ├── logo2.png │ ├── plugin-logo.png │ ├── discord.svg │ ├── react-logo.svg │ └── rekit-logo.svg ├── fonts │ ├── roboto-v19-latin-100.eot │ ├── roboto-v19-latin-100.ttf │ ├── roboto-v19-latin-100.woff │ ├── roboto-v19-latin-100.woff2 │ ├── roboto-v19-latin-300.eot │ ├── roboto-v19-latin-300.ttf │ ├── roboto-v19-latin-300.woff │ └── roboto-v19-latin-300.woff2 ├── common │ ├── history.js │ ├── store.js │ ├── rootReducer.js │ ├── configStore.js │ └── routeConfig.js ├── index.js ├── Root.js └── logo.svg ├── tools └── index.js ├── icon.png ├── icon.icns ├── scripts ├── release.js ├── publish.js ├── copyPlugins.js ├── buildElectron.js ├── build.js ├── test.js └── startRekitStudio.js ├── public ├── favicon.ico ├── manifest.json └── index.html ├── node ├── store.js ├── logger.js ├── log.js ├── bridge.js ├── studio.js ├── ua.js ├── utils.js ├── checkUpdate.js ├── init.js ├── taskRunner.js └── main.js ├── .editorconfig ├── .prettierrc ├── README.md ├── .babelrc ├── rekit.json ├── tests ├── index.test.js ├── features │ ├── common │ │ ├── OpenLink.test.js │ │ ├── FolderPicker.test.js │ │ ├── FormBuilder.test.js │ │ └── PageNotFound.test.js │ ├── plugin-manager │ │ ├── PluginIcon.test.js │ │ ├── PluginHeader.test.js │ │ ├── MainPage.test.js │ │ ├── PluginList.test.js │ │ ├── InstallButton.test.js │ │ ├── PluginDetail.test.js │ │ └── redux │ │ │ ├── enablePlugin.test.js │ │ │ ├── disablePlugin.test.js │ │ │ ├── installPlugin.test.js │ │ │ ├── uninstallPlugin.test.js │ │ │ ├── fetchOnlinePlugins.test.js │ │ │ └── fetchInstalledPlugins.test.js │ ├── home │ │ ├── App.test.js │ │ ├── DefaultPage.test.js │ │ ├── RecentProjects.test.js │ │ └── redux │ │ │ └── showWelcomePage.test.js │ └── new-project │ │ ├── NewProjectForm.test.js │ │ ├── AppTypeSelect.test.js │ │ ├── CreatingStatusView.test.js │ │ └── redux │ │ ├── clearCreateAppStatus.test.js │ │ ├── createApp.test.js │ │ └── fetchAppTypes.test.js ├── setup.js └── Root.test.js ├── test.js ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── paths.js └── env.js └── .gitignore /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /src/features/common/redux/actions.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/common/redux/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/index.js: -------------------------------------------------------------------------------- 1 | // Just a placeholder. -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | img {width: 100px;} -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/icon.png -------------------------------------------------------------------------------- /icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/icon.icns -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('child_process'); 2 | spawn('npx', []) -------------------------------------------------------------------------------- /src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/favicon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /node/store.js: -------------------------------------------------------------------------------- 1 | const Store = require('electron-store'); 2 | module.exports = new Store(); 3 | -------------------------------------------------------------------------------- /src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/images/logo.png -------------------------------------------------------------------------------- /src/images/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/images/logo2.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | { 2 | indent_style: 'space', 3 | indent_size: 2, 4 | tab_width: 2 5 | }; 6 | -------------------------------------------------------------------------------- /node/logger.js: -------------------------------------------------------------------------------- 1 | module.exports = require('rekit-core').core.logger.child({ label: 'app' }); 2 | -------------------------------------------------------------------------------- /src/images/plugin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/images/plugin-logo.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /src/features/common/OpenLink.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .common-open-link { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/features/home/DialogPlace.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .home-dialog-place { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | # Develop 4 | How to publish: 5 | ``` 6 | npx electron-builder -p always 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /src/features/common/FolderPicker.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .common-folder-picker { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-100.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-100.eot -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-100.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-100.ttf -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-100.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-100.woff -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-100.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-100.woff2 -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-300.eot -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-300.ttf -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-300.woff -------------------------------------------------------------------------------- /src/fonts/roboto-v19-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rekit/rekit-app/HEAD/src/fonts/roboto-v19-latin-300.woff2 -------------------------------------------------------------------------------- /src/features/plugin-manager/PluginIcon.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .plugin-manager-plugin-icon { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-app"], 3 | "plugins": ["react-hot-loader/babel", "syntax-dynamic-import", "lodash"] 4 | } 5 | -------------------------------------------------------------------------------- /src/features/new-project/NewProjectForm.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .new-project-new-project-form { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/features/plugin-manager/PluginDetail.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .plugin-manager-plugin-detail { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/features/new-project/ProjectProperties.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .new-project-project-properties { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/features/common/PageNotFound.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .common-page-not-found { 4 | color: #fff; 5 | padding: 10px; 6 | } 7 | -------------------------------------------------------------------------------- /rekit.json: -------------------------------------------------------------------------------- 1 | { 2 | "devPort": 6093, 3 | "studioPort": 3002, 4 | "appType": "rekit-react", 5 | "rekitTest": true, 6 | "exclude": ["dist"], 7 | "css": "less" 8 | } 9 | -------------------------------------------------------------------------------- /src/features/new-project/CreatingStatusView.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .new-project-creating-status-view { 4 | .ant-timeline { 5 | margin: 20px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/common/history.js: -------------------------------------------------------------------------------- 1 | import { createHashHistory } from 'history'; 2 | 3 | // A singleton history object for easy API navigation 4 | const history = createHashHistory(); 5 | export default history; 6 | -------------------------------------------------------------------------------- /src/features/common/style.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | @import './WebView'; 3 | @import './FormBuilder'; 4 | @import './FolderPicker'; 5 | @import './OpenLink'; 6 | @import './PageNotFound'; 7 | -------------------------------------------------------------------------------- /src/features/home/App.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .home-app { 4 | .page-container { 5 | z-index: 0; 6 | .abs-fullsize(); 7 | top: @header-height; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /node/log.js: -------------------------------------------------------------------------------- 1 | const log = require('electron-log'); 2 | // Config electron log 3 | log.transports.console.level = 'info'; 4 | log.transports.file.level = 'info'; 5 | log.info('electron log set up.'); 6 | 7 | module.exports = log; -------------------------------------------------------------------------------- /src/features/common/WebView.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .common-web-view { 4 | position: relative; 5 | width: 100%; 6 | height: 100%; 7 | webview { 8 | .abs-fullsize; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/features/new-project/style.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | @import './ProjectProperties'; 3 | @import './NewProjectDialog'; 4 | @import './NewProjectForm'; 5 | @import './AppTypeSelect'; 6 | @import './CreatingStatusView'; 7 | -------------------------------------------------------------------------------- /src/features/plugin-manager/style.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | @import './MainPage'; 3 | @import './PluginList'; 4 | @import './PluginHeader'; 5 | @import './PluginDetail'; 6 | @import './PluginIcon'; 7 | @import './InstallButton'; 8 | -------------------------------------------------------------------------------- /tests/index.test.js: -------------------------------------------------------------------------------- 1 | // index.js should run without errors. 2 | describe('index', () => { 3 | it('index.js has no error', () => { 4 | document.body.innerHTML = '
'; 5 | require('../src/index'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/features/new-project/redux/actions.js: -------------------------------------------------------------------------------- 1 | export { fetchAppTypes, dismissFetchAppTypesError } from './fetchAppTypes'; 2 | export { createApp, dismissCreateAppError } from './createApp'; 3 | export { clearCreateAppStatus } from './clearCreateAppStatus'; 4 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | // index is the entry for all styles. 2 | @import './global'; 3 | @import '../features/home/style'; 4 | @import '../features/common/style'; 5 | @import '../features/new-project/style'; 6 | @import '../features/plugin-manager/style'; 7 | -------------------------------------------------------------------------------- /src/features/home/style.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | @import './App'; 3 | @import './WelcomePage'; 4 | @import './RekitStudioPage'; 5 | @import './TitleBar'; 6 | @import './NewProjectDialog'; 7 | @import './DialogPlace'; 8 | @import './RecentProjects'; 9 | -------------------------------------------------------------------------------- /src/features/new-project/route.js: -------------------------------------------------------------------------------- 1 | // This is the JSON way to define React Router rules in a Rekit app. 2 | // Learn more from: http://rekit.js.org/docs/routing.html 3 | 4 | 5 | export default { 6 | path: 'new-project', 7 | childRoutes: [ 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | // const chokidar = require('chokidar'); 2 | // const watcher = chokidar.watch(__dirname, { ignored: /node_modules/ }); 3 | // watcher.on('all', (...args) => console.log('file changed', args)); 4 | // setInterval(() => { 5 | console.log(new Date()); 6 | // }, 1000); 7 | -------------------------------------------------------------------------------- /src/features/common/route.js: -------------------------------------------------------------------------------- 1 | // This is the JSON way to define React Router rules in a Rekit app. 2 | // Learn more from: http://rekit.js.org/docs/routing.html 3 | 4 | export default { 5 | path: 'common', 6 | name: 'Common', 7 | childRoutes: [ 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /src/features/home/route.js: -------------------------------------------------------------------------------- 1 | import { WelcomePage } from './'; 2 | 3 | export default { 4 | path: '/', 5 | name: 'Home', 6 | childRoutes: [ 7 | { 8 | path: 'welcome', 9 | component: WelcomePage, 10 | isIndex: true, 11 | }, 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /src/features/new-project/NewProjectDialog.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .new-project-new-project-dialog { 4 | .ant-modal-body { 5 | height: 500px; 6 | text-align:justify; 7 | position: relative; 8 | } 9 | 10 | .btn-back { 11 | float: left; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 1. Build Rekit Studio: yarn build 3 | * 2. Build Rekit Plugins: yarn build 4 | * 3. Build Rekit App static server: yarn build This will copy built-in plugins to build server 5 | * 4. Build Electron app: yarn dist 6 | * 7 | * To test: 8 | * 1. Remove /.rekit folders 9 | */ -------------------------------------------------------------------------------- /src/features/common/index.js: -------------------------------------------------------------------------------- 1 | export { default as PageNotFound } from './PageNotFound'; 2 | export { default as WebView } from './WebView'; 3 | export { default as FormBuilder } from './FormBuilder'; 4 | export { default as FolderPicker } from './FolderPicker'; 5 | export { default as OpenLink } from './OpenLink'; 6 | -------------------------------------------------------------------------------- /tests/features/common/OpenLink.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { OpenLink } from '../../../src/features/common'; 4 | 5 | it('renders node with correct class name', () => { 6 | const renderedComponent = shallow(); 7 | expect(renderedComponent.find('.common-open-link').length).toBe(1); 8 | }); 9 | -------------------------------------------------------------------------------- /src/features/plugin-manager/route.js: -------------------------------------------------------------------------------- 1 | import { MainPage } from './'; 2 | // This is the JSON way to define React Router rules in a Rekit app. 3 | // Learn more from: http://rekit.js.org/docs/routing.html 4 | 5 | 6 | export default { 7 | path: 'plugins', 8 | childRoutes: [ 9 | { path: '/plugins/:plugin?', component: MainPage }, 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /src/features/new-project/index.js: -------------------------------------------------------------------------------- 1 | export { default as ProjectProperties } from './ProjectProperties'; 2 | export { default as NewProjectDialog } from './NewProjectDialog'; 3 | export { default as NewProjectForm } from './NewProjectForm'; 4 | export { default as AppTypeSelect } from './AppTypeSelect'; 5 | export { default as CreatingStatusView } from './CreatingStatusView'; 6 | -------------------------------------------------------------------------------- /tests/features/common/FolderPicker.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { FolderPicker } from '../../../src/features/common'; 4 | 5 | it('renders node with correct class name', () => { 6 | const renderedComponent = shallow(); 7 | expect(renderedComponent.find('.common-folder-picker').length).toBe(1); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/features/common/FormBuilder.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { FormBuilder } from '../../../src/features/common'; 4 | 5 | it('renders node with correct class name', () => { 6 | const renderedComponent = shallow(); 7 | expect(renderedComponent.find('.common-form-builder-js').length).toBe(1); 8 | }); 9 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/features/plugin-manager/index.js: -------------------------------------------------------------------------------- 1 | export { default as MainPage } from './MainPage'; 2 | export { default as PluginList } from './PluginList'; 3 | export { default as PluginHeader } from './PluginHeader'; 4 | export { default as PluginDetail } from './PluginDetail'; 5 | export { default as PluginIcon } from './PluginIcon'; 6 | export { default as InstallButton } from './InstallButton'; 7 | -------------------------------------------------------------------------------- /tests/features/plugin-manager/PluginIcon.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { PluginIcon } from '../../../src/features/plugin-manager'; 4 | 5 | it('renders node with correct class name', () => { 6 | const renderedComponent = shallow(); 7 | expect(renderedComponent.find('.plugin-manager-plugin-icon').length).toBe(1); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/features/home/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { App } from '../../../src/features/home'; 4 | 5 | describe('home/App', () => { 6 | it('renders node with correct class name', () => { 7 | const renderedComponent = shallow(); 8 | 9 | expect(renderedComponent.find('.home-app').length).toBe(1); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/features/new-project/NewProjectForm.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { NewProjectForm } from '../../../src/features/new-project'; 4 | 5 | it('renders node with correct class name', () => { 6 | const renderedComponent = shallow(); 7 | expect(renderedComponent.find('.new-project-new-project-form').length).toBe(1); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/features/plugin-manager/PluginHeader.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { PluginHeader } from '../../../src/features/plugin-manager'; 4 | 5 | it('renders node with correct class name', () => { 6 | const renderedComponent = shallow(); 7 | expect(renderedComponent.find('.plugin-manager-plugin-header').length).toBe(1); 8 | }); 9 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /node/bridge.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer, shell, remote } = require('electron'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const ua = require('./ua'); 5 | 6 | window.bridge = { 7 | ipcRenderer, 8 | isWin: process.platform === 'win32', 9 | shell, 10 | remote, 11 | fs, 12 | path, 13 | ua, 14 | openUrl(url) { 15 | shell.openExternal(url); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /node/studio.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const PATH_APP_NODE_MODULES = path.join(__dirname, '../node-app/nms'); 4 | require('module').globalPaths.push(PATH_APP_NODE_MODULES); 5 | module.paths.push(PATH_APP_NODE_MODULES); 6 | console.log(PATH_APP_NODE_MODULES); 7 | require('rekit-studio/lib/start')({ 8 | projectRoot: '/Users/pwang7/workspace/rekit-studio', 9 | port: '9001', 10 | }); 11 | -------------------------------------------------------------------------------- /scripts/copyPlugins.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | const srcDir = path.join(__dirname, '../../rekit-studio/src/features'); 4 | const destDir = path.join(__dirname, '../build/plugins'); 5 | fs.ensureDirSync(destDir); 6 | [].forEach(plugin => { 7 | fs.copySync(path.join(srcDir, plugin), path.join(destDir, plugin)); 8 | }); 9 | console.log('Built-in plugin copied.'); 10 | -------------------------------------------------------------------------------- /scripts/buildElectron.js: -------------------------------------------------------------------------------- 1 | // Copy files before build electron app 2 | const path = require('path'); 3 | const fs = require('fs-extra'); 4 | 5 | const buildDir = path.join(__dirname, '../build'); 6 | console.log('copy files'); 7 | ['rekit-react', 'ebay-node'].forEach(name => { 8 | fs.copySync( 9 | path.join(__dirname, '../../rekit-studio/src/features', name), 10 | path.join(buildDir, 'plugins', name), 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /src/features/home/index.js: -------------------------------------------------------------------------------- 1 | export { default as App } from './App'; 2 | export { default as WelcomePage } from './WelcomePage'; 3 | export { default as RekitStudioPage } from './RekitStudioPage'; 4 | export { default as TitleBar } from './TitleBar'; 5 | export { default as NewProjectDialog } from './NewProjectDialog'; 6 | export { default as DialogPlace } from './DialogPlace'; 7 | export { default as RecentProjects } from './RecentProjects'; 8 | -------------------------------------------------------------------------------- /tests/features/common/PageNotFound.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { PageNotFound } from '../../../src/features/common'; 4 | 5 | describe('common/PageNotFound', () => { 6 | it('renders node with correct class name', () => { 7 | const renderedComponent = shallow(); 8 | 9 | expect(renderedComponent.find('.common-page-not-found').length).toBe(1); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/features/common/PageNotFound.js: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react'; 2 | 3 | export default class PageNotFound extends PureComponent { 4 | render() { 5 | return null; 6 | } 7 | // render() { 8 | // return ( 9 | //
10 | // Ops, page not found. 11 | //

12 | // Back to welcome page. 13 | //

14 | //
15 | // ); 16 | // } 17 | } 18 | -------------------------------------------------------------------------------- /src/features/common/OpenLink.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class OpenLink extends Component { 4 | handleClick = evt => { 5 | evt.preventDefault(); 6 | evt.stopPropagation(); 7 | window.bridge.shell.openExternal(this.props.href); 8 | }; 9 | 10 | render() { 11 | return ( 12 | 13 | {this.props.children} 14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/features/home/redux/actions.js: -------------------------------------------------------------------------------- 1 | export { openProject, dismissOpenProjectError } from './openProject'; 2 | export { closeProject, dismissCloseProjectError } from './closeProject'; 3 | export { getInitialState, dismissGetInitialStateError } from './getInitialState'; 4 | export { getMainState, dismissGetMainStateError } from './getMainState'; 5 | export { showNewProjectDialog } from './showNewProjectDialog'; 6 | export { hideNewProjectDialog } from './hideNewProjectDialog'; 7 | export { showWelcomePage } from './showWelcomePage'; 8 | -------------------------------------------------------------------------------- /src/features/home/redux/initialState.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | openProjectPending: false, 3 | openProjectError: null, 4 | closeProjectPending: false, 5 | closeProjectError: null, 6 | 7 | studios: [], 8 | studioById: {}, 9 | getInitialStatePending: false, 10 | getInitialStateError: null, 11 | 12 | initializing: true, 13 | getMainStatePending: false, 14 | getMainStateError: null, 15 | 16 | newProjectDialogVisible: false, 17 | welcomePageVisible: false, 18 | }; 19 | 20 | export default initialState; 21 | -------------------------------------------------------------------------------- /tests/features/home/DefaultPage.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { DefaultPage } from '../../../src/features/home/DefaultPage'; 4 | 5 | describe('home/DefaultPage', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | home: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow(); 12 | 13 | expect(renderedComponent.find('.home-default-page').length).toBe(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/features/plugin-manager/redux/actions.js: -------------------------------------------------------------------------------- 1 | export { fetchInstalledPlugins, dismissFetchInstalledPluginsError } from './fetchInstalledPlugins'; 2 | export { enablePlugin, dismissEnablePluginError } from './enablePlugin'; 3 | export { disablePlugin, dismissDisablePluginError } from './disablePlugin'; 4 | export { installPlugin, dismissInstallPluginError } from './installPlugin'; 5 | export { uninstallPlugin, dismissUninstallPluginError } from './uninstallPlugin'; 6 | export { fetchOnlinePlugins, dismissFetchOnlinePluginsError } from './fetchOnlinePlugins'; 7 | -------------------------------------------------------------------------------- /src/common/store.js: -------------------------------------------------------------------------------- 1 | import configStore from './configStore'; 2 | 3 | export default { 4 | store: null, 5 | getStore() { 6 | if (!this.store) this.store = configStore(); 7 | return this.store; 8 | }, 9 | getState() { 10 | return this.getStore().getState(); 11 | }, 12 | dispatch(action) { 13 | return this.getStore().dispatch(action); 14 | }, 15 | subscribe(listener) { 16 | return this.getStore().subscribe(listener); 17 | }, 18 | replaceReducer(reducer) { 19 | return this.getStore().replaceReducer(reducer); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | // This module will be executed before all other tests are executed, 2 | // so import all necessary modules which should be included for webpack compiling. 3 | import axios from 'axios'; 4 | import httpAdapter from 'axios/lib/adapters/http'; 5 | import { configure } from 'enzyme'; 6 | import Adapter from 'enzyme-adapter-react-16'; 7 | 8 | configure({ adapter: new Adapter(), disableLifecycleMethods: true }); 9 | 10 | if (process.env.NODE_ENV === 'test') { 11 | axios.defaults.baseURL = 'http://localhost'; 12 | axios.defaults.adapter = httpAdapter; 13 | } 14 | -------------------------------------------------------------------------------- /tests/features/home/RecentProjects.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { RecentProjects } from '../../../src/features/home/RecentProjects'; 4 | 5 | describe('home/RecentProjects', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | home: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | 15 | expect( 16 | renderedComponent.find('.home-recent-projects').length 17 | ).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/features/plugin-manager/MainPage.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { MainPage } from '../../../src/features/plugin-manager/MainPage'; 4 | 5 | describe('plugin-manager/MainPage', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | pluginManager: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | 15 | expect( 16 | renderedComponent.find('.plugin-manager-main-page').length 17 | ).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/features/plugin-manager/PluginList.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { PluginList } from '../../../src/features/plugin-manager/PluginList'; 4 | 5 | describe('plugin-manager/PluginList', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | pluginManager: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | 15 | expect( 16 | renderedComponent.find('.plugin-manager-plugin-list').length 17 | ).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/features/new-project/AppTypeSelect.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { AppTypeSelect } from '../../../src/features/new-project/AppTypeSelect'; 4 | 5 | describe('new-project/AppTypeSelect', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | newProject: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | 15 | expect( 16 | renderedComponent.find('.new-project-app-type-select').length 17 | ).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/features/plugin-manager/InstallButton.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { InstallButton } from '../../../src/features/plugin-manager/PluginButton'; 4 | 5 | describe('plugin-manager/InstallButton', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | pluginManager: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | 15 | expect( 16 | renderedComponent.find('.plugin-manager-plugin-button').length 17 | ).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/features/plugin-manager/PluginDetail.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { PluginDetail } from '../../../src/features/plugin-manager/PluginDetail'; 4 | 5 | describe('plugin-manager/PluginDetail', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | pluginManager: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | 15 | expect( 16 | renderedComponent.find('.plugin-manager-plugin-detail').length 17 | ).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/features/new-project/CreatingStatusView.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { CreatingStatusView } from '../../../src/features/new-project/CreatingStatusView'; 4 | 5 | describe('new-project/CreatingStatusView', () => { 6 | it('renders node with correct class name', () => { 7 | const props = { 8 | newProject: {}, 9 | actions: {}, 10 | }; 11 | const renderedComponent = shallow( 12 | 13 | ); 14 | 15 | expect( 16 | renderedComponent.find('.new-project-creating-status-view').length 17 | ).toBe(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const paths = require('../config/paths'); 3 | 4 | const buildStatic = require('./buildStatic'); 5 | 6 | console.log('Building static assets...'); 7 | // buildStatic().then(() => { 8 | // console.log('Build static assets done.'); 9 | 10 | // // Copy node files 11 | // console.log('Copy node files'); 12 | 13 | // // Process index.html 14 | // }); 15 | 16 | function buildElectron() { 17 | fs.removeSync(paths.resolveApp('app/node')); 18 | fs.copySync(paths.resolveApp('node'), paths.resolveApp('app/node'), { 19 | dereference: true, 20 | }); 21 | } 22 | 23 | // buildElectron(); 24 | buildStatic(); 25 | -------------------------------------------------------------------------------- /src/features/plugin-manager/PluginIcon.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | const defaultPluginLogo = require('../../images/plugin-logo.png'); 3 | 4 | export default class PluginIcon extends Component { 5 | static propTypes = {}; 6 | 7 | render() { 8 | const { item } = this.props; 9 | return ( 10 | { 19 | evt.target.src = defaultPluginLogo; 20 | }} 21 | /> 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /node/ua.js: -------------------------------------------------------------------------------- 1 | const store = require('./store'); 2 | const uuidv4 = require('uuid/v4'); 3 | const log = require('./log'); 4 | const analytics = require('universal-analytics'); 5 | 6 | let uuid = store.get('uuid'); 7 | if (!uuid) { 8 | uuid = uuidv4(); 9 | log.info('uuid created: ', uuid); 10 | store.set('uuid', uuid); 11 | } else { 12 | log.info('uuid exists: ', uuid); 13 | } 14 | 15 | const ua = analytics('UA-132547525-1', uuid, { http: true }); 16 | ua.set('uid', uuid); 17 | ua.set('source', 'rekit-app'); 18 | 19 | ua.screenview('start-page', 'rekit-app', '3.0.0', err => { 20 | if (err) { 21 | log.warn('ga screenview start-page failed'); 22 | log.warn(err); 23 | } 24 | }).send(); 25 | 26 | module.exports = ua; 27 | -------------------------------------------------------------------------------- /src/features/home/redux/showWelcomePage.js: -------------------------------------------------------------------------------- 1 | // Rekit uses a new approach to organizing actions and reducers. That is 2 | // putting related actions and reducers in one file. See more at: 3 | // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da 4 | 5 | import { 6 | HOME_SHOW_WELCOME_PAGE, 7 | } from './constants'; 8 | 9 | export function showWelcomePage() { 10 | return { 11 | type: HOME_SHOW_WELCOME_PAGE, 12 | }; 13 | } 14 | 15 | export function reducer(state, action) { 16 | switch (action.type) { 17 | case HOME_SHOW_WELCOME_PAGE: 18 | return { 19 | ...state, 20 | welcomePageVisible: true, 21 | }; 22 | 23 | default: 24 | return state; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/features/common/redux/initialState.js: -------------------------------------------------------------------------------- 1 | // Initial state is the place you define all initial values for the Redux store of the feature. 2 | // In the 'standard' way, initialState is defined in reducers: http://redux.js.org/docs/basics/Reducers.html 3 | // But when application grows, there will be multiple reducers files, it's not intuitive what data is managed by the whole store. 4 | // So Rekit extracts the initial state definition into a separate module so that you can have 5 | // a quick view about what data is used for the feature, at any time. 6 | 7 | // NOTE: initialState constant is necessary so that Rekit could auto add initial state when creating async actions. 8 | 9 | const initialState = { 10 | }; 11 | 12 | export default initialState; 13 | -------------------------------------------------------------------------------- /src/features/home/redux/hideNewProjectDialog.js: -------------------------------------------------------------------------------- 1 | // Rekit uses a new approach to organizing actions and reducers. That is 2 | // putting related actions and reducers in one file. See more at: 3 | // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da 4 | 5 | import { 6 | HOME_HIDE_NEW_PROJECT_DIALOG, 7 | } from './constants'; 8 | 9 | export function hideNewProjectDialog() { 10 | return { 11 | type: HOME_HIDE_NEW_PROJECT_DIALOG, 12 | }; 13 | } 14 | 15 | export function reducer(state, action) { 16 | switch (action.type) { 17 | case HOME_HIDE_NEW_PROJECT_DIALOG: 18 | return { 19 | ...state, 20 | newProjectDialogVisible: false, 21 | }; 22 | 23 | default: 24 | return state; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/features/home/redux/showNewProjectDialog.js: -------------------------------------------------------------------------------- 1 | // Rekit uses a new approach to organizing actions and reducers. That is 2 | // putting related actions and reducers in one file. See more at: 3 | // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da 4 | 5 | import { 6 | HOME_SHOW_NEW_PROJECT_DIALOG, 7 | } from './constants'; 8 | 9 | export function showNewProjectDialog() { 10 | return { 11 | type: HOME_SHOW_NEW_PROJECT_DIALOG, 12 | }; 13 | } 14 | 15 | export function reducer(state, action) { 16 | switch (action.type) { 17 | case HOME_SHOW_NEW_PROJECT_DIALOG: 18 | return { 19 | ...state, 20 | newProjectDialogVisible: true, 21 | }; 22 | 23 | default: 24 | return state; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/features/common/FormBuilder.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .form-builder-row { 4 | .ant-form-item { 5 | margin-bottom: 5px; 6 | } 7 | } 8 | 9 | .form-builder-row-view-mode { 10 | .ant-form-item-label { 11 | text-align: left; 12 | line-height: 150%; 13 | font-weight: normal; 14 | label { 15 | font-weight: bold; 16 | padding-left: 5px; 17 | } 18 | } 19 | .ant-form-item-control { 20 | line-height: 150%; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | } 24 | .ant-form-item { 25 | padding: 6px 0; 26 | white-space: nowrap; 27 | &:hover { 28 | background-color: #f7f7f7; 29 | } 30 | 31 | } 32 | } 33 | 34 | .ant-form-item-children { 35 | .ant-input-number { 36 | width: 100%; 37 | } 38 | } -------------------------------------------------------------------------------- /src/features/home/DialogPlace.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { NewProjectDialog } from '../new-project'; 5 | 6 | export class DialogPlace extends Component { 7 | static propTypes = { 8 | newProjectDialogVisible: PropTypes.bool.isRequired, 9 | }; 10 | 11 | render() { 12 | return ( 13 | 14 | {this.props.newProjectDialogVisible && } 15 | 16 | ); 17 | } 18 | } 19 | 20 | /* istanbul ignore next */ 21 | function mapStateToProps(state) { 22 | return { 23 | newProjectDialogVisible: state.home.newProjectDialogVisible, 24 | }; 25 | } 26 | 27 | export default connect( 28 | mapStateToProps, 29 | )(DialogPlace); 30 | -------------------------------------------------------------------------------- /src/features/new-project/redux/constants.js: -------------------------------------------------------------------------------- 1 | export const NEW_PROJECT_FETCH_APP_TYPES_BEGIN = 'NEW_PROJECT_FETCH_APP_TYPES_BEGIN'; 2 | export const NEW_PROJECT_FETCH_APP_TYPES_SUCCESS = 'NEW_PROJECT_FETCH_APP_TYPES_SUCCESS'; 3 | export const NEW_PROJECT_FETCH_APP_TYPES_FAILURE = 'NEW_PROJECT_FETCH_APP_TYPES_FAILURE'; 4 | export const NEW_PROJECT_FETCH_APP_TYPES_DISMISS_ERROR = 'NEW_PROJECT_FETCH_APP_TYPES_DISMISS_ERROR'; 5 | export const NEW_PROJECT_CREATE_APP_BEGIN = 'NEW_PROJECT_CREATE_APP_BEGIN'; 6 | export const NEW_PROJECT_CREATE_APP_SUCCESS = 'NEW_PROJECT_CREATE_APP_SUCCESS'; 7 | export const NEW_PROJECT_CREATE_APP_FAILURE = 'NEW_PROJECT_CREATE_APP_FAILURE'; 8 | export const NEW_PROJECT_CREATE_APP_DISMISS_ERROR = 'NEW_PROJECT_CREATE_APP_DISMISS_ERROR'; 9 | export const NEW_PROJECT_CLEAR_CREATE_APP_STATUS = 'NEW_PROJECT_CLEAR_CREATE_APP_STATUS'; 10 | -------------------------------------------------------------------------------- /src/features/new-project/redux/clearCreateAppStatus.js: -------------------------------------------------------------------------------- 1 | // Rekit uses a new approach to organizing actions and reducers. That is 2 | // putting related actions and reducers in one file. See more at: 3 | // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da 4 | 5 | import { 6 | NEW_PROJECT_CLEAR_CREATE_APP_STATUS, 7 | } from './constants'; 8 | 9 | export function clearCreateAppStatus() { 10 | return { 11 | type: NEW_PROJECT_CLEAR_CREATE_APP_STATUS, 12 | }; 13 | } 14 | 15 | export function reducer(state, action) { 16 | switch (action.type) { 17 | case NEW_PROJECT_CLEAR_CREATE_APP_STATUS: 18 | return { 19 | ...state, 20 | createAppStatus: [], 21 | createAppPending: false, 22 | createAppError: null, 23 | }; 24 | 25 | default: 26 | return state; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0 && argv.indexOf('--no-watch') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | jest.run(argv); 27 | -------------------------------------------------------------------------------- /src/features/new-project/redux/initialState.js: -------------------------------------------------------------------------------- 1 | // Initial state is the place you define all initial values for the Redux store of the feature. 2 | // In the 'standard' way, initialState is defined in reducers: http://redux.js.org/docs/basics/Reducers.html 3 | // But when application grows, there will be multiple reducers files, it's not intuitive what data is managed by the whole store. 4 | // So Rekit extracts the initial state definition into a separate module so that you can have 5 | // a quick view about what data is used for the feature, at any time. 6 | 7 | // NOTE: initialState constant is necessary so that Rekit could auto add initial state when creating async actions. 8 | const initialState = { 9 | fetchAppTypesPending: false, 10 | fetchAppTypesError: null, 11 | appTypes: null, 12 | createAppPending: false, 13 | createAppError: null, 14 | createAppStatus: [], 15 | }; 16 | 17 | export default initialState; 18 | -------------------------------------------------------------------------------- /tests/features/home/redux/showWelcomePage.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | HOME_SHOW_WELCOME_PAGE, 3 | } from '../../../../src/features/home/redux/constants'; 4 | 5 | import { 6 | showWelcomePage, 7 | reducer, 8 | } from '../../../../src/features/home/redux/showWelcomePage'; 9 | 10 | describe('home/redux/showWelcomePage', () => { 11 | it('returns correct action by showWelcomePage', () => { 12 | expect(showWelcomePage()).toHaveProperty('type', HOME_SHOW_WELCOME_PAGE); 13 | }); 14 | 15 | it('handles action type HOME_SHOW_WELCOME_PAGE correctly', () => { 16 | const prevState = {}; 17 | const state = reducer( 18 | prevState, 19 | { type: HOME_SHOW_WELCOME_PAGE } 20 | ); 21 | // Should be immutable 22 | expect(state).not.toBe(prevState); 23 | 24 | // TODO: use real case expected value instead of {}. 25 | const expectedState = {}; 26 | expect(state).toEqual(expectedState); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /src/features/common/WebView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class WebView extends Component { 5 | static propTypes = { 6 | src: PropTypes.string.isRequired, 7 | visible: PropTypes.bool.isRequired, 8 | onload: PropTypes.func, 9 | }; 10 | 11 | static defaultProps = { 12 | onload() {}, 13 | }; 14 | componentDidMount() { 15 | const wv = document.createElement('webview'); 16 | this.node.appendChild(wv); 17 | wv.src = this.props.src; 18 | // wv.onload = this.props.onload; 19 | wv.addEventListener('did-finish-load', this.props.onload); 20 | } 21 | 22 | assignRef = node => { 23 | this.node = node; 24 | }; 25 | 26 | render() { 27 | return ( 28 |
33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/common/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | import homeReducer from '../features/home/redux/reducer'; 4 | import commonReducer from '../features/common/redux/reducer'; 5 | import newProjectReducer from '../features/new-project/redux/reducer'; 6 | import pluginManagerReducer from '../features/plugin-manager/redux/reducer'; 7 | 8 | // NOTE 1: DO NOT CHANGE the 'reducerMap' name and the declaration pattern. 9 | // This is used for Rekit cmds to register new features, remove features, etc. 10 | // NOTE 2: always use the camel case of the feature folder name as the store branch name 11 | // So that it's easy for others to understand it and Rekit could manage them. 12 | 13 | const reducerMap = { 14 | router: routerReducer, 15 | home: homeReducer, 16 | common: commonReducer, 17 | newProject: newProjectReducer, 18 | pluginManager: pluginManagerReducer, 19 | }; 20 | 21 | export default combineReducers(reducerMap); 22 | -------------------------------------------------------------------------------- /src/features/common/redux/reducer.js: -------------------------------------------------------------------------------- 1 | // This is the root reducer of the feature. It is used for: 2 | // 1. Load reducers from each action in the feature and process them one by one. 3 | // Note that this part of code is mainly maintained by Rekit, you usually don't need to edit them. 4 | // 2. Write cross-topic reducers. If a reducer is not bound to some specific action. 5 | // Then it could be written here. 6 | // Learn more from the introduction of this approach: 7 | // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da. 8 | 9 | import initialState from './initialState'; 10 | 11 | const reducers = [ 12 | ]; 13 | 14 | export default function reducer(state = initialState, action) { 15 | let newState; 16 | switch (action.type) { 17 | // Handle cross-topic actions here 18 | default: 19 | newState = state; 20 | break; 21 | } 22 | /* istanbul ignore next */ 23 | return reducers.reduce((s, r) => r(s, action), newState); 24 | } 25 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React App 8 | 9 | 10 | 13 |
14 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /node/utils.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow } = require('electron'); 2 | const logger = require('./logger'); 3 | 4 | function toggleWindowMaximize() { 5 | const win = BrowserWindow.getFocusedWindow(); 6 | if (!win) return; 7 | if (win.isMaximized()) win.unmaximize(); 8 | else win.maximize(); 9 | } 10 | 11 | module.exports = { 12 | toggleWindowMaximize, 13 | notifyMainStateChange() { 14 | const win = BrowserWindow.getAllWindows()[0]; 15 | if (win) { 16 | win.webContents.send('state-changed'); 17 | } else { 18 | logger.warn('No window found when notifiyMainStateChange in utils.js, retry in 2 seconds...'); 19 | if (!this.pending) 20 | this.pending = setTimeout(() => { 21 | delete this.pending; 22 | this.notifyMainStateChange(); 23 | }, 2000); 24 | } 25 | }, 26 | reduxAction(action) { 27 | const win = BrowserWindow.getAllWindows()[0]; 28 | if (win) { 29 | win.webContents.send('redux-action', action); 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/features/plugin-manager/PluginHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Row, Col } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import { PluginIcon, InstallButton } from './'; 5 | 6 | export default class PluginHeader extends Component { 7 | static propTypes = { 8 | item: PropTypes.object.isRequired, 9 | }; 10 | 11 | render() { 12 | const { item } = this.props; 13 | return ( 14 |
15 | 16 | 17 | 18 |

{item.description}

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Root.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import configStore from '../src/common/configStore'; 4 | import Root from '../src/Root'; 5 | 6 | describe('Root', () => { 7 | it('Root has no error', () => { 8 | const DumpContainer = () =>
{this.props.children}
; 9 | const NotFoundComp = () =>
Not found
; 10 | const routes = [{ 11 | childRoutes: [ 12 | { path: '/', component: DumpContainer, childRoutes: [{ path: 'abc' }] }, 13 | { path: '/root', autoIndexRoute: true }, 14 | { path: 'relative-path', name: 'Link Name' }, 15 | { 16 | path: 'sub-links', 17 | childRoutes: [ 18 | { path: 'sub-link' }, 19 | ], 20 | }, 21 | { path: '*', component: NotFoundComp }, 22 | ], 23 | }]; 24 | const store = configStore(); 25 | 26 | shallow( 27 | 28 | ); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/features/new-project/redux/clearCreateAppStatus.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | NEW_PROJECT_CLEAR_CREATE_APP_STATUS, 3 | } from '../../../../src/features/new-project/redux/constants'; 4 | 5 | import { 6 | clearCreateAppStatus, 7 | reducer, 8 | } from '../../../../src/features/new-project/redux/clearCreateAppStatus'; 9 | 10 | describe('new-project/redux/clearCreateAppStatus', () => { 11 | it('returns correct action by clearCreateAppStatus', () => { 12 | expect(clearCreateAppStatus()).toHaveProperty('type', NEW_PROJECT_CLEAR_CREATE_APP_STATUS); 13 | }); 14 | 15 | it('handles action type NEW_PROJECT_CLEAR_CREATE_APP_STATUS correctly', () => { 16 | const prevState = {}; 17 | const state = reducer( 18 | prevState, 19 | { type: NEW_PROJECT_CLEAR_CREATE_APP_STATUS } 20 | ); 21 | // Should be immutable 22 | expect(state).not.toBe(prevState); 23 | 24 | // TODO: use real case expected value instead of {}. 25 | const expectedState = {}; 26 | expect(state).toEqual(expectedState); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/features/new-project/ProjectProperties.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import * as actions from './redux/actions'; 6 | 7 | export class ProjectProperties extends Component { 8 | static propTypes = { 9 | newProject: PropTypes.object.isRequired, 10 | actions: PropTypes.object.isRequired, 11 | }; 12 | 13 | render() { 14 | return ( 15 |
16 | Page Content: new-project/ProjectProperties2 17 |
18 | ); 19 | } 20 | } 21 | 22 | /* istanbul ignore next */ 23 | function mapStateToProps(state) { 24 | return { 25 | newProject: state.newProject, 26 | }; 27 | } 28 | 29 | /* istanbul ignore next */ 30 | function mapDispatchToProps(dispatch) { 31 | return { 32 | actions: bindActionCreators({ ...actions }, dispatch) 33 | }; 34 | } 35 | 36 | export default connect( 37 | mapStateToProps, 38 | mapDispatchToProps 39 | )(ProjectProperties); 40 | -------------------------------------------------------------------------------- /src/styles/mixins.less: -------------------------------------------------------------------------------- 1 | @primary-color: #1890ff; 2 | @header-height: 22px; 3 | @red2: rgb(242, 119, 119); 4 | @green2: rgb(93, 167, 0); 5 | .scroll-style() { 6 | &::-webkit-scrollbar { 7 | width: 0.5rem; 8 | height: 0.5rem; 9 | } 10 | 11 | &::-webkit-scrollbar-thumb { 12 | transition: all 0.3s ease; 13 | border-color: transparent; 14 | background-color: hsla(0, 0%, 100%, 0.1); 15 | z-index: 40; 16 | } 17 | 18 | &::-webkit-scrollbar-corner { 19 | background-color: rgba(0, 0, 0, 0); 20 | } 21 | } 22 | 23 | .abs-fullsize { 24 | position: absolute; 25 | left: 0; 26 | right: 0; 27 | top: 0; 28 | bottom: 0; 29 | } 30 | 31 | .vertical-center { 32 | position: relative; 33 | top: 50%; 34 | transform: translateY(-50%); 35 | } 36 | 37 | .full-page { 38 | .abs-fullsize; 39 | top: @header-height; 40 | } 41 | 42 | .spin { 43 | animation-name: spin; 44 | animation-duration: 2000ms; 45 | animation-iteration-count: infinite; 46 | animation-timing-function: linear; 47 | } 48 | 49 | @keyframes spin { 50 | from { 51 | transform: rotate(0deg); 52 | } 53 | to { 54 | transform: rotate(360deg); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { AppContainer } from 'react-hot-loader'; 3 | import { render } from 'react-dom'; 4 | // import configStore from './common/configStore'; 5 | import store from './common/store'; 6 | 7 | import routeConfig from './common/routeConfig'; 8 | import Root from './Root'; 9 | 10 | function renderApp(app) { 11 | render({app}, document.getElementById('root')); 12 | } 13 | 14 | renderApp(); 15 | 16 | window.bridge.ipcRenderer.on('redux-action', (evt, action) => store.getStore().dispatch(action)); 17 | 18 | // Hot Module Replacement API 19 | /* istanbul ignore if */ 20 | if (module.hot) { 21 | module.hot.accept('./common/routeConfig', () => { 22 | const nextRouteConfig = require('./common/routeConfig').default; // eslint-disable-line 23 | renderApp(); 24 | }); 25 | module.hot.accept('./Root', () => { 26 | const nextRoot = require('./Root').default; // eslint-disable-line 27 | renderApp(); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/features/home/NewProjectDialog.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .home-new-project-dialog { 4 | .ant-modal-body { 5 | height: 500px; 6 | text-align:justify; 7 | position: relative; 8 | } 9 | 10 | h2 { 11 | margin-bottom: 20px; 12 | } 13 | .icon-block-container { 14 | overflow: auto; 15 | } 16 | .icon-block { 17 | display: inline-block; 18 | margin-top: 10px; 19 | text-align: center; 20 | width: 107px; 21 | padding-top: 15px; 22 | height: 100px; 23 | cursor: pointer; 24 | &:hover { 25 | background-color: #eee; 26 | } 27 | .anticon { 28 | font-size: 48px; 29 | } 30 | label { 31 | display: block; 32 | line-height: 40px; 33 | cursor: pointer; 34 | } 35 | 36 | &.selected { 37 | background-color: @primary-color; 38 | color: #fff; 39 | } 40 | } 41 | .description { 42 | border-top: 1px solid #ddd; 43 | position: absolute; 44 | bottom: 0px; 45 | left: 0px; 46 | right: 0px; 47 | overflow: auto; 48 | padding: 10px 20px; 49 | height: 100px; 50 | background-color: #f5f5f5; 51 | line-height: 150%; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/features/common/FolderPicker.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Input, Icon } from 'antd'; 4 | 5 | export default class FolderPicker extends Component { 6 | static propTypes = { 7 | value: PropTypes.string, 8 | onChange: PropTypes.func, 9 | }; 10 | 11 | static defaultProps = { 12 | value: '', 13 | onChange() {}, 14 | }; 15 | 16 | handleBrowse = () => { 17 | window.bridge.remote.dialog.showOpenDialog( 18 | { 19 | title: 'Select a folder', 20 | filters: [], 21 | properties: ['openDirectory'], 22 | }, 23 | folders => { 24 | if (!folders) return; // canceled 25 | this.props.onChange(folders[0]); 26 | }, 27 | ); 28 | }; 29 | 30 | render() { 31 | const addonAfter = ( 32 | 33 | ); 34 | 35 | return ( 36 | this.props.onChange(evt.target.value)} 41 | /> 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/features/plugin-manager/redux/initialState.js: -------------------------------------------------------------------------------- 1 | // Initial state is the place you define all initial values for the Redux store of the feature. 2 | // In the 'standard' way, initialState is defined in reducers: http://redux.js.org/docs/basics/Reducers.html 3 | // But when application grows, there will be multiple reducers files, it's not intuitive what data is managed by the whole store. 4 | // So Rekit extracts the initial state definition into a separate module so that you can have 5 | // a quick view about what data is used for the feature, at any time. 6 | 7 | // NOTE: initialState constant is necessary so that Rekit could auto add initial state when creating async actions. 8 | const initialState = { 9 | fetchInstalledPluginsPending: false, 10 | fetchInstalledPluginsError: null, 11 | enablePluginPending: false, 12 | enablePluginError: null, 13 | disablePluginPending: false, 14 | disablePluginError: null, 15 | installPluginPending: false, 16 | installPluginError: null, 17 | uninstallPluginPending: false, 18 | uninstallPluginError: null, 19 | plugins: [], 20 | onlinePlugins: [], 21 | installing: {}, 22 | uninstalling: {}, 23 | fetchOnlinePluginsPending: false, 24 | fetchOnlinePluginsError: null, 25 | }; 26 | 27 | export default initialState; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .DS_Store 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | /build 35 | dist 36 | build 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | 66 | # electron env 67 | electron-builder.env -------------------------------------------------------------------------------- /scripts/startRekitStudio.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Start Rekit Studio 4 | const path = require('path'); 5 | const http = require('http'); 6 | const express = require('express'); 7 | const rekitStudioMiddleWare = require('rekit-studio/middleware'); 8 | const fallback = require('express-history-api-fallback'); 9 | 10 | function startRekitStudio(port) { 11 | console.log('Starting Rekit Studio...'); 12 | return new Promise((resolve, reject) => { 13 | const app = express(); 14 | const server = http.createServer(app); 15 | const root = path.join(__dirname, '../node_modules/rekit-studio/dist'); 16 | app.use(rekitStudioMiddleWare()(server, app)); 17 | app.use(express.static(root)); 18 | app.use(fallback('index.html', { root })); 19 | 20 | // Other files should not happen, respond 404 21 | app.get('*', (req, res) => { 22 | console.log('Warning: unknown req: ', req.path); 23 | res.sendStatus(404); 24 | }); 25 | 26 | server.listen(port, err => { 27 | if (err) { 28 | console.error(err); 29 | reject(err); 30 | return; 31 | } 32 | console.log(`Rekit Studio is running at http://localhost:${port}/`); 33 | resolve(); 34 | }); 35 | }); 36 | } 37 | 38 | module.exports = startRekitStudio; 39 | -------------------------------------------------------------------------------- /src/features/new-project/AppTypeSelect.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .new-project-app-type-select { 4 | h2 { 5 | margin-bottom: 20px; 6 | } 7 | .icon-block-container { 8 | overflow: auto; 9 | } 10 | .icon-block { 11 | display: inline-block; 12 | margin-bottom: 10px; 13 | text-align: center; 14 | width: 100px; 15 | margin-right: 7px; 16 | padding-top: 15px; 17 | height: 100px; 18 | cursor: pointer; 19 | &:hover { 20 | background-color: #eee; 21 | } 22 | img { 23 | display: inline-block; 24 | width: 48px; 25 | height: 48px; 26 | } 27 | label { 28 | display: block; 29 | line-height: 40px; 30 | white-space: nowrap; 31 | overflow: hidden; 32 | text-overflow: ellipsis; 33 | cursor: pointer; 34 | } 35 | 36 | &.selected { 37 | background-color: #e7e7e7; 38 | // color: #fff; 39 | // label { 40 | // background-color: @primary-color; 41 | // } 42 | } 43 | } 44 | .description { 45 | border-top: 1px solid #eee; 46 | position: absolute; 47 | bottom: 0px; 48 | left: 0px; 49 | right: 0px; 50 | overflow: auto; 51 | padding: 10px 20px; 52 | height: 100px; 53 | background-color: #f5f5f5; 54 | line-height: 150%; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/features/home/redux/reducer.js: -------------------------------------------------------------------------------- 1 | import initialState from './initialState'; 2 | import { reducer as openProjectReducer } from './openProject'; 3 | import { reducer as closeProjectReducer } from './closeProject'; 4 | import { reducer as getInitialStateReducer } from './getInitialState'; 5 | import { reducer as getMainStateReducer } from './getMainState'; 6 | import { reducer as showNewProjectDialogReducer } from './showNewProjectDialog'; 7 | import { reducer as hideNewProjectDialogReducer } from './hideNewProjectDialog'; 8 | import { reducer as showWelcomePageReducer } from './showWelcomePage'; 9 | 10 | const reducers = [ 11 | openProjectReducer, 12 | closeProjectReducer, 13 | getInitialStateReducer, 14 | getMainStateReducer, 15 | showNewProjectDialogReducer, 16 | hideNewProjectDialogReducer, 17 | showWelcomePageReducer, 18 | ]; 19 | 20 | export default function reducer(state = initialState, action) { 21 | let newState = state; 22 | switch (action.type) { 23 | // Handle cross-topic actions here 24 | case 'CREATE_APP_STATUS': 25 | break; 26 | case 'CREATE_APP_SUCCESS': 27 | break; 28 | case 'CREATE_APP_FAILURE': 29 | break; 30 | default: 31 | newState = state; 32 | break; 33 | } 34 | /* istanbul ignore next */ 35 | return reducers.reduce((s, r) => r(s, action), newState); 36 | } 37 | -------------------------------------------------------------------------------- /src/common/configStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { routerMiddleware } from 'react-router-redux'; 4 | import history from './history'; 5 | import rootReducer from './rootReducer'; 6 | 7 | const router = routerMiddleware(history); 8 | 9 | // NOTE: Do not change middleares delaration pattern since rekit plugins may register middlewares to it. 10 | const middlewares = [ 11 | thunk, 12 | router, 13 | ]; 14 | 15 | let devToolsExtension = f => f; 16 | 17 | /* istanbul ignore if */ 18 | if (process.env.NODE_ENV === 'development') { 19 | const { createLogger } = require('redux-logger'); 20 | 21 | const logger = createLogger({ collapsed: true }); 22 | middlewares.push(logger); 23 | 24 | if (window.devToolsExtension) { 25 | devToolsExtension = window.devToolsExtension(); 26 | } 27 | } 28 | 29 | export default function configureStore(initialState) { 30 | const store = createStore(rootReducer, initialState, compose( 31 | applyMiddleware(...middlewares), 32 | devToolsExtension 33 | )); 34 | 35 | /* istanbul ignore if */ 36 | if (module.hot) { 37 | // Enable Webpack hot module replacement for reducers 38 | module.hot.accept('./rootReducer', () => { 39 | const nextRootReducer = require('./rootReducer').default; // eslint-disable-line 40 | store.replaceReducer(nextRootReducer); 41 | }); 42 | } 43 | return store; 44 | } 45 | -------------------------------------------------------------------------------- /src/features/plugin-manager/InstallButton.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .plugin-manager-install-button { 4 | &.status-installing { 5 | display: inline-block; 6 | background-color: @green2; 7 | border-radius: 2px; 8 | font-size: 10px; 9 | padding: 0 3px; 10 | color: #fff; 11 | .anticon { 12 | font-size: 9px; 13 | margin-right: 5px; 14 | } 15 | cursor: default; 16 | } 17 | &.status-uninstalling { 18 | display: inline-block; 19 | background-color: #444; 20 | border-radius: 2px; 21 | font-size: 10px; 22 | padding: 0 3px; 23 | color: #bbb; 24 | .anticon { 25 | font-size: 9px; 26 | margin-right: 5px; 27 | } 28 | cursor: default; 29 | } 30 | .ant-btn { 31 | border: none; 32 | background-color: @green2; 33 | font-size: 10px; 34 | color: #fff; 35 | padding: 0 3px; 36 | line-height: 14px; 37 | height: 14px; 38 | border-radius: 2px; 39 | transition: none; 40 | &:hover { 41 | background-color: darken(@green2, 5%); 42 | color: #fff; 43 | } 44 | } 45 | 46 | .btn-installed { 47 | color: #bbb; 48 | background-color: #444; 49 | 50 | &:hover { 51 | background-color: #454545; 52 | color: #eee; 53 | } 54 | .anticon { 55 | font-size: 10px; 56 | } 57 | } 58 | 59 | .btn-to-update { 60 | background-color: #2175bc; 61 | color: #fff; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /node/checkUpdate.js: -------------------------------------------------------------------------------- 1 | const { dialog, app } = require('electron'); 2 | const { autoUpdater } = require('electron-updater'); 3 | const logger = require('./logger'); 4 | autoUpdater.logger = logger; 5 | 6 | module.exports = { 7 | checkUpdate() { 8 | autoUpdater.checkForUpdatesAndNotify(); 9 | }, 10 | handleCheckMenuClick(menuItem) { 11 | menuItem.enabled = false; 12 | // menuItem.label = 'Checking for Updates...'; 13 | autoUpdater.checkForUpdatesAndNotify().then( 14 | (args) => { 15 | const updateInfo = (args && args.updateInfo) || null; 16 | logger.info('update info:', updateInfo); 17 | menuItem.enabled = true; 18 | if (updateInfo && updateInfo.version !== app.getVersion()) { 19 | dialog.showMessageBox({ 20 | type: 'info', 21 | title: 'Found Updates', 22 | message: "Found updates, downloading behind and will notify you when it's ready.", 23 | }); 24 | } else { 25 | dialog.showMessageBox({ 26 | type: 'info', 27 | title: 'Up to date', 28 | message: 'There are currently no updates available.', 29 | }); 30 | } 31 | }, 32 | err => { 33 | logger.warn('Failed to check update.'); 34 | dialog.showMessageBox({ 35 | title: 'Error', 36 | message: 'Failed to check update.', 37 | }); 38 | menuItem.enabled = true; 39 | }, 40 | ); 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/features/home/RekitStudioPage.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .home-rekit-studio-page { 4 | .full-page; 5 | z-index: 9; 6 | .not-found { 7 | .abs-fullsize; 8 | color: #aaa; 9 | padding: 20px; 10 | } 11 | &.hidden { 12 | display: none; 13 | } 14 | .wv-container { 15 | width: 100%; 16 | height: 100%; 17 | .common-web-view { 18 | .abs-fullsize; 19 | } 20 | } 21 | 22 | .loading-status { 23 | .abs-fullsize; 24 | z-index: 10; 25 | text-align: center; 26 | background-color: #333; 27 | 28 | .center-block { 29 | .vertical-center(); 30 | } 31 | img { 32 | width: 60px; 33 | .spin; 34 | } 35 | p { 36 | color: #ccc; 37 | margin-top: 30px; 38 | } 39 | } 40 | 41 | .error-message { 42 | .abs-fullsize; 43 | z-index: 10; 44 | background-color: #333; 45 | text-align: left; 46 | 47 | .center-block { 48 | // .vertical-center(); 49 | padding: 30px; 50 | } 51 | h2 { 52 | color: red; 53 | } 54 | ul, li { 55 | margin: 0; 56 | padding: 0; 57 | list-style: none; 58 | } 59 | li { 60 | color: red; 61 | } 62 | .buttons { 63 | margin-top: 20px; 64 | .ant-btn { 65 | margin-right: 20px; 66 | } 67 | 68 | .btn-close { 69 | background-color: rgba(255, 255, 255, 0.1); 70 | border-color: rgba(255, 255, 255, 0.1); 71 | color: #ccc; 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/features/home/redux/constants.js: -------------------------------------------------------------------------------- 1 | export const HOME_OPEN_PROJECT_BEGIN = 'HOME_OPEN_PROJECT_BEGIN'; 2 | export const HOME_OPEN_PROJECT_SUCCESS = 'HOME_OPEN_PROJECT_SUCCESS'; 3 | export const HOME_OPEN_PROJECT_FAILURE = 'HOME_OPEN_PROJECT_FAILURE'; 4 | export const HOME_OPEN_PROJECT_DISMISS_ERROR = 'HOME_OPEN_PROJECT_DISMISS_ERROR'; 5 | export const HOME_CLOSE_PROJECT_BEGIN = 'HOME_CLOSE_PROJECT_BEGIN'; 6 | export const HOME_CLOSE_PROJECT_SUCCESS = 'HOME_CLOSE_PROJECT_SUCCESS'; 7 | export const HOME_CLOSE_PROJECT_FAILURE = 'HOME_CLOSE_PROJECT_FAILURE'; 8 | export const HOME_CLOSE_PROJECT_DISMISS_ERROR = 'HOME_CLOSE_PROJECT_DISMISS_ERROR'; 9 | export const HOME_GET_INITIAL_STATE_BEGIN = 'HOME_GET_INITIAL_STATE_BEGIN'; 10 | export const HOME_GET_INITIAL_STATE_SUCCESS = 'HOME_GET_INITIAL_STATE_SUCCESS'; 11 | export const HOME_GET_INITIAL_STATE_FAILURE = 'HOME_GET_INITIAL_STATE_FAILURE'; 12 | export const HOME_GET_INITIAL_STATE_DISMISS_ERROR = 'HOME_GET_INITIAL_STATE_DISMISS_ERROR'; 13 | export const HOME_GET_MAIN_STATE_BEGIN = 'HOME_GET_MAIN_STATE_BEGIN'; 14 | export const HOME_GET_MAIN_STATE_SUCCESS = 'HOME_GET_MAIN_STATE_SUCCESS'; 15 | export const HOME_GET_MAIN_STATE_FAILURE = 'HOME_GET_MAIN_STATE_FAILURE'; 16 | export const HOME_GET_MAIN_STATE_DISMISS_ERROR = 'HOME_GET_MAIN_STATE_DISMISS_ERROR'; 17 | export const HOME_SHOW_NEW_PROJECT_DIALOG = 'HOME_SHOW_NEW_PROJECT_DIALOG'; 18 | export const HOME_HIDE_NEW_PROJECT_DIALOG = 'HOME_HIDE_NEW_PROJECT_DIALOG'; 19 | export const HOME_SHOW_WELCOME_PAGE = 'HOME_SHOW_WELCOME_PAGE'; 20 | -------------------------------------------------------------------------------- /node/init.js: -------------------------------------------------------------------------------- 1 | // Initializing Rekit environment if not set up. 2 | // 1. Copying built in plugins to ~/.rekit/plugins folder. 3 | // NOTE: this may not needeed since rekit-react plugin is packaged in rekit-studio 4 | 5 | const os = require('os'); 6 | const fs = require('fs-extra'); 7 | const path = require('path'); 8 | const log = require('./log'); 9 | 10 | const systemPluginDir = path.join(os.homedir(), '.rekit/plugins'); 11 | fs.ensureDirSync(systemPluginDir); 12 | 13 | const builtInPlugins = []; 14 | 15 | // Use read and write to copy files to avoid permission issue, don't know why 16 | function copy(src, dest) { 17 | if (fs.statSync(src).isFile()) { 18 | fs.writeFileSync(dest, fs.readFileSync(src)); 19 | } else if (fs.statSync(src).isDirectory()) { 20 | fs.ensureDirSync(dest); 21 | fs.readdirSync(src).forEach(fileOrFolderName => { 22 | copy(path.join(src, fileOrFolderName), path.join(dest, fileOrFolderName)); 23 | }); 24 | } 25 | } 26 | 27 | builtInPlugins.forEach(name => { 28 | if (1 || !fs.existsSync(path.join(systemPluginDir, name))) { 29 | // Always replace built-in plugins when started for beta release. 30 | log.info('Initializing built in plugin: ', name); 31 | const src = path.join(__dirname, '../build/plugins', name); 32 | if (fs.existsSync(src)) { 33 | const dest = path.join(systemPluginDir, name); 34 | copy(src, dest); 35 | } else { 36 | log.error('Built in plugin not found: ', name, src); 37 | } 38 | } else { 39 | log.info('Initial plugin exists: ', name); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /src/features/plugin-manager/MainPage.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .plugin-manager-main-page { 4 | color: #ccc; 5 | height: 100%; 6 | h2 { 7 | font-size: 16px; 8 | color: #ccc; 9 | line-height: 40px; 10 | padding-left: 15px; 11 | .plugin-count { 12 | color: #777; 13 | font-size: 12px; 14 | } 15 | } 16 | .main-area { 17 | padding-top: 1px; 18 | margin: 0 auto; 19 | .vertical-center(); 20 | height: 600px; 21 | width: 900px; 22 | background-color: #222; 23 | .ant-row, .ant-col{ 24 | height: 100%; 25 | } 26 | .plugin-manager-sider { 27 | border-right: 1px solid #111; 28 | } 29 | } 30 | .plugin-manager-header { 31 | height: 40px; 32 | margin-top: -40px; 33 | text-align: right; 34 | .ant-btn { 35 | cursor: pointer; 36 | margin-right: -15px; 37 | background: #222; 38 | border:none; 39 | margin-top: 22px; 40 | color: #999; 41 | transition: none; 42 | &:hover { 43 | color:#ddd; 44 | } 45 | } 46 | } 47 | 48 | .plugin-intro { 49 | padding: 15px 20px 20px 20px; 50 | color: #bbb; 51 | h2 { 52 | border-bottom: 1px solid #333; 53 | padding-left: 0; 54 | } 55 | p { 56 | color: #aaa; 57 | } 58 | } 59 | .ant-row { 60 | // position: absolute; 61 | // left: 30px; 62 | // top: 30px; 63 | // right: 30px; 64 | // bottom: 30px; 65 | } 66 | 67 | .ant-col-12 { 68 | // height: 100%; 69 | // position: relative; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/features/plugin-manager/PluginHeader.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .plugin-manager-plugin-header { 4 | position: relative; 5 | padding: 5px 5px 5px 65px; 6 | border-bottom: 1px solid #333; 7 | padding-bottom: 10px; 8 | margin-bottom: 10px; 9 | .plugin-logo { 10 | position: absolute; 11 | left: 10px; 12 | top: 10px; 13 | width: 40px; 14 | max-height: 40px; 15 | } 16 | .name { 17 | overflow: hidden; 18 | text-overflow: ellipsis; 19 | display: inline-block; 20 | max-width: 200px; 21 | white-space: nowrap; 22 | font-size: 16px; 23 | vertical-align: bottom; 24 | } 25 | .version { 26 | display: inline-block; 27 | margin-left: 5px; 28 | font-size: 10px; 29 | color: #999; 30 | } 31 | p { 32 | color: #999; 33 | font-size: 12px; 34 | margin-bottom: 0; 35 | white-space: nowrap; 36 | overflow: hidden; 37 | text-overflow: ellipsis; 38 | } 39 | .plugin-manager-install-button { 40 | font-size: 12px; 41 | .ant-btn { 42 | font-size: 12px; 43 | padding: 2px 5px; 44 | height: auto; 45 | } 46 | } 47 | .ant-col { 48 | font-size: 12px; 49 | .author { 50 | overflow: hidden; 51 | max-width: 100%; 52 | text-overflow: ellipsis; 53 | } 54 | // .ant-btn { 55 | // border: none; 56 | // background-color: @green2; 57 | // font-size: 12px; 58 | // color: #fff; 59 | // padding: 0 3px; 60 | // line-height: 18px; 61 | // height: 18px; 62 | // border-radius: 2px; 63 | // } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/common/routeConfig.js: -------------------------------------------------------------------------------- 1 | import { App } from '../features/home'; 2 | import { PageNotFound } from '../features/common'; 3 | import homeRoute from '../features/home/route'; 4 | import commonRoute from '../features/common/route'; 5 | import _ from 'lodash'; 6 | import newProjectRoute from '../features/new-project/route'; 7 | import pluginManagerRoute from '../features/plugin-manager/route'; 8 | 9 | // NOTE: DO NOT CHANGE the 'childRoutes' name and the declaration pattern. 10 | // This is used for Rekit cmds to register routes config for new features, and remove config when remove features, etc. 11 | const childRoutes = [ 12 | homeRoute, 13 | commonRoute, 14 | newProjectRoute, 15 | pluginManagerRoute, 16 | ]; 17 | 18 | const routes = [{ 19 | path: '/', 20 | component: App, 21 | childRoutes: [ 22 | ...childRoutes, 23 | { path: '*', name: 'Page not found', component: PageNotFound }, 24 | ].filter(r => r.component || (r.childRoutes && r.childRoutes.length > 0)), 25 | }]; 26 | 27 | // Handle isIndex property of route config: 28 | // Dupicate it and put it as the first route rule. 29 | function handleIndexRoute(route) { 30 | if (!route.childRoutes || !route.childRoutes.length) { 31 | return; 32 | } 33 | 34 | const indexRoute = _.find(route.childRoutes, (child => child.isIndex)); 35 | if (indexRoute) { 36 | const first = { ...indexRoute }; 37 | first.path = ''; 38 | first.exact = true; 39 | first.autoIndexRoute = true; // mark it so that the simple nav won't show it. 40 | route.childRoutes.unshift(first); 41 | } 42 | route.childRoutes.forEach(handleIndexRoute); 43 | } 44 | 45 | routes.forEach(handleIndexRoute); 46 | export default routes; 47 | -------------------------------------------------------------------------------- /src/styles/global.less: -------------------------------------------------------------------------------- 1 | @import './mixins'; 2 | 3 | // Here you put all global css rules. 4 | 5 | /* roboto-100 - latin */ 6 | @font-face { 7 | font-family: 'Roboto'; 8 | font-style: normal; 9 | font-weight: 100; 10 | src: url('../fonts/roboto-v19-latin-100.eot'); /* IE9 Compat Modes */ 11 | src: local('Roboto Thin'), local('Roboto-Thin'), 12 | url('../fonts/roboto-v19-latin-100.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 13 | url('../fonts/roboto-v19-latin-100.woff2') format('woff2'), /* Super Modern Browsers */ 14 | url('../fonts/roboto-v19-latin-100.woff') format('woff'), /* Modern Browsers */ 15 | url('../fonts/roboto-v19-latin-100.ttf') format('truetype'), /* Safari, Android, iOS */ 16 | url('../fonts/roboto-v19-latin-100.svg#Roboto') format('svg'); /* Legacy iOS */ 17 | } 18 | /* roboto-300 - latin */ 19 | @font-face { 20 | font-family: 'Roboto'; 21 | font-style: normal; 22 | font-weight: 300; 23 | src: url('../fonts/roboto-v19-latin-300.eot'); /* IE9 Compat Modes */ 24 | src: local('Roboto Light'), local('Roboto-Light'), 25 | url('../fonts/roboto-v19-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 26 | url('../fonts/roboto-v19-latin-300.woff2') format('woff2'), /* Super Modern Browsers */ 27 | url('../fonts/roboto-v19-latin-300.woff') format('woff'), /* Modern Browsers */ 28 | url('../fonts/roboto-v19-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */ 29 | url('../fonts/roboto-v19-latin-300.svg#Roboto') format('svg'); /* Legacy iOS */ 30 | } 31 | 32 | body { 33 | margin: 0; 34 | padding: 0; 35 | font-family: sans-serif; 36 | background-color: #1e1e1e!important; 37 | } 38 | -------------------------------------------------------------------------------- /src/features/home/RecentProjects.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .home-recent-projects { 4 | background-color: #222; 5 | height: 400px; 6 | padding-left: 20px; 7 | overflow: hidden; 8 | .scroll-style(); 9 | 10 | h2 { 11 | line-height: 44px; 12 | border-bottom: 1px solid #444; 13 | margin-bottom: 0; 14 | margin-right: 20px; 15 | } 16 | ul, 17 | li { 18 | margin: 0; 19 | padding: 0; 20 | list-style: none; 21 | } 22 | ul { 23 | height: 360px; 24 | overflow: auto; 25 | padding-right: 20px; 26 | .scroll-style(); 27 | } 28 | 29 | .no-recent { 30 | color: #777; 31 | line-height: 36px; 32 | } 33 | 34 | .row-button { 35 | padding-left: 50px; 36 | margin: 0; 37 | color: #ccc; 38 | height: 60px; 39 | border-bottom: 1px solid #444; 40 | white-space: nowrap; 41 | .anticon { 42 | left: 7px; 43 | top: 15px; 44 | font-size: 32px; 45 | color: #BCAAA4; 46 | opacity: 0.7; 47 | } 48 | img.icon { 49 | width: 32px; 50 | height: 32px; 51 | position: absolute; 52 | left: 7px; 53 | top: 15px; 54 | opacity: 0.7; 55 | } 56 | h4 { 57 | padding-top: 13px; 58 | margin: 0; 59 | line-height: 16px; 60 | color: #ccc; 61 | text-overflow: ellipsis; 62 | overflow: hidden; 63 | font-weight: 300; 64 | } 65 | p { 66 | color: #777; 67 | text-overflow: ellipsis; 68 | overflow: hidden; 69 | } 70 | &:hover { 71 | h4, 72 | p { 73 | color: @primary-color; 74 | } 75 | img.icon { 76 | opacity: 0.9; 77 | } 78 | } 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/features/new-project/NewProjectForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Form } from 'antd'; 4 | import { FormBuilder, FolderPicker } from '../common'; 5 | 6 | export class NewProjectForm extends Component { 7 | static propTypes = { 8 | form: PropTypes.object.isRequired, 9 | onSubmit: PropTypes.func.isRequired, 10 | values: PropTypes.object.isRequired, 11 | appType: PropTypes.object.isRequired, 12 | }; 13 | 14 | getMeta() { 15 | const { appType } = this.props; 16 | const values = this.props.values || {}; 17 | const meta = { 18 | elements: [ 19 | 20 | { 21 | key: 'name', 22 | label: 'Project Name', 23 | required: true, 24 | }, 25 | { 26 | key: 'location', 27 | label: 'Location', 28 | required: true, 29 | widget: FolderPicker, 30 | }, 31 | ], 32 | }; 33 | if (appType.args) { 34 | meta.elements.push(...appType.args); 35 | } 36 | meta.elements.forEach(ele => { 37 | if (ele.key in values) ele.initialValue = values[ele.key]; 38 | }); 39 | 40 | return meta; 41 | } 42 | 43 | doSubmit = () => { 44 | this.props.form.validateFields((errors, values) => { 45 | if (!errors) { 46 | this.props.onSubmit(values); 47 | } 48 | }); 49 | }; 50 | 51 | render() { 52 | return ( 53 |
54 |
55 | 56 | 57 |
58 | ); 59 | } 60 | } 61 | 62 | export default Form.create()(NewProjectForm); 63 | -------------------------------------------------------------------------------- /src/features/home/WelcomePage.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .home-welcome-page { 4 | .abs-fullsize; 5 | top: @header-height; 6 | background-color: #1e1e1e; 7 | color: #ccc; 8 | font-family: Roboto, Arial, Helvetica, sans-serif; 9 | font-weight: 300; 10 | 11 | h1, 12 | h2, 13 | h3, 14 | h4 { 15 | color: #ccc; 16 | font-weight: 100; 17 | } 18 | 19 | .main-area { 20 | width: 800px; 21 | height: 400px; 22 | background-color: #191919; 23 | border-radius: 6px; 24 | margin: 0 auto; 25 | .vertical-center; 26 | .rekit-logo { 27 | width: 60px; 28 | } 29 | .footer { 30 | // background-color: red; 31 | // margin-bottom: 32 | line-height: 50px; 33 | text-align: left; 34 | color: #555; 35 | font-size: 12px; 36 | padding-left: 10px; 37 | label, 38 | a { 39 | color: #555; 40 | cursor: pointer; 41 | margin-right: 15px; 42 | &:last-child { 43 | margin-right: 0; 44 | } 45 | &:hover { 46 | color: #888; 47 | } 48 | } 49 | } 50 | } 51 | 52 | .welcome-area { 53 | text-align: center; 54 | padding-top: 50px; 55 | } 56 | p { 57 | margin-bottom: 50px; 58 | } 59 | 60 | .row-button { 61 | position: relative; 62 | padding-left: 20px; 63 | margin: 15px 0 0 110px; 64 | text-align: left; 65 | line-height: 20px; 66 | color: @primary-color; 67 | cursor: pointer; 68 | // background-color: rgba(255, 255, 255, 0.05); 69 | height: 20px; 70 | .anticon { 71 | position: absolute; 72 | font-size: 16px; 73 | color: @primary-color; 74 | left: 0px; 75 | top: 2px; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/features/plugin-manager/PluginList.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixins'; 2 | 3 | .plugin-manager-plugin-list { 4 | background-color: #262626; 5 | position: absolute; 6 | top: 40px; 7 | left: 0px; 8 | right: 0px; 9 | bottom: 0; 10 | 11 | &.plugins-loading { 12 | padding: 10px; 13 | color: #777; 14 | } 15 | ul { 16 | height: 100%; 17 | overflow: auto; 18 | .scroll-style(); 19 | } 20 | ul, 21 | li { 22 | list-style: none; 23 | padding: 0; 24 | margin: 0; 25 | } 26 | 27 | li { 28 | position: relative; 29 | padding: 5px 5px 5px 45px; 30 | border-bottom: 1px solid #333; 31 | &.selected { 32 | background-color: #111; 33 | } 34 | .plugin-logo { 35 | position: absolute; 36 | left: 10px; 37 | top: 10px; 38 | width: 26px; 39 | max-height: 40px; 40 | } 41 | .name { 42 | overflow: hidden; 43 | text-overflow: ellipsis; 44 | display: inline-block; 45 | max-width: 200px; 46 | white-space: nowrap; 47 | font-size: 12px; 48 | vertical-align: bottom; 49 | } 50 | .version { 51 | display: inline-block; 52 | margin-left: 5px; 53 | font-size: 10px; 54 | color: #999; 55 | } 56 | p { 57 | color: #999; 58 | font-size: 12px; 59 | margin-bottom: 0; 60 | white-space: nowrap; 61 | overflow: hidden; 62 | text-overflow: ellipsis; 63 | } 64 | 65 | .ant-col { 66 | font-size: 12px; 67 | .author { 68 | overflow: hidden; 69 | white-space: nowrap; 70 | color:#777; 71 | font-size: 10px; 72 | max-width: 100%; 73 | text-overflow: ellipsis; 74 | } 75 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/images/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/plugin-manager/PluginDetail.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import _ from 'lodash'; 4 | import { bindActionCreators } from 'redux'; 5 | import { connect } from 'react-redux'; 6 | import * as actions from './redux/actions'; 7 | import { PluginHeader } from './'; 8 | 9 | export class PluginDetail extends Component { 10 | static propTypes = { 11 | pluginManager: PropTypes.object.isRequired, 12 | actions: PropTypes.object.isRequired, 13 | name: PropTypes.string, 14 | }; 15 | renderNotFound() { 16 | return
Plugin not found: {this.props.name}.
; 17 | } 18 | renderNotSelected() { 19 | return
No plugin selected.
; 20 | } 21 | render() { 22 | const { name, plugins, onlinePlugins } = this.props; 23 | if (!name) return this.renderNotSelected(); 24 | const allPlugins = [...plugins, ...onlinePlugins]; 25 | const found = _.find(allPlugins, { name }); 26 | if (!found) return this.renderNotFound(); 27 | return ( 28 |
29 | 30 | {name} 31 |
32 | ); 33 | } 34 | } 35 | 36 | /* istanbul ignore next */ 37 | function mapStateToProps(state) { 38 | return { 39 | onlinePlugins: state.pluginManager.onlinePlugins, 40 | plugins: state.pluginManager.plugins, 41 | pluginManager: state.pluginManager, 42 | }; 43 | } 44 | 45 | /* istanbul ignore next */ 46 | function mapDispatchToProps(dispatch) { 47 | return { 48 | actions: bindActionCreators({ ...actions }, dispatch), 49 | }; 50 | } 51 | 52 | export default connect( 53 | mapStateToProps, 54 | mapDispatchToProps, 55 | )(PluginDetail); 56 | -------------------------------------------------------------------------------- /src/features/plugin-manager/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import * as actions from './redux/actions'; 6 | import { Row, Col, Button } from 'antd'; 7 | import { PluginList, PluginDetail } from './'; 8 | import history from '../../common/history'; 9 | 10 | export class MainPage extends Component { 11 | static propTypes = { 12 | pluginManager: PropTypes.object.isRequired, 13 | actions: PropTypes.object.isRequired, 14 | match: PropTypes.object.isRequired, 15 | }; 16 | 17 | render() { 18 | return ( 19 |
20 |
21 |
22 |
24 | 25 | 26 |

Plugins

27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 | ); 36 | } 37 | } 38 | 39 | /* istanbul ignore next */ 40 | function mapStateToProps(state) { 41 | return { 42 | pluginManager: state.pluginManager, 43 | }; 44 | } 45 | 46 | /* istanbul ignore next */ 47 | function mapDispatchToProps(dispatch) { 48 | return { 49 | actions: bindActionCreators({ ...actions }, dispatch), 50 | }; 51 | } 52 | 53 | export default connect( 54 | mapStateToProps, 55 | mapDispatchToProps, 56 | )(MainPage); 57 | -------------------------------------------------------------------------------- /src/features/plugin-manager/redux/reducer.js: -------------------------------------------------------------------------------- 1 | // This is the root reducer of the feature. It is used for: 2 | // 1. Load reducers from each action in the feature and process them one by one. 3 | // Note that this part of code is mainly maintained by Rekit, you usually don't need to edit them. 4 | // 2. Write cross-topic reducers. If a reducer is not bound to some specific action. 5 | // Then it could be written here. 6 | // Learn more from the introduction of this approach: 7 | // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da. 8 | 9 | import initialState from './initialState'; 10 | import { reducer as fetchInstalledPluginsReducer } from './fetchInstalledPlugins'; 11 | import { reducer as enablePluginReducer } from './enablePlugin'; 12 | import { reducer as disablePluginReducer } from './disablePlugin'; 13 | import { reducer as installPluginReducer } from './installPlugin'; 14 | import { reducer as uninstallPluginReducer } from './uninstallPlugin'; 15 | import { reducer as fetchOnlinePluginsReducer } from './fetchOnlinePlugins'; 16 | 17 | import { HOME_GET_MAIN_STATE_SUCCESS } from '../../home/redux/constants'; 18 | 19 | const reducers = [ 20 | fetchInstalledPluginsReducer, 21 | enablePluginReducer, 22 | disablePluginReducer, 23 | installPluginReducer, 24 | uninstallPluginReducer, 25 | fetchOnlinePluginsReducer, 26 | ]; 27 | 28 | export default function reducer(state = initialState, action) { 29 | let newState; 30 | switch (action.type) { 31 | // Handle cross-topic actions here 32 | case HOME_GET_MAIN_STATE_SUCCESS: 33 | newState = { 34 | ...state, 35 | installing: action.data.installing, 36 | uninstalling: action.data.uninstalling, 37 | }; 38 | break; 39 | default: 40 | newState = state; 41 | break; 42 | } 43 | return reducers.reduce((s, r) => r(s, action), newState); 44 | } 45 | -------------------------------------------------------------------------------- /src/features/home/RecentProjects.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import * as actions from './redux/actions'; 6 | import { Icon } from 'antd'; 7 | import utils from './utils'; 8 | 9 | export class RecentProjects extends Component { 10 | static propTypes = { 11 | recentProjects: PropTypes.array.isRequired, 12 | actions: PropTypes.object.isRequired, 13 | }; 14 | 15 | handleOpenProject = (dir) => { 16 | utils.openProject(dir); 17 | }; 18 | 19 | render() { 20 | const { recentProjects } = this.props; 21 | return ( 22 |
23 |

Recent Projects

24 |
    25 | {recentProjects.map(prj => ( 26 |
  • this.handleOpenProject(prj.path)} 31 | > 32 | {prj.logo ? logo : } 33 |

    {prj.path.split('/').pop()}

    34 |

    {prj.path}

    35 |
  • 36 | ))} 37 | {recentProjects.length === 0 &&
  • No recent projects.
  • } 38 |
39 |
40 | ); 41 | } 42 | } 43 | 44 | /* istanbul ignore next */ 45 | function mapStateToProps(state) { 46 | return { 47 | recentProjects: state.home.recentProjects, 48 | }; 49 | } 50 | 51 | /* istanbul ignore next */ 52 | function mapDispatchToProps(dispatch) { 53 | return { 54 | actions: bindActionCreators({ ...actions }, dispatch) 55 | }; 56 | } 57 | 58 | export default connect( 59 | mapStateToProps, 60 | mapDispatchToProps 61 | )(RecentProjects); 62 | -------------------------------------------------------------------------------- /src/features/home/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { bindActionCreators } from 'redux'; 4 | import { matchPath } from 'react-router-dom'; 5 | import { connect } from 'react-redux'; 6 | import { RekitStudioPage, TitleBar, DialogPlace } from './'; 7 | import { getMainState } from './redux/actions'; 8 | 9 | /* 10 | This is the root component of your app. Here you define the overall layout 11 | and the container of the react router. 12 | You should adjust it according to the requirement of your app. 13 | */ 14 | export class App extends Component { 15 | static propTypes = { 16 | children: PropTypes.node, 17 | actions: PropTypes.object.isRequired, 18 | initializing: PropTypes.bool.isRequired, 19 | router: PropTypes.object.isRequired, 20 | }; 21 | 22 | static defaultProps = { 23 | children: '', 24 | }; 25 | 26 | componentDidMount() { 27 | this.props.actions.getMainState(); 28 | window.bridge.ipcRenderer.on('state-changed', () => { 29 | this.props.actions.getMainState(); 30 | }); 31 | } 32 | 33 | render() { 34 | const match = matchPath(this.props.router.location.pathname, { 35 | path: '/rekit-studio/:port', 36 | exact: true, 37 | }); 38 | return ( 39 |
40 | 41 | 42 |
43 | {this.props.initializing ? 'Loading...' : this.props.children} 44 |
45 | 46 |
47 | ); 48 | } 49 | } 50 | 51 | /* istanbul ignore next */ 52 | function mapStateToProps(state) { 53 | return { 54 | initializing: state.home.initializing, 55 | router: state.router, 56 | }; 57 | } 58 | 59 | /* istanbul ignore next */ 60 | function mapDispatchToProps(dispatch) { 61 | return { 62 | actions: bindActionCreators({ getMainState }, dispatch), 63 | }; 64 | } 65 | 66 | export default connect( 67 | mapStateToProps, 68 | mapDispatchToProps, 69 | )(App); 70 | -------------------------------------------------------------------------------- /src/features/new-project/redux/reducer.js: -------------------------------------------------------------------------------- 1 | // This is the root reducer of the feature. It is used for: 2 | // 1. Load reducers from each action in the feature and process them one by one. 3 | // Note that this part of code is mainly maintained by Rekit, you usually don't need to edit them. 4 | // 2. Write cross-topic reducers. If a reducer is not bound to some specific action. 5 | // Then it could be written here. 6 | // Learn more from the introduction of this approach: 7 | // https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da. 8 | 9 | import initialState from './initialState'; 10 | import { reducer as fetchAppTypesReducer } from './fetchAppTypes'; 11 | import { reducer as createAppReducer } from './createApp'; 12 | import { reducer as clearCreateAppStatusReducer } from './clearCreateAppStatus'; 13 | import { HOME_GET_MAIN_STATE_SUCCESS } from '../../home/redux/constants'; 14 | const reducers = [ 15 | fetchAppTypesReducer, 16 | createAppReducer, 17 | clearCreateAppStatusReducer, 18 | ]; 19 | 20 | export default function reducer(state = initialState, action) { 21 | let newState = state; 22 | switch (action.type) { 23 | // Handle cross-topic actions here 24 | case 'CREATE_APP_STATUS': 25 | newState = { 26 | ...state, 27 | createAppStatus: [...state.createAppStatus, action.data], 28 | }; 29 | break; 30 | case 'CREATE_APP_SUCCESS': 31 | newState = { 32 | ...state, 33 | createAppStatus: [], 34 | createAppPending: false, 35 | }; 36 | break; 37 | case 'CREATE_APP_FAILURE': 38 | newState = { 39 | ...state, 40 | createAppStatus: [], 41 | createAppPending: false, 42 | createAppError: action.data, 43 | }; 44 | break; 45 | case HOME_GET_MAIN_STATE_SUCCESS: 46 | newState = { 47 | ...state, 48 | appTypes: action.data.appTypes, 49 | }; 50 | break; 51 | default: 52 | newState = state; 53 | break; 54 | } 55 | return reducers.reduce((s, r) => r(s, action), newState); 56 | } 57 | -------------------------------------------------------------------------------- /src/Root.js: -------------------------------------------------------------------------------- 1 | /* This is the Root component mainly initializes Redux and React Router. */ 2 | 3 | import React from 'react'; 4 | import PropTypes from 'prop-types'; 5 | import { Provider } from 'react-redux'; 6 | import { Switch, Route } from 'react-router-dom'; 7 | import { ConnectedRouter } from 'react-router-redux'; 8 | import history from './common/history'; 9 | 10 | function renderRouteConfigV3(routes, contextPath) { 11 | // Resolve route config object in React Router v3. 12 | const children = []; // children component list 13 | 14 | const renderRoute = (item, routeContextPath) => { 15 | let newContextPath; 16 | if (/^\//.test(item.path)) { 17 | newContextPath = item.path; 18 | } else { 19 | newContextPath = `${routeContextPath}/${item.path}`; 20 | } 21 | newContextPath = newContextPath.replace(/\/+/g, '/'); 22 | if (item.component && item.childRoutes) { 23 | const childRoutes = renderRouteConfigV3(item.childRoutes, newContextPath); 24 | children.push( 25 | {childRoutes}} 28 | path={newContextPath} 29 | /> 30 | ); 31 | } else if (item.component) { 32 | children.push(); 33 | } else if (item.childRoutes) { 34 | item.childRoutes.forEach(r => renderRoute(r, newContextPath)); 35 | } 36 | }; 37 | 38 | routes.forEach(item => renderRoute(item, contextPath)); 39 | 40 | // Use Switch so that only the first matched route is rendered. 41 | return {children}; 42 | } 43 | 44 | export default class Root extends React.Component { 45 | static propTypes = { 46 | store: PropTypes.object.isRequired, 47 | routeConfig: PropTypes.array.isRequired, 48 | }; 49 | render() { 50 | const children = renderRouteConfigV3(this.props.routeConfig, '/'); 51 | return ( 52 | 53 | {children} 54 | 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/features/home/NewProjectDialog.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import { Modal, Icon } from 'antd'; 6 | import { hideNewProjectDialog } from './redux/actions'; 7 | 8 | export class NewProjectDialog extends Component { 9 | static propTypes = { 10 | home: PropTypes.object.isRequired, 11 | actions: PropTypes.object.isRequired, 12 | }; 13 | 14 | state = { 15 | selected: null, 16 | }; 17 | 18 | handleSelect = key => { 19 | this.setState({ selected: key }); 20 | }; 21 | 22 | render() { 23 | return ( 24 | 33 |

Which type of the project to create?

34 |
38 | {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13].map(i => ( 39 |
this.handleSelect(i)} 43 | > 44 | 45 | 46 |
47 | ))} 48 |
49 | {this.state.selected && ( 50 |
51 | Rekit React template provides feature based SPA boilerplate with React Router, Redux 52 | outof box. 53 |
54 | )} 55 |
56 | ); 57 | } 58 | } 59 | 60 | /* istanbul ignore next */ 61 | function mapStateToProps(state) { 62 | return { 63 | home: state.home, 64 | }; 65 | } 66 | 67 | /* istanbul ignore next */ 68 | function mapDispatchToProps(dispatch) { 69 | return { 70 | actions: bindActionCreators({ hideNewProjectDialog }, dispatch), 71 | }; 72 | } 73 | 74 | export default connect( 75 | mapStateToProps, 76 | mapDispatchToProps, 77 | )(NewProjectDialog); 78 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => envPublicUrl || require(appPackageJson).homepage; 26 | 27 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 28 | // "public path" at which the app is served. 29 | // Webpack needs to know it to put the right