├── .eslintrc ├── .github └── FUNDING.yml ├── .gitignore ├── .sass-lint.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── README.md ├── appveyor.yml ├── conf ├── linux.sh ├── paths.json └── tasks │ ├── annotate.js │ ├── clean.js │ ├── dist.js │ ├── es6-build.js │ ├── front-end.js │ ├── lint.js │ └── minify.js ├── doc ├── ABOUT.md ├── BUILD.md ├── CONTRIBUTE.md ├── DEVELOP.md ├── FAQ.md └── RECOMMENDATIONS.md ├── gulpfile.js ├── icon.icns ├── icon.ico ├── index.js ├── lib ├── content.pug ├── editor-prompt.pug ├── footer.pug ├── history-prompt.pug ├── icons │ └── fontello │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ ├── config.json │ │ ├── css │ │ ├── animation.css │ │ ├── fontello-codes.css │ │ ├── fontello-embedded.css │ │ ├── fontello-ie7-codes.css │ │ ├── fontello-ie7.css │ │ └── fontello.css │ │ └── font │ │ ├── fontello.eot │ │ ├── fontello.svg │ │ ├── fontello.ttf │ │ ├── fontello.woff │ │ └── fontello.woff2 ├── img.scss ├── img │ ├── loading.svg │ └── npm-logo-cube.svg ├── index.pug ├── install-new-package-version.pug ├── install-new-package.pug ├── js │ ├── assets.js │ ├── directives │ │ ├── ng-ace-editor.js │ │ ├── ng-auto-scroll.js │ │ ├── ng-autofocus.js │ │ ├── ng-drag-drop.js │ │ ├── ng-resizable.js │ │ ├── ng-right-click.js │ │ ├── ng-table-keyboard.js │ │ └── ng-tag-input.js │ ├── errors.js │ ├── filters.js │ ├── index.js │ ├── interface │ │ ├── content.js │ │ ├── left.js │ │ ├── shell.js │ │ └── top.js │ ├── loading.js │ ├── notification.js │ ├── npm │ │ ├── npm-api.js │ │ ├── npm-operations.js │ │ └── npm-runner.js │ └── update.js ├── left.pug ├── npm-doctor-log.pug ├── npm-update-log.pug ├── package-informations.pug ├── scss │ ├── ace-editor.scss │ ├── animations.scss │ ├── dragdrop.scss │ ├── footer.scss │ ├── functions.scss │ ├── header.scss │ ├── home.scss │ ├── layout.scss │ ├── linux │ │ ├── index.scss │ │ └── linux.scss │ ├── loading.scss │ ├── mac │ │ ├── index.scss │ │ └── mac.scss │ ├── progress.scss │ ├── prompt.scss │ ├── settings.scss │ ├── table.scss │ ├── tabs.scss │ ├── updates.scss │ ├── utils.scss │ ├── variables.scss │ └── win │ │ ├── index.scss │ │ └── win.scss ├── top.pug └── update.pug ├── menu.js └── package.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true 4 | }, 5 | parserOptions: { 6 | ecmaVersion: 6, 7 | sourceType: "module" 8 | }, 9 | "rules": { 10 | "comma-dangle": [ 11 | 2, 12 | "never" 13 | ], 14 | "no-cond-assign": [ 15 | 2, 16 | "always" 17 | ], 18 | "no-console": 1, 19 | "no-constant-condition": 2, 20 | "no-control-regex": 2, 21 | "no-debugger": 2, 22 | "no-dupe-args": 2, 23 | "no-dupe-keys": 2, 24 | "no-duplicate-case": 2, 25 | "no-empty-character-class": 2, 26 | "no-empty": 2, 27 | "no-ex-assign": 1, 28 | "no-extra-boolean-cast": 1, 29 | "no-extra-parens": [ 30 | 2, 31 | "functions" 32 | ], 33 | "no-extra-semi": 2, 34 | "no-func-assign": 1, 35 | "no-inner-declarations": [ 36 | 2, 37 | "both" 38 | ], 39 | "no-invalid-regexp": 2, 40 | "no-irregular-whitespace": 2, 41 | "no-negated-in-lhs": 2, 42 | "no-obj-calls": 2, 43 | "no-regex-spaces": 2, 44 | "no-sparse-arrays": 2, 45 | "no-unexpected-multiline": 2, 46 | "no-unreachable": 2, 47 | "use-isnan": 2, 48 | "valid-typeof": 2, 49 | "accessor-pairs": 2, 50 | "block-scoped-var": 2, 51 | "consistent-return": 0, 52 | "curly": [ 53 | 2, 54 | "all" 55 | ], 56 | "default-case": 2, 57 | "dot-location": [ 58 | 2, 59 | "property" 60 | ], 61 | "dot-notation": 2, 62 | "eqeqeq": [ 63 | 2, 64 | "smart" 65 | ], 66 | "guard-for-in": 1, 67 | "no-alert": 1, 68 | "no-caller": 2, 69 | "no-case-declarations": 0, 70 | "no-div-regex": 2, 71 | "no-else-return": 2, 72 | "no-labels": 2, 73 | "no-empty-pattern": 2, 74 | "no-eq-null": 2, 75 | "no-eval": 2, 76 | "no-extend-native": 2, 77 | "no-extra-bind": 2, 78 | "no-fallthrough": 2, 79 | "no-floating-decimal": 2, 80 | "no-implicit-coercion": [2, 81 | { 82 | "boolean": true, 83 | "number": true, 84 | "string": true 85 | } 86 | ], 87 | "no-implied-eval": 2, 88 | "no-invalid-this": 2, 89 | "no-iterator": 2, 90 | "no-lone-blocks": 2, 91 | "no-loop-func": 2, 92 | "no-magic-numbers": 0, 93 | "no-multi-spaces": 2, 94 | "no-multi-str": 2, 95 | "no-native-reassign": 2, 96 | "no-new-func": 2, 97 | "no-new-wrappers": 2, 98 | "no-new": 2, 99 | "no-octal-escape": 2, 100 | "no-octal": 2, 101 | "no-param-reassign": 2, 102 | "no-process-env": 1, 103 | "no-proto": 2, 104 | "no-redeclare": [2, { 105 | "builtinGlobals": true 106 | } 107 | ], 108 | "no-return-assign": 2, 109 | "no-script-url": 2, 110 | "no-self-compare": 2, 111 | "no-sequences": 2, 112 | "no-throw-literal": 1, 113 | "no-unused-expressions": 2, 114 | "no-useless-call": 2, 115 | "no-useless-concat": 2, 116 | "no-void": 2, 117 | "no-with": 2, 118 | "radix": 2, 119 | "vars-on-top": 2, 120 | "wrap-iife": [ 121 | 2, 122 | "outside" 123 | ], 124 | "yoda": 2, 125 | "strict": [ 126 | 2, 127 | "safe" 128 | ], 129 | "no-catch-shadow": 2, 130 | "no-delete-var": 2, 131 | "no-label-var": 2, 132 | "no-shadow-restricted-names": 2, 133 | "no-shadow": [ 134 | 2, 135 | { 136 | "builtinGlobals": true 137 | } 138 | ], 139 | "no-undef-init": 2, 140 | "no-undef": 2, 141 | "no-unused-vars": [ 142 | 2, 143 | { 144 | "vars": "all", 145 | "args": "after-used" 146 | } 147 | ], 148 | "no-use-before-define": 2, 149 | "callback-return": 1, 150 | "handle-callback-err": 2, 151 | "no-new-require": 2, 152 | "no-path-concat": 2, 153 | "no-process-exit": 0, 154 | "no-sync": 1, 155 | "array-bracket-spacing": [ 156 | 2, 157 | "never" 158 | ], 159 | "block-spacing": [ 160 | 2, 161 | "always" 162 | ], 163 | "brace-style": [ 164 | 2, 165 | "1tbs", 166 | { 167 | "allowSingleLine": false 168 | } 169 | ], 170 | "camelcase": [ 171 | 2, 172 | { 173 | "properties": "always" 174 | } 175 | ], 176 | "comma-spacing": [ 177 | 2, 178 | { 179 | "before": false, 180 | "after": true 181 | } 182 | ], 183 | "comma-style": [ 184 | 2, 185 | "last", 186 | { 187 | "exceptions": { 188 | "VariableDeclaration": true 189 | } 190 | } 191 | ], 192 | "computed-property-spacing": [ 193 | 2, 194 | "never" 195 | ], 196 | "consistent-this": [ 197 | 2, 198 | "that" 199 | ], 200 | "eol-last": 2, 201 | "func-names": 1, 202 | "func-style": [ 203 | 1, 204 | "expression", 205 | { 206 | "allowArrowFunctions": true 207 | } 208 | ], 209 | "id-length": 1, 210 | "key-spacing": [ 211 | 2, 212 | { 213 | "beforeColon": false, 214 | "afterColon": true 215 | } 216 | ], 217 | "linebreak-style": [ 218 | 2, 219 | "unix" 220 | ], 221 | "new-cap": 2, 222 | "new-parens": 2, 223 | "newline-after-var": [ 224 | 2, 225 | "always" 226 | ], 227 | "no-array-constructor": 2, 228 | "no-bitwise": 2, 229 | "no-continue": 2, 230 | "no-lonely-if": 2, 231 | "no-mixed-spaces-and-tabs": 2, 232 | "no-multiple-empty-lines": [ 233 | 1, 234 | { 235 | "max": 2, 236 | "maxEOF": 1 237 | } 238 | ], 239 | "no-negated-condition": 1, 240 | "no-nested-ternary": 2, 241 | "no-new-object": 2, 242 | "no-plusplus": 2, 243 | "no-spaced-func": 2, 244 | "no-ternary": 0, 245 | "no-trailing-spaces": [ 246 | 2, 247 | { 248 | "skipBlankLines": false 249 | } 250 | ], 251 | "no-underscore-dangle": 2, 252 | "no-unneeded-ternary": 2, 253 | "object-curly-spacing": [ 254 | 2, 255 | "never" 256 | ], 257 | "one-var": 2, 258 | "operator-assignment": [ 259 | 2, 260 | "always" 261 | ], 262 | "operator-linebreak": [ 263 | 2, 264 | "after" 265 | ], 266 | "quote-props": 2, 267 | "quotes": [ 268 | 2, 269 | "single", 270 | "avoid-escape" 271 | ], 272 | "semi-spacing": 2, 273 | "semi": [ 274 | 2, 275 | "always" 276 | ], 277 | "keyword-spacing": [ 278 | 2, 279 | { 280 | "before": true, 281 | "after": true 282 | } 283 | ], 284 | "space-before-blocks": [ 285 | 2, 286 | "always" 287 | ], 288 | "space-before-function-paren": [ 289 | 2, 290 | "never" 291 | ], 292 | "space-in-parens": [ 293 | 2, 294 | "never" 295 | ], 296 | "space-infix-ops": 2, 297 | "space-unary-ops": [ 298 | 2, 299 | { 300 | "words": true, 301 | "nonwords": false 302 | } 303 | ], 304 | "wrap-regex": 2, 305 | "arrow-parens": [ 306 | 2, 307 | "as-needed" 308 | ], 309 | "arrow-spacing": [ 310 | 2, 311 | { 312 | "before": true, 313 | "after": true 314 | } 315 | ], 316 | "constructor-super": 2, 317 | "generator-star-spacing": [ 318 | 2, 319 | { 320 | "before": false, 321 | "after": true 322 | } 323 | ], 324 | "no-confusing-arrow": 1, 325 | "no-class-assign": 2, 326 | "no-const-assign": 2, 327 | "no-dupe-class-members": 1, 328 | "no-this-before-super": 2, 329 | "no-var": 1, 330 | "object-shorthand": [ 331 | 2, 332 | "always" 333 | ], 334 | "prefer-arrow-callback": 1, 335 | "prefer-const": 1, 336 | "prefer-spread": 1, 337 | "prefer-template": 2, 338 | "require-yield": 2 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .tmp/ 3 | etc/ 4 | dist/ 5 | .DS_Store 6 | npm-debug.log 7 | npm-debug* 8 | *.zip 9 | *.dmg 10 | releases/ 11 | releases/* 12 | npm-shrinkwrap.json 13 | -------------------------------------------------------------------------------- /.sass-lint.yml: -------------------------------------------------------------------------------- 1 | options: 2 | formatter: stylish 3 | files: 4 | include: '**/*.s+(a|c)ss' 5 | rules: 6 | # Extends 7 | extends-before-mixins: 1 8 | extends-before-declarations: 1 9 | placeholder-in-extend: 1 10 | 11 | # Mixins 12 | mixins-before-declarations: 1 13 | 14 | # Line Spacing 15 | one-declaration-per-line: 1 16 | empty-line-between-blocks: 1 17 | single-line-per-selector: 0 18 | 19 | # Disallows 20 | no-color-keywords: 0 21 | no-color-literals: 0 22 | no-css-comments: 0 23 | no-debug: 1 24 | no-duplicate-properties: 1 25 | no-empty-rulesets: 1 26 | no-extends: 0 27 | no-ids: 3 28 | no-important: 1 29 | no-invalid-hex: 1 30 | no-mergeable-selectors: 1 31 | no-misspelled-properties: 1 32 | no-qualifying-elements: 0 33 | no-trailing-zero: 1 34 | no-transition-all: 1 35 | no-url-protocols: 1 36 | no-vendor-prefixes: 3 37 | no-warn: 1 38 | 39 | # Nesting 40 | force-attribute-nesting: 3 41 | force-element-nesting: 1 42 | force-pseudo-nesting: 1 43 | 44 | # Name Formats 45 | function-name-format: 1 46 | mixin-name-format: 1 47 | placeholder-name-format: 1 48 | variable-name-format: 1 49 | 50 | # Style Guide 51 | border-zero: 0 52 | brace-style: 1 53 | clean-import-paths: 1 54 | empty-args: 1 55 | hex-length: 1 56 | hex-notation: 1 57 | indentation: 1 58 | leading-zero: 1 59 | nesting-depth: 0 60 | property-sort-order: 3 61 | quotes: 1 62 | shorthand-values: 0 63 | url-quotes: 1 64 | variable-for-property: 1 65 | zero-unit: 1 66 | 67 | # Inner Spacing 68 | space-after-comma: 1 69 | space-before-colon: 1 70 | space-after-colon: 1 71 | space-before-brace: 1 72 | space-before-bang: 1 73 | space-after-bang: 1 74 | space-between-parens: 1 75 | 76 | # Final Items 77 | trailing-semicolon: 1 78 | final-newline: 1 79 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: osx 2 | osx_image: xcode8.2 3 | language: node_js 4 | before_install: 5 | - brew update 6 | - brew install gnu-tar graphicsmagick rpm 7 | node_js: 8 | - "6" 9 | script: 10 | - npm run lint 11 | - npm run build-mac 12 | - npm run build-linux 13 | - export VERSION=$(echo $TRAVIS_TAG | tr -d "v") 14 | deploy: 15 | - provider: releases 16 | api_key: $GITHUB_ACCESS_TOKEN 17 | file: 18 | - "releases/ndm-$(echo $VERSION).dmg" 19 | - "releases/ndm-$(echo $VERSION)-mac.zip" 20 | - "releases/ndm-$(echo $VERSION).zip" 21 | skip_cleanup: true 22 | on: 23 | tags: true 24 | - provider: script 25 | script: conf/linux.sh $VERSION $GEMFURY_TOKEN 26 | skip_cleanup: true 27 | on: 28 | tags: true 29 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tech@720kb.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | If you are so kind to help and support us, please consider following these guidelines before sending PRs or commits: 4 | 5 | - Possibly no lint errors (eslint, jscs, scss, pug etc ...) if accidentally you find some then feel free to fix it as you go. 6 | 7 | - Possibly no formatting errors (if you use [Atom](https://atom.io/) you just have to use the IDE default settings for formatting code and you are synced) 8 | 9 | - Possibly use ONLY English language (everywhere in the code and outside the code) 10 | 11 | - Possibly no English typos (it can happen of course, just try to avoid them as much as possible) 12 | 13 | - Possibly no comments inside the code 14 | 15 | - Possibly, just use .lint files in your IDE (don't remove or disable the linters: .eslintrc, .jscslint and so on) 16 | 17 | - Possibly, just do not disable linters with inline comments (or at least remove comments as you want to PR) but be sure there are no errors in the end. 18 | 19 | - If you are editing the GUI style (CSS) do not change or edit .pug/.html files to fit your style; CSS must fit the html structure and not the opposite. 20 | 21 | - As you finished to code your changes always make a new clean npm install (`rm -Rf node_modules/ && npm install`) 22 | , Then run the app and test all your changes very deeply (`npm start`) 23 | 24 | - Be sure to always update your node version to LTS before to start coding 25 | 26 | - If you are not sure or you have any doubt about what you are doing/editing: consider opening an issue and ask, before to go PR or commit. You can even join the [live chat](https://gitter.im/720kb/ndm) and ask there. 27 | 28 | - If your changes are radicals, please open an issue or contact us [here](https://gitter.im/720kb/ndm), so that we can discuss it togheter before everything goes on. By radicals we can list for example: 29 | - changed the HTML layout 30 | - changed UI and UX behaviors 31 | - changed logo or icons or graphics in general 32 | - changed package.json by changing | updating | removing dependencies 33 | - added new js files to the folder structure 34 | - changed the project folders structure 35 | - rewrote js file/s for a good 50% and up 36 | 37 | These guidelines are not imperative at all, it's just the simplest method we have to be synced with you. 38 | You can PR any file in the repo: even this same file you are now reading. :ok_hand: 39 | 40 | #### Look! One thing... 41 | 42 | To be absolutely clear with you: 43 | 44 | contributing on the ***ndm*** project, and in general on open source projects, does not mean to get paid or receive any good for the time/ and work and service you freely provide for the project. It is your time/service/work and you provide it on your own choice; Contributing to **ndm** doesn't mean neither to get hired or to get a job in any company. By reading these contribution guidelines and before to contribute on the **ndm** project you declare you have read and accepted these conditions, thank you. 45 | 46 | Thank you for listening :speaker:! We really would love and hope to have you on board, to have some fun and share new tricks and tips, each others of course! 47 | 48 | Bests. 49 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Issue Report 2 | 7 | 8 | #### REQUIRED 9 | OS / OS version: 10 | 11 | ndm version: 12 | 13 | node version: 14 | 15 | npm version: 16 | 17 | node installed via (brew, n, nvm, pkg, other ...): 18 | 19 | #### OPTIONAL 20 | 21 | $ which node: 22 | 23 | $ which npm: 24 | 25 | #### IMPORTANT 26 | 27 | - npm permissions fixed or not-fixed? (see https://docs.npmjs.com/getting-started/fixing-npm-permissions): 28 | 29 | - If you are using npm > v4.1.1 then run `npm doctor` and paste the output here: 30 | 31 | #### APPRECIATED 32 | _open the devtools console: OS Menu -> View -> Developer -> Open DevTools_ 33 | 34 | Now make a screenshot of your devtools console or paste the devtools console full-log in here: 35 | 36 | #### THE PROBLEM 37 | What's the problem you facing, in few lines: 38 | 39 | #### REPRODUCE THE PROBLEM 40 | How to reproduce the problem in few lines: 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ndm 2 | 3 | ![screenshot-npm-desktop-manager](http://i.imgur.com/6KL3pt7.png) 4 | 5 | 6 | The Open Source npm desktop GUI. 7 | 8 | Runs on Linux, MacOS and Windows. 9 | 10 | **ndm** stands for **"npm desktop manager"**. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |

30 | About ndm   31 | | 32 |   Develop it  | 33 |   Build it  34 | | 35 |   Contribute  36 | | 37 |   Recommendations  38 | | 39 |   FAQ  40 | | 41 |   License 42 |

43 | 44 | 45 | ## Download 46 | **[Download for MacOS](https://720kb.github.io/ndm#mac)**   |  **[Download for Linux](https://720kb.github.io/ndm#linux)**   |  **[Download for Windows](https://720kb.github.io/ndm#win)**   47 | 48 | ###### You can browse all the releases at [github.com/720kb/ndm/releases](https://github.com/720kb/ndm/releases) 49 | 50 | 51 | 52 | ## Homebrew 53 | 54 | On MacOS you can install **ndm** also with [Homebrew Cask](https://caskroom.github.io/): 55 | 56 | ```bash 57 | $ brew update 58 | $ brew cask install ndm 59 | ``` 60 | 61 | ## Arch Linux 62 | 63 | On Linux you can install **ndm** also like this: 64 | 65 | ```bash 66 | $ yaourt -S ndm 67 | ``` 68 | 69 | ## Debian 70 | 71 | On Debian based linux is possible to install **ndm** doing: 72 | 73 | ```bash 74 | $ echo "deb [trusted=yes] https://apt.fury.io/720kb/ /" | sudo tee 75 | /etc/apt/sources.list.d/ndm.list && sudo apt-get update && sudo apt-get install ndm 76 | ``` 77 | 78 | ## RedHat 79 | 80 | On RedHat based linux is possible to install **ndm** doing: 81 | 82 | ```bash 83 | echo "[fury] 84 | name=ndm repository 85 | baseurl=https://repo.fury.io/720kb/ 86 | enabled=1 87 | gpgcheck=0" | sudo tee /etc/yum.repos.d/ndm.repo && sudo yum update && sudo yum install ndm 88 | ``` 89 | 90 | **Core team** 91 | [720kb](https://720kb.net) 92 | 93 | **Contributors** [All the awesome contributors](https://github.com/720kb/ndm/graphs/contributors) 94 | 95 | 96 | ## Support ndm 97 | 98 | > Donating to an open source project is the best way to tie your love for it. 99 | 100 | If you enjoy **ndm** consider donating to the project and help mantain and continuously improve it! 101 | 102 | **Backers** 103 | 104 | Support us with a monthly donation and help us continue our activities. [Become a backer](https://opencollective.com/ndm#backer) 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | **Sponsors** 138 | 139 | Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor](https://opencollective.com/ndm#sponsor) 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | #environment: 2 | # nodejs_version: '6' 3 | 4 | platform: 5 | - x86 6 | 7 | install: 8 | #- ps: Install-Product node $env:nodejs_version 9 | - echo 'NODE_VERSION' && node -v 10 | - echo 'NPM_VERSION' && npm -v 11 | - appveyor-retry npm install 12 | - npm run build-win 13 | - ps: get-childItem releases\*.exe | rename-item -newname { $_.name -replace " Setup ","-" } 14 | 15 | build: off 16 | test: off 17 | 18 | artifacts: 19 | - path: releases\*.exe 20 | - path: releases\*win.zip 21 | 22 | deploy: 23 | - provider: GitHub 24 | auth_token: 25 | secure: ZQe3awDIx7JDSXPfjp8Y+mKgdXERdHAeNikOKo3eyYWZzjykn74n6S6B8qJqqbBx 26 | draft: false 27 | prerelease: false 28 | on: 29 | appveyor_repo_tag: true 30 | -------------------------------------------------------------------------------- /conf/linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -F package=@releases/ndm_$(echo $1)_amd64.deb https://$2@push.fury.io/720kb/ && \ 4 | curl -F package=@releases/ndm-$(echo $1).rpm https://$2@push.fury.io/720kb/ && \ 5 | 6 | echo "Done." 7 | -------------------------------------------------------------------------------- /conf/paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "tmp": ".tmp/", 3 | "lib": "lib/", 4 | "dist": "dist/" 5 | } 6 | -------------------------------------------------------------------------------- /conf/tasks/annotate.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | const gulp = require('gulp') 3 | , ngAnnotate = require('gulp-ng-annotate') 4 | , paths = require('../paths.json'); 5 | 6 | gulp.task('annotate', ['es6-build'], () => { 7 | 8 | return gulp.src(`${paths.tmp}**/*.js`) 9 | .pipe(ngAnnotate()) 10 | .pipe(gulp.dest(`${paths.tmp}`)); 11 | }); 12 | -------------------------------------------------------------------------------- /conf/tasks/clean.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | const gulp = require('gulp') 3 | , del = require('del') 4 | , paths = require('../paths.json'); 5 | 6 | gulp.task('clean', () => { 7 | 8 | return del([ 9 | paths.tmp, 10 | paths.dist 11 | ]); 12 | }); 13 | -------------------------------------------------------------------------------- /conf/tasks/dist.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | const gulp = require('gulp') 3 | , runSequence = require('run-sequence') 4 | , paths = require('../paths.json'); 5 | 6 | gulp.task('dist', ['annotate'], done => { 7 | 8 | runSequence([ 9 | 'copy-transpiled-js-files', 10 | 'copy-img-files', 11 | 'copy-icon-files' 12 | ], done); 13 | }); 14 | 15 | gulp.task('copy-transpiled-js-files', () => { 16 | 17 | return gulp.src(`${paths.tmp}**/*`) 18 | .pipe(gulp.dest(`${paths.dist}`)); 19 | }); 20 | 21 | gulp.task('copy-img-files', () => { 22 | 23 | return gulp.src(`${paths.lib}img/**/*`) 24 | .pipe(gulp.dest(`${paths.dist}img`)); 25 | }); 26 | 27 | gulp.task('copy-icon-files', () => { 28 | 29 | return gulp.src(`${paths.lib}icons/**/*`) 30 | .pipe(gulp.dest(`${paths.dist}icons`)); 31 | }); 32 | -------------------------------------------------------------------------------- /conf/tasks/es6-build.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | const gulp = require('gulp') 3 | , rollup = require('rollup').rollup 4 | , rollupJSON = require('rollup-plugin-json') 5 | , rollupBabel = require('rollup-plugin-babel') 6 | , runSequence = require('run-sequence') 7 | , paths = require('../paths.json'); 8 | 9 | gulp.task('es6-build', done => { 10 | 11 | return runSequence( 12 | 'front-end', 13 | 'ndm', 14 | 'ndm-updater', 15 | 'npm-runner', 16 | done); 17 | }); 18 | 19 | gulp.task('npm-runner', () => { 20 | 21 | return rollup({ 22 | 'entry': `${paths.lib}js/npm/npm-runner.js`, 23 | 'plugins': [ 24 | rollupJSON(), 25 | rollupBabel({ 26 | 'presets': [ 27 | 'es2015-rollup' 28 | ] 29 | }) 30 | ] 31 | }).then(bundle => { 32 | 33 | return bundle.write({ 34 | 'format': 'iife', 35 | 'moduleId': 'npm-ui-ng', 36 | 'moduleName': 'npm-ui-ng', 37 | 'sourceMap': true, 38 | 'dest': `${paths.tmp}/npm-runner.js` 39 | }); 40 | }); 41 | }); 42 | 43 | gulp.task('ndm', () => { 44 | 45 | return rollup({ 46 | 'entry': `${paths.lib}js/index.js`, 47 | 'plugins': [ 48 | rollupJSON(), 49 | rollupBabel({ 50 | 'presets': [ 51 | 'es2015-rollup' 52 | ] 53 | }) 54 | ] 55 | }).then(bundle => { 56 | 57 | return bundle.write({ 58 | 'format': 'iife', 59 | 'moduleId': 'npm-ui-ng', 60 | 'moduleName': 'npm-ui-ng', 61 | 'sourceMap': true, 62 | 'dest': `${paths.tmp}/js/index.js` 63 | }); 64 | }); 65 | }); 66 | 67 | gulp.task('ndm-updater', () => { 68 | 69 | return rollup({ 70 | 'entry': `${paths.lib}js/update.js`, 71 | 'plugins': [ 72 | rollupJSON(), 73 | rollupBabel({ 74 | 'presets': [ 75 | 'es2015-rollup' 76 | ] 77 | }) 78 | ] 79 | }).then(bundle => { 80 | 81 | return bundle.write({ 82 | 'format': 'iife', 83 | 'moduleId': 'npm-updater-ng', 84 | 'moduleName': 'npm-updater-ng', 85 | 'sourceMap': true, 86 | 'dest': `${paths.tmp}/js/update.js` 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /conf/tasks/front-end.js: -------------------------------------------------------------------------------- 1 | /*global require,console*/ 2 | const gulp = require('gulp') 3 | , gulpPug = require('gulp-pug') 4 | , plumber = require('gulp-plumber') 5 | , runSequence = require('run-sequence') 6 | , sourcemaps = require('gulp-sourcemaps') 7 | , gulpSass = require('gulp-sass') 8 | , paths = require('../paths.json') 9 | , argv = require('yargs').argv 10 | , platform = argv.platform || 'mac'; 11 | 12 | /*eslint-disable no-console */ 13 | console.info(`Setting app for ${platform}`); 14 | /*eslint-enable*/ 15 | 16 | gulp.task('front-end', done => { 17 | 18 | return runSequence( 19 | 'clean', 20 | ['scss', 'pug'], 21 | done); 22 | }); 23 | 24 | gulp.task('scss', () => { 25 | 26 | return gulp.src(`${paths.lib}scss/${platform}/index.scss`) 27 | .pipe(plumber()) 28 | .pipe(sourcemaps.init()) 29 | .pipe(gulpSass({ 30 | 'outputStyle': 'compressed' 31 | })) 32 | .pipe(sourcemaps.write('.')) 33 | .pipe(gulp.dest(`${paths.tmp}/css`)); 34 | }); 35 | 36 | gulp.task('pug', () => { 37 | 38 | return gulp.src(`${paths.lib}**/*.pug`) 39 | .pipe(gulpPug()) 40 | .pipe(gulp.dest(`${paths.tmp}`)); 41 | }); 42 | -------------------------------------------------------------------------------- /conf/tasks/lint.js: -------------------------------------------------------------------------------- 1 | /*global __dirname,require*/ 2 | 3 | const gulp = require('gulp') 4 | , eslint = require('gulp-eslint') 5 | , path = require('path') 6 | , paths = require('../paths.json') 7 | , toLint = path.resolve(__dirname, '../..', paths.lib, '**/*.js') 8 | , gulpFolder = path.resolve(__dirname, '**/*.js'); 9 | 10 | gulp.task('lint', () => { 11 | 12 | return gulp.src([gulpFolder, toLint]) 13 | .pipe(eslint()) 14 | .pipe(eslint.format()) 15 | .pipe(eslint.failOnError()); 16 | }); 17 | -------------------------------------------------------------------------------- /conf/tasks/minify.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | const gulp = require('gulp') 3 | , runSequence = require('run-sequence') 4 | , paths = require('../paths.json') 5 | , minifyJS = require('gulp-uglify'); 6 | 7 | gulp.task('distify', done => { 8 | 9 | runSequence( 10 | 'dist', 11 | 'dist-minify-js', 12 | done); 13 | }); 14 | 15 | gulp.task('dist-minify-js', () => { 16 | 17 | return gulp.src(`${paths.dist}js/*.js`) 18 | .pipe(minifyJS()) 19 | .pipe(gulp.dest(`${paths.dist}js/`)); 20 | }); 21 | -------------------------------------------------------------------------------- /doc/ABOUT.md: -------------------------------------------------------------------------------- 1 | ## About ndm 2 | 3 | **ndm** stands for _"npm desktop manager"_ 4 | 5 | A cross-platform GUI for [npm](https://npmjs.com/) built with web technologies. 6 | 7 | With **ndm** you can manage npm, npm projects and packages straight from the couch. 8 | 9 | **ndm** is packed up thanks to [Electron](https://github.com/electron/electron) and developed in HTML/CSS/JS powered by AngularJS, Sass and Pug. 10 | 11 | 12 | -------------------------------------------------------------------------------- /doc/BUILD.md: -------------------------------------------------------------------------------- 1 | 2 | ## Build the app 3 | 4 | Generate the Desktop executables which you can run whitout needing to open the terminal (.dmg, .deb, .exe, etc ..) 5 | 6 | #### Setup 7 | 8 | ``` 9 | $ git clone https://github.com/720kb/ndm.git 10 | 11 | $ cd ndm 12 | 13 | $ npm install 14 | ``` 15 | 16 | 17 | #### Builds for Mac 18 | 19 | `$ npm run build-mac` 20 | 21 | #### Builds for Linux 22 | 23 | `$ npm run build-linux` 24 | 25 | #### Builds for Windows 26 | 27 | `$ npm run build-win` 28 | 29 | #### Builds for all the platforms 30 | 31 | `$ npm run build` 32 | 33 | 34 | The executables are generated thanks to the [electron-builder](https://github.com/electron-userland/electron-builder), if you want you can change the build settings to your needs, just follow their documentation. 35 | 36 | The executables will be generated inside the `/releases` folder. 37 | -------------------------------------------------------------------------------- /doc/CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | ## :tada: Come Contribute! :tada: 2 | 3 | We'll be much grateful if you help and contribute to the project, in any way, even a feature request. 4 | 5 | Doors are wide open! 6 | 7 | 8 | ### How to contribute 9 | 10 | - Fork this repository 11 | - Open the repository folder in your IDE 12 | - Make your changes to the files you intend to edit 13 | - Commit the changes to your forked repo 14 | - Create a Pull Request 15 | - Done! 16 | 17 | To run the app while developing [is this simple](https://github.com/720kb/ndm/blob/master/doc/DEVELOP.md) 18 | 19 | ### Which programming languages 20 | 21 | Languages you'll need to have experience with in order to contribute to *ndm* are simply: Javascript (ES6) and CSS. 22 | 23 | Tools we use: 24 | 25 | - Angular 26 | - Electron 27 | - node 28 | - gulp 29 | - Sass 30 | - Babel 31 | - npm 32 | - bash 33 | - svg 34 | 35 | ### Guidelines 36 | 37 | Below are some Contribution Guidelines, consider reading these before to contribute, just that! 38 | 39 | [Contributing Guidelines](https://github.com/720kb/ndm/blob/master/CONTRIBUTING.md) 40 | 41 | 42 | -------------------------------------------------------------------------------- /doc/DEVELOP.md: -------------------------------------------------------------------------------- 1 | 2 | ## Develop ndm 3 | 4 | ### Setup 5 | 6 | `$ git clone https://github.com/720kb/ndm.git` 7 | 8 | `$ cd ndm` 9 | 10 | `$ npm install` 11 | 12 | ### Run app 13 | 14 | #### Run on Linux 15 | `$ npm run linux` 16 | 17 | #### Run on Mac 18 | `$ npm run mac` 19 | 20 | #### Run on Windows 21 | `$ npm run win` 22 | 23 | 24 | if you then want to test the executables follow [How to build](https://github.com/720kb/ndm/blob/master/doc/BUILD.md) 25 | 26 | -------------------------------------------------------------------------------- /doc/FAQ.md: -------------------------------------------------------------------------------- 1 | ## FAQ 2 | 3 | **1) I use the CLI, why use an app?** 4 | 5 | Of course, we all love the npm CLI, no jokes. 6 | 7 | It is obviously very powerful. 8 | 9 | *ndm* is an alternative experience to the `npmCLI` and here are all the pros: 10 | 11 | - Less struggling with long terminal logs, scrolling to find eventual warnings and/or errors. 12 | - All your projects are on the same view, no more multiple `cd` to move from a project to another, just drag them all into the app view. 13 | - Notifications (specially when your long long npm install finishes) 14 | - List all your npm global and local packages in one maybe two clicks. 15 | - Cleaner view of all your npm projects and dependencies. 16 | - Run npm commands and scripts in one maybe two clicks. 17 | - Search npm packages and see packages infos like a pro 18 | - More features to be finished and to come ... 19 | 20 | Some of the greatest and widely used package managers got their own GUI... brew, apt ... just for example. 21 | npm got none. That's bad, that doesn't help npm itself and the npm community. 22 | 23 | Here is ndm, give it a try before to say "_no, mmmmm, no, meh, maybe_" :ok_hand: 24 | 25 | **Obviously:** using **ndm** doesn't mean you can no longer use the CLI. 26 | 27 | **2) Is ndm stable?** 28 | 29 | The first releases are not guaranteed to be very stable, some problem/bug may happen. 30 | 31 | Just give it some time, have some patience and if you like ndm project then come contribute! 32 | Your help is always appreciated and you are welcome anytime! 33 | 34 | 35 | **3) Do i have to worry about anything when using ndm?** 36 | 37 | Actually not, not really. 38 | **ndm** does not run any malicious or env/system breaking commands in background, and it doesn't run anything outside of the npm native commands. 39 | 40 | If you want to be 100% sure about it, just look at the source code, which is clear and very readable. 41 | 42 | Then (if you have 5 minutes to spend) what we suggest is to read this tiny mini guide of sane [recommendations](https://github.com/720kb/ndm/blob/master/doc/RECOMMENDATIONS.md). 43 | 44 | **4) Why is ndm so slow on my pc?** 45 | 46 | **ndm** speed depends exclusively on your pc/device specs and [npm-cli](https://docs.npmjs.com/cli/npm) speed. 47 | We can't do much to speed up your machine or the npm native commands. 48 | 49 | **5) Yarn?** 50 | 51 | _Premise: **ndm** was born several months before Yarn was out._ 52 | 53 | Yarn is a great tool, we are looking forward to seeing what happens: both on the Yarn and the npm side. 54 | 55 | Many things could change in the meantime. 56 | 57 | That said: if you have any idea or suggestion you are welcome to share and discuss! 58 | 59 | **6) Support?** 60 | 61 | Just open an issue, we'll be in touch :ok_hand: 62 | -------------------------------------------------------------------------------- /doc/RECOMMENDATIONS.md: -------------------------------------------------------------------------------- 1 | ## Recommendations :ok_hand: 2 | 3 | - It is highly recommended to install node and npm via brew or nvm or n or similars 4 | - It is highly recommended (when developing or testing ndm) to not start the app with `sudo` (WRONG! `sudo npm run...`) 5 | - It is highly recommended to not rename `node_modules/` folder in your projects (which is a standard naming for node pkgs folder and should never be renamed) 6 | - It is recommended to manage only versioned projects with ndm (git, svn, mercurial etc..). This way everything can be reverted to it's previous/original status in case of unlikely events that gone wrong. 7 | - It is recommended to install and always use the LTS node version (brew or nvm or n or similars will help you to manage this with comfort) 8 | - It is highly recommended to fix npm permissions on your machine (if not already fixed). This means no more `sudo` for global actions. How to fix permissions is simple and written here: https://docs.npmjs.com/getting-started/fixing-npm-permissions 9 | - It is recommended to always run the latest version of ndm 10 | - It is highly reccomended to not install packages globally if those packages aren't meant/developed to be installed globally. You might face strange problems when trying to uninstall them and probably other related problems. 11 | - It is recomended to not change default npm configs, npm config and the use of .npmrc aren't yet fully supported by ndm (we will remove this recommandation as soon has these features will be implemented/supported) 12 | 13 | 🌈 Happy npm desktop managing! 14 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | require('require-dir')('conf/tasks'); 3 | -------------------------------------------------------------------------------- /icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/720kb/ndm/0ca1b3a8cb13ac0347fdadcb288bb3de4b70bbcd/icon.icns -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/720kb/ndm/0ca1b3a8cb13ac0347fdadcb288bb3de4b70bbcd/icon.ico -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*global require,process,__dirname*/ 2 | const {app, Menu, BrowserWindow, shell} = require('electron') 3 | , path = require('path') 4 | , url = require('url') 5 | , packageJSON = require('./package.json') 6 | , applicationTemplate = packageJSON.appTemplate; 7 | 8 | //Set main window height bigger for Windows ONLY 9 | if (process.platform === 'win32') { 10 | applicationTemplate.minHeight += 30; 11 | applicationTemplate.height += 30; 12 | } 13 | //Set main window height smaller for Linux ONLY 14 | if (process.platform !== 'win32' && 15 | process.platform !== 'darwin') { 16 | applicationTemplate.minHeight -= 20; 17 | applicationTemplate.height -= 20; 18 | } 19 | app.on('window-all-closed', () => { 20 | app.quit(); 21 | }); 22 | 23 | app.on('ready', () => { 24 | 25 | const mainWindow = new BrowserWindow(applicationTemplate) 26 | , updateWindow = new BrowserWindow({ 27 | 'width': 400, 28 | 'height': 192, 29 | 'parent': mainWindow, 30 | 'show': false, 31 | 'resizable': false, 32 | 'maximizable': false, 33 | 'alwaysOnTop': true, 34 | 'fullscreenable': false, 35 | 'title': '' 36 | }) 37 | , OSMenu = require('./menu.js')(mainWindow, updateWindow, shell, packageJSON, app); 38 | 39 | Menu.setApplicationMenu(Menu.buildFromTemplate(OSMenu)); 40 | updateWindow.loadURL(url.format({ 41 | 'pathname': path.resolve(__dirname, 'dist', 'update.html'), 42 | 'protocol': 'file:', 43 | 'slashes': true 44 | })); 45 | 46 | updateWindow.on('close', event => { 47 | event.preventDefault(); 48 | 49 | mainWindow.webContents.send('loading:unfreeze-app'); 50 | updateWindow.hide(); 51 | }); 52 | 53 | mainWindow.on('ready-to-show', () => { 54 | 55 | mainWindow.show(); 56 | }); 57 | 58 | mainWindow.on('page-title-updated', event => { 59 | //lock app title otherwise gets the index.html filename 60 | event.preventDefault(); 61 | }); 62 | 63 | mainWindow.on('restore', () => { 64 | //hide autoupdates window 65 | updateWindow.hide(); 66 | mainWindow.webContents.send('loading:unfreeze-app'); 67 | }); 68 | 69 | mainWindow.on('enter-full-screen', () => { 70 | //hide autoupdates window 71 | updateWindow.hide(); 72 | mainWindow.webContents.send('loading:unfreeze-app'); 73 | }); 74 | 75 | mainWindow.on('closed', () => { 76 | 77 | app.quit(); 78 | }); 79 | 80 | mainWindow.loadURL(url.format({ 81 | 'pathname': path.resolve(__dirname, 'dist', 'index.html'), 82 | 'protocol': 'file:', 83 | 'slashes': true 84 | })); 85 | }); 86 | -------------------------------------------------------------------------------- /lib/content.pug: -------------------------------------------------------------------------------- 1 | .content(ng-controller="ContentController as content") 2 | span(npm-loading) 3 | include ./npm-doctor-log.pug 4 | .row.home.bg-ultralight(ng-show="content.tabs.length <= 0") 5 | div 6 | .separator10 7 | small.color-black 8 | | Select or drag npm projects to start 9 | .separator10 10 | .separator10 11 | button.home-button(ng-click="shell.openChooser()") 12 | | Add projects 13 | .tab(npm-tabs, ng-repeat="tab in content.tabs", npm-tab-id="{{tab}}", ng-show="content.activeTab === tab && tab") 14 | .tab-menu 15 | span.tab-button(ng-repeat="tab in content.tabs", ng-class="{'active': content.activeTab === tab}", ng-click="content.activeTab = tab") 16 | spanner(ng-if="tab === ''") 17 | img.global-img(src="img/npm-logo-cube.svg") 18 | | Globals 19 | spanner(ng-if="tab !== ''") 20 | | {{ tab | lastNameInPath}} 21 | a(ng-click="content.closeProjectTab(tab)") 22 | i(class="fa fa-remove") 23 | include ./top.pug 24 | div(ng-show="tab") 25 | .row.table-header 26 | .col-xs-4.clickable(ng-click="sortTableBy('name')") 27 | | Package 28 | 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')}") 29 | .col-xs-2 30 | | Current 31 | .col-xs-2 32 | | Wanted 33 | .col-xs-2 34 | | Latest 35 | .col-xs-2.clickable(ng-click="sortTableBy('kind')") 36 | | Env 37 | 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')}") 38 | .table-body(ng-table-keyboard, ng-class="{'freezed': showLoadingSelectedRow}") 39 | .table-loader(ng-show="loading && !loaded") 40 | .table-loader-content 41 | img(src='img/loading.svg') 42 | | Loading packages... 43 | 44 | .row.table-row.disabled(ng-repeat='aPackage in packageInformations', ng-if="isGlobalProject && aPackage.name === 'npm'", title="Do not perform npm global actions from here") 45 | .col-xs-4 {{ aPackage.name }} 46 | .col-xs-2 47 | span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}") 48 | | {{ aPackage.current }} 49 | .col-xs-2 50 | i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest") 51 | | {{ aPackage.wanted }} 52 | .col-xs-2 53 | b(ng-if="aPackage.latest") 54 | | {{ aPackage.latest }} 55 | i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest") 56 | .col-xs-2 57 | | {{ aPackage.kind }} 58 | 59 | .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}") 60 | .col-xs-4 {{ aPackage.name }} 61 | .col-xs-2 62 | span(ng-class="{'color-positive font-light': !aPackage.wanted && !aPackage.latest}") 63 | | {{ aPackage.current }} 64 | .col-xs-2 65 | i(class="fa fa-check", ng-if="!aPackage.wanted && !aPackage.latest") 66 | | {{ aPackage.wanted }} 67 | .col-xs-2 68 | b(ng-if="aPackage.latest") 69 | | {{ aPackage.latest }} 70 | i(class="fa fa-check color-positive", ng-if="!aPackage.wanted && !aPackage.latest") 71 | .col-xs-2 72 | | {{ aPackage.kind }} 73 | div 74 | div.table-infos 75 | include ./package-informations.pug 76 | -------------------------------------------------------------------------------- /lib/editor-prompt.pug: -------------------------------------------------------------------------------- 1 | div.dialog.dialog-window(ng-if="leftBar.editorFilePath", ng-ace-editor, ng-ace-editor-theme="xcode", ng-ace-file-name="{{leftBar.editorFileName}}", ng-ace-file="{{leftBar.editorFilePath}}" ng-model="aceFileModel") 2 | div(class="prompt-window-options") 3 | span(class="prompt-window-infos", title="{{leftBar.rightClickedProject.path}}") 4 | img(src="img/loading.svg", width="13", ng-show="(savingFile && !savedFile) || loadingFile") 5 | i(class="fa fa-check color-primary", ng-show="savedFile && !savingFile") 6 | | {{leftBar.rightClickedProject.dirName}}/{{leftBar.editorFileName}} 7 | button(ng-click="saveFile()") 8 | | Save 9 | button(ng-click="leftBar.editorFilePath = undefined; aceFileModel = undefined;") 10 | | Close 11 | div(class="window") 12 | div(class="ng-ace-editor", autofocus, ng-autofocus="true") 13 | -------------------------------------------------------------------------------- /lib/footer.pug: -------------------------------------------------------------------------------- 1 | .footer.bg-footer 2 | span(class="badge-version", title="Current npm version", ng-mouseover="shell.updateNpmBadgeVersion()") 3 | b 4 | | npm 5 | = " " 6 | small(ng-if="shell.npmCurrentVersionBadge") 7 | | v{{shell.npmCurrentVersionBadge}} 8 | button.button-global(type="button", title="Enable ndm in global folder", ng-show="shell.globalDisabled", ng-click="shell.enableGlobal()") 9 | i.fa.fa-globe.color-primary 10 | | Enable globals 11 | button.button-update(type="button", ng-show="!shell.globalDisabled && shell.npmCurrentVersionBadge", title="Update npm", ng-click="shell.updateNpm()") 12 | i.fa.fa-history 13 | | Update npm 14 | span(class="npm-status pull-right", ng-mouseenter="shell.checkRegistryStatus()") 15 | i.fa.fa-disk(title="npm registry is available", ng-show="!shell.loadingRegistryStatus && shell.registryStatus") 16 | i.fa.fa-disk(title="npm registry checking ...", ng-show="shell.loadingRegistryStatus") 17 | i.fa.fa-disk(title="npm registry is unavailable", ng-show="!shell.loadingRegistryStatus && !shell.registryStatus") 18 | div.loader(ng-class="{'loading': shell.loadingRegistryStatus}") 19 | i.fa.fa-circle(ng-class="{'available': !shell.loadingRegistryStatus && shell.registryStatus}") 20 | i.fa.fa-circle(ng-class="{'unavailable': !shell.loadingRegistryStatus && !shell.registryStatus}") 21 | span(class="pull-right") 22 | button.button-doctor(type="button", title="Run doctor", ng-click="shell.activeClickedLink('doctor'); shell.runDoctor()") 23 | i.fa.fa-doctor 24 | | Doctor 25 | -------------------------------------------------------------------------------- /lib/history-prompt.pug: -------------------------------------------------------------------------------- 1 | div.dialog.dialog-window(ng-if="leftBar.showHistoryPrompt", ng-init="leftBar.showSnapshotStatus = []; leftBar.selectedSnapshot = undefined;") 2 | div(class="prompt-window-options") 3 | span(class="prompt-window-infos", title="{{leftBar.rightClickedProject.path}}") 4 | img(src="img/loading.svg", width="13", ng-show="leftBar.restoringSnapshot[leftBar.rightClickedProject.path] && !leftBar.restoredSnapshot[leftBar.rightClickedProject.path]") 5 | = " " 6 | | {{leftBar.rightClickedProject.dirName}}/ 7 | button(ng-click="leftBar.showHistoryPrompt = undefined;") 8 | | Close 9 | button(ng-click="leftBar.deleteSnapshot()", ng-if="leftBar.selectedSnapshot && leftBar.projectHistory && leftBar.projectHistory.length > 0") 10 | | Delete 11 | button(ng-if="leftBar.projectHistory && leftBar.projectHistory.length > 0 && leftBar.selectedSnapshot", ng-click="leftBar.restoreSnapshot()", ng-disabled="leftBar.restoringSnapshot[leftBar.rightClickedProject.path]") 12 | span(ng-show="!leftBar.restoringSnapshot[leftBar.rightClickedProject.path]") 13 | | Restore 14 | span(ng-show="leftBar.restoringSnapshot[leftBar.rightClickedProject.path]") 15 | | Restoring 16 | div(class="window") 17 | div(class="prompt-window-holder", ng-if="!leftBar.projectHistory || leftBar.projectHistory && leftBar.projectHistory.length <= 0") 18 | | You have no snapshots for this project. 19 | div(class="prompt-history-item", ng-class="{'active': leftBar.selectedSnapshot === item}", ng-repeat="item in leftBar.projectHistory | orderBy : $index : -1", ng-click="leftBar.selectedSnapshot = item;") 20 | div 21 | a(ng-click="leftBar.showSnapshotStatus[item.datetime] = true;", ng-show="!leftBar.showSnapshotStatus[item.datetime]") 22 | i(class="fa fa-caret-right color-black") 23 | a(ng-click="leftBar.showSnapshotStatus[item.datetime] = false;", ng-show="leftBar.showSnapshotStatus[item.datetime]") 24 | i(class="fa fa-caret-down color-black") 25 | i(class="fa fa-database color-primary") 26 | = " " 27 | | {{item.datetime}} 28 | div(class="prompt-history-status", ng-if="leftBar.showSnapshotStatus[item.datetime]", ng-ace-editor, ng-ace-editor-readonly="true" ng-ace-editor-mode="json", ng-ace-editor-theme="xcode", ng-model="aceFileModel", ng-ace-source="{{item.status}}") 29 | div(class="ng-ace-editor") 30 | -------------------------------------------------------------------------------- /lib/icons/fontello/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font license info 2 | 3 | 4 | ## Font Awesome 5 | 6 | Copyright (C) 2016 by Dave Gandy 7 | 8 | Author: Dave Gandy 9 | License: SIL () 10 | Homepage: http://fortawesome.github.com/Font-Awesome/ 11 | 12 | 13 | ## Entypo 14 | 15 | Copyright (C) 2012 by Daniel Bruce 16 | 17 | Author: Daniel Bruce 18 | License: SIL (http://scripts.sil.org/OFL) 19 | Homepage: http://www.entypo.com 20 | 21 | 22 | ## Modern Pictograms 23 | 24 | Copyright (c) 2012 by John Caserta. All rights reserved. 25 | 26 | Author: John Caserta 27 | License: SIL (http://scripts.sil.org/OFL) 28 | Homepage: http://thedesignoffice.org/project/modern-pictograms/ 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/icons/fontello/README.txt: -------------------------------------------------------------------------------- 1 | This webfont is generated by http://fontello.com open source project. 2 | 3 | 4 | ================================================================================ 5 | Please, note, that you should obey original font licenses, used to make this 6 | webfont pack. Details available in LICENSE.txt file. 7 | 8 | - Usually, it's enough to publish content of LICENSE.txt file somewhere on your 9 | site in "About" section. 10 | 11 | - If your project is open-source, usually, it will be ok to make LICENSE.txt 12 | file publicly available in your repository. 13 | 14 | - Fonts, used in Fontello, don't require a clickable link on your site. 15 | But any kind of additional authors crediting is welcome. 16 | ================================================================================ 17 | 18 | 19 | Comments on archive content 20 | --------------------------- 21 | 22 | - /font/* - fonts in different formats 23 | 24 | - /css/* - different kinds of css, for all situations. Should be ok with 25 | twitter bootstrap. Also, you can skip style and assign icon classes 26 | directly to text elements, if you don't mind about IE7. 27 | 28 | - demo.html - demo file, to show your webfont content 29 | 30 | - LICENSE.txt - license info about source fonts, used to build your one. 31 | 32 | - config.json - keeps your settings. You can import it back into fontello 33 | anytime, to continue your work 34 | 35 | 36 | Why so many CSS files ? 37 | ----------------------- 38 | 39 | Because we like to fit all your needs :) 40 | 41 | - basic file, .css - is usually enough, it contains @font-face 42 | and character code definitions 43 | 44 | - *-ie7.css - if you need IE7 support, but still don't wish to put char codes 45 | directly into html 46 | 47 | - *-codes.css and *-ie7-codes.css - if you like to use your own @font-face 48 | rules, but still wish to benefit from css generation. That can be very 49 | convenient for automated asset build systems. When you need to update font - 50 | no need to manually edit files, just override old version with archive 51 | content. See fontello source code for examples. 52 | 53 | - *-embedded.css - basic css file, but with embedded WOFF font, to avoid 54 | CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain. 55 | We strongly recommend to resolve this issue by `Access-Control-Allow-Origin` 56 | server headers. But if you ok with dirty hack - this file is for you. Note, 57 | that data url moved to separate @font-face to avoid problems with 2 | 3 | 4 | Copyright (C) 2017 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/icons/fontello/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/720kb/ndm/0ca1b3a8cb13ac0347fdadcb288bb3de4b70bbcd/lib/icons/fontello/font/fontello.ttf -------------------------------------------------------------------------------- /lib/icons/fontello/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/720kb/ndm/0ca1b3a8cb13ac0347fdadcb288bb3de4b70bbcd/lib/icons/fontello/font/fontello.woff -------------------------------------------------------------------------------- /lib/icons/fontello/font/fontello.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/720kb/ndm/0ca1b3a8cb13ac0347fdadcb288bb3de4b70bbcd/lib/icons/fontello/font/fontello.woff2 -------------------------------------------------------------------------------- /lib/img.scss: -------------------------------------------------------------------------------- 1 | img[src=""] { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /lib/img/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/img/npm-logo-cube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 11 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /lib/index.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset='UTF-8') 5 | 6 | link(rel='stylesheet', href='icons/fontello/css/fontello-embedded.css', media='screen', charset='utf-8') 7 | link(rel='stylesheet', href='../node_modules/bootstrap/dist/css/bootstrap.min.css', media='screen', charset='utf-8') 8 | link(rel='stylesheet', href='css/index.css', media='screen', charset='utf-8') 9 | 10 | script(type='text/javascript', src='../node_modules/angular/angular.min.js') 11 | script(type='text/javascript', src='../node_modules/selection-model/dist/selection-model.min.js') 12 | script(type='text/javascript', src='../node_modules/ace-builds/src-min-noconflict/ace.js') 13 | 14 | script(type='text/javascript', src='js/index.js') 15 | body(ng-app='ndm', ng-controller='ShellController as shell', ng-drag-drop) 16 | .app-big-loading 17 | img(src="img/loading.svg") 18 | div 19 | | Loading 20 | .page 21 | include ./left.pug 22 | .right-column 23 | include ./content.pug 24 | 25 | include ./footer.pug 26 | -------------------------------------------------------------------------------- /lib/install-new-package-version.pug: -------------------------------------------------------------------------------- 1 | div.dialog.prompt(ng-show="showSpecificVersionPrompt && currentSelectedPackages.length === 1", ng-init="versionPackageVersion = undefined") 2 | form(ng-submit='installVersionPackage(currentSelectedPackages[0], versionPackageVersion)') 3 | input(placeholder='Package name', 4 | type='text', 5 | readonly, 6 | disabled, 7 | ng-value="currentSelectedPackages[0].name") 8 | = " " 9 | input(class="hide", 10 | placeholder='@version', 11 | ng-autofocus, 12 | type='text', 13 | ng-model='versionPackageVersion', 14 | ng-value="versionPackageVersion") 15 | = " " 16 | span(class="prompt-kind") 17 | select(name="packageVersionSelect", ng-model="pkgVersionModel", ng-change="versionPackageVersion = pkgVersionModel") 18 | option(value="", selected) 19 | | - 20 | option(ng-repeat="pkgVersion in selectedPackageViewInfos.versions | orderBy : pkgVersion : 'reverse' track by $index", ng-value="pkgVersion") 21 | | {{pkgVersion}} 22 | button(ng-disabled="installingPackageVersion") 23 | span(ng-show="!installingPackageVersion") 24 | | Install 25 | span(ng-show="installingPackageVersion") 26 | img(src="img/loading.svg", width="13") 27 | = " " 28 | = " " 29 | button(class="button-close-prompt pull-right", type="button", ng-click="hideInstallVersionPrompt();") 30 | i(class="fa fa-remove") 31 | -------------------------------------------------------------------------------- /lib/install-new-package.pug: -------------------------------------------------------------------------------- 1 | div.dialog.prompt(ng-show="showInstallPrompt") 2 | form(ng-submit='installPackage(packageName, newPackageKind)') 3 | 4 | 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> ...") 5 | 6 | input(ng-hide="true", ng-model="searchKeywords", ng-bind="packageName") 7 | = " " 8 | span(class="prompt-kind") 9 | input(type="checkbox", ng-model="newPackageKind", ng-disabled="tab === ''") 10 | = " " 11 | = " " 12 | | dev 13 | = " " 14 | 15 | button(id="install-new-packages-button", ng-disabled="installingPackage || !packageName") 16 | span(ng-show="!installingPackage") 17 | | Install 18 | span(ng-show="installingPackage") 19 | img(src="img/loading.svg", width="13") 20 | = " " 21 | = " " 22 | button(class="button-close-prompt pull-right", type="button", ng-click="hideInstallPrompt();") 23 | i(class="fa fa-remove") 24 | .prompt-search(ng-hide="installingPackage") 25 | .prompt-search-content 26 | .prompt-search-item(ng-repeat="item in searchResults.objects", ng-click="searchChoosePackage(item.package.name)") 27 | h5 28 | | {{item.package.name}} 29 | div 30 | | {{item.package.description}} 31 | .prompt-search-loader(ng-show="searchingNpm") 32 | img(src="img/loading.svg") 33 | | Loading results ... 34 | -------------------------------------------------------------------------------- /lib/js/assets.js: -------------------------------------------------------------------------------- 1 | /*global require,console*/ 2 | import angular from 'angular'; 3 | 4 | const moduleName = 'npm-ui.assets' 5 | , fs = require('fs') 6 | , path = require('path') 7 | , storage = require('electron-storage'); 8 | 9 | angular.module(moduleName, []) 10 | .provider('assets', /*@ngInject*/ function Session() { 11 | const projectsFolder = 'projects.json' 12 | , projects = []; 13 | 14 | storage.get(projectsFolder) 15 | .then(data => { 16 | 17 | if (data && 18 | data.length) { 19 | 20 | data.forEach(item => { 21 | let isPath 22 | , isShrinkwrap; 23 | 24 | if (item && 25 | item.path) { 26 | 27 | try { 28 | //is a directory? 29 | if (fs.lstatSync(item.path).isDirectory()) { 30 | isPath = true; 31 | } else { 32 | isPath = false; 33 | } 34 | } catch (excp) { 35 | isPath = false; 36 | console.warn(`Unable to read project path: ${excp}`); 37 | } 38 | 39 | try { 40 | //is shrinkwrapped -> has npm-shrinkwrap.json inside? 41 | if (fs.existsSync(path.join(item.path, 'npm-shrinkwrap.json'))) { 42 | isShrinkwrap = true; 43 | } else { 44 | isShrinkwrap = false; 45 | } 46 | } catch (excp) { 47 | isShrinkwrap = false; 48 | console.warn(`No npm-shrinkwrap.json found in project path: ${excp}`); 49 | } 50 | 51 | if (isShrinkwrap) { 52 | item.shrinkwrap = true; 53 | } else { 54 | item.shrinkwrap = false; 55 | } 56 | 57 | if (isPath) { 58 | projects.push(item); 59 | } 60 | } 61 | }); 62 | } 63 | }) 64 | .catch(err => () => { 65 | console.err(`Unable to retrieve saved projects: ${err}`); 66 | }); 67 | 68 | projects.save = projectInfo => storage.set(projectsFolder, projectInfo); 69 | 70 | this.$get = /*@ngInject*/ () => ({ 71 | projects 72 | }); 73 | }); 74 | 75 | export default moduleName; 76 | -------------------------------------------------------------------------------- /lib/js/directives/ng-ace-editor.js: -------------------------------------------------------------------------------- 1 | /*global require window*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.ng-ace-editor' 4 | , fs = require('fs') 5 | , ace = window.ace; 6 | 7 | angular.module(moduleName, []) 8 | .directive('ngAceEditor', /*@ngInject*/ function ngAceEditor($rootScope, $document) { 9 | return { 10 | 'require': '?ngModel', 11 | 'link': (scope, element, attrs, ngModel) => { 12 | 13 | const editorElement = element[0].querySelector('.ng-ace-editor') 14 | , aceEditor = ace.edit(editorElement) 15 | , aceSession = aceEditor.getSession() 16 | , theme = attrs.ngAceEditorTheme 17 | , readonly = scope.$eval(attrs.ngAceEditorReadonly) 18 | , setAceMode = () => { 19 | if (attrs.ngAceFileName.endsWith('.json')) { 20 | 21 | aceSession.setMode('ace/mode/json'); 22 | } else if (attrs.ngAceFileName.startsWith('.')) { 23 | aceSession.setMode('ace/mode/text'); 24 | } 25 | } 26 | , unregisterSavedFile = $rootScope.$on('ace-editor:saved-file', () => { 27 | scope.$evalAsync(() => { 28 | scope.savingFile = false; 29 | scope.savedFile = true; 30 | }); 31 | }) 32 | , unregisterSavingFile = $rootScope.$on('ace-editor:saving-file', () => { 33 | scope.$evalAsync(() => { 34 | scope.savingFile = true; 35 | scope.savedFile = false; 36 | }); 37 | }) 38 | , unregisterLoadedFile = $rootScope.$on('ace-editor:loaded-file', () => { 39 | scope.$evalAsync(() => { 40 | scope.loadingFile = false; 41 | aceEditor.focus(); 42 | }); 43 | }) 44 | , unregisterLoadingFile = $rootScope.$on('ace-editor:loading-file', () => { 45 | scope.$evalAsync(() => { 46 | scope.loadingFile = true; 47 | scope.savedFile = false; 48 | scope.savingFile = false; 49 | setAceMode(); 50 | }); 51 | }); 52 | 53 | attrs.$observe('ngAceFile', filePath => { 54 | if (filePath) { 55 | $rootScope.$emit('ace-editor:loading-file', { 56 | 'path': filePath 57 | }); 58 | try { 59 | if (fs.existsSync(filePath)) { 60 | scope.aceFileModel = fs.readFileSync(filePath).toString(); 61 | } else { 62 | scope.aceFileModel = ''; 63 | aceEditor.setValue(''); 64 | } 65 | } catch (e) { 66 | scope.aceFileModel = ''; 67 | aceEditor.setValue(''); 68 | } 69 | 70 | $rootScope.$emit('ace-editor:loaded-file', { 71 | 'path': filePath, 72 | 'content': scope.aceFileModel 73 | }); 74 | } 75 | }); 76 | 77 | attrs.$observe('ngAceSource', source => { 78 | if (source) { 79 | scope.aceFileModel = source; 80 | } else { 81 | scope.aceFileModel = ''; 82 | } 83 | }); 84 | 85 | scope.saveFile = () => { 86 | $rootScope.$emit('ace-editor:saving-file', { 87 | 'path': attrs.ngAceFile, 88 | 'content': scope.aceFileModel 89 | }); 90 | fs.writeFileSync(attrs.ngAceFile, scope.aceFileModel, {'flag': 'w'}, 'utf8'); 91 | $rootScope.$emit('ace-editor:saved-file', { 92 | 'path': attrs.ngAceFile, 93 | 'content': scope.aceFileModel 94 | }); 95 | }; 96 | 97 | scope.$watch(() => { 98 | return [editorElement.offsetWidth, editorElement.offsetHeight]; 99 | }, () => { 100 | aceEditor.resize(); 101 | aceEditor.setOptions({ 102 | 'showInvisibles': true, 103 | 'cursorStyle': 'smooth', 104 | 'highlightSelectedWord': true, 105 | 'theme': `ace/theme/${theme}`, 106 | 'readOnly': readonly 107 | }); 108 | aceEditor.renderer.updateFull(); 109 | }, true); 110 | 111 | aceSession.on('change', () => { 112 | if (aceSession.getValue()) { 113 | ngModel.$setViewValue(aceSession.getValue()); 114 | } 115 | }); 116 | 117 | ngModel.$render = () => { 118 | if (ngModel.$viewValue) { 119 | aceSession.setValue(ngModel.$viewValue); 120 | } 121 | }; 122 | 123 | $document.on('mousedown mouseup mouseover', () => { 124 | aceEditor.resize(); 125 | }); 126 | 127 | element.on('$destroy', () => { 128 | aceSession.$stopWorker(); 129 | aceEditor.destroy(); 130 | unregisterSavingFile(); 131 | unregisterSavedFile(); 132 | unregisterLoadingFile(); 133 | unregisterLoadedFile(); 134 | }); 135 | } 136 | }; 137 | }); 138 | 139 | export default moduleName; 140 | -------------------------------------------------------------------------------- /lib/js/directives/ng-auto-scroll.js: -------------------------------------------------------------------------------- 1 | /*global*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.ng-autoscroll'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('ngAutoscroll', /*@ngInject*/ function ngAutoscroll() { 7 | return (scope, element) => { 8 | scope.$watch(() => { 9 | element[0].scrollTop = element[0].scrollHeight; 10 | }); 11 | }; 12 | }); 13 | 14 | export default moduleName; 15 | -------------------------------------------------------------------------------- /lib/js/directives/ng-autofocus.js: -------------------------------------------------------------------------------- 1 | /*global*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.autofocus'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('ngAutofocus', /*@ngInject*/ function ngAutofocus() { 7 | return (scope, element) => { 8 | scope.$evalAsync(() => { 9 | element[0].focus(); 10 | }); 11 | }; 12 | }); 13 | 14 | export default moduleName; 15 | -------------------------------------------------------------------------------- /lib/js/directives/ng-drag-drop.js: -------------------------------------------------------------------------------- 1 | /*global*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.ng-drag-drop'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('ngDragDrop', /*@ngInject*/ function ngDragAndDrop($rootScope) { 7 | return (scope, element) => { 8 | element.on('drop', event => { 9 | event.preventDefault(); 10 | element.removeClass('dragging'); 11 | $rootScope.$emit('shell:file-drop', event); 12 | }); 13 | element.on('dragover', event => { 14 | event.preventDefault(); 15 | element.addClass('dragging'); 16 | }); 17 | element.on('dragleave', event => { 18 | event.preventDefault(); 19 | element.removeClass('dragging'); 20 | }); 21 | }; 22 | }); 23 | 24 | export default moduleName; 25 | -------------------------------------------------------------------------------- /lib/js/directives/ng-resizable.js: -------------------------------------------------------------------------------- 1 | /*global*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.ng-resizable'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('ngResizable', /*@ngInject*/ function ngResizable($window, $document) { 7 | return (scope, element) => { 8 | 9 | let getMaxHeight = () => { 10 | return Number($window.innerHeight - 120); 11 | } 12 | , maxHeight = getMaxHeight() 13 | , minHeight = 250; 14 | 15 | const onMouseMove = event => { 16 | 17 | element.css({ 18 | 'height': `${event.pageY - Number(element[0].offsetTop)}px` 19 | }); 20 | 21 | if (element[0].offsetHeight <= minHeight) { 22 | element.css('height', `${minHeight}px`); 23 | } 24 | 25 | if (element[0].offsetHeight >= maxHeight) { 26 | element.css('height', `${maxHeight}px`); 27 | } 28 | } 29 | , onMouseUp = () => { 30 | $document.unbind('mousemove', onMouseMove); 31 | $document.unbind('mouseup', onMouseUp); 32 | }; 33 | 34 | element.on('mousedown', event => { 35 | event.preventDefault(); 36 | $document.on('mousemove', onMouseMove); 37 | $document.on('mouseup', onMouseUp); 38 | }); 39 | 40 | angular.element($window).on('resize', () => { 41 | maxHeight = getMaxHeight(); 42 | }); 43 | }; 44 | }); 45 | 46 | export default moduleName; 47 | -------------------------------------------------------------------------------- /lib/js/directives/ng-right-click.js: -------------------------------------------------------------------------------- 1 | /*global*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.ng-right-click'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('ngRightClick', /*@ngInject*/ function ngRightClick($parse) { 7 | return (scope, element, attrs) => { 8 | 9 | element.on('contextmenu', event => { 10 | scope.$apply(() => { 11 | event.preventDefault(); 12 | let fn = $parse(attrs.ngRightClick); 13 | 14 | fn(scope, { 15 | '$event': event 16 | }); 17 | }); 18 | }); 19 | }; 20 | }); 21 | 22 | export default moduleName; 23 | -------------------------------------------------------------------------------- /lib/js/directives/ng-table-keyboard.js: -------------------------------------------------------------------------------- 1 | /*global*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.ng-table-keyboard'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('ngTableKeyboard', /*@ngInject*/ function ngTableKeyboard($window, $document) { 7 | return (scope, element) => { 8 | 9 | const onArrowDown = () => { 10 | let tableRows = element[0].querySelectorAll('.tab:not(.ng-hide) .table-row:not(.disabled)') 11 | , clickedElement; 12 | 13 | if (tableRows && tableRows.length > 0) { 14 | tableRows.forEach((row, index) => { 15 | if (angular.element(row).hasClass('selected')) { 16 | if (!clickedElement) { 17 | if (tableRows[index + 1]) { 18 | clickedElement = true; 19 | angular.element(tableRows[index + 1]).triggerHandler('click'); 20 | } 21 | } 22 | } 23 | }); 24 | if (!clickedElement) { 25 | angular.element(tableRows[0]).triggerHandler('click'); 26 | } 27 | } 28 | } 29 | , onArrowUp = () => { 30 | let tableRows = element[0].querySelectorAll('.table-row:not(.disabled)') 31 | , clickedElement; 32 | 33 | if (tableRows && tableRows.length > 0) { 34 | tableRows.forEach((row, index) => { 35 | if (angular.element(row).hasClass('selected')) { 36 | if (!clickedElement) { 37 | if (tableRows[index - 1]) { 38 | clickedElement = true; 39 | angular.element(tableRows[index - 1]).triggerHandler('click'); 40 | return false; 41 | } 42 | } 43 | } 44 | }); 45 | if (!clickedElement) { 46 | angular.element(tableRows[0]).triggerHandler('click'); 47 | } 48 | } 49 | } 50 | , bindOnKey = event => { 51 | //if not loading 52 | if (!angular.element($document[0].body).hasClass('loading')) { 53 | if (event && 54 | event.keyCode) { 55 | if (event.keyCode === 40) { 56 | onArrowDown(); 57 | } 58 | if (event.keyCode === 38) { 59 | onArrowUp(); 60 | } 61 | } 62 | } 63 | }; 64 | 65 | angular.element($window).bind('keydown', bindOnKey); 66 | scope.$on('destroy', () => { 67 | angular.element($window).unbind('keydown', bindOnKey); 68 | }); 69 | }; 70 | }); 71 | 72 | export default moduleName; 73 | -------------------------------------------------------------------------------- /lib/js/directives/ng-tag-input.js: -------------------------------------------------------------------------------- 1 | /*global document window*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.ng-tag-input'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('ngTagInput', /*@ngInject*/ function ngTagInput($rootScope, $log) { 7 | return { 8 | 'require': '?ngModel', 9 | 'link': (scope, element, attrs, ngModel) => { 10 | let ngTagInputIdentifier = attrs.tabPathId 11 | , documentRange 12 | , windowSelection 13 | , focusTheEnd = () => { 14 | try { 15 | element[0].focus(); 16 | documentRange = document.createRange(); 17 | documentRange.selectNodeContents(element[0].lastChild); 18 | documentRange.collapse(false); 19 | windowSelection = window.getSelection(); 20 | windowSelection.removeAllRanges(); 21 | windowSelection.addRange(documentRange); 22 | } catch (excp) { 23 | $log.warn('ng-tag-input warning when setting focus', excp); 24 | } 25 | } 26 | , createTags = () => { 27 | try { 28 | let digits = element[0].innerText.split(' ') 29 | , tags = []; 30 | 31 | if (digits && 32 | digits.length > 0) { 33 | digits.forEach(tag => { 34 | if (tag.trim().length > 0) { 35 | tags.push(`${tag.trim()}`); 36 | } else { 37 | tags.push(tag); 38 | } 39 | }); 40 | if (tags && 41 | tags.length > 0) { 42 | element.html(`${tags.join(' ')}  `); 43 | focusTheEnd(); 44 | } 45 | } 46 | } catch (excp) { 47 | $log.warn('ng-tag-input warning', excp); 48 | } 49 | } 50 | , updateModel = () => { 51 | 52 | let packages = element[0].innerText.trim().split(' ') 53 | , modelValue = [] 54 | , pkgName 55 | , pkgVersion; 56 | 57 | packages.forEach(item => { 58 | if (item.trim() && 59 | item.trim().length > 0) { 60 | 61 | if (item.includes('@')) { 62 | pkgName = item.split('@')[0].replace('@', '').trim(); 63 | pkgVersion = item.split('@')[1].replace('@', '').trim(); 64 | } else { 65 | pkgName = item.trim(); 66 | pkgVersion = false; 67 | } 68 | modelValue.push({ 69 | 'name': pkgName, 70 | 'version': pkgVersion 71 | }); 72 | } 73 | }); 74 | ngModel.$setViewValue(modelValue); 75 | } 76 | , onBlur = () => { 77 | updateModel(); 78 | } 79 | , onKeyUp = event => { 80 | updateModel(); 81 | if (element[0].innerText.trim().length > 0 && 82 | ((event.keyCode && event.keyCode === 32) || 83 | (event.which && event.which === 32)) 84 | ) { 85 | createTags(); 86 | updateModel(); 87 | } 88 | } 89 | , onKeyDown = event => { 90 | updateModel(); 91 | if (event && 92 | event.keyCode && 93 | event.keyCode.toString() === '13' && 94 | element[0].innerText.trim().length > 0) { //enter key to submit form 95 | try { 96 | createTags(); 97 | updateModel(); 98 | //find button to submit form 99 | angular.element(document.querySelector('#install-new-packages-button'))[0].click(); 100 | } catch (excp) { 101 | $log.warn('Cannot find form to submit', excp); 102 | } 103 | } 104 | } 105 | , onKeyPress = () => { 106 | updateModel(); 107 | } 108 | , onPaste = () => { 109 | scope.$evalAsync(() => { 110 | createTags(); 111 | updateModel(); 112 | }); 113 | } 114 | , onTrigger = () => { 115 | focusTheEnd(); 116 | } 117 | , onKeypressDisabled = event => { 118 | return event.preventDefault(); 119 | } 120 | , updateOnSearchChoosenPackage = $rootScope.$on('top-menu:search-choosen-package', (eventInformation, data) => { 121 | if (data && 122 | data.data && 123 | data.tabPath && 124 | data.tabPath === ngTagInputIdentifier) { //if search input is showing on this specific tab 125 | 126 | let newInputValue = ''; 127 | 128 | data.data.forEach(pkg => { 129 | newInputValue += pkg.name; 130 | if (pkg.version && 131 | pkg.version.length > 0) { 132 | newInputValue += `@${pkg.version}`; 133 | } 134 | newInputValue += ' '; //leave a blank space at the end of the string to split into tags again 135 | }); 136 | 137 | element[0].innerText = newInputValue; 138 | createTags(); 139 | updateModel(); 140 | } 141 | }); 142 | 143 | element.on('mousedown', onTrigger); 144 | element.on('click', onTrigger); 145 | element.on('paste', onPaste); 146 | element.on('blur', onBlur); 147 | element.on('keyup', onKeyUp); 148 | element.on('keydown', onKeyDown); 149 | element.on('keypress', onKeyPress); 150 | //disable input on submit 151 | attrs.$observe('disabled', value => { 152 | if (value === 'disabled') { 153 | element.on('keypress', onKeypressDisabled); 154 | } else { 155 | element.unbind('keypress', onKeypressDisabled); 156 | } 157 | }); 158 | scope.$on('$destroy', () => { 159 | updateOnSearchChoosenPackage(); 160 | element.unbind('keyup', onKeyUp); 161 | element.unbind('keydown', onKeyDown); 162 | element.unbind('paste', onPaste); 163 | element.unbind('mousedown', onTrigger); 164 | element.unbind('click', onTrigger); 165 | element.unbind('keypress', onKeyPress); 166 | element.unbind('blur', onBlur); 167 | element.unbind('keypress', onKeypressDisabled); 168 | }); 169 | } 170 | }; 171 | }); 172 | 173 | export default moduleName; 174 | -------------------------------------------------------------------------------- /lib/js/errors.js: -------------------------------------------------------------------------------- 1 | /*global require*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.errors-handler' 4 | , electron = require('electron') 5 | , {remote} = electron 6 | , dialog = remote.dialog; 7 | 8 | angular.module(moduleName, []) 9 | .service('errorsService', /*@ngInject*/ function ErrorHandler($log) { 10 | 11 | this.handleError = (message, error) => { 12 | 13 | /*if (error && error.toString().includes('EACCES')) { 14 | dialog.showErrorBox(message, `\n\n${error}\n\nThis kind of error usually happen when npm has no granted permissions on your machine.\n\nBe sure to have fixed npm permissions.\n\nCheck this simple tutorial on how to fix them: \nhttps://docs.npmjs.com/getting-started/fixing-npm-permissions.`); 15 | }*/ 16 | $log.error(message, error); 17 | }; 18 | 19 | this.showErrorBox = (message, error) => { 20 | 21 | dialog.showErrorBox(message, error); 22 | }; 23 | }); 24 | 25 | export default moduleName; 26 | -------------------------------------------------------------------------------- /lib/js/filters.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | const moduleName = 'npm-ui.filters'; 3 | 4 | angular.module(moduleName, []) 5 | .filter('removeHTML', () => { 6 | return string => { 7 | return string.replace(/<\/?[^>]+(>|$)/g, ''); 8 | }; 9 | }) 10 | .filter('lastNameInPath', () => { 11 | return string => { 12 | let toReturn 13 | , split; 14 | 15 | if (string.includes('\\')) { 16 | //on windows 17 | split = string.split('\\'); 18 | toReturn = split[split.length - 1]; 19 | } 20 | 21 | if (string.includes('/')) { 22 | //on linux and mac 23 | split = string.split('/'); 24 | toReturn = split[split.length - 1]; 25 | } 26 | return toReturn ? toReturn : string; 27 | }; 28 | }); 29 | 30 | export default moduleName; 31 | -------------------------------------------------------------------------------- /lib/js/index.js: -------------------------------------------------------------------------------- 1 | /*globals require process navigator */ 2 | import angular from 'angular'; 3 | import shellModule from './interface/shell.js'; 4 | import contentModule from './interface/content.js'; 5 | import leftModule from './interface/left.js'; 6 | import topModule from './interface/top.js'; 7 | import ngRightClickModule from './directives/ng-right-click.js'; 8 | import ngDragDropModule from './directives/ng-drag-drop.js'; 9 | import ngAceEditor from './directives/ng-ace-editor.js'; 10 | import ngResizable from './directives/ng-resizable.js'; 11 | import ngAutoscroll from './directives/ng-auto-scroll.js'; 12 | import ngAutofocus from './directives/ng-autofocus.js'; 13 | import ngTagInput from './directives/ng-tag-input.js'; 14 | import ngTableKeyboard from './directives/ng-table-keyboard.js'; 15 | 16 | import assetsModule from './assets.js'; 17 | import loadingModule from './loading.js'; 18 | import errorsModule from './errors.js'; 19 | import filtersModule from './filters.js'; 20 | import notificationModule from './notification.js'; 21 | 22 | const Storage = require('electron-storage') 23 | , {ipcRenderer} = require('electron') 24 | , analytics = require('universal-analytics') 25 | , uuid = require('uuid/v4') 26 | , visitorId = uuid() 27 | , visitor = analytics('UA-90211405-1', visitorId); 28 | 29 | angular.module('ndm', [ 30 | 'selectionModel', 31 | shellModule, 32 | contentModule, 33 | leftModule, 34 | topModule, 35 | loadingModule, 36 | notificationModule, 37 | errorsModule, 38 | ngRightClickModule, 39 | ngDragDropModule, 40 | ngAceEditor, 41 | ngResizable, 42 | ngAutoscroll, 43 | ngAutofocus, 44 | ngTagInput, 45 | ngTableKeyboard, 46 | assetsModule, 47 | filtersModule 48 | ]) 49 | .constant('timeoutForWhenUserIsPresent', 2500) 50 | .constant('appHistoryFile', 'snapshots.json') 51 | .constant('npmGlobal', '') 52 | .run(/*@ngInject*/ function RunInitStorage(appHistoryFile, $log) { 53 | //create storage file in case 54 | Storage.isPathExists(appHistoryFile, exist => { 55 | if (exist) { 56 | $log.info('Storage: OK'); 57 | } else { 58 | Storage.set(appHistoryFile, '{}', err => { 59 | if (err) { 60 | $log.error('Not able to initialize storage for the app'); 61 | } else { 62 | $log.info('Storage initialized for the app'); 63 | } 64 | }); 65 | } 66 | }); 67 | }) 68 | .run(/*@ngInject*/ function onLoadingEvents(loadingFactory) { 69 | ipcRenderer.on('loading:freeze-app', () => { 70 | loadingFactory.freeze(); 71 | }); 72 | 73 | ipcRenderer.on('loading:unfreeze-app', () => { 74 | loadingFactory.unfreeze(); 75 | }); 76 | }) 77 | .run(/*@ngInject*/ function RunOnlineOfflineCheck($window, notificationFactory) { 78 | //alert user when he goes offLine 79 | const showMessageAlert = () => { 80 | notificationFactory.notify('You are offline. ndm may not work as expected.', true); 81 | } 82 | , onOffline = () => { 83 | showMessageAlert(); 84 | } 85 | , onStart = () => { 86 | if (navigator && 87 | !navigator.onLine) { 88 | showMessageAlert(); 89 | } 90 | }; 91 | 92 | angular.element($window).on('offline', onOffline); 93 | onStart(); 94 | }) 95 | .run(/*ngInject*/ function runDomReady($document, $rootScope, $timeout, $log, loadingFactory, timeoutForWhenUserIsPresent) { 96 | $document.ready(() => { 97 | $log.info('DOM is ready'); 98 | //communicate to the app DOM is ready 99 | $rootScope.$emit('dom:ready'); 100 | $timeout(() => { 101 | //ga user is on 102 | try { 103 | visitor.pageview(`/platform/${process.platform}`).send(); 104 | $log.info(`Platform ${process.platform}`); 105 | } catch (excp) { 106 | $log.warn('Unable to send ga pageview', excp); 107 | } 108 | }, timeoutForWhenUserIsPresent); 109 | }); 110 | }) 111 | .run(/*ngInject*/ function runNpmReady($rootScope, $log, loadingFactory) { 112 | $rootScope.$on('npm:ready', () => { 113 | $log.info('npm is ready'); 114 | loadingFactory.appReady(); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /lib/js/interface/top.js: -------------------------------------------------------------------------------- 1 | /*global */ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.top-menu'; 4 | 5 | angular.module(moduleName, []) 6 | .directive('topMenu', /*@ngInject*/ function TopMenuController($document, $rootScope, $log, $timeout, npm) { 7 | return (scope, element, attrs) => { 8 | 9 | let searchTimeout //debounce search 10 | , prevSearchKeyword; 11 | 12 | const topMenuIdentifierPath = attrs.topMenuProjectPathId; 13 | 14 | scope.destroyActiveClickedLink = () => { 15 | scope.activeLink = undefined; 16 | }; 17 | 18 | scope.activeClickedLink = activeLink => { 19 | if ((activeLink === '1' || activeLink === '4') && 20 | scope.activeLink === activeLink) { 21 | //toggle prompts show/hide 22 | scope.activeLink = false; 23 | } else { 24 | 25 | scope.activeLink = activeLink; 26 | $rootScope.$emit('top-bar:active-link', { 27 | 'link': activeLink 28 | }); 29 | } 30 | }; 31 | 32 | scope.search = keyword => { 33 | $log.info('search', keyword); 34 | if (keyword && 35 | keyword.trim() !== prevSearchKeyword) { 36 | /*eslint-disable*/ 37 | if (searchTimeout) { 38 | $timeout.cancel(searchTimeout); 39 | } 40 | prevSearchKeyword = keyword; 41 | /*eslint-enable*/ 42 | searchTimeout = $timeout(() => { 43 | scope.searchingNpm = true; 44 | scope.searchResults = []; 45 | npm.npmInFolder(topMenuIdentifierPath).then(npmInFolder => { 46 | npmInFolder.search(keyword).then(data => { 47 | scope.$apply(() => { 48 | scope.searchingNpm = false; 49 | scope.searchResults = data; 50 | }); 51 | }).catch(err => { 52 | scope.$apply(() => { 53 | scope.searchingNpm = false; 54 | scope.searchResults = []; 55 | }); 56 | $log.error('SEARCH ERROR', err); 57 | }); 58 | }); 59 | }, 500); 60 | } else { 61 | scope.searchingNpm = false; 62 | scope.searchResults = []; 63 | } 64 | }; 65 | 66 | scope.searchChoosePackage = pkgName => { 67 | //update digits in input 68 | scope.$evalAsync(() => { 69 | scope.packageName[scope.packageName.length - 1].name = pkgName; 70 | scope.searchResults = []; 71 | $log.warn(pkgName, scope.packageName); 72 | //communicate to ng-tag-input to update itself and model 73 | $rootScope.$emit('top-menu:search-choosen-package', {'data': scope.packageName, 'tabPath': topMenuIdentifierPath}); 74 | }); 75 | }; 76 | 77 | scope.hideInstallPrompt = () => { 78 | scope.showInstallPrompt = false; 79 | }; 80 | 81 | scope.hideInstallVersionPrompt = () => { 82 | scope.showSpecificVersionPrompt = false; 83 | }; 84 | }; 85 | }); 86 | 87 | export default moduleName; 88 | -------------------------------------------------------------------------------- /lib/js/loading.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | const moduleName = 'npm-ui.loading'; 3 | 4 | angular.module(moduleName, []) 5 | .service('loadingFactory', /*@ngInject*/ function loadingFactory($document) { 6 | 7 | const bodyElement = $document.find('body') 8 | , appReady = () => { 9 | bodyElement.addClass('ready'); 10 | } 11 | , loading = () => { 12 | // bodyElement.addClass('loading'); 13 | } 14 | , finished = () => { 15 | bodyElement.removeClass('loading'); 16 | } 17 | , freeze = () => { 18 | bodyElement.addClass('freezed'); 19 | } 20 | , unfreeze = () => { 21 | bodyElement.removeClass('freezed'); 22 | }; 23 | 24 | return { 25 | loading, 26 | finished, 27 | freeze, 28 | unfreeze, 29 | appReady 30 | }; 31 | }) 32 | .directive('npmLoading', /*@ngInject*/ $rootScope => { 33 | 34 | return { 35 | 'scope': true, 36 | 'restrict': 'A', 37 | 'templateUrl': 'npm-update-log.html', 38 | 'controller': /*@ngInject*/ function NpmLoadingController($scope) { 39 | 40 | const unregisterOnNpmLogs = $rootScope.$on('npm:log:log', (eventInfo, npmLog) => { 41 | $rootScope.$apply(() => { 42 | if (npmLog.type === 'installLatest' && 43 | npmLog.data) { 44 | $scope.log.logs.push(npmLog.data); 45 | } 46 | }); 47 | }); 48 | 49 | this.logs = []; 50 | 51 | $scope.$on('$destroy', () => { 52 | unregisterOnNpmLogs(); 53 | }); 54 | }, 55 | 'controllerAs': 'log' 56 | }; 57 | }); 58 | 59 | export default moduleName; 60 | -------------------------------------------------------------------------------- /lib/js/notification.js: -------------------------------------------------------------------------------- 1 | /*global require Notification*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-ui.notification' 4 | , electron = require('electron') 5 | , BrowserWindow = electron.remote.BrowserWindow; 6 | 7 | angular.module(moduleName, []) 8 | .service('notificationFactory', /*@ngInject*/ () => { 9 | 10 | const notify = (body, skeepFocus, onClickCallback) => { 11 | if (!BrowserWindow.getFocusedWindow() || skeepFocus) { 12 | 13 | let windows 14 | , notification = new Notification('ndm', { 15 | body, 16 | 'sticky': true 17 | }); 18 | 19 | notification.onclick = () => { 20 | if (!skeepFocus) { 21 | windows = BrowserWindow.getAllWindows(); 22 | if (windows[0] && windows[1]) { 23 | //hide updates window and show main window 24 | windows[0].hide(); 25 | windows[1].show(); 26 | windows[1].focus(); 27 | windows[0].hide(); 28 | } 29 | } 30 | notification = undefined; 31 | 32 | if (onClickCallback) { 33 | return onClickCallback(); 34 | } 35 | }; 36 | } 37 | }; 38 | 39 | return { 40 | notify 41 | }; 42 | }); 43 | 44 | export default moduleName; 45 | -------------------------------------------------------------------------------- /lib/js/npm/npm-api.js: -------------------------------------------------------------------------------- 1 | /*global require navigator process,__dirname*/ 2 | import angular from 'angular'; 3 | const moduleName = 'npm-api.service' 4 | , fs = require('fs') 5 | , path = require('path') 6 | , cp = require('child_process'); 7 | 8 | angular.module(moduleName, []) 9 | .service('npm', /*@ngInject*/ function NpmService($rootScope, $log, errorsService) { 10 | const configureNpm = (folder, isGlobal) => new Promise(resolve => { 11 | const forkFactory = (command, param1, param2) => new Promise((forkFactoryResolved, forkFactoryRejected) => { 12 | const theParams = [folder, isGlobal] 13 | .concat([command, param1, param2]) 14 | .map(element => { 15 | 16 | if (Object.prototype.toString.call(element) === '[object Object]') { 17 | 18 | return JSON.stringify(element); 19 | } 20 | 21 | return element; 22 | }) 23 | , child = cp.fork(path.resolve(__dirname, 'npm-runner.js'), theParams, { 24 | 'cwd': __dirname, 25 | 'silent': true 26 | }); 27 | 28 | child.stdout.on('data', data => { 29 | $log.info(`stdout: ${data}`); 30 | }); 31 | 32 | child.stderr.on('data', data => { 33 | forkFactoryRejected(data); 34 | $log.error(`stderr: ${data}`); 35 | }); 36 | 37 | child.on('close', code => { 38 | $rootScope.$emit('npm:log:end', { 39 | 'type': command, 40 | 'message': code 41 | }); 42 | $log.info(`End of command ${command}`); 43 | $log.info(`child process exited with code ${code}`); 44 | }); 45 | 46 | child.on('message', message => { 47 | 48 | if (command === message.type) { 49 | 50 | forkFactoryResolved(message.payload); 51 | } else if (message.type === 'log') { 52 | 53 | $rootScope.$emit('npm:log:log', { 54 | 'type': command, 55 | 'data': message.payload 56 | }); 57 | } else { 58 | 59 | $log.debug(message); 60 | } 61 | }); 62 | }); 63 | 64 | resolve({ 65 | 'ping': () => { 66 | return forkFactory('ping'); 67 | }, 68 | 'launchInstall': () => { 69 | return forkFactory('launchInstall'); 70 | }, 71 | 'search': keyword => { 72 | return forkFactory('search', keyword); 73 | }, 74 | 'run': scriptName => { 75 | return forkFactory('run', scriptName); 76 | }, 77 | 'view': packageName => { 78 | return forkFactory('view', packageName); 79 | }, 80 | 'build': buildFolder => { 81 | return forkFactory('build', buildFolder); 82 | }, 83 | 'rebuild': () => { 84 | return forkFactory('rebuild'); 85 | }, 86 | 'install': (dependency, version) => { 87 | return forkFactory('install', dependency, version); 88 | }, 89 | 'installLatest': dependency => { 90 | return forkFactory('installLatest', dependency); 91 | }, 92 | 'update': dependency => { 93 | return forkFactory('update', dependency); 94 | }, 95 | 'rm': dependency => { 96 | return forkFactory('rm', dependency); 97 | }, 98 | 'listOutdated': () => { 99 | return forkFactory('listOutdated'); 100 | }, 101 | 'outdated': () => { 102 | return forkFactory('outdated'); 103 | }, 104 | 'prune': () => { 105 | return forkFactory('prune'); 106 | }, 107 | 'dedupe': () => { 108 | return forkFactory('dedupe'); 109 | }, 110 | 'list': () => { 111 | return forkFactory('list'); 112 | }, 113 | 'shrinkwrap': () => { 114 | return forkFactory('shrinkwrap'); 115 | }, 116 | 'doctor': () => { 117 | return forkFactory('doctor'); 118 | }, 119 | 'root': () => { 120 | return forkFactory('root'); 121 | } 122 | }); 123 | }) 124 | , getNpmVersion = () => new Promise(resolve => { 125 | cp.exec('npm -v', (err, stdout, stderr) => { 126 | let npmVersion; 127 | 128 | $log.warn(err, stderr); 129 | 130 | if (stdout && 131 | stdout.length > 0) { 132 | npmVersion = stdout.toString(); 133 | } 134 | resolve(npmVersion); 135 | }); 136 | }) 137 | , isNpmGloballyInstalled = () => new Promise((resolve, reject) => { 138 | cp.exec('npm -v', err => { 139 | if (err) { 140 | reject(err); 141 | } else { 142 | resolve(); 143 | } 144 | }); 145 | }) 146 | , pingRegistry = () => new Promise((resolve, reject) => { 147 | if (navigator.onLine) { 148 | return configureNpm('').then(npm => { 149 | 150 | return npm.ping() 151 | .then(resolve) 152 | .catch(reject); 153 | }) 154 | .catch(err => { 155 | $log.warn('Ping registry', err); 156 | reject(err); 157 | }); 158 | } 159 | 160 | $log.warn('You are offline: unable to ping registry.'); 161 | return reject(); 162 | }) 163 | , outdatedGlobalVersion = () => new Promise(resolve => { 164 | this.npmGlobal() 165 | .catch(error => errorsService.showErrorBox('Npm error', `Error during configuring npm for asking the globally installed version: ${error}`)) 166 | .then(npmInFolder => { 167 | 168 | return npmInFolder.outdated() 169 | .then(resolve) 170 | .catch(error => errorsService.showErrorBox('Npm error', `Error asking the globally installed version: ${error}`)); 171 | }); 172 | }); 173 | 174 | this.updateNpmGlobally = () => { 175 | const npmLib = { 176 | 'name': 'npm' 177 | }; 178 | 179 | return new Promise((resolve, reject) => { 180 | 181 | this.npmGlobal() 182 | .catch(error => errorsService.showErrorBox('Npm error', `Error configuring npm for installing latest ${npmLib.name}: ${error} `)) 183 | .then(npmInFolder => { 184 | 185 | $rootScope.$emit('npm:log:start'); 186 | npmInFolder.installLatest(npmLib) 187 | .then(() => { 188 | 189 | $rootScope.$emit('npm:log:stop'); 190 | resolve(); 191 | }) 192 | .catch(error => { 193 | 194 | $rootScope.$emit('npm:log:stop'); 195 | reject(error); 196 | }); 197 | }); 198 | }); 199 | }; 200 | 201 | $rootScope.$on('dom:ready', () => { 202 | $log.info('DOM is ready for npm'); 203 | //sync shell path or app will not work, yep. 204 | process.env.PATH = require('shell-path').sync(); 205 | 206 | cp.exec('npm root -g', (err, stdout, stderr) => { 207 | 208 | if (err) { 209 | 210 | throw new Error(err); 211 | } 212 | 213 | if (stderr) { 214 | 215 | $log.warn(stderr); 216 | } 217 | 218 | let globalFolder = stdout 219 | , nodeModulesExt = ''; //important 220 | 221 | if (process.platform != 'win32') { 222 | 223 | globalFolder = globalFolder.replace('/lib/node_modules', ''); 224 | } else { 225 | 226 | globalFolder = globalFolder.replace('node_modules', ''); 227 | } 228 | 229 | globalFolder = globalFolder.trim(); 230 | 231 | this.npmGlobal = () => { 232 | 233 | return configureNpm(globalFolder, true); 234 | }; 235 | 236 | if (process.platform && 237 | process.platform !== 'win32') { 238 | //on windows it doesn't exists 239 | nodeModulesExt = '/lib/node_modules'; 240 | } 241 | 242 | fs.stat(`${globalFolder}${nodeModulesExt}`, (statError, stats) => { 243 | 244 | if (statError) { 245 | 246 | $log.error(statError); 247 | } 248 | 249 | if (process.platform === 'win32') { 250 | //win if error occur it means that folder is not writable and so we set the check to fail. 251 | if (statError) { 252 | $rootScope.$apply(scope => { 253 | 254 | scope.$emit('npm:global-privilege-check', { 255 | 'user': -1, 256 | 'processUser': 1, 257 | 'group': -1, 258 | 'processGroup': 1 259 | }); 260 | }); 261 | } 262 | 263 | return $rootScope.$apply(scope => { 264 | 265 | scope.$emit('npm:global-privilege-check', { 266 | 'user': 1, 267 | 'processUser': 1, 268 | 'group': 1, 269 | 'processGroup': 1 270 | }); 271 | }); 272 | } 273 | 274 | return $rootScope.$apply(scope => { 275 | 276 | scope.$emit('npm:global-privilege-check', { 277 | 'user': stats.uid, 278 | 'processUser': process.getuid(), 279 | 'group': stats.gid, 280 | 'processGroup': process.getgid() 281 | }); 282 | }); 283 | }); 284 | 285 | $rootScope.$emit('npm:ready'); 286 | }); 287 | }); 288 | 289 | this.npmInFolder = configureNpm; 290 | this.outdatedGlobalVersion = outdatedGlobalVersion; 291 | this.getNpmVersion = getNpmVersion; 292 | this.isNpmGloballyInstalled = isNpmGloballyInstalled; 293 | this.pingRegistry = pingRegistry; 294 | }); 295 | 296 | export default moduleName; 297 | -------------------------------------------------------------------------------- /lib/js/npm/npm-operations.js: -------------------------------------------------------------------------------- 1 | /* global process,require */ 2 | const isGlobalSym = Symbol('isGlobal') 3 | , npmSym = Symbol('npm') 4 | , whereSym = Symbol('where') 5 | , fetch = require('node-fetch') 6 | , swapFolderAndGlobal = function SwapFolderAndGlobal(prefix, isGlobal) { 7 | 8 | const oldPrefix = this[npmSym].config.prefix 9 | , oldGlobal = this[npmSym].config.global; 10 | 11 | this[npmSym].config.prefix = prefix; 12 | this[npmSym].config.global = isGlobal; 13 | 14 | return [oldPrefix, oldGlobal]; 15 | }; 16 | 17 | 18 | class NpmOperations { 19 | 20 | constructor(where, configuredNpm, isGlobal) { 21 | 22 | this[whereSym] = where; 23 | this[npmSym] = configuredNpm; 24 | this[isGlobalSym] = isGlobal; 25 | } 26 | 27 | ping() { 28 | return new Promise((resolvePing, rejectPing) => { 29 | this[npmSym].commands.ping('', err => { 30 | if (err) { 31 | 32 | return rejectPing(err); 33 | } 34 | 35 | return resolvePing(); 36 | }); 37 | }); 38 | } 39 | 40 | launchInstall() { 41 | 42 | return new Promise((resolveInstall, rejectInstall) => { 43 | this[npmSym].commands.install(this[whereSym], [], err => { 44 | if (err) { 45 | 46 | return rejectInstall(err); 47 | } 48 | 49 | return resolveInstall(); 50 | }); 51 | }); 52 | } 53 | 54 | search(keyword) { 55 | 56 | return fetch(`https://registry.npmjs.org/-/v1/search?text=${keyword}&size=25`) 57 | .then(res => res.json()); 58 | } 59 | 60 | run(scriptName) { 61 | return new Promise((resolveRun, rejectRun) => { 62 | this[npmSym].commands.runScript([scriptName], (err, infos) => { 63 | if (err) { 64 | 65 | return rejectRun(err); 66 | } 67 | 68 | return resolveRun(infos); 69 | }); 70 | }); 71 | } 72 | 73 | view(packageName) { 74 | return new Promise((resolveView, rejectView) => { 75 | 76 | this[npmSym].commands.view([packageName], (err, infos) => { 77 | if (err) { 78 | 79 | return rejectView(err); 80 | } 81 | 82 | return resolveView(infos); 83 | }); 84 | }); 85 | } 86 | 87 | build(folder) { 88 | 89 | return new Promise((resolveBuild, rejectBuild) => { 90 | 91 | this[npmSym].commands.build([folder], (err, infos) => { 92 | if (err) { 93 | 94 | return rejectBuild(err); 95 | } 96 | 97 | return resolveBuild(infos); 98 | }); 99 | }); 100 | } 101 | 102 | rebuild() { 103 | 104 | return new Promise((resolveRebuild, rejectRebuild) => { 105 | 106 | this[npmSym].commands.rebuild([], (err, infos) => { 107 | if (err) { 108 | return rejectRebuild(err); 109 | } 110 | return resolveRebuild(infos); 111 | }); 112 | }); 113 | } 114 | 115 | install(dependency, version) { 116 | let dependencyToSubmit = dependency.name; 117 | 118 | if (version && 119 | version !== 'false' && 120 | version !== 'undefined') { 121 | 122 | dependencyToSubmit += `@${version}`; 123 | } 124 | 125 | return new Promise((resolveInstall, rejectInstall) => { 126 | const toInstall = [dependencyToSubmit] 127 | , whereToInstall = this[isGlobalSym] ? '' : this[whereSym]; 128 | 129 | if (!this[isGlobalSym] && 130 | dependency.kind === 'dev') { 131 | 132 | this[npmSym].config.set('save-dev', true); 133 | } else if (!this[isGlobalSym]) { 134 | 135 | this[npmSym].config.set('save', true); 136 | } 137 | 138 | this[npmSym].commands.install(whereToInstall, toInstall, err => { 139 | 140 | if (err) { 141 | 142 | return rejectInstall(err); 143 | } 144 | 145 | if (!this[isGlobalSym]) { 146 | 147 | this[npmSym].config.set('save-dev', false); 148 | this[npmSym].config.set('save', false); 149 | } 150 | return resolveInstall(); 151 | }); 152 | }); 153 | } 154 | 155 | installLatest(dependency) { 156 | 157 | return this.install(dependency, dependency.latest); 158 | } 159 | 160 | update(dependency) { 161 | 162 | return new Promise((resolveUpdate, rejectUpdate) => { 163 | const toUpdate = [dependency.name]; 164 | 165 | if (!this[isGlobalSym] && 166 | dependency.kind === 'dev') { 167 | 168 | this[npmSym].config.set('save-dev', true); 169 | } else if (!this[isGlobalSym]) { 170 | 171 | this[npmSym].config.set('save', true); 172 | } 173 | 174 | this[npmSym].commands.update(toUpdate, err => { 175 | 176 | if (err) { 177 | 178 | return rejectUpdate(err); 179 | } 180 | 181 | if (!this[isGlobalSym]) { 182 | 183 | this[npmSym].config.set('save-dev', false); 184 | this[npmSym].config.set('save', false); 185 | } 186 | return resolveUpdate(); 187 | }); 188 | }); 189 | } 190 | 191 | rm(dependency) { 192 | 193 | return new Promise((resolveRm, rejectRm) => { 194 | const toRemove = [dependency.name]; 195 | 196 | if (!this[isGlobalSym] && 197 | dependency.kind === 'dev') { 198 | 199 | this[npmSym].config.set('save-dev', true); 200 | } else if (!this[isGlobalSym]) { 201 | 202 | this[npmSym].config.set('save', true); 203 | } 204 | 205 | this[npmSym].commands.rm(toRemove, err => { 206 | 207 | if (err) { 208 | 209 | return rejectRm(err); 210 | } 211 | 212 | if (!this[isGlobalSym]) { 213 | 214 | this[npmSym].config.set('save-dev', false); 215 | this[npmSym].config.set('save', false); 216 | } 217 | return resolveRm(); 218 | }); 219 | }); 220 | } 221 | 222 | listOutdated() { 223 | return new Promise((listOutdatedResolve, listOutdatedReject) => { 224 | 225 | Promise.all([this.list(), this.outdated()]) 226 | .then(resolved => { 227 | 228 | if (resolved && 229 | Array.isArray(resolved) && 230 | resolved.length === 2) { 231 | const outdatedList = resolved[1] 232 | , listList = resolved[0]; 233 | let toResolve = []; 234 | 235 | listList.forEach(element => { 236 | 237 | if (element && 238 | element.name) { 239 | const outdatedData = outdatedList.filter(filterElement => { 240 | return filterElement && filterElement.name === element.name; 241 | }).map(mapElement => ({ 242 | 'name': element.name, 243 | 'kind': element.kind, 244 | 'current': mapElement.current, 245 | 'wanted': mapElement.wanted, 246 | 'latest': mapElement.latest 247 | })); 248 | 249 | if (outdatedData.length > 0) { 250 | 251 | toResolve = toResolve.concat(outdatedData); 252 | } else { 253 | 254 | toResolve.push(element); 255 | } 256 | } 257 | }); 258 | 259 | return listOutdatedResolve(toResolve); 260 | } 261 | 262 | return listOutdatedReject('Output from list and oudated commands wrong!'); 263 | }) 264 | .catch(err => listOutdatedReject(err)); 265 | }); 266 | } 267 | 268 | outdated() { 269 | return new Promise((resolveOutdated, rejectOutdated) => { 270 | const [oldPrefix, oldGlobal] = swapFolderAndGlobal.apply(this, [this[whereSym], this[isGlobalSym]]); 271 | 272 | this[npmSym].commands.outdated([], true, (outdatedError, packageInformations) => { 273 | 274 | if (outdatedError) { 275 | 276 | this[npmSym].config.prefix = oldPrefix; 277 | this[npmSym].config.global = oldGlobal; 278 | return rejectOutdated(outdatedError); 279 | } 280 | 281 | if (packageInformations && 282 | Array.isArray(packageInformations)) { 283 | const toResolve = []; 284 | 285 | for (const aPackageInformation of packageInformations) { 286 | 287 | toResolve.push({ 288 | 'name': aPackageInformation[1], 289 | 'current': aPackageInformation[2], 290 | 'wanted': aPackageInformation[3], 291 | 'latest': aPackageInformation[4] 292 | }); 293 | } 294 | 295 | this[npmSym].config.prefix = oldPrefix; 296 | this[npmSym].config.global = oldGlobal; 297 | return resolveOutdated(toResolve); 298 | } 299 | 300 | return rejectOutdated('Package informations from outdated are wrong!'); 301 | }); 302 | }); 303 | } 304 | 305 | prune() { 306 | return new Promise((resolvePrune, rejectPrune) => { 307 | const oldCWD = process.cwd(); 308 | 309 | process.chdir(this[whereSym]); 310 | this[npmSym].commands.prune([], (pruneError, packageInformations) => { 311 | 312 | process.chdir(oldCWD); 313 | if (pruneError) { 314 | 315 | return rejectPrune(pruneError); 316 | } 317 | 318 | return resolvePrune(packageInformations); 319 | }); 320 | }); 321 | } 322 | 323 | dedupe() { 324 | return new Promise((resolveDedupe, rejectDedupe) => { 325 | 326 | this[npmSym].commands.dedupe([], (DedupeError, packageInformations) => { 327 | 328 | if (DedupeError) { 329 | 330 | return rejectDedupe(DedupeError); 331 | } 332 | 333 | return resolveDedupe(packageInformations); 334 | }); 335 | }); 336 | } 337 | 338 | list() { 339 | return new Promise((resolveList, rejectList) => { 340 | const [oldPrefix, oldGlobal] = swapFolderAndGlobal.apply(this, [this[whereSym], this[isGlobalSym]]); 341 | 342 | this[npmSym].commands.list([], true, (listError, packageInformations) => { 343 | 344 | if (listError) { 345 | 346 | this[npmSym].config.prefix = oldPrefix; 347 | this[npmSym].config.global = oldGlobal; 348 | return rejectList(listError); 349 | } 350 | 351 | if (packageInformations && 352 | packageInformations.dependencies && 353 | packageInformations.devDependencies) { 354 | const toResolve = [] 355 | , dependenciesKeys = Object.keys(packageInformations.dependencies) 356 | , dependenciesKeysLength = dependenciesKeys.length 357 | , devDependenciesKeys = Object.keys(packageInformations.devDependencies) 358 | , filteringFunction = function FilteringFunction(element) { 359 | 360 | return element && element.name !== packageInformations.dependencies[this]; 361 | } 362 | , filterIsADevDependency = function filterIsADevDependency(aDependency, element) { 363 | 364 | return element && element === aDependency; 365 | }; 366 | 367 | for (let dependenciesKeysIndex = 0; dependenciesKeysIndex < dependenciesKeysLength; dependenciesKeysIndex += 1) { 368 | const aDependencyKey = dependenciesKeys[dependenciesKeysIndex]; 369 | 370 | if (aDependencyKey && 371 | toResolve.every(filteringFunction, aDependencyKey)) { 372 | const aDependency = packageInformations.dependencies[aDependencyKey] 373 | , isADevDependency = devDependenciesKeys.filter(filterIsADevDependency.bind(devDependenciesKeys, aDependencyKey)); 374 | 375 | toResolve.push({ 376 | 'name': aDependencyKey, 377 | 'current': aDependency.version, 378 | 'kind': isADevDependency[0] ? 'dev' : '' 379 | }); 380 | } 381 | } 382 | 383 | this[npmSym].config.prefix = oldPrefix; 384 | this[npmSym].config.global = oldGlobal; 385 | return resolveList(toResolve); 386 | } 387 | 388 | return rejectList('Package informations from list command are wrong!'); 389 | }); 390 | }); 391 | } 392 | 393 | shrinkwrap() { 394 | return new Promise((resolveShrink, rejectShrink) => { 395 | 396 | this[npmSym].commands.shrinkwrap([], (shrinkError, infos) => { 397 | 398 | if (shrinkError) { 399 | 400 | return rejectShrink(shrinkError); 401 | } 402 | 403 | return resolveShrink(infos); 404 | }); 405 | }); 406 | } 407 | 408 | doctor() { 409 | return new Promise((resolveDoctor, rejectDoctor) => { 410 | 411 | this[npmSym].commands.doctor((doctorErr, doctorInfo) => { 412 | 413 | if (doctorErr) { 414 | 415 | return rejectDoctor(doctorErr); 416 | } 417 | 418 | return resolveDoctor(doctorInfo); 419 | }); 420 | }); 421 | } 422 | 423 | root() { 424 | return new Promise((resolveRoot, rejectRoot) => { 425 | 426 | this[npmSym].commands.root([], (rootError, rootInfo) => { 427 | 428 | if (rootError) { 429 | 430 | return rejectRoot(rootError); 431 | } 432 | 433 | return resolveRoot(rootInfo); 434 | }); 435 | }); 436 | } 437 | } 438 | 439 | export default NpmOperations; 440 | -------------------------------------------------------------------------------- /lib/js/npm/npm-runner.js: -------------------------------------------------------------------------------- 1 | /*global require,process,Buffer*/ 2 | import NpmOperations from './npm-operations.js'; 3 | 4 | const npm = require('npm') 5 | , stream = require('stream') 6 | , writable = new stream.Writable({ 7 | 'write': (chunk, encoding, next) => { 8 | const thisLogBuffer = new Buffer(chunk) 9 | , thisLog = thisLogBuffer 10 | .toString() 11 | .trim(); 12 | 13 | if (thisLog) { 14 | 15 | process.send({ 16 | 'type': 'log', 17 | 'payload': thisLog 18 | }); 19 | } 20 | 21 | next(); 22 | } 23 | }) 24 | , npmDefaultConfiguration = { 25 | 'loglevel': 'info', 26 | 'progress': false, 27 | 'logstream': writable 28 | } 29 | , exec = (folder, isGlobal, command, param1, param2) => { 30 | const confObject = Object.assign({}, 31 | npmDefaultConfiguration, 32 | { 33 | 'prefix': folder, 34 | 'global': isGlobal 35 | }); 36 | 37 | process.send({folder, isGlobal, command, param1, param2}); 38 | return npm.load(confObject, (err, configuredNpm) => { 39 | if (err) { 40 | 41 | process.send({ 42 | 'type': 'error', 43 | 'payload': err 44 | }); 45 | } 46 | const npmOperations = new NpmOperations(folder, configuredNpm, isGlobal); 47 | 48 | npmOperations[command](param1, param2).then(resolved => process.send({ 49 | 'type': command, 50 | 'payload': resolved 51 | })); 52 | }); 53 | } 54 | , inputs = process.argv 55 | .slice(2) 56 | .map(element => { 57 | try { 58 | 59 | return JSON.parse(element); 60 | } catch (err) { 61 | 62 | if (element === 'undefined') { 63 | 64 | return undefined; 65 | } 66 | 67 | return element; 68 | } 69 | }); 70 | 71 | exec(...inputs); 72 | -------------------------------------------------------------------------------- /lib/js/update.js: -------------------------------------------------------------------------------- 1 | /*globals require,__dirname,process,window,Buffer*/ 2 | import angular from 'angular'; 3 | 4 | const path = require('path') 5 | , {remote} = require('electron') 6 | , fs = require('fs') 7 | , AdmZip = require('adm-zip') 8 | , fse = require('fs-extra') 9 | , tmpDirectory = require('os').tmpdir() 10 | , tmpFolder = path.resolve(tmpDirectory, 'ndm-') 11 | , packageJSON = require(path.resolve(__dirname, '..', 'package.json')); 12 | 13 | angular.module('ndm-updater', []) 14 | .constant('updateUrl', 'https://api.github.com/repos/720kb/ndm/releases/latest') 15 | .controller('ShellController', /* @ngInject */function ShellController( 16 | $scope, 17 | $document, 18 | $log, 19 | updateUrl 20 | ) { 21 | const onUpdateCheckEnd = event => { 22 | 23 | let updateMetaInfo 24 | , indexOfZipName; 25 | 26 | if (process.platform === 'darwin') { 27 | indexOfZipName = 'mac.zip'; 28 | } else if (process.platform === 'win32') { 29 | indexOfZipName = 'win.zip'; 30 | } 31 | 32 | $scope.$apply(() => { 33 | 34 | this.checking = false; 35 | if (!event.target.responseText) { 36 | 37 | this.error = 'No response'; 38 | this.errorChecking = true; 39 | return; 40 | } 41 | 42 | try { 43 | 44 | updateMetaInfo = JSON.parse(event.target.responseText); 45 | $log.info('Github Fetched Metas', updateMetaInfo); 46 | } catch (parsingError) { 47 | 48 | this.error = parsingError; 49 | this.errorChecking = true; 50 | return; 51 | } 52 | 53 | if (updateMetaInfo.tag_name === `v${packageJSON.version}` || 54 | updateMetaInfo.tag_name.indexOf('v') === -1) { 55 | 56 | //Nothing to update 57 | this.toUpdate = false; 58 | return; 59 | } 60 | 61 | //There is something to update 62 | this.nextVesion = updateMetaInfo.name.replace('v', ''); 63 | this.toUpdate = true; 64 | this.url = updateMetaInfo.assets 65 | .filter(element => element.name.indexOf(indexOfZipName) > -1) 66 | .reduce(prev => prev).browser_download_url; 67 | }); 68 | } 69 | , onUpdateCheckErr = () => { 70 | 71 | $scope.$apply(() => { 72 | 73 | this.checking = false; 74 | this.errorChecking = true; 75 | }); 76 | } 77 | , onUpdateErr = () => { 78 | 79 | $scope.$apply(() => { 80 | 81 | this.checking = false; 82 | this.errorUpdating = true; 83 | this.progress = 100; 84 | }); 85 | } 86 | , onUpdateEnd = event => { 87 | 88 | fs.mkdtemp(tmpFolder, (err, folder) => { 89 | const fileToSave = path.resolve(folder, `npm_${new Date().getTime()}`); 90 | let newResources; 91 | 92 | if (process.platform === 'darwin') { 93 | newResources = path.resolve(folder, path.join('ndm.app', 'Contents', 'Resources', 'app')); 94 | } else if (process.platform === 'win32') { 95 | newResources = path.resolve(folder, path.join('win-ia32-unpacked', 'resources', 'app')); 96 | } 97 | 98 | if (err) { 99 | 100 | throw err; 101 | } 102 | 103 | fs.appendFile(fileToSave, new Buffer(event.target.response), appendErr => { 104 | 105 | if (appendErr) { 106 | 107 | throw appendErr; 108 | } 109 | try { 110 | const zip = new AdmZip(fileToSave); 111 | 112 | zip.extractAllTo(/*target path*/folder, /*overwrite*/true); 113 | } catch (excp) { 114 | $log.warn(excp); 115 | } 116 | 117 | fse.move(newResources, remote.app.getAppPath(), { 118 | 'overwrite': true 119 | }, moveErr => { 120 | 121 | if (moveErr) { 122 | 123 | throw moveErr; 124 | } 125 | remote.app.relaunch(); 126 | remote.app.exit(0); 127 | }); 128 | }); 129 | }); 130 | } 131 | , onUpdateProgress = event => { 132 | 133 | $scope.$apply(() => { 134 | 135 | if (event.lengthComputable) { 136 | this.progress = (event.loaded / event.total) * 100; 137 | } else { 138 | 139 | this.progress = (this.progress + 10) % 100; 140 | } 141 | }); 142 | }; 143 | 144 | this.checkNow = () => { 145 | if (!this.checking) { 146 | 147 | this.errorChecking = false; 148 | this.checking = true; 149 | const updateRequest = new window.XMLHttpRequest(); 150 | 151 | delete this.toUpdate; 152 | updateRequest.addEventListener('load', onUpdateCheckEnd); 153 | updateRequest.addEventListener('error', onUpdateCheckErr); 154 | updateRequest.open('GET', updateUrl); 155 | updateRequest.send(); 156 | } 157 | }; 158 | 159 | this.updateIt = () => { 160 | if (!this.updating) { 161 | 162 | this.errorUpdating = false; 163 | this.updating = true; 164 | const updating = new window.XMLHttpRequest(); 165 | 166 | delete this.toUpdate; 167 | updating.addEventListener('load', onUpdateEnd); 168 | updating.addEventListener('error', onUpdateErr); 169 | updating.addEventListener('progress', onUpdateProgress); 170 | 171 | updating.open('GET', this.url); 172 | updating.responseType = 'arraybuffer'; 173 | updating.send(); 174 | } 175 | }; 176 | 177 | this.currentVersion = packageJSON.version; 178 | this.checkNow(); 179 | 180 | $document[0].addEventListener('visibilitychange', () => { 181 | if ($document[0].visibilityState === 'visible' && 182 | !this.checking && 183 | !this.updating) { 184 | this.checkNow(); 185 | } 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /lib/left.pug: -------------------------------------------------------------------------------- 1 | div(ng-controller='LeftBarController as leftBar') 2 | include ./editor-prompt.pug 3 | include ./history-prompt.pug 4 | .left-column.overflow-x-hidden 5 | .row 6 | a.row(ng-class="{'active': leftBar.global, 'action-link-disabled': shell.globalDisabled}", ng-click='shell.globalDisabled ? shell.enableGlobal() : leftBar.selectGlobal()') 7 | img.global-img(src="img/npm-logo-cube.svg") 8 | | Globals 9 | div(ng-repeat="item in shell.projects | orderBy: 'dirName' track by $index") 10 | a.row.project(title="{{ item.shrinkwrap ? 'Has shrinkwrap' : '' }}", ng-right-click="leftBar.rightClickMenu(item, $event)", ng-class="{'active': leftBar.selectedProject === item && !leftBar.global, 'shrinkwrapped': item.shrinkwrap}", ng-if="leftBar.removedProject !== item", ng-click='leftBar.selectProject(item, $event)') 11 | i.fa.fa-folder-o 12 | i.fa.fa-lock(ng-if="item.shrinkwrap") 13 | | {{item.dirName}} 14 | span.button-delete-project(ng-click="leftBar.deleteProject(item, $event)") 15 | i.fa.fa-times-circle-o 16 | div(ng-init="leftBar.cancelProgressActionId = false;") 17 | .left-progress.left-progress-minor.color-white.fake-link(ng-repeat="runningScript in leftBar.npmRunningScriptsProject[item.path] track by $index") 18 | small.running-name 19 | | ↳ running: {{runningScript}} 20 | small.running-end(ng-click="leftBar.deleteRunningScript(item.path, runningScript)") 21 | i.fa.fa-times-circle-o 22 | | dismiss 23 | .left-progress-loading 24 | .left-progress.color-white.fake-link(ng-show="leftBar.npmInstallingProjects[item.dirName]") 25 | small.running-name 26 | | ↳ installing 27 | small.running-end(ng-click="leftBar.deleteInstallingProjects(item.dirName)") 28 | i.fa.fa-times-circle-o 29 | | dismiss 30 | .left-progress-loading 31 | .left-progress.color-white(ng-show="leftBar.npmReinstallingProjects[item.dirName]") 32 | small.running-name 33 | | ↳ reinstalling 34 | small.running-end(ng-click="leftBar.deleteReinstallingProjects(item.dirName)") 35 | i.fa.fa-times-circle-o 36 | | dismiss 37 | .left-progress-loading 38 | .left-progress.color-white(ng-show="leftBar.npmBuildingProjects[item.dirName]") 39 | small.running-name 40 | | ↳ building 41 | small.running-end(ng-click="leftBar.deleteBuildingProjects(item.dirName)") 42 | i.fa.fa-times-circle-o 43 | | dismiss 44 | .left-progress-loading 45 | .left-progress.color-white(ng-show="leftBar.npmPruningProjects[item.dirName]") 46 | small.running-name 47 | | ↳ pruning 48 | small.running-end(ng-click="leftBar.deletePruningProjects(item.dirName)") 49 | i.fa.fa-times-circle-o 50 | | dismiss 51 | .left-progress-loading 52 | .left-progress.color-white(ng-show="leftBar.npmDedupingProjects[item.dirName]") 53 | small.running-name 54 | | ↳ deduping 55 | small.running-end(ng-click="leftBar.deleteDedupingProjects(item.dirName)") 56 | i.fa.fa-times-circle-o 57 | | dismiss 58 | .left-progress-loading 59 | .left-progress.color-white(ng-show="leftBar.npmShrinkwrappingProjects[item.dirName]") 60 | small.running-name 61 | | ↳ shrinkwrapping 62 | small.running-end(ng-click="leftBar.deleteShrinkwrappingProjects(item.dirName)") 63 | i.fa.fa-times-circle-o 64 | | dismiss 65 | .left-progress-loading 66 | br 67 | -------------------------------------------------------------------------------- /lib/npm-doctor-log.pug: -------------------------------------------------------------------------------- 1 | div.dialog.dialog-window(ng-if="shell.activeLink === 'doctor'") 2 | div(class="prompt-window-options") 3 | img(ng-show="!shell.finishedRunningDoctor", src='img/loading.svg', width='13') 4 | i(class="fa fa-check color-primary", ng-show="shell.finishedRunningDoctor") 5 | button(ng-click="shell.activeLink = false") 6 | | Close 7 | button(ng-click="shell.activeClickedLink('doctor'); shell.runDoctor()", ng-if="!shell.runningDoctor") 8 | | Run again 9 | div(contenteditable="true", class="window", ng-autoscroll) 10 | div(class="col-md-12 logs") 11 | b 12 | | Running doctor 13 | div(class="col-md-12 logs") 14 | | npm ping: 15 | = " " 16 | b 17 | | {{shell.doctorLog[0][1]}} 18 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[0][2] && shell.doctorLog[0][2].length > 0") 19 | | ↳ {{shell.doctorLog[0][2]}} 20 | div(class="col-md-12 logs") 21 | | npm -v: 22 | = " " 23 | b 24 | | {{shell.doctorLog[1][1]}} 25 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[1][2] && shell.doctorLog[1][2].length > 0") 26 | | ↳ {{shell.doctorLog[1][2]}} 27 | div(class="col-md-12 logs") 28 | | node -v: 29 | = " " 30 | b 31 | | {{shell.doctorLog[2][1]}} 32 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[2][2] && shell.doctorLog[2][2].length > 0") 33 | | ↳ {{shell.doctorLog[2][2]}} 34 | div(class="col-md-12 logs") 35 | | npm config get registry: 36 | = " " 37 | b 38 | | {{shell.doctorLog[3][1]}} 39 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[3][2] && shell.doctorLog[3][2].length > 0") 40 | | ↳ {{shell.doctorLog[3][2]}} 41 | div(class="col-md-12 logs") 42 | | which git: 43 | = " " 44 | b 45 | | {{shell.doctorLog[4][1]}} 46 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[4][2] && shell.doctorLog[4][2].length > 0") 47 | | ↳ {{shell.doctorLog[4][2]}} 48 | div(class="col-md-12 logs") 49 | | Perms check on cached files: 50 | = " " 51 | b 52 | | {{shell.doctorLog[5][1]}} 53 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[5][2] && shell.doctorLog[5][2].length > 0") 54 | | ↳ {{shell.doctorLog[5][2]}} 55 | div(class="col-md-12 logs") 56 | | Perms check on global node_modules: 57 | = " " 58 | b 59 | | {{shell.doctorLog[6][1]}} 60 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[6][2] && shell.doctorLog[6][2].length > 0") 61 | | ↳ {{shell.doctorLog[6][2]}} 62 | div(class="col-md-12 logs") 63 | | Perms check on local node_modules: 64 | = " " 65 | b 66 | | {{shell.doctorLog[7][1]}} 67 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[7][2] && shell.doctorLog[7][2].length > 0") 68 | | ↳ {{shell.doctorLog[7][2]}} 69 | div(class="col-md-12 logs") 70 | | Checksum cached files : 71 | = " " 72 | b 73 | | {{shell.doctorLog[8][1]}} 74 | div(class="col-md-12 logs logs-error", ng-if="shell.doctorLog[8][2] && shell.doctorLog[8][2].length > 0") 75 | | ↳ {{shell.doctorLog[8][2]}} 76 | -------------------------------------------------------------------------------- /lib/npm-update-log.pug: -------------------------------------------------------------------------------- 1 | div.dialog.dialog-window(ng-if="shell.activeLink === 'update'") 2 | div(class="prompt-window-options") 3 | img(ng-show="shell.updatingNpm", src='img/loading.svg', width='13') 4 | i(class="fa fa-check color-primary", ng-show="!shell.updatingNpm && log.logs") 5 | button(ng-click="shell.activeLink = false") 6 | | Close 7 | button(ng-click="shell.activeClickedLink('update'); shell.updateNpm()", ng-if="!shell.updatingNpm") 8 | | Run again 9 | div(contenteditable="true", class="window", ng-autoscroll) 10 | div(class="col-md-12 logs") 11 | b 12 | | Updating npm 13 | div(class="col-md-12 logs") 14 | | Output: 15 | div(class="col-md-12 logs", contenteditable="true", ng-repeat="aLog in log.logs track by $index") 16 | | {{ aLog }} 17 | -------------------------------------------------------------------------------- /lib/package-informations.pug: -------------------------------------------------------------------------------- 1 | div.table-infos-content 2 | div.information 3 | b 4 | | Name: 5 | = " " 6 | | {{packageViewInfos.name || '-'}} 7 | div.information(title="{{packageViewInfos.description}}") 8 | b 9 | | Description: 10 | = " " 11 | | {{packageViewInfos.description || '-' | removeHTML}} 12 | div.information(title="Package dependencies") 13 | b 14 | | Dependencies: 15 | = " " 16 | span(ng-repeat="(dep, value) in packageViewInfos.dependencies") 17 | a(title="Open in browser", ng-click="shell.openBrowserLink('https://npmjs.com/package/' + dep)") 18 | | {{ dep }} 19 | span(ng-if="!$last") 20 | | , 21 | = " " 22 | span(ng-if="!packageViewInfos.dependencies || packageViewInfos.dependencies.length <= 0") 23 | | - 24 | div.information(title="Package repository url") 25 | b 26 | | Repository: 27 | = " " 28 | | {{packageViewInfos.repository.url || '-'}} 29 | div.information 30 | b 31 | | Issues: 32 | = " " 33 | a(title="Open in browser", ng-if="packageViewInfos.bugs.url", ng-click="shell.openBrowserLink(packageViewInfos.bugs.url)") 34 | | {{packageViewInfos.bugs.url}} 35 | span(ng-if="!packageViewInfos.bugs || !packageViewInfos.bugs.url") 36 | | - 37 | div.information 38 | b 39 | | Url: 40 | = " " 41 | a(title="Open in browser", ng-init="pkgUrlToNpmJsWebsite = 'https://npmjs.com/package/' + packageViewInfos.name", ng-if="packageViewInfos.name", ng-click="shell.openBrowserLink(pkgUrlToNpmJsWebsite)") 42 | | {{ pkgUrlToNpmJsWebsite }} 43 | span(ng-if="!packageViewInfos.name") 44 | | - 45 | div.information 46 | b 47 | | License: 48 | = " " 49 | | {{packageViewInfos.license.type || packageViewInfos.license || '-'}} 50 | -------------------------------------------------------------------------------- /lib/scss/ace-editor.scss: -------------------------------------------------------------------------------- 1 | //Leave the !important to override ace-editor default which is rendered later 2 | .ace_editor { 3 | font-size: 13.5px !important; 4 | } 5 | .ace_tooltip { 6 | background: $bg-error !important; 7 | padding: 1.5px 4px !important; 8 | border: 0 !important; 9 | border-radius: 0 !important; 10 | font-size: 12px !important; 11 | color: white !important; 12 | } 13 | .ace_search { 14 | display: none !important; 15 | } 16 | .ace_gutter { 17 | background: none !important; 18 | } 19 | .ace_gutter-cell { 20 | 21 | &.ace_error { 22 | background: none !important; 23 | &::before { 24 | position: absolute; 25 | float: left; 26 | left: 0; 27 | content: '\e801'; 28 | color: $color-error; 29 | font-family: $font-icon; 30 | font-size: 14.5px; 31 | margin: 1px 5px; 32 | text-shadow: 0 1px rgba(255, 255, 255, .75); 33 | } 34 | } 35 | } 36 | 37 | .ace_fold { 38 | background-color: $bg-muted-more !important; 39 | background-image: none !important; 40 | border-radius: 0 !important; 41 | margin:0 !important; 42 | border: 0 !important; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /lib/scss/animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes loadingStripes { 2 | from { 3 | background-position: 0 0; 4 | } 5 | 6 | to { 7 | background-position: 50px 0; 8 | } 9 | } 10 | @keyframes promptSliding { 11 | 0% { 12 | transform: translateY(-5px); 13 | } 14 | 15 | 100% { 16 | transform: translateY(0); 17 | } 18 | } 19 | 20 | @keyframes toggleOpacity { 21 | 22 | 0% { 23 | opacity: .2; 24 | } 25 | 26 | 100% { 27 | opacity: 1; 28 | } 29 | } 30 | @keyframes toggleOpacityInverse { 31 | 32 | 0% { 33 | opacity: 1; 34 | } 35 | 36 | 100% { 37 | opacity: .2; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/scss/dragdrop.scss: -------------------------------------------------------------------------------- 1 | body { 2 | 3 | &.dragging { 4 | cursor: copy; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/scss/footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | -webkit-app-region: drag; 3 | bottom: 3px; 4 | font-weight: normal; 5 | height: 21px; 6 | padding: 0 11px; 7 | position: fixed; 8 | width: 100%; 9 | z-index: 9; 10 | font-size: 12px; 11 | 12 | b { 13 | font-weight: 500; 14 | } 15 | 16 | i { 17 | font-size: 16px; 18 | } 19 | .loader { 20 | width: 7px; 21 | display: inline-block; 22 | line-height: 2px; 23 | 24 | i { 25 | width: 7px; 26 | min-width: 10px; 27 | position: relative; 28 | text-align: center; 29 | font-size: 5px; 30 | top: -1px; 31 | color: $color-muted; 32 | 33 | &.available { 34 | color: $color-green; 35 | } 36 | 37 | &.unavailable { 38 | color: $color-error; 39 | } 40 | } 41 | &.loading { 42 | i { 43 | animation: toggleOpacity .4s linear infinite; 44 | 45 | &:first-child { 46 | animation: toggleOpacityInverse .25s linear infinite; 47 | } 48 | } 49 | } 50 | } 51 | 52 | span { 53 | &.badge-version { 54 | small { 55 | font-size: 10px; 56 | } 57 | i { 58 | font-size: 11px; 59 | color: $color-positive; 60 | } 61 | } 62 | &.npm-status { 63 | 64 | i { 65 | &.checking { 66 | color: $color-muted; 67 | } 68 | &.unavailable { 69 | color: $color-error; 70 | } 71 | } 72 | } 73 | } 74 | 75 | button { 76 | margin-left: 6px; 77 | margin-right: 5px; 78 | font-size: 11.5px; 79 | line-height: 12px; 80 | padding: 0 2px 0 0; 81 | height: 19px; 82 | 83 | i { 84 | color: $color-positive; 85 | position: relative; 86 | top: -1px; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/scss/functions.scss: -------------------------------------------------------------------------------- 1 | @mixin bg-rgba-white($opacity) { 2 | background-color: rgba(255, 255, 255, $opacity); 3 | } 4 | @mixin bg-rgba-black($opacity) { 5 | background-color: rgba(0, 0, 0, $opacity); 6 | } 7 | -------------------------------------------------------------------------------- /lib/scss/header.scss: -------------------------------------------------------------------------------- 1 | .top-menu { 2 | width: calc(100% - 8px); 3 | margin: 0 auto; 4 | background: white; 5 | margin-top: 4px; 6 | margin-bottom: 2px; 7 | 8 | &.freezed { 9 | cursor: wait; 10 | * { 11 | cursor: wait; 12 | pointer-events: none; 13 | } 14 | } 15 | 16 | img { 17 | width: 23px; 18 | margin-top: 4px; 19 | margin-right: 6px; 20 | } 21 | 22 | .button-add-package, 23 | .button-update, 24 | .button-uninstall, 25 | .button-version, 26 | .button-latest { 27 | float: right; 28 | font-size: 12px; 29 | padding-left: 0; 30 | padding-right: 3px; 31 | margin: 1px 0 0 4px; 32 | line-height: 13px; 33 | 34 | i { 35 | color: $color-222; 36 | vertical-align: baseline; 37 | } 38 | 39 | } 40 | 41 | .button-add-package { 42 | float: left; 43 | border: 0; 44 | line-height: 18px; 45 | margin: 0 auto; 46 | padding: 0 0 0 0; 47 | background: none; 48 | margin-bottom: 3px; 49 | border-radius: 10px; 50 | 51 | i { 52 | color: $color-primary; 53 | } 54 | 55 | &:focus { 56 | opacity: .7; 57 | } 58 | } 59 | 60 | .button-uninstall { 61 | 62 | i { 63 | color: $color-error; 64 | } 65 | } 66 | 67 | .button-update { 68 | 69 | i { 70 | color: $color-green; 71 | } 72 | } 73 | 74 | 75 | a { 76 | 77 | &:active, &.active { 78 | i { 79 | color: $color-primary; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/scss/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | min-height: calc(100vh - 37px); 3 | padding-top: 29vh; 4 | text-align: center; 5 | position: relative; 6 | z-index: 9; 7 | background: white; 8 | border: 1px solid #ccc; 9 | margin: 0 auto; 10 | 11 | button { 12 | height: 21px; 13 | font-size: 12.5px; 14 | width: 105px; 15 | } 16 | small { 17 | font-size: 13px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/scss/layout.scss: -------------------------------------------------------------------------------- 1 | .page { 2 | height: calc(100vh - 23px); 3 | } 4 | 5 | .right-column { 6 | border-radius: 2px; 7 | float: left; 8 | margin: 10px 0; 9 | width: calc(100vw - 200px); 10 | height: calc(100vh - 37px); 11 | background: white; 12 | overflow: hidden; 13 | } 14 | 15 | .left-column { 16 | border-radius: 2px; 17 | float: left; 18 | height: calc(100vh - 37px); 19 | overflow: auto; 20 | margin: 10px 5px 10px 10px; 21 | padding: 0 15px 15px 15px; 22 | width: 175px; 23 | background: white; 24 | border: 1px solid $color-ccc; 25 | 26 | a { 27 | display: inline-block; 28 | margin: 0 auto; 29 | padding: 0 6px; 30 | white-space: nowrap; 31 | min-width: 100%; 32 | line-height: 20px; 33 | 34 | &:not(.project) { 35 | line-height: 25px; 36 | } 37 | 38 | img { 39 | &.global-img { 40 | width: 20px; 41 | margin-right: 3px; 42 | margin-top: -3px; 43 | } 44 | } 45 | 46 | i { 47 | font-size: 14.5px; 48 | margin-right: 2px; 49 | margin-left: 7px; 50 | color: $color-primary; 51 | } 52 | 53 | &.shrinkwrapped { 54 | 55 | i { 56 | &.fa-lock { 57 | color: $color-444; 58 | margin:0 auto; 59 | margin-left: -6px; 60 | margin-right: -2px; 61 | font-size: 11.5px; 62 | } 63 | 64 | &.fa-folder-o { 65 | color: $color-npm; 66 | } 67 | } 68 | } 69 | 70 | b { 71 | font-weight: normal; 72 | white-space: nowrap; 73 | width: 100%; 74 | } 75 | 76 | &:active, &:focus { 77 | background: $color-ddd; 78 | } 79 | 80 | &.project { 81 | 82 | &:hover { 83 | 84 | span.button-delete-project { 85 | display: inherit; 86 | } 87 | } 88 | } 89 | 90 | span.button-delete-project { 91 | display: none; 92 | width: 26px; 93 | line-height: 21px; 94 | height: 20px; 95 | text-align: center; 96 | position: absolute; 97 | left: 157.5px; 98 | 99 | i { 100 | color: $color-222; 101 | margin: 0 auto; 102 | font-size: 13px; 103 | } 104 | } 105 | } 106 | 107 | h6 { 108 | font-weight: 500; 109 | font-size: 12px; 110 | margin-bottom: 5px; 111 | padding: 5px 9px; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/scss/linux/index.scss: -------------------------------------------------------------------------------- 1 | @import '../animations'; 2 | @import '../functions'; 3 | @import '../variables'; 4 | @import '../utils'; 5 | @import '../settings'; 6 | @import '../layout'; 7 | @import '../header'; 8 | @import '../table'; 9 | @import '../footer'; 10 | @import '../prompt'; 11 | @import '../loading'; 12 | @import '../progress'; 13 | @import '../dragdrop'; 14 | @import '../ace-editor'; 15 | @import '../home'; 16 | @import '../updates'; 17 | @import '../tabs'; 18 | @import 'linux'; 19 | -------------------------------------------------------------------------------- /lib/scss/linux/linux.scss: -------------------------------------------------------------------------------- 1 | body, html { 2 | cursor: default; 3 | font-family: 'Oxygen', 'Ubuntu', 'Cantarell', 'Arial', 'sans-serif'; 4 | } 5 | 6 | * { 7 | outline: none; 8 | text-decoration: none; 9 | cursor: default; 10 | 11 | &:hover, &:active, &:focus { 12 | outline: none; 13 | text-decoration: none; 14 | cursor: default; 15 | } 16 | } 17 | 18 | .home { 19 | button { 20 | height: 23px; 21 | } 22 | } 23 | 24 | .footer { 25 | button { 26 | font-size: 10.5px; 27 | } 28 | } 29 | .ace_editor { 30 | font-size: 15px !important; 31 | } 32 | 33 | .dialog { 34 | 35 | select { 36 | height: 18.5px; 37 | bottom: 0; 38 | top: -1px; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/scss/loading.scss: -------------------------------------------------------------------------------- 1 | body { 2 | .app-big-loading { 3 | position: fixed; 4 | left: 0; 5 | top: 0; 6 | text-align: center; 7 | background: $bg-light; 8 | min-width: 100vw; 9 | min-height: 100vh; 10 | z-index: 99999; 11 | 12 | img { 13 | width: 33px; 14 | margin-top: 40vh; 15 | opacity: .6; 16 | } 17 | } 18 | 19 | &.freezed { 20 | opacity: .5; 21 | pointer-events: none; 22 | * { 23 | pointer-events: none; 24 | } 25 | } 26 | 27 | &.ready { 28 | .app-big-loading { 29 | display: none; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/scss/mac/index.scss: -------------------------------------------------------------------------------- 1 | @import '../animations'; 2 | @import '../functions'; 3 | @import '../variables'; 4 | @import '../utils'; 5 | @import '../settings'; 6 | @import '../layout'; 7 | @import '../header'; 8 | @import '../table'; 9 | @import '../footer'; 10 | @import '../prompt'; 11 | @import '../loading'; 12 | @import '../progress'; 13 | @import '../dragdrop'; 14 | @import '../ace-editor'; 15 | @import '../home'; 16 | @import '../updates'; 17 | @import '../tabs'; 18 | @import 'mac'; 19 | -------------------------------------------------------------------------------- /lib/scss/mac/mac.scss: -------------------------------------------------------------------------------- 1 | body, html { 2 | cursor: default; 3 | font-family: '-apple-system', 'BlinkMacSystemFont', 'Helvetica Neue', 'Arial', 'sans-serif'; 4 | } 5 | 6 | * { 7 | outline: none; 8 | text-decoration: none; 9 | cursor: default; 10 | 11 | &:hover, &:active, &:focus { 12 | outline: none; 13 | text-decoration: none; 14 | cursor: default; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/scss/progress.scss: -------------------------------------------------------------------------------- 1 | 2 | .left-progress { 3 | font-size: 11px; 4 | line-height: 0; 5 | width: 80%; 6 | margin: 2px 0 0 25px; 7 | 8 | small { 9 | font-size: 11px; 10 | line-height: 11px; 11 | max-height: 11px; 12 | overflow: hidden; 13 | i { 14 | font-size: 9px; 15 | } 16 | &.running-end { 17 | text-indent: -2px; 18 | display: none; 19 | } 20 | } 21 | 22 | &:hover { 23 | small { 24 | &.running-name { 25 | display: none; 26 | } 27 | &.running-end { 28 | display: inherit; 29 | } 30 | } 31 | } 32 | 33 | .left-progress-loading { 34 | height: 6px; 35 | margin-top: 6px; 36 | background: $bg-progress-secondary; 37 | border-radius: 2px; 38 | animation: loadingStripes 1.3s ease-out infinite; 39 | } 40 | 41 | &.left-progress-minor { 42 | 43 | .left-progress-loading { 44 | background: $bg-progress-minor; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/scss/prompt.scss: -------------------------------------------------------------------------------- 1 | .dialog { 2 | background: $bg-light; 3 | box-shadow: 1px .5px 3px rgba(0, 0, 0, .29), -.5px .5px .5px rgba(0, 0, 0, .2); 4 | float: none; 5 | left: calc(199px + ((100vw - 199px) / 2) - 226px); 6 | padding: 3px 5px; 7 | position: absolute; 8 | margin: 0 auto; 9 | width: 100%; 10 | max-width: 432px; 11 | z-index: 999999999; 12 | top: 34px; 13 | 14 | &:not(.dialog-window) { 15 | z-index: 999; 16 | border-radius: 1.5px 1.5px 0 0; 17 | left: 194px; 18 | border: 1px solid $color-ddd; 19 | width: calc(100vw - 208px); 20 | box-shadow: none; 21 | max-width: none; 22 | margin-top: 2px; 23 | min-height: 25px; 24 | border-bottom: 0; 25 | padding: 0; 26 | 27 | form { 28 | padding: 1px 3px; 29 | } 30 | } 31 | 32 | i { 33 | margin-left: 4px; 34 | font-size: 15px; 35 | } 36 | 37 | button { 38 | min-width: 13%; 39 | width: auto; 40 | margin-left: 8px; 41 | line-height: 13px; 42 | margin-top: 2px; 43 | font-size: 12px; 44 | 45 | img { 46 | width: 12px; 47 | top: -1px; 48 | position: relative; 49 | margin: 0; 50 | } 51 | 52 | &.disabled { 53 | pointer-events: none; 54 | opacity: .5; 55 | } 56 | } 57 | 58 | &:not(.dialog-window) { 59 | button { 60 | width: 55px; 61 | min-width: auto; 62 | 63 | img { 64 | width: 13px; 65 | } 66 | 67 | &.button-close-prompt { 68 | width: 18px; 69 | max-width: 18px; 70 | min-width: 18px; 71 | text-indent: -2.5px; 72 | 73 | i { 74 | font-size: 11px; 75 | left: -6.5px; 76 | position: relative; 77 | float: left; 78 | } 79 | } 80 | } 81 | } 82 | 83 | select { 84 | width: 77px; 85 | height: 17px; 86 | font-size: 11px; 87 | position: relative; 88 | bottom: -1px; 89 | } 90 | 91 | input { 92 | 93 | &[type="checkbox"] { 94 | vertical-align: middle; 95 | margin: 0; 96 | } 97 | &[type='text'] { 98 | border: none; 99 | line-height: initial; 100 | border-radius: $border-radius-inputs; 101 | box-shadow: 0 1px 1px rgba(0, 0, 0, .25) inset; 102 | font-size: 12px; 103 | padding: 3px 4px 2.5px 4px; 104 | width: 18%; 105 | 106 | &:first-child { 107 | width: 57%; 108 | margin-right: 0; 109 | } 110 | } 111 | } 112 | 113 | .tags-input { 114 | line-height: initial; 115 | border-radius: 3px; 116 | box-shadow: 0 1px 1px rgba(0, 0, 0, .25) inset; 117 | font-size: 12px; 118 | padding: 3px 4px 2.5px 4px; 119 | width: 66%; 120 | background: white; 121 | white-space: nowrap; 122 | overflow: hidden; 123 | float: left; 124 | margin-right: 2%; 125 | 126 | //placeholder for fake contenteditable input 127 | &:empty:before { 128 | color: $color-muted; 129 | content: attr(placeholder); 130 | } 131 | br { 132 | display: none; 133 | } 134 | 135 | * { 136 | display: inline; 137 | white-space: nowrap; 138 | } 139 | 140 | b { 141 | font-weight: normal; 142 | position: relative; 143 | bottom: -.07em; 144 | } 145 | 146 | span { 147 | background: $bg-tags; 148 | border-radius: $border-radius-tags; 149 | border: $border-tags; 150 | padding: 0 3px 1px 3px; 151 | margin: 1px 0 0 0; 152 | } 153 | } 154 | 155 | &.dialog-window { 156 | height: calc(100vh - 39px); 157 | position: fixed; 158 | left: 190px; 159 | border: 0; 160 | box-shadow: none; 161 | top: 10px; 162 | width: 100%; 163 | padding: 0; 164 | border-radius: 0; 165 | background: white; 166 | z-index: 9999; 167 | max-width: calc(100vw - 200px); 168 | border: 1px solid $color-ccc; 169 | } 170 | 171 | .window { 172 | float: none; 173 | height: calc(100vh - 65px); 174 | overflow: auto; 175 | background: white; 176 | font-size: 12.5px; 177 | margin: 0 auto; 178 | margin-top: -4px; 179 | 180 | .logs { 181 | padding: 1px 6px; 182 | 183 | &:first-child { 184 | margin-top: 5px; 185 | } 186 | } 187 | .logs-error { 188 | color: $color-error; 189 | } 190 | 191 | .ng-ace-editor { 192 | height: 100%; 193 | } 194 | } 195 | } 196 | 197 | .prompt-history-item { 198 | line-height: 23px; 199 | width: 100%; 200 | margin: 0 auto; 201 | 202 | &:first-child { 203 | padding-top: 3px; 204 | } 205 | &:hover { 206 | &:not(.active) { 207 | background: $bg-lighter; 208 | } 209 | } 210 | 211 | &.active { 212 | background-color: $bg-muted-invisible; 213 | } 214 | 215 | i { 216 | font-size: 13px; 217 | color: $color-primary; 218 | 219 | &.fa-caret-right { 220 | color: $color-222; 221 | } 222 | } 223 | } 224 | 225 | .prompt-history-status { 226 | width: 100%; 227 | margin: 0 auto; 228 | float: none; 229 | height: 300px; 230 | overflow: auto; 231 | } 232 | 233 | .prompt-window-infos { 234 | font-size: 12.5px; 235 | max-width: 200px; 236 | white-space: nowrap; 237 | overflow: hidden; 238 | text-overflow: ellipsis; 239 | display: inline-block; 240 | } 241 | .prompt-window-holder { 242 | line-height: 100px; 243 | text-align: center; 244 | } 245 | .prompt-window-options { 246 | padding: 0 6px; 247 | width: 100%; 248 | display: inline-block; 249 | background: $bg-light; 250 | margin: 0 auto; 251 | line-height: 25px; 252 | float: none; 253 | height: 30px; 254 | 255 | i { 256 | margin-left: 0; 257 | color: $color-777; 258 | margin-right: 5px; 259 | font-size: 13px; 260 | } 261 | 262 | button { 263 | height: 19.5px; 264 | line-height: 13px; 265 | font-size: 12px; 266 | float: right; 267 | margin-top: 3px; 268 | } 269 | 270 | img { 271 | width: 13px; 272 | } 273 | 274 | .prompt-resize-holder { 275 | width: 130%; 276 | height: 40px; 277 | z-index: -1; 278 | position: absolute; 279 | bottom: -10px; 280 | left: -5%; 281 | 282 | &:hover, &:focus, &:active { 283 | cursor: ns-resize; 284 | } 285 | } 286 | } 287 | 288 | .prompt-search { 289 | font-size: 11.5px; 290 | margin-top: 3px; 291 | color: $color-muted; 292 | overflow: hidden; 293 | overflow-y: auto; 294 | max-height: 39.8vh; 295 | border-bottom: 1px solid $color-ddd; 296 | box-shadow: 10px -10px 10px $bg-eee inset; 297 | 298 | .prompt-search-loader { 299 | color: $color-222; 300 | padding: 1.5px 3.5px 2.5px 3.5px; 301 | 302 | img { 303 | vertical-align: bottom; 304 | margin: 0; 305 | margin-right: 2px; 306 | width: 16px; 307 | } 308 | } 309 | 310 | h5 { 311 | color: $color-222; 312 | margin: 0; 313 | padding: 0; 314 | margin-top: 5px; 315 | font-weight: 500; 316 | font-size: 12px; 317 | margin-bottom: 3px; 318 | } 319 | 320 | .prompt-search-item { 321 | padding: 3px 6px; 322 | border-bottom: 1px solid $bg-muted-invisible; 323 | 324 | &:last-child { 325 | border: 0; 326 | } 327 | 328 | &:hover { 329 | background: $bg-muted-invisible; 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /lib/scss/settings.scss: -------------------------------------------------------------------------------- 1 | * { 2 | outline: none; 3 | text-decoration: none; 4 | 5 | &:hover, &:active, &:focus { 6 | outline: none; 7 | text-decoration: none; 8 | } 9 | } 10 | 11 | body, html { 12 | -webkit-user-select: none; 13 | color: $color-222; 14 | background: $bg-light; 15 | font-size: 13px; 16 | height: 100vh; 17 | margin: 0 auto; 18 | overflow: hidden; 19 | padding: 0; 20 | } 21 | 22 | a { 23 | cursor: default; 24 | outline: none; 25 | color: $color-222; 26 | text-decoration: none; 27 | 28 | &:hover, &:active, &:focus { 29 | cursor: default; 30 | outline: none; 31 | text-decoration: none; 32 | color: $color-222; 33 | } 34 | } 35 | 36 | small { 37 | font-size: 12px; 38 | 39 | * { 40 | font-size: 12px; 41 | } 42 | } 43 | 44 | pre { 45 | border: none; 46 | font-size: 11.5px; 47 | line-height: 20px; 48 | } 49 | 50 | button { 51 | 52 | i { 53 | font-size: 13px; 54 | vertical-align: middle; 55 | } 56 | 57 | &[disabled] { 58 | opacity: .5; 59 | } 60 | } 61 | 62 | ::-webkit-input-placeholder { 63 | font-weight: lighter; 64 | } 65 | 66 | input { 67 | 68 | &:focus { 69 | box-shadow: 0 0 4px $color-royalblue; 70 | } 71 | 72 | &[disabled] { 73 | opacity: .5; 74 | } 75 | 76 | &[readonly] { 77 | cursor: not-allowed; 78 | } 79 | } 80 | 81 | h1, h2, h3, h4, h5, h6 { 82 | color: $color-666; 83 | font-weight: bold; 84 | } 85 | 86 | b, strong { 87 | 88 | font-weight: 600; 89 | } 90 | 91 | //RESET BUTTONS DUE BOOTSTRAP STYLE 92 | -------------------------------------------------------------------------------- /lib/scss/table.scss: -------------------------------------------------------------------------------- 1 | .table-header { 2 | background: $bg-light; 3 | font-size: 12px; 4 | padding-top: 1.5px; 5 | line-height: 18px; 6 | width: calc(100% - 8px); 7 | margin: 0 auto; 8 | transition: opacity .3s linear; 9 | border: 1px solid $color-ddd; 10 | border-radius: 1.5px 1.5px 0 0; 11 | 12 | .clickable { 13 | &:active { 14 | opacity: .7; 15 | } 16 | } 17 | 18 | div { 19 | background: none; 20 | text-align: left; 21 | padding: 0 4px; 22 | border-left: 1px solid $color-ddd; 23 | 24 | &:first-child { 25 | border-left: 0; 26 | } 27 | } 28 | 29 | i { 30 | float: right; 31 | position: relative; 32 | 33 | &.fa-sort-up { 34 | top: 3px; 35 | } 36 | &.fa-sort-down { 37 | bottom: 2px; 38 | } 39 | } 40 | 41 | } 42 | 43 | .table-row { 44 | line-height: 22.5px; 45 | padding: 0 5px; 46 | 47 | b { 48 | font-weight: 500; 49 | } 50 | &:nth-child(odd) { 51 | background: $bg-table-row; 52 | } 53 | 54 | &:focus, &:active, &.active { 55 | 56 | &:not(.disabled) { 57 | * { 58 | color: white; 59 | } 60 | } 61 | 62 | background: $bg-table-row-highlight; 63 | } 64 | 65 | i { 66 | color: $color-muted; 67 | } 68 | 69 | &.disabled { 70 | background: none; 71 | cursor: not-allowed; 72 | 73 | * { 74 | cursor: not-allowed; 75 | color: $color-muted; 76 | } 77 | 78 | &:active, &:hover, &.active, &:focus { 79 | cursor: not-allowed; 80 | * { 81 | cursor: not-allowed; 82 | color: $color-muted; 83 | } 84 | } 85 | } 86 | } 87 | 88 | .table-body { 89 | //(table - header - table-infos) to calculate height 90 | height: calc(100vh - 155px - 105px); 91 | overflow-x: hidden; 92 | padding-bottom: 20px; 93 | background: white; 94 | border-radius: 0 0 2px 2px; 95 | width: calc(100% - 8px); 96 | margin: 0 auto; 97 | border: 1px solid $color-ccc; 98 | border-top: 0; 99 | margin-bottom: 5px; 100 | 101 | &.freezed { 102 | cursor: wait; 103 | * { 104 | cursor: wait; 105 | pointer-events: none; 106 | } 107 | } 108 | } 109 | 110 | .table-loader { 111 | padding-top: 41vh; 112 | position: relative; 113 | margin-top: -30vh; 114 | z-index: 9; 115 | background: white; 116 | text-align: center; 117 | .table-loader-content { 118 | font-size: 12px; 119 | margin-top: 3vh; 120 | img { 121 | width: 17px; 122 | margin-right: 1px; 123 | } 124 | } 125 | } 126 | 127 | .table-row-loading, 128 | .table-row-loading.active, 129 | .table-row-loading.active:active, 130 | .table-row-loading.active:focus { 131 | background: $bg-table-row-loading; 132 | animation: loadingStripes 1.2s linear infinite; 133 | box-shadow: 0 1px 0 rgba(0, 0, 0, .03) inset; 134 | } 135 | 136 | .table-infos { 137 | background: $bg-light; 138 | max-height: 140px; 139 | padding: 0 5px 5px 5px; 140 | overflow-y: auto; 141 | font-size: 12px; 142 | color: $color-777; 143 | border: 1px solid $color-ccc; 144 | border-radius: 2px; 145 | width: calc(100% - 8px); 146 | margin: 0 auto; 147 | 148 | .information { 149 | margin-top: 2px; 150 | -webkit-user-select: text; 151 | 152 | b { 153 | font-weight: 500; 154 | font-size: 11.5px; 155 | color: $color-222; 156 | } 157 | a { 158 | color: $color-primary; 159 | 160 | &:hover { 161 | color: $color-primary; 162 | background: $bg-muted-invisible; 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/scss/tabs.scss: -------------------------------------------------------------------------------- 1 | .tab { 2 | overflow-x: hidden; 3 | .tab-menu { 4 | display: flex; 5 | overflow: hidden; 6 | height: 23px; 7 | background: $bg-light; 8 | } 9 | .tab-button { 10 | display: inline-flex; 11 | font-size: 13px; 12 | background: $bg-light; 13 | padding: 4px 3px 5px 6px; 14 | line-height: 15px; 15 | white-space: nowrap; 16 | position: relative; 17 | bottom: -1px; 18 | 19 | img { 20 | vertical-align: top; 21 | margin-right: 3px; 22 | } 23 | a { 24 | opacity: .5; 25 | border-radius: 3px; 26 | color: #666; 27 | margin: 0 4px; 28 | margin-left: 8px; 29 | 30 | i { 31 | display: block; 32 | width: 13px; 33 | } 34 | 35 | &:hover { 36 | opacity: 1; 37 | } 38 | } 39 | &.active { 40 | border-radius: 4px 4px 0 0; 41 | background: white; 42 | 43 | a { 44 | opacity: 1; 45 | font-size: 7px; 46 | position: relative; 47 | bottom: -1px; 48 | background: $color-error; 49 | color: white; 50 | text-shadow: 0 -1px rgba(0, 0, 0, .3); 51 | box-shadow: 0 2px 3px red inset; 52 | right: -2px; 53 | height: 13px; 54 | width: 13px; 55 | line-height: 12px; 56 | text-align: center; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/scss/updates.scss: -------------------------------------------------------------------------------- 1 | body.updates { 2 | background: $bg-light; 3 | 4 | * { 5 | color: $color-222; 6 | } 7 | 8 | h1 { 9 | font-size: 14px; 10 | line-height: 16px; 11 | margin-bottom: 0; 12 | } 13 | 14 | h2 { 15 | font-size: 13px; 16 | font-weight: normal; 17 | margin-top: 10px; 18 | line-height: 17px; 19 | 20 | &.is-uptodate, &.is-to-update { 21 | i { 22 | color: $color-positive; 23 | } 24 | } 25 | } 26 | 27 | img { 28 | width: 17px; 29 | position: relative; 30 | top: -1px; 31 | margin-right: 1px; 32 | } 33 | 34 | small { 35 | font-size: 11.5px; 36 | color: $color-muted-less; 37 | } 38 | 39 | .left { 40 | text-align: center; 41 | } 42 | 43 | .logo { 44 | margin-top: 10vh; 45 | width: 90px; 46 | } 47 | 48 | button { 49 | margin: 0; 50 | line-height: 21px; 51 | padding: 0 15px; 52 | font-size: 12.5px; 53 | } 54 | 55 | i { 56 | margin-left: -4px; 57 | &.errored { 58 | color: $color-error; 59 | } 60 | } 61 | 62 | progress { 63 | margin-top: 5px; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/scss/utils.scss: -------------------------------------------------------------------------------- 1 | .overflow-x-hidden { 2 | overflow-x: hidden; 3 | } 4 | 5 | .out-of-screen { 6 | position: absolute; 7 | z-index: -1; 8 | } 9 | 10 | .separator10 { 11 | clear: both; 12 | float: none; 13 | min-height: 10px; 14 | width: 100%; 15 | } 16 | 17 | .action-link-disabled { 18 | opacity: .5; 19 | } 20 | -------------------------------------------------------------------------------- /lib/scss/variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | fonts 3 | */ 4 | $font-icon: 'fontello'; 5 | /* 6 | colors 7 | */ 8 | $color-error: #e81616; 9 | $color-stripes-one: rgba(55, 255, 255, .4); 10 | $color-stripes-two: rgba(155, 255, 255, .5); 11 | $color-primary: #327dff; 12 | $color-positive: $color-primary; 13 | $color-muted: rgba(0, 0, 0, .4); 14 | $color-muted-more: rgba(0, 0, 0, .28); 15 | $color-muted-less: rgba(0, 0, 0, .5); 16 | $color-royalblue: royalblue; 17 | $color-green: #00d842; 18 | $color-npm: #cc0000; 19 | $color-999: #999; 20 | $color-777: #777; 21 | $color-666: #666; 22 | $color-444: #444; 23 | $color-222: #222; 24 | $color-ddd: #ddd; 25 | $color-ccc: #ccc; 26 | /* 27 | backgrounds 28 | */ 29 | $bg-header: #dedede; 30 | $bg-light: #f0f0f0; 31 | $bg-lighter: #fcfcfc; 32 | $bg-eee: #eee; 33 | $bg-muted: rgba(0, 0, 0, .35); 34 | $bg-muted-more: rgba(0, 0, 0, .15); 35 | $bg-muted-invisible: rgba(0, 0, 0, .065); 36 | $bg-error: #e81616; 37 | $bg-table-row: #eee; 38 | $bg-table-row-highlight: $color-positive; 39 | $bg-table-row-loading: repeating-linear-gradient(90deg, $color-primary, $color-primary 5px, 0px, #0882f5 10px); 40 | $bg-progress: repeating-linear-gradient(135deg, $color-primary, $color-primary 5px, #147ada 5px, #147ada 10px); 41 | $bg-progress-secondary: repeating-linear-gradient(135deg, $color-primary, $color-primary 5px, #147ada 5px, #147ada 10px); 42 | $bg-progress-minor: repeating-linear-gradient(135deg, #d83232, #d83232 5px, #c71212 5px, #c71212 10px);; 43 | $bg-tags: #d7ebff; 44 | /*border-radius*/ 45 | $border-radius-inputs: 3px; 46 | $border-radius-button: 3px; 47 | $border-radius-tags: 2px; 48 | $border-radius-prompt: 0 0 2px 2px; 49 | /*border-color*/ 50 | $border-tags: 1px solid #a9cffd; 51 | -------------------------------------------------------------------------------- /lib/scss/win/index.scss: -------------------------------------------------------------------------------- 1 | @import '../animations'; 2 | @import '../functions'; 3 | @import '../variables'; 4 | @import '../utils'; 5 | @import '../settings'; 6 | @import '../layout'; 7 | @import '../header'; 8 | @import '../table'; 9 | @import '../footer'; 10 | @import '../prompt'; 11 | @import '../loading'; 12 | @import '../progress'; 13 | @import '../dragdrop'; 14 | @import '../ace-editor'; 15 | @import '../home'; 16 | @import '../updates'; 17 | @import '../tabs'; 18 | @import 'win'; 19 | -------------------------------------------------------------------------------- /lib/scss/win/win.scss: -------------------------------------------------------------------------------- 1 | body, html { 2 | cursor: default; 3 | font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; 4 | } 5 | 6 | a, button, .fake-link { 7 | cursor: pointer; 8 | &:hover, &:active, &:focus { 9 | cursor: pointer; 10 | } 11 | } 12 | 13 | .page { 14 | 15 | box-shadow: 0 1px 0 $color-ccc inset; 16 | } 17 | 18 | .home { 19 | button { 20 | height: 23px; 21 | } 22 | } 23 | 24 | .dialog { 25 | select { 26 | vertical-align: text-top; 27 | } 28 | } 29 | 30 | .ace_editor { 31 | font-size: 14px !important; 32 | } 33 | -------------------------------------------------------------------------------- /lib/top.pug: -------------------------------------------------------------------------------- 1 | .top-menu(top-menu, top-menu-project-path-id="{{tab}}", ng-class="{'freezed': performingAction}") 2 | include ./install-new-package-version.pug 3 | include ./install-new-package.pug 4 | .row 5 | .col-xs-12 6 | button.button-add-package(title="Add Packages", ng-click="showInstallPrompt = true", ng-class="{'active': showInstallPrompt}") 7 | i.fa.fa-plus-circle 8 | | Add package 9 | span(ng-show='showMenuButtons') 10 | button.button-uninstall(title="Uninstall", ng-click="activeClickedLink('5'); uninstallPackage(currentSelectedPackages)", ng-class="{'active': activeLink === '5'}") 11 | i.fa.fa-remove 12 | | Uninstall 13 | button.button-update(title="Update", ng-click="activeClickedLink('2'); updatePackage(currentSelectedPackages)", ng-class="{'active': activeLink === '2'}") 14 | i.fa.fa-level-up 15 | | Update 16 | button.button-latest(title="Install Latest", ng-click="activeClickedLink('3'); installLatest(currentSelectedPackages)", ng-class="{'active': activeLink === '3'}") 17 | i.fa.fa-rocket 18 | | Latest 19 | button.button-version(title="Install Version", ng-show="currentSelectedPackages.length === 1", ng-click="showSpecificVersionPrompt = true", ng-class="{'active': showSpecificVersionPrompt}") 20 | i.fa.fa-at 21 | | Version 22 | -------------------------------------------------------------------------------- /lib/update.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset='UTF-8') 5 | 6 | link(rel='stylesheet', href='icons/fontello/css/fontello-embedded.css', media='screen', charset='utf-8') 7 | link(rel='stylesheet', href='../node_modules/bootstrap/dist/css/bootstrap.min.css', media='screen', charset='utf-8') 8 | link(rel='stylesheet', href='css/index.css', media='screen', charset='utf-8') 9 | 10 | script(type='text/javascript', src='../node_modules/angular/angular.min.js') 11 | script(type='text/javascript', src='../node_modules/ace-builds/src-min-noconflict/ace.js') 12 | 13 | script(type='text/javascript', src='js/update.js') 14 | body.updates(ng-app='ndm-updater') 15 | .container(ng-controller="ShellController as vm") 16 | .col-xs-4.left 17 | img.logo(src="../icon.ico") 18 | .copy 19 | small 20 | | © 720kb 21 | .col-xs-8 22 | h1 23 | | ndm 24 | div 25 | small 26 | | Installed version {{ vm.currentVersion }} 27 | 28 | h2(ng-show="vm.checking") 29 | img(src="img/loading.svg") 30 | | Checking for updates ... 31 | h2(ng-show="vm.errorChecking") 32 | i(class="fa fa-attention-circled errored") 33 | | Unavailable please retry later. 34 | 35 | h2.is-uptodate(ng-show="!vm.checking && vm.toUpdate === false") 36 | i(class="fa fa-check") 37 | | Already up to date 38 | div 39 | small 40 | | ndm is up to date with {{ vm.currentVersion }} 41 | 42 | button(ng-click="vm.checkNow()", ng-show="!vm.updating && !vm.checking && !vm.toUpdate") 43 | | Check again 44 | 45 | h2.is-to-update(ng-show="vm.toUpdate") 46 | | Updates available 47 | div 48 | small 49 | | Install the updates now. 50 | button(ng-click="vm.updateIt()", ng-show="vm.toUpdate") 51 | | Install and Relaunch 52 | 53 | h2.is-downloading(ng-show="vm.updating") 54 | span(ng-show="!vm.progress || vm.progress <= 90") 55 | | Downloading updates ... 56 | span(ng-show="vm.progress > 90") 57 | | Installing updates ... 58 | progress(class="col-xs-11", ng-value="vm.progress", max="100") 59 | 60 | h2(ng-show="vm.errorUpdating") 61 | i(class="fa fa-attention-circled errored") 62 | | Error updating, please retry later. 63 | div 64 | small 65 | | This is weird... consider download ndm again. 66 | -------------------------------------------------------------------------------- /menu.js: -------------------------------------------------------------------------------- 1 | /*global module process*/ 2 | (function withNode() { 3 | 4 | module.exports = (mainWindow, updateWindow, shell, packageJSON, app) => { 5 | 6 | let menuTemplate; 7 | 8 | const aboutMenuItem = { 9 | 'submenu': [ 10 | { 11 | 'role': 'about' 12 | }, 13 | { 14 | 'label': `Version ${packageJSON.version}`, 15 | 'enabled': false 16 | }, 17 | { 18 | 'label': 'Check for Updates...', 19 | click() { 20 | 21 | mainWindow.webContents.send('loading:freeze-app'); 22 | updateWindow.setMenu(null); 23 | updateWindow.show(); 24 | } 25 | }, 26 | { 27 | 'type': 'separator' 28 | }, 29 | { 30 | 'label': 'Visit Website', 31 | click() { 32 | shell.openExternal(packageJSON.homepage); 33 | } 34 | } 35 | ] 36 | } 37 | , fileMenuItem = { 38 | 'label': 'File', 39 | 'submenu': [ 40 | { 41 | 'label': 'Add Project...', 42 | 'accelerator': 'CmdOrCtrl+O', 43 | click() { 44 | mainWindow.webContents.send('menu:add-project-folder'); 45 | } 46 | } 47 | ] 48 | } 49 | , editMenuItem = { 50 | 'label': 'Edit', 51 | 'submenu': [ 52 | { 53 | 'role': 'undo' 54 | }, 55 | { 56 | 'role': 'redo' 57 | }, 58 | { 59 | 'type': 'separator' 60 | }, 61 | { 62 | 'role': 'cut' 63 | }, 64 | { 65 | 'role': 'copy' 66 | }, 67 | { 68 | 'role': 'paste' 69 | }, 70 | { 71 | 'role': 'pasteandmatchstyle' 72 | }, 73 | { 74 | 'role': 'delete' 75 | }, 76 | { 77 | 'role': 'selectall' 78 | } 79 | ] 80 | } 81 | , viewMenuItem = { 82 | 'label': 'View', 83 | 'submenu': [ 84 | { 85 | 'role': 'togglefullscreen' 86 | }, 87 | { 88 | 'type': 'separator' 89 | }, 90 | { 91 | 'label': 'Developer', 92 | 'submenu': [{ 93 | 'label': 'Open DevTools', 94 | click(item, focusedWindow) { 95 | if (focusedWindow) { 96 | focusedWindow.openDevTools(); 97 | } 98 | } 99 | }] 100 | } 101 | ] 102 | } 103 | , windowMenuItem = { 104 | 'role': 'window', 105 | 'submenu': [ 106 | { 107 | 'role': 'minimize' 108 | }, 109 | { 110 | 'role': 'close' 111 | } 112 | ] 113 | } 114 | , helpMenuItem = { 115 | 'role': 'help', 116 | 'submenu': [ 117 | { 118 | 'label': 'More About', 119 | click() { 120 | shell.openExternal(`${packageJSON.github}`); 121 | } 122 | }, 123 | { 124 | 'label': 'Report an issue', 125 | click() { 126 | shell.openExternal(`${packageJSON.bugs.url}`); 127 | } 128 | }, 129 | { 130 | 'type': 'separator' 131 | }, 132 | { 133 | 'label': 'Donate', 134 | click() { 135 | shell.openExternal(packageJSON.donate.opencollective); 136 | } 137 | }, 138 | { 139 | 'type': 'separator' 140 | }, 141 | { 142 | 'label': 'Join Chat', 143 | click() { 144 | shell.openExternal(`${packageJSON.social.gitter.url}`); 145 | } 146 | }, 147 | { 148 | 'label': 'Follow on Twitter', 149 | click() { 150 | shell.openExternal(`${packageJSON.social.twitter.url}`); 151 | } 152 | } 153 | ] 154 | }; 155 | 156 | if (process.platform !== 'darwin' && 157 | process.platform !== 'win32') { 158 | 159 | //if linux no need for "check for updates" 160 | aboutMenuItem.submenu.splice(2, 1); 161 | } 162 | 163 | if (process.platform && 164 | process.platform === 'darwin') { 165 | aboutMenuItem.label = packageJSON.name; 166 | aboutMenuItem.submenu.push({ 167 | 'type': 'separator' 168 | }); 169 | aboutMenuItem.submenu.push({ 170 | 'role': 'hide' 171 | }); 172 | aboutMenuItem.submenu.push({ 173 | 'role': 'hideothers' 174 | }); 175 | aboutMenuItem.submenu.push({ 176 | 'role': 'unhide' 177 | }); 178 | aboutMenuItem.submenu.push({ 179 | 'type': 'separator' 180 | }); 181 | aboutMenuItem.submenu.push({ 182 | 'label': 'Restart', 183 | 'accelerator': 'CmdOrCtrl+R', 184 | click() { 185 | app.relaunch(); 186 | app.quit(); 187 | } 188 | }); 189 | aboutMenuItem.submenu.push({ 190 | 'role': 'quit' 191 | }); 192 | 193 | menuTemplate = [ 194 | aboutMenuItem, 195 | fileMenuItem, 196 | editMenuItem, 197 | viewMenuItem, 198 | windowMenuItem, 199 | helpMenuItem 200 | ]; 201 | } else { 202 | aboutMenuItem.label = 'About'; 203 | 204 | viewMenuItem.submenu.unshift({ 205 | 'label': 'Toggle menu', 206 | click() { 207 | mainWindow.setAutoHideMenuBar(true); 208 | if (mainWindow.isMenuBarVisible()) { 209 | 210 | mainWindow.setMenuBarVisibility(false); 211 | } else { 212 | 213 | mainWindow.setMenuBarVisibility(true); 214 | } 215 | } 216 | }); 217 | 218 | menuTemplate = [ 219 | fileMenuItem, 220 | editMenuItem, 221 | viewMenuItem, 222 | helpMenuItem, 223 | aboutMenuItem 224 | ]; 225 | } 226 | 227 | return menuTemplate; 228 | }; 229 | }()); 230 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ndm", 3 | "version": "1.2.0", 4 | "description": "npm desktop manager", 5 | "main": "index.js", 6 | "homepage": "https://720kb.github.io/ndm", 7 | "github": "https://github.com/720kb/ndm", 8 | "donate": { 9 | "opencollective": "https://opencollective.com/ndm" 10 | }, 11 | "author": { 12 | "name": "720kb", 13 | "email": "tech@720kb.net", 14 | "url": "http://720kb.net" 15 | }, 16 | "authors": [ 17 | "720kb", 18 | "Filippo Oretti", 19 | "Dario Andrei" 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/720kb/ndm.git" 24 | }, 25 | "copyright": "©720kb, Filippo Oretti, Dario Andrei", 26 | "bugs": { 27 | "url": "https://github.com/720kb/ndm/issues" 28 | }, 29 | "license": { 30 | "type": "GPL-3.0", 31 | "url": "https://github.com/720kb/ndm/blob/master/LICENSE.md" 32 | }, 33 | "build": { 34 | "appId": "net.720kb.ndm", 35 | "copyright": "© 720kb - Filippo Oretti - Dario Andrei", 36 | "asar": false, 37 | "productName": "ndm", 38 | "icon": "icon.icns", 39 | "mac": { 40 | "category": "public.app-category.productivity", 41 | "target": [ 42 | "dmg", 43 | "zip" 44 | ] 45 | }, 46 | "dmg": { 47 | "backgroundColor": "#cdcdcd" 48 | }, 49 | "linux": { 50 | "maintainer": "720kb.net", 51 | "category": "Utility", 52 | "description": "npm desktop manager", 53 | "packageCategory": "Utility", 54 | "target": [ 55 | "deb", 56 | "rpm", 57 | "zip" 58 | ] 59 | }, 60 | "win": { 61 | "icon": "icon.ico", 62 | "target": [ 63 | "zip", 64 | "nsis" 65 | ] 66 | }, 67 | "files": [ 68 | "node_modules", 69 | "dist", 70 | "index.js", 71 | "menu.js", 72 | "icon.ico", 73 | "LICENSE.md" 74 | ], 75 | "directories": { 76 | "output": "./releases" 77 | } 78 | }, 79 | "social": { 80 | "twitter": { 81 | "url": "https://twitter.com/720kb_" 82 | }, 83 | "gitter": { 84 | "url": "https://gitter.im/720kb/ndm" 85 | } 86 | }, 87 | "appTemplate": { 88 | "title": "ndm", 89 | "width": 640, 90 | "height": 420, 91 | "minWidth": 640, 92 | "minHeight": 420, 93 | "show": false, 94 | "center": true, 95 | "movable": true, 96 | "resizable": true, 97 | "minimizable": true, 98 | "maximizable": true, 99 | "closable": true, 100 | "fullscreenable": true 101 | }, 102 | "scripts": { 103 | "precommit": "npm run lint", 104 | "lint": "gulp lint", 105 | "prestart": "npm install && gulp dist --platform=mac", 106 | "start": "electron .", 107 | "mac": "LANG=en_US.UTF-8 && gulp dist --platform=mac && electron .", 108 | "linux": "gulp dist --platform=linux && electron .", 109 | "win": "gulp dist --platform=win && electron .", 110 | "build": "npm run build-mac && npm run build-linux && npm run build-win", 111 | "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 :).'", 112 | "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 :).'", 113 | "build-linux": "gulp distify --platform=linux && build --linux --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 :).'", 114 | "build-linux-deb": "gulp distify --platform=linux && build --linux deb --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 :).'", 115 | "postversion": "git push && git push --tags" 116 | }, 117 | "dependencies": { 118 | "ace-builds": "^1.2.5", 119 | "adm-zip": "^0.4.7", 120 | "angular": "^1.6.0", 121 | "bootstrap": "^3.3.6", 122 | "electron-storage": "^1.0.6", 123 | "fs-extra": "^2.0.0", 124 | "node-fetch": "^1.6.3", 125 | "npm": "^4.4.0", 126 | "rimraf": "^2.6.1", 127 | "selection-model": "^0.11.0", 128 | "shell-path": "^2.0.0", 129 | "universal-analytics": "^0.4.8", 130 | "uuid": "^3.0.1" 131 | }, 132 | "devDependencies": { 133 | "babel-preset-es2015-rollup": "^3.0.0", 134 | "del": "^2.2.0", 135 | "electron": "^1.6.6", 136 | "electron-builder": "^17.3.1", 137 | "eslint": "^3.13.1", 138 | "gulp": "^3.9.1", 139 | "gulp-clean-css": "^2.2.0", 140 | "gulp-eslint": "^3.0.1", 141 | "gulp-ng-annotate": "^2.0.0", 142 | "gulp-plumber": "^1.1.0", 143 | "gulp-pug": "^3.2.0", 144 | "gulp-sass": "^3.1.0", 145 | "gulp-sourcemaps": "^2.3.1", 146 | "gulp-uglify": "^2.0.0", 147 | "husky": "^0.12.0", 148 | "pug": "^2.0.0-beta6", 149 | "require-dir": "^0.3.1", 150 | "rollup": "^0.41.1", 151 | "rollup-plugin-babel": "^2.4.0", 152 | "rollup-plugin-json": "^2.0.0", 153 | "run-sequence": "^1.1.5", 154 | "yargs": "^6.5.0" 155 | } 156 | } 157 | --------------------------------------------------------------------------------