├── .appveyor.yml ├── .babelrc ├── .deblist ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .rpmlist ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── RELEASENOTES.json ├── app ├── fonts │ ├── MaterialIcons-Regular.eot │ ├── MaterialIcons-Regular.svg │ ├── MaterialIcons-Regular.ttf │ ├── MaterialIcons-Regular.woff │ ├── MaterialIcons-Regular.woff2 │ ├── Roboto-Light.ttf │ ├── Roboto-Light.woff │ ├── Roboto-Light.woff2 │ ├── photon-entypo.eot │ ├── photon-entypo.svg │ ├── photon-entypo.ttf │ └── photon-entypo.woff ├── html │ ├── controller.html │ ├── overlay.html │ └── preferences.html ├── images │ ├── darwin-tray-icon-default-Template.png │ ├── darwin-tray-icon-default-Template@2x.png │ ├── darwin-tray-icon-translucent-Template.png │ ├── darwin-tray-icon-translucent-Template@2x.png │ ├── linux-tray-icon-default.png │ ├── linux-tray-icon-translucent.png │ ├── win32-tray-icon-default.png │ └── win32-tray-icon-translucent.png ├── scripts │ ├── main │ │ ├── components │ │ │ ├── application.js │ │ │ ├── globals.js │ │ │ └── menubar.js │ │ ├── managers │ │ │ ├── configuration-manager.js │ │ │ └── overlay-manager.js │ │ ├── menus │ │ │ └── tray-menu.js │ │ ├── providers │ │ │ └── dialog-provider.js │ │ ├── services │ │ │ ├── debug-service.js │ │ │ ├── power-service.js │ │ │ └── updater-service.js │ │ └── windows │ │ │ ├── overlay-configuration.js │ │ │ ├── overlay-window.js │ │ │ └── preferences-window.js │ └── renderer │ │ ├── controller.js │ │ ├── overlay.js │ │ └── preferences.js ├── styles │ └── styles.css └── vendor │ ├── material-icons.css │ ├── photon.css │ └── roboto.css ├── bin └── cli.js ├── gulpfile.babel.js ├── icons ├── darwin │ ├── background-setup.png │ ├── icon-setup.icns │ └── icon.icns ├── linux │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── 96x96.png │ ├── icon-setup.png │ └── icon.png └── win32 │ ├── background-setup.bmp │ ├── header-setup.bmp │ ├── icon-setup.ico │ └── icon.ico ├── package-lock.json ├── package.json ├── resources ├── graphics │ ├── icon.ai │ └── icon.png └── screenshots │ ├── screenshot-macos.png │ └── screenshot-win32.png └── yarn.lock /.appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2015 2 | 3 | platform: 4 | - x64 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | version: '{build}-{branch}' 11 | 12 | cache: 13 | - "node_modules" 14 | - "%LOCALAPPDATA%\\Yarn" 15 | 16 | init: 17 | - cmd: echo 🚦 Authorizing Build 18 | - ps: if ($env:OS -eq "Windows_NT" -And $env:DEPLOY_WINDOWS -eq "0") { $host.SetShouldExit(0) } 19 | - cmd: git config --global core.autocrlf input 20 | 21 | install: 22 | - cmd: echo 🔧 Setting up Node 23 | - ps: Install-Product node 9 24 | - cmd: npm --global update npm 25 | - cmd: npm --global install yarn 26 | 27 | before_build: 28 | - cmd: echo 📥 Installing Dependencies 29 | - cmd: yarn install 30 | 31 | build_script: 32 | - cmd: echo 📦 Building 33 | - cmd: npm run build --metadata 34 | 35 | deploy_script: 36 | - cmd: echo 📮 Deploying to GitHub 37 | - cmd: npm run deploy 38 | 39 | artifacts: 40 | - path: build\output\*.exe 41 | 42 | notifications: 43 | - provider: Webhook 44 | url: https://webhooks.gitter.im/e/a1a9de50af1c703bfe6c 45 | method: GET 46 | on_build_success: true 47 | on_build_failure: true 48 | on_build_status_changed: true 49 | 50 | test: off 51 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["electron"] 3 | } 4 | -------------------------------------------------------------------------------- /.deblist: -------------------------------------------------------------------------------- 1 | linuxmint/sarah 2 | linuxmint/sonya 3 | linuxmint/serena 4 | elementaryos/freya 5 | debian/jessie 6 | debian/stretch 7 | debian/buster 8 | ubuntu/xenial 9 | ubuntu/yakkety 10 | ubuntu/zesty 11 | ubuntu/artful -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [.eslintrc] 13 | indent_size = 2 14 | 15 | [*.json] 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | 21 | [*.yml] 22 | indent_size = 2 23 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | resources/** -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "globals": { 8 | "global": false, 9 | "getEventListeners": false 10 | }, 11 | "rules": { 12 | "no-bitwise": "off", 13 | "camelcase": "off", 14 | "curly": "error", 15 | "eqeqeq": "error", 16 | "wrap-iife": "off", 17 | "indent": [ 18 | "error", 19 | 4, 20 | { 21 | "SwitchCase": 1 22 | } 23 | ], 24 | "no-use-before-define": [ 25 | "error", 26 | { 27 | "functions": false 28 | } 29 | ], 30 | "no-caller": "error", 31 | "quotes": [ 32 | "error", 33 | "single", 34 | { 35 | "allowTemplateLiterals": true 36 | } 37 | ], 38 | "no-undef": "error", 39 | "no-unused-vars": "error", 40 | "strict": [ 41 | "error", 42 | "global" 43 | ], 44 | "dot-notation": "off", 45 | "no-with": "error", 46 | "no-mixed-spaces-and-tabs": "error", 47 | "no-multi-str": "error", 48 | "key-spacing": [ 49 | "error", 50 | { 51 | "beforeColon": false, 52 | "afterColon": true 53 | } 54 | ], 55 | "object-curly-spacing": [ 56 | "error", 57 | "always" 58 | ], 59 | "space-unary-ops": [ 60 | "error", 61 | { 62 | "words": false, 63 | "nonwords": false 64 | } 65 | ], 66 | "space-before-function-paren": [ 67 | "error", 68 | { 69 | "anonymous": "ignore", 70 | "named": "never" 71 | } 72 | ], 73 | "space-in-parens": [ 74 | "error", 75 | "never" 76 | ], 77 | "comma-dangle": [ 78 | "error", 79 | "never" 80 | ], 81 | "no-trailing-spaces": "error", 82 | "comma-style": [ 83 | "error", 84 | "last" 85 | ], 86 | "eol-last": "error", 87 | "space-infix-ops": "error", 88 | "keyword-spacing": [ 89 | "error", 90 | {} 91 | ], 92 | "space-before-blocks": [ 93 | "error", 94 | "always" 95 | ], 96 | "linebreak-style": [ 97 | "error", 98 | "unix" 99 | ], 100 | "valid-jsdoc": [ 101 | "warn", 102 | { 103 | "requireReturn": false, 104 | "requireReturnDescription": false 105 | } 106 | ] 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Creating Issues 4 | 5 | To file bug reports and feature suggestions, use the [issues page](../../issues?q=is%3Aissue). 6 | 7 | 1. Make sure the issue has not been filed before. 8 | 9 | 1. [Create a new Issue](../../issues/new) by filling out the form. 10 | 11 | 1. If an issue requires more information and receives no further input, it will be closed. 12 | 13 | 14 | ## Creating Pull Requests 15 | 16 | To create pull requests, use the [Pull Requests page](../../pulls). 17 | 18 | 1. [Create a new Issue](#creating-issues) describing the Bug or Feature you are addressing, to let others know you are working on it. 19 | 20 | 1. If a related issue exists, add a comment to let others know that you'll submit a pull request. 21 | 22 | 1. [Create a new Pull Request](../../pulls/new) by filling out the form. 23 | 24 | 25 | ### Setup 26 | 27 | 1. Fork the repository. 28 | 1. Clone your fork. 29 | 1. Make a branch for your change. 30 | 1. Run `npm install`. 31 | 32 | ## Commit Message 33 | 34 | Use the AngularJS commit message format: 35 | 36 | ``` 37 | type(scope): subject 38 | ``` 39 | 40 | #### type 41 | - `feat` New feature 42 | - `fix` A bugfix 43 | - `refactor` Code changes which are neither bugfix nor feature 44 | - `docs`: Documentation changes 45 | - `test`: New tests or changes to existing tests 46 | - `chore`: Changes to tooling or library changes 47 | 48 | #### scope 49 | The context of the changes, e.g. `preferences-window` or `compiler`. Use consistent names. 50 | 51 | #### subject 52 | A **brief, yet descriptive** description of the changes, using the following format: 53 | 54 | - present tense 55 | - lowercase 56 | - no period at the end 57 | - describe what the commit does 58 | - reference to issues via their id – e.g. `(#1337)` 59 | 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 🤷🏽‍♂️ Current Behaviour 4 | 8 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. 9 | 10 | ## 🎯 Expected Behaviour 11 | 15 | At vero eos et accusam et justo duo dolores et ea rebum. 16 | 17 | ## 👟 Steps to Reproduce (S2R) 18 | 21 | 1. At vero eos et accusam, 22 | 2. justo duo dolores et ea rebum, 23 | 3. stet clita kasd gubergren, 24 | 3. no sea takimata sanctus est. 25 | 26 | ## 🏡 Environmental Context 27 | 30 | **App Version** 31 | v0.0.1 32 | **Installation Type** 33 | Setup 34 | **Operating System** 35 | Windows 10 Enterprise x64 (15042.00) 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 📋 Description 4 | 7 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. 8 | 9 | ## 🗂 Type 10 | 13 | - [ ] 🍾 Feature 14 | - [ ] 🚨 Bugfix 15 | - [ ] 📒 Documentation 16 | - [ ] 👷 Internals 17 | 18 | ## 🔥 Severity 19 | 22 | - [ ] 💎 Non-Breaking Changes 23 | - [ ] 💔 Breaking Changes 24 | 25 | ## 🖥 Platforms 26 | 29 | - [x] 🍏 macOS 30 | - [x] 💾 Windows 31 | - [x] 🐧 Linux 32 | 33 | ## 🛃 Tests 34 | 37 | - [ ] My changes have been tested manually. 38 | - [ ] My changes are covered by automated testing methods. 39 | 40 | ## 👨‍🎓 Miscellaneous 41 | 44 | - [ ] My changes follow the style guide. 45 | - [ ] My changes require updates to the documentation. 46 | 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## BASELINE 2 | # macOS 3 | .DS_Store 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Temporary 10 | temp 11 | tmp 12 | 13 | # Cache 14 | .cache 15 | cache 16 | .sass-cache 17 | 18 | # JetBrains 19 | .idea 20 | *.sln.iml 21 | 22 | # VSCode 23 | .vscode 24 | 25 | # nodeJS 26 | node_modules 27 | jspm_packages 28 | bower_components 29 | pids 30 | *.pid 31 | *.seed 32 | lib-cov 33 | coverage 34 | .grunt 35 | 36 | # Compiled 37 | build 38 | 39 | # Generated documentation 40 | RELEASENOTES.md 41 | -------------------------------------------------------------------------------- /.rpmlist: -------------------------------------------------------------------------------- 1 | el/7 2 | fedora/26 3 | opensuse/42.3 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - os: linux 4 | sudo: required 5 | dist: trusty 6 | group: edge 7 | compiler: clang 8 | 9 | language: c 10 | 11 | branches: 12 | only: 13 | - master 14 | 15 | cache: 16 | directories: 17 | - "./node_modules" 18 | - "${HOME}/.yarn-cache" 19 | 20 | addons: 21 | apt: 22 | packages: 23 | - bsdtar 24 | - g++-multilib 25 | - gcc-multilib 26 | - graphicsmagick 27 | - icnsutils 28 | - rpm 29 | - xz-utils 30 | 31 | before_install: 32 | - echo "🚦 Authorizing Build" 33 | - echo "🔧 Setting up Node" 34 | - curl -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | NVM_DIR="${HOME}"/.nvm sh 35 | - source "${HOME}"/.nvm/nvm.sh && nvm install 9.1.0 && nvm use 9.1.0 36 | - npm --global update npm 37 | - npm --global install yarn 38 | 39 | install: 40 | - echo "📥 Installing Dependencies" 41 | - yarn install 42 | 43 | script: 44 | - echo "📦 Building" 45 | - npm run build --metadata 46 | 47 | after_success: 48 | - echo "📮 Deploying to Github" 49 | - npm run deploy 50 | 51 | notifications: 52 | webhooks: 53 | on_failure: always 54 | on_start: never 55 | on_success: change 56 | urls: 57 | - https://webhooks.gitter.im/e/a1a9de50af1c703bfe6c 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Creating Issues 4 | 5 | To file bug reports and feature suggestions, use the ["Issues"](https://github.com/sidneys/pb-for-desktop/issues?q=is%3Aissue) page. 6 | 7 | 1. Make sure the issue has not been filed before. 8 | 1. Create a new issue by filling out [the issue form](https://github.com/sidneys/pb-for-desktop/issues/new). 9 | 1. If an issue requires more information and receives no further input, it will be closed. 10 | 11 | 12 | ## Creating Pull Requests 13 | 14 | To create pull requests, use the ["Pull Requests"](https://github.com/sidneys/pb-for-desktop/pulls) page. 15 | 16 | 1. [Create a new Issue](#creating-issues) describing the Bug or Feature you are addressing, to let others know you are working on it. 17 | 1. If a related issue exists, add a comment to let others know that you'll submit a pull request. 18 | 1. Create a new pull request by filling out [the pull request form](https://github.com/sidneys/pb-for-desktop/pulls/compare). 19 | 20 | 21 | ### Setup 22 | 23 | 1. Fork the repository. 24 | 1. Clone your fork. 25 | 1. Make a branch for your change. 26 | 1. Run `npm install`. 27 | 28 | ## Commit Message 29 | 30 | Use the AngularJS commit message format: 31 | 32 | ``` 33 | type(scope): subject 34 | ``` 35 | 36 | #### type 37 | - `feat` New feature 38 | - `fix` A bugfix 39 | - `refactor` Code changes which are neither bugfix nor feature 40 | - `docs`: Documentation changes 41 | - `test`: New tests or changes to existing tests 42 | - `chore`: Changes to tooling or library changes 43 | 44 | #### scope 45 | The context of the changes, e.g. `preferences-window` or `compiler`. Use consistent names. 46 | 47 | #### subject 48 | A **brief, yet descriptive** description of the changes, using the following format: 49 | 50 | - present tense 51 | - lowercase 52 | - no period at the end 53 | - describe what the commit does 54 | - reference to issues via their id – e.g. `(#1337)` 55 | 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Desktop Dimmer [![Beta](https://img.shields.io/badge/status-alpha-blue.svg?style=flat)]() [![travis](https://img.shields.io/travis/sidneys/desktop-dimmer.svg?style=flat)](https://travis-ci.org/sidneys/desktop-dimmer) [![appveyor](https://ci.appveyor.com/api/projects/status/oc57pq7hfslqg3ru?svg=true)](https://ci.appveyor.com/project/sidneys/desktop-dimmer) [![npm](https://img.shields.io/npm/v/desktop-dimmer.svg?style=flat)](https://npmjs.com/package/desktop-dimmer) [![dependencies](https://img.shields.io/david/sidneys/desktop-dimmer.svg?style=flat-square)](https://npmjs.com/package/desktop-dimmer) [![devDependencies](https://img.shields.io/david/dev/sidneys/desktop-dimmer.svg?style=flat-square)](https://npmjs.com/package/desktop-dimmer) 2 | 3 |

4 |

5 | Enable darker-than-dark dimming for internal and external screens.
6 | Available for macOS, Windows and Linux (Beta). 7 |

8 | 9 | 10 | ------ 11 | 12 | ![macOS](https://raw.githubusercontent.com/sidneys/desktop-dimmer/master/resources/screenshots/screenshot-macos.png) 13 | ![Windows 10](https://raw.githubusercontent.com/sidneys/desktop-dimmer/master/resources/screenshots/screenshot-win32.png) 14 | 15 | ------ 16 | 17 | > **Cross-Platform** 18 | 19 | Tested on macOS Sierra, Windows 10 Anniversary. Beta support for Ubuntu 16.10. 20 | 21 | > **Lean** 22 | 23 | Small resource footprint, minimal User Interface. 24 | 25 | > **Unobstrusive** 26 | 27 | Settings are persisted and restored per-Display without any configuration. 28 | 29 | > **Smart** 30 | 31 | Heading out? Disconnecting and reconnecting external displays are handled seamlessly. 32 | 33 | > **Open Source** 34 | 35 | GitHub-based workflow, MIT licensed. 36 | 37 | 38 | ## Contents 39 | 40 | 1. [Installation](#installation) 41 | 2. [Developers](#development) 42 | 3. [Continuous Integration](#continuous-integration) 43 | 4. [Up Next](#up-next) 44 | 5. [Contact](#contact) 45 | 6. [Author](#author) 46 | 47 | 48 | ## Installation 49 | 50 | ### Standard Installation 51 | 52 | Download the latest version of Desktop Dimmer on the [Releases](https://github.com/sidneys/desktop-dimmer/releases) page. 53 | 54 | ### Installation as Commandline Tool 55 | 56 | ```bash 57 | npm install --global desktop-dimmer # Installs the node CLI module 58 | desktop-dimmer # Runs it 59 | ``` 60 | 61 | 62 | ## Developers 63 | 64 | ### Sources 65 | 66 | Clone the repo and install dependencies. 67 | 68 | ```shell 69 | git clone https://github.com/sidneys/desktop-dimmer.git desktop-dimmer 70 | cd desktop-dimmer 71 | npm install 72 | ``` 73 | 74 | ### Scripts 75 | 76 | #### npm run **start** 77 | 78 | Run the app with integrated Electron. 79 | 80 | ```bash 81 | npm run start 82 | npm run start:dev # with Debugging Tools 83 | npm run start:livereload # with Debugging Tools and Livereload 84 | ``` 85 | 86 | #### npm run **localsetup** 87 | 88 | Install the app in the System app folder and start it. 89 | 90 | ```bash 91 | npm run localsetup 92 | npm run localsetup:rebuild # Build before installation 93 | npm run localsetup:rebuild:dev # Build before installation, use Developer Tools 94 | ``` 95 | 96 | #### npm run **build** 97 | 98 | Build the app and create installers (see [requirements](#build-requirements)). 99 | 100 | ```bash 101 | npm run build # build all available platforms 102 | npm run build macos windows # build specific platforms (macos/linux/windows) 103 | ``` 104 | 105 | ### Build Requirements 106 | 107 | * Building for Windows requires [`wine`](https://winehq.org) and [`mono`](https://nsis.sourceforge.net/Docs/Chapter3.htm) (on macOS, Linux) 108 | * Building for Linux requires [`fakeroot`](https://wiki.debian.org/FakeRoot) and [`dpkg `](https://wiki.ubuntuusers.de/dpkg/) (on macOS, Windows) 109 | * Only macOS can build for other platforms. 110 | 111 | #### macOS Build Setup 112 | 113 | Install [Homebrew](https://brew.sh), then run: 114 | 115 | ```bash 116 | brew install wine mono fakeroot dpkg 117 | ``` 118 | 119 | #### Linux Build Setup 120 | 121 | ```bash 122 | sudo apt-get install wine mono fakeroot dpkg 123 | ``` 124 | 125 | 126 | ## Continuous Integration 127 | 128 | > Turnkey **build-in-the-cloud** for Windows 10, macOS and Linux. 129 | 130 | The process is managed by a custom layer of node scripts and Electron-optimized configuration templates. 131 | Completed Installation packages are deployed to [GitHub Releases](https://github.com/sidneys/desktop-dimmer/releases). Builds for all platforms and architectures take about 5 minutes. 132 | Backed by the open-source-friendly guys at [Travis](https://travis-ci.org/) and [AppVeyor](https://ci.appveyor.com/) and running [electron-packager](https://github.com/electron-userland/electron-packager) under the hood. 133 | 134 | ### Setup 135 | 136 | 1. [Fork](https://github.com/sidneys/desktop-dimmer/fork) the repo 137 | 2. Generate your GitHub [Personal Access Token](https://github.com/settings/tokens) using "repo" as scope. Copy it to the clipboard. 138 | 3. **macOS + Linux** 139 | 1. Sign in to [Travis](https://travis-ci.org/) using GitHub. 140 | 2. Open your [Travis Profile](https://travis-ci.org/profile), click "Sync Account" and wait for the process to complete. 141 | 3. Find this repository in the list, enable it and click "⚙" to open its settings. 142 | 4. Create a new Environment Variable named **GITHUB_TOKEN**. Paste your Token from step 2 as *value*. 143 | 4. **Windows** 144 | 1. Sign in to [AppVeyor](https://ci.appveyor.com/) using GitHub. 145 | 2. Click on ["New Project"](https://ci.appveyor.com/projects/new), select "GitHub", look up this repo in the list and click "Add". 146 | 3. After import navigate to the *Settings* > *Environment* subsection 147 | 4. Select "Add Variable", insert **GITHUB_TOKEN** for *name*, paste your Token as *value*. Save. 148 | 149 | ### Triggering Builds 150 | 151 | 1. Add a new Tag to start the build process: 152 | 153 | ```shell 154 | git tag -a v1.0.1 155 | git push --tags 156 | ``` 157 | The builds are started in parallel and added to the "Releases" page of the GitHub repo (in draft mode). 158 | 159 | 2. Use the editing feature to publish the new app version. 160 | 161 | 3. There is no step 3 162 | 163 | 164 | ## Up Next ![img](https://img.shields.io/badge/proposals-welcome-green.svg?style=flat) 165 | 166 | - [ ] Colored Shades 167 | - [ ] In-App Updates (Squirrel) 168 | - [ ] Signed binaries 169 | - [ ] E2E Testing ([Spectron](https://github.com/electron/spectron)) 170 | 171 | 172 | ## Contact ![Contributions Wanted](https://img.shields.io/badge/contributions-wanted-red.svg?style=flat) 173 | 174 | * [Gitter](https://gitter.im/sidneys/desktop-dimmer) Developer Chat 175 | * [Issues](https://github.com/sidneys/desktop-dimmer/issues) File, track and discuss features and issues 176 | * [Wiki](https://github.com/sidneys/desktop-dimmer/wiki) Read or contribute to the project Wiki 177 | 178 | 179 | ## Author 180 | 181 | [sidneys](https://sidneys.github.io) 2016 182 | -------------------------------------------------------------------------------- /RELEASENOTES.json: -------------------------------------------------------------------------------- 1 | { 2 | "4.0.4": { 3 | "🍾 features": [ 4 | "Adds preference: application auto updates (#26)" 5 | ], 6 | "👷 internals": [ 7 | "Upgrades `node_modules`", 8 | "Upgrades `services`" 9 | ] 10 | }, 11 | "4.0.3": { 12 | "🍾 features": [ 13 | "Per-display enabling and disabling of Desktop Dimmer", 14 | "Improved user interface performance", 15 | "Extended range for dimming factor", 16 | "Updated application icon" 17 | ], 18 | "💎 improved": [ 19 | "Augmentations to configuration storage strategy", 20 | "Various stability and performance improvements" 21 | ], 22 | "🚨 fixed": [ 23 | "Upgraded to work with Apple macOS High Sierra (macOS)", 24 | "Upgraded to work with latest Gnome-based desktop environments (Linux) (#22)", 25 | "Upgraded to work with latest Ubuntu-based distributions (Linux) (#22)" 26 | ], 27 | "📒 documentation": [ 28 | "Expanded `README.md`", 29 | "Extended JSDoc coverage" 30 | ], 31 | "👷 internals": [ 32 | "Rollback `Electron` to `v1.4.16` upstream issue (https://github.com/electron/electron/issues/2170)", 33 | "Extensive refactoring", 34 | "Upgrades `node_modules`", 35 | "Upgrades `services`", 36 | "Upgrades `lib`" 37 | ] 38 | } 39 | } -------------------------------------------------------------------------------- /app/fonts/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /app/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /app/fonts/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /app/fonts/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /app/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /app/fonts/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/Roboto-Light.woff -------------------------------------------------------------------------------- /app/fonts/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/Roboto-Light.woff2 -------------------------------------------------------------------------------- /app/fonts/photon-entypo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/photon-entypo.eot -------------------------------------------------------------------------------- /app/fonts/photon-entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/photon-entypo.ttf -------------------------------------------------------------------------------- /app/fonts/photon-entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/fonts/photon-entypo.woff -------------------------------------------------------------------------------- /app/html/controller.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 |
16 | settings 17 | close 18 |
19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/html/overlay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/html/preferences.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 | 19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/images/darwin-tray-icon-default-Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/darwin-tray-icon-default-Template.png -------------------------------------------------------------------------------- /app/images/darwin-tray-icon-default-Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/darwin-tray-icon-default-Template@2x.png -------------------------------------------------------------------------------- /app/images/darwin-tray-icon-translucent-Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/darwin-tray-icon-translucent-Template.png -------------------------------------------------------------------------------- /app/images/darwin-tray-icon-translucent-Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/darwin-tray-icon-translucent-Template@2x.png -------------------------------------------------------------------------------- /app/images/linux-tray-icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/linux-tray-icon-default.png -------------------------------------------------------------------------------- /app/images/linux-tray-icon-translucent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/linux-tray-icon-translucent.png -------------------------------------------------------------------------------- /app/images/win32-tray-icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/win32-tray-icon-default.png -------------------------------------------------------------------------------- /app/images/win32-tray-icon-translucent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/app/images/win32-tray-icon-translucent.png -------------------------------------------------------------------------------- /app/scripts/main/components/application.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const events = require('events'); 10 | const path = require('path'); 11 | 12 | /** 13 | * Modules 14 | * External 15 | * @constant 16 | */ 17 | const appRootPath = require('app-root-path'); 18 | const platformTools = require('@sidneys/platform-tools'); 19 | 20 | /** 21 | * Modules 22 | * Configuration 23 | */ 24 | appRootPath['setPath'](path.join(__dirname, '..', '..', '..', '..')); 25 | events.EventEmitter.defaultMaxListeners = Infinity; 26 | if (platformTools.isLinux) { 27 | process.env.XDG_CURRENT_DESKTOP = 'Unity'; 28 | } 29 | 30 | /** 31 | * Modules 32 | * Internal 33 | * @constant 34 | */ 35 | /* eslint-disable no-unused-vars */ 36 | const globals = require(path.join(appRootPath['path'], 'app', 'scripts', 'main', 'components', 'globals')); 37 | const menubar = require(path.join(appRootPath['path'], 'app', 'scripts', 'main', 'components', 'menubar')); 38 | const updaterService = require(path.join(appRootPath['path'], 'app', 'scripts', 'main', 'services', 'updater-service')); 39 | const debugService = require(path.join(appRootPath['path'], 'app', 'scripts', 'main', 'services', 'debug-service')); 40 | const configurationManager = require(path.join(appRootPath['path'], 'app', 'scripts', 'main', 'managers', 'configuration-manager')); 41 | const overlayManager = require(path.join(appRootPath.path, 'app', 'scripts', 'main', 'managers', 'overlay-manager')); 42 | const trayMenu = require(path.join(appRootPath['path'], 'app', 'scripts', 'main', 'menus', 'tray-menu')); 43 | const preferencesWindow = require(path.join(appRootPath.path, 'app', 'scripts', 'main', 'windows', 'preferences-window')); 44 | /* eslint-enable */ 45 | -------------------------------------------------------------------------------- /app/scripts/main/components/globals.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * External 14 | * @constant 15 | */ 16 | const appRootPath = require('app-root-path'); 17 | 18 | /** 19 | * Modules 20 | * Internal 21 | * @constant 22 | */ 23 | const packageJson = require(path.join(appRootPath['path'], 'package.json')); 24 | 25 | 26 | /** 27 | * Manifest 28 | * @global 29 | * @constant 30 | */ 31 | global.manifest = { 32 | homepage: packageJson.homepage, 33 | name: packageJson.name, 34 | productName: packageJson.productName || packageJson.name, 35 | version: packageJson.version 36 | }; 37 | 38 | /** 39 | * State 40 | * @global 41 | */ 42 | global.state = { 43 | isQuitting: false 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /app/scripts/main/components/menubar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | const url = require('url'); 11 | 12 | /** 13 | * Modules 14 | * Electron 15 | * @constant 16 | */ 17 | const electron = require('electron'); 18 | const { systemPreferences } = electron; 19 | 20 | /** 21 | * Modules 22 | * External 23 | * @constant 24 | */ 25 | const appRootPath = require('app-root-path')['path']; 26 | const electronMenubar = require('menubar'); 27 | 28 | /** 29 | * Modules 30 | * Internal 31 | * @constant 32 | */ 33 | const isDebug = require('@sidneys/is-env')('debug'); 34 | const logger = require('@sidneys/logger')({ write: false }); 35 | const platformTools = require('@sidneys/platform-tools'); 36 | 37 | 38 | /** 39 | * Filesystem 40 | * @constant 41 | * @default 42 | */ 43 | const windowHtml = path.join(appRootPath, 'app', 'html', 'controller.html'); 44 | 45 | /** 46 | * Application 47 | * @constant 48 | * @default 49 | */ 50 | const windowUrl = url.format({ protocol: 'file:', pathname: windowHtml }); 51 | 52 | 53 | /** 54 | * Tray icons 55 | * @constant 56 | */ 57 | const trayIconDefault = path.join(appRootPath, 'app', 'images', `${platformTools.type}-tray-icon-default${platformTools.templateImageExtension(platformTools.type)}`); 58 | 59 | 60 | /** 61 | * Adapt BrowserWindow to OS theme 62 | * @param {BrowserWindow} browserWindow - Target window 63 | */ 64 | let handleThemeChange = (browserWindow) => { 65 | logger.debug('handleThemeChange'); 66 | 67 | const isDarkTheme = systemPreferences.isDarkMode(); 68 | 69 | isDarkTheme ? browserWindow.setVibrancy('dark') : browserWindow.setVibrancy('light'); 70 | }; 71 | 72 | /** 73 | * @class Menubar 74 | * @property {electronMenubar} menubar - electronMenubar 75 | * @property {Electron.App} app - Menubar App 76 | */ 77 | class Menubar { 78 | /** 79 | * @constructor 80 | */ 81 | constructor() { 82 | logger.debug('constructor'); 83 | 84 | this.menubar = electronMenubar({ 85 | alwaysOnTop: isDebug, 86 | backgroundColor: platformTools.isMacOS ? void 0 : '#404040', 87 | frame: false, 88 | hasShadow: false, 89 | height: 48, 90 | maxWidth: 256, 91 | minHeight: 48, 92 | minWidth: 256, 93 | resizable: false, 94 | transparent: Boolean(platformTools.isMacOS), 95 | vibrancy: platformTools.isMacOS ? 'dark' : void 0, 96 | width: 256, 97 | icon: trayIconDefault, 98 | index: windowUrl, 99 | preloadWindow: true, 100 | showDockIcon: isDebug 101 | }); 102 | 103 | this.app = this.menubar.app; 104 | 105 | this.init(); 106 | } 107 | 108 | /** 109 | * Init 110 | */ 111 | init() { 112 | logger.debug('init'); 113 | 114 | /** 115 | * Linux 116 | */ 117 | if (platformTools.isLinux) { 118 | this.app.commandLine.appendSwitch('enable-transparent-visuals'); 119 | this.app.commandLine.appendSwitch('disable-gpu'); 120 | } 121 | 122 | /** 123 | * @listens Menubar.menubar#create-window 124 | */ 125 | this.menubar.on('create-window', () => { 126 | logger.debug('Menubar.menubar#create-window'); 127 | }); 128 | 129 | /** 130 | * @listens Electron.App#before-quit 131 | */ 132 | this.app.on('before-quit', () => { 133 | logger.debug('app#before-quit'); 134 | 135 | global.state.isQuitting = true; 136 | }); 137 | 138 | /** 139 | * @listens Electron.App#Event:ready 140 | */ 141 | this.app.once('ready', () => { 142 | logger.debug('app#ready'); 143 | }); 144 | 145 | /** 146 | * @listens Menubar.menubar#after-create-window 147 | */ 148 | this.menubar.on('after-create-window', () => { 149 | logger.debug('Menubar#after-create-window'); 150 | 151 | if (isDebug) { 152 | this.menubar.window.openDevTools({ mode: 'undocked' }); 153 | } 154 | 155 | /** 156 | * @listens Menubar.window#show 157 | */ 158 | this.menubar.window.on('show', () => { 159 | logger.debug('Menubar.window#show'); 160 | 161 | /** 162 | * @fires Menubar.window.webContents#controller-show 163 | */ 164 | this.menubar.window.webContents.send('controller-show'); 165 | 166 | /** 167 | * Linux 168 | */ 169 | if (platformTools.isLinux) { 170 | const cursorPosition = electron.screen.getCursorScreenPoint(); 171 | const targetPosition = { 172 | x: cursorPosition.x - (this.menubar.window.getBounds().width + 20), 173 | y: cursorPosition.y 174 | }; 175 | 176 | this.menubar.window.setPosition(targetPosition.x, targetPosition.y); 177 | } 178 | }); 179 | 180 | /** 181 | * @listens Menubar.window#hide 182 | */ 183 | this.menubar.window.on('hide', () => { 184 | logger.debug('Menubar.window#hide'); 185 | 186 | /** 187 | * @fires Menubar.window.webContents#controller-hide 188 | */ 189 | this.menubar.window.webContents.send('controller-hide'); 190 | }); 191 | 192 | /** 193 | * @listens Menubar.window.webContents#dom-ready 194 | */ 195 | this.menubar.window.webContents.on('dom-ready', () => { 196 | logger.debug('Menubar.window.webContents#dom-ready'); 197 | 198 | if (isDebug) { 199 | this.menubar.window.openDevTools({ mode: 'undocked' }); 200 | } 201 | }); 202 | }); 203 | 204 | /** 205 | * @listens systemPreferences:AppleInterfaceThemeChangedNotification 206 | */ 207 | if (platformTools.isMacOS) { 208 | 209 | systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => { 210 | logger.debug('systemPreferences#AppleInterfaceThemeChangedNotification'); 211 | 212 | handleThemeChange(this.menubar.window); 213 | }); 214 | } 215 | } 216 | } 217 | 218 | /** 219 | * Init 220 | */ 221 | let init = () => { 222 | logger.debug('init'); 223 | 224 | // Ensure single instance 225 | if (!global.menubar) { 226 | global.menubar = new Menubar(); 227 | } 228 | }; 229 | 230 | 231 | init(); 232 | 233 | 234 | /** 235 | * @exports 236 | */ 237 | module.exports = global.menubar; 238 | -------------------------------------------------------------------------------- /app/scripts/main/managers/configuration-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * External 14 | * @constant 15 | */ 16 | const _ = require('lodash'); 17 | const Appdirectory = require('appdirectory'); 18 | const AutoLaunch = require('auto-launch'); 19 | const electronSettings = require('electron-settings'); 20 | 21 | /** 22 | * Modules 23 | * Internal 24 | * @constant 25 | */ 26 | const logger = require('@sidneys/logger')({ write: true }); 27 | 28 | 29 | /** 30 | * Application 31 | * @constant 32 | * @default 33 | */ 34 | const appName = global.manifest.name; 35 | const appVersion = global.manifest.version; 36 | 37 | /** 38 | * Filesystem 39 | * @constant 40 | * @default 41 | */ 42 | const appSettingsFilepath = path.join(path.dirname(electronSettings.file()), `${appName}.json`); 43 | const appLogDirectory = (new Appdirectory(appName)).userLogs(); 44 | 45 | /** 46 | * Modules 47 | * Configuration 48 | */ 49 | const app = global.menubar.menubar.app; 50 | const autoLauncher = new AutoLaunch({ name: appName, mac: { useLaunchAgent: true } }); 51 | electronSettings.setPath(appSettingsFilepath); 52 | /** @namespace Electron */ 53 | /** @namespace electronSettings.delete */ 54 | /** @namespace electronSettings.deleteAll */ 55 | /** @namespace electronSettings.file */ 56 | /** @namespace electronSettings.get */ 57 | /** @namespace electronSettings.getAll */ 58 | /** @namespace electronSettings.set */ 59 | /** @namespace electronSettings.setAll */ 60 | /** @namespace electronSettings.setPath */ 61 | 62 | /** 63 | * Configuration Items 64 | */ 65 | let configurationItems = { 66 | /** 67 | * appAutoUpdate 68 | */ 69 | appAutoUpdate: { 70 | keypath: 'appAutoUpdate', 71 | default: true, 72 | init() { 73 | logger.debug(this.keypath, 'init'); 74 | }, 75 | get() { 76 | logger.debug(this.keypath, 'get'); 77 | 78 | return electronSettings.get(this.keypath); 79 | }, 80 | set(value) { 81 | logger.debug(this.keypath, 'set'); 82 | 83 | electronSettings.set(this.keypath, value); 84 | } 85 | }, 86 | /** 87 | * appChangelog 88 | */ 89 | appChangelog: { 90 | keypath: 'appChangelog', 91 | default: '', 92 | init() { 93 | logger.debug(this.keypath, 'init'); 94 | }, 95 | get() { 96 | logger.debug(this.keypath, 'get'); 97 | 98 | return electronSettings.get(this.keypath); 99 | }, 100 | set(value) { 101 | logger.debug(this.keypath, 'set'); 102 | 103 | electronSettings.set(this.keypath, value); 104 | } 105 | }, 106 | /** 107 | * appLastVersion 108 | */ 109 | appLastVersion: { 110 | keypath: 'appLastVersion', 111 | default: appVersion, 112 | init() { 113 | logger.debug(this.keypath, 'init'); 114 | }, 115 | get() { 116 | logger.debug(this.keypath, 'get'); 117 | 118 | return electronSettings.get(this.keypath); 119 | }, 120 | set(value) { 121 | logger.debug(this.keypath, 'set'); 122 | 123 | electronSettings.set(this.keypath, value); 124 | } 125 | }, 126 | /** 127 | * appLaunchOnStartup 128 | */ 129 | appLaunchOnStartup: { 130 | keypath: 'appLaunchOnStartup', 131 | default: true, 132 | init() { 133 | logger.debug(this.keypath, 'init'); 134 | 135 | this.implement(this.get()); 136 | }, 137 | get() { 138 | logger.debug(this.keypath, 'get'); 139 | 140 | return electronSettings.get(this.keypath); 141 | }, 142 | set(value) { 143 | logger.debug(this.keypath, 'set', value); 144 | 145 | this.implement(value); 146 | electronSettings.set(this.keypath, value); 147 | }, 148 | implement(value) { 149 | logger.debug(this.keypath, 'implement', value); 150 | 151 | if (value) { 152 | autoLauncher.enable(); 153 | } else { 154 | autoLauncher.disable(); 155 | } 156 | } 157 | }, 158 | /** 159 | * appLogFile 160 | */ 161 | appLogFile: { 162 | keypath: 'appLogFile', 163 | default: path.join(appLogDirectory, appName + '.log'), 164 | init() { 165 | logger.debug(this.keypath, 'init'); 166 | }, 167 | get() { 168 | logger.debug(this.keypath, 'get'); 169 | 170 | return electronSettings.get(this.keypath); 171 | }, 172 | set(value) { 173 | logger.debug(this.keypath, 'set'); 174 | 175 | electronSettings.set(this.keypath, value); 176 | } 177 | }, 178 | /** 179 | * overlayConfigurationDatabase 180 | */ 181 | overlayConfigurationDatabase: { 182 | keypath: 'overlayConfigurationDatabase', 183 | default: [], 184 | init() { 185 | logger.debug(this.keypath, 'init'); 186 | }, 187 | get() { 188 | logger.debug(this.keypath, 'get'); 189 | 190 | return electronSettings.get(this.keypath); 191 | }, 192 | set(value) { 193 | logger.debug(this.keypath, 'set', value); 194 | 195 | electronSettings.set(this.keypath, value); 196 | } 197 | } 198 | }; 199 | 200 | /** 201 | * Access single item 202 | * @param {String} playlistItemId - Configuration item identifier 203 | * @returns {Object|void} 204 | */ 205 | let getItem = (playlistItemId) => { 206 | //logger.debug('getConfigurationItem', playlistItemId); 207 | 208 | if (configurationItems.hasOwnProperty(playlistItemId)) { 209 | return configurationItems[playlistItemId]; 210 | } 211 | }; 212 | 213 | /** 214 | * Get defaults of all items 215 | * @returns {Object} 216 | */ 217 | let getConfigurationDefaults = () => { 218 | logger.debug('getConfigurationDefaults'); 219 | 220 | let defaults = {}; 221 | for (let item of Object.keys(configurationItems)) { 222 | defaults[item] = getItem(item).default; 223 | } 224 | 225 | return defaults; 226 | }; 227 | 228 | /** 229 | * Delete all settings 230 | * @param {function(*)} callback - Callback 231 | */ 232 | let deleteAll = (callback = () => {}) => { 233 | logger.debug('deleteAll'); 234 | 235 | electronSettings.deleteAll(); 236 | 237 | callback(); 238 | }; 239 | 240 | /** 241 | * Set defaults 242 | * @param {function(*)} callback - Callback 243 | */ 244 | let applyAllDefaults = (callback = () => {}) => { 245 | logger.debug('applyAllDefaults'); 246 | 247 | electronSettings.setAll(_.defaultsDeep(electronSettings.getAll(), getConfigurationDefaults())); 248 | 249 | callback(); 250 | }; 251 | 252 | /** 253 | * Reset all to defaults 254 | * @param {function(*)} callback - Callback 255 | */ 256 | let resetAllToDefaults = (callback = () => {}) => { 257 | logger.debug('resetAllToDefaults'); 258 | 259 | deleteAll(() => applyAllDefaults(() => callback())); 260 | }; 261 | 262 | /** 263 | * Run init() method of all configuration items 264 | * @param {function(*)} callback - Callback 265 | * @function 266 | */ 267 | let runItemInitializers = (callback = () => {}) => { 268 | logger.debug('runItemInitializers'); 269 | 270 | let configurationItemList = Object.keys(configurationItems); 271 | 272 | configurationItemList.forEach((item, itemIndex) => { 273 | getItem(item).init(); 274 | 275 | // Last item 276 | if (configurationItemList.length === (itemIndex + 1)) { 277 | logger.debug('initConfigurationItems', 'complete'); 278 | callback(); 279 | } 280 | }); 281 | }; 282 | 283 | /** 284 | * Remove unknown items 285 | * @param {function(*)} callback - Callback 286 | * @function 287 | */ 288 | let removeLegacyItems = (callback = () => {}) => { 289 | logger.debug('removeLegacyItems'); 290 | 291 | let savedSettings = electronSettings.getAll(); 292 | let savedSettingsList = Object.keys(savedSettings); 293 | 294 | savedSettingsList.forEach((item, itemIndex) => { 295 | if (!configurationItems.hasOwnProperty(item)) { 296 | electronSettings.delete(item); 297 | logger.debug('cleanConfiguration', 'deleted', item); 298 | } 299 | 300 | // Last item 301 | if (savedSettingsList.length === (itemIndex + 1)) { 302 | logger.debug('cleanConfiguration', 'complete'); 303 | callback(); 304 | } 305 | }); 306 | }; 307 | 308 | 309 | /** 310 | * Init 311 | */ 312 | let init = () => { 313 | logger.debug('init'); 314 | 315 | applyAllDefaults(() => runItemInitializers(() => removeLegacyItems(() => logger.debug('init complete')))); 316 | }; 317 | 318 | 319 | /** 320 | * @listens Electron.App#Event:ready 321 | */ 322 | app.once('ready', () => { 323 | logger.debug('app#ready'); 324 | 325 | init(); 326 | }); 327 | 328 | 329 | /** 330 | * @listens Electron.App#before-quit 331 | */ 332 | app.on('quit', () => { 333 | logger.debug('app#quit'); 334 | 335 | // Prettify 336 | // electronSettings.setAll(electronSettings.getAll(), { prettify: true }); 337 | 338 | logger.debug('appSettingsFilepath', electronSettings.file()); 339 | logger.debug('appSettings', electronSettings.getAll()); 340 | }); 341 | 342 | /** 343 | * @exports 344 | */ 345 | module.exports = getItem; 346 | module.exports.resetAllToDefaults = resetAllToDefaults; 347 | -------------------------------------------------------------------------------- /app/scripts/main/managers/overlay-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Modules 5 | * Node 6 | * @constant 7 | */ 8 | const events = require('events'); 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { BrowserWindow } = electron || electron.remote; 18 | 19 | /** 20 | * Modules 21 | * Configuration 22 | */ 23 | const app = global.menubar.app; 24 | 25 | /** 26 | * Modules 27 | * External 28 | * @constant 29 | */ 30 | const _ = require('lodash'); 31 | const appRootPath = require('app-root-path').path; 32 | const logger = require('@sidneys/logger')({ write: true }); 33 | 34 | 35 | /** 36 | * Modules 37 | * Internal 38 | * @constant 39 | */ 40 | const configurationManager = require(path.join(appRootPath, 'app', 'scripts', 'main', 'managers', 'configuration-manager')); 41 | const OverlayWindow = require(path.join(appRootPath, 'app', 'scripts', 'main', 'windows', 'overlay-window')); 42 | 43 | 44 | /** 45 | * Retrieve OverlayConfiguration 46 | * @param {OverlayId} id - Overlay id 47 | * @return {OverlayConfiguration} - Overlay configuration 48 | */ 49 | let retrieveOverlayConfiguration = (id) => { 50 | const map = new Map(configurationManager('overlayConfigurationDatabase').get()); 51 | 52 | return map.get(id); 53 | }; 54 | 55 | /** 56 | * Store OverlayConfiguration 57 | * @param {OverlayId} id - Overlay id 58 | * @param {OverlayConfiguration} configuration - Overlay configuration 59 | */ 60 | let storeOverlayConfiguration = (id, configuration) => { 61 | const map = new Map(configurationManager('overlayConfigurationDatabase').get()); 62 | map.set(id, configuration); 63 | 64 | configurationManager('overlayConfigurationDatabase').set([...map]); 65 | }; 66 | 67 | 68 | /** 69 | * @class OverlayManager 70 | */ 71 | class OverlayManager extends events.EventEmitter { 72 | /** 73 | * @constructor 74 | */ 75 | constructor() { 76 | logger.debug('constructor'); 77 | 78 | super(); 79 | this.init(); 80 | } 81 | 82 | /** 83 | * Init 84 | */ 85 | init() { 86 | logger.debug('init'); 87 | 88 | /** 89 | * @listens Electron.App#before-quit 90 | */ 91 | app.on('before-quit', () => { 92 | logger.debug('app#before-quit'); 93 | 94 | this.storeConfigurations(); 95 | }); 96 | 97 | /** 98 | * @listens Electron.Screen 99 | */ 100 | const eventList = ['display-metrics-changed', 'display-added', 'display-removed']; 101 | eventList.forEach((eventName) => { 102 | electron.screen.on(eventName, () => { 103 | logger.debug(`Electron.Screen#${eventName}`); 104 | 105 | this.resetAll(); 106 | }); 107 | }); 108 | 109 | this.createAll(); 110 | this.retrieveConfigurations(); 111 | } 112 | 113 | /** 114 | * Get all overlayWindows 115 | * @return {Array} 116 | */ 117 | getAll() { 118 | logger.debug('getAll'); 119 | 120 | return BrowserWindow.getAllWindows().filter((browserWindow) => { 121 | if (!browserWindow.constructor.name) { return; } 122 | 123 | return browserWindow.constructor.name === 'OverlayWindow'; 124 | }); 125 | } 126 | 127 | /** 128 | * Check whether any overlays are visible 129 | * @return {Boolean} 130 | */ 131 | isVisible() { 132 | logger.debug('isVisible'); 133 | 134 | const overlayWindowList = this.getAll(); 135 | const visibleOverlayWindowList = _.filter(overlayWindowList, overlayWindow => overlayWindow.overlayConfiguration.visibility === true); 136 | 137 | return visibleOverlayWindowList.length > 0; 138 | } 139 | 140 | /** 141 | * Retrieve all overlay configurations 142 | */ 143 | retrieveConfigurations() { 144 | logger.debug('retrieveConfigurations'); 145 | 146 | const overlayWindowList = this.getAll(); 147 | 148 | overlayWindowList.forEach((overlayWindow) => { 149 | const overlayConfiguration = retrieveOverlayConfiguration(overlayWindow.overlayId); 150 | if (!overlayConfiguration) { 151 | return; 152 | } 153 | 154 | overlayWindow.setConfiguration(overlayConfiguration); 155 | }); 156 | }; 157 | 158 | /** 159 | * Store all overlay configurations 160 | */ 161 | storeConfigurations() { 162 | logger.debug('storeConfigurations'); 163 | 164 | const overlayWindowList = this.getAll(); 165 | 166 | overlayWindowList.forEach((overlayWindow) => { 167 | storeOverlayConfiguration(overlayWindow.overlayId, overlayWindow.overlayConfiguration); 168 | }); 169 | }; 170 | 171 | /** 172 | * Remove all overlay windows 173 | * @param {Boolean} restart - relaunch app after removal 174 | */ 175 | removeAll(restart) { 176 | logger.debug('removeAll'); 177 | 178 | this.storeConfigurations(); 179 | 180 | const overlayWindowList = this.getAll(); 181 | 182 | overlayWindowList.forEach((overlayWindow, overlayWindowIndex, overlayWindowList) => { 183 | // Guard 184 | if (overlayWindow.isDestroyed()) { 185 | return; 186 | } 187 | 188 | /** 189 | * @listens OverlayWindow#closed 190 | */ 191 | overlayWindow.on('closed', () => { 192 | logger.debug('overlayWindow#closed'); 193 | 194 | // Remove reference 195 | overlayWindow = null; 196 | delete overlayWindowList[overlayWindowIndex]; 197 | 198 | // Last window 199 | if (overlayWindowIndex === overlayWindowList.length - 1) { 200 | // Restart 201 | if (restart) { 202 | app.relaunch(); 203 | app.exit(); 204 | } 205 | } 206 | }); 207 | 208 | overlayWindow.close(); 209 | }); 210 | }; 211 | 212 | /** 213 | * Create all overlays 214 | */ 215 | createAll() { 216 | logger.debug('createAll'); 217 | 218 | electron.screen.getAllDisplays().forEach((electronDisplay) => { 219 | new OverlayWindow(electronDisplay); 220 | }); 221 | }; 222 | 223 | /** 224 | * Reset all overlays 225 | */ 226 | resetAll() { 227 | logger.debug('resetAll'); 228 | 229 | this.removeAll(true); 230 | }; 231 | } 232 | 233 | /** 234 | * Init 235 | */ 236 | let init = () => { 237 | logger.debug('init'); 238 | 239 | // Ensure single instance 240 | if (!global.overlayManager) { 241 | global.overlayManager = new OverlayManager(); 242 | } 243 | }; 244 | 245 | 246 | /** 247 | * @listens Electron.App#on 248 | */ 249 | app.on('ready', () => { 250 | logger.debug('app#ready'); 251 | 252 | init(); 253 | }); 254 | 255 | 256 | /** 257 | * @exports 258 | */ 259 | module.exports = global.overlayManager; 260 | -------------------------------------------------------------------------------- /app/scripts/main/menus/tray-menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { Menu } = electron || electron.remote; 18 | 19 | /** 20 | * Modules 21 | * Configuration 22 | */ 23 | const app = global.menubar.menubar.app; 24 | 25 | /** 26 | * Modules 27 | * External 28 | * @constant 29 | */ 30 | const appRootPath = require('app-root-path')['path']; 31 | 32 | /** 33 | * Modules 34 | * Internal 35 | * @constant 36 | */ 37 | const logger = require('@sidneys/logger')({ write: true }); 38 | const platformTools = require('@sidneys/platform-tools'); 39 | 40 | 41 | /** 42 | * Application 43 | * @constant 44 | */ 45 | const appCurrentVersion = global.manifest.version; 46 | const appProductName = global.manifest.productName; 47 | 48 | 49 | /** 50 | * Get controller window 51 | * @return {Electron.BrowserWindow} 52 | */ 53 | let getControllerWindow = () => global.menubar.menubar.window; 54 | 55 | /** 56 | * Get tray 57 | * @return {Electron.Tray} 58 | */ 59 | let getTray = () => global.menubar.menubar.tray; 60 | 61 | 62 | /** 63 | * Tray, Dock Menu Template 64 | * @return {Electron.MenuItemConstructorOptions[]} 65 | */ 66 | let createTrayMenuTemplate = () => { 67 | return [ 68 | { 69 | id: 'appProductName', 70 | label: `Show ${appProductName}`, 71 | click() { 72 | getControllerWindow().show(); 73 | } 74 | }, 75 | { 76 | id: 'appCurrentVersion', 77 | label: `v${appCurrentVersion}`, 78 | type: 'normal', 79 | enabled: false 80 | }, 81 | { 82 | type: 'separator' 83 | }, 84 | { 85 | label: `Quit ${appProductName}`, 86 | click() { 87 | app.quit(); 88 | } 89 | } 90 | ]; 91 | }; 92 | 93 | /** 94 | * @class TrayMenu 95 | * @property {Electron.MenuItemConstructorOptions[]} template - Template 96 | * @property {Electron.Tray} tray - Tray 97 | * @property {Electron.Menu} menu - Menu 98 | */ 99 | class TrayMenu { 100 | /** 101 | * @param {Electron.MenuItemConstructorOptions[]} template - Menu template 102 | * @constructor 103 | */ 104 | constructor(template) { 105 | logger.debug('constructor'); 106 | 107 | this.template = template; 108 | this.tray = getTray(); 109 | this.menu = Menu.buildFromTemplate(this.template); 110 | 111 | this.init(); 112 | } 113 | 114 | /** 115 | * Init 116 | */ 117 | init() { 118 | logger.debug('init'); 119 | 120 | this.tray.setContextMenu(this.menu); 121 | 122 | /** 123 | * @listens Electron.Tray#click 124 | */ 125 | this.tray.on('click', () => { 126 | logger.debug('TrayMenu#click'); 127 | 128 | const controllerWindow = getControllerWindow(); 129 | 130 | if (!controllerWindow) { return; } 131 | 132 | controllerWindow.isVisible() ? controllerWindow.hide() : controllerWindow.show(); 133 | }); 134 | } 135 | } 136 | 137 | 138 | /** 139 | * Init 140 | */ 141 | let init = () => { 142 | logger.debug('init'); 143 | 144 | // Ensure single instance 145 | if (!global.trayMenu) { 146 | global.trayMenu = new TrayMenu(createTrayMenuTemplate()); 147 | } 148 | }; 149 | 150 | 151 | /** 152 | * @listens Electron.App#Event:ready 153 | */ 154 | app.once('ready', () => { 155 | logger.debug('app#ready'); 156 | 157 | if (!platformTools.isLinux) { return; } 158 | 159 | init(); 160 | }); 161 | 162 | 163 | /** 164 | * @exports 165 | */ 166 | module.exports = global.trayMenu; 167 | -------------------------------------------------------------------------------- /app/scripts/main/providers/dialog-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const fs = require('fs-extra'); 10 | const os = require('os'); 11 | const path = require('path'); 12 | 13 | /** 14 | * Modules 15 | * Electron 16 | * @constant 17 | */ 18 | const { app } = require('menubar'); 19 | const electron = require('electron'); 20 | const { BrowserWindow, dialog } = electron || electron.remote; 21 | 22 | /** 23 | * Modules 24 | * External 25 | * @constant 26 | */ 27 | const logger = require('@sidneys/logger')({ write: true }); 28 | const platformTools = require('@sidneys/platform-tools'); 29 | 30 | 31 | /** 32 | * Application 33 | * @constant 34 | * @default 35 | */ 36 | const appProductName = global.manifest.productName; 37 | 38 | 39 | /** 40 | * Wrapper for dialog.showMessageBox 41 | * @param {String} title - Title 42 | * @param {String} message - Message 43 | * @param {Array} buttonList - Buttons 44 | * @param {String} type - Type 45 | * @param {function(*)} callback - Callback 46 | * 47 | * @private 48 | */ 49 | let showMessage = (title = appProductName, message = title, buttonList = ['OK'], type = 'info', callback = () => {}) => { 50 | logger.debug('showMessage'); 51 | 52 | logger.debug('showMessage', 'title', title, 'message', message, 'buttonList', buttonList, 'type', type); 53 | 54 | dialog.showMessageBox(BrowserWindow.getFocusedWindow(), { 55 | type: type, 56 | title: title, 57 | message: title, 58 | detail: message, 59 | buttons: buttonList, 60 | defaultId: 0 61 | }, (response) => { 62 | logger.debug('showMessage', `title: '${title}' message: '${message}' response: '${response} (${buttonList[response]})'`); 63 | callback(response); 64 | }); 65 | }; 66 | 67 | 68 | /** @namespace detectedType.ext */ 69 | /** @namespace detectedType.mime */ 70 | /** @namespace fs.copy */ 71 | /** @namespace fs.exists */ 72 | /** @namespace fs.stat */ 73 | 74 | /** 75 | * Validate file exists 76 | * @param {String|String[]} filePath - Path 77 | * @param {function(*)} callback - Callback 78 | * 79 | * @private 80 | */ 81 | let validateFile = (filePath, callback = () => {}) => { 82 | logger.debug('validateFile'); 83 | 84 | filePath = path.normalize(filePath.toString()); 85 | 86 | fs.stat(filePath, (error) => { 87 | if (error) { 88 | callback(error); 89 | return; 90 | } 91 | 92 | callback(null, filePath); 93 | }); 94 | }; 95 | 96 | 97 | /** 98 | * File Dialog 99 | * @param {String=} dialogTitle - Title 100 | * @param {Array=} extensionList - Accepted file extensions 101 | * @param {String=} initialFolder - Initial lookup folder 102 | * @param {function(*)} callback - Callback 103 | * 104 | * @private 105 | */ 106 | let file = (dialogTitle = appProductName, extensionList = ['*'], initialFolder = app.getPath(name), callback = () => {}) => { 107 | logger.debug('file'); 108 | 109 | dialog.showOpenDialog({ 110 | title: dialogTitle, 111 | properties: ['openFile', 'showHiddenFiles', 'treatPackageAsDirectory'], 112 | defaultPath: initialFolder, 113 | filters: [{ name: 'Filter', extensions: extensionList }] 114 | }, (filePath) => { 115 | 116 | if (!filePath) { 117 | return callback(new Error(`filepath required`)); 118 | } 119 | 120 | validateFile(filePath, (error, filePathVerified) => { 121 | if (error) { 122 | showMessage(`File not found.${os.EOL}`, ['Dismiss'], 'warning', (error) => { 123 | callback(error); 124 | }); 125 | return; 126 | } 127 | 128 | callback(null, filePathVerified); 129 | }); 130 | }); 131 | }; 132 | 133 | 134 | /** 135 | * Error Dialog 136 | * @param {String=} message - Message 137 | * @param {function(*)} callback - Callback 138 | * 139 | * @public 140 | */ 141 | let error = (message, callback = () => {}) => { 142 | logger.debug('error'); 143 | 144 | if (platformTools.isMacOS) { 145 | app.dock.bounce('critical'); 146 | } 147 | 148 | app.focus(); 149 | 150 | showMessage('Error', message, ['Dismiss', 'Quit', 'Reload'], 'error', (response, checkboxChecked) => { 151 | if (response === 1) { 152 | app.quit(); 153 | return; 154 | } else if (response === 2) { 155 | BrowserWindow.getFocusedWindow().reload(); 156 | return; 157 | } 158 | 159 | callback(response, checkboxChecked); 160 | }); 161 | }; 162 | 163 | /** 164 | * Info Dialog 165 | * @param {String=} title - Title 166 | * @param {String=} message - Message 167 | * @param {function(*)} callback - Callback 168 | * 169 | * @public 170 | */ 171 | let info = (title, message, callback = () => {}) => { 172 | logger.debug('info'); 173 | 174 | showMessage(title, message, ['Dismiss'], 'info', callback); 175 | }; 176 | 177 | /** 178 | * Question Show 179 | * @param {String=} title - Title 180 | * @param {String=} message - Message 181 | * @param {function(*)} callback - Callback 182 | * 183 | * @public 184 | */ 185 | let question = (title, message, callback = () => {}) => { 186 | logger.debug('question'); 187 | 188 | app.focus(); 189 | 190 | showMessage(title, message, ['Yes', 'No'], 'question', callback); 191 | }; 192 | 193 | 194 | /** 195 | * @exports 196 | */ 197 | module.exports = { 198 | error: error, 199 | file: file, 200 | info: info, 201 | question: question 202 | }; 203 | -------------------------------------------------------------------------------- /app/scripts/main/services/debug-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Electron 7 | * @constant 8 | */ 9 | const electron = require('electron'); 10 | const { webContents } = electron || electron.remote; 11 | 12 | /** 13 | * Modules 14 | * Configuration 15 | */ 16 | const app = global.menubar.menubar.app; 17 | 18 | /** 19 | * Modules 20 | * External 21 | * @constant 22 | */ 23 | const isDebug = require('@sidneys/is-env')('debug'); 24 | const isLivereload = require('@sidneys/is-env')('livereload'); 25 | const logger = require('@sidneys/logger')({ write: true }); 26 | /* eslint-disable no-unused-vars */ 27 | const filesize = require('filesize'); 28 | const tryRequire = require('try-require'); 29 | /* eslint-enable */ 30 | 31 | 32 | /** 33 | * @constant 34 | * @default 35 | */ 36 | const defaultTimeout = 5000; 37 | 38 | /** 39 | * Init 40 | */ 41 | let init = () => { 42 | logger.debug('init'); 43 | 44 | let timeout = setTimeout(() => { 45 | webContents.getAllWebContents().forEach((contents) => { 46 | /** 47 | * Open Developer Tools 48 | */ 49 | if (isDebug) { 50 | logger.info('opening developer tools:', `"${contents.getURL()}"`); 51 | 52 | contents.openDevTools(); 53 | } 54 | 55 | /** 56 | * Start Live Reload 57 | */ 58 | if (isLivereload) { 59 | logger.info('starting live reload:', `"${contents.getURL()}"`); 60 | 61 | tryRequire('electron-connect')['client'].create(); 62 | } 63 | }); 64 | clearTimeout(timeout); 65 | }, defaultTimeout); 66 | }; 67 | 68 | 69 | /** 70 | * @listens Electron.App#Event:ready 71 | */ 72 | app.once('ready', () => { 73 | logger.debug('app#ready'); 74 | 75 | init(); 76 | }); 77 | -------------------------------------------------------------------------------- /app/scripts/main/services/power-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Electron 7 | * @constant 8 | */ 9 | const { app } = require('menubar'); 10 | const electron = require('electron'); 11 | const { powerMonitor } = electron || electron.remote; 12 | 13 | /** 14 | * Modules 15 | * External 16 | * @constant 17 | */ 18 | const logger = require('@sidneys/logger')({ write: true }); 19 | 20 | 21 | /** 22 | * @constant 23 | * @default 24 | */ 25 | const defaultTimeout = 5000; 26 | 27 | 28 | /** 29 | * Init 30 | */ 31 | let init = () => { 32 | logger.debug('init'); 33 | 34 | /** 35 | * @listens Electron.powerMonitor#suspend 36 | */ 37 | powerMonitor.on('suspend', () => { 38 | logger.debug('electron.powerMonitor#suspend'); 39 | }); 40 | 41 | /** 42 | * @listens Electron.powerMonitor#resume 43 | */ 44 | powerMonitor.on('resume', () => { 45 | logger.debug('electron.powerMonitor#resume'); 46 | 47 | let timeout = setTimeout(() => { 48 | logger.debug('electron.powerMonitor#resume', 'relaunching app'); 49 | 50 | clearTimeout(timeout); 51 | 52 | app.relaunch(); 53 | app.exit(); 54 | }, defaultTimeout); 55 | }); 56 | }; 57 | 58 | 59 | /** 60 | * @listens Electron.App#ready 61 | */ 62 | app.once('ready', () => { 63 | logger.debug('app#ready'); 64 | 65 | init(); 66 | }); 67 | -------------------------------------------------------------------------------- /app/scripts/main/services/updater-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const os = require('os'); 10 | const path = require('path'); 11 | 12 | /** 13 | * Modules 14 | * Electron 15 | * @constant 16 | */ 17 | const electron = require('electron'); 18 | const { app, BrowserWindow } = electron; 19 | 20 | /** 21 | * Modules 22 | * External 23 | * @constant 24 | */ 25 | const appRootPath = require('app-root-path')['path']; 26 | const removeMarkdown = require('remove-markdown'); 27 | const semverCompare = require('semver-compare'); 28 | const logger = require('@sidneys/logger')({ write: true }); 29 | const platformTools = require('@sidneys/platform-tools'); 30 | const { autoUpdater } = require('electron-updater'); 31 | 32 | /** 33 | * Modules 34 | * Internal 35 | * @constant 36 | */ 37 | const dialogProvider = require(path.join(appRootPath, 'app', 'scripts', 'main', 'providers', 'dialog-provider')); 38 | //const notificationProvider = require(path.join(appRootPath, 'app', 'scripts', 'main', 'providers', 'notification-provider')); 39 | const configurationManager = require(path.join(appRootPath, 'app', 'scripts', 'main', 'managers', 'configuration-manager')); 40 | 41 | /** 42 | * Application 43 | * @constant 44 | * @default 45 | */ 46 | const appProductName = global.manifest.productName; 47 | const appCurrentVersion = global.manifest.version; 48 | 49 | 50 | /** 51 | * Get mainWindow 52 | * @return {Electron.BrowserWindow} 53 | */ 54 | let getMainWindow = () => global.mainWindow; 55 | 56 | 57 | /** 58 | * Retrieve appAutoUpdate 59 | * @return {Boolean} - Yes / no 60 | */ 61 | let retrieveAppAutoUpdate = () => configurationManager('appAutoUpdate').get(); 62 | 63 | /** 64 | * Retrieve appChangelog 65 | * @return {String} - changelog 66 | */ 67 | let retrieveAppChangelog = () => configurationManager('appChangelog').get(); 68 | 69 | /** 70 | * Store appChangelog 71 | * @param {String} changelog - Changelog 72 | * @return {void} 73 | */ 74 | let storeAppChangelog = (changelog) => configurationManager('appChangelog').set(changelog); 75 | 76 | /** 77 | * Retrieve appLastVersion 78 | * @return {String} - Version 79 | */ 80 | let retrieveAppLastVersion = () => configurationManager('appLastVersion').get(); 81 | 82 | /** 83 | * Store appLastVersion 84 | * @param {String} version - Version 85 | * @return {void} 86 | */ 87 | let storeAppLastVersion = (version) => configurationManager('appLastVersion').set(version); 88 | 89 | 90 | /** 91 | * @class UpdaterService 92 | * @property {AppUpdater} autoUpdater - Auto updater instance 93 | * @property {Boolean} isUpdating - App is currently updating 94 | */ 95 | class UpdaterService { 96 | /** 97 | * @constructor 98 | */ 99 | constructor() { 100 | logger.debug('constructor'); 101 | 102 | // Do not run with debug Electron application 103 | if (process.defaultApp) { return; } 104 | 105 | // Do not run on Linux 106 | if (platformTools.isLinux) { return; } 107 | 108 | this.autoUpdater = autoUpdater; 109 | this.isUpdating = false; 110 | 111 | this.init(); 112 | } 113 | 114 | /** 115 | * Init 116 | */ 117 | init() { 118 | logger.debug('init'); 119 | 120 | this.bumpAppLastVersion(); 121 | this.registerAutoUpdater(); 122 | } 123 | 124 | /** 125 | * Register Electron.AutoUpdater Singleton 126 | */ 127 | registerAutoUpdater() { 128 | logger.debug('registerAutoUpdater'); 129 | 130 | /** 131 | * Add Logger 132 | */ 133 | this.autoUpdater.logger = logger; 134 | 135 | /** 136 | * @listens Electron.AutoUpdater#error 137 | */ 138 | this.autoUpdater.on('error', (error) => { 139 | logger.error('autoUpdater#error', error.message); 140 | 141 | this.isUpdating = false; 142 | }); 143 | 144 | /** 145 | * @listens Electron.AutoUpdater#checking-for-update 146 | */ 147 | this.autoUpdater.on('checking-for-update', () => { 148 | logger.debug('UpdaterService#checking-for-update'); 149 | 150 | this.isUpdating = true; 151 | }); 152 | 153 | /** 154 | * @listens Electron.AutoUpdater#update-available 155 | */ 156 | this.autoUpdater.on('update-available', (info) => { 157 | logger.debug('UpdaterService#update-available', info); 158 | 159 | this.isUpdating = true; 160 | 161 | //const notification = notificationProvider.create({ title: `Update available for ${appProductName}`, subtitle: info.version }); 162 | //notification.show(); 163 | }); 164 | 165 | /** 166 | * @listens Electron.AutoUpdater#update-not-available 167 | */ 168 | this.autoUpdater.on('update-not-available', (info) => { 169 | logger.debug('UpdaterService#update-not-available', info); 170 | 171 | this.isUpdating = false; 172 | }); 173 | 174 | /** 175 | * @listens Electron.AutoUpdater#download-progress 176 | */ 177 | this.autoUpdater.on('download-progress', (progress) => { 178 | logger.debug('UpdaterService#download-progress'); 179 | 180 | logger.info('application update', 'progress', `${progress['percent'].toFixed(2)}%`); 181 | 182 | /** 183 | * Show update progress (Windows) 184 | */ 185 | if (platformTools.isWindows) { 186 | const mainWindow = getMainWindow(); 187 | if (!mainWindow) { return; } 188 | 189 | mainWindow.setProgressBar(progress['percent'] / 100); 190 | } 191 | }); 192 | 193 | /** 194 | * @listens Electron.AutoUpdater#update-downloaded 195 | */ 196 | this.autoUpdater.on('update-downloaded', (info) => { 197 | logger.info('UpdaterService#update-downloaded', info); 198 | 199 | this.isUpdating = true; 200 | 201 | //const notification = notificationProvider.create({ title: `Update ready to install for ${appProductName}`, subtitle: info.version }); 202 | //notification.show(); 203 | 204 | if (!!info.releaseNotes) { 205 | const releaseNotesPlaintext = removeMarkdown(info.releaseNotes); 206 | 207 | logger.info('UpdaterService#update-downloaded', 'releaseNotesPlaintext', releaseNotesPlaintext); 208 | 209 | storeAppChangelog(releaseNotesPlaintext); 210 | } 211 | 212 | dialogProvider.question( 213 | `Update successfully installed`, 214 | `${appProductName} has been updated successfully.${os.EOL}${os.EOL}` + 215 | `To apply the changes and complete the updating process, the app needs to be restarted.${os.EOL}${os.EOL}` + 216 | `Restart now?`, (response) => { 217 | if (response === 0) { 218 | BrowserWindow.getAllWindows().forEach((browserWindow) => browserWindow.destroy()); 219 | this.autoUpdater.quitAndInstall(); 220 | } 221 | if (response === 1) { return true; } 222 | 223 | return true; 224 | }); 225 | }); 226 | 227 | this.checkForUpdates(); 228 | } 229 | 230 | /** 231 | * Bump last app version 232 | * @static 233 | */ 234 | bumpAppLastVersion() { 235 | const appLastVersion = retrieveAppLastVersion(); 236 | 237 | // Initialize version 238 | if (!appLastVersion) { 239 | storeAppLastVersion(appCurrentVersion); 240 | 241 | return; 242 | } 243 | 244 | // Compare internal/current version 245 | let wasUpdated = Boolean(semverCompare(appCurrentVersion, appLastVersion) === 1); 246 | 247 | // Update internal version 248 | if (wasUpdated) { 249 | storeAppLastVersion(appCurrentVersion); 250 | 251 | const changelogPlaintext = retrieveAppChangelog(); 252 | 253 | logger.debug('bumpAppLastVersion', 'changelogPlaintext', changelogPlaintext); 254 | 255 | if (!!changelogPlaintext) { 256 | dialogProvider.info(`${appProductName} has been updated to ${appCurrentVersion}`, changelogPlaintext); 257 | } else { 258 | dialogProvider.info(`${appProductName} has been updated to ${appCurrentVersion}`, `Update completed successfully.`); 259 | } 260 | 261 | //const notification = notificationProvider.create({ title: `Update installed for ${appProductName}`, subtitle: appCurrentVersion }); 262 | //notification.show(); 263 | } 264 | } 265 | 266 | /** 267 | * Check if updater is ready 268 | * @return {Boolean} - Yes / no 269 | */ 270 | isAvailable() { 271 | logger.debug('isAvailable'); 272 | 273 | return Boolean(this.autoUpdater); 274 | } 275 | 276 | /** 277 | * Check if currently updating 278 | * @return {Boolean} - Yes / no 279 | */ 280 | isActive() { 281 | logger.debug('isActive'); 282 | 283 | return Boolean(this.isUpdating); 284 | } 285 | 286 | /** 287 | * Check for updates 288 | */ 289 | checkForUpdates() { 290 | logger.debug('checkForUpdates'); 291 | 292 | if (!this.isAvailable()) { return; } 293 | if (this.isActive()) { return; } 294 | 295 | if (!retrieveAppAutoUpdate()) { return; } 296 | 297 | this.autoUpdater.checkForUpdates(); 298 | } 299 | } 300 | 301 | 302 | /** 303 | * Init 304 | */ 305 | let init = () => { 306 | logger.debug('init'); 307 | 308 | // Ensure single instance 309 | if (!global.updaterService) { 310 | global.updaterService = new UpdaterService(); 311 | } 312 | }; 313 | 314 | 315 | /** 316 | * @listens Electron.App#browser-window-focus 317 | */ 318 | app.on('browser-window-focus', () => { 319 | logger.debug('app#browser-window-focus'); 320 | 321 | if (!global.updaterService) { return; } 322 | 323 | global.updaterService.checkForUpdates(); 324 | }); 325 | 326 | /** 327 | * @listens Electron.App#Event:ready 328 | */ 329 | app.once('ready', () => { 330 | logger.debug('app#ready'); 331 | 332 | init(); 333 | }); 334 | 335 | 336 | /** 337 | * @exports 338 | */ 339 | module.exports = global.updaterService; 340 | -------------------------------------------------------------------------------- /app/scripts/main/windows/overlay-configuration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * External 7 | * @constant 8 | */ 9 | const logger = require('@sidneys/logger')({ write: true }); 10 | 11 | 12 | /** 13 | * @typedef {String} OverlayAlpha - Floating-point alpha value 14 | * @example '0.23' 15 | */ 16 | 17 | /** 18 | * @typedef {String} OverlayColor - Overlay color hexadecimal 19 | * @example '#404040' 20 | */ 21 | 22 | /** 23 | * @typedef {Boolean} OverlayVisibility - Overlay visibility 24 | * @example true 25 | */ 26 | 27 | 28 | /** 29 | * @type {OverlayAlpha} 30 | * @const 31 | * @default 32 | */ 33 | const defaultAlpha = '0.0'; 34 | 35 | /** 36 | * @type {OverlayColor} 37 | * @const 38 | * @default 39 | */ 40 | const defaultColor = '#000000'; 41 | 42 | /** 43 | * @type {OverlayVisibility} 44 | * @const 45 | * @default 46 | */ 47 | const defaultVisibility = true; 48 | 49 | 50 | /** 51 | * @class OverlayConfiguration 52 | * @property {OverlayAlpha} alpha 53 | * @property {OverlayColor} color 54 | * @property {OverlayVisibility} visibility 55 | */ 56 | class OverlayConfiguration { 57 | /** 58 | * @param {OverlayAlpha=} alpha - Overlay alpha 59 | * @param {OverlayColor=} color - Overlay color 60 | * @param {OverlayVisibility=} visibility - Overlay visibility 61 | * @constructor 62 | */ 63 | constructor(alpha = defaultAlpha, color = defaultColor, visibility = defaultVisibility) { 64 | logger.debug('constructor'); 65 | 66 | this.alpha = alpha; 67 | this.color = color; 68 | this.visibility = visibility; 69 | } 70 | } 71 | 72 | 73 | /** 74 | * @exports 75 | */ 76 | module.exports = OverlayConfiguration; 77 | -------------------------------------------------------------------------------- /app/scripts/main/windows/overlay-window.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | const url = require('url'); 11 | 12 | /** 13 | * Modules 14 | * Electron 15 | * @constant 16 | */ 17 | const electron = require('electron'); 18 | const { BrowserWindow } = electron || electron.remote; 19 | 20 | /** 21 | * Modules 22 | * External 23 | * @constant 24 | */ 25 | const _ = require('lodash'); 26 | const appRootPath = require('app-root-path').path; 27 | const isDebug = require('@sidneys/is-env')('debug'); 28 | const logger = require('@sidneys/logger')({ write: true }); 29 | const platformTools = require('@sidneys/platform-tools'); 30 | 31 | 32 | /** 33 | * Modules 34 | * Internal 35 | * @constant 36 | */ 37 | const OverlayConfiguration = require(path.join(appRootPath, 'app', 'scripts', 'main', 'windows', 'overlay-configuration')); 38 | 39 | 40 | /** 41 | * Tray icons 42 | * @constant 43 | */ 44 | const trayIconDefault = path.join(appRootPath, 'app', 'images', `${platformTools.type}-tray-icon-default${platformTools.templateImageExtension(platformTools.type)}`); 45 | const trayIconTranslucent = path.join(appRootPath, 'app', 'images', `${platformTools.type}-tray-icon-translucent${platformTools.templateImageExtension(platformTools.type)}`); 46 | 47 | /** 48 | * Filesystem 49 | * @constant 50 | * @default 51 | */ 52 | const windowHtml = path.join(appRootPath, 'app', 'html', 'overlay.html'); 53 | 54 | /** 55 | * Application 56 | * @constant 57 | * @default 58 | */ 59 | const windowUrl = url.format({ protocol: 'file:', pathname: windowHtml }); 60 | 61 | 62 | /** 63 | * Get overlayManager 64 | * @return {OverlayManager} 65 | */ 66 | let getOverlayManager = () => global.overlayManager; 67 | 68 | /** 69 | * Get TrayMenu 70 | * @return {TrayMenu} 71 | */ 72 | let getTrayMenu = () => global.menubar.menubar.tray; 73 | 74 | 75 | 76 | /** 77 | * @typedef {Electron.Display} OverlayDisplay - Target display 78 | */ 79 | 80 | /** 81 | * @typedef {Electron.Display.id} OverlayId - Unique identifier 82 | */ 83 | 84 | 85 | /** 86 | * @class OverlayWindow 87 | * @property {OverlayDisplay} overlayDisplay 88 | * @property {OverlayId} overlayId 89 | * @property {OverlayConfiguration} overlayConfiguration 90 | * @extends Electron.BrowserWindow 91 | * @namespace Electron 92 | */ 93 | class OverlayWindow extends BrowserWindow { 94 | 95 | /** 96 | * @param {Electron.Display} display - Display 97 | * @constructor 98 | */ 99 | constructor(display) { 100 | logger.debug('constructor'); 101 | 102 | super({ 103 | enableLargerThanScreen: true, 104 | frame: false, 105 | focusable: false, 106 | hasShadow: false, 107 | height: 1, 108 | show: false, 109 | transparent: true, 110 | width: 1, 111 | x: 0, 112 | y: 0 113 | }); 114 | 115 | // BrowserWindow 116 | this.setIgnoreMouseEvents(true); 117 | 118 | if (isDebug) { 119 | this.setAlwaysOnTop(false); 120 | } else { 121 | this.setAlwaysOnTop(true, 'screen-saver', 2); 122 | } 123 | 124 | // Display assignment 125 | this.overlayDisplay = display; 126 | this.overlayId = display.id; 127 | 128 | // Initial configuration 129 | this.overlayConfiguration = new OverlayConfiguration(); 130 | 131 | this.init(); 132 | } 133 | 134 | /** 135 | * Init 136 | */ 137 | init() { 138 | logger.debug('init'); 139 | 140 | /** 141 | * @listens OverlayWindow.WebContents#dom-ready 142 | */ 143 | this.webContents.on('dom-ready', () => { 144 | logger.debug('OverlayWindow.WebContents#dom-ready'); 145 | 146 | // Apply configuration 147 | this.applyConfiguration(); 148 | 149 | this.show(); 150 | }); 151 | 152 | /** 153 | * @listens OverlayWindow#dom-ready 154 | */ 155 | this.on('update-overlay', () => { 156 | logger.debug('overlay:update'); 157 | 158 | const overlayManager = getOverlayManager(); 159 | const isVisible = overlayManager.isVisible(); 160 | 161 | isVisible ? getTrayMenu().setImage(trayIconDefault) : getTrayMenu().setImage(trayIconTranslucent); 162 | }); 163 | 164 | this.loadURL(windowUrl); 165 | } 166 | 167 | /** 168 | * @fires OverlayWindow#Event:update-overlay 169 | */ 170 | updateRenderer() { 171 | logger.debug('updateRenderer'); 172 | 173 | this.emit('update-overlay', this.overlayId, this.overlayConfiguration); 174 | this.webContents.send('update-overlay', this.overlayConfiguration); 175 | } 176 | 177 | /** 178 | * applyAlpha 179 | * 180 | * @private 181 | */ 182 | applyAlpha() { 183 | logger.debug('applyAlpha'); 184 | 185 | let debounced = _.debounce(() => this.updateRenderer(), 150); 186 | debounced(); 187 | } 188 | 189 | /** 190 | * applyColor 191 | * 192 | * @private 193 | */ 194 | applyColor() { 195 | logger.debug('applyColor'); 196 | 197 | let debounced = _.debounce(() => this.updateRenderer(), 150); 198 | debounced(); 199 | } 200 | 201 | /** 202 | * applyVisibility 203 | * 204 | * @private 205 | */ 206 | applyVisibility() { 207 | logger.debug('applyVisibility'); 208 | 209 | const bounds = this.overlayDisplay.bounds; 210 | 211 | if (this.overlayConfiguration.visibility) { 212 | this.setBounds({ 213 | x: bounds.x, 214 | y: bounds.y, 215 | width: isDebug ? Math.floor(bounds.width / 4) : (bounds.width + 1), 216 | height: isDebug ? Math.floor(bounds.height / 4) : (bounds.height + 1) 217 | }, false); 218 | } else { 219 | this.setBounds({ x: 0, y: 0, width: 1, height: 1 }, false); 220 | } 221 | } 222 | 223 | /** 224 | * Apply configuration 225 | * 226 | * @private 227 | */ 228 | applyConfiguration() { 229 | logger.debug('applyConfiguration'); 230 | 231 | this.applyAlpha(); 232 | this.applyColor(); 233 | this.applyVisibility(); 234 | } 235 | 236 | /** 237 | * Set configuration 238 | * @param {OverlayConfiguration|Object} configuration - Overlay configuration 239 | * 240 | * @public 241 | */ 242 | setConfiguration(configuration) { 243 | logger.debug('setConfiguration'); 244 | 245 | this.overlayConfiguration = Object.assign({}, this.overlayConfiguration, configuration); 246 | this.applyConfiguration(); 247 | } 248 | } 249 | 250 | /** 251 | * @exports 252 | */ 253 | module.exports = OverlayWindow; 254 | -------------------------------------------------------------------------------- /app/scripts/main/windows/preferences-window.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Modules 5 | * Node 6 | * @constant 7 | */ 8 | const path = require('path'); 9 | const url = require('url'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { BrowserWindow } = electron || electron.remote; 18 | 19 | /** 20 | * Modules 21 | * Configuration 22 | */ 23 | const app = global.menubar.menubar.app; 24 | 25 | 26 | /** 27 | * Modules 28 | * External 29 | * @constant 30 | */ 31 | const appRootPath = require('app-root-path').path; 32 | const logger = require('@sidneys/logger')({ write: true }); 33 | 34 | 35 | /** 36 | * Filesystem 37 | * @constant 38 | * @default 39 | */ 40 | const windowHtml = path.join(appRootPath, 'app', 'html', 'preferences.html'); 41 | 42 | /** 43 | * Application 44 | * @constant 45 | * @default 46 | */ 47 | const windowTitle = global.manifest.productName; 48 | const windowUrl = url.format({ protocol: 'file:', pathname: windowHtml }); 49 | 50 | 51 | /** 52 | * @class PreferencesWindow 53 | * @extends Electron.BrowserWindow 54 | * @namespace Electron 55 | */ 56 | class PreferencesWindow extends BrowserWindow { 57 | constructor() { 58 | super({ 59 | acceptFirstMouse: true, 60 | alwaysOnTop: false, 61 | autoHideMenuBar: true, 62 | fullscreenable: false, 63 | maximizable: false, 64 | minimizable: false, 65 | height: 128, 66 | minHeight: 128, 67 | maxHeight: 256, 68 | resizable: false, 69 | show: false, 70 | title: `${windowTitle} Preferences`, 71 | type: 'textured', 72 | width: 320, 73 | minWidth: 320, 74 | maxWidth: 640 75 | }); 76 | 77 | this.init(); 78 | } 79 | 80 | /** 81 | * Init 82 | */ 83 | init() { 84 | logger.debug('init'); 85 | 86 | /** 87 | * @listens PreferencesWindow#close 88 | */ 89 | this.on('close', (event) => { 90 | logger.debug('AppWindow#close'); 91 | 92 | if (global.state.isQuitting === false) { 93 | event.preventDefault(); 94 | this.hide(); 95 | } 96 | }); 97 | 98 | 99 | this.loadURL(windowUrl); 100 | } 101 | } 102 | 103 | 104 | /** 105 | * Init 106 | */ 107 | let init = () => { 108 | logger.debug('init'); 109 | 110 | // Ensure single instance 111 | if (!global.preferencesWindow) { 112 | global.preferencesWindow = new PreferencesWindow(); 113 | } 114 | }; 115 | 116 | 117 | /** 118 | * @listens Electron.App#on 119 | */ 120 | app.once('ready', () => { 121 | logger.debug('app#ready'); 122 | 123 | init(); 124 | }); 125 | 126 | 127 | /** 128 | * @exports 129 | */ 130 | module.exports = global.preferencesWindow; 131 | -------------------------------------------------------------------------------- /app/scripts/renderer/controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Electron 7 | * @constant 8 | */ 9 | const electron = require('electron'); 10 | const { ipcRenderer, remote } = electron; 11 | 12 | /** 13 | * Modules 14 | * External 15 | * @constant 16 | */ 17 | const domTools = require('@sidneys/dom-tools'); 18 | const logger = require('@sidneys/logger')({ write: true }); 19 | 20 | 21 | /** 22 | * Get controller window 23 | * @return {Electron.BrowserWindow} 24 | */ 25 | let getControllerWindow = () => remote.getGlobal('menubar').menubar.window; 26 | 27 | /** 28 | * Get overlayManager 29 | * @return {OverlayManager} 30 | */ 31 | let getOverlayManager = () => remote.getGlobal('overlayManager'); 32 | 33 | /** 34 | * Get preferencesWindow 35 | * @return {PreferencesWindow} 36 | */ 37 | let getPreferencesWindow = () => remote.getGlobal('preferencesWindow'); 38 | 39 | 40 | /** 41 | * DOM Elements 42 | * @global 43 | */ 44 | const contentElement = document.querySelector('.content'); 45 | const controllerListElement = document.querySelector('.display-control-list'); 46 | const exitButtonElement = document.querySelector('.window-controls .exit'); 47 | /* eslint-disable no-unused-vars */ 48 | const preferencesButtonElement = document.querySelector('.window-controls .settings'); 49 | /* eslint-enable */ 50 | 51 | 52 | /** 53 | * Get percentage string for fraction 54 | * @param {OverlayAlpha} alpha - Fraction 55 | * @return {String} percentage 56 | */ 57 | let getPercentage = (alpha) => `${Math.floor(100 * parseFloat(alpha))} %`; 58 | 59 | /** 60 | * Pass content size changes to native wrapper window 61 | */ 62 | let handleSizeChanges = () => { 63 | logger.debug('handleSizeChanges'); 64 | 65 | const controllerWindow = getControllerWindow(); 66 | 67 | let currentWidth = controllerWindow.getSize()[0]; 68 | let currentHeight = controllerWindow.getSize()[1]; 69 | let contentHeight = contentElement.getBoundingClientRect().height; 70 | 71 | if (contentHeight !== currentHeight) { 72 | controllerWindow.setSize(currentWidth, contentHeight); 73 | } 74 | }; 75 | 76 | /** 77 | * Register Controllers 78 | */ 79 | let registerControllers = () => { 80 | logger.debug('registerControllers'); 81 | 82 | const overlayManager = getOverlayManager(); 83 | const overlayWindowList = overlayManager.getAll(); 84 | 85 | while (controllerListElement.firstChild) { 86 | controllerListElement.removeChild(controllerListElement.firstChild); 87 | } 88 | 89 | overlayWindowList.forEach((overlayWindow) => { 90 | const id = overlayWindow.overlayId; 91 | 92 | let displayControlElement = document.createElement('form'); 93 | displayControlElement.classList.add('display-control-list__display-control'); 94 | displayControlElement.dataset.displayId = id; 95 | controllerListElement.appendChild(displayControlElement); 96 | 97 | /** 98 | * Visibility 99 | */ 100 | const visibility = overlayWindow.overlayConfiguration.visibility; 101 | 102 | const visibilityInputElement = document.createElement('input'); 103 | visibilityInputElement.classList.add('visibility'); 104 | visibilityInputElement.type = 'checkbox'; 105 | visibilityInputElement.checked = visibility; 106 | displayControlElement.appendChild(visibilityInputElement); 107 | 108 | /** 109 | * @listens visibilityInputElement:MouseEvent#click 110 | */ 111 | visibilityInputElement.addEventListener('click', () => { 112 | logger.debug('visibilityInputElement#click '); 113 | 114 | overlayWindow.setConfiguration({ visibility: visibilityInputElement.checked }); 115 | }); 116 | 117 | overlayWindow.setConfiguration({ visibility: visibility }); 118 | 119 | /** 120 | * Alpha 121 | */ 122 | const alpha = overlayWindow.overlayConfiguration.alpha; 123 | 124 | const alphaOutputElement = document.createElement('output'); 125 | alphaOutputElement.classList.add('alpha'); 126 | alphaOutputElement.value = getPercentage(alpha); 127 | displayControlElement.appendChild(alphaOutputElement); 128 | 129 | const alphaInputElement = document.createElement('input'); 130 | alphaInputElement.classList.add('alpha'); 131 | alphaInputElement.type = 'range'; 132 | alphaInputElement.max = '0.92'; 133 | alphaInputElement.min = '0.01'; 134 | alphaInputElement.step = '0.01'; 135 | alphaInputElement.value = alpha; 136 | displayControlElement.appendChild(alphaInputElement); 137 | 138 | /** 139 | * @listens alphaInputElement:MouseEvent#click 140 | */ 141 | alphaInputElement.addEventListener('input', () => { 142 | logger.debug('alphaInputElement#input'); 143 | 144 | alphaOutputElement.value = getPercentage(alphaInputElement.value); 145 | overlayWindow.setConfiguration({ alpha: alphaInputElement.value }); 146 | }); 147 | 148 | overlayWindow.setConfiguration({ alpha: alpha }); 149 | }); 150 | 151 | handleSizeChanges(); 152 | controllerListElement.style.opacity = '1'; 153 | }; 154 | 155 | 156 | /** 157 | * @listens ipcRenderer#controller-show 158 | */ 159 | ipcRenderer.on('controller-show', () => { 160 | logger.debug('ipcRenderer#controller-show'); 161 | 162 | registerControllers(); 163 | }); 164 | 165 | 166 | /** 167 | * @listens exitButtonElement:MouseEvent#click 168 | */ 169 | exitButtonElement.addEventListener('click', () => { 170 | logger.debug('exitButtonElement#click'); 171 | 172 | remote.app.quit(); 173 | }); 174 | 175 | /** 176 | * @listens preferencesButtonElement:MouseEvent#click 177 | */ 178 | preferencesButtonElement.addEventListener('click', () => { 179 | logger.debug('preferencesButtonElement#click'); 180 | 181 | const preferencesWindow = getPreferencesWindow(); 182 | 183 | preferencesWindow.isVisible() ? preferencesWindow.hide() : preferencesWindow.show(); 184 | }); 185 | 186 | /** 187 | * @listens document:DOMContentLoaded 188 | */ 189 | document.addEventListener('DOMContentLoaded', () => { 190 | logger.debug('document#DOMContentLoaded'); 191 | 192 | // Add platform name to 193 | domTools.addPlatformClass(); 194 | 195 | // Add slider controls 196 | registerControllers(); 197 | }); 198 | -------------------------------------------------------------------------------- /app/scripts/renderer/overlay.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Electron 7 | * @constant 8 | */ 9 | const electron = require('electron'); 10 | const { ipcRenderer } = electron; 11 | 12 | /** 13 | * Modules 14 | * External 15 | * @constant 16 | */ 17 | const tinycolor = require('tinycolor2'); 18 | 19 | /** 20 | * Modules 21 | * Internal 22 | * @constant 23 | */ 24 | const logger = require('@sidneys/logger')({ write: true }); 25 | 26 | 27 | /** 28 | * @global 29 | */ 30 | let dom = { 31 | container: document.querySelector('html') 32 | }; 33 | 34 | /** 35 | * Get color 36 | * @return {tinycolor} Color 37 | */ 38 | let getColor = () => tinycolor(dom.container.style.backgroundColor); 39 | 40 | /** 41 | * Get alpha 42 | * @return {OverlayAlpha} Alpha 43 | */ 44 | let getAlpha = () => (getColor()).getAlpha(); 45 | 46 | 47 | /** 48 | * @listens Electron:ipcRenderer#overlay-change 49 | */ 50 | ipcRenderer.on('update-overlay', (event, overlayConfiguration) => { 51 | logger.debug('ipcRenderer#overlay-change'); 52 | 53 | /** 54 | * Alpha 55 | */ 56 | if (overlayConfiguration.alpha) { 57 | const targetColor = getColor(); 58 | targetColor.setAlpha(parseFloat(overlayConfiguration.alpha)); 59 | dom.container.style.backgroundColor = targetColor.toRgbString(); 60 | } 61 | 62 | /** 63 | * Color 64 | */ 65 | if (overlayConfiguration.color) { 66 | const targetColor = tinycolor(overlayConfiguration.color); 67 | targetColor.setAlpha(getAlpha()); 68 | dom.container.style.backgroundColor = targetColor.toRgbString(); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /app/scripts/renderer/preferences.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { remote } = electron; 18 | 19 | /** 20 | * Modules 21 | * External 22 | * @constant 23 | */ 24 | const _ = require('lodash'); 25 | const appRootPath = require('app-root-path').path; 26 | const logger = require('@sidneys/logger')({ write: true }); 27 | 28 | /** 29 | * Modules 30 | * Internal 31 | * @constant 32 | */ 33 | const configurationManager = remote.require(path.join(appRootPath, 'app', 'scripts', 'main', 'managers', 'configuration-manager')); 34 | 35 | 36 | /** 37 | * App 38 | * @global 39 | * @constant 40 | */ 41 | const appVersion = remote.getGlobal('manifest').version; 42 | 43 | 44 | /** 45 | * DOM Elements 46 | * @global 47 | */ 48 | const preferencesListElement = document.querySelector('.preferencesList'); 49 | const versionTextElement = document.querySelector('.version'); 50 | 51 | /** 52 | * Set version 53 | * @param {String} version - Version 54 | * @return {void} 55 | */ 56 | let setVersion = (version) => versionTextElement.innerText = version.trim(); 57 | 58 | 59 | /** 60 | * Reads electron-settings and generates HTML form 61 | */ 62 | let renderPreferences = () => { 63 | logger.debug('registerPreferences'); 64 | 65 | let preferenceKeyList = [ 66 | 'appAutoUpdate', 67 | 'appLaunchOnStartup' 68 | ]; 69 | 70 | preferenceKeyList.forEach((keyName) => { 71 | const preferenceInputElement = document.createElement('input'); 72 | preferenceInputElement.id = keyName; 73 | preferenceInputElement.type = 'checkbox'; 74 | preferenceInputElement.checked = configurationManager(keyName).get(); 75 | 76 | const preferenceLabelElement = document.createElement('label'); 77 | preferenceLabelElement.appendChild(preferenceInputElement); 78 | preferenceLabelElement.appendChild(document.createTextNode(_.startCase(keyName))); 79 | preferencesListElement.appendChild(preferenceLabelElement); 80 | 81 | /** 82 | * @listens preferenceInputElement:MouseEvent#click 83 | */ 84 | preferenceInputElement.addEventListener('click', () => { 85 | logger.debug('preferenceInputElement#click '); 86 | 87 | configurationManager(keyName).set(preferenceInputElement.checked); 88 | }); 89 | }); 90 | }; 91 | 92 | /** 93 | * @listens window#load 94 | */ 95 | window.addEventListener('load', () => { 96 | logger.debug('window#load'); 97 | 98 | setVersion(appVersion); 99 | }); 100 | 101 | /** 102 | * @listens document:DOMContentLoaded 103 | */ 104 | document.addEventListener('DOMContentLoaded', () => { 105 | logger.debug('document#DOMContentLoaded'); 106 | 107 | renderPreferences(); 108 | }); 109 | -------------------------------------------------------------------------------- /app/styles/styles.css: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | GLOBAL 3 | ========================================================================== */ 4 | 5 | /* html 6 | ========================================================================== */ 7 | 8 | html, 9 | body 10 | { 11 | padding: 0; 12 | margin: 0; 13 | overflow: hidden; 14 | } 15 | 16 | html * 17 | { 18 | -webkit-font-smoothing: subpixel-antialiased; 19 | } 20 | 21 | textarea, 22 | input, 23 | button 24 | { 25 | outline: none; 26 | } 27 | 28 | 29 | /* draggable 30 | ========================================================================== */ 31 | 32 | .linux .window-controls 33 | { 34 | -webkit-app-region: drag; 35 | } 36 | 37 | .linux .window-controls .button 38 | { 39 | -webkit-app-region: no-drag; 40 | } 41 | 42 | /* ========================================================================== 43 | OVERLAY 44 | ========================================================================== */ 45 | 46 | /* html 47 | ========================================================================== */ 48 | 49 | html.overlay 50 | { 51 | background-color: transparent; 52 | transition: all 250ms ease-in-out; 53 | } 54 | 55 | /* ========================================================================== 56 | CONTROLLER 57 | ========================================================================== */ 58 | 59 | /* html 60 | ========================================================================== */ 61 | 62 | html.controller 63 | { 64 | font-size: 62.5%; 65 | font-family: 'Roboto', sans-serif; 66 | font-weight: 300; 67 | } 68 | 69 | /* .window-controls 70 | ========================================================================== */ 71 | .window-controls 72 | { 73 | display: block; 74 | text-align: right; 75 | position: relative; 76 | background-color: rgba(0, 0, 0, 0.15); 77 | transition: all 100ms 50ms ease-out; 78 | } 79 | 80 | .window-controls:hover 81 | { 82 | transform: translate(-2.4rem, -0.1rem) scale(1.2); 83 | } 84 | 85 | .window-controls .button 86 | { 87 | color: rgba(255, 255, 255, 0.9); 88 | position: relative; 89 | padding: 0 0.2rem; 90 | margin: 0; 91 | height: 2rem; 92 | font-style: normal; 93 | line-height: 2rem; 94 | font-size: 1.5rem; 95 | text-decoration: none; 96 | 97 | transition: all 250ms ease-in-out; 98 | } 99 | 100 | .window-controls .button:hover 101 | { 102 | color: rgba(255, 255, 255, 1.0); 103 | transform: scale(1.5); 104 | } 105 | 106 | .window-controls .hide 107 | { 108 | display: none; 109 | opacity: 0; 110 | visibility: hidden; 111 | } 112 | 113 | /* .display-control-list 114 | ========================================================================== */ 115 | 116 | .display-control-list 117 | { 118 | padding: 0.5rem 1rem 0 1rem; 119 | opacity: 0; 120 | transition: all 200ms ease-out; 121 | } 122 | 123 | /* .display-control 124 | ========================================================================== */ 125 | 126 | .display-control-list__display-control 127 | { 128 | margin: 0; 129 | height: 3rem; 130 | } 131 | 132 | .display-control-list__display-control:not(:last-child) 133 | { 134 | border-bottom: 1px solid rgba(255, 255, 255, 0.3); 135 | } 136 | 137 | /* .display-control__alpha_slider 138 | ========================================================================== */ 139 | 140 | input.alpha 141 | { 142 | position: relative; 143 | display: block; 144 | float: right; 145 | width: 75%; 146 | height: 3rem; 147 | margin: 0; 148 | padding: 0; 149 | } 150 | 151 | /* .display-control__alpha-label 152 | ========================================================================== */ 153 | 154 | output.alpha 155 | { 156 | color: rgba(255, 255, 255, 1); 157 | padding: 0 0 0 4%; 158 | width: 15%; 159 | margin: 0; 160 | line-height: 3rem; 161 | text-align: left; 162 | float: left; 163 | font-size: 1.2rem; 164 | } 165 | 166 | /* input 167 | ========================================================================== */ 168 | 169 | input[type=range] 170 | { 171 | -webkit-appearance: none; 172 | background: transparent; 173 | } 174 | 175 | input[type=range]::-webkit-slider-runnable-track 176 | { 177 | width: 100%; 178 | height: 0.25rem; 179 | background-color: rgba(255, 255, 255, 0.3); 180 | border: none; 181 | } 182 | 183 | input[type=range]::-webkit-slider-thumb 184 | { 185 | background-color: rgb(255, 255, 255); 186 | -webkit-appearance: none; 187 | border: 1px solid rgba(0, 0, 0, 0.3); 188 | height: 1.2rem; 189 | width: 1.2rem; 190 | border-radius: 50%; 191 | padding: 0.2rem; 192 | margin-top: -0.44rem; 193 | } 194 | 195 | input[type=range]:focus 196 | { 197 | outline: none; 198 | } 199 | 200 | /* .display-control__visibility 201 | ========================================================================== */ 202 | 203 | input.visibility 204 | { 205 | -webkit-appearance: none; 206 | background: transparent; 207 | border: rgba(255, 255, 255, 0.3) 0.1rem solid; 208 | padding: 0; 209 | width: 5%; 210 | margin: 0.9rem 0 0 0.2rem; 211 | height: 1.2rem; 212 | text-align: left; 213 | float: left; 214 | font-size: 1rem; 215 | color: rgba(255, 255, 255, 0.75); 216 | } 217 | 218 | input.visibility:checked:after 219 | { 220 | content: '\2715'; 221 | position: relative; 222 | left: 0.12rem; 223 | top: -0.1rem; 224 | } 225 | 226 | /* .preferences 227 | ========================================================================== */ 228 | 229 | .preferences .version 230 | { 231 | text-align: right; 232 | font-size: 0.7rem; 233 | line-height: 0.7rem; 234 | opacity: 0.5; 235 | } 236 | -------------------------------------------------------------------------------- /app/vendor/material-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url('../fonts/MaterialIcons-Regular.woff2') format('woff2'), 6 | url('../fonts/MaterialIcons-Regular.woff') format('woff'), 7 | url('../fonts/MaterialIcons-Regular.ttf') format('truetype'); 8 | } 9 | 10 | .material-icons, .button { 11 | font-family: 'Material Icons'; 12 | font-weight: normal; 13 | font-style: normal; 14 | font-size: 24px; /* Preferred icon size */ 15 | display: inline-block; 16 | line-height: 1; 17 | text-transform: none; 18 | letter-spacing: normal; 19 | word-wrap: normal; 20 | white-space: nowrap; 21 | direction: ltr; 22 | 23 | /* Support for all WebKit browsers. */ 24 | -webkit-font-smoothing: antialiased; 25 | /* Support for Safari and Chrome. */ 26 | text-rendering: optimizeLegibility; 27 | 28 | /* Support for Firefox. */ 29 | -moz-osx-font-smoothing: grayscale; 30 | 31 | /* Support for IE. */ 32 | font-feature-settings: 'liga'; 33 | } 34 | -------------------------------------------------------------------------------- /app/vendor/photon.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * ===================================================== 3 | * Photon v0.1.2 4 | * Copyright 2016 Connor Sears 5 | * Licensed under MIT (https://github.com/connors/proton/blob/master/LICENSE) 6 | * 7 | * v0.1.2 designed by @connors. 8 | * ===================================================== 9 | */ 10 | 11 | @charset "UTF-8"; 12 | audio, 13 | canvas, 14 | progress, 15 | video { 16 | vertical-align: baseline; 17 | } 18 | 19 | audio:not([controls]) { 20 | display: none; 21 | } 22 | 23 | a:active, 24 | a:hover { 25 | outline: 0; 26 | } 27 | 28 | abbr[title] { 29 | border-bottom: 1px dotted; 30 | } 31 | 32 | b, 33 | strong { 34 | font-weight: bold; 35 | } 36 | 37 | dfn { 38 | font-style: italic; 39 | } 40 | 41 | h1 { 42 | font-size: 2em; 43 | margin: 0.67em 0; 44 | } 45 | 46 | small { 47 | font-size: 80%; 48 | } 49 | 50 | sub, 51 | sup { 52 | font-size: 75%; 53 | line-height: 0; 54 | position: relative; 55 | vertical-align: baseline; 56 | } 57 | 58 | sup { 59 | top: -0.5em; 60 | } 61 | 62 | sub { 63 | bottom: -0.25em; 64 | } 65 | 66 | pre { 67 | overflow: auto; 68 | } 69 | 70 | code, 71 | kbd, 72 | pre, 73 | samp { 74 | font-family: monospace, monospace; 75 | font-size: 1em; 76 | } 77 | 78 | button, 79 | input, 80 | optgroup, 81 | select, 82 | textarea { 83 | color: inherit; 84 | font: inherit; 85 | margin: 0; 86 | } 87 | 88 | input[type="number"]::-webkit-inner-spin-button, 89 | input[type="number"]::-webkit-outer-spin-button { 90 | height: auto; 91 | } 92 | 93 | input[type="search"] { 94 | -webkit-appearance: textfield; 95 | box-sizing: content-box; 96 | } 97 | 98 | input[type="search"]::-webkit-search-cancel-button, 99 | input[type="search"]::-webkit-search-decoration { 100 | -webkit-appearance: none; 101 | } 102 | 103 | fieldset { 104 | border: 1px solid #c0c0c0; 105 | margin: 0 2px; 106 | padding: 0.35em 0.625em 0.75em; 107 | } 108 | 109 | legend { 110 | border: 0; 111 | padding: 0; 112 | } 113 | 114 | table { 115 | border-collapse: collapse; 116 | border-spacing: 0; 117 | } 118 | 119 | td, 120 | th { 121 | padding: 0; 122 | } 123 | 124 | * { 125 | cursor: default; 126 | -webkit-user-select: none; 127 | } 128 | 129 | input, 130 | textarea { 131 | -webkit-user-select: text; 132 | } 133 | 134 | form, 135 | input, 136 | optgroup, 137 | select, 138 | textarea { 139 | -webkit-user-select: text; 140 | -webkit-app-region: no-drag; 141 | } 142 | 143 | * { 144 | -webkit-box-sizing: border-box; 145 | box-sizing: border-box; 146 | } 147 | 148 | html { 149 | height: 100%; 150 | width: 100%; 151 | overflow: hidden; 152 | } 153 | 154 | body { 155 | height: 100%; 156 | padding: 0; 157 | margin: 0; 158 | font-family: system, -apple-system, ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "Segoe UI", sans-serif; 159 | font-size: 13px; 160 | line-height: 1.6; 161 | color: #333; 162 | background-color: transparent; 163 | } 164 | 165 | hr { 166 | margin: 15px 0; 167 | overflow: hidden; 168 | background: transparent; 169 | border: 0; 170 | border-bottom: 1px solid #ddd; 171 | } 172 | 173 | h1, h2, h3, h4, h5, h6 { 174 | margin-top: 20px; 175 | margin-bottom: 10px; 176 | font-weight: 500; 177 | white-space: nowrap; 178 | overflow: hidden; 179 | text-overflow: ellipsis; 180 | } 181 | 182 | h1 { 183 | font-size: 36px; 184 | } 185 | 186 | h2 { 187 | font-size: 30px; 188 | } 189 | 190 | h3 { 191 | font-size: 24px; 192 | } 193 | 194 | h4 { 195 | font-size: 18px; 196 | } 197 | 198 | h5 { 199 | font-size: 14px; 200 | } 201 | 202 | h6 { 203 | font-size: 12px; 204 | } 205 | 206 | .window { 207 | position: absolute; 208 | top: 0; 209 | right: 0; 210 | bottom: 0; 211 | left: 0; 212 | display: flex; 213 | flex-direction: column; 214 | background-color: #fff; 215 | } 216 | 217 | .window-content { 218 | position: relative; 219 | overflow-y: auto; 220 | display: flex; 221 | flex: 1; 222 | } 223 | 224 | .selectable-text { 225 | cursor: text; 226 | -webkit-user-select: text; 227 | } 228 | 229 | .text-center { 230 | text-align: center; 231 | } 232 | 233 | .text-right { 234 | text-align: right; 235 | } 236 | 237 | .text-left { 238 | text-align: left; 239 | } 240 | 241 | .pull-left { 242 | float: left; 243 | } 244 | 245 | .pull-right { 246 | float: right; 247 | } 248 | 249 | .padded { 250 | padding: 10px; 251 | } 252 | 253 | .padded-less { 254 | padding: 5px; 255 | } 256 | 257 | .padded-more { 258 | padding: 20px; 259 | } 260 | 261 | .padded-vertically { 262 | padding-top: 10px; 263 | padding-bottom: 10px; 264 | } 265 | 266 | .padded-vertically-less { 267 | padding-top: 5px; 268 | padding-bottom: 5px; 269 | } 270 | 271 | .padded-vertically-more { 272 | padding-top: 20px; 273 | padding-bottom: 20px; 274 | } 275 | 276 | .padded-horizontally { 277 | padding-right: 10px; 278 | padding-left: 10px; 279 | } 280 | 281 | .padded-horizontally-less { 282 | padding-right: 5px; 283 | padding-left: 5px; 284 | } 285 | 286 | .padded-horizontally-more { 287 | padding-right: 20px; 288 | padding-left: 20px; 289 | } 290 | 291 | .padded-top { 292 | padding-top: 10px; 293 | } 294 | 295 | .padded-top-less { 296 | padding-top: 5px; 297 | } 298 | 299 | .padded-top-more { 300 | padding-top: 20px; 301 | } 302 | 303 | .padded-bottom { 304 | padding-bottom: 10px; 305 | } 306 | 307 | .padded-bottom-less { 308 | padding-bottom: 5px; 309 | } 310 | 311 | .padded-bottom-more { 312 | padding-bottom: 20px; 313 | } 314 | 315 | .sidebar { 316 | background-color: #f5f5f4; 317 | } 318 | 319 | .draggable { 320 | -webkit-app-region: drag; 321 | } 322 | 323 | .not-draggable { 324 | -webkit-app-region: no-drag; 325 | } 326 | 327 | .clearfix:before, .clearfix:after { 328 | display: table; 329 | content: " "; 330 | } 331 | .clearfix:after { 332 | clear: both; 333 | } 334 | 335 | .btn { 336 | display: inline-block; 337 | padding: 3px 8px; 338 | margin-bottom: 0; 339 | font-size: 12px; 340 | line-height: 1.4; 341 | text-align: center; 342 | white-space: nowrap; 343 | vertical-align: middle; 344 | cursor: default; 345 | background-image: none; 346 | border: 1px solid transparent; 347 | border-radius: 4px; 348 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.06); 349 | -webkit-app-region: no-drag; 350 | } 351 | .btn:focus { 352 | outline: none; 353 | box-shadow: none; 354 | } 355 | 356 | .btn-mini { 357 | padding: 2px 6px; 358 | } 359 | 360 | .btn-large { 361 | padding: 6px 12px; 362 | } 363 | 364 | .btn-form { 365 | padding-right: 20px; 366 | padding-left: 20px; 367 | } 368 | 369 | .btn-default { 370 | color: #333; 371 | border-top-color: #c2c0c2; 372 | border-right-color: #c2c0c2; 373 | border-bottom-color: #a19fa1; 374 | border-left-color: #c2c0c2; 375 | background-color: #fcfcfc; 376 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fcfcfc), color-stop(100%, #f1f1f1)); 377 | background-image: -webkit-linear-gradient(top, #fcfcfc 0%, #f1f1f1 100%); 378 | background-image: linear-gradient(to bottom, #fcfcfc 0%, #f1f1f1 100%); 379 | } 380 | .btn-default:active { 381 | background-color: #ddd; 382 | background-image: none; 383 | } 384 | 385 | .btn-primary, 386 | .btn-positive, 387 | .btn-negative, 388 | .btn-warning { 389 | color: #fff; 390 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); 391 | } 392 | 393 | .btn-primary { 394 | border-color: #388df8; 395 | border-bottom-color: #0866dc; 396 | background-color: #6eb4f7; 397 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #6eb4f7), color-stop(100%, #1a82fb)); 398 | background-image: -webkit-linear-gradient(top, #6eb4f7 0%, #1a82fb 100%); 399 | background-image: linear-gradient(to bottom, #6eb4f7 0%, #1a82fb 100%); 400 | } 401 | .btn-primary:active { 402 | background-color: #3e9bf4; 403 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #3e9bf4), color-stop(100%, #0469de)); 404 | background-image: -webkit-linear-gradient(top, #3e9bf4 0%, #0469de 100%); 405 | background-image: linear-gradient(to bottom, #3e9bf4 0%, #0469de 100%); 406 | } 407 | 408 | .btn-positive { 409 | border-color: #29a03b; 410 | border-bottom-color: #248b34; 411 | background-color: #5bd46d; 412 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bd46d), color-stop(100%, #29a03b)); 413 | background-image: -webkit-linear-gradient(top, #5bd46d 0%, #29a03b 100%); 414 | background-image: linear-gradient(to bottom, #5bd46d 0%, #29a03b 100%); 415 | } 416 | .btn-positive:active { 417 | background-color: #34c84a; 418 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #34c84a), color-stop(100%, #248b34)); 419 | background-image: -webkit-linear-gradient(top, #34c84a 0%, #248b34 100%); 420 | background-image: linear-gradient(to bottom, #34c84a 0%, #248b34 100%); 421 | } 422 | 423 | .btn-negative { 424 | border-color: #fb2f29; 425 | border-bottom-color: #fb1710; 426 | background-color: #fd918d; 427 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fd918d), color-stop(100%, #fb2f29)); 428 | background-image: -webkit-linear-gradient(top, #fd918d 0%, #fb2f29 100%); 429 | background-image: linear-gradient(to bottom, #fd918d 0%, #fb2f29 100%); 430 | } 431 | .btn-negative:active { 432 | background-color: #fc605b; 433 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fc605b), color-stop(100%, #fb1710)); 434 | background-image: -webkit-linear-gradient(top, #fc605b 0%, #fb1710 100%); 435 | background-image: linear-gradient(to bottom, #fc605b 0%, #fb1710 100%); 436 | } 437 | 438 | .btn-warning { 439 | border-color: #fcaa0e; 440 | border-bottom-color: #ee9d02; 441 | background-color: #fece72; 442 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fece72), color-stop(100%, #fcaa0e)); 443 | background-image: -webkit-linear-gradient(top, #fece72 0%, #fcaa0e 100%); 444 | background-image: linear-gradient(to bottom, #fece72 0%, #fcaa0e 100%); 445 | } 446 | .btn-warning:active { 447 | background-color: #fdbc40; 448 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fdbc40), color-stop(100%, #ee9d02)); 449 | background-image: -webkit-linear-gradient(top, #fdbc40 0%, #ee9d02 100%); 450 | background-image: linear-gradient(to bottom, #fdbc40 0%, #ee9d02 100%); 451 | } 452 | 453 | .btn .icon { 454 | float: left; 455 | width: 14px; 456 | height: 14px; 457 | margin-top: 1px; 458 | margin-bottom: 1px; 459 | color: #737475; 460 | font-size: 14px; 461 | line-height: 1; 462 | } 463 | 464 | .btn .icon-text { 465 | margin-right: 5px; 466 | } 467 | 468 | .btn-dropdown:after { 469 | font-family: "photon-entypo"; 470 | margin-left: 5px; 471 | content: '\e873'; 472 | } 473 | 474 | .btn-group { 475 | position: relative; 476 | display: inline-block; 477 | vertical-align: middle; 478 | -webkit-app-region: no-drag; 479 | } 480 | .btn-group .btn { 481 | position: relative; 482 | float: left; 483 | } 484 | .btn-group .btn:focus, .btn-group .btn:active { 485 | z-index: 2; 486 | } 487 | .btn-group .btn.active { 488 | z-index: 3; 489 | } 490 | 491 | .btn-group .btn + .btn, 492 | .btn-group .btn + .btn-group, 493 | .btn-group .btn-group + .btn, 494 | .btn-group .btn-group + .btn-group { 495 | margin-left: -1px; 496 | } 497 | .btn-group > .btn:first-child { 498 | border-top-right-radius: 0; 499 | border-bottom-right-radius: 0; 500 | } 501 | .btn-group > .btn:last-child { 502 | border-top-left-radius: 0; 503 | border-bottom-left-radius: 0; 504 | } 505 | .btn-group > .btn:not(:first-child):not(:last-child) { 506 | border-radius: 0; 507 | } 508 | .btn-group .btn + .btn { 509 | border-left: 1px solid #c2c0c2; 510 | } 511 | .btn-group .btn + .btn.active { 512 | border-left: 0; 513 | } 514 | .btn-group .active { 515 | color: #fff; 516 | border: 1px solid transparent; 517 | background-color: #6d6c6d; 518 | background-image: none; 519 | } 520 | .btn-group .active .icon { 521 | color: #fff; 522 | } 523 | 524 | .toolbar { 525 | min-height: 22px; 526 | box-shadow: inset 0 1px 0 #f5f4f5; 527 | background-color: #e8e6e8; 528 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e8e6e8), color-stop(100%, #d1cfd1)); 529 | background-image: -webkit-linear-gradient(top, #e8e6e8 0%, #d1cfd1 100%); 530 | background-image: linear-gradient(to bottom, #e8e6e8 0%, #d1cfd1 100%); 531 | } 532 | .toolbar:before, .toolbar:after { 533 | display: table; 534 | content: " "; 535 | } 536 | .toolbar:after { 537 | clear: both; 538 | } 539 | 540 | .toolbar-header { 541 | border-bottom: 1px solid #c2c0c2; 542 | } 543 | .toolbar-header .title { 544 | margin-top: 1px; 545 | } 546 | 547 | .toolbar-footer { 548 | border-top: 1px solid #c2c0c2; 549 | -webkit-app-region: drag; 550 | } 551 | 552 | .title { 553 | margin: 0; 554 | font-size: 12px; 555 | font-weight: 400; 556 | text-align: center; 557 | color: #555; 558 | cursor: default; 559 | } 560 | 561 | .toolbar-borderless { 562 | border-top: 0; 563 | border-bottom: 0; 564 | } 565 | 566 | .toolbar-actions { 567 | margin-top: 4px; 568 | margin-bottom: 3px; 569 | padding-right: 3px; 570 | padding-left: 3px; 571 | padding-bottom: 3px; 572 | -webkit-app-region: drag; 573 | } 574 | .toolbar-actions:before, .toolbar-actions:after { 575 | display: table; 576 | content: " "; 577 | } 578 | .toolbar-actions:after { 579 | clear: both; 580 | } 581 | .toolbar-actions > .btn, 582 | .toolbar-actions > .btn-group { 583 | margin-left: 4px; 584 | margin-right: 4px; 585 | } 586 | 587 | label { 588 | display: inline-block; 589 | font-size: 13px; 590 | margin-bottom: 5px; 591 | white-space: nowrap; 592 | overflow: hidden; 593 | text-overflow: ellipsis; 594 | } 595 | 596 | input[type="search"] { 597 | box-sizing: border-box; 598 | } 599 | 600 | input[type="radio"], 601 | input[type="checkbox"] { 602 | margin: 4px 0 0; 603 | line-height: normal; 604 | } 605 | 606 | .form-control { 607 | display: inline-block; 608 | width: 100%; 609 | min-height: 25px; 610 | padding: 5px 10px; 611 | font-size: 13px; 612 | line-height: 1.6; 613 | background-color: #fff; 614 | border: 1px solid #ddd; 615 | border-radius: 4px; 616 | outline: none; 617 | } 618 | .form-control:focus { 619 | border-color: #6db3fd; 620 | box-shadow: 0 0 0 3px #6db3fd; 621 | } 622 | 623 | textarea { 624 | height: auto; 625 | } 626 | 627 | .form-group { 628 | margin-bottom: 10px; 629 | } 630 | 631 | .radio, 632 | .checkbox { 633 | position: relative; 634 | display: block; 635 | margin-top: 10px; 636 | margin-bottom: 10px; 637 | } 638 | .radio label, 639 | .checkbox label { 640 | padding-left: 20px; 641 | margin-bottom: 0; 642 | font-weight: normal; 643 | } 644 | 645 | .radio input[type="radio"], 646 | .radio-inline input[type="radio"], 647 | .checkbox input[type="checkbox"], 648 | .checkbox-inline input[type="checkbox"] { 649 | position: absolute; 650 | margin-left: -20px; 651 | margin-top: 4px; 652 | } 653 | 654 | .form-actions .btn { 655 | margin-right: 10px; 656 | } 657 | .form-actions .btn:last-child { 658 | margin-right: 0; 659 | } 660 | 661 | .pane-group { 662 | position: absolute; 663 | top: 0; 664 | right: 0; 665 | bottom: 0; 666 | left: 0; 667 | display: flex; 668 | } 669 | 670 | .pane { 671 | position: relative; 672 | overflow-y: auto; 673 | flex: 1; 674 | border-left: 1px solid #ddd; 675 | } 676 | .pane:first-child { 677 | border-left: 0; 678 | } 679 | 680 | .pane-sm { 681 | max-width: 220px; 682 | min-width: 150px; 683 | } 684 | 685 | .pane-mini { 686 | width: 80px; 687 | flex: none; 688 | } 689 | 690 | .pane-one-fourth { 691 | width: 25%; 692 | flex: none; 693 | } 694 | 695 | .pane-one-third { 696 | width: 33.3%; 697 | flex: none; 698 | } 699 | 700 | img { 701 | -webkit-user-drag: text; 702 | } 703 | 704 | .img-circle { 705 | border-radius: 50%; 706 | } 707 | 708 | .img-rounded { 709 | border-radius: 4px; 710 | } 711 | 712 | .list-group { 713 | width: 100%; 714 | list-style: none; 715 | margin: 0; 716 | padding: 0; 717 | } 718 | .list-group * { 719 | margin: 0; 720 | white-space: nowrap; 721 | overflow: hidden; 722 | text-overflow: ellipsis; 723 | } 724 | 725 | .list-group-item { 726 | padding: 10px; 727 | font-size: 12px; 728 | color: #414142; 729 | border-top: 1px solid #ddd; 730 | } 731 | .list-group-item:first-child { 732 | border-top: 0; 733 | } 734 | .list-group-item.active, .list-group-item.selected { 735 | color: #fff; 736 | background-color: #116cd6; 737 | } 738 | 739 | .list-group-header { 740 | padding: 10px; 741 | } 742 | 743 | .media-object { 744 | margin-top: 3px; 745 | } 746 | 747 | .media-object.pull-left { 748 | margin-right: 10px; 749 | } 750 | 751 | .media-object.pull-right { 752 | margin-left: 10px; 753 | } 754 | 755 | .media-body { 756 | overflow: hidden; 757 | } 758 | 759 | .nav-group { 760 | font-size: 14px; 761 | } 762 | 763 | .nav-group-item { 764 | padding: 2px 10px 2px 25px; 765 | display: block; 766 | color: #333; 767 | text-decoration: none; 768 | white-space: nowrap; 769 | overflow: hidden; 770 | text-overflow: ellipsis; 771 | } 772 | .nav-group-item:active, .nav-group-item.active { 773 | background-color: #dcdfe1; 774 | } 775 | .nav-group-item .icon { 776 | width: 19px; 777 | height: 18px; 778 | float: left; 779 | color: #737475; 780 | margin-top: -3px; 781 | margin-right: 7px; 782 | font-size: 18px; 783 | text-align: center; 784 | } 785 | 786 | .nav-group-title { 787 | margin: 0; 788 | padding: 10px 10px 2px; 789 | font-size: 12px; 790 | font-weight: 500; 791 | color: #666666; 792 | } 793 | 794 | @font-face { 795 | font-family: "photon-entypo"; 796 | src: url("../fonts/photon-entypo.eot"); 797 | src: url("../fonts/photon-entypo.eot?#iefix") format("eot"), url("../fonts/photon-entypo.woff") format("woff"), url("../fonts/photon-entypo.ttf") format("truetype"); 798 | font-weight: normal; 799 | font-style: normal; 800 | } 801 | .icon:before { 802 | position: relative; 803 | display: inline-block; 804 | font-family: "photon-entypo"; 805 | speak: none; 806 | font-size: 100%; 807 | font-style: normal; 808 | font-weight: normal; 809 | font-variant: normal; 810 | text-transform: none; 811 | line-height: 1; 812 | -webkit-font-smoothing: antialiased; 813 | -moz-osx-font-smoothing: grayscale; 814 | } 815 | 816 | .icon-note:before { 817 | content: '\e800'; 818 | } 819 | 820 | /* '' */ 821 | .icon-note-beamed:before { 822 | content: '\e801'; 823 | } 824 | 825 | /* '' */ 826 | .icon-music:before { 827 | content: '\e802'; 828 | } 829 | 830 | /* '' */ 831 | .icon-search:before { 832 | content: '\e803'; 833 | } 834 | 835 | /* '' */ 836 | .icon-flashlight:before { 837 | content: '\e804'; 838 | } 839 | 840 | /* '' */ 841 | .icon-mail:before { 842 | content: '\e805'; 843 | } 844 | 845 | /* '' */ 846 | .icon-heart:before { 847 | content: '\e806'; 848 | } 849 | 850 | /* '' */ 851 | .icon-heart-empty:before { 852 | content: '\e807'; 853 | } 854 | 855 | /* '' */ 856 | .icon-star:before { 857 | content: '\e808'; 858 | } 859 | 860 | /* '' */ 861 | .icon-star-empty:before { 862 | content: '\e809'; 863 | } 864 | 865 | /* '' */ 866 | .icon-user:before { 867 | content: '\e80a'; 868 | } 869 | 870 | /* '' */ 871 | .icon-users:before { 872 | content: '\e80b'; 873 | } 874 | 875 | /* '' */ 876 | .icon-user-add:before { 877 | content: '\e80c'; 878 | } 879 | 880 | /* '' */ 881 | .icon-video:before { 882 | content: '\e80d'; 883 | } 884 | 885 | /* '' */ 886 | .icon-picture:before { 887 | content: '\e80e'; 888 | } 889 | 890 | /* '' */ 891 | .icon-camera:before { 892 | content: '\e80f'; 893 | } 894 | 895 | /* '' */ 896 | .icon-layout:before { 897 | content: '\e810'; 898 | } 899 | 900 | /* '' */ 901 | .icon-menu:before { 902 | content: '\e811'; 903 | } 904 | 905 | /* '' */ 906 | .icon-check:before { 907 | content: '\e812'; 908 | } 909 | 910 | /* '' */ 911 | .icon-cancel:before { 912 | content: '\e813'; 913 | } 914 | 915 | /* '' */ 916 | .icon-cancel-circled:before { 917 | content: '\e814'; 918 | } 919 | 920 | /* '' */ 921 | .icon-cancel-squared:before { 922 | content: '\e815'; 923 | } 924 | 925 | /* '' */ 926 | .icon-plus:before { 927 | content: '\e816'; 928 | } 929 | 930 | /* '' */ 931 | .icon-plus-circled:before { 932 | content: '\e817'; 933 | } 934 | 935 | /* '' */ 936 | .icon-plus-squared:before { 937 | content: '\e818'; 938 | } 939 | 940 | /* '' */ 941 | .icon-minus:before { 942 | content: '\e819'; 943 | } 944 | 945 | /* '' */ 946 | .icon-minus-circled:before { 947 | content: '\e81a'; 948 | } 949 | 950 | /* '' */ 951 | .icon-minus-squared:before { 952 | content: '\e81b'; 953 | } 954 | 955 | /* '' */ 956 | .icon-help:before { 957 | content: '\e81c'; 958 | } 959 | 960 | /* '' */ 961 | .icon-help-circled:before { 962 | content: '\e81d'; 963 | } 964 | 965 | /* '' */ 966 | .icon-info:before { 967 | content: '\e81e'; 968 | } 969 | 970 | /* '' */ 971 | .icon-info-circled:before { 972 | content: '\e81f'; 973 | } 974 | 975 | /* '' */ 976 | .icon-back:before { 977 | content: '\e820'; 978 | } 979 | 980 | /* '' */ 981 | .icon-home:before { 982 | content: '\e821'; 983 | } 984 | 985 | /* '' */ 986 | .icon-link:before { 987 | content: '\e822'; 988 | } 989 | 990 | /* '' */ 991 | .icon-attach:before { 992 | content: '\e823'; 993 | } 994 | 995 | /* '' */ 996 | .icon-lock:before { 997 | content: '\e824'; 998 | } 999 | 1000 | /* '' */ 1001 | .icon-lock-open:before { 1002 | content: '\e825'; 1003 | } 1004 | 1005 | /* '' */ 1006 | .icon-eye:before { 1007 | content: '\e826'; 1008 | } 1009 | 1010 | /* '' */ 1011 | .icon-tag:before { 1012 | content: '\e827'; 1013 | } 1014 | 1015 | /* '' */ 1016 | .icon-bookmark:before { 1017 | content: '\e828'; 1018 | } 1019 | 1020 | /* '' */ 1021 | .icon-bookmarks:before { 1022 | content: '\e829'; 1023 | } 1024 | 1025 | /* '' */ 1026 | .icon-flag:before { 1027 | content: '\e82a'; 1028 | } 1029 | 1030 | /* '' */ 1031 | .icon-thumbs-up:before { 1032 | content: '\e82b'; 1033 | } 1034 | 1035 | /* '' */ 1036 | .icon-thumbs-down:before { 1037 | content: '\e82c'; 1038 | } 1039 | 1040 | /* '' */ 1041 | .icon-download:before { 1042 | content: '\e82d'; 1043 | } 1044 | 1045 | /* '' */ 1046 | .icon-upload:before { 1047 | content: '\e82e'; 1048 | } 1049 | 1050 | /* '' */ 1051 | .icon-upload-cloud:before { 1052 | content: '\e82f'; 1053 | } 1054 | 1055 | /* '' */ 1056 | .icon-reply:before { 1057 | content: '\e830'; 1058 | } 1059 | 1060 | /* '' */ 1061 | .icon-reply-all:before { 1062 | content: '\e831'; 1063 | } 1064 | 1065 | /* '' */ 1066 | .icon-forward:before { 1067 | content: '\e832'; 1068 | } 1069 | 1070 | /* '' */ 1071 | .icon-quote:before { 1072 | content: '\e833'; 1073 | } 1074 | 1075 | /* '' */ 1076 | .icon-code:before { 1077 | content: '\e834'; 1078 | } 1079 | 1080 | /* '' */ 1081 | .icon-export:before { 1082 | content: '\e835'; 1083 | } 1084 | 1085 | /* '' */ 1086 | .icon-pencil:before { 1087 | content: '\e836'; 1088 | } 1089 | 1090 | /* '' */ 1091 | .icon-feather:before { 1092 | content: '\e837'; 1093 | } 1094 | 1095 | /* '' */ 1096 | .icon-print:before { 1097 | content: '\e838'; 1098 | } 1099 | 1100 | /* '' */ 1101 | .icon-retweet:before { 1102 | content: '\e839'; 1103 | } 1104 | 1105 | /* '' */ 1106 | .icon-keyboard:before { 1107 | content: '\e83a'; 1108 | } 1109 | 1110 | /* '' */ 1111 | .icon-comment:before { 1112 | content: '\e83b'; 1113 | } 1114 | 1115 | /* '' */ 1116 | .icon-chat:before { 1117 | content: '\e83c'; 1118 | } 1119 | 1120 | /* '' */ 1121 | .icon-bell:before { 1122 | content: '\e83d'; 1123 | } 1124 | 1125 | /* '' */ 1126 | .icon-attention:before { 1127 | content: '\e83e'; 1128 | } 1129 | 1130 | /* '' */ 1131 | .icon-alert:before { 1132 | content: '\e83f'; 1133 | } 1134 | 1135 | /* '' */ 1136 | .icon-vcard:before { 1137 | content: '\e840'; 1138 | } 1139 | 1140 | /* '' */ 1141 | .icon-address:before { 1142 | content: '\e841'; 1143 | } 1144 | 1145 | /* '' */ 1146 | .icon-location:before { 1147 | content: '\e842'; 1148 | } 1149 | 1150 | /* '' */ 1151 | .icon-map:before { 1152 | content: '\e843'; 1153 | } 1154 | 1155 | /* '' */ 1156 | .icon-direction:before { 1157 | content: '\e844'; 1158 | } 1159 | 1160 | /* '' */ 1161 | .icon-compass:before { 1162 | content: '\e845'; 1163 | } 1164 | 1165 | /* '' */ 1166 | .icon-cup:before { 1167 | content: '\e846'; 1168 | } 1169 | 1170 | /* '' */ 1171 | .icon-trash:before { 1172 | content: '\e847'; 1173 | } 1174 | 1175 | /* '' */ 1176 | .icon-doc:before { 1177 | content: '\e848'; 1178 | } 1179 | 1180 | /* '' */ 1181 | .icon-docs:before { 1182 | content: '\e849'; 1183 | } 1184 | 1185 | /* '' */ 1186 | .icon-doc-landscape:before { 1187 | content: '\e84a'; 1188 | } 1189 | 1190 | /* '' */ 1191 | .icon-doc-text:before { 1192 | content: '\e84b'; 1193 | } 1194 | 1195 | /* '' */ 1196 | .icon-doc-text-inv:before { 1197 | content: '\e84c'; 1198 | } 1199 | 1200 | /* '' */ 1201 | .icon-newspaper:before { 1202 | content: '\e84d'; 1203 | } 1204 | 1205 | /* '' */ 1206 | .icon-book-open:before { 1207 | content: '\e84e'; 1208 | } 1209 | 1210 | /* '' */ 1211 | .icon-book:before { 1212 | content: '\e84f'; 1213 | } 1214 | 1215 | /* '' */ 1216 | .icon-folder:before { 1217 | content: '\e850'; 1218 | } 1219 | 1220 | /* '' */ 1221 | .icon-archive:before { 1222 | content: '\e851'; 1223 | } 1224 | 1225 | /* '' */ 1226 | .icon-box:before { 1227 | content: '\e852'; 1228 | } 1229 | 1230 | /* '' */ 1231 | .icon-rss:before { 1232 | content: '\e853'; 1233 | } 1234 | 1235 | /* '' */ 1236 | .icon-phone:before { 1237 | content: '\e854'; 1238 | } 1239 | 1240 | /* '' */ 1241 | .icon-cog:before { 1242 | content: '\e855'; 1243 | } 1244 | 1245 | /* '' */ 1246 | .icon-tools:before { 1247 | content: '\e856'; 1248 | } 1249 | 1250 | /* '' */ 1251 | .icon-share:before { 1252 | content: '\e857'; 1253 | } 1254 | 1255 | /* '' */ 1256 | .icon-shareable:before { 1257 | content: '\e858'; 1258 | } 1259 | 1260 | /* '' */ 1261 | .icon-basket:before { 1262 | content: '\e859'; 1263 | } 1264 | 1265 | /* '' */ 1266 | .icon-bag:before { 1267 | content: '\e85a'; 1268 | } 1269 | 1270 | /* '' */ 1271 | .icon-calendar:before { 1272 | content: '\e85b'; 1273 | } 1274 | 1275 | /* '' */ 1276 | .icon-login:before { 1277 | content: '\e85c'; 1278 | } 1279 | 1280 | /* '' */ 1281 | .icon-logout:before { 1282 | content: '\e85d'; 1283 | } 1284 | 1285 | /* '' */ 1286 | .icon-mic:before { 1287 | content: '\e85e'; 1288 | } 1289 | 1290 | /* '' */ 1291 | .icon-mute:before { 1292 | content: '\e85f'; 1293 | } 1294 | 1295 | /* '' */ 1296 | .icon-sound:before { 1297 | content: '\e860'; 1298 | } 1299 | 1300 | /* '' */ 1301 | .icon-volume:before { 1302 | content: '\e861'; 1303 | } 1304 | 1305 | /* '' */ 1306 | .icon-clock:before { 1307 | content: '\e862'; 1308 | } 1309 | 1310 | /* '' */ 1311 | .icon-hourglass:before { 1312 | content: '\e863'; 1313 | } 1314 | 1315 | /* '' */ 1316 | .icon-lamp:before { 1317 | content: '\e864'; 1318 | } 1319 | 1320 | /* '' */ 1321 | .icon-light-down:before { 1322 | content: '\e865'; 1323 | } 1324 | 1325 | /* '' */ 1326 | .icon-light-up:before { 1327 | content: '\e866'; 1328 | } 1329 | 1330 | /* '' */ 1331 | .icon-adjust:before { 1332 | content: '\e867'; 1333 | } 1334 | 1335 | /* '' */ 1336 | .icon-block:before { 1337 | content: '\e868'; 1338 | } 1339 | 1340 | /* '' */ 1341 | .icon-resize-full:before { 1342 | content: '\e869'; 1343 | } 1344 | 1345 | /* '' */ 1346 | .icon-resize-small:before { 1347 | content: '\e86a'; 1348 | } 1349 | 1350 | /* '' */ 1351 | .icon-popup:before { 1352 | content: '\e86b'; 1353 | } 1354 | 1355 | /* '' */ 1356 | .icon-publish:before { 1357 | content: '\e86c'; 1358 | } 1359 | 1360 | /* '' */ 1361 | .icon-window:before { 1362 | content: '\e86d'; 1363 | } 1364 | 1365 | /* '' */ 1366 | .icon-arrow-combo:before { 1367 | content: '\e86e'; 1368 | } 1369 | 1370 | /* '' */ 1371 | .icon-down-circled:before { 1372 | content: '\e86f'; 1373 | } 1374 | 1375 | /* '' */ 1376 | .icon-left-circled:before { 1377 | content: '\e870'; 1378 | } 1379 | 1380 | /* '' */ 1381 | .icon-right-circled:before { 1382 | content: '\e871'; 1383 | } 1384 | 1385 | /* '' */ 1386 | .icon-up-circled:before { 1387 | content: '\e872'; 1388 | } 1389 | 1390 | /* '' */ 1391 | .icon-down-open:before { 1392 | content: '\e873'; 1393 | } 1394 | 1395 | /* '' */ 1396 | .icon-left-open:before { 1397 | content: '\e874'; 1398 | } 1399 | 1400 | /* '' */ 1401 | .icon-right-open:before { 1402 | content: '\e875'; 1403 | } 1404 | 1405 | /* '' */ 1406 | .icon-up-open:before { 1407 | content: '\e876'; 1408 | } 1409 | 1410 | /* '' */ 1411 | .icon-down-open-mini:before { 1412 | content: '\e877'; 1413 | } 1414 | 1415 | /* '' */ 1416 | .icon-left-open-mini:before { 1417 | content: '\e878'; 1418 | } 1419 | 1420 | /* '' */ 1421 | .icon-right-open-mini:before { 1422 | content: '\e879'; 1423 | } 1424 | 1425 | /* '' */ 1426 | .icon-up-open-mini:before { 1427 | content: '\e87a'; 1428 | } 1429 | 1430 | /* '' */ 1431 | .icon-down-open-big:before { 1432 | content: '\e87b'; 1433 | } 1434 | 1435 | /* '' */ 1436 | .icon-left-open-big:before { 1437 | content: '\e87c'; 1438 | } 1439 | 1440 | /* '' */ 1441 | .icon-right-open-big:before { 1442 | content: '\e87d'; 1443 | } 1444 | 1445 | /* '' */ 1446 | .icon-up-open-big:before { 1447 | content: '\e87e'; 1448 | } 1449 | 1450 | /* '' */ 1451 | .icon-down:before { 1452 | content: '\e87f'; 1453 | } 1454 | 1455 | /* '' */ 1456 | .icon-left:before { 1457 | content: '\e880'; 1458 | } 1459 | 1460 | /* '' */ 1461 | .icon-right:before { 1462 | content: '\e881'; 1463 | } 1464 | 1465 | /* '' */ 1466 | .icon-up:before { 1467 | content: '\e882'; 1468 | } 1469 | 1470 | /* '' */ 1471 | .icon-down-dir:before { 1472 | content: '\e883'; 1473 | } 1474 | 1475 | /* '' */ 1476 | .icon-left-dir:before { 1477 | content: '\e884'; 1478 | } 1479 | 1480 | /* '' */ 1481 | .icon-right-dir:before { 1482 | content: '\e885'; 1483 | } 1484 | 1485 | /* '' */ 1486 | .icon-up-dir:before { 1487 | content: '\e886'; 1488 | } 1489 | 1490 | /* '' */ 1491 | .icon-down-bold:before { 1492 | content: '\e887'; 1493 | } 1494 | 1495 | /* '' */ 1496 | .icon-left-bold:before { 1497 | content: '\e888'; 1498 | } 1499 | 1500 | /* '' */ 1501 | .icon-right-bold:before { 1502 | content: '\e889'; 1503 | } 1504 | 1505 | /* '' */ 1506 | .icon-up-bold:before { 1507 | content: '\e88a'; 1508 | } 1509 | 1510 | /* '' */ 1511 | .icon-down-thin:before { 1512 | content: '\e88b'; 1513 | } 1514 | 1515 | /* '' */ 1516 | .icon-left-thin:before { 1517 | content: '\e88c'; 1518 | } 1519 | 1520 | /* '' */ 1521 | .icon-right-thin:before { 1522 | content: '\e88d'; 1523 | } 1524 | 1525 | /* '' */ 1526 | .icon-up-thin:before { 1527 | content: '\e88e'; 1528 | } 1529 | 1530 | /* '' */ 1531 | .icon-ccw:before { 1532 | content: '\e88f'; 1533 | } 1534 | 1535 | /* '' */ 1536 | .icon-cw:before { 1537 | content: '\e890'; 1538 | } 1539 | 1540 | /* '' */ 1541 | .icon-arrows-ccw:before { 1542 | content: '\e891'; 1543 | } 1544 | 1545 | /* '' */ 1546 | .icon-level-down:before { 1547 | content: '\e892'; 1548 | } 1549 | 1550 | /* '' */ 1551 | .icon-level-up:before { 1552 | content: '\e893'; 1553 | } 1554 | 1555 | /* '' */ 1556 | .icon-shuffle:before { 1557 | content: '\e894'; 1558 | } 1559 | 1560 | /* '' */ 1561 | .icon-loop:before { 1562 | content: '\e895'; 1563 | } 1564 | 1565 | /* '' */ 1566 | .icon-switch:before { 1567 | content: '\e896'; 1568 | } 1569 | 1570 | /* '' */ 1571 | .icon-play:before { 1572 | content: '\e897'; 1573 | } 1574 | 1575 | /* '' */ 1576 | .icon-stop:before { 1577 | content: '\e898'; 1578 | } 1579 | 1580 | /* '' */ 1581 | .icon-pause:before { 1582 | content: '\e899'; 1583 | } 1584 | 1585 | /* '' */ 1586 | .icon-record:before { 1587 | content: '\e89a'; 1588 | } 1589 | 1590 | /* '' */ 1591 | .icon-to-end:before { 1592 | content: '\e89b'; 1593 | } 1594 | 1595 | /* '' */ 1596 | .icon-to-start:before { 1597 | content: '\e89c'; 1598 | } 1599 | 1600 | /* '' */ 1601 | .icon-fast-forward:before { 1602 | content: '\e89d'; 1603 | } 1604 | 1605 | /* '' */ 1606 | .icon-fast-backward:before { 1607 | content: '\e89e'; 1608 | } 1609 | 1610 | /* '' */ 1611 | .icon-progress-0:before { 1612 | content: '\e89f'; 1613 | } 1614 | 1615 | /* '' */ 1616 | .icon-progress-1:before { 1617 | content: '\e8a0'; 1618 | } 1619 | 1620 | /* '' */ 1621 | .icon-progress-2:before { 1622 | content: '\e8a1'; 1623 | } 1624 | 1625 | /* '' */ 1626 | .icon-progress-3:before { 1627 | content: '\e8a2'; 1628 | } 1629 | 1630 | /* '' */ 1631 | .icon-target:before { 1632 | content: '\e8a3'; 1633 | } 1634 | 1635 | /* '' */ 1636 | .icon-palette:before { 1637 | content: '\e8a4'; 1638 | } 1639 | 1640 | /* '' */ 1641 | .icon-list:before { 1642 | content: '\e8a5'; 1643 | } 1644 | 1645 | /* '' */ 1646 | .icon-list-add:before { 1647 | content: '\e8a6'; 1648 | } 1649 | 1650 | /* '' */ 1651 | .icon-signal:before { 1652 | content: '\e8a7'; 1653 | } 1654 | 1655 | /* '' */ 1656 | .icon-trophy:before { 1657 | content: '\e8a8'; 1658 | } 1659 | 1660 | /* '' */ 1661 | .icon-battery:before { 1662 | content: '\e8a9'; 1663 | } 1664 | 1665 | /* '' */ 1666 | .icon-back-in-time:before { 1667 | content: '\e8aa'; 1668 | } 1669 | 1670 | /* '' */ 1671 | .icon-monitor:before { 1672 | content: '\e8ab'; 1673 | } 1674 | 1675 | /* '' */ 1676 | .icon-mobile:before { 1677 | content: '\e8ac'; 1678 | } 1679 | 1680 | /* '' */ 1681 | .icon-network:before { 1682 | content: '\e8ad'; 1683 | } 1684 | 1685 | /* '' */ 1686 | .icon-cd:before { 1687 | content: '\e8ae'; 1688 | } 1689 | 1690 | /* '' */ 1691 | .icon-inbox:before { 1692 | content: '\e8af'; 1693 | } 1694 | 1695 | /* '' */ 1696 | .icon-install:before { 1697 | content: '\e8b0'; 1698 | } 1699 | 1700 | /* '' */ 1701 | .icon-globe:before { 1702 | content: '\e8b1'; 1703 | } 1704 | 1705 | /* '' */ 1706 | .icon-cloud:before { 1707 | content: '\e8b2'; 1708 | } 1709 | 1710 | /* '' */ 1711 | .icon-cloud-thunder:before { 1712 | content: '\e8b3'; 1713 | } 1714 | 1715 | /* '' */ 1716 | .icon-flash:before { 1717 | content: '\e8b4'; 1718 | } 1719 | 1720 | /* '' */ 1721 | .icon-moon:before { 1722 | content: '\e8b5'; 1723 | } 1724 | 1725 | /* '' */ 1726 | .icon-flight:before { 1727 | content: '\e8b6'; 1728 | } 1729 | 1730 | /* '' */ 1731 | .icon-paper-plane:before { 1732 | content: '\e8b7'; 1733 | } 1734 | 1735 | /* '' */ 1736 | .icon-leaf:before { 1737 | content: '\e8b8'; 1738 | } 1739 | 1740 | /* '' */ 1741 | .icon-lifebuoy:before { 1742 | content: '\e8b9'; 1743 | } 1744 | 1745 | /* '' */ 1746 | .icon-mouse:before { 1747 | content: '\e8ba'; 1748 | } 1749 | 1750 | /* '' */ 1751 | .icon-briefcase:before { 1752 | content: '\e8bb'; 1753 | } 1754 | 1755 | /* '' */ 1756 | .icon-suitcase:before { 1757 | content: '\e8bc'; 1758 | } 1759 | 1760 | /* '' */ 1761 | .icon-dot:before { 1762 | content: '\e8bd'; 1763 | } 1764 | 1765 | /* '' */ 1766 | .icon-dot-2:before { 1767 | content: '\e8be'; 1768 | } 1769 | 1770 | /* '' */ 1771 | .icon-dot-3:before { 1772 | content: '\e8bf'; 1773 | } 1774 | 1775 | /* '' */ 1776 | .icon-brush:before { 1777 | content: '\e8c0'; 1778 | } 1779 | 1780 | /* '' */ 1781 | .icon-magnet:before { 1782 | content: '\e8c1'; 1783 | } 1784 | 1785 | /* '' */ 1786 | .icon-infinity:before { 1787 | content: '\e8c2'; 1788 | } 1789 | 1790 | /* '' */ 1791 | .icon-erase:before { 1792 | content: '\e8c3'; 1793 | } 1794 | 1795 | /* '' */ 1796 | .icon-chart-pie:before { 1797 | content: '\e8c4'; 1798 | } 1799 | 1800 | /* '' */ 1801 | .icon-chart-line:before { 1802 | content: '\e8c5'; 1803 | } 1804 | 1805 | /* '' */ 1806 | .icon-chart-bar:before { 1807 | content: '\e8c6'; 1808 | } 1809 | 1810 | /* '' */ 1811 | .icon-chart-area:before { 1812 | content: '\e8c7'; 1813 | } 1814 | 1815 | /* '' */ 1816 | .icon-tape:before { 1817 | content: '\e8c8'; 1818 | } 1819 | 1820 | /* '' */ 1821 | .icon-graduation-cap:before { 1822 | content: '\e8c9'; 1823 | } 1824 | 1825 | /* '' */ 1826 | .icon-language:before { 1827 | content: '\e8ca'; 1828 | } 1829 | 1830 | /* '' */ 1831 | .icon-ticket:before { 1832 | content: '\e8cb'; 1833 | } 1834 | 1835 | /* '' */ 1836 | .icon-water:before { 1837 | content: '\e8cc'; 1838 | } 1839 | 1840 | /* '' */ 1841 | .icon-droplet:before { 1842 | content: '\e8cd'; 1843 | } 1844 | 1845 | /* '' */ 1846 | .icon-air:before { 1847 | content: '\e8ce'; 1848 | } 1849 | 1850 | /* '' */ 1851 | .icon-credit-card:before { 1852 | content: '\e8cf'; 1853 | } 1854 | 1855 | /* '' */ 1856 | .icon-floppy:before { 1857 | content: '\e8d0'; 1858 | } 1859 | 1860 | /* '' */ 1861 | .icon-clipboard:before { 1862 | content: '\e8d1'; 1863 | } 1864 | 1865 | /* '' */ 1866 | .icon-megaphone:before { 1867 | content: '\e8d2'; 1868 | } 1869 | 1870 | /* '' */ 1871 | .icon-database:before { 1872 | content: '\e8d3'; 1873 | } 1874 | 1875 | /* '' */ 1876 | .icon-drive:before { 1877 | content: '\e8d4'; 1878 | } 1879 | 1880 | /* '' */ 1881 | .icon-bucket:before { 1882 | content: '\e8d5'; 1883 | } 1884 | 1885 | /* '' */ 1886 | .icon-thermometer:before { 1887 | content: '\e8d6'; 1888 | } 1889 | 1890 | /* '' */ 1891 | .icon-key:before { 1892 | content: '\e8d7'; 1893 | } 1894 | 1895 | /* '' */ 1896 | .icon-flow-cascade:before { 1897 | content: '\e8d8'; 1898 | } 1899 | 1900 | /* '' */ 1901 | .icon-flow-branch:before { 1902 | content: '\e8d9'; 1903 | } 1904 | 1905 | /* '' */ 1906 | .icon-flow-tree:before { 1907 | content: '\e8da'; 1908 | } 1909 | 1910 | /* '' */ 1911 | .icon-flow-line:before { 1912 | content: '\e8db'; 1913 | } 1914 | 1915 | /* '' */ 1916 | .icon-flow-parallel:before { 1917 | content: '\e8dc'; 1918 | } 1919 | 1920 | /* '' */ 1921 | .icon-rocket:before { 1922 | content: '\e8dd'; 1923 | } 1924 | 1925 | /* '' */ 1926 | .icon-gauge:before { 1927 | content: '\e8de'; 1928 | } 1929 | 1930 | /* '' */ 1931 | .icon-traffic-cone:before { 1932 | content: '\e8df'; 1933 | } 1934 | 1935 | /* '' */ 1936 | .icon-cc:before { 1937 | content: '\e8e0'; 1938 | } 1939 | 1940 | /* '' */ 1941 | .icon-cc-by:before { 1942 | content: '\e8e1'; 1943 | } 1944 | 1945 | /* '' */ 1946 | .icon-cc-nc:before { 1947 | content: '\e8e2'; 1948 | } 1949 | 1950 | /* '' */ 1951 | .icon-cc-nc-eu:before { 1952 | content: '\e8e3'; 1953 | } 1954 | 1955 | /* '' */ 1956 | .icon-cc-nc-jp:before { 1957 | content: '\e8e4'; 1958 | } 1959 | 1960 | /* '' */ 1961 | .icon-cc-sa:before { 1962 | content: '\e8e5'; 1963 | } 1964 | 1965 | /* '' */ 1966 | .icon-cc-nd:before { 1967 | content: '\e8e6'; 1968 | } 1969 | 1970 | /* '' */ 1971 | .icon-cc-pd:before { 1972 | content: '\e8e7'; 1973 | } 1974 | 1975 | /* '' */ 1976 | .icon-cc-zero:before { 1977 | content: '\e8e8'; 1978 | } 1979 | 1980 | /* '' */ 1981 | .icon-cc-share:before { 1982 | content: '\e8e9'; 1983 | } 1984 | 1985 | /* '' */ 1986 | .icon-cc-remix:before { 1987 | content: '\e8ea'; 1988 | } 1989 | 1990 | /* '' */ 1991 | .icon-github:before { 1992 | content: '\e8eb'; 1993 | } 1994 | 1995 | /* '' */ 1996 | .icon-github-circled:before { 1997 | content: '\e8ec'; 1998 | } 1999 | 2000 | /* '' */ 2001 | .icon-flickr:before { 2002 | content: '\e8ed'; 2003 | } 2004 | 2005 | /* '' */ 2006 | .icon-flickr-circled:before { 2007 | content: '\e8ee'; 2008 | } 2009 | 2010 | /* '' */ 2011 | .icon-vimeo:before { 2012 | content: '\e8ef'; 2013 | } 2014 | 2015 | /* '' */ 2016 | .icon-vimeo-circled:before { 2017 | content: '\e8f0'; 2018 | } 2019 | 2020 | /* '' */ 2021 | .icon-twitter:before { 2022 | content: '\e8f1'; 2023 | } 2024 | 2025 | /* '' */ 2026 | .icon-twitter-circled:before { 2027 | content: '\e8f2'; 2028 | } 2029 | 2030 | /* '' */ 2031 | .icon-facebook:before { 2032 | content: '\e8f3'; 2033 | } 2034 | 2035 | /* '' */ 2036 | .icon-facebook-circled:before { 2037 | content: '\e8f4'; 2038 | } 2039 | 2040 | /* '' */ 2041 | .icon-facebook-squared:before { 2042 | content: '\e8f5'; 2043 | } 2044 | 2045 | /* '' */ 2046 | .icon-gplus:before { 2047 | content: '\e8f6'; 2048 | } 2049 | 2050 | /* '' */ 2051 | .icon-gplus-circled:before { 2052 | content: '\e8f7'; 2053 | } 2054 | 2055 | /* '' */ 2056 | .icon-pinterest:before { 2057 | content: '\e8f8'; 2058 | } 2059 | 2060 | /* '' */ 2061 | .icon-pinterest-circled:before { 2062 | content: '\e8f9'; 2063 | } 2064 | 2065 | /* '' */ 2066 | .icon-tumblr:before { 2067 | content: '\e8fa'; 2068 | } 2069 | 2070 | /* '' */ 2071 | .icon-tumblr-circled:before { 2072 | content: '\e8fb'; 2073 | } 2074 | 2075 | /* '' */ 2076 | .icon-linkedin:before { 2077 | content: '\e8fc'; 2078 | } 2079 | 2080 | /* '' */ 2081 | .icon-linkedin-circled:before { 2082 | content: '\e8fd'; 2083 | } 2084 | 2085 | /* '' */ 2086 | .icon-dribbble:before { 2087 | content: '\e8fe'; 2088 | } 2089 | 2090 | /* '' */ 2091 | .icon-dribbble-circled:before { 2092 | content: '\e8ff'; 2093 | } 2094 | 2095 | /* '' */ 2096 | .icon-stumbleupon:before { 2097 | content: '\e900'; 2098 | } 2099 | 2100 | /* '' */ 2101 | .icon-stumbleupon-circled:before { 2102 | content: '\e901'; 2103 | } 2104 | 2105 | /* '' */ 2106 | .icon-lastfm:before { 2107 | content: '\e902'; 2108 | } 2109 | 2110 | /* '' */ 2111 | .icon-lastfm-circled:before { 2112 | content: '\e903'; 2113 | } 2114 | 2115 | /* '' */ 2116 | .icon-rdio:before { 2117 | content: '\e904'; 2118 | } 2119 | 2120 | /* '' */ 2121 | .icon-rdio-circled:before { 2122 | content: '\e905'; 2123 | } 2124 | 2125 | /* '' */ 2126 | .icon-spotify:before { 2127 | content: '\e906'; 2128 | } 2129 | 2130 | /* '' */ 2131 | .icon-spotify-circled:before { 2132 | content: '\e907'; 2133 | } 2134 | 2135 | /* '' */ 2136 | .icon-qq:before { 2137 | content: '\e908'; 2138 | } 2139 | 2140 | /* '' */ 2141 | .icon-instagram:before { 2142 | content: '\e909'; 2143 | } 2144 | 2145 | /* '' */ 2146 | .icon-dropbox:before { 2147 | content: '\e90a'; 2148 | } 2149 | 2150 | /* '' */ 2151 | .icon-evernote:before { 2152 | content: '\e90b'; 2153 | } 2154 | 2155 | /* '' */ 2156 | .icon-flattr:before { 2157 | content: '\e90c'; 2158 | } 2159 | 2160 | /* '' */ 2161 | .icon-skype:before { 2162 | content: '\e90d'; 2163 | } 2164 | 2165 | /* '' */ 2166 | .icon-skype-circled:before { 2167 | content: '\e90e'; 2168 | } 2169 | 2170 | /* '' */ 2171 | .icon-renren:before { 2172 | content: '\e90f'; 2173 | } 2174 | 2175 | /* '' */ 2176 | .icon-sina-weibo:before { 2177 | content: '\e910'; 2178 | } 2179 | 2180 | /* '' */ 2181 | .icon-paypal:before { 2182 | content: '\e911'; 2183 | } 2184 | 2185 | /* '' */ 2186 | .icon-picasa:before { 2187 | content: '\e912'; 2188 | } 2189 | 2190 | /* '' */ 2191 | .icon-soundcloud:before { 2192 | content: '\e913'; 2193 | } 2194 | 2195 | /* '' */ 2196 | .icon-mixi:before { 2197 | content: '\e914'; 2198 | } 2199 | 2200 | /* '' */ 2201 | .icon-behance:before { 2202 | content: '\e915'; 2203 | } 2204 | 2205 | /* '' */ 2206 | .icon-google-circles:before { 2207 | content: '\e916'; 2208 | } 2209 | 2210 | /* '' */ 2211 | .icon-vkontakte:before { 2212 | content: '\e917'; 2213 | } 2214 | 2215 | /* '' */ 2216 | .icon-smashing:before { 2217 | content: '\e918'; 2218 | } 2219 | 2220 | /* '' */ 2221 | .icon-sweden:before { 2222 | content: '\e919'; 2223 | } 2224 | 2225 | /* '' */ 2226 | .icon-db-shape:before { 2227 | content: '\e91a'; 2228 | } 2229 | 2230 | /* '' */ 2231 | .icon-logo-db:before { 2232 | content: '\e91b'; 2233 | } 2234 | 2235 | /* '' */ 2236 | table { 2237 | width: 100%; 2238 | border: 0; 2239 | border-collapse: separate; 2240 | font-size: 12px; 2241 | text-align: left; 2242 | } 2243 | 2244 | thead { 2245 | background-color: #f5f5f4; 2246 | } 2247 | 2248 | tbody { 2249 | background-color: #fff; 2250 | } 2251 | 2252 | .table-striped tr:nth-child(even) { 2253 | background-color: #f5f5f4; 2254 | } 2255 | 2256 | tr:active, 2257 | .table-striped tr:active:nth-child(even) { 2258 | color: #fff; 2259 | background-color: #116cd6; 2260 | } 2261 | 2262 | thead tr:active { 2263 | color: #333; 2264 | background-color: #f5f5f4; 2265 | } 2266 | 2267 | th { 2268 | font-weight: normal; 2269 | border-right: 1px solid #ddd; 2270 | border-bottom: 1px solid #ddd; 2271 | } 2272 | 2273 | th, 2274 | td { 2275 | padding: 2px 15px; 2276 | white-space: nowrap; 2277 | overflow: hidden; 2278 | text-overflow: ellipsis; 2279 | } 2280 | th:last-child, 2281 | td:last-child { 2282 | border-right: 0; 2283 | } 2284 | 2285 | .tab-group { 2286 | margin-top: -1px; 2287 | display: flex; 2288 | border-top: 1px solid #989698; 2289 | border-bottom: 1px solid #989698; 2290 | } 2291 | 2292 | .tab-item { 2293 | position: relative; 2294 | flex: 1; 2295 | padding: 3px; 2296 | font-size: 12px; 2297 | text-align: center; 2298 | border-left: 1px solid #989698; 2299 | background-color: #b8b6b8; 2300 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b8b6b8), color-stop(100%, #b0aeb0)); 2301 | background-image: -webkit-linear-gradient(top, #b8b6b8 0%, #b0aeb0 100%); 2302 | background-image: linear-gradient(to bottom, #b8b6b8 0%, #b0aeb0 100%); 2303 | } 2304 | .tab-item:first-child { 2305 | border-left: 0; 2306 | } 2307 | .tab-item.active { 2308 | background-color: #d4d2d4; 2309 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #d4d2d4), color-stop(100%, #cccacc)); 2310 | background-image: -webkit-linear-gradient(top, #d4d2d4 0%, #cccacc 100%); 2311 | background-image: linear-gradient(to bottom, #d4d2d4 0%, #cccacc 100%); 2312 | } 2313 | .tab-item .icon-close-tab { 2314 | position: absolute; 2315 | top: 50%; 2316 | left: 5px; 2317 | width: 15px; 2318 | height: 15px; 2319 | font-size: 15px; 2320 | line-height: 15px; 2321 | text-align: center; 2322 | color: #666; 2323 | opacity: 0; 2324 | transition: opacity .1s linear, background-color .1s linear; 2325 | border-radius: 3px; 2326 | transform: translateY(-50%); 2327 | z-index: 10; 2328 | } 2329 | .tab-item:after { 2330 | position: absolute; 2331 | top: 0; 2332 | right: 0; 2333 | bottom: 0; 2334 | left: 0; 2335 | content: ""; 2336 | background-color: rgba(0, 0, 0, 0.08); 2337 | opacity: 0; 2338 | transition: opacity .1s linear; 2339 | z-index: 1; 2340 | } 2341 | .tab-item:hover:not(.active):after { 2342 | opacity: 1; 2343 | } 2344 | .tab-item:hover .icon-close-tab { 2345 | opacity: 1; 2346 | } 2347 | .tab-item .icon-close-tab:hover { 2348 | background-color: rgba(0, 0, 0, 0.08); 2349 | } 2350 | 2351 | .tab-item-fixed { 2352 | flex: none; 2353 | padding: 3px 10px; 2354 | } 2355 | -------------------------------------------------------------------------------- /app/vendor/roboto.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-style: normal; 4 | font-weight: 300; 5 | src: url('../fonts/Roboto-Light.woff2') format('woff2'), 6 | url('../fonts/Roboto-Light.woff') format('woff'), 7 | url('../fonts/Roboto-Light.ttf') format('truetype'); 8 | } -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | /** 6 | * Modules 7 | * Node 8 | * @constant 9 | */ 10 | const path = require('path'); 11 | const child_process = require('child_process'); 12 | 13 | /** 14 | * Modules 15 | * External 16 | * @constant 17 | */ 18 | const appRootPath = require('app-root-path'); 19 | const parseSemver = require('parse-semver'); 20 | const simpleReload = require('simple-reload'); 21 | const tryRequire = require('try-require'); 22 | const _ = require('lodash'); 23 | 24 | /** 25 | * Modules 26 | * Configuration 27 | */ 28 | appRootPath.setPath(path.join(__dirname, '..')); 29 | 30 | /** 31 | * Modules 32 | * Internal 33 | * @constant 34 | */ 35 | const packageJson = require(path.join(appRootPath.path, 'package.json')); 36 | const logger = require(path.join(appRootPath.path, 'lib', 'logger'))({ namespace: packageJson.productName, timestamp: false }); 37 | 38 | /** 39 | * Filesystem 40 | * @constant 41 | * @default 42 | */ 43 | const applicationPath = path.join(appRootPath.path, packageJson.main); 44 | 45 | 46 | /** 47 | * Install required Dependencies 48 | * @param {Object} dependencyObject - package.json dependency map 49 | * @param {Function} callback - Callback 50 | */ 51 | let installDependencies = (dependencyObject, callback) => { 52 | logger.debug('installDependencies', dependencyObject); 53 | 54 | let cb = callback || function () {}; 55 | 56 | // Generate list of dependencies 57 | let dependencyNameList = Object.keys(dependencyObject); 58 | 59 | // Determine uninstalled dependencies 60 | dependencyNameList = dependencyNameList.filter((dependencyName) => { 61 | const isInstalled = tryRequire.resolve(dependencyName); 62 | return !isInstalled; 63 | }); 64 | 65 | // Generate package names 66 | let dependencyList = dependencyNameList.map((dependencyName) => { 67 | const foundVersion = dependencyObject[dependencyName]; 68 | let parsedVersion; 69 | 70 | try { 71 | parsedVersion = parseSemver(`${dependencyName}@${foundVersion}`).version; 72 | } catch(err) { 73 | parsedVersion = foundVersion; 74 | } 75 | 76 | return `${dependencyName}@${parsedVersion}`; 77 | }); 78 | 79 | // Install 80 | if (dependencyList.length > 0) { 81 | /** 82 | * npm install 83 | */ 84 | logger.info('installing dependencies:', `"${dependencyList.join('", "')}"`); 85 | 86 | child_process.execSync(`npm install ${dependencyList.join(' ')} --loglevel silent`, { 87 | cwd: appRootPath.path, 88 | maxBuffer: (20000 * 1024), 89 | stdio: 'inherit' 90 | }); 91 | 92 | logger.info(`installing dependencies complete.`); 93 | } 94 | 95 | cb(null); 96 | }; 97 | 98 | /** 99 | * Launch App 100 | * @param {String} electronPath - Path to Electron 101 | * @param {String} applicationPath - Path to App 102 | */ 103 | let launchApplication = (electronPath, applicationPath) => { 104 | logger.debug('launchApplication'); 105 | 106 | /** 107 | * Launch application 108 | */ 109 | logger.info('application location:', `"${appRootPath.path}"`); 110 | logger.info('electron installation:', `"${path.relative(appRootPath.path, electronPath)}"`); 111 | 112 | const child = child_process.spawn(electronPath, [applicationPath], { 113 | cwd: appRootPath.path, 114 | detached: true, 115 | stdio: 'ignore' 116 | }); 117 | 118 | /** 119 | * Fork process 120 | */ 121 | child.unref(); 122 | 123 | /** 124 | * Exit 125 | */ 126 | process.on('exit', () => { 127 | logger.info('successfully started.'); 128 | }); 129 | 130 | process.exit(0); 131 | }; 132 | 133 | 134 | /** 135 | * Main 136 | */ 137 | let main = () => { 138 | logger.debug('main'); 139 | 140 | installDependencies(packageJson.devDependencies, () => { 141 | let interval = setInterval(() => { 142 | const electron = simpleReload('electron'); 143 | 144 | if (!electron) { 145 | return; 146 | } 147 | 148 | launchApplication(electron, applicationPath); 149 | clearInterval(interval); 150 | }, 2000); 151 | }); 152 | }; 153 | 154 | 155 | /** 156 | * Init 157 | */ 158 | if (require.main === module) { 159 | main(); 160 | } 161 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Modules 6 | * Node 7 | * @constant 8 | */ 9 | const path = require('path'); 10 | 11 | /** 12 | * Modules 13 | * Electron 14 | * @constant 15 | */ 16 | const electron = require('electron'); 17 | const { app } = electron; 18 | 19 | /** 20 | * Modules 21 | * External 22 | * @constant 23 | */ 24 | const appRootPath = require('app-root-path'); 25 | const electronConnect = require('electron-connect'); 26 | const gulp = require('gulp'); 27 | const minimist = require('minimist'); 28 | 29 | 30 | /** 31 | * Modules 32 | * Configuration 33 | */ 34 | appRootPath.setPath(path.join(__dirname)); 35 | 36 | /** 37 | * Modules 38 | * Internal 39 | * @constant 40 | */ 41 | const logger = require(path.join(appRootPath.path, 'lib', 'logger'))({ write: true }); 42 | const packageJson = require(path.join(appRootPath.path, 'package.json')); 43 | 44 | 45 | /** 46 | * Filesystem 47 | * @constant 48 | * @default 49 | */ 50 | const applicationPath = path.join(appRootPath['path'], packageJson.main); 51 | 52 | /** 53 | * Electron Connect Server 54 | * Init 55 | */ 56 | const electronConnectServer = electronConnect.server.create({ 57 | //logLevel: 2, 58 | //verbose: true, 59 | stopOnClose: true, 60 | electron: electron, 61 | path: applicationPath, 62 | useGlobalElectron: false 63 | }); 64 | 65 | /** 66 | * Electron Connect Server 67 | * Files 68 | */ 69 | let appSources = { 70 | main: [ 71 | path.join(appRootPath['path'], 'app', 'fonts', '**', '*.*'), 72 | path.join(appRootPath['path'], 'app', 'html', '**', '*.*'), 73 | path.join(appRootPath['path'], 'app', 'images', '**', '*.*'), 74 | path.join(appRootPath['path'], 'app', 'scripts', 'main', '**', '*.*'), 75 | path.join(appRootPath['path'], 'app', 'styles', '**', '*.*'), 76 | path.join(appRootPath['path'], 'package.json') 77 | ], 78 | renderer: [ 79 | path.join(appRootPath['path'], 'app', 'scripts', 'renderer', '**', '*.*') 80 | ] 81 | }; 82 | 83 | 84 | /** 85 | * Server 86 | * start 87 | */ 88 | gulp.task('livereload', () => { 89 | let globalArgvObj; 90 | let npmArgvObj; 91 | try { globalArgvObj = minimist(process.argv); } catch (err) {} 92 | try { npmArgvObj = minimist(JSON.parse(process.env.npm_config_argv).original); } catch (err) {} 93 | 94 | logger.info(globalArgvObj) 95 | logger.info(npmArgvObj) 96 | 97 | electronConnectServer.start(); 98 | gulp.watch(appSources.main, ['main:restart']); 99 | gulp.watch(appSources.renderer, ['renderer:reload']); 100 | }); 101 | 102 | /** 103 | * Main Process 104 | * restart 105 | */ 106 | gulp.task('main:restart', (callback) => { 107 | electronConnectServer.restart(); 108 | callback(); 109 | }); 110 | 111 | /** 112 | * Renderer Process 113 | * restart 114 | */ 115 | gulp.task('renderer:reload', (callback) => { 116 | electronConnectServer.reload(); 117 | callback(); 118 | }); 119 | 120 | 121 | gulp.task('default', ['livereload']); 122 | 123 | -------------------------------------------------------------------------------- /icons/darwin/background-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/darwin/background-setup.png -------------------------------------------------------------------------------- /icons/darwin/icon-setup.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/darwin/icon-setup.icns -------------------------------------------------------------------------------- /icons/darwin/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/darwin/icon.icns -------------------------------------------------------------------------------- /icons/linux/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/1024x1024.png -------------------------------------------------------------------------------- /icons/linux/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/128x128.png -------------------------------------------------------------------------------- /icons/linux/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/16x16.png -------------------------------------------------------------------------------- /icons/linux/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/24x24.png -------------------------------------------------------------------------------- /icons/linux/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/256x256.png -------------------------------------------------------------------------------- /icons/linux/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/32x32.png -------------------------------------------------------------------------------- /icons/linux/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/48x48.png -------------------------------------------------------------------------------- /icons/linux/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/512x512.png -------------------------------------------------------------------------------- /icons/linux/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/64x64.png -------------------------------------------------------------------------------- /icons/linux/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/96x96.png -------------------------------------------------------------------------------- /icons/linux/icon-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/icon-setup.png -------------------------------------------------------------------------------- /icons/linux/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/linux/icon.png -------------------------------------------------------------------------------- /icons/win32/background-setup.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/win32/background-setup.bmp -------------------------------------------------------------------------------- /icons/win32/header-setup.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/win32/header-setup.bmp -------------------------------------------------------------------------------- /icons/win32/icon-setup.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/win32/icon-setup.ico -------------------------------------------------------------------------------- /icons/win32/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/icons/win32/icon.ico -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "desktop-dimmer", 3 | "productName": "Desktop Dimmer", 4 | "version": "4.0.4", 5 | "description": "Enables darker-than-dark Desktop dimming for your displays", 6 | "license": "MIT", 7 | "homepage": "https://sidneys.github.io/desktop-dimmer", 8 | "author": { 9 | "name": "sidneys", 10 | "email": "sidneys.github.io@outlook.com", 11 | "url": "https://github.com/sidneys" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/sidneys/desktop-dimmer.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/sidneys/desktop-dimmer/issues" 19 | }, 20 | "engines": { 21 | "node": ">=9.0.0" 22 | }, 23 | "os": [ 24 | "darwin", 25 | "win32", 26 | "linux" 27 | ], 28 | "keywords": [ 29 | "desktop dimmer", 30 | "shades", 31 | "dimmer", 32 | "shade", 33 | "turn off the lights" 34 | ], 35 | "preferGlobal": true, 36 | "dependencies": { 37 | "@sidneys/dom-tools": "^1.0.5", 38 | "@sidneys/electron-build": "^1.1.5", 39 | "@sidneys/electron-deploy-github": "^1.0.5", 40 | "@sidneys/electron-localsetup": "^1.0.6", 41 | "@sidneys/is-env": "^1.0.4", 42 | "@sidneys/logger": "^1.0.9", 43 | "@sidneys/platform-tools": "^1.0.1", 44 | "@sidneys/releasenotes": "^1.0.10", 45 | "app-root-path": "^2.0.1", 46 | "appdirectory": "^0.1.0", 47 | "auto-launch": "git://github.com/sidneys/node-auto-launch.git#master", 48 | "babel-cli": "^6.26.0", 49 | "babel-preset-electron": "^1.4.15", 50 | "chalk": "^2.3.0", 51 | "chalkline": "0.0.5", 52 | "electron-editor-context-menu": "^1.1.1", 53 | "electron-settings": "^3.1.4", 54 | "electron-updater": "^2.16.1", 55 | "file-type": "^7.2.0", 56 | "file-url": "^2.0.2", 57 | "filesize": "^3.5.11", 58 | "fs-extra": "^4.0.2", 59 | "globby": "^6.1.0", 60 | "hammerjs": "^2.0.8", 61 | "is-reachable": "^2.3.3", 62 | "keypath": "0.0.1", 63 | "lodash": "^4.17.4", 64 | "menubar": "git://github.com/sidneys/menubar.git#add-taskbar-support", 65 | "minimist": "^1.2.0", 66 | "moment": "^2.19.2", 67 | "moment-duration-format": "^1.3.0", 68 | "parse-domain": "^1.1.0", 69 | "parse-semver": "^1.1.1", 70 | "present": "^1.0.0", 71 | "query-string": "^5.0.1", 72 | "random-int": "^1.0.0", 73 | "read-chunk": "^2.1.0", 74 | "remove-markdown": "^0.2.2", 75 | "request": "^2.83.0", 76 | "semver-compare": "^1.0.0", 77 | "simple-reload": "0.0.4", 78 | "tinycolor2": "^1.4.1", 79 | "try-require": "latest" 80 | }, 81 | "devDependencies": { 82 | "docdash": "^0.4.0", 83 | "electron": "1.4.15", 84 | "electron-builder": "19.45.4", 85 | "electron-connect": "^0.6.2", 86 | "electron-prebuilt-compile": "1.4.15", 87 | "eslint": "^4.11.0", 88 | "gulp": "^3.9.1", 89 | "gulp-load-plugins": "^1.5.0", 90 | "jsdoc": "^3.5.5", 91 | "minami": "^1.2.3" 92 | }, 93 | "main": "./app/scripts/main/components/application.js", 94 | "bin": { 95 | "desktop-dimmer": "./bin/cli.js" 96 | }, 97 | "scripts": { 98 | "build": "node ./node_modules/@sidneys/electron-build/index.js", 99 | "deploy": "node ./node_modules/@sidneys/electron-deploy-github/index.js", 100 | "docs": "./node_modules/.bin/jsdoc --verbose --configure .jsdoc.json", 101 | "livereload": "./node_modules/.bin/gulp", 102 | "localsetup": "node ./node_modules/@sidneys/electron-localsetup/index.js", 103 | "releasenotes": "node ./node_modules/@sidneys/releasenotes/index.js", 104 | "start": "./node_modules/.bin/electron ./app/scripts/main/components/application.js" 105 | }, 106 | "build": { 107 | "appId": "de.sidneys.desktop-dimmer", 108 | "asar": true, 109 | "compression": "maximum", 110 | "directories": { 111 | "buildResources": "./build/staging", 112 | "output": "./build/output" 113 | }, 114 | "files": [ 115 | "!.appveyor.yml", 116 | "!.babelrc", 117 | "!.editorconfig", 118 | "!.eslintignore", 119 | "!.eslintrc", 120 | "!.github/**", 121 | "!.gitignore", 122 | "!.npmignore", 123 | "!.travis.yml", 124 | "!build/**", 125 | "!gh-pages/**", 126 | "!resources/**", 127 | "!website/**" 128 | ], 129 | "mac": { 130 | "category": "public.app-category.utilities", 131 | "icon": "./icons/darwin/icon.icns", 132 | "target": [ 133 | "dir", 134 | "dmg", 135 | "zip" 136 | ] 137 | }, 138 | "win": { 139 | "icon": "./icons/win32/icon.ico", 140 | "target": [ 141 | "nsis" 142 | ] 143 | }, 144 | "linux": { 145 | "category": "Utility", 146 | "icon": "./icons/linux", 147 | "target": [ 148 | "AppImage", 149 | "deb", 150 | "pacman", 151 | "rpm" 152 | ] 153 | }, 154 | "dmg": { 155 | "background": "./icons/darwin/background-setup.png", 156 | "icon": "./icons/darwin/icon-setup.icns" 157 | }, 158 | "nsis": { 159 | "artifactName": "${name}-${version}-setup.${ext}", 160 | "deleteAppDataOnUninstall": true, 161 | "installerHeader": "./icons/win32/header-setup.bmp", 162 | "installerHeaderIcon": "./icons/win32/icon.ico", 163 | "installerIcon": "./icons/win32/icon-setup.ico", 164 | "installerSidebar": "./icons/win32/background-setup.bmp", 165 | "oneClick": false, 166 | "runAfterFinish": false 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /resources/graphics/icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/resources/graphics/icon.ai -------------------------------------------------------------------------------- /resources/graphics/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/resources/graphics/icon.png -------------------------------------------------------------------------------- /resources/screenshots/screenshot-macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/resources/screenshots/screenshot-macos.png -------------------------------------------------------------------------------- /resources/screenshots/screenshot-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidneys/desktop-dimmer/e1a1d2911a25a7b69314710b2a29ff0e0ded744e/resources/screenshots/screenshot-win32.png --------------------------------------------------------------------------------