Repository: mglaman/conductor Branch: main Commit: 304a783084d1 Files: 48 Total size: 52.3 KB Directory structure: gitextract_qvivt0ij/ ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── appveyor.yml ├── build/ │ ├── background.tiff │ └── icon.icns ├── composer.phar ├── docs/ │ ├── index.md │ └── installation/ │ ├── linux.md │ ├── macos.md │ └── windows.md ├── main.js ├── mkdocs.yml ├── package.json ├── public/ │ ├── css/ │ │ └── conductor.css │ └── scss/ │ ├── _flex.scss │ ├── _vars.scss │ └── styles.scss ├── src/ │ ├── components/ │ │ ├── ComposerFrame.js │ │ ├── CreateProjectForm.js │ │ ├── MainLayout.js │ │ ├── MainNavigation.js │ │ ├── PackageDetails.js │ │ ├── ProjectDetails.js │ │ ├── ProjectLayout.js │ │ ├── ProjectNavigation.js │ │ ├── RecentProjects.js │ │ └── SettingsForm.js │ ├── models/ │ │ ├── Lock.js │ │ ├── Package.js │ │ └── Project.js │ ├── store.js │ ├── utils/ │ │ ├── AppMenu.js │ │ ├── AppUpdater.js │ │ ├── BrowserWindowFactory.js │ │ ├── Composer.js │ │ ├── ProjectList.js │ │ └── misc.js │ └── windows/ │ ├── index.html │ ├── mainWindow.js │ └── project/ │ ├── project.html │ └── projectWindow.js ├── test/ │ └── mainWindowTest.js └── update-composer.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [package.json] indent_style = space indent_size = 2 ================================================ FILE: .gitignore ================================================ node_modules npm-debug.log .idea dist ================================================ FILE: .travis.yml ================================================ osx_image: xcode7.3 sudo: required dist: trusty language: c matrix: include: - os: osx - os: linux env: CC=clang CXX=clang++ npm_config_clang=1 compiler: clang cache: directories: - node_modules - $HOME/.electron - $HOME/.cache addons: apt: packages: - libgnome-keyring-dev - icnsutils install: - nvm install 6 - npm install - npm prune before_script: - | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0 sh -e /etc/init.d/xvfb start sleep 3 fi script: - npm test - npm run dist branches: except: - "/^v\\d+\\.\\d+\\.\\d+$/" ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Conductor All forms of contribution are welcomed for Conductor: bug fixes, feature pull requests, bug reports, design concepts, documentation, or any kind of feedback. ## How you can contribute 1. Diagnose a bug by creating an issue 1. Suggest an improvement by creating an issue 1. Ask a question 1. Answer a question 1. Provide design/UI feedback and suggestions. 1. Fix a bug/resolve an issue 1. Support via Gratipay for maintenance and development: https://gratipay.com/Conductor/ ## Code Contributions Coding style should be `standard`: [![js-standard-style](https://raw.githubusercontent.com/feross/standard/master/badge.png)](https://github.com/feross/standard) @todo: More ================================================ FILE: Gruntfile.js ================================================ module.exports = function (grunt) { /** @var {Grunt} grunt */ 'use strict'; // Force use of Unix newlines grunt.util.linefeed = '\n'; RegExp.quote = function (string) { return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'); }; var fs = require('fs'); var path = require('path'); // Project configuration. grunt.initConfig({ // Metadata. pkg: grunt.file.readJSON('package.json'), banner: '/*!\n' + ' * ForkdIn v<%= pkg.version %> (<%= pkg.homepage %>)\n' + ' */\n', // Task configuration. clean: { dist: 'css' }, sass: { options: { includePaths: ['scss'], precision: 6, sourceComments: false, sourceMap: true, outputStyle: 'expanded' }, core: { files: { 'css/<%= pkg.name %>.css': 'scss/styles.scss' } } }, watch: { sass: { files: 'scss/**/*.scss', tasks: ['dist-css'] } } }); // These plugins provide necessary tasks. require('load-grunt-tasks')(grunt); grunt.registerTask('sass-compile', ['sass:core']); grunt.registerTask('dist-css', ['sass-compile']); // Full distribution task. grunt.registerTask('build', ['clean:dist', 'dist-css']); grunt.registerTask('default', ['build', 'watch']); }; ================================================ FILE: LICENSE ================================================ The MIT License Copyright (c) 2016 Matt Glaman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ![Conductor](build/icons/24x24.png) Conductor [![Build Status](https://travis-ci.org/mglaman/conductor.svg?branch=master)](https://travis-ci.org/mglaman/conductor) [![Build status](https://ci.appveyor.com/api/projects/status/anprg1634xsqm7g3?svg=true)](https://ci.appveyor.com/project/mglaman/conductor) [![Gratipay Team](https://img.shields.io/gratipay/team/conductor.svg)](https://gratipay.com/Conductor/) A user interface for Composer, the dependency management tool for PHP. ![Project page](docs/images/project-page.png) Conductor provides a user interface for creating and managing PHP applications using Composer. ## To Use ### Documentation Documentation can be found at https://mglaman.github.io/conductor/ ### Releases Compiled builds are available in the list of [releases](https://github.com/mglaman/conductor/releases/latest). ### Developers To use and preview, follow these instructions while still in early development. To clone and run this repository you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line: ```bash # Clone this repository git clone https://github.com/mglaman/conductor.git # Go into the repository cd conductor # Install dependencies and run the app npm install && npm start ``` ## Icon license ![Conductor](build/icons/128x128.png) Conductor: [Created with Baton by Luis Prado from the Noun Project](https://thenounproject.com/term/baton/248063). This icon is licensed as Creative Commons – Attribution (CC BY 3.0) #### License [MIT](LICENSE.md) ================================================ FILE: appveyor.yml ================================================ version: "{build}" skip_tags: true clone_folder: c:\projects\conductor clone_depth: 5 platform: - x64 cache: - node_modules - '%APPDATA%\npm-cache' - '%USERPROFILE%\.electron' init: - git config --global core.autocrlf input install: - ps: Install-Product node 6 x64 - git reset --hard HEAD - npm install npm -g - npm install - npm prune test_script: - node --version - npm --version - npm test build_script: - node --version - npm --version - npm run dist #test: off ================================================ FILE: docs/index.md ================================================ **Conductor**: A user interface for Composer, the dependency management tool for PHP. ![Project page](images/project-page.png) Conductor provides a user interface for creating and managing PHP applications using Composer. ================================================ FILE: docs/installation/linux.md ================================================ * Find the latest release: https://github.com/mglaman/conductor/releases * Use the .deb ================================================ FILE: docs/installation/macos.md ================================================ * Find the latest release: https://github.com/mglaman/conductor/releases * Download the available *.dmg file, or mac.zip ================================================ FILE: docs/installation/windows.md ================================================ * Find latest release: https://github.com/mglaman/conductor/releases * Downloand and run .exe ================================================ FILE: main.js ================================================ const fs = require('fs'); const electron = require('electron'); const Project = require('./src/models/Project'); const ProjectList = require('./src/utils/ProjectList'); const BrowserWindowFactory = require('./src/utils/BrowserWindowFactory'); const AppMenu = require('./src/utils/AppMenu'); const AppUpdater = require('./src/utils/AppUpdater'); const app = electron.app; const dialog = electron.dialog; let windowIcon = __dirname + '/build/icons/icon.svg'; let windowSize = 1024; let debug = false; let projectList = new ProjectList(); let mainWindow = null; let projectWindow = null; /** * @type {Project} */ let activeProject = null; let viewingPackage = null; /** * Main window for the app */ function createMainWindow() { mainWindow = BrowserWindowFactory.createWindow(`file://${__dirname}/src/windows/index.html`, windowSize, windowIcon); if (debug) mainWindow.webContents.openDevTools(); mainWindow.on('closed', () => { mainWindow = null }) } /** * Open a project in a new window * * @param folder */ function createProjectWindow(folder) { try { activeProject = new Project(folder); projectWindow = BrowserWindowFactory.createWindow(`file://${__dirname}/src/windows/project/project.html`, windowSize, windowIcon); if (debug) projectWindow.webContents.openDevTools(); projectWindow.on('show', () => { if (mainWindow !== null) { mainWindow.close(); } }); projectWindow.on('closed', () => { activeProject = null; projectWindow = null; if (mainWindow === null) { createMainWindow(); } }); } catch (e) { console.error(e); dialog.showMessageBox({ 'type': 'error', 'buttons': [], 'message': 'There is was an error parsing the composer.json' }); } } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', () => { refreshProjectList(); AppMenu.setProjects(projectList.getList()); AppMenu.setMenu(); createMainWindow(); new AppUpdater(); }); // Quit when all windows are closed. app.on('window-all-closed', function () { if (process.platform !== 'darwin') { app.quit() } }); app.on('activate', function () { if (mainWindow === null && projectWindow === null) { createMainWindow() } }); /** * Add a project to the ProjectsList from a chosen directory, then open that project * @param folder * @void */ const fromNewProject = function (folder) { const composerJson = require(folder + '/composer.json'); projectList.addProject(folder, composerJson.name); createProjectWindow(folder); }; /** * Open an existing project directory * @void */ const openDirectory = function () { let folder = dialog.showOpenDialog(mainWindow, { properties: ['openDirectory'], }); if (!folder) { return; } fs.exists(folder + '/composer.json', function (exists) { if (exists) { const composerJson = require(folder + '/composer.json'); projectList.addProject(folder, composerJson.name); createProjectWindow(folder[0]); } else { dialog.showMessageBox(mainWindow, { 'type': 'error', 'buttons': [], 'message': 'There is no composer.json in that directory' }); } }); }; /** * Open a new window for a project * @param path * @void */ const openProject = function (path) { createProjectWindow(path); }; /** * @returns {Project} */ const getActiveProject = () => { return activeProject; }; /** * @void */ const refreshProjectList = () => { try { projectList.refreshList(); } catch (e) { // This has issues when first run, or JSON missing. } }; /** * @returns {ProjectList} */ const getProjectList = () => { return projectList; }; /** * @returns {Package} */ const getViewingPackage = () => { return viewingPackage; }; module.exports = { openDirectory, openProject, getActiveProject, refreshProjectList, getProjectList, getViewingPackage, fromNewProject } ================================================ FILE: mkdocs.yml ================================================ site_name: Conductor Documentation repo_url: https://github.com/mglaman/conductor site_description: 'Conductor - A cross-platform Composer user interface' theme: readthedocs markdown_extensions: - toc: permalink: True pages: - Home: 'index.md' - Installation: - 'macOS': 'installation/macos.md' - 'Windows': 'installation/windows.md' - 'Linux': 'installation/linux.md' ================================================ FILE: package.json ================================================ { "name": "conductor", "version": "0.1.6", "description": "A Composer user interface", "main": "main.js", "scripts": { "start": "electron .", "pack": "build --dir", "dist": "build", "dist-all": "build -mwl", "test": "mocha" }, "repository": { "type": "git", "url": "git+https://github.com/mglaman/conductor.git" }, "keywords": [ "composer", "php" ], "author": "Matt Glaman ", "license": "MIT", "bugs": { "url": "https://github.com/mglaman/conductor/issues" }, "homepage": "https://github.com/mglaman/conductor", "build": { "appId": "com.mglaman.conductor", "asar": false, "productName": "Conductor", "copyright": "© Matt Glaman", "extraResources": [ "composer.phar" ], "mac": { "publish": [ "github" ], "target": [ "dmg", "zip" ], "category": "public.app-category.developer-tools" }, "win": { "publish": [ "github" ], "target": [ "zip", "nsis" ] }, "linux": { "publish": [ "github" ], "target": [ "deb", "rpm", "zip" ], "category": "Utility", "packageCategory": "Utility", "synopsis": "A Composer user interface" } }, "devDependencies": { "autoprefixer": "^6.0.3", "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "electron": "^9.4.0", "electron-builder": "^7.14.2", "grunt": "^1.0.1", "grunt-contrib-clean": "^1.0.0", "grunt-contrib-watch": "^1.0.0", "grunt-sass": "^1.0.0", "grunt-scss-lint": "^0.3.8", "load-grunt-tasks": "^3.4.0", "mocha": "^3.4.2", "spectron": "^3.7.2" }, "dependencies": { "electron-config": "^0.2.1", "electron-is-dev": "^0.1.2", "electron-updater": "^1.16.0", "font-awesome": "^4.6.3", "vue": "^2.4.2", "vue-router": "^2.7.0", "vuetify": "^0.14.5", "vuex": "^2.3.1" } } ================================================ FILE: public/css/conductor.css ================================================ /* Color definitions */ /* Semantic definitions */ .flex { display: flex; } .flex--row { flex-direction: row; } .flex--column { flex-direction: column; } .flex--grow { flex-grow: 1; } html, body { margin: 0; padding: 0; height: 100%; max-height: 100vh; display: flex; flex-direction: column; } html { font-size: 16px; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 1rem; line-height: 1.5; background: #fff; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { margin-top: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } p { margin-top: 0; } input { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 1rem; padding: 0.5rem; margin: 0.25rem; border-radius: 0.25rem; border: 1px solid #d5d5d5; } details { padding: 0 1rem; } details:focus { outline: 0; } details summary { background: #f5f5f5; border: 1px solid #e5e5e5; font-size: 1.25rem; padding: 0.5rem 0; margin: 0 -0.75rem; } details summary:focus { outline: 0; } button { cursor: pointer; border: 0; background: transparent; font-size: 1rem; padding: 0; } button:hover { color: #4078c0; } .main__actions { align-items: flex-start; } .main__actions button { margin-bottom: 0.5rem; } .main__projects-list { flex: 1; background: #f5f5f5; overflow: scroll; } .main__projects-list ::-webkit-details-marker { display: none; } .main__projects-list summary { border: 0; } .main__projects-list ul { margin: 0; padding: 0; list-style: none; } .main__projects-list ul li { display: block; padding: 0.25rem 0.5rem 0.25rem 0; border-bottom: solid 1px #e5e5e5; } .main__projects-list button { border: 0; background: transparent; font-size: 1rem; padding: 0; } .project__title { padding: 0.5rem 0; } .project__title h1 { flex: 1; } .project__sidebar { flex: 0 0 35%; max-width: 35%; overflow-y: scroll; background: white; border-right: 1px solid #f5f5f5; } .project__sidebar ul { margin: 0; padding: 0; list-style: none; } .project__sidebar ul li { display: block; padding: 0.25rem 0.5rem; border-bottom: solid 1px #e5e5e5; font-size: 90%; } .project__sidebar ul li:last-child { border-bottom: 0; } .project__sidebar ul li:hover { background: #4078c0; color: #fff; } .button__actions button { display: block; padding: 0.5rem; background-image: linear-gradient(#f5f5f5, #d5d5d5); border: 1px solid #e5e5e5; border-radius: 3px; flex: 1; } .button__actions button:focus { outline: 0; border: 1px solid black; } .button__actions button:hover { background: #e3e3e3; } .project__information { padding: 0 1rem; } .project__output { flex: 1; border: 0; display: flex; flex-direction: column; overflow: scroll; } .project__output p { padding: 1px; } ::-webkit-scrollbar { display: none; } .hidden { display: none; } .project__output { font-family: monospace; font-size: 80%; background: #111; color: #f5f5f5; } .project__dependencies-list li { cursor: pointer; } .window__create-project .button__actions { margin-top: 1rem; } .window__create-project button { display: block; padding: 0.35rem; background-image: linear-gradient(#f5f5f5, #d5d5d5); border: 1px solid #e5e5e5; border-radius: 3px; } .window__create-project button:focus { outline: 0; border: 1px solid #aaa; } .window__create-project button:hover { background: #e3e3e3; } .window__create-project #project-destination { cursor: pointer; } .main__landing { background: linear-gradient(135deg, #3700aa 0%, #4500d4 22%, #6c00e9 57%, #992aff 100%); color: #fff; } .main__landing button { color: #fff; font-weight: normal; } .window__main--header { margin-top: 2rem; margin-bottom: 1rem; align-items: center; } .window__main--header h1 { margin-bottom: 0; font-weight: normal; } .hidden { display: none !important; } .log--error { background: #A94443; color: white; } .log--output { color: #FFFF91; } /*# sourceMappingURL=conductor.css.map */ ================================================ FILE: public/scss/_flex.scss ================================================ .flex { display: flex; } .flex--row { flex-direction: row; } .flex--column { flex-direction: column; } .flex--grow { flex-grow: 1; } ================================================ FILE: public/scss/_vars.scss ================================================ /* Color definitions */ $light-gray: #f5f5f5; $middle-gray: #d5d5d5; $dark-gray: #e5e5e5; $blue: #4078c0; $white: #fff; $black: #111; /* Semantic definitions */ $bg-color: $white; $details-summary-bg: $light-gray; $fontstack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; ================================================ FILE: public/scss/styles.scss ================================================ @import "vars"; @import "flex"; html, body { margin: 0; padding: 0; height: 100%; max-height: 100vh; display: flex; flex-direction: column; } html { font-size: 16px; } body { font-family: $fontstack; font-size: 1rem; line-height: 1.5; background: $bg-color; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { margin-top: 0; font-family: $fontstack; } p { margin-top: 0; } input { font-family: $fontstack; font-size: 1rem; padding: 0.5rem; margin: 0.25rem; border-radius: 0.25rem; border: 1px solid $middle-gray; } details { padding: 0 1rem; &:focus { outline: 0; } summary { background: $details-summary-bg; border: 1px solid $dark-gray; font-size: 1.25rem; padding: 0.5rem 0; margin: 0 -0.75rem; &:focus { outline: 0; } } } button { cursor: pointer; border: 0; background: transparent; font-size: 1rem; padding: 0; &:hover { color: $blue; } } .main__actions { align-items: flex-start; button { margin-bottom: 0.5rem; } } .main__projects-list { flex: 1; background: $light-gray; overflow: scroll; ::-webkit-details-marker { display:none; } summary { border:0; } ul { margin: 0; padding: 0; list-style: none; li { display: block; padding: 0.25rem 0.5rem 0.25rem 0; border-bottom: solid 1px $dark-gray; } } button { border: 0; background: transparent; font-size: 1rem; padding: 0; } } .project__title { padding: 0.5rem 0; h1 { flex: 1; } } .project__sidebar { flex: 0 0 35%; max-width: 35%; overflow-y: scroll; background: lighten($light-gray, 50%); border-right: 1px solid $light-gray; ul { margin: 0; padding: 0; list-style: none; li { display: block; padding: 0.25rem 0.5rem; border-bottom: solid 1px $dark-gray; font-size: 90%; &:last-child { border-bottom: 0; } &:hover { background: $blue; color: #fff; } } } } .button__actions { button { display: block; padding: 0.5rem; background-image: linear-gradient($light-gray, $middle-gray); border: 1px solid $dark-gray; border-radius: 3px; &:focus { outline: 0; border: 1px solid darken($dark-gray, 100); } flex: 1; &:hover { background: darken($dark-gray, .75); } } } .project__information { padding: 0 1rem; } .project__output { flex: 1; border: 0; display: flex; flex-direction: column; overflow: scroll; p { padding: 1px; } } ::-webkit-scrollbar { display: none; } .hidden { display: none; } .project__output { font-family: monospace; font-size: 80%; background: $black; color: $light-gray; } .project__dependencies-list li { cursor: pointer; } .window__create-project { .button__actions { margin-top: 1rem; } button { display: block; padding: 0.35rem; background-image: linear-gradient($light-gray, $middle-gray); border: 1px solid $dark-gray; border-radius: 3px; &:focus { outline: 0; border: 1px solid #aaa; } &:hover { background: darken($dark-gray, .75); } } #project-destination { cursor: pointer; } } .main__landing { background: linear-gradient(135deg, #3700aa 0%,#4500d4 22%,#6c00e9 57%,#992aff 100%); color: $white; button { color: $white; font-weight: normal; } } .window__main--header { margin-top: 2rem; margin-bottom: 1rem; align-items: center; h1 { margin-bottom: 0; font-weight: normal; } } .hidden { display: none !important; } .log--error { background: #A94443; color: white; } .log--output { color: #FFFF91; } ================================================ FILE: src/components/ComposerFrame.js ================================================ 'use strict'; const fs = require('fs'); const remote = require('electron').remote; const dialog = remote.dialog; const mainProcess = remote.require('./main'); const thisWindow = remote.getCurrentWindow(); const Composer = require('../utils/Composer'); module.exports = { template: ` Create project! Install Validate Update Show Update Remove

{{ line.text }}

`, data: function() { return { doing: '', composerOutLines: [], } }, props: { type: { default: '', // project | package | create type: String }, project: { default: null, type: Object, }, package: { default: null, type: Object, }, newProjectDetails: { default: null, } }, methods: { composerExecute(command) { this.doing = command; let process = null; let composer = null; let activeProject = null; let projectInstalled = null; switch( this.type ) { /* * Project actions */ case 'project': activeProject = mainProcess.getActiveProject(); projectInstalled = activeProject.isInstalled(); composer = new Composer(activeProject.getPath()); switch( command ) { case 'install': process = composer.install(); break; case 'updateProject': process = composer.update(null); break; case 'validate': process = composer.validate(); break; } break; /* * Package actions */ case 'package': composer = new Composer(mainProcess.getActiveProject().getPath()); switch( command ){ // package case 'updatePackage': process = composer.update(this.package.__get('name')); break; case 'remove': process = composer.remove(this.package.getName()); break; case 'show': process = composer.show(this.package.getName()); break; } break; /* * Create project actions */ case 'create': composer = new Composer(remote.app.getAppPath()); switch( command ){ // create project case 'create': process = composer.createProject(this.newProjectDetails.packageName, this.newProjectDetails.destination); break; } break; } if (composer && process){ process.stdout.on('data', (data) => { this.composerOutLines.push({ text: String(data), className: 'log--output' }); }); process.stderr.on('data', (data) => { this.composerOutLines.push({ text: String(data), className: 'log--output' }); }); process.on('error', (data) => { this.doing = ''; this.composerOutLines.push({ text: String(data), className: 'log--error' }); }); process.on('close', (code) => { this.doing = ''; // From project if ( this.project && this.type === 'project' ){ if (code === 0) { activeProject.refreshData(); projectInstalled = activeProject.isInstalled(); // @todo get some kind of binding to not need to do this. // thisWindow.reload(); } } // from Create project if ( this.type === 'create' ){ if (code === 0 && fs.existsSync(this.newProjectDetails.destination + '/composer.json')) { mainProcess.fromNewProject(this.newProjectDetails.destination); } } }); } } } } ================================================ FILE: src/components/CreateProjectForm.js ================================================ 'use strict'; const fs = require('fs'); const remote = require('electron').remote; const dialog = remote.dialog; const mainProcess = remote.require('./main'); const thisWindow = remote.getCurrentWindow(); const Composer = require('../utils/Composer'); const ComposerFrame = require('./ComposerFrame'); module.exports = { template: ` Browse `, components: { 'composer-frame': ComposerFrame }, data: function(){ return { packageName: '', projectName: '', description: '', destinationFolder: '', } }, computed: { destination(){ return this.destinationFolder + '/' + this.projectName; } }, methods: { projectDestinationBrowse (){ let folder = dialog.showOpenDialog(thisWindow, { properties: ['openDirectory'], }); if (!folder) { this.destinationFolder = ''; } this.destinationFolder = folder; }, } } ================================================ FILE: src/components/MainLayout.js ================================================ 'use strict'; const MainNavigation = require('./MainNavigation'); module.exports = { template: ` Conductor {{ pageTitle }}
`, components: { 'main-navigation': MainNavigation }, data() { return { // settings for the vuetify layout drawer: null, right: null, } }, computed: { pageTitle() { return this.$store.getters.pageTitle; } } } ================================================ FILE: src/components/MainNavigation.js ================================================ 'use strict'; const fs = require('fs'); const remote = require('electron').remote; const mainProcess = remote.require('./main'); module.exports = { template: ` list Recent projects folder_open Add existing project create_new_folder Create new project public Global composer settings Settings `, computed: { globalComposerFileExists() { return fs.existsSync(remote.app.getPath('home') + '/.composer'); } }, methods: { existingProject: () => { mainProcess.openDirectory(); }, globalProject: () => { mainProcess.openProject(remote.app.getPath('home') + '/.composer'); }, } } ================================================ FILE: src/components/PackageDetails.js ================================================ 'use strict'; const ComposerFrame = require('./ComposerFrame'); module.exports = { template: `
  • Version {{ package.json.version }}
  • Homepage {{ package.json.homepage }}
  • {{ package.json.description }}
  • Requirements
    • {{ dependency }}
  • Dev Requirements
    • {{ dependency }}
`, components: { 'composer-frame': ComposerFrame }, computed: { package() { return this.$store.getters.activeProject.getLock().getPackage(this.$route.params.packagePath); } }, created() { this.$store.commit('setPageTitle', this.package.json.name); }, watch: { '$route' () { this.$store.commit('setPageTitle', this.package.json.name); } } } ================================================ FILE: src/components/ProjectDetails.js ================================================ 'use strict'; const ComposerFrame = require('./ComposerFrame'); module.exports = { template: `

{{ project.json.homepage }}

{{ project.json.description }}

`, components: { 'composer-frame': ComposerFrame }, computed: { project() { return this.$store.getters.activeProject; }, }, } ================================================ FILE: src/components/ProjectLayout.js ================================================ 'use strict'; const ProjectNavigation = require('../components/ProjectNavigation'); const mainProcess = require('electron').remote.require('./main'); module.exports = { template: ` {{ project.json.name }} {{ pageTitle }}
`, created() { this.$store.commit('setActiveProject', mainProcess.getActiveProject()); }, mounted() { this.$store.commit('setPageTitle', this.project.json.name); }, data() { return { // settings for the vuetify layout drawer: null, right: null, } }, computed: { pageTitle() { return this.$store.getters.pageTitle; }, project() { return this.$store.getters.activeProject; } }, components: { 'project-navigation': ProjectNavigation } } ================================================ FILE: src/components/ProjectNavigation.js ================================================ 'use strict'; const mainProcess = require('electron').remote.require('./main') module.exports = { template: ` queue_music Composer keyboard_arrow_down composer.json composer.lock `, computed: { project() { return this.$store.getters.activeProject; }, dependencies() { return { required: { title: 'Requirements', icon: 'beenhere', items: this.project.getRequire(), active: true, }, dev: { title: 'Development Requirements', icon: 'build', items: this.project.getRequireDev(), active: false, } }; } }, methods: { openPackage: (packageName) => { mainProcess.openPackage(packageName); } } } ================================================ FILE: src/components/RecentProjects.js ================================================ 'use strict'; const mainProcess = require('electron').remote.require('./main'); module.exports = { template: ` folder {{ project.name }} {{ project.path }} delete_forever `, computed: { projects() { return this.$store.getters.projectsList; } }, mounted() { this.$store.commit('setPageTitle', 'Recent Projects'); }, methods: { removeProject(path) { this.$store.dispatch('removeProjectAction', path); }, openProject: (path) => { mainProcess.openProject(path); }, } } ================================================ FILE: src/components/SettingsForm.js ================================================ 'use strict'; module.exports = { template: ` TODO `, mounted() { this.$store.commit('setPageTitle', 'Settings'); }, methods: { } } ================================================ FILE: src/models/Lock.js ================================================ 'use strict'; const Package = require('./Package'); const Lock = function (json) { const self = this; this.json = json; this.packages = []; this.packagesDev = []; Array.from(json.packages).forEach(function (item) { self.packages[item.name] = new Package(item); }); Array.from(json['packages-dev']).forEach(function (item) { self.packagesDev[item.name] = new Package(item); }); /** * * @param name * @returns {Package} */ this.getPackage = (name) => { if (this.packages.hasOwnProperty(name)) { return this.packages[name]; } else if (this.packagesDev.hasOwnProperty(name)){ return this.packagesDev[name]; } }; }; Lock.prototype.json = {}; Lock.prototype.packages = {}; module.exports = Lock; ================================================ FILE: src/models/Package.js ================================================ 'use strict'; var Package = function (json) { this.json = json; this.__get = (key) => { return this.json[key]}; this.getVersion = () => { return this.json.version; }; this.getName = () => { return this.json.name }; }; Package.prototype.json = {}; module.exports = Package; ================================================ FILE: src/models/Project.js ================================================ 'use strict'; let Lock = require('./Lock'); /** * * @param {String} path * @constructor */ let Project = function (path) { this.path = path; this.getPath = () => { return this.path }; this.getName = () => { return this.json['name'] || '' }; this.getDescription = () => { return this.json['description'] || '' }; /** * @returns {Object} */ this.getRequire = () => { return this.json['require'] }; /** * @returns {Object} */ this.getRequireDev = () => { return this.json['require-dev'] }; /** * @returns {*|string} */ this.getHomepage = () => { return this.json['homepage'] || '' }; /** * @returns {null|Lock} */ this.getLock = () => { return this.lock }; this.refreshLock = () => { this.lock = null; if (this.filesystem.existsSync(path + '/composer.lock')) { try { this.filesystem.readFile(path + '/composer.lock', (err, data) => { if (!err) { this.lock = new Lock(JSON.parse(data)); } }); } catch (e) { console.log(e); } } }; this.isInstalled = () => { return this.lock !== null; }; this.refreshData = () => { this.json = require(this.path + '/composer.json'); this.refreshLock(); }; this.refreshData(); }; Project.prototype.path = ''; Project.prototype.json = {}; Project.prototype.filesystem = require('fs'); module.exports = Project; ================================================ FILE: src/store.js ================================================ 'use strict'; const Vuex = require('vuex'); const projectList = require('electron').remote.require('./main').getProjectList(); let list = projectList.getList(); let projects = []; Object.keys(list).map((path) => { projects.push({ path, name: list[path] }); }); const store = new Vuex.Store({ state: { pageTitle: '', projects: projects, activeProject: {}, }, getters: { /** * Get the current page title */ pageTitle(state) { return state.pageTitle; }, /** * Get the formatted projects array */ projectsList(state) { return state.projects; }, /** * Get the active project being viewed */ activeProject(state) { return state.activeProject; }, }, // no async mutations: { setPageTitle(state, title) { state.pageTitle = title; }, setActiveProject(state, project) { state.activeProject = project; }, removeProject(state, path) { // remove project from the stored data file projectList.removeProject(path); // remove project from the global state let index = state.projects.findIndex((project) => project.path === path ); state.projects.splice(index, 1); } }, // handles asynchronous actions: { removeProjectAction(context, path) { context.commit('removeProject', path); } } }); module.exports = { store } ================================================ FILE: src/utils/AppMenu.js ================================================ 'use strict'; const electron = require('electron'); const app = electron.app; const menu = electron.Menu; const shell = electron.shell; const main = require('../../main'); let template = [ { label: 'Projects', submenu: [], }, { label: 'Help', role: 'help', submenu: [ { label: 'Documentation', click() { shell.openExternal('https://github.com/mglaman/conductor/wiki') } }, { label: 'Report an issue', click() { shell.openExternal('https://github.com/mglaman/conductor/issues') } } ], } ]; if (process.platform === 'darwin') { const name = 'Conductor'; template.unshift({ label: name, submenu: [ { label: 'About ' + name, role: 'about' }, { type: 'separator' }, { label: 'Services', role: 'services', submenu: [] }, { type: 'separator' }, { label: 'Hide ' + name, accelerator: 'Command+H', role: 'hide' }, { label: 'Hide Others', accelerator: 'Command+Alt+H', role: 'hideothers' }, { label: 'Show All', role: 'unhide' }, { type: 'separator' }, { label: 'Quit', accelerator: 'Command+Q', click() { app.quit(); } }, ] }); } module.exports = { setProjects: (projects) => { let key = (process.platform === 'darwin') ? 1 : 0; for (let path in projects) { if (!projects.hasOwnProperty(path)) { continue; } template[key].submenu.push({ label: projects[path], click() { main.openProject(path) } }); } }, setMenu: () => { let appMenu = menu.buildFromTemplate(template); menu.setApplicationMenu(appMenu); } }; ================================================ FILE: src/utils/AppUpdater.js ================================================ const BrowserWindowElectron = require('electron').BrowserWindow; const autoUpdater = require('electron-updater').autoUpdater; const isDev = require('electron-is-dev'); const os = require('os'); class AppUpdater { constructor() { if (isDev === true) { return; } const platform = os.platform(); if (platform === "linux") { return } autoUpdater.signals.updateDownloaded(it => { notify("A new update is ready to install", `Version ${it.version} is downloaded and will be automatically installed on Quit`) }); autoUpdater.checkForUpdates() } } function notify(title, message) { let windows = BrowserWindowElectron.getAllWindows(); if (windows.length === 0) { return } windows[0].webContents.send("notify", title, message) } module.exports = AppUpdater; ================================================ FILE: src/utils/BrowserWindowFactory.js ================================================ 'use strict'; let BrowserWindow = require('electron').BrowserWindow; function getDefaultWindowHeight(w) { return Math.round((w / 4) * 3); } module.exports = { createWindow: (url, width, icon) => { let browserWindow = new BrowserWindow({ width: width, height: getDefaultWindowHeight(width), icon: icon, show: false, }); browserWindow.loadURL(url); browserWindow.on('ready-to-show', function() { browserWindow.show(); browserWindow.focus(); }); return browserWindow; } } ; ================================================ FILE: src/utils/Composer.js ================================================ 'use strict'; /** * * @param {String} projectPath * @constructor */ let Composer = function(projectPath) { const bin = this.binPath + '/composer.phar'; this.install = (opts) => { return this._runCommand(['install', '--no-progress'], opts); }; this.update = (dependency, opts) => { let command = ['update', '--no-progress']; if (typeof dependency === 'string') { command.push(dependency); } return this._runCommand(command, opts); }; this.validate = (opts) => { return this._runCommand(['validate'], opts); }; /** * * @param dependency * @param opts * @returns {ChildProcess} */ this.show = (dependency, opts) => { return this._runCommand(['show', dependency], opts); }; this.remove = (dependency, opts) => { return this._runCommand(['remove', dependency], opts); }; this.createProject = (project, destination, opts) => { return this._runCommand(['create-project', project, destination,'--stability', 'dev', '--no-interaction'], opts) }; this._normalizeOpts = (opts) => { if (typeof opts === 'undefined' || opts.length === 0) { opts = {}; } opts['cwd'] = projectPath; return opts; }; /** * * @param command * @param opts * @returns {ChildProcess} * @private */ this._runCommand = (command, opts) => { opts = this._normalizeOpts(opts); return this.spawn(bin, command, opts); } }; Composer.prototype.spawn = require('child_process').spawn; Composer.prototype.binPath = (process.mainModule.filename.indexOf('app.asar') !== -1) ? process.resourcesPath : process.cwd(); module.exports = Composer; ================================================ FILE: src/utils/ProjectList.js ================================================ 'use strict'; const Config = require('electron-config'); /** * @constructor */ let ProjectList = function() { /** @type {ElectronConfig} */ const config = new Config(); this.setList = (list) => { this.list = list; }; this.getList = () => { return this.list; }; this.refreshList = () => { this.setList({}); if (config.has('projects')) { this.setList(config.get('projects')); } }; this.addProject = (dir, name) => { this.list[dir] = name; config.set('projects', this.list); }; this.removeProject = (dir) => { delete this.list[dir]; config.set('projects', this.list); } this.refreshList(); }; ProjectList.prototype.list = {}; module.exports = ProjectList; ================================================ FILE: src/utils/misc.js ================================================ const readline = require('readline'); exports.$addEventListener = (elId, event, callback) => { document.getElementById(elId).addEventListener(event, callback); }; exports.$onClick = (elId, callback) => { this.$addEventListener(elId, 'click', callback); }; const outputLogMessage = (message, lineClass) => { let p = document.createElement('p'); p.classList.add(lineClass); p.innerText = message; return p; }; exports.outputLogMessage = outputLogMessage; exports.outputReadLine = (stream, lineClass, elOutput) => { readline.createInterface({ input : stream, terminal : false }).on('line', line => { elOutput.appendChild(outputLogMessage(line, lineClass)); }); }; exports.findButtonicon = (el) => { return el.childNodes[1]; }; ================================================ FILE: src/windows/index.html ================================================ Conductor - the Composer UI
================================================ FILE: src/windows/mainWindow.js ================================================ 'use strict'; const VueRouter = require('vue-router'); const Vuetify = require('vuetify'); Vue.use(Vuetify); Vue.use(VueRouter); // components const MainLayout = require('../components/MainLayout'); const RecentProjects = require('../components/RecentProjects'); const CreateProjectForm = require('../components/CreateProjectForm'); const SettingsForm = require('../components/SettingsForm'); const {store} = require('../store'); const routes = [ { path: '/recent-projects', name: 'recent-projects', component: RecentProjects }, { path: '/create-project', name: 'create-project', component: CreateProjectForm }, { path: '/settings', name: 'settings', component: SettingsForm }, { path: '*', redirect: { name: 'recent-projects', }, }, ]; const router = new VueRouter({ routes: routes, root: '/recent-projects', mode: 'history', }); new Vue({ el: '#app', router: router, store: store, render: h => h(MainLayout) }) ================================================ FILE: src/windows/project/project.html ================================================ Conductor - the Composer UI
================================================ FILE: src/windows/project/projectWindow.js ================================================ 'use strict'; const electron = require('electron'); const remote = electron.remote; const mainProcess = remote.require('./main'); const thisWindow = remote.getCurrentWindow(); let activeProject = mainProcess.getActiveProject(); thisWindow.setTitle(activeProject.getName()); const VueRouter = require('vue-router'); const Vuetify = require('vuetify'); const {store} = require('../../store'); Vue.use(Vuetify); Vue.use(VueRouter); // Vue components const ProjectLayout = require('../../components/ProjectLayout'); const ProjectDetails = require('../../components/ProjectDetails'); const PackageDetails = require('../../components/PackageDetails'); const routes = [ { path: '/project', name: 'project', component: ProjectDetails }, { path: '/package/:packagePath', name: 'package', component: PackageDetails }, { path: '*', redirect: { name: 'project', }, }, ]; const router = new VueRouter({ routes: routes, root: '/project', mode: 'history', }); new Vue({ el: '#app', router: router, store: store, render: h => h(ProjectLayout) }) ================================================ FILE: test/mainWindowTest.js ================================================ const Application = require('spectron').Application; const path = require('path'); const chai = require('chai'); const chaiAsPromised = require('chai-as-promised'); const assert = require("assert"); let electronPath = path.join(__dirname, '..', 'node_modules', '.bin', 'electron'); if (process.platform === 'win32') { electronPath += '.cmd'; } // Path to your application const appPath = path.join(__dirname, '..'); global.before(function () { chai.should(); chai.use(chaiAsPromised) }); describe('application launch', function () { this.timeout(10000); let app = null; beforeEach(function () { app = new Application({ path: electronPath, env: { RUNNING_IN_SPECTRON: '1' }, args: [appPath] }); return app.start().then(() => { assert.equal(app.isRunning(), true); chaiAsPromised.transferPromiseness = app.transferPromiseness; return app; }) }); afterEach(function () { return app.stop().then(() => { app = null; }) }); it('loads project listing window', function () { /** @type WebdriverIO.Client**/ const client = app.client.waitUntilWindowLoaded(); return client .browserWindow.focus() .getWindowCount().should.eventually.equal(1) .browserWindow.isMinimized().should.eventually.be.false .browserWindow.isDevToolsOpened().should.eventually.be.false .browserWindow.isVisible().should.eventually.be.true .browserWindow.isFocused().should.eventually.be.true .getTitle().should.eventually.equal('Conductor - the Composer UI') .getText('#open-project').should.eventually.equal('Add existing project') .getText('#create-project').should.eventually.equal('Create new project') .getText('#open-settings').should.eventually.equal('Settings') }); // @todo find a way to actually test dialogs. it('opens existing project window', function () { /** @type WebdriverIO.Client**/ const client = app.client.waitUntilWindowLoaded(); return client .browserWindow.focus() .getWindowCount().should.eventually.equal(1); }); }); ================================================ FILE: update-composer.sh ================================================ #!/usr/bin/env bash # See https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig) php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] then >&2 echo 'ERROR: Invalid installer signature' rm composer-setup.php exit 1 fi php composer-setup.php --quiet RESULT=$? rm composer-setup.php exit $RESULT