├── .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 | 
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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------