diff --git a/conf/tasks/es6-build.js b/conf/tasks/es6-build.js index e45afcdd..4531d575 100644 --- a/conf/tasks/es6-build.js +++ b/conf/tasks/es6-build.js @@ -12,9 +12,34 @@ gulp.task('es6-build', done => { 'front-end', 'ndm', 'ndm-updater', + 'npm-runner', done); }); +gulp.task('npm-runner', () => { + + return rollup({ + 'entry': `${paths.lib}js/npm/npm-runner.js`, + 'plugins': [ + rollupJSON(), + rollupBabel({ + 'presets': [ + 'es2015-rollup' + ] + }) + ] + }).then(bundle => { + + return bundle.write({ + 'format': 'iife', + 'moduleId': 'npm-ui-ng', + 'moduleName': 'npm-ui-ng', + 'sourceMap': true, + 'dest': `${paths.tmp}/npm-runner.js` + }); + }); +}); + gulp.task('ndm', () => { return rollup({ diff --git a/lib/content.pug b/lib/content.pug index 5885aa45..f3e9c962 100644 --- a/lib/content.pug +++ b/lib/content.pug @@ -1,6 +1,7 @@ -include ./top.pug -.content(ng-controller='ContentController as content') - .row.home.bg-ultralight(ng-hide="content.loading || content.packageInformations", ng-show="content.goBackHome") +.content(ng-controller="ContentController as content") + span(npm-loading) + include ./npm-doctor-log.pug + .row.home.bg-ultralight(ng-show="content.tabs.length <= 0") div .separator10 small.color-black @@ -9,59 +10,66 @@ include ./top.pug .separator10 button.home-button(ng-click="shell.openChooser()") | Add projects - .row.table-loader.bg-lighter(ng-show="content.loading", ng-hide='!content.loading || content.loaded') - img(src='img/loading.svg', width='22') - | Loading ... - .separator10 - .color-black - small May take some time - div(ng-show='content.loaded && !content.goBackHome') - .row.table-header - .col-xs-4.clickable(ng-click="content.sortTableBy('name')") - | Package - i(class="fa", ng-class="{'fa-sort': !content.tableOrderBy.includes('-name') || !content.tableOrderBy.includes('name'), 'fa-sort-down': content.tableOrderBy.includes('-name'), 'fa-sort-up': content.tableOrderBy.includes('name')}") - .col-xs-2 - | Current - .col-xs-2 - | Wanted - .col-xs-2 - | Latest - .col-xs-2.clickable(ng-click="content.sortTableBy('kind')") - | Env - i(class="fa", ng-class="{'fa-sort': !content.tableOrderBy.includes('-kind') || !content.tableOrderBy.includes('kind'), 'fa-sort-down': content.tableOrderBy.includes('-kind'), 'fa-sort-up': content.tableOrderBy.includes('kind')}") - .table-body(ng-table-keyboard) - - .row.table-row.disabled(ng-repeat='aPackage in content.packageInformations', ng-if="content.isGlobalProject && aPackage.name === 'npm'", title="Do not perform npm global actions from here") - .col-xs-4 {{ aPackage.name }} + .tab(npm-tabs, ng-repeat="tab in content.tabs", npm-tab-id="{{tab}}", ng-show="content.activeTab === tab && tab") + .tab-menu + span.tab-button(ng-repeat="tab in content.tabs", ng-class="{'active': content.activeTab === tab}", ng-click="content.activeTab = tab") + spanner(ng-if="tab === ''") + img(src="img/npm-logo-cube.svg", width="16") + | Globals + spanner(ng-if="tab !== ''") + | {{ tab | lastNameInPath}} + a(ng-click="content.closeProjectTab(tab)") + i(class="fa fa-remove") + include ./top.pug + div(ng-show="tab") + .row.table-header + .col-xs-4.clickable(ng-click="sortTableBy('name')") + | Package + i(class="fa", ng-class="{'fa-sort': !tableOrderBy.includes('-name') || !tableOrderBy.includes('name'), 'fa-sort-down': tableOrderBy.includes('-name'), 'fa-sort-up': tableOrderBy.includes('name')}") .col-xs-2 - span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}") - | {{ aPackage.current }} + | Current .col-xs-2 - i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest") - | {{ aPackage.wanted }} + | Wanted .col-xs-2 - b(ng-if="aPackage.latest") - | {{ aPackage.latest }} - i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest") - .col-xs-2 - | {{ aPackage.kind }} - - .row.table-row(ng-repeat='aPackage in content.packageInformations | orderBy: content.tableOrderBy', id="table-item-{{$index}}", ng-table-keyboard-selected-items="content.selectedPackages", ng-if="!content.isGlobalProject || content.isGlobalProject && aPackage.name !== 'npm'", selection-model, selection-model-mode="'multiple'", selection-model-selected-items="content.selectedPackages", ng-click="shell.selectPackages(content.selectedPackages)", ng-class="{'active': content.selectedPackages.includes(aPackage), 'table-row-loading': shell.currentSelectedPackages.includes(aPackage) && content.showLoadingSelectedRow}") - .col-xs-4 {{ aPackage.name }} - .col-xs-2 - span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}") - | {{ aPackage.current }} - .col-xs-2 - i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest") - | {{ aPackage.wanted }} - .col-xs-2 - b(ng-if="aPackage.latest") - | {{ aPackage.latest }} - i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest") - .col-xs-2 - | {{ aPackage.kind }} - div(ng-show="content.packageInformations && content.packageInformations.length > 0") - h6 - | Packages informations - div.table-infos - include ./package-informations.pug + | Latest + .col-xs-2.clickable(ng-click="sortTableBy('kind')") + | Env + i(class="fa", ng-class="{'fa-sort': !tableOrderBy.includes('-kind') || !tableOrderBy.includes('kind'), 'fa-sort-down': tableOrderBy.includes('-kind'), 'fa-sort-up': tableOrderBy.includes('kind')}") + .table-body(ng-table-keyboard, ng-class="{'freezed': showLoadingSelectedRow}") + .table-loader(ng-show="loading && !loaded") + .table-loader-content + img(src='img/loading.svg') + | Loading packages... + + .row.table-row.disabled(ng-repeat='aPackage in packageInformations', ng-if="isGlobalProject && aPackage.name === 'npm'", title="Do not perform npm global actions from here") + .col-xs-4 {{ aPackage.name }} + .col-xs-2 + span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}") + | {{ aPackage.current }} + .col-xs-2 + i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest") + | {{ aPackage.wanted }} + .col-xs-2 + b(ng-if="aPackage.latest") + | {{ aPackage.latest }} + i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest") + .col-xs-2 + | {{ aPackage.kind }} + + .row.table-row(ng-repeat='aPackage in packageInformations | orderBy: tableOrderBy', ng-hide="!packageInformations", id="table-item-{{$index}}", ng-table-keyboard-selected-items="selectedPackages", ng-if="!isGlobalProject || isGlobalProject && aPackage.name !== 'npm'", selection-model, selection-model-mode="'multiple'", selection-model-selected-items="selectedPackages", ng-click="selectPackages(selectedPackages)", ng-class="{'active': selectedPackages.includes(aPackage), 'table-row-loading': currentSelectedPackages.includes(aPackage) && showLoadingSelectedRow}") + .col-xs-4 {{ aPackage.name }} + .col-xs-2 + span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}") + | {{ aPackage.current }} + .col-xs-2 + i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest") + | {{ aPackage.wanted }} + .col-xs-2 + b(ng-if="aPackage.latest") + | {{ aPackage.latest }} + i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest") + .col-xs-2 + | {{ aPackage.kind }} + div + div.table-infos + include ./package-informations.pug diff --git a/lib/footer.pug b/lib/footer.pug index 7da29568..edb59cc6 100644 --- a/lib/footer.pug +++ b/lib/footer.pug @@ -7,17 +7,18 @@ | v{{shell.npmCurrentVersionBadge}} button.button-global(type="button", title="Enable ndm in global folder", ng-show="shell.globalDisabled", ng-click="shell.enableGlobal()") i.fa.fa-globe.color-primary - | Enable + | Enable globals button.button-update(type="button", ng-show="!shell.globalDisabled && shell.npmCurrentVersionBadge", title="Update npm", ng-click="shell.activeClickedLink('update'); shell.updateNpm()") i.fa.fa-history - | Update - button.button-update(type="button", title="Run doctor", ng-click="shell.activeClickedLink('doctor'); shell.runDoctor()") - i.fa.fa-doctor - | Doctor - span(class="npm-status", ng-mouseenter="shell.checkRegistryStatus()") + | Update npm + span(class="npm-status pull-right", ng-mouseenter="shell.checkRegistryStatus()") i.fa.fa-disk(title="npm registry is available", ng-show="!shell.loadingRegistryStatus && shell.registryStatus") i.fa.fa-disk(title="npm registry checking ...", ng-show="shell.loadingRegistryStatus") i.fa.fa-disk(title="npm registry is unavailable", ng-show="!shell.loadingRegistryStatus && !shell.registryStatus") div.loader(ng-class="{'loading': shell.loadingRegistryStatus}") i.fa.fa-circle(ng-class="{'available': !shell.loadingRegistryStatus && shell.registryStatus}") i.fa.fa-circle(ng-class="{'unavailable': !shell.loadingRegistryStatus && !shell.registryStatus}") + span(class="pull-right") + button.button-doctor(type="button", title="Run doctor", ng-click="shell.activeClickedLink('doctor'); shell.runDoctor()") + i.fa.fa-doctor + | Doctor diff --git a/lib/index.pug b/lib/index.pug index 3461b43c..ea4d607f 100644 --- a/lib/index.pug +++ b/lib/index.pug @@ -8,7 +8,6 @@ html link(rel='stylesheet', href='css/index.css', media='screen', charset='utf-8') script(type='text/javascript', src='../node_modules/angular/angular.min.js') - script(type='text/javascript', src='../node_modules/angular-ui-router/release/angular-ui-router.min.js') script(type='text/javascript', src='../node_modules/selection-model/dist/selection-model.min.js') script(type='text/javascript', src='../node_modules/ace-builds/src-min-noconflict/ace.js') @@ -20,6 +19,7 @@ html | Loading .page include ./left.pug - .right-column(ui-view) + .right-column + include ./content.pug include ./footer.pug diff --git a/lib/install-new-package-version.pug b/lib/install-new-package-version.pug index 101db3f0..bf85cd84 100644 --- a/lib/install-new-package-version.pug +++ b/lib/install-new-package-version.pug @@ -1,29 +1,30 @@ -div.dialog.prompt(ng-if="shell.activeLink === '4'", ng-init="topMenu.versionPackageVersion = undefined") - form(ng-submit='topMenu.installVersionPackage(shell.currentSelectedPackages[0], topMenu.versionPackageVersion)') +div.dialog.prompt(ng-show="showSpecificVersionPrompt && currentSelectedPackages.length === 1", ng-init="versionPackageVersion = undefined") + form(ng-submit='installVersionPackage(currentSelectedPackages[0], versionPackageVersion)') input(placeholder='Package name', type='text', readonly, disabled, - ng-value="shell.currentSelectedPackages[0].name") + ng-value="currentSelectedPackages[0].name") = " " input(class="hide", placeholder='@version', ng-autofocus, type='text', - ng-model='topMenu.versionPackageVersion', - ng-value="topMenu.versionPackageVersion") + ng-model='versionPackageVersion', + ng-value="versionPackageVersion") = " " span(class="prompt-kind") - select(name="packageVersionSelect", ng-model="topMenu.pkgVersionModel", ng-change="topMenu.versionPackageVersion = topMenu.pkgVersionModel") + select(name="packageVersionSelect", ng-model="pkgVersionModel", ng-change="versionPackageVersion = pkgVersionModel") option(value="", selected) | - - option(ng-repeat="pkgVersion in shell.selectedPackageViewInfos.versions | orderBy : pkgVersion : 'reverse' track by $index", ng-value="pkgVersion") + option(ng-repeat="pkgVersion in selectedPackageViewInfos.versions | orderBy : pkgVersion : 'reverse' track by $index", ng-value="pkgVersion") | {{pkgVersion}} - button - span(ng-show="!topMenu.installingPackageVersion") + button(ng-disabled="installingPackageVersion") + span(ng-show="!installingPackageVersion") | Install - span(ng-show="topMenu.installingPackageVersion") + span(ng-show="installingPackageVersion") img(src="img/loading.svg", width="13") = " " = " " - i(class="fa fa-times-circle-o button-close-prompt", ng-click="shell.activeLink = undefined;") + button(class="button-close-prompt pull-right", type="button", ng-click="hideInstallVersionPrompt();") + i(class="fa fa-remove") diff --git a/lib/install-new-package.pug b/lib/install-new-package.pug index 23117326..e3657080 100644 --- a/lib/install-new-package.pug +++ b/lib/install-new-package.pug @@ -1,32 +1,33 @@ -div.dialog.prompt(ng-if="shell.activeLink === '1'") - form(ng-submit='topMenu.installPackage(topMenu.packageName, topMenu.newPackageKind)') +div.dialog.prompt(ng-show="showInstallPrompt") + form(ng-submit='installPackage(packageName, newPackageKind)') - div(class="tags-input", ng-tag-input, ng-autofocus, ng-model="topMenu.packageName", ng-keyup="topMenu.search(topMenu.packageName[topMenu.packageName.length - 1].name)", contenteditable="true", ng-attr-disabled="{{topMenu.installingPackage ? 'disabled' : ''}}" placeholder="package<@version> ...") + div(class="tags-input", ng-tag-input, tab-path-id="{{tab}}" ng-autofocus, ng-model="packageName", ng-keyup="search(packageName[packageName.length - 1].name)", contenteditable="true", ng-attr-disabled="{{installingPackage ? 'disabled' : ''}}" placeholder="package<@version> ...") - input(ng-hide="true", ng-model="topMenu.searchKeywords", ng-bind="topMenu.packageName") + input(ng-hide="true", ng-model="searchKeywords", ng-bind="packageName") = " " span(class="prompt-kind") - input(type="checkbox", ng-model='topMenu.newPackageKind', ng-disabled='shell.globalSelected') + input(type="checkbox", ng-model='newPackageKind', ng-disabled='shell.globalSelected') = " " = " " | dev = " " - button(id="install-new-packages-button", ng-class="{'disabled': topMenu.installingPackage || !topMenu.packageName}") - span(ng-show="!topMenu.installingPackage") + button(id="install-new-packages-button", ng-disabled="installingPackage || !packageName") + span(ng-show="!installingPackage") | Install - span(ng-show="topMenu.installingPackage") + span(ng-show="installingPackage") img(src="img/loading.svg", width="13") = " " = " " - i(class="fa fa-times-circle-o button-close-prompt", ng-click="shell.activeLink = undefined") - .prompt-search + button(class="button-close-prompt pull-right", type="button", ng-click="hideInstallPrompt();") + i(class="fa fa-remove") + .prompt-search(ng-hide="installingPackage") .prompt-search-content - .prompt-search-item(ng-repeat="item in topMenu.searchResults.objects", ng-click="topMenu.searchChoosePackage(item.package.name)") + .prompt-search-item(ng-repeat="item in searchResults.objects", ng-click="searchChoosePackage(item.package.name)") h5 | {{item.package.name}} div | {{item.package.description}} - .prompt-search-loader(ng-show="topMenu.searchingNpm") + .prompt-search-loader(ng-show="searchingNpm") img(src="img/loading.svg") | Loading results ... diff --git a/lib/js/directives/ng-table-keyboard.js b/lib/js/directives/ng-table-keyboard.js index fa74f920..8a754fbd 100644 --- a/lib/js/directives/ng-table-keyboard.js +++ b/lib/js/directives/ng-table-keyboard.js @@ -7,7 +7,7 @@ angular.module(moduleName, []) return (scope, element) => { const onArrowDown = () => { - let tableRows = element[0].querySelectorAll('.table-row:not(.disabled)') + let tableRows = element[0].querySelectorAll('.tab:not(.ng-hide) .table-row:not(.disabled)') , clickedElement; if (tableRows && tableRows.length > 0) { diff --git a/lib/js/directives/ng-tag-input.js b/lib/js/directives/ng-tag-input.js index a4b9b6b4..3b7874da 100644 --- a/lib/js/directives/ng-tag-input.js +++ b/lib/js/directives/ng-tag-input.js @@ -7,7 +7,8 @@ angular.module(moduleName, []) return { 'require': '?ngModel', 'link': (scope, element, attrs, ngModel) => { - let documentRange + let ngTagInputIdentifier = attrs.tabPathId + , documentRange , windowSelection , focusTheEnd = () => { try { @@ -117,20 +118,26 @@ angular.module(moduleName, []) return event.preventDefault(); } , updateOnSearchChoosenPackage = $rootScope.$on('top-menu:search-choosen-package', (eventInformation, data) => { - let newInputValue = ''; + if (data && + data.data && + data.tabPath && + data.tabPath === ngTagInputIdentifier) { //if search input is showing on this specific tab - data.forEach(pkg => { - newInputValue += pkg.name; - if (pkg.version && - pkg.version.length > 0) { - newInputValue += `@${pkg.version}`; - } - newInputValue += ' '; //leave a blank space at the end of the string to split into tags again - }); + let newInputValue = ''; - element[0].innerText = newInputValue; - createTags(); - updateModel(); + data.data.forEach(pkg => { + newInputValue += pkg.name; + if (pkg.version && + pkg.version.length > 0) { + newInputValue += `@${pkg.version}`; + } + newInputValue += ' '; //leave a blank space at the end of the string to split into tags again + }); + + element[0].innerText = newInputValue; + createTags(); + updateModel(); + } }); element.on('mousedown', onTrigger); diff --git a/lib/js/filters.js b/lib/js/filters.js index f6ae67a7..20fd2c8f 100644 --- a/lib/js/filters.js +++ b/lib/js/filters.js @@ -6,6 +6,25 @@ angular.module(moduleName, []) return string => { return string.replace(/<\/?[^>]+(>|$)/g, ''); }; +}) +.filter('lastNameInPath', () => { + return string => { + let toReturn + , split; + + if (string.includes('\\')) { + //on windows + split = string.split('\\'); + toReturn = split[split.length - 1]; + } + + if (string.includes('/')) { + //on linux and mac + split = string.split('/'); + toReturn = split[split.length - 1]; + } + return toReturn ? toReturn : string; + }; }); export default moduleName; diff --git a/lib/js/index.js b/lib/js/index.js index 8c128693..494d9ed1 100644 --- a/lib/js/index.js +++ b/lib/js/index.js @@ -27,7 +27,6 @@ const Storage = require('electron-storage') , visitor = analytics('UA-90211405-1', visitorId); angular.module('ndm', [ - 'ui.router', 'selectionModel', shellModule, contentModule, @@ -50,15 +49,6 @@ angular.module('ndm', [ .constant('timeoutForWhenUserIsPresent', 2500) .constant('appHistoryFile', 'snapshots.json') .constant('npmGlobal', '') -.config(/*@ngInject*/ function configNg($stateProvider, $urlRouterProvider) { - - $urlRouterProvider.otherwise('/'); - $stateProvider - .state('project', { - 'url': '/?{project:json}', - 'templateUrl': 'content.html' - }); -}) .run(/*@ngInject*/ function RunInitStorage(appHistoryFile, $log) { //create storage file in case Storage.isPathExists(appHistoryFile, exist => { diff --git a/lib/js/interface/content.js b/lib/js/interface/content.js index f0c3c6f3..c2e7a24e 100644 --- a/lib/js/interface/content.js +++ b/lib/js/interface/content.js @@ -1,171 +1,523 @@ +/*global require*/ import angular from 'angular'; import npmApi from '../npm/npm-api.js'; -const moduleName = 'npm-ui.content'; +const moduleName = 'npm-ui.content' + , fileSystem = require('fs') + , Path = require('path'); angular.module(moduleName, [ npmApi ]) -.controller('ContentController', /*@ngInject*/ function ContentController($state, $stateParams, $rootScope, $scope, $log, npmGlobal, npm) { +.controller('ContentController', /*@ngInject*/ function ContentController($rootScope, $scope, $log) { - const unregisterShellSelectedPackages = $rootScope.$on('shell:selected-packages', (eventInformation, data) => { - //selected packages could be multiple, just get only the first one in array - if (data && - data[0]) { - //update package infos for view - this.loadPackagesInfos(data[0].name); - } - }) - , unregisterTopBarActiveLinkListener = $rootScope.$on('top-bar:active-link', (eventInfo, data) => { - //if selected package and clicked "update package" OR "install latest" OR "uninstall" + const unregisterLeftBarDeleteProjectListener = $rootScope.$on('left-bar:delete-project', (eventInfo, data) => { if (data && - data.link === '2' || - data.link === '3' || - data.link === '5') { - this.showLoadingSelectedRow = true; - } else { - this.showLoadingSelectedRow = false; + data.project && + data.project.path) { + this.closeProjectTab(data.project.path); } }) - , unregisterInstallVersionPackageListener = $rootScope.$on('top-bar:installing-version-package', () => { - //while selected package and clicked "install release" and clicked prompt dialog button "ok" (while literally installing package new release) - this.showLoadingSelectedRow = true; - }) - , unregisterInstallVersionPackageErrorListener = $rootScope.$on('top-bar:installing-version-package-error', () => { - //while selected package and clicked "install" + version, if no version available it shows an error message and we must remove active loading status from that package row in table - $scope.$apply(() => { - this.showLoadingSelectedRow = false; - }); - }) - , unregisterLeftBarDeleteSelectedProjectListener = $rootScope.$on('left-bar:delete-selected-project', () => { - - this.packageInformations = []; - this.goBackHome = true; - }) , unregisterLeftBarSelectProjectListener = $rootScope.$on('left-bar:select-project', (eventInformation, payload) => { + $log.info('Selected project payload', payload); - $log.info('Selected project payload', payload); + if (payload && + !this.tabs.includes(payload.path)) { - if (!this.loading || - this.loaded) { + this.tabs.unshift(payload.path); + this.activeTab = this.tabs[0] ? this.tabs[0] : undefined; + } else { - if (payload && - payload.path) { + this.activeTab = this.tabs[this.tabs.indexOf(payload.path)]; + } + }); - if (typeof npm.npmGlobal === 'function' && - typeof npm.npmInFolder === 'function') { + this.closeProjectTab = tab => { + if (tab) { + if (this.tabs.includes(tab)) { + this.tabs.splice(this.tabs.indexOf(tab), 1); + //show active tab + this.activeTab = this.tabs[0] ? this.tabs[0] : undefined; + } + } + }; - this.loading = true; - this.loaded = false; - this.goBackHome = false; - this.showLoadingSelectedRow = false; - this.packageViewInfos = false; + this.tabs = []; - let npmPromise; + $scope.$on('$destroy', () => { + unregisterLeftBarSelectProjectListener(); + unregisterLeftBarDeleteProjectListener(); + }); +}) +.directive('npmTabs', /*@ngInject*/ function npmTabs($rootScope, $log, npmGlobal, npm, errorsService, loadingFactory, notificationFactory) { + return (scope, element, attrs) => { - if (payload.path === npmGlobal) { - this.isGlobalProject = true; - npmPromise = npm.npmGlobal(); - } else { - this.isGlobalProject = false; - npmPromise = npm.npmInFolder(payload.path); - } + const tabIdentifierPath = attrs.npmTabId + , unregisterLeftBarSelectProjectListener = $rootScope.$on('left-bar:select-project', (eventInfo, payload) => { - npmPromise.then(npmInFolder => - npmInFolder.listOutdated() - .then(infos => { + if (payload && + payload.path && + payload.path === tabIdentifierPath) { + scope.listPackages(); + } + }) + , unregisterOnContentPackageInfosListener = $rootScope.$on('content:selected-package-info', (eventInfo, data) => { + if (data && + data.info) { + scope.$apply(() => { + scope.selectedPackageViewInfos = data.info; + }); + } else { + scope.$apply(() => { + scope.selectedPackageViewInfos = false; + }); + } + }) + , unregisterInstallVersionPackageListener = $rootScope.$on('content:installing-version-package', () => { + //while selected package and clicked "install release" and clicked prompt dialog button "ok" (while literally installing package new release) + scope.showLoadingSelectedRow = true; + }) + , unregisterInstallVersionPackageErrorListener = $rootScope.$on('content:installing-version-package-error', () => { + //while selected package and clicked "install" + version, if no version available it shows an error message and we must remove active loading status from that package row in table + scope.$apply(() => { + scope.showLoadingSelectedRow = false; + }); + }) + , unregisterLeftBarDeleteSelectedProjectListener = $rootScope.$on('left-bar:delete-selected-project', () => { + scope.packageInformations = []; + scope.goBackHome = true; + }); - $scope.$apply(() => { - this.packageInformations = infos; - this.tableOrderBy = ['name']; - this.loaded = true; - }); - }) - .catch(error => { + scope.installPackage = (pkgs, packageKind) => { + if (pkgs && + pkgs.length > 0 && + !scope.performingAction) { - $scope.$apply(() => { - this.packageInformations = false; - }); - $log.error(error); - })) - .catch(error => { + scope.performingAction = true; + scope.installingPackage = true; + + let promiseSequence = Promise.resolve() + , choosedPackageKind + , npmPromise + , launchPackagesInstallation = () => { - $scope.$apply(() => { - this.packageInformations = false; + pkgs.forEach(pkg => { + + promiseSequence = promiseSequence.then(() => { + return npmPromise.catch(error => { + errorsService.showErrorBox('Error', `Configuring npm for installing ${pkg.name}...`, error); + }) + .then(npmInFolder => { + $log.info(`Now installing ${pkg.name}...`); + return npmInFolder.install({ + 'name': pkg.name, + 'kind': choosedPackageKind + }, pkg.version).then(() => { + $log.info(`Installed ${pkg.name}...`); + }).catch(error => { + errorsService.showErrorBox('Error', `Installing ${pkg.name}: ${error}`); + }); }); - $log.error(`Error on npmPomise, content.js: ${error}`); + }); + }); + + scope.showLoadingSelectedRow = true; + promiseSequence.then(() => { + scope.$apply(() => { + scope.showLoadingSelectedRow = false; + scope.performingAction = false; + scope.installingPackage = false; + }); + $log.info('Finished installing packages.'); + notificationFactory.notify('Finished installing packages.'); + scope.listPackages(); + }).catch(error => { + scope.$apply(() => { + scope.showLoadingSelectedRow = false; + scope.performingAction = false; + scope.installingPackage = false; + }); + errorsService.handleError('Error installing packages.', error); + scope.listPackages(); + }); + }; + + if (packageKind) { + choosedPackageKind = 'dev'; + } + + if (tabIdentifierPath === npmGlobal) { + + npmPromise = npm.npmGlobal(); + } else { + + npmPromise = npm.npmInFolder(tabIdentifierPath); + } + //if not global install + if (tabIdentifierPath === npmGlobal) { + launchPackagesInstallation(); + } else { + //read package.json and create it if project is an empty folder + fileSystem.readFile(Path.join(tabIdentifierPath, 'package.json'), 'utf8', (err, packageJson) => { + if (err || + !packageJson || + (packageJson && + JSON.parse(packageJson).length <= 0 && + packageJson !== '{}')) { + + $log.info(`Writing a package.json file in folder ${tabIdentifierPath}`); + //create package.json if not exist or if exist and not well formatted + fileSystem.writeFile(Path.join(tabIdentifierPath, 'package.json'), '{"name": "ndm-created-project", "description": "add-a-description", "license": "add-a-license", "respository": "add-a-repository"}', error => { + if (error) { + launchPackagesInstallation(); + $log.warn(`Unable to create package.json in: ${tabIdentifierPath}`); + } else { + launchPackagesInstallation(); + $log.info(`Initialized a package.json file in ${tabIdentifierPath}`); + } }); } else { - $log.error('npm.npmGlobal or npm.infolder are not functions ready probably :/ :/ !? :/ content.js'); + launchPackagesInstallation(); } - } else { - $log.error('Path is missing in payload, content.js:', payload); - } + }); } - }); + } + }; - this.loadPackagesInfos = packageName => { - let folder = '/'; //not really needed probably + scope.updatePackage = currentSelectedPackages => { + if (!scope.performingAction) { + scope.performingAction = true; - this.packageViewInfos = false; + let promiseSequence = Promise.resolve() + , npmPromise; + + if (tabIdentifierPath === npmGlobal) { + + npmPromise = npm.npmGlobal(); + } else { + + npmPromise = npm.npmInFolder(tabIdentifierPath); + } - npm.npmInFolder(folder).then(npmInFolder => { - npmInFolder.view(packageName).then(packageInfos => { - try { - $scope.$apply(() => { - this.packageViewInfos = packageInfos[Object.keys(packageInfos)[0]]; + currentSelectedPackages.forEach(pkg => { + + promiseSequence = promiseSequence.then(() => { + return npmPromise.then(npmInFolder => { + $log.info(`Preparing to update ${pkg.name}...`); + return npmInFolder.update(pkg).then(() => { + $log.info(`Updated ${pkg.name}...`); + }).catch(error => { + errorsService.handleError(`Error updating ${pkg.name}...`, error); + }); + }).catch(error => { + errorsService.handleError(`Error preparing to update ${pkg.name}...`, error); + }); + }); + }); + + scope.showLoadingSelectedRow = true; + promiseSequence.then(() => { + notificationFactory.notify('Finished updating selected packages.'); + $log.info('Updated all the selected packages.'); + scope.$apply(() => { + scope.performingAction = false; + scope.showLoadingSelectedRow = false; }); - } catch (e) { - $scope.$apply(() => { - this.packageViewInfos = false; + scope.listPackages(); + }).catch(error => { + scope.$apply(() => { + scope.performingAction = false; + scope.showLoadingSelectedRow = false; }); - $log.warn(e); + scope.listPackages(); + errorsService.handleError('Error updating package.', error); + }); + } + }; + + scope.installLatest = currentSelectedPackages => { + if (!scope.performingAction) { + scope.performingAction = true; + + let npmPromise + , promiseSequence = Promise.resolve(); + + if (tabIdentifierPath === npmGlobal) { + + npmPromise = npm.npmGlobal(); + } else { + + npmPromise = npm.npmInFolder(tabIdentifierPath); } - $rootScope.$emit('content:selected-package-info', {'package': packageName, 'info': this.packageViewInfos}); + + currentSelectedPackages.forEach(pkg => { + + promiseSequence = promiseSequence.then(() => { + return npmPromise.then(npmInFolder => { + $log.info(`Preparing to update latest ${pkg.name}...`); + return npmInFolder.installLatest(pkg).then(() => { + $log.info(`Updated to latest ${pkg.name}...`); + }).catch(error => { + errorsService.handleError(`Error updating to latest ${pkg.name}...`, error); + }); + }).catch(error => { + errorsService.handleError(`Error preparing to update to latest ${pkg.name}...`, error); + }); + }); + }); + + scope.showLoadingSelectedRow = true; + promiseSequence.then(() => { + scope.$apply(() => { + scope.showLoadingSelectedRow = false; + scope.performingAction = false; + }); + $log.info('Updated all the selected packages to latest versions.'); + notificationFactory.notify('Updated all the selected packages to latest versions.'); + scope.listPackages(); + }) + .catch(error => { + scope.$apply(() => { + scope.showLoadingSelectedRow = false; + scope.performingAction = false; + }); + scope.listPackages(); + errorsService.handleError('Error updating to latest package version.', error); + }); + } + }; + + scope.uninstallPackage = currentSelectedPackages => { + if (!scope.performingAction) { + + scope.performingAction = true; + scope.showLoadingSelectedRow = true; + + let promiseSequence = Promise.resolve() + , npmPromise; + + if (tabIdentifierPath === npmGlobal) { + + npmPromise = npm.npmGlobal(); + } else { + + npmPromise = npm.npmInFolder(tabIdentifierPath); + } + + currentSelectedPackages.forEach(pkg => { + + promiseSequence = promiseSequence.then(() => { + return npmPromise.then(npmInFolder => { + $log.info(`Preparing to uninstall ${pkg.name}...`); + return npmInFolder.rm(pkg).then(() => { + if (tabIdentifierPath === npmGlobal) { + $log.info(`Uninstalled ${pkg.name} from globals...`); + } else { + $log.info(`Uninstalled ${pkg.name} now pruning...`); + return npmInFolder.prune().then(() => { + $log.info(`Uninstalled and Pruned ${pkg.name}...`); + }); + } + }).catch(error => { + errorsService.handleError(`Error uninstalling ${pkg.name}...`, error); + }); + }).catch(error => { + errorsService.handleError(`Error preparing to uninstall ${pkg.name}...`, error); + }); + }); + }); + + promiseSequence.then(() => { + scope.$apply(() => { + scope.showLoadingSelectedRow = false; + scope.performingAction = false; + }); + scope.listPackages(); + $log.info('Uninstalled all the selected packages.'); + notificationFactory.notify('Uninstalled all the selected packages.'); + }).catch(error => { + scope.$apply(() => { + scope.showLoadingSelectedRow = false; + scope.performingAction = false; + }); + scope.listPackages(); + errorsService.handleError('Error uninstalling package.', error); + }); + } + }; + + scope.installVersionPackage = (currentSelectedPackage, specificVersion) => { + if (specificVersion && + !scope.performingAction) { + + scope.performingAction = true; + scope.showLoadingSelectedRow = true; + + let npmPromise; + + if (tabIdentifierPath === npmGlobal) { + + npmPromise = npm.npmGlobal(); + } else { + + npmPromise = npm.npmInFolder(tabIdentifierPath); + } + + $rootScope.$emit('content:installing-version-package'); + npmPromise.catch(error => { + scope.installingPackageVersion = false; + scope.performingAction = false; + $rootScope.$emit('content:installing-version-package-error'); + scope.listPackages(); + errorsService.showErrorBox('Error', `Error configuring npm for installing ${currentSelectedPackage.name}: ${error}`); + }).then(npmInFolder => { + npmInFolder.install(currentSelectedPackage, specificVersion) + .then(() => { + scope.installingPackageVersion = false; + scope.performingAction = false; + notificationFactory.notify(`Finished installing ${currentSelectedPackage.name}@${specificVersion}`); + scope.listPackages(); + }).catch(error => { + scope.$apply(() => { + scope.installingPackageVersion = false; + scope.performingAction = false; + }); + $rootScope.$emit('content:installing-version-package-error'); + scope.listPackages(); + errorsService.showErrorBox('Error', `Error installing ${currentSelectedPackage.name}@${specificVersion}: ${error}`); + }); + }); + } + }; + + scope.loadPackagesInfos = packageName => { + let folder = '/'; //not really needed probably + + scope.packageViewInfos = false; + scope.selectedPackageViewInfos = false; + + npm.npmInFolder(folder).then(npmInFolder => { + npmInFolder.view(packageName).then(packageInfos => { + try { + scope.$apply(() => { + scope.packageViewInfos = packageInfos[Object.keys(packageInfos)[0]]; + }); + } catch (e) { + scope.$apply(() => { + scope.packageViewInfos = false; + }); + $log.warn(e); + } + $rootScope.$emit('content:selected-package-info', {'package': packageName, 'info': scope.packageViewInfos}); + }).catch(err => { + scope.$apply(() => { + scope.packageViewInfos = false; + }); + $log.warn(`Problem with npm view ${packageName} in folder ${folder}`, err); + }); }).catch(err => { - $scope.$apply(() => { - this.packageViewInfos = false; + scope.$apply(() => { + scope.packageViewInfos = false; }); - $log.warn(`Problem with npm view ${packageName} in folder ${folder}`, err); + $log.warn(`Problem configuring npm for $ npm view ${packageName} in folder ${folder}`, err); }); - }).catch(err => { - $scope.$apply(() => { - this.packageViewInfos = false; - }); - $log.warn(`Problem configuring npm for $ npm view ${packageName} in folder ${folder}`, err); - }); - }; + }; + + scope.selectPackages = packages => { + scope.currentSelectedPackages = packages; + scope.showMenuButtons = true; + scope.loadPackagesInfos(packages[0].name); + }; + + scope.sortTableBy = by => { + if (!scope.tableOrderBy.includes(by) && + !scope.tableOrderBy.includes(`-${by}`)) { + scope.tableOrderBy.unshift(by); + } else if (scope.tableOrderBy.includes(by) && + !scope.tableOrderBy.includes(`-${by}`)) { + scope.tableOrderBy.splice(scope.tableOrderBy.indexOf(by), 1); + scope.tableOrderBy.unshift(`-${by}`); + } else if (scope.tableOrderBy.includes(`-${by}`)) { + scope.tableOrderBy.splice(scope.tableOrderBy.indexOf(by), 1); + } + }; + + scope.listPackages = () => { + if (typeof npm.npmGlobal === 'function' && + typeof npm.npmInFolder === 'function') { + + scope.$evalAsync(() => { + scope.loading = true; + scope.loaded = false; + scope.goBackHome = false; + scope.showLoadingSelectedRow = false; + scope.packageViewInfos = false; + scope.packageInformations = []; + scope.currentSelectedPackages = []; + scope.showMenuButtons = false; + }); + + let npmPromise; + + if (tabIdentifierPath === npmGlobal) { + scope.isGlobalProject = true; + npmPromise = npm.npmGlobal(); + } else { + scope.isGlobalProject = false; + npmPromise = npm.npmInFolder(tabIdentifierPath); + } - this.sortTableBy = by => { - if (!this.tableOrderBy.includes(by) && - !this.tableOrderBy.includes(`-${by}`)) { - this.tableOrderBy.unshift(by); - } else if (this.tableOrderBy.includes(by) && - !this.tableOrderBy.includes(`-${by}`)) { - this.tableOrderBy.splice(this.tableOrderBy.indexOf(by), 1); - this.tableOrderBy.unshift(`-${by}`); - } else if (this.tableOrderBy.includes(`-${by}`)) { - this.tableOrderBy.splice(this.tableOrderBy.indexOf(by), 1); + npmPromise.then(npmInFolder => + npmInFolder.listOutdated() + .then(infos => { + + scope.$apply(() => { + scope.packageInformations = infos; + scope.tableOrderBy = ['name']; + scope.loaded = true; + scope.loading = false; + }); + }) + .catch(error => { + + scope.$apply(() => { + scope.packageInformations = false; + scope.loaded = true; + scope.loading = false; + }); + $log.error(error); + })) + .catch(error => { + + scope.$apply(() => { + scope.packageInformations = false; + scope.loaded = true; + scope.loading = false; + }); + $log.error(`Error on npmPomise, content.js: ${error}`); + }); + } else { + $log.error('npm.npmGlobal or npm.infolder are not functions ready probably :/ :/ !? :/ content.js'); + } + }; + //array of selected packages in table, we must init the array + scope.selectedPackages = []; + + if (tabIdentifierPath) { + + scope.listPackages(); + } else { + $log.error('Path for tab identifier is missing'); } - }; - //array of selected packages in table, we must init the array - this.selectedPackages = []; - - if ($stateParams && - $stateParams.project) { - $rootScope.$emit('left-bar:select-project', { - 'path': $stateParams.project.path, - 'rawData': $stateParams.project - }); - } - $scope.$on('$destroy', () => { - unregisterInstallVersionPackageListener(); - unregisterInstallVersionPackageErrorListener(); - unregisterTopBarActiveLinkListener(); - unregisterLeftBarDeleteSelectedProjectListener(); - unregisterLeftBarSelectProjectListener(); - unregisterShellSelectedPackages(); - }); + scope.$on('$destroy', () => { + + unregisterInstallVersionPackageListener(); + unregisterInstallVersionPackageErrorListener(); + unregisterLeftBarSelectProjectListener(); + unregisterLeftBarDeleteSelectedProjectListener(); + unregisterOnContentPackageInfosListener(); + }); + }; }); export default moduleName; diff --git a/lib/js/interface/left.js b/lib/js/interface/left.js index 7069744e..b6c543d5 100644 --- a/lib/js/interface/left.js +++ b/lib/js/interface/left.js @@ -9,7 +9,7 @@ const moduleName = 'npm-ui.left-bar' , rmRf = require('rimraf'); angular.module(moduleName, []) - .controller('LeftBarController', /*@ngInject*/ function LeftBarController($state, $rootScope, $scope, $window, $filter, $log, npm, npmGlobal, errorsService, loadingFactory, notificationFactory, appHistoryFile) { + .controller('LeftBarController', /*@ngInject*/ function LeftBarController($rootScope, $scope, $window, $filter, $log, npm, npmGlobal, errorsService, loadingFactory, notificationFactory, appHistoryFile) { const unregisterOnTopBarActiveLink = $rootScope.$on('top-bar:active-link', (eventInfo, data) => { if (data && @@ -304,14 +304,6 @@ angular.module(moduleName, []) delete this.npmPruningProjects[item]; }; - this.selectGlobal = () => { - - this.selectedProject = npmGlobal; - this.global = true; - - $state.transitionTo('project', {'project': {'path': this.selectedProject}}, {'reload': true}); - }; - this.deleteSnapshot = () => { const projectKey = this.rightClickedProject.path @@ -686,14 +678,25 @@ angular.module(moduleName, []) this.selectProject = (item, event) => { if (item) { - this.global = false; this.selectedProject = item; - $state.transitionTo('project', {'project': item}, {'reload': true}); + $rootScope.$emit('left-bar:select-project', { + 'path': this.selectedProject.path + }); } event.stopPropagation();//prevent folder deletion }; + this.selectGlobal = () => { + + this.selectedProject = npmGlobal; + this.global = true; + $rootScope.$emit('left-bar:select-project', { + 'path': npmGlobal, + 'isGlobal': true + }); + }; + this.deleteProject = (item, event) => { loadingFactory.finished(); diff --git a/lib/js/interface/shell.js b/lib/js/interface/shell.js index 86582876..d59b12d4 100644 --- a/lib/js/interface/shell.js +++ b/lib/js/interface/shell.js @@ -56,6 +56,14 @@ angular.module(moduleName, [ this.projects.unshift(newProject); assets.projects.save(this.projects); } + //automatically open the first project + if (folder && + files[0] && + !files[1]) { + $rootScope.$emit('left-bar:select-project', { + 'path': newDir + }); + } } }); }); @@ -63,7 +71,7 @@ angular.module(moduleName, [ } , unregisterNpmReady = $rootScope.$on('npm:ready', () => { //is npm installed globally? - npm.isNpmInstalled().then(() => { + npm.isNpmGloballyInstalled().then(() => { //if it's ok ping registry this.checkRegistryStatus(); this.updateNpmBadgeVersion(); @@ -98,18 +106,6 @@ angular.module(moduleName, [ //when user wants to add new project directly from OS menu this.openChooser(); }) - , unregisterOnContentPackageInfosListener = $rootScope.$on('content:selected-package-info', (eventInfo, data) => { - if (data && - data.info) { - $scope.$apply(() => { - this.selectedPackageViewInfos = data.info; - }); - } else { - $scope.$apply(() => { - this.selectedPackageViewInfos = false; - }); - } - }) , unregisterDragAndDropListener = $rootScope.$on('shell:file-drop', (eventInfo, data) => { if (data && data.dataTransfer && @@ -156,7 +152,6 @@ angular.module(moduleName, [ , unregisterLeftBarSelectProjectListener = $rootScope.$on('left-bar:select-project', (eventInfo, data) => { this.showMenuButtons = false; - this.currentSelectedPackages = []; if (data && data.path === npmGlobal) { @@ -300,6 +295,7 @@ angular.module(moduleName, [ } }; + this.runDoctor = () => { if (!this.runningDoctor) { @@ -354,13 +350,6 @@ angular.module(moduleName, [ }); }; - this.selectPackages = packages => { - - this.currentSelectedPackages = packages; - this.showMenuButtons = true; - $rootScope.$emit('shell:selected-packages', packages); - }; - this.openBrowserLink = url => { shell.openExternal(url); @@ -370,19 +359,9 @@ angular.module(moduleName, [ if (!this.loadingRegistryStatus) { this.loadingRegistryStatus = true; - - npm.pingRegistry().then(response => { - if (response && - response.trim() === '{}') { - $scope.$apply(() => { - this.registryStatus = true; - }); - } else { - $scope.$apply(() => { - this.registryStatus = false; - }); - } + npm.pingRegistry().then(() => { $scope.$apply(() => { + this.registryStatus = true; this.loadingRegistryStatus = false; }); }).catch(err => { @@ -398,17 +377,32 @@ angular.module(moduleName, [ this.updateNpmBadgeVersion = () => { if (!this.updatingVersionBadge) { this.updatingVersionBadge = true; - npm.getNpmOutdatedVersion().then(data => { - if (data.npm) { + npm.outdatedGlobalVersion().then(data => { + if (data && + data.length > 0) { + + const globalNpm = data + .filter(element => element.name === 'npm') + .reduce((prev, current) => { + + if (current.name === 'name') { + return current; + } + }); + $scope.$apply(() => { - this.npmLatestVersionBadge = data.npm.latest || undefined; - this.npmCurrentVersionBadge = data.npm.current || undefined; + + this.npmLatestVersionBadge = globalNpm.latest || undefined; + this.npmCurrentVersionBadge = globalNpm.current || undefined; this.updatingVersionBadge = false; }); } else { + //since npm outdated npm does not return the current if up to date npm.getNpmVersion().then(version => { + $scope.$apply(() => { + this.npmLatestVersionBadge = version || undefined; this.npmCurrentVersionBadge = version || undefined; this.updatingVersionBadge = false; @@ -420,12 +414,12 @@ angular.module(moduleName, [ }; $rootScope.$on('$destroy', () => { + unregisterNpmReady(); unregisterOnLeftBarEditProject(); unregisterOnLeftBarOpenHistory(); unregisterOnMenuAddProjectFolder(); unregisterNpmGlobalPrivilegeCheckResult(); - unregisterOnContentPackageInfosListener(); unregisterLeftBarSelectProjectListener(); unregisterLeftBarDeleteProjectListener(); unregisterLeftBarUnshrinkwrapProjectListener(); diff --git a/lib/js/interface/top.js b/lib/js/interface/top.js index d0ff63be..e0ad7fef 100644 --- a/lib/js/interface/top.js +++ b/lib/js/interface/top.js @@ -1,348 +1,87 @@ -/*global require */ +/*global */ import angular from 'angular'; -const moduleName = 'npm-ui.top-menu' - , fileSystem = require('fs') - , Path = require('path'); +const moduleName = 'npm-ui.top-menu'; angular.module(moduleName, []) -.controller('TopMenuController', /*@ngInject*/ function TopMenuController($document, $rootScope, $scope, $log, $timeout, npm, npmGlobal, loadingFactory, notificationFactory, errorsService) { +.directive('topMenu', /*@ngInject*/ function TopMenuController($document, $rootScope, $log, $timeout, npm) { + return (scope, element, attrs) => { - let searchTimeout //debounce search + let searchTimeout //debounce search , prevSearchKeyword; - const unregisterLeftBarSelectProjectListener = $rootScope.$on('left-bar:select-project', (eventInformation, data) => { - if (data && - data.path) { - this.projectPath = data.path; - } - }); - this.installPackage = (pkgs, packageKind) => { - if (pkgs && - pkgs.length > 0 && - !this.installingPackage) { + const topMenuIdentifierPath = attrs.topMenuProjectPathId; - let promiseSequence = Promise.resolve() - , npmPromise - , choosedPackageKind - , installLaunch = () => { + scope.destroyActiveClickedLink = () => { + scope.activeLink = undefined; + }; - pkgs.forEach(pkg => { - - promiseSequence = promiseSequence.then(() => { - return npmPromise.catch(error => { - errorsService.showErrorBox('Error', `Configuring npm for installing ${pkg.name}...`, error); - }) - .then(npmInFolder => { - $log.info(`Now installing ${pkg.name}...`); - return npmInFolder.install({ - 'name': pkg.name, - 'kind': choosedPackageKind - }, pkg.version).then(() => { - $log.info(`Installed ${pkg.name}...`); - }).catch(error => { - errorsService.showErrorBox('Error', `Installing ${pkg.name}: ${error}`); - }); - }); - }); - }); - - promiseSequence.then(() => { - loadingFactory.finished(); - $log.info('Finished installing packages.'); - notificationFactory.notify('Finished installing packages.'); - $scope.$apply(() => { - this.installingPackage = false; - }); - $rootScope.$emit('left-bar:select-project', { - 'path': this.projectPath - }); - }).catch(error => { - $scope.$apply(() => { - this.installingPackage = false; - }); - loadingFactory.finished(); - errorsService.handleError('Error installing packages.', error); - }); - }; - - this.showPackageInstallPrompt = false; - this.installingPackage = true; - loadingFactory.loading(); - - if (packageKind) { - choosedPackageKind = 'dev'; - } - - if (this.projectPath === npmGlobal) { - - npmPromise = npm.npmGlobal(); - } else { - - npmPromise = npm.npmInFolder(this.projectPath); - } - //if not global install - if (this.projectPath === npmGlobal) { - installLaunch(); + scope.activeClickedLink = activeLink => { + if ((activeLink === '1' || activeLink === '4') && + scope.activeLink === activeLink) { + //toggle prompts show/hide + scope.activeLink = false; } else { - //read package.json and create it if project is an empty folder - fileSystem.readFile(Path.join(this.projectPath, 'package.json'), 'utf8', (err, packageJson) => { - if (err || - !packageJson || - (packageJson && - JSON.parse(packageJson).length <= 0 && - packageJson !== '{}')) { - $log.info(`Writing a package.json file in folder ${this.projectPath}`); - //create package.json if not exist or if exist and not well formatted - fileSystem.writeFile(Path.join(this.projectPath, 'package.json'), '{"name": "ndm-created-project", "description": "add-a-description", "license": "add-a-license", "respository": "add-a-repository"}', error => { - if (error) { - installLaunch(); - $log.warn(`Unable to create package.json in: ${this.projectPath}`); - } else { - installLaunch(); - $log.info(`Initialized a package.json file in ${this.projectPath}`); - } - }); - } else { - installLaunch(); - } + scope.activeLink = activeLink; + $rootScope.$emit('top-bar:active-link', { + 'link': activeLink }); } - } - }; - - this.updatePackage = currentSelectedPackages => { - - let promiseSequence = Promise.resolve() - , npmPromise; - - loadingFactory.loading(); - - if (this.projectPath === npmGlobal) { - - npmPromise = npm.npmGlobal(); - } else { - - npmPromise = npm.npmInFolder(this.projectPath); - } - - currentSelectedPackages.forEach(pkg => { - - promiseSequence = promiseSequence.then(() => { - return npmPromise.then(npmInFolder => { - $log.info(`Preparing to update ${pkg.name}...`); - return npmInFolder.update(pkg).then(() => { - $log.info(`Updated ${pkg.name}...`); - }).catch(error => { - errorsService.handleError(`Error updating ${pkg.name}...`, error); - }); - }).catch(error => { - errorsService.handleError(`Error preparing to update ${pkg.name}...`, error); - }); - }); - }); - - promiseSequence.then(() => { - loadingFactory.finished(); - notificationFactory.notify('Finished updating selected packages.'); - $log.info('Updated all the selected packages.'); - $rootScope.$emit('left-bar:select-project', { - 'path': this.projectPath - }); - }).catch(error => { - loadingFactory.finished(); - errorsService.handleError('Error updating package.', error); - }); - }; - - this.installLatest = currentSelectedPackages => { - let npmPromise - , promiseSequence = Promise.resolve(); - - if (this.projectPath === npmGlobal) { - - npmPromise = npm.npmGlobal(); - } else { - - npmPromise = npm.npmInFolder(this.projectPath); - } - - loadingFactory.loading(); - - currentSelectedPackages.forEach(pkg => { - - promiseSequence = promiseSequence.then(() => { - return npmPromise.then(npmInFolder => { - $log.info(`Preparing to update latest ${pkg.name}...`); - return npmInFolder.installLatest(pkg).then(() => { - $log.info(`Updated to latest ${pkg.name}...`); - }).catch(error => { - errorsService.handleError(`Error updating to latest ${pkg.name}...`, error); + }; + + scope.search = keyword => { + $log.info('search', keyword); + if (keyword && + keyword.trim() !== prevSearchKeyword) { + /*eslint-disable*/ + if (searchTimeout) { + $timeout.cancel(searchTimeout); + } + prevSearchKeyword = keyword; + /*eslint-enable*/ + searchTimeout = $timeout(() => { + scope.searchingNpm = true; + scope.searchResults = []; + npm.npmInFolder(topMenuIdentifierPath).then(npmInFolder => { + npmInFolder.search(keyword).then(data => { + scope.$apply(() => { + scope.searchingNpm = false; + scope.searchResults = data; + }); + }).catch(err => { + scope.$apply(() => { + scope.searchingNpm = false; + scope.searchResults = []; + }); + $log.error('SEARCH ERROR', err); + }); }); - }).catch(error => { - errorsService.handleError(`Error preparing to update to latest ${pkg.name}...`, error); - }); - }); - }); - - promiseSequence.then(() => { - loadingFactory.finished(); - $log.info('Updated all the selected packages to latest versions.'); - notificationFactory.notify('Updated all the selected packages to latest versions.'); - $rootScope.$emit('left-bar:select-project', { - 'path': this.projectPath - }); - }) - .catch(error => { - loadingFactory.finished(); - errorsService.handleError('Error updating to latest package version.', error); - }); - - }; - - this.installVersionPackage = (currentSelectedPackage, specificVersion) => { - if (specificVersion && - !this.installingPackageVersion) { - this.showSpecificVersionPrompt = false; - loadingFactory.loading(); - this.installingPackageVersion = true; - - let npmPromise; - - if (this.projectPath === npmGlobal) { - - npmPromise = npm.npmGlobal(); + }, 500); } else { - - npmPromise = npm.npmInFolder(this.projectPath); + scope.searchingNpm = false; + scope.searchResults = []; } - - $rootScope.$emit('top-bar:installing-version-package'); - npmPromise.catch(error => { - loadingFactory.finished(); - this.installingPackageVersion = false; - $rootScope.$emit('top-bar:installing-version-package-error'); - errorsService.showErrorBox('Error', `Error configuring npm for installing ${currentSelectedPackage.name}: ${error}`); - }).then(npmInFolder => { - npmInFolder.install(currentSelectedPackage, specificVersion) - .then(() => { - loadingFactory.finished(); - this.installingPackageVersion = false; - notificationFactory.notify(`Finished installing ${currentSelectedPackage.name}@${specificVersion}`); - $rootScope.$emit('left-bar:select-project', { - 'path': this.projectPath - }); - }).catch(error => { - loadingFactory.finished(); - $scope.$apply(() => { - this.installingPackageVersion = false; - }); - $rootScope.$emit('top-bar:installing-version-package-error'); - errorsService.showErrorBox('Error', `Error installing ${currentSelectedPackage.name}@${specificVersion}: ${error}`); - }); - }); - } - }; - - this.uninstallPackage = currentSelectedPackages => { - - let promiseSequence = Promise.resolve() - , npmPromise; - - loadingFactory.loading(); - - if (this.projectPath === npmGlobal) { - - npmPromise = npm.npmGlobal(); - } else { - - npmPromise = npm.npmInFolder(this.projectPath); - } - - currentSelectedPackages.forEach(pkg => { - - promiseSequence = promiseSequence.then(() => { - return npmPromise.then(npmInFolder => { - $log.info(`Preparing to uninstall ${pkg.name}...`); - return npmInFolder.rm(pkg).then(() => { - if (this.projectPath === npmGlobal) { - $log.info(`Uninstalled ${pkg.name} from globals...`); - } else { - $log.info(`Uninstalled ${pkg.name} now pruning...`); - return npmInFolder.prune().then(() => { - $log.info(`Uninstalled and Pruned ${pkg.name}...`); - }); - } - }).catch(error => { - errorsService.handleError(`Error uninstalling ${pkg.name}...`, error); - }); - }).catch(error => { - errorsService.handleError(`Error preparing to uninstall ${pkg.name}...`, error); - }); + }; + + scope.searchChoosePackage = pkgName => { + //update digits in input + scope.$evalAsync(() => { + scope.packageName[scope.packageName.length - 1].name = pkgName; + scope.searchResults = []; + $log.warn(pkgName, scope.packageName); + //communicate to ng-tag-input to update itself and model + $rootScope.$emit('top-menu:search-choosen-package', {'data': scope.packageName, 'tabPath': topMenuIdentifierPath}); }); - }); + }; - promiseSequence.then(() => { - loadingFactory.finished(); - $log.info('Uninstalled all the selected packages.'); - notificationFactory.notify('Uninstalled all the selected packages.'); - $rootScope.$emit('left-bar:select-project', { - 'path': this.projectPath - }); - }).catch(error => { - loadingFactory.finished(); - errorsService.handleError('Error uninstalling package.', error); - }); - }; - - this.search = keyword => { - $log.info('search', keyword); - if (keyword && - keyword.trim() !== prevSearchKeyword) { - /*eslint-disable*/ - if (searchTimeout) { - $timeout.cancel(searchTimeout); - } - prevSearchKeyword = keyword; - /*eslint-enable*/ - searchTimeout = $timeout(() => { - this.searchingNpm = true; - this.searchResults = []; - npm.npmInFolder(this.projectPath).then(npmInFolder => { - npmInFolder.search(keyword).then(data => { - $scope.$apply(() => { - this.searchingNpm = false; - this.searchResults = data; - }); - }).catch(err => { - $scope.$apply(() => { - this.searchingNpm = false; - this.searchResults = []; - }); - $log.error('SEARCH ERROR', err); - }); - }); - }, 500); - } else { - this.searchingNpm = false; - this.searchResults = []; - } - }; + scope.hideInstallPrompt = () => { + scope.showInstallPrompt = false; + }; - this.searchChoosePackage = packageName => { - //update digits in input - $scope.$evalAsync(() => { - this.packageName[this.packageName.length - 1].name = packageName; - this.searchResults = []; - //communicate to ng-tag-input to update itself and model - $rootScope.$emit('top-menu:search-choosen-package', this.packageName); - this.searchResults = []; - }); + scope.hideInstallVersionPrompt = () => { + scope.showSpecificVersionPrompt = false; + }; }; - - $rootScope.$on('$destroy', () => { - unregisterLeftBarSelectProjectListener(); - }); }); export default moduleName; diff --git a/lib/js/loading.js b/lib/js/loading.js index 45ebc905..f934e52d 100644 --- a/lib/js/loading.js +++ b/lib/js/loading.js @@ -9,7 +9,7 @@ angular.module(moduleName, []) bodyElement.addClass('ready'); } , loading = () => { - bodyElement.addClass('loading'); + // bodyElement.addClass('loading'); } , finished = () => { bodyElement.removeClass('loading'); @@ -29,54 +29,27 @@ angular.module(moduleName, []) appReady }; }) - .directive('npmLoading', /*@ngInject*/ ($rootScope, loadingFactory) => { + .directive('npmLoading', /*@ngInject*/ $rootScope => { return { 'scope': true, 'restrict': 'A', 'templateUrl': 'npm-update-log.html', 'controller': /*@ngInject*/ function NpmLoadingController($scope) { - let unregisterOnNpmLogs; - - const manageNpmLogs = (eventInfos, payload) => { - if (payload) { + const unregisterOnNpmLogs = $rootScope.$on('npm:log:log', (eventInfo, npmLog) => { $rootScope.$apply(() => { - - $scope.log.logs.push(payload); + if (npmLog.type === 'installLatest' && + npmLog.data) { + $scope.log.logs.push(npmLog.data); + } }); - } - } - , loading = () => { - - $scope.$apply(() => { - this.nowDatetime = new Date(); - this.logs = []; - this.logsFinished = undefined; }); - loadingFactory.loading(); - unregisterOnNpmLogs = $rootScope.$on('npm:log', manageNpmLogs); - } - , finished = () => { - - $scope.$apply(() => { - - this.logsFinished = true; - }); - - loadingFactory.finished(); - unregisterOnNpmLogs(); - } - , unregisterOnStart = $rootScope.$on('npm:log:start', loading) - , unregisterOnStop = $rootScope.$on('npm:log:stop', finished); - this.logs = []; $scope.$on('$destroy', () => { - - unregisterOnStart(); - unregisterOnStop(); + unregisterOnNpmLogs(); }); }, 'controllerAs': 'log' diff --git a/lib/js/npm/npm-api.js b/lib/js/npm/npm-api.js index f528d1c5..011c830e 100644 --- a/lib/js/npm/npm-api.js +++ b/lib/js/npm/npm-api.js @@ -1,57 +1,128 @@ -/*global require navigator process Buffer*/ +/*global require navigator process,__dirname*/ import angular from 'angular'; -import NpmOperations from './npm-operations.js'; const moduleName = 'npm-api.service' - , npm = require('npm') - , stream = require('stream') , fs = require('fs') - , cp = require('child_process') - , exec = cp.exec; + , path = require('path') + , cp = require('child_process'); angular.module(moduleName, []) .service('npm', /*@ngInject*/ function NpmService($rootScope, $log, errorsService) { - const writable = new stream.Writable({ - 'write': (chunk, encoding, next) => { - const thisLogBuffer = new Buffer(chunk) - , thisLog = thisLogBuffer - .toString() - .trim(); + const configureNpm = (folder, isGlobal) => new Promise(resolve => { + const forkFactory = (command, param1, param2) => new Promise((forkFactoryResolved, forkFactoryRejected) => { + const theParams = [folder, isGlobal] + .concat([command, param1, param2]) + .map(element => { - if (thisLog) { + if (Object.prototype.toString.call(element) === '[object Object]') { - $rootScope.$emit('npm:log', thisLog); - } + return JSON.stringify(element); + } - next(); - } - }) - , npmDefaultConfiguration = { - 'loglevel': 'info', - 'progress': false, - 'logstream': writable - } - , configureNpm = (folder, isGlobal) => new Promise((resolve, reject) => { + return element; + }) + , child = cp.fork(path.resolve(__dirname, 'npm-runner.js'), theParams, { + 'cwd': __dirname, + 'silent': true + }); - if (!npm.config.loaded) { + child.stdout.on('data', data => { + $log.info(`stdout: ${data}`); + }); - return npm.load(Object.assign({}, npmDefaultConfiguration), (err, configuredNpm) => { - if (err) { + child.stderr.on('data', data => { + forkFactoryRejected(data); + $log.error(`stderr: ${data}`); + }); - return reject(err); - } + child.on('close', code => { + $rootScope.$emit('npm:log:end', { + 'type': command, + 'message': code + }); + $log.info(`End of command ${command}`); + $log.info(`child process exited with code ${code}`); + }); + + child.on('message', message => { + + if (command === message.type) { - const npmOperations = new NpmOperations(folder, configuredNpm, isGlobal); + forkFactoryResolved(message.payload); + } else if (message.type === 'log') { - return resolve(npmOperations); + $rootScope.$emit('npm:log:log', { + 'type': command, + 'data': message.payload + }); + } else { + + $log.debug(message); + } }); - } - const npmOperations = new NpmOperations(folder, npm, isGlobal); + }); - return resolve(npmOperations); + resolve({ + 'ping': () => { + return forkFactory('ping'); + }, + 'launchInstall': () => { + return forkFactory('launchInstall'); + }, + 'search': keyword => { + return forkFactory('search', keyword); + }, + 'run': scriptName => { + return forkFactory('run', scriptName); + }, + 'view': packageName => { + return forkFactory('view', packageName); + }, + 'build': buildFolder => { + return forkFactory('build', buildFolder); + }, + 'rebuild': () => { + return forkFactory('rebuild'); + }, + 'install': (dependency, version) => { + return forkFactory('install', dependency, version); + }, + 'installLatest': dependency => { + return forkFactory('installLatest', dependency); + }, + 'update': dependency => { + return forkFactory('update', dependency); + }, + 'rm': dependency => { + return forkFactory('rm', dependency); + }, + 'listOutdated': () => { + return forkFactory('listOutdated'); + }, + 'outdated': () => { + return forkFactory('outdated'); + }, + 'prune': () => { + return forkFactory('prune'); + }, + 'dedupe': () => { + return forkFactory('dedupe'); + }, + 'list': () => { + return forkFactory('list'); + }, + 'shrinkwrap': () => { + return forkFactory('shrinkwrap'); + }, + 'doctor': () => { + return forkFactory('doctor'); + }, + 'root': () => { + return forkFactory('root'); + } + }); }) , getNpmVersion = () => new Promise(resolve => { - exec('npm -v', (err, stdout, stderr) => { - + cp.exec('npm -v', (err, stdout, stderr) => { let npmVersion; $log.warn(err, stderr); @@ -63,7 +134,7 @@ angular.module(moduleName, []) resolve(npmVersion); }); }) - , isNpmInstalled = () => new Promise((resolve, reject) => { + , isNpmGloballyInstalled = () => new Promise((resolve, reject) => { cp.exec('npm -v', err => { if (err) { reject(err); @@ -73,73 +144,32 @@ angular.module(moduleName, []) }); }) , pingRegistry = () => new Promise((resolve, reject) => { - if (navigator.onLine) { - exec('npm ping registry', (err, stdout, stderr) => { - - if (err) { - $log.warn('Ping registry', err, stderr); - reject(err); - } - - if (stdout && - stdout.length > 0) { - resolve(stdout.toString()); - } else { - reject(); - } + if (navigator.onLine) { + return configureNpm('').then(npm => { + + return npm.ping() + .then(resolve) + .catch(reject); + }) + .catch(err => { + $log.warn('Ping registry', err); + reject(err); }); - } else { - $log.warn('You are offline: unable to ping registry.'); - reject(); - } - }) - , getNpmOutdatedVersion = () => new Promise(resolve => { - exec('npm outdated npm -g --json', (err, stdout, stderr) => { - - let npmOutdatedJson = {}; - - //do not reject - $log.warn(err, stderr); - if (stdout && - stdout.length > 0) { - npmOutdatedJson = JSON.parse(stdout); - - } - resolve(npmOutdatedJson); - }); - }); -/*, changePermission = folder => new Promise((resolve, reject) => { - - if (folder === '/usr') { - - throw new Error('This strategy isn\'t the best choice'); } - sudo.exec(`chown -R $(whoami):$(whoami) ${folder}/{lib/node_modules,bin,share}`, sudoOptions, err => { - - if (err) { - - return reject(err); - } - - resolve(); - }); + $log.warn('You are offline: unable to ping registry.'); + return reject(); }) - , changeGlobalFolder = () => new Promise((resolve, reject) => { - - exec(`mkdir ~/.npm-global && - npm config set prefix \'~/.npm-global\' && - echo "export PATH=~/.npm-global/bin:$PATH" >> ~/.profile && - source ~/.profile`, (err, stdout, stderr) => { - - if (err || stderr) { - - return reject(err || stderr); - } - - resolve(); + , outdatedGlobalVersion = () => new Promise(resolve => { + this.npmGlobal() + .catch(error => errorsService.showErrorBox('Npm error', `Error during configuring npm for asking the globally installed version: ${error}`)) + .then(npmInFolder => { + + return npmInFolder.outdated() + .then(resolve) + .catch(error => errorsService.showErrorBox('Npm error', `Error asking the globally installed version: ${error}`)); }); - });*/ + }); this.updateNpmGlobally = () => { const npmLib = { @@ -173,35 +203,31 @@ angular.module(moduleName, []) //sync shell path or app will not work, yep. process.env.PATH = require('shell-path').sync(); - exec('npm root -g', (err, stdout, stderr) => { + cp.exec('npm root -g', (err, stdout, stderr) => { if (err || stderr) { throw new Error(err || stderr); } - this.npmGlobal = () => { - return configureNpm(stdout.replace('/node_modules', '').replace('\\node_modules', '').trim(), true); - }; - $rootScope.$emit('npm:ready'); - }); - - exec('npm config get prefix', (err, stdout, stderr) => { - - let libNodeModulesExt = ''; //important + const globalFolder = stdout + .replace('/node_modules', '') + .replace('\\node_modules', '') + .trim(); + let nodeModulesExt = ''; //important - if (err || stderr) { + this.npmGlobal = () => { - throw new Error(err || stderr); - } + return configureNpm(globalFolder, true); + }; if (process.platform && process.platform !== 'win32') { //on windows it doesn't exists - libNodeModulesExt = '/lib/node_modules'; + nodeModulesExt = '/node_modules'; } - fs.stat(`${stdout.trim()}${libNodeModulesExt}`, (statError, stats) => { + fs.stat(`${globalFolder}${nodeModulesExt}`, (statError, stats) => { if (statError) { @@ -243,15 +269,15 @@ angular.module(moduleName, []) }); }); }); + + $rootScope.$emit('npm:ready'); }); }); this.npmInFolder = configureNpm; - //this.changePermission = changePermission; - //this.changeGlobalFolder = changeGlobalFolder; - this.getNpmOutdatedVersion = getNpmOutdatedVersion; + this.outdatedGlobalVersion = outdatedGlobalVersion; this.getNpmVersion = getNpmVersion; - this.isNpmInstalled = isNpmInstalled; + this.isNpmGloballyInstalled = isNpmGloballyInstalled; this.pingRegistry = pingRegistry; }); diff --git a/lib/js/npm/npm-operations.js b/lib/js/npm/npm-operations.js index 4a81e87a..35b60884 100644 --- a/lib/js/npm/npm-operations.js +++ b/lib/js/npm/npm-operations.js @@ -1,7 +1,8 @@ -/* global process,fetch */ +/* global process,require */ const isGlobalSym = Symbol('isGlobal') , npmSym = Symbol('npm') , whereSym = Symbol('where') +, fetch = require('node-fetch') , swapFolderAndGlobal = function SwapFolderAndGlobal(prefix, isGlobal) { const oldPrefix = this[npmSym].config.prefix @@ -23,6 +24,19 @@ class NpmOperations { this[isGlobalSym] = isGlobal; } + ping() { + return new Promise((resolvePing, rejectPing) => { + this[npmSym].commands.ping('', err => { + if (err) { + + return rejectPing(err); + } + + return resolvePing(); + }); + }); + } + launchInstall() { return new Promise((resolveInstall, rejectInstall) => { @@ -99,9 +113,12 @@ class NpmOperations { } install(dependency, version) { + process.send(dependency); let dependencyToSubmit = dependency.name; - if (version) { + if (version && + version !== 'false' && + version !== 'undefined') { dependencyToSubmit += `@${version}`; } diff --git a/lib/js/npm/npm-runner.js b/lib/js/npm/npm-runner.js new file mode 100644 index 00000000..74861260 --- /dev/null +++ b/lib/js/npm/npm-runner.js @@ -0,0 +1,68 @@ +/*global require,process,Buffer*/ +import NpmOperations from './npm-operations.js'; + +const npm = require('npm') + , stream = require('stream') + , writable = new stream.Writable({ + 'write': (chunk, encoding, next) => { + const thisLogBuffer = new Buffer(chunk) + , thisLog = thisLogBuffer + .toString() + .trim(); + + if (thisLog) { + + process.send({ + 'type': 'log', + 'payload': thisLog + }); + } + + next(); + } + }) + , npmDefaultConfiguration = { + 'loglevel': 'info', + 'progress': false, + 'logstream': writable + } + , exec = (folder, isGlobal, command, param1, param2) => { + const confObject = Object.assign({}, + npmDefaultConfiguration, + {'prefix': folder}); + + process.send({folder, isGlobal, command, param1, param2}); + return npm.load(confObject, (err, configuredNpm) => { + if (err) { + + process.send({ + 'type': 'error', + 'payload': err + }); + } + const npmOperations = new NpmOperations(folder, configuredNpm, isGlobal); + + npmOperations[command](param1, param2).then(resolved => process.send({ + 'type': command, + 'payload': resolved + })); + }); + } + , inputs = process.argv + .slice(2) + .map(element => { + try { + + return JSON.parse(element); + } catch (err) { + + if (element === 'undefined') { + + return undefined; + } + + return element; + } + }); + +exec(...inputs); diff --git a/lib/npm-update-log.pug b/lib/npm-update-log.pug index 21ec675b..641030d8 100644 --- a/lib/npm-update-log.pug +++ b/lib/npm-update-log.pug @@ -1,7 +1,7 @@ div.dialog.dialog-window(ng-if="shell.activeLink === 'update'") div(class="prompt-window-options") - img(ng-show="!log.logsFinished", src='img/loading.svg', width='13') - i(class="fa fa-check color-primary", ng-show="log.logsFinished") + img(ng-show="shell.updatingNpm", src='img/loading.svg', width='13') + i(class="fa fa-check color-primary", ng-show="!shell.updatingNpm && log.logs") button(ng-click="shell.activeLink = false", ng-if="!shell.updatingNpm") | Close button(ng-click="shell.activeClickedLink('update'); shell.updateNpm()", ng-if="!shell.updatingNpm") diff --git a/lib/package-informations.pug b/lib/package-informations.pug index ccd125da..2e204d4f 100644 --- a/lib/package-informations.pug +++ b/lib/package-informations.pug @@ -3,47 +3,47 @@ div.table-infos-content b | Name: = " " - | {{content.packageViewInfos.name || '-'}} - div.information(title="{{content.packageViewInfos.description}}") + | {{packageViewInfos.name || '-'}} + div.information(title="{{packageViewInfos.description}}") b | Description: = " " - | {{content.packageViewInfos.description || '-' | removeHTML}} + | {{packageViewInfos.description || '-' | removeHTML}} div.information(title="Package dependencies") b | Dependencies: = " " - span(ng-repeat="(dep, value) in content.packageViewInfos.dependencies") + span(ng-repeat="(dep, value) in packageViewInfos.dependencies") a(title="Open in browser", ng-click="shell.openBrowserLink('https://npmjs.com/package/' + dep)") | {{ dep }} span(ng-if="!$last") | , = " " - span(ng-if="!content.packageViewInfos.dependencies || content.packageViewInfos.dependencies.length <= 0") + span(ng-if="!packageViewInfos.dependencies || packageViewInfos.dependencies.length <= 0") | - div.information(title="Package repository url") b | Repository: = " " - | {{content.packageViewInfos.repository.url || '-'}} + | {{packageViewInfos.repository.url || '-'}} div.information b | Issues: = " " - a(title="Open in browser", ng-if="content.packageViewInfos.bugs.url", ng-click="shell.openBrowserLink(content.packageViewInfos.bugs.url)") - | {{content.packageViewInfos.bugs.url}} - span(ng-if="!content.packageViewInfos.bugs || !content.packageViewInfos.bugs.url") + a(title="Open in browser", ng-if="packageViewInfos.bugs.url", ng-click="shell.openBrowserLink(packageViewInfos.bugs.url)") + | {{packageViewInfos.bugs.url}} + span(ng-if="!packageViewInfos.bugs || !packageViewInfos.bugs.url") | - div.information b | Url: = " " - a(title="Open in browser", ng-init="content.pkgUrlToNpmJsWebsite = 'https://npmjs.com/package/' + content.packageViewInfos.name", ng-if="content.packageViewInfos.name", ng-click="shell.openBrowserLink(content.pkgUrlToNpmJsWebsite)") - | {{ content.pkgUrlToNpmJsWebsite }} - span(ng-if="!content.packageViewInfos.name") + a(title="Open in browser", ng-init="pkgUrlToNpmJsWebsite = 'https://npmjs.com/package/' + packageViewInfos.name", ng-if="packageViewInfos.name", ng-click="shell.openBrowserLink(pkgUrlToNpmJsWebsite)") + | {{ pkgUrlToNpmJsWebsite }} + span(ng-if="!packageViewInfos.name") | - div.information b | License: = " " - | {{content.packageViewInfos.license.type || content.packageViewInfos.license || '-'}} + | {{packageViewInfos.license.type || packageViewInfos.license || '-'}} diff --git a/lib/scss/footer.scss b/lib/scss/footer.scss index 916a4ffa..b21ead06 100644 --- a/lib/scss/footer.scss +++ b/lib/scss/footer.scss @@ -60,7 +60,6 @@ } } &.npm-status { - float: right; i { &.checking { @@ -74,13 +73,15 @@ } button { - margin-left: 4px; + margin-left: 6px; + margin-right: 5px; font-size: 11.5px; line-height: 12px; padding: 0 2px 0 0; height: 19px; i { + color: $color-positive; position: relative; top: -1px; } diff --git a/lib/scss/header.scss b/lib/scss/header.scss index c644bcb5..da454d1c 100644 --- a/lib/scss/header.scss +++ b/lib/scss/header.scss @@ -1,14 +1,18 @@ .top-menu { - width: 100%; - background: $bg-light; - border-bottom: 1px solid $color-ddd; + width: calc(100% - 8px); + margin: 0 auto; + background: white; + margin-top: 4px; + margin-bottom: 2px; - .top-menu-left { - width: 200px; - } - .top-menu-right { - width: calc(100% - 200px); + &.freezed { + cursor: wait; + * { + cursor: wait; + pointer-events: none; + } } + img { width: 23px; margin-top: 4px; @@ -22,8 +26,9 @@ .button-latest { float: right; font-size: 12px; - padding-left: 2px; - margin: 4.5px 4px 0 0; + padding-left: 0; + padding-right: 3px; + margin: 1px 0 0 4px; line-height: 13px; i { @@ -36,17 +41,19 @@ .button-add-package { float: left; border: 0; - line-height: 26px; + line-height: 18px; margin: 0 auto; - padding: 0 6px 0 2px; + padding: 0 0 0 0; background: none; + margin-bottom: 3px; + border-radius: 10px; i { color: $color-primary; } - &:active { - background: $bg-muted-invisible; + &:focus { + opacity: .7; } } diff --git a/lib/scss/home.scss b/lib/scss/home.scss index 9d3b32c1..a1c57de9 100644 --- a/lib/scss/home.scss +++ b/lib/scss/home.scss @@ -1,11 +1,12 @@ .home { - min-height: 100vh; - padding-top: 58vh; + min-height: calc(100vh - 37px); + padding-top: 29vh; text-align: center; position: relative; - margin-top: -30vh; z-index: 9; background: white; + border: 1px solid #ccc; + margin: 0 auto; button { height: 21px; diff --git a/lib/scss/layout.scss b/lib/scss/layout.scss index b6342b09..9b1a9d32 100644 --- a/lib/scss/layout.scss +++ b/lib/scss/layout.scss @@ -6,20 +6,10 @@ border-radius: 2px; float: left; margin: 10px 0; - width: calc(100vw - 225px); + width: calc(100vw - 200px); height: calc(100vh - 37px); background: white; overflow: hidden; - border: 1px solid $color-ccc; - - h6 { - font-size: 11px; - line-height: 19px; - font-weight: 500; - padding: 0 5px; - margin: 0; - border-top: 1px solid $color-ccc; - } } .left-column { @@ -29,7 +19,7 @@ overflow: auto; margin: 10px 5px 10px 10px; padding: 0 15px 15px 15px; - width: 200px; + width: 175px; background: white; border: 1px solid $color-ccc; @@ -42,7 +32,6 @@ line-height: 20px; &:not(.project) { - line-height: 25px; } @@ -84,7 +73,7 @@ width: 100%; } - &.active, &:active, &:focus { + &:active, &:focus { background: $color-ddd; } @@ -105,7 +94,7 @@ height: 20px; text-align: center; position: absolute; - left: 182.5px; + left: 157.5px; i { color: $color-222; diff --git a/lib/scss/linux/index.scss b/lib/scss/linux/index.scss index 48fd2857..7aa5a5d8 100644 --- a/lib/scss/linux/index.scss +++ b/lib/scss/linux/index.scss @@ -14,4 +14,5 @@ @import '../ace-editor'; @import '../home'; @import '../updates'; +@import '../tabs'; @import 'linux'; diff --git a/lib/scss/linux/linux.scss b/lib/scss/linux/linux.scss index 236015fb..eb2d64a0 100644 --- a/lib/scss/linux/linux.scss +++ b/lib/scss/linux/linux.scss @@ -29,3 +29,12 @@ body, html { .ace_editor { font-size: 15px !important; } + +.dialog { + + select { + height: 18.5px; + bottom: 0; + top: -1px; + } +} diff --git a/lib/scss/loading.scss b/lib/scss/loading.scss index 159ee923..adaeb4f8 100644 --- a/lib/scss/loading.scss +++ b/lib/scss/loading.scss @@ -15,29 +15,6 @@ body { opacity: .6; } } - &.loading { - - &:hover { - cursor: wait; - } - - * { - cursor: wait; - pointer-events: none; - } - - .dialog { - * { - pointer-events: all; - } - } - - .footer { - * { - pointer-events: all; - } - } - } &.freezed { opacity: .5; diff --git a/lib/scss/mac/index.scss b/lib/scss/mac/index.scss index 73ad137a..4ca3b811 100644 --- a/lib/scss/mac/index.scss +++ b/lib/scss/mac/index.scss @@ -14,4 +14,5 @@ @import '../ace-editor'; @import '../home'; @import '../updates'; +@import '../tabs'; @import 'mac'; diff --git a/lib/scss/progress.scss b/lib/scss/progress.scss index bec3a370..338f63b3 100644 --- a/lib/scss/progress.scss +++ b/lib/scss/progress.scss @@ -1,6 +1,5 @@ .left-progress { - animation: loadingStripes 1.3s linear infinite; font-size: 11px; line-height: 0; width: 80%; @@ -36,6 +35,7 @@ margin-top: 6px; background: $bg-progress-secondary; border-radius: 2px; + animation: loadingStripes 1.3s ease-out infinite; } &.left-progress-minor { diff --git a/lib/scss/prompt.scss b/lib/scss/prompt.scss index cd8a5831..b8050ff3 100644 --- a/lib/scss/prompt.scss +++ b/lib/scss/prompt.scss @@ -1,19 +1,32 @@ .dialog { background: $bg-light; - border-radius: $border-radius-prompt; box-shadow: 1px .5px 3px rgba(0, 0, 0, .29), -.5px .5px .5px rgba(0, 0, 0, .2); float: none; - left: 0; + left: calc(199px + ((100vw - 199px) / 2) - 226px); padding: 3px 5px; position: absolute; - right: 0; margin: 0 auto; - width: 500px; - z-index: 9999; - top: 0; + width: 100%; + max-width: 432px; + z-index: 999999999; + top: 34px; &:not(.dialog-window) { - animation: promptSliding .13s ease; + z-index: 999; + border-radius: 1.5px 1.5px 0 0; + left: 194px; + border: 1px solid $color-ddd; + width: calc(100vw - 208px); + box-shadow: none; + max-width: none; + margin-top: 2px; + min-height: 25px; + border-bottom: 0; + padding: 0; + + form { + padding: 1px 3px; + } } i { @@ -21,11 +34,6 @@ font-size: 15px; } - .button-close-prompt { - position: relative; - bottom: -1px; - } - button { min-width: 13%; width: auto; @@ -46,9 +54,29 @@ opacity: .5; } } + &:not(.dialog-window) { button { - width: 56px; + width: 55px; + min-width: auto; + + img { + width: 13px; + } + + &.button-close-prompt { + width: 18px; + max-width: 18px; + min-width: 18px; + text-indent: -2.5px; + + i { + font-size: 11px; + left: -6.5px; + position: relative; + float: left; + } + } } } @@ -71,12 +99,12 @@ line-height: initial; border-radius: $border-radius-inputs; box-shadow: 0 1px 1px rgba(0, 0, 0, .25) inset; - font-size: 11px; + font-size: 12px; padding: 3px 4px 2.5px 4px; width: 18%; &:first-child { - width: 62%; + width: 57%; margin-right: 0; } } @@ -86,9 +114,9 @@ line-height: initial; border-radius: 3px; box-shadow: 0 1px 1px rgba(0, 0, 0, .25) inset; - font-size: 12.5px; + font-size: 12px; padding: 3px 4px 2.5px 4px; - width: calc(72% - 15px); + width: 66%; background: white; white-space: nowrap; overflow: hidden; @@ -125,17 +153,19 @@ } &.dialog-window { - height: 89vh; + height: calc(100vh - 39px); position: fixed; - left: 204px; + left: 190px; border: 0; box-shadow: none; - top: 11px; - width: calc(100vw - 227px); + top: 10px; + width: 100%; padding: 0; border-radius: 0; background: white; z-index: 9999; + max-width: calc(100vw - 200px); + border: 1px solid $color-ccc; } .window { @@ -261,9 +291,14 @@ color: $color-muted; overflow: hidden; overflow-y: auto; - max-height: 200px; + max-height: 39.8vh; + border-bottom: 1px solid $color-ddd; + box-shadow: 10px -10px 10px $bg-eee inset; .prompt-search-loader { + color: $color-222; + padding: 1.5px 3.5px 2.5px 3.5px; + img { vertical-align: bottom; margin: 0; @@ -283,7 +318,7 @@ } .prompt-search-item { - padding: 3px; + padding: 3px 6px; border-bottom: 1px solid $bg-muted-invisible; &:last-child { diff --git a/lib/scss/table.scss b/lib/scss/table.scss index 1c86f26d..fd0b3e3c 100644 --- a/lib/scss/table.scss +++ b/lib/scss/table.scss @@ -1,8 +1,13 @@ .table-header { + background: $bg-light; font-size: 12px; - line-height: 20px; + padding-top: 1.5px; + line-height: 18px; + width: calc(100% - 8px); margin: 0 auto; transition: opacity .3s linear; + border: 1px solid $color-ddd; + border-radius: 1.5px 1.5px 0 0; .clickable { &:active { @@ -11,11 +16,10 @@ } div { - background: $bg-light; + background: none; text-align: left; padding: 0 4px; - border: 1px solid $color-ddd; - border-top: 0; + border-left: 1px solid $color-ddd; &:first-child { border-left: 0; @@ -44,7 +48,7 @@ font-weight: 500; } &:nth-child(odd) { - background: $bg-lighter; + background: $bg-table-row; } &:focus, &:active, &.active { @@ -83,43 +87,66 @@ .table-body { //(table - header - table-infos) to calculate height - height: calc(100vh - 150px - 105px); + height: calc(100vh - 155px - 105px); overflow-x: hidden; padding-bottom: 20px; background: white; - border-radius: 0 2px 2px 0; + border-radius: 0 0 2px 2px; + width: calc(100% - 8px); + margin: 0 auto; + border: 1px solid $color-ccc; border-top: 0; + margin-bottom: 5px; + + &.freezed { + cursor: wait; + * { + cursor: wait; + pointer-events: none; + } + } } .table-loader { - min-height: 100vh; - padding-top: 58vh; + padding-top: 41vh; position: relative; margin-top: -30vh; z-index: 9; background: white; text-align: center; + .table-loader-content { + font-size: 12px; + margin-top: 3vh; + img { + width: 17px; + margin-right: 1px; + } + } } .table-row-loading, .table-row-loading.active, .table-row-loading.active:active, .table-row-loading.active:focus { - background: $bg-progress; - animation: loadingStripes 1.5s linear infinite; + background: $bg-table-row-loading; + animation: loadingStripes 1.2s linear infinite; + box-shadow: 0 1px 0 rgba(0, 0, 0, .05) inset; } .table-infos { background: $bg-light; - max-height: 159px; - padding: 0 5px 11px 5px; + max-height: 140px; + padding: 0 5px 5px 5px; overflow-y: auto; font-size: 12px; color: $color-777; - border-top: 1px solid $color-ccc; + border: 1px solid $color-ccc; + border-radius: 2px; + width: calc(100% - 8px); + margin: 0 auto; .information { - margin-top: 3px; + margin-top: 2px; -webkit-user-select: text; b { diff --git a/lib/scss/tabs.scss b/lib/scss/tabs.scss new file mode 100644 index 00000000..4324381d --- /dev/null +++ b/lib/scss/tabs.scss @@ -0,0 +1,60 @@ +.tab { + overflow-x: hidden; + .tab-menu { + display: flex; + overflow: hidden; + height: 23px; + background: $bg-light; + } + .tab-button { + display: inline-flex; + font-size: 13px; + background: $bg-light; + padding: 4px 3px 5px 6px; + line-height: 15px; + white-space: nowrap; + position: relative; + bottom: -1px; + + img { + vertical-align: top; + margin-right: 3px; + } + a { + opacity: .5; + border-radius: 3px; + color: #666; + margin: 0 4px; + margin-left: 8px; + + i { + display: block; + width: 13px; + } + + &:hover { + opacity: 1; + } + } + &.active { + border-radius: 4px 4px 0 0; + background: white; + + a { + opacity: 1; + font-size: 7px; + position: relative; + bottom: -1px; + background: $color-error; + color: white; + text-shadow: 0 -1px rgba(0, 0, 0, .3); + box-shadow: 0 2px 3px red inset; + right: -2px; + height: 13px; + width: 13px; + line-height: 12px; + text-align: center; + } + } + } +} diff --git a/lib/scss/variables.scss b/lib/scss/variables.scss index 373c25a5..7fd0f7dd 100644 --- a/lib/scss/variables.scss +++ b/lib/scss/variables.scss @@ -29,11 +29,14 @@ backgrounds $bg-header: #dedede; $bg-light: #f0f0f0; $bg-lighter: #fcfcfc; +$bg-eee: #eee; $bg-muted: rgba(0, 0, 0, .35); $bg-muted-more: rgba(0, 0, 0, .15); $bg-muted-invisible: rgba(0, 0, 0, .065); $bg-error: #e81616; +$bg-table-row: #eee; $bg-table-row-highlight: $color-positive; +$bg-table-row-loading: repeating-linear-gradient(90deg, $color-primary, $color-primary 5px, 0px, #147ada 10px); $bg-progress: repeating-linear-gradient(135deg, $color-primary, $color-primary 5px, #147ada 5px, #147ada 10px); $bg-progress-secondary: repeating-linear-gradient(135deg, $color-primary, $color-primary 5px, #147ada 5px, #147ada 10px); $bg-progress-minor: repeating-linear-gradient(135deg, #d83232, #d83232 5px, #c71212 5px, #c71212 10px);; diff --git a/lib/scss/win/index.scss b/lib/scss/win/index.scss index 2eaead87..4dc9371d 100644 --- a/lib/scss/win/index.scss +++ b/lib/scss/win/index.scss @@ -14,4 +14,5 @@ @import '../ace-editor'; @import '../home'; @import '../updates'; +@import '../tabs'; @import 'win'; diff --git a/lib/scss/win/win.scss b/lib/scss/win/win.scss index 30f05a10..86d47be7 100644 --- a/lib/scss/win/win.scss +++ b/lib/scss/win/win.scss @@ -27,9 +27,6 @@ a, button, .fake-link { } } -.top-menu .button-add-package { - line-height: 30px; -} .ace_editor { font-size: 14px !important; } diff --git a/lib/top.pug b/lib/top.pug index a7ef5a01..46b5547e 100644 --- a/lib/top.pug +++ b/lib/top.pug @@ -1,23 +1,21 @@ -.top-menu(ng-controller='TopMenuController as topMenu') - span(npm-loading) - include ./npm-doctor-log.pug +.top-menu(top-menu, top-menu-project-path-id="{{tab}}", ng-class="{'freezed': performingAction}") include ./install-new-package-version.pug include ./install-new-package.pug .row .col-xs-12 - button.button-add-package(title="Add Packages", ng-click="shell.activeClickedLink('1');", ng-class="{'active': shell.activeLink === '1'}") + button.button-add-package(title="Add Packages", ng-click="showInstallPrompt = true", ng-class="{'active': showInstallPrompt}") i.fa.fa-plus-circle | Add package - span(ng-show='shell.showMenuButtons') - button.button-uninstall(title="Uninstall", ng-click="shell.activeClickedLink('5'); topMenu.uninstallPackage(shell.currentSelectedPackages)", ng-class="{'active': shell.activeLink === '5'}") + span(ng-show='showMenuButtons') + button.button-uninstall(title="Uninstall", ng-click="activeClickedLink('5'); uninstallPackage(currentSelectedPackages)", ng-class="{'active': activeLink === '5'}") i.fa.fa-remove | Uninstall - button.button-update(title="Update", ng-click="shell.activeClickedLink('2'); topMenu.updatePackage(shell.currentSelectedPackages)", ng-class="{'active': shell.activeLink === '2'}") + button.button-update(title="Update", ng-click="activeClickedLink('2'); updatePackage(currentSelectedPackages)", ng-class="{'active': activeLink === '2'}") i.fa.fa-level-up | Update - button.button-latest(title="Install Latest", ng-click="shell.activeClickedLink('3'); topMenu.installLatest(shell.currentSelectedPackages)", ng-class="{'active': shell.activeLink === '3'}") + button.button-latest(title="Install Latest", ng-click="activeClickedLink('3'); installLatest(currentSelectedPackages)", ng-class="{'active': activeLink === '3'}") i.fa.fa-rocket | Latest - button.button-version(title="Install Version", ng-show="shell.currentSelectedPackages.length === 1", ng-click="shell.activeClickedLink('4'); topMenu.showPackageInstallPrompt = undefined; topMenu.showSpecificVersionPrompt = true", ng-class="{'active': shell.activeLink === '4'}") + button.button-version(title="Install Version", ng-show="currentSelectedPackages.length === 1", ng-click="showSpecificVersionPrompt = true", ng-class="{'active': showSpecificVersionPrompt}") i.fa.fa-at | Version diff --git a/package.json b/package.json index d0bf4b6b..591d015c 100644 --- a/package.json +++ b/package.json @@ -103,10 +103,10 @@ "precommit": "npm run lint", "lint": "gulp lint", "prestart": "npm install && gulp dist --platform=mac", - "start": "LANG=en_US.UTF-8 && electron .", + "start": "electron .", "mac": "LANG=en_US.UTF-8 && gulp dist --platform=mac && electron .", - "linux": "LANG=en_US.UTF-8 && gulp dist --platform=linux && electron .", - "win": "LANG=en_US.UTF-8 && gulp dist --platform=win && electron .", + "linux": "gulp dist --platform=linux && electron .", + "win": "gulp dist --platform=win && electron .", "build": "npm run build-mac && npm run build-linux && npm run build-win", "build-mac": "gulp distify --platform=mac && build --mac --publish=never && echo 'IMPORTANT! if build fails see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build , you probably miss some library on your OS, just install them and retry :).'", "build-win": "gulp distify --platform=win && build --win --publish=never && echo 'IMPORTANT! if build fails see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build , you probably miss some library on your OS, just install them and retry :).'", @@ -118,10 +118,10 @@ "ace-builds": "^1.2.5", "adm-zip": "^0.4.7", "angular": "^1.6.0", - "angular-ui-router": "^0.4.2", "bootstrap": "^3.3.6", "electron-storage": "^1.0.6", "fs-extra": "^2.0.0", + "node-fetch": "^1.6.3", "npm": "^4.4.0", "rimraf": "^2.5.4", "selection-model": "^0.11.0",