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`:
[](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 [](https://travis-ci.org/mglaman/conductor) [](https://ci.appveyor.com/project/mglaman/conductor) [](https://gratipay.com/Conductor/)
A user interface for Composer, the dependency management tool for PHP.

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: [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.

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 <nmd.matt@gmail.com>",
"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: `
<v-card>
<v-card-text>
<v-btn light
v-if="type == 'create'"
@click="composerExecute('create')">
Create project!
<i class="fa fa-spin fa-circle-o-notch" v-if="doing == 'create'"></i>
</v-btn>
<v-btn light
v-if="type == 'project'"
@click="composerExecute('install')">
Install
<i class="fa fa-spin fa-circle-o-notch" v-if="doing == 'install'"></i>
</v-btn>
<v-btn light
v-if="type == 'project'"
@click="composerExecute('validate')">
Validate
<i class="fa fa-spin fa-circle-o-notch" v-if="doing == 'validate'"></i>
</v-btn>
<v-btn light
v-if="type == 'project'"
@click="composerExecute('updateProject')">
Update
<i class="fa fa-spin fa-circle-o-notch" v-if="doing == 'updateProject'"></i>
</v-btn>
<v-btn light
v-if="type == 'package'"
@click="composerExecute('show')">
Show
<i class="fa fa-spin fa-circle-o-notch" v-if="doing == 'show'"></i>
</v-btn>
<v-btn light
v-if="type == 'package'"
@click="composerExecute('updatePackage')">
Update
<i class="fa fa-spin fa-circle-o-notch" v-if="doing == 'updatePackage'"></i>
</v-btn>
<v-btn light
v-if="type == 'package'"
@click="composerExecute('remove')">
Remove
<i class="fa fa-spin fa-circle-o-notch" v-if="doing == 'remove'"></i>
</v-btn>
<!--<button class="flex" id="action-composer-add">Add <i class="fa fa-spin fa-circle-o-notch hidden"></i></button>-->
</v-card-text>
<div class="project__output" id="composer-output">
<p v-for="line in composerOutLines" :class="line.className">
{{ line.text }}
</p>
</div>
</v-card>
`,
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: `
<v-card>
<v-card-text>
<v-container fluid>
<v-layout row>
<v-text-field
v-model="packageName"
name="packageName"
label="Package Name"
hint="vendor/package"
persistent-hint
required
></v-text-field>
</v-layout>
<v-layout row>
<v-text-field
v-model="projectName"
name="projectName"
label="Project Name"
hint="some-dir"
persistent-hint
required
></v-text-field>
</v-layout>
<v-layout row>
<v-flex xs8>
<v-text-field
v-model="destinationFolder"
name="destinationFolder"
label="Destination Folder"
disabled
></v-text-field>
</v-flex>
<v-flex xs4>
<v-btn light
@click="projectDestinationBrowse">Browse <i class="fa fa-search"></i></v-btn>
</v-flex>
</v-layout>
<v-layout row>
<v-text-field
v-model="destination"
label="Destination"
disabled
></v-text-field>
</v-layout>
</v-container>
</v-card-text>
<composer-frame :type="'create'" :newProjectDetails="{packageName,projectName,description,destinationFolder,destination}"></composer-frame>
</v-card>
`,
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: `
<v-app standalone>
<v-navigation-drawer permanent light>
<v-toolbar flat class="transparent">
<v-list class="pa-0">
<v-list-tile avatar tag="ul">Conductor</v-list-tile>
</v-list>
</v-toolbar>
<v-divider></v-divider>
<main-navigation></main-navigation>
</v-navigation-drawer>
<v-toolbar class="cyan" dark>
<v-toolbar-title>{{ pageTitle }}</v-toolbar-title>
</v-toolbar>
<main>
<v-container fluid>
<router-view></router-view>
</v-container>
</main>
</v-app>
`,
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: `
<v-list dense class="pt-0">
<v-list-tile to="recent-projects" :router="true">
<v-list-tile-action>
<v-icon>list</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title id="recent-projects">Recent projects</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="existingProject">
<v-list-tile-action>
<v-icon>folder_open</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title id="open-project">Add existing project</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="create-project" :router="true">
<v-list-tile-action>
<v-icon>create_new_folder</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title id="create-project">Create new project</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="globalProject" v-if="globalComposerFileExists">
<v-list-tile-action>
<v-icon>public</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title id="global-composer">Global composer</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="settings" :router="true">
<v-list-tile-action>
<v-icon>settings</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title id="open-settings">Settings</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
`,
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: `
<v-card>
<v-card-text>
<ul class="package__details flex flex--column">
<li class="package__details--item">
<span class="package__details--label">Version</span>
<span class="package__details--value">{{ package.json.version }}</span>
</li>
<li class="package__details--item" v-if="package.json.homepage">
<span class="package__details--label">Homepage</span>
<span class="package__details--value">{{ package.json.homepage }}</span>
</li>
<li class="package__details--item">
<span class="package__details--value">{{ package.json.description }}</span>
</li>
<li v-if="package.packages">
<span>Requirements</span>
<ul>
<li v-for="(version, dependency) in package.packages">
{{ dependency }}
</li>
</ul>
</li>
<li v-if="package.packagesDev">
<span>Dev Requirements</span>
<ul id="package-dev-dependencies">
<li v-for="(version, dependency) in package.packagesDev">
{{ dependency }}
</li>
</ul>
</li>
</ul>
</v-card-text>
<composer-frame :type="'package'" :package="package"></composer-frame>
</v-card>
`,
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: `
<v-card>
<v-card-text>
<div>
<p v-if="project.json.homepage">{{ project.json.homepage }}</p>
<p>{{ project.json.description }}</p>
</div>
</v-card-text>
<composer-frame :type="'project'" :project="project"></composer-frame>
</v-card>
`,
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: `
<v-app id="app" standalone>
<v-navigation-drawer permanent light>
<v-toolbar flat class="transparent">
<v-list class="pa-0">
<v-list-tile avatar tag="ul" :to="project" :router="true">{{ project.json.name }}</v-list-tile>
</v-list>
</v-toolbar>
<v-divider></v-divider>
<project-navigation></project-navigation>
</v-navigation-drawer>
<v-toolbar class="cyan" dark>
<v-toolbar-title>{{ pageTitle }}</v-toolbar-title>
</v-toolbar>
<main>
<v-container fluid>
<router-view></router-view>
</v-card>
</v-container>
</main>
</v-app>
`,
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: `
<v-list dense>
<v-list-group>
<v-list-tile slot="item">
<v-list-tile-action>
<v-icon>queue_music</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Composer</v-list-tile-title>
</v-list-tile-content>
<v-list-tile-action>
<v-icon>keyboard_arrow_down</v-icon>
</v-list-tile-action>
</v-list-tile>
<v-list-tile>
<v-list-tile-content>
<v-list-tile-title>composer.json</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile>
<v-list-tile-content>
<v-list-tile-title>composer.lock</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list-group>
<template v-if="list.items" v-for="list in dependencies" >
<v-list-group :value="list.active" :key="list.type">
<v-list-tile slot="item">
<v-list-tile-action>
<v-icon>{{ list.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ list.title }}</v-list-tile-title>
</v-list-tile-content>
<v-list-tile-action>
<v-icon>keyboard_arrow_down</v-icon>
</v-list-tile-action>
</v-list-tile>
<template v-for="(version,package) in list.items">
<v-list-tile
:to="{name: 'package', params: { packagePath: package } }"
:router="true"
v-if="package != 'php' || package.indexOf('ext-') > -1">
<v-list-tile-content>
<v-list-tile-title>{{ package }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</template>
</v-list-group>
</template>
</v-list>
`,
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: `
<v-list two-line subheader>
<v-list-tile avatar v-for="project in projects">
<v-list-tile-avatar>
<v-icon class="grey lighten-1 white--text">folder</v-icon>
</v-list-tile-avatar>
<v-list-tile-content @click="openProject(project.path)">
<v-list-tile-title>{{ project.name }}</v-list-tile-title>
<v-list-tile-sub-title>{{ project.path }}</v-list-tile-sub-title>
</v-list-tile-content>
<v-list-tile-action>
<v-btn icon ripple @click="removeProject(project.path)">
<v-icon class="grey--text text--lighten-1">delete_forever</v-icon>
</v-btn>
</v-list-tile-action>
</v-list-tile>
</v-list>
`,
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: `
<v-card>TODO</v-card>
`,
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Conductor - the Composer UI</title>
<link href="../../node_modules/font-awesome/css/font-awesome.css" rel="stylesheet" type="text/css" />
<link href="../../public/css/conductor.css" rel="stylesheet" type="text/css"/>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
<link href="../../node_modules/vuetify/dist/vuetify.min.css" rel="stylesheet" type="text/css">
<script src="../../node_modules/vue/dist/vue.js"></script>
<script src="../../node_modules/vuetify/dist/vuetify.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
<script>
// You can also require other files to run in this process
require('./mainWindow.js')
</script>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Conductor - the Composer UI</title>
<link href="../../../node_modules/font-awesome/css/font-awesome.css" rel="stylesheet" type="text/css" />
<link href="../../../public/css/conductor.css" rel="stylesheet" type="text/css"/>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
<link href="../../../node_modules/vuetify/dist/vuetify.min.css" rel="stylesheet" type="text/css">
<script src="../../../node_modules/vue/dist/vue.js"></script>
<script src="../../../node_modules/vuetify/dist/vuetify.min.js"></script>
</head>
<body>
<div id="app"></div>
</body>
<script>
require('./projectWindow.js')
</script>
</html>
================================================
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
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
SYMBOL INDEX (38 symbols across 15 files)
FILE: main.js
function createMainWindow (line 27) | function createMainWindow() {
function createProjectWindow (line 40) | function createProjectWindow(folder) {
FILE: src/components/ComposerFrame.js
method composerExecute (line 90) | composerExecute(command) {
FILE: src/components/CreateProjectForm.js
method destination (line 74) | destination(){
method projectDestinationBrowse (line 79) | projectDestinationBrowse (){
FILE: src/components/MainLayout.js
method data (line 32) | data() {
method pageTitle (line 40) | pageTitle() {
FILE: src/components/MainNavigation.js
method globalComposerFileExists (line 53) | globalComposerFileExists() {
FILE: src/components/PackageDetails.js
method package (line 46) | package() {
method created (line 50) | created() {
method '$route' (line 54) | '$route' () {
FILE: src/components/ProjectDetails.js
method project (line 21) | project() {
FILE: src/components/ProjectLayout.js
method created (line 29) | created() {
method mounted (line 32) | mounted() {
method data (line 35) | data() {
method pageTitle (line 43) | pageTitle() {
method project (line 46) | project() {
FILE: src/components/ProjectNavigation.js
method project (line 59) | project() {
method dependencies (line 62) | dependencies() {
FILE: src/components/RecentProjects.js
method projects (line 25) | projects() {
method mounted (line 29) | mounted() {
method removeProject (line 33) | removeProject(path) {
FILE: src/components/SettingsForm.js
method mounted (line 8) | mounted() {
FILE: src/store.js
method pageTitle (line 25) | pageTitle(state) {
method projectsList (line 32) | projectsList(state) {
method activeProject (line 39) | activeProject(state) {
method setPageTitle (line 45) | setPageTitle(state, title) {
method setActiveProject (line 48) | setActiveProject(state, project) {
method removeProject (line 51) | removeProject(state, path) {
method removeProjectAction (line 61) | removeProjectAction(context, path) {
FILE: src/utils/AppMenu.js
method click (line 20) | click() {
method click (line 26) | click() {
method click (line 74) | click() {
method click (line 92) | click() {
FILE: src/utils/AppUpdater.js
class AppUpdater (line 7) | class AppUpdater {
method constructor (line 8) | constructor() {
function notify (line 26) | function notify(title, message) {
FILE: src/utils/BrowserWindowFactory.js
function getDefaultWindowHeight (line 4) | function getDefaultWindowHeight(w) {
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (62K chars).
[
{
"path": ".editorconfig",
"chars": 182,
"preview": "root = true\n\n[*]\nindent_style = tab\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newlin"
},
{
"path": ".gitignore",
"chars": 38,
"preview": "node_modules\nnpm-debug.log\n.idea\ndist\n"
},
{
"path": ".travis.yml",
"chars": 642,
"preview": "osx_image: xcode7.3\n\nsudo: required\ndist: trusty\n\nlanguage: c\n\nmatrix:\n include:\n - os: osx\n - os: linux\n en"
},
{
"path": "CONTRIBUTING.md",
"chars": 707,
"preview": "# Contributing to Conductor\n\nAll forms of contribution are welcomed for Conductor: bug fixes, feature pull requests, bug"
},
{
"path": "Gruntfile.js",
"chars": 1211,
"preview": "module.exports = function (grunt) {\n\t/** @var {Grunt} grunt */\n\t'use strict';\n\n\t// Force use of Unix newlines\n\tgrunt.uti"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "The MIT License\n\nCopyright (c) 2016 Matt Glaman\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 1624,
"preview": "#  Conductor [;\nconst electron = require('electron');\nconst Project = require('./src/models/Project');\nconst P"
},
{
"path": "mkdocs.yml",
"chars": 398,
"preview": "site_name: Conductor Documentation\n\nrepo_url: https://github.com/mglaman/conductor\nsite_description: 'Conductor - A cros"
},
{
"path": "package.json",
"chars": 2010,
"preview": "{\n \"name\": \"conductor\",\n \"version\": \"0.1.6\",\n \"description\": \"A Composer user interface\",\n \"main\": \"main.js\",\n \"scr"
},
{
"path": "public/css/conductor.css",
"chars": 4226,
"preview": "/* Color definitions */\n/* Semantic definitions */\n.flex {\n display: flex;\n}\n\n.flex--row {\n flex-direction: row;\n}\n\n.f"
},
{
"path": "public/scss/_flex.scss",
"chars": 141,
"preview": ".flex {\n display: flex;\n}\n.flex--row {\n flex-direction: row;\n}\n.flex--column {\n flex-direction: column;\n}\n.flex--grow"
},
{
"path": "public/scss/_vars.scss",
"chars": 320,
"preview": "/* Color definitions */\n$light-gray: #f5f5f5;\n$middle-gray: #d5d5d5;\n$dark-gray: #e5e5e5;\n$blue: #4078c0;\n$white: #fff;\n"
},
{
"path": "public/scss/styles.scss",
"chars": 3504,
"preview": "@import \"vars\";\n@import \"flex\";\n\nhtml, body {\n margin: 0;\n padding: 0;\n height: 100%;\n max-height: 100vh;\n display:"
},
{
"path": "src/components/ComposerFrame.js",
"chars": 4951,
"preview": "'use strict';\n\nconst fs = require('fs');\nconst remote = require('electron').remote;\nconst dialog = remote.dialog;\nconst "
},
{
"path": "src/components/CreateProjectForm.js",
"chars": 2085,
"preview": "'use strict';\n\nconst fs = require('fs');\nconst remote = require('electron').remote;\nconst dialog = remote.dialog;\nconst "
},
{
"path": "src/components/MainLayout.js",
"chars": 867,
"preview": "'use strict';\n\nconst MainNavigation = require('./MainNavigation');\n\nmodule.exports = {\n\ttemplate: `\n\t\t<v-app standalone>"
},
{
"path": "src/components/MainNavigation.js",
"chars": 1947,
"preview": "'use strict';\n\nconst fs = require('fs');\nconst remote = require('electron').remote;\nconst mainProcess = remote.require('"
},
{
"path": "src/components/PackageDetails.js",
"chars": 1642,
"preview": "'use strict';\n\nconst ComposerFrame = require('./ComposerFrame');\n\nmodule.exports = {\n\ttemplate: `\n\t\t<v-card>\n\t\t\t<v-card-"
},
{
"path": "src/components/ProjectDetails.js",
"chars": 503,
"preview": "'use strict';\n\nconst ComposerFrame = require('./ComposerFrame');\n\nmodule.exports = {\n\ttemplate: `\n\t\t<v-card>\n\t\t\t<v-card-"
},
{
"path": "src/components/ProjectLayout.js",
"chars": 1262,
"preview": "'use strict';\n\nconst ProjectNavigation = require('../components/ProjectNavigation');\nconst mainProcess = require('electr"
},
{
"path": "src/components/ProjectNavigation.js",
"chars": 2222,
"preview": "'use strict';\n\nconst mainProcess = require('electron').remote.require('./main')\n\nmodule.exports = {\n\ttemplate: `\n\t\t<v-li"
},
{
"path": "src/components/RecentProjects.js",
"chars": 1152,
"preview": "'use strict';\n\nconst mainProcess = require('electron').remote.require('./main');\n\nmodule.exports = {\n\ttemplate: `\n <v"
},
{
"path": "src/components/SettingsForm.js",
"chars": 162,
"preview": "'use strict';\n\n\nmodule.exports = {\n\ttemplate: `\n <v-card>TODO</v-card>\n\t`,\n\tmounted() {\n\t\tthis.$store.commit('setPage"
},
{
"path": "src/models/Lock.js",
"chars": 729,
"preview": "'use strict';\nconst Package = require('./Package');\nconst Lock = function (json) {\n\tconst self = this;\n\tthis.json = json"
},
{
"path": "src/models/Package.js",
"chars": 278,
"preview": "'use strict';\n\nvar Package = function (json) {\n\tthis.json = json;\n\tthis.__get = (key) => { return this.json[key]};\n\tthis"
},
{
"path": "src/models/Project.js",
"chars": 1329,
"preview": "'use strict';\nlet Lock = require('./Lock');\n/**\n *\n * @param {String} path\n * @constructor\n */\nlet Project = function (p"
},
{
"path": "src/store.js",
"chars": 1313,
"preview": "'use strict';\n\nconst Vuex = require('vuex');\nconst projectList = require('electron').remote.require('./main').getProject"
},
{
"path": "src/utils/AppMenu.js",
"chars": 1661,
"preview": "'use strict';\n\nconst electron = require('electron');\nconst app = electron.app;\nconst menu = electron.Menu;\nconst shell ="
},
{
"path": "src/utils/AppUpdater.js",
"chars": 789,
"preview": "const BrowserWindowElectron = require('electron').BrowserWindow;\nconst autoUpdater = require('electron-updater').autoUpd"
},
{
"path": "src/utils/BrowserWindowFactory.js",
"chars": 506,
"preview": "'use strict';\n\nlet BrowserWindow = require('electron').BrowserWindow;\nfunction getDefaultWindowHeight(w) {\n\treturn Math."
},
{
"path": "src/utils/Composer.js",
"chars": 1575,
"preview": "'use strict';\n\n/**\n *\n * @param {String} projectPath\n * @constructor\n */\nlet Composer = function(projectPath) {\n\tconst b"
},
{
"path": "src/utils/ProjectList.js",
"chars": 689,
"preview": "'use strict';\nconst Config = require('electron-config');\n\n/**\n * @constructor\n */\nlet ProjectList = function() {\n\t/** @t"
},
{
"path": "src/utils/misc.js",
"chars": 749,
"preview": "const readline = require('readline');\n\nexports.$addEventListener = (elId, event, callback) => {\n\tdocument.getElementById"
},
{
"path": "src/windows/index.html",
"chars": 796,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Conductor - the Composer UI</title>\n\t<link href=\"../../nod"
},
{
"path": "src/windows/mainWindow.js",
"chars": 963,
"preview": "'use strict';\n\nconst VueRouter = require('vue-router');\nconst Vuetify = require('vuetify');\n\nVue.use(Vuetify);\nVue.use(V"
},
{
"path": "src/windows/project/project.html",
"chars": 754,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>Conductor - the Composer UI</title>\n\t<link href=\"../../../"
},
{
"path": "src/windows/project/projectWindow.js",
"chars": 1073,
"preview": "'use strict';\n\nconst electron = require('electron');\nconst remote = electron.remote;\nconst mainProcess = remote.require("
},
{
"path": "test/mainWindowTest.js",
"chars": 2010,
"preview": "const Application = require('spectron').Application;\nconst path = require('path');\nconst chai = require('chai');\nconst c"
},
{
"path": "update-composer.sh",
"chars": 555,
"preview": "#!/usr/bin/env bash\n# See https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md\nEXPECTED_SIGNATUR"
}
]
// ... and 3 more files (download for full content)
About this extraction
This page contains the full source code of the mglaman/conductor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (52.3 KB), approximately 16.0k tokens, and a symbol index with 38 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.