├── .babelrc ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── dist └── js │ ├── Aether.js │ ├── Aether.js.map │ └── draggable.min.js ├── index.html ├── index.theme ├── package-lock.json ├── package.json ├── src ├── css │ ├── style.css │ └── style.css.map ├── font │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.woff │ └── OpenSans-Regular-webfont.woff ├── img │ ├── arch-logo.png │ ├── arrow.svg │ ├── avatar-background.png │ ├── default-user.png │ ├── dropdown-caret.svg │ ├── dropdown-carext.svg │ ├── gl │ │ ├── smoke.png │ │ └── spark.png │ ├── hibernate.svg │ ├── logos │ │ ├── antergos.png │ │ ├── archlinux.png │ │ ├── debian.png │ │ ├── generic.png │ │ ├── tux-silhouette.png │ │ ├── tux.png │ │ └── ubuntu.png │ ├── reboot.svg │ ├── shutdown.svg │ ├── sleep.svg │ └── wallpapers │ │ ├── abstract.jpg │ │ ├── boko.jpg │ │ ├── car.jpg │ │ ├── cavern.jpg │ │ ├── dark-grass.jpg │ │ ├── frosted.jpg │ │ ├── jet.jpg │ │ ├── mountains-1.jpg │ │ ├── mountains-2.png │ │ ├── mountains-3.jpg │ │ ├── night.png │ │ ├── ocean.jpg │ │ ├── paper-lanterns.jpg │ │ ├── pier.jpg │ │ ├── sanfran.jpg │ │ ├── space-1.jpg │ │ ├── space-2.jpg │ │ ├── space-3.jpg │ │ └── tree.jpg ├── js │ ├── Components │ │ ├── DateDisplay │ │ │ └── Main.jsx │ │ ├── ExperimentalStars │ │ │ ├── Main.jsx │ │ │ └── Particle.js │ │ ├── LoginWindow │ │ │ ├── Main.jsx │ │ │ ├── Sidebar │ │ │ │ ├── Clock.jsx │ │ │ │ ├── Item.jsx │ │ │ │ ├── List.jsx │ │ │ │ ├── Main.jsx │ │ │ │ └── WallpaperSwitcher.jsx │ │ │ └── UserPicker │ │ │ │ ├── Form.jsx │ │ │ │ ├── Main.jsx │ │ │ │ ├── PasswordField.jsx │ │ │ │ ├── SessionSwitcher │ │ │ │ ├── Main.jsx │ │ │ │ └── SessionItem.jsx │ │ │ │ └── UserSwitcher │ │ │ │ ├── Main.jsx │ │ │ │ └── UserSwitchButton.jsx │ │ ├── Settings │ │ │ ├── DefaultThemes.js │ │ │ ├── Main.jsx │ │ │ ├── SaveDialogue.jsx │ │ │ ├── inputs │ │ │ │ ├── Checkbox.jsx │ │ │ │ ├── ColorPicker.jsx │ │ │ │ ├── Dropdown.jsx │ │ │ │ └── TextField.jsx │ │ │ └── sections │ │ │ │ ├── General.jsx │ │ │ │ ├── Style.jsx │ │ │ │ └── Themes.jsx │ │ └── SettingsToggler │ │ │ └── Main.jsx │ ├── Logic │ │ ├── FileOperations.js │ │ ├── Settings.js │ │ └── SystemOperations.js │ ├── Main.jsx │ ├── Reducers │ │ ├── PrimaryReducer.js │ │ └── SettingsReducer.js │ ├── Themes │ │ └── ThemeMatcher.jsx │ └── Utils │ │ ├── Notifications.js │ │ └── Utils.js ├── sass │ ├── _animations.sass │ ├── _config.sass │ ├── _fonts.sass │ ├── _reset.sass │ ├── _scroll.sass │ ├── _utility.sass │ └── style.sass └── test │ ├── logos │ ├── antergos.png │ ├── archlinux.png │ ├── debian.png │ ├── generic.png │ ├── tux-silhouette.png │ ├── tux.png │ └── ubuntu.png │ └── wallpapers │ ├── boko.jpg │ ├── mountains-2.png │ └── space-1.jpg ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "browsers": [ "last 2 versions", "chrome >= 46" ] 8 | } 9 | } 10 | ], 11 | "@babel/preset-react" 12 | ], 13 | "plugins": [ 14 | "jsx-control-statements" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "globals": { 7 | "require": true 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:react/recommended", 12 | "plugin:jsx-control-statements/recommended" 13 | ], 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "ecmaFeatures": { 17 | "jsx": true 18 | }, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "jsx-control-statements" 24 | ], 25 | "rules": { 26 | "react/jsx-no-undef": [2, { "allowGlobals": true }], 27 | "indent": [ 28 | "error", 29 | 2, 30 | { 31 | "SwitchCase": 1 32 | } 33 | ], 34 | "linebreak-style": [ 35 | "error", 36 | "unix" 37 | ], 38 | "no-unused-vars" : [ 39 | "warn", 40 | { 41 | "argsIgnorePattern": "_*" 42 | } 43 | ], 44 | "no-warning-comments" : [ 45 | "warn", 46 | { 47 | "terms": [ 48 | "todo", 49 | "fixme", 50 | "hack" 51 | ] 52 | } 53 | ], 54 | "semi": [ 55 | "error", 56 | "always" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Thanks for using Aether! Describe your bug in detail so it can be addressed. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Expected behavior** 11 | A clear and concise description of what you expected to happen. 12 | 13 | **Actual behavior** 14 | A clear and concise description of what actually happened. 15 | 16 | **Steps To Reproduce** 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Additional context** 26 | Additional context, and logs results are extremely useful here! 27 | 28 | If relevant, include logs from the following command: 29 | `sudo tail /var/log/lightdm/seat0-greeter.log` 30 | 31 | 32 | **WARNING**: 33 | Bug reports that do not follow this template WILL be closed immediately. Aether is developed for free, please be considerate of my time! 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for Aether. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/** 2 | **/.sass-cache/** 3 | 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aether 2 | ###### ( lightdm-webkit-theme-aether ) 3 | Inspired by a lifelong love with space. 4 | 5 | A Sleek, straightforward Archlinux themed login screen written on lightdm and the lightdm-webkit2-greeter. 6 | 7 | ## **[➡ ➡ Try the live demo of Aether here ⬅ ⬅](https://noisek.github.io/Aether/)** 8 | 9 | ![](../screenshots/screenshot.png) 10 | 11 | ## Table of Contents 12 | 13 | - [Aether](#aether) 14 | - [Features](#features) 15 | - [Requirements](#requirements) 16 | - [Installation](#installation) 17 | - [Accessing the Settings Dialogue](#accessing-the-settings-dialogue) 18 | - [Setting an Avatar Image](#setting-an-avatar-image) 19 | - [Using Your Own Wallpapers](#using-your-own-wallpapers) 20 | - [Modifying Date and Time Format](#modifying-date-and-time-format) 21 | - [Troubleshooting](#troubleshooting) 22 | - [My login screen hasn't changed!](#my-login-screen-hasnt-changed) 23 | - [My screen is black!](#my-screen-is-black) 24 | - [My system hangs at the boot screen!](#my-system-hangs-at-the-boot-screen) 25 | - [The lock screen isn't using my lightdm theme!](#the-lock-screen-isnt-using-my-lightdm-theme) 26 | - [Development](#development) 27 | - [Running Tests](#running-tests) 28 | - [Building Project](#building-project) 29 | - [Monitoring Changes](#monitoring-changes) 30 | - [Todo](#todo) 31 | - [Credit](#credits) 32 | 33 | ## Features 34 | 35 | **Stylish Default Themes** 36 | 37 | ![](../screenshots/theme-showcase.gif) 38 | 39 | **Advanced Customization** 40 | 41 | ![](../screenshots/settings-customization.gif) 42 | 43 | **Multi User Support** 44 | 45 | ![](../screenshots/user-switcher.gif) 46 | 47 | **Built-in Wallpaper Customization** 48 | 49 | ![](../screenshots/wallpaper-switcher.gif) 50 | 51 | ## Requirements 52 | - [lightdm-webkit2-greeter (aur/lightdm-webkit2-greeter )](https://github.com/Antergos/lightdm-webkit2-greeter) 53 | 54 | ## Installation 55 | 56 | **Recommended Automatic Installation** 57 | 58 | [Available on the AUR](https://aur.archlinux.org/packages/lightdm-webkit-theme-aether/). ArchLinux users can substitute pacaur with yaourt, packer, etc. as necessary and install with the following: 59 | 60 | ``` 61 | pacaur -S lightdm-webkit-theme-aether 62 | ``` 63 | 64 | **Manual Installation** 65 | 66 | This assumes that you already have lightdm and lightdm-webkit2-greeter installed (but not configured). 67 | 68 | NOTE: Users performing a manual installation directly from Github should replace the `lightdm-webkit-theme-aether` values in the provided sed commands with `Aether` to match the name of the theme directory. Users performing a manual installation from the AUR should make no changes. 69 | 70 | ``` 71 | # If you prefer the last stable release, download from the releases page instead: https://github.com/NoiSek/Aether/releases/latest 72 | git clone git@github.com:NoiSek/Aether.git 73 | sudo cp --recursive Aether /usr/share/lightdm-webkit/themes/Aether 74 | 75 | # Set default lightdm-webkit2-greeter theme to Aether 76 | sudo sed -i 's/^webkit_theme\s*=\s*\(.*\)/webkit_theme = lightdm-webkit-theme-aether #\1/g' /etc/lightdm/lightdm-webkit2-greeter.conf 77 | 78 | # Set default lightdm greeter to lightdm-webkit2-greeter 79 | sudo sed -i 's/^\(#?greeter\)-session\s*=\s*\(.*\)/greeter-session = lightdm-webkit2-greeter #\1/ #\2g' /etc/lightdm/lightdm.conf 80 | ``` 81 | 82 | 83 | ### **Accessing the Settings Dialogue** 84 | 85 | Hover over the bottom left of your screen to find the settings button. Once active, theme settings will remain open in a draggable dialogue window until dismissed. 86 | 87 | 88 | ### **Setting an Avatar Image** 89 | 90 | ![](./src/img/default-user.png) 91 | 92 | Once LightDM, LightDM Webkit Greeter, and Aether are installed you will need to set an avatar image for your users. Size is irrelevant, and avatars will be displayed as a 125x125 circle (Yes, square images too). Users that don't have an avatar set will default to the [astronaut](./src/img/default-user.png). 93 | 94 | To accomplish this, you can do either of the following: 95 | - Create an image in your home directory named `.face`. 96 | - Append `Icon=/path/to/your/avatar.png` to the bottom of the file at `/var/lib/AccountsService/users/` 97 | 98 | ### **Using Your Own Wallpapers** 99 | 100 | #### Method One: 101 | Add and delete wallpapers within the `src/img/wallpapers/` directory as you see fit. By default, you will find this folder at the absolute path: `/usr/share/lightdm-webkit/themes/lightdm-webkit-theme-aether/src/img/wallpapers/`. 102 | 103 | #### Method Two: 104 | Edit the `background_images` value under `branding` within your lightdm-webkit config file located at `/etc/lightdm/lightdm-webkit2-greeter.conf`. 105 | *Note: This ignores the default value of /usr/share/backgrounds, as this is always set and would prevent the default wallpapers from working. To use wallpapers from within that directory, create a subdirectory at /usr/share/backgrounds/aether (or any other folder name) and change your config value accordingly.* 106 | 107 | ### **Modifying Date and Time Format** 108 | 109 | The formatting symbols are not necessarily what you would expect them to be! See the following: 110 | 111 | https://github.com/samsonjs/strftime#supported-specifiers 112 | 113 | ## Troubleshooting 114 | 115 | ### My login screen hasn't changed! 116 | 117 | Make sure you have lightdm enabled via systemctl with `systemctl is-enabled lightdm.service`. If it isn't, follow up with: 118 | ``` 119 | sudo systemctl enable lightdm.service 120 | ``` 121 | 122 | ### My screen is black! 123 | 124 | Verify that your libgl / glx drivers are properly installed. Find any potential issues with your X config by switching to another TTY with Ctrl+Alt+F2 and trying: 125 | ``` 126 | sudo cat /var/log/Xorg.0.log | grep -i "glx" 127 | ``` 128 | 129 | Are you able to run `glxinfo` without errors? 130 | 131 | ### My system hangs at the boot screen! 132 | 133 | Switch to another TTY with Ctrl+Alt+F2 and check your lightdm logs by running: 134 | ``` 135 | sudo tail /var/log/lightdm/seat0-greeter.log 136 | ``` 137 | 138 | If you see something similar to: 139 | ``` 140 | *** (lightdm:709): CRITICAL **: session_get_login1_session_id: assertion 'session != NULL' failed 141 | ``` 142 | 143 | Then you should try re-installing and / or reconfiguring your graphics drivers, especially if this occurred after a kernel update. 144 | 145 | ### The lock screen isn't using my lightdm theme! 146 | 147 | If you are using cinnamon, gnome, or any gnome derivative; Good Luck. The solution involves [light-locker (community/light-locker)](https://github.com/the-cavalry/light-locker), but conflicts with the existing lock / screensaver applications. There is no known way to resolve this. 148 | 149 | If you are not using a gnome derivative, see below. 150 | 151 | Solution: 152 | 153 | ``` 154 | echo "light-locker &" >> ~/.xprofile 155 | ``` 156 | 157 | ## Development 158 | 159 | Make sure you have [Node](https://nodejs.org/en/) installed. 160 | 161 | - `npm install` *(While in project directory)* 162 | 163 | ### Running Tests 164 | ``` 165 | npm run test 166 | ``` 167 | 168 | ### Building Project 169 | ``` 170 | npm run build 171 | ``` 172 | 173 | ### Monitoring Changes 174 | ``` 175 | npm run watch 176 | ``` 177 | 178 | ##### Credit 179 | - *Bear by Yu luck from the Noun Project* 180 | - *Power by Nikita Kozin from the Noun Project* 181 | - *Arrow by Landan Lloyd from the Noun Project* 182 | - Implements [Draggable](https://github.com/bcherny/draggable) by [bcherny](https://github.com/bcherny) 183 | - Implements [React-Color](https://github.com/casesandberg/react-color) by [bcherny](https://github.com/casesandberg) 184 | -------------------------------------------------------------------------------- /dist/js/draggable.min.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | !function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define([],b):a.Draggable=b()}(this,function(){"use strict";function a(a,b){var c=this,d=k.bind(c.start,c),e=k.bind(c.drag,c),g=k.bind(c.stop,c);if(!f(a))throw new TypeError("Draggable expects argument 0 to be an Element");b=k.assign({},i,b),k.assign(c,{element:a,handle:b.handle&&f(b.handle)?b.handle:a,handlers:{start:{mousedown:d,touchstart:d},move:{mousemove:e,mouseup:g,touchmove:e,touchend:g}},options:b}),c.initialize()}function b(a){return parseInt(a,10)}function c(a){return"currentStyle"in a?a.currentStyle:getComputedStyle(a)}function d(a){return a instanceof Array}function e(a){return void 0!==a&&null!==a}function f(a){return a instanceof Element||"undefined"!=typeof HTMLDocument&&a instanceof HTMLDocument}function g(a){return a instanceof Function}function h(){}var i={grid:0,filterTarget:null,limit:{x:null,y:null},threshold:0,setCursor:!1,setPosition:!0,smoothDrag:!0,useGPU:!0,onDrag:h,onDragStart:h,onDragEnd:h},j={transform:function(){for(var a=" -o- -ms- -moz- -webkit-".split(" "),b=document.body.style,c=a.length;c--;){var d=a[c]+"transform";if(d in b)return d}}()},k={assign:function(){for(var a=arguments[0],b=arguments.length,c=1;b>c;c++){var d=arguments[c];for(var e in d)a[e]=d[e]}return a},bind:function(a,b){return function(){a.apply(b,arguments)}},on:function(a,b,c){if(b&&c)k.addEvent(a,b,c);else if(b)for(var d in b)k.addEvent(a,d,b[d])},off:function(a,b,c){if(b&&c)k.removeEvent(a,b,c);else if(b)for(var d in b)k.removeEvent(a,d,b[d])},limit:function(a,b){return d(b)?(b=[+b[0],+b[1]],ab[1]&&(a=b[1])):a=+b,a},addEvent:"attachEvent"in Element.prototype?function(a,b,c){a.attachEvent("on"+b,c)}:function(a,b,c){a.addEventListener(b,c,!1)},removeEvent:"attachEvent"in Element.prototype?function(a,b,c){a.detachEvent("on"+b,c)}:function(a,b,c){a.removeEventListener(b,c)}};return k.assign(a.prototype,{setOption:function(a,b){var c=this;return c.options[a]=b,c.initialize(),c},get:function(){var a=this.dragEvent;return{x:a.x,y:a.y}},set:function(a,b){var c=this,d=c.dragEvent;return d.original={x:d.x,y:d.y},c.move(a,b),c},dragEvent:{started:!1,x:0,y:0},initialize:function(){var a,b=this,d=b.element,e=(b.handle,d.style),f=c(d),g=b.options,h=j.transform,i=b._dimensions={height:d.offsetHeight,left:d.offsetLeft,top:d.offsetTop,width:d.offsetWidth};g.useGPU&&h&&(a=f[h],"none"===a&&(a=""),e[h]=a+" translate3d(0,0,0)"),g.setPosition&&(e.display="block",e.left=i.left+"px",e.top=i.top+"px",e.bottom=e.right="auto",e.margin=0,e.position="absolute"),g.setCursor&&(e.cursor="move"),b.setLimit(g.limit),k.assign(b.dragEvent,{x:i.left,y:i.top}),k.on(b.handle,b.handlers.start)},start:function(a){var b=this,c=b.getCursor(a),d=b.element;b.useTarget(a.target||a.srcElement)&&(a.preventDefault?a.preventDefault():a.returnValue=!1,b.dragEvent.oldZindex=d.style.zIndex,d.style.zIndex=1e4,b.setCursor(c),b.setPosition(),b.setZoom(),k.on(document,b.handlers.move))},drag:function(a){var b=this,c=b.dragEvent,d=b.element,e=b._cursor,f=b._dimensions,g=b.options,h=f.zoom,i=b.getCursor(a),j=g.threshold,k=(i.x-e.x)/h+f.left,l=(i.y-e.y)/h+f.top;!c.started&&j&&Math.abs(e.x-i.x) 2 | 3 | 4 | 5 | Amazing Content 6 | 7 | 35 | 36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 | 47 | 51 | 52 | 53 | 54 |
55 | 56 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /index.theme: -------------------------------------------------------------------------------- 1 | [theme] 2 | name=Aether 3 | description=Aether, an Archlinux LightDM Theme. 4 | engine=lightdm-webkit-greeter 5 | url=index.html 6 | session=cinnamon 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightdm-webkit-aether", 3 | "version": "1.0.0", 4 | "description": "Elegant LightDM Webkit theme.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/eslint src/es6/**", 8 | "build": "./node_modules/.bin/webpack-cli --progress --colors --env.production", 9 | "watch": "./node_modules/.bin/webpack-cli --progress --colors --watch --env.development" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/noisek/Aether.git" 14 | }, 15 | "keywords": [ 16 | "webkit", 17 | "aether", 18 | "lightdm", 19 | "login", 20 | "manager", 21 | "archlinux", 22 | "arch" 23 | ], 24 | "author": "Noi Sek", 25 | "license": "GPL-3.0", 26 | "bugs": { 27 | "url": "https://github.com/noisek/Aether/issues" 28 | }, 29 | "homepage": "https://github.com/noisek/Aether#readme", 30 | "dependencies": { 31 | "@babel/cli": "^7.2.3", 32 | "@babel/core": "^7.2.2", 33 | "@babel/preset-env": "^7.2.3", 34 | "@babel/preset-react": "^7.0.0", 35 | "autoprefixer": "^9.4.5", 36 | "babel-loader": "^8.0.5", 37 | "babel-plugin-jsx-control-statements": "^4.0.0", 38 | "babel-plugin-lodash": "^3.3.4", 39 | "css-loader": "^2.1.0", 40 | "cssnano": "^4.1.8", 41 | "cxs": "^6.2.0", 42 | "eslint": "~5.12.1", 43 | "eslint-loader": "^2.1.1", 44 | "eslint-plugin-jsx-control-statements": "^2.2.1", 45 | "eslint-plugin-react": "^7.12.4", 46 | "jquery": "^3.3.1", 47 | "node-sass": "^4.11.0", 48 | "pixi.js": "^4.8.5", 49 | "postcss-loader": "^3.0.0", 50 | "prop-types": "^15.6.2", 51 | "react": "^16.7.0", 52 | "react-color": "^2.17.0", 53 | "react-dom": "^16.7.0", 54 | "react-redux": "^6.0.0", 55 | "reactcss": "^1.2.3", 56 | "redux": "~4.0.1", 57 | "sass-loader": "^7.1.0", 58 | "strftime": "^0.10.0", 59 | "style-loader": "^0.23.1", 60 | "svg-inline-loader": "^0.8.0", 61 | "tinycolor2": "^1.4.1", 62 | "webpack": "~4.28.4", 63 | "webpack-cli": "^3.2.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/css/style.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "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", 4 | "sources": ["../sass/_reset.sass","../sass/_fonts.sass","../sass/_scroll.sass","../sass/_utility.sass","../sass/_animations.sass","../sass/style.sass","../sass/_config.sass"], 5 | "names": [], 6 | "file": "style.css" 7 | } -------------------------------------------------------------------------------- /src/font/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/font/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /src/font/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/font/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /src/font/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/font/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /src/font/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/font/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /src/img/arch-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/arch-logo.png -------------------------------------------------------------------------------- /src/img/arrow.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/img/avatar-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/avatar-background.png -------------------------------------------------------------------------------- /src/img/default-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/default-user.png -------------------------------------------------------------------------------- /src/img/dropdown-caret.svg: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /src/img/dropdown-carext.svg: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /src/img/gl/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/gl/smoke.png -------------------------------------------------------------------------------- /src/img/gl/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/gl/spark.png -------------------------------------------------------------------------------- /src/img/hibernate.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/img/logos/antergos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/logos/antergos.png -------------------------------------------------------------------------------- /src/img/logos/archlinux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/logos/archlinux.png -------------------------------------------------------------------------------- /src/img/logos/debian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/logos/debian.png -------------------------------------------------------------------------------- /src/img/logos/generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/logos/generic.png -------------------------------------------------------------------------------- /src/img/logos/tux-silhouette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/logos/tux-silhouette.png -------------------------------------------------------------------------------- /src/img/logos/tux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/logos/tux.png -------------------------------------------------------------------------------- /src/img/logos/ubuntu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/logos/ubuntu.png -------------------------------------------------------------------------------- /src/img/reboot.svg: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /src/img/shutdown.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/img/sleep.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/img/wallpapers/abstract.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/abstract.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/boko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/boko.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/car.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/car.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/cavern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/cavern.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/dark-grass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/dark-grass.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/frosted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/frosted.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/jet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/jet.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/mountains-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/mountains-1.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/mountains-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/mountains-2.png -------------------------------------------------------------------------------- /src/img/wallpapers/mountains-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/mountains-3.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/night.png -------------------------------------------------------------------------------- /src/img/wallpapers/ocean.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/ocean.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/paper-lanterns.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/paper-lanterns.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/pier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/pier.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/sanfran.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/sanfran.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/space-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/space-1.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/space-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/space-2.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/space-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/space-3.jpg -------------------------------------------------------------------------------- /src/img/wallpapers/tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/img/wallpapers/tree.jpg -------------------------------------------------------------------------------- /src/js/Components/DateDisplay/Main.jsx: -------------------------------------------------------------------------------- 1 | // DateDisplay -> Required by Main 2 | // -------------------------------------- 3 | // Displays date below the login window. 4 | 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import Strftime from 'strftime'; 8 | import PropTypes from 'prop-types'; 9 | 10 | import { connect } from 'react-redux'; 11 | 12 | 13 | class DateDisplay extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | "initialized": false, 19 | "formattedDate": "", 20 | }; 21 | } 22 | 23 | 24 | componentDidMount() { 25 | // Wait two seconds, so that the clock can render first and they fade in sequentially rather than in parallel. 26 | setTimeout(() => { 27 | this.setDate(); 28 | }, 2000); 29 | } 30 | 31 | 32 | setDate() { 33 | this.setState({ 34 | "initialized": true, 35 | "formattedDate": Strftime(this.props.settings.date_format) 36 | }); 37 | 38 | setTimeout(() => { 39 | this.setDate(); 40 | }, 1000); 41 | } 42 | 43 | 44 | render() { 45 | let dateClasses = ['date']; 46 | let dateString = this.state.formattedDate; 47 | 48 | if (this.state.initialized === true && this.props.settings.date_enabled === true) { 49 | dateClasses.push('loaded'); 50 | } else if (this.state.date_enabled === false) { 51 | dateClasses.push('invisible'); 52 | } 53 | 54 | return ReactDOM.createPortal( 55 |
, 56 | document.getElementById("date-display") 57 | ); 58 | } 59 | } 60 | 61 | 62 | DateDisplay.propTypes = { 63 | 'settings': PropTypes.object.isRequired 64 | }; 65 | 66 | 67 | export default connect( 68 | (state) => { 69 | return { 70 | 'settings': state.settings 71 | }; 72 | }, 73 | null 74 | )(DateDisplay); 75 | -------------------------------------------------------------------------------- /src/js/Components/ExperimentalStars/Main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import PropTypes from 'prop-types'; 4 | import * as PIXI from 'pixi.js'; 5 | 6 | import { connect } from 'react-redux'; 7 | 8 | import Particle from './Particle'; 9 | 10 | import { interpolatePoints, scale, randomRange } from 'Utils/Utils'; 11 | 12 | 13 | const GradientGenerator = (start, end) => { 14 | const calculatedColors = {}; 15 | 16 | let RGBStart = PIXI.utils.hex2rgb(start); 17 | let RGBEnd = PIXI.utils.hex2rgb(end); 18 | 19 | for (let i = 0; i < 100; i++) { 20 | let percentage = i / 100; 21 | 22 | let [ A_R, A_G, A_B ] = RGBStart; 23 | let [ B_R, B_G, B_B ] = RGBEnd; 24 | 25 | let thisColor = [ 26 | scale(A_R, B_R, percentage), 27 | scale(A_G, B_G, percentage), 28 | scale(A_B, B_B, percentage) 29 | ]; 30 | 31 | calculatedColors[i] = PIXI.utils.rgb2hex(thisColor); 32 | } 33 | 34 | return (percentage) => { 35 | return calculatedColors[Math.floor(percentage * 100)]; 36 | }; 37 | }; 38 | 39 | 40 | class ExperimentalStars extends React.Component { 41 | constructor(props) { 42 | super(props); 43 | 44 | this.state = {}; 45 | this.nodes = {}; 46 | 47 | // Pixi objects 48 | this.application = undefined; 49 | this.stars = []; 50 | this.sparks = []; 51 | 52 | this.gradientGenerator = undefined; 53 | } 54 | 55 | 56 | componentDidMount() { 57 | this.application = new PIXI.Application({ 58 | 'height': window.innerHeight, 59 | 'width': window.innerWidth, 60 | 'transparent': true, 61 | 'resolution': this.props.settings.page_zoom 62 | }); 63 | 64 | this.application.autoResize = true; 65 | 66 | this.nodes.screen.appendChild(this.application.view); 67 | 68 | setTimeout(() => { 69 | this.startAnimation(); 70 | }, 1000); 71 | } 72 | 73 | 74 | startAnimation() { 75 | let circle = new PIXI.Graphics(); 76 | 77 | circle.beginFill(0xFFFFFF, 1); 78 | circle.drawCircle(1, 1, 1); 79 | circle.cacheAsBitmap = true; 80 | 81 | const starCount = 4; 82 | const sparkCount = 100; // Per star 83 | const sparkMinDecay = 750; // milliseconds 84 | const sparkMaxDecay = 1250; // milliseconds 85 | const sparkStartScale = 0.001; 86 | const sparkEndScale = 0.1; 87 | const startColor = 0xd426e0; 88 | const endColor = 0xed7b39; 89 | 90 | this.gradientGenerator = GradientGenerator(startColor, endColor); 91 | 92 | // Generate stars 93 | for (let i = 0; i < starCount; i++) { 94 | let circleInstance = circle.clone(); 95 | 96 | circleInstance.blendMode = PIXI.BLEND_MODES.SCREEN; 97 | 98 | circleInstance.recycle = () => { 99 | circleInstance.alpha = randomRange(0.7, 1, 2); 100 | 101 | circleInstance.x = -20; 102 | circleInstance.y = -20; 103 | circleInstance.startX = randomRange(0, window.innerWidth - (window.innerWidth * 0.2)); 104 | circleInstance.startY = randomRange(0, window.innerHeight - (window.innerHeight * 0.2)); 105 | circleInstance.lastX = circleInstance.x; 106 | circleInstance.lastY = circleInstance.y; 107 | 108 | circleInstance.deathY = randomRange(circleInstance.y + (window.innerHeight * 0.10), window.innerHeight); 109 | circleInstance.startTime = Number(new Date()) + randomRange(1000, 8000); 110 | 111 | circleInstance.velocity = { 112 | 'x': -(randomRange(1, 2, 2)), 113 | 'y': randomRange(2, 4, 2) 114 | }; 115 | }; 116 | 117 | circleInstance.interpolate = (ratio, stretch=0) => { 118 | return interpolatePoints( 119 | { 120 | 'x': circleInstance.lastX - (circleInstance.velocity.x * stretch), 121 | 'y': circleInstance.lastY - (circleInstance.velocity.y * stretch) 122 | }, 123 | { 124 | 'x': circleInstance.x, 125 | 'y': circleInstance.y 126 | }, 127 | ratio 128 | ); 129 | }; 130 | 131 | circleInstance.recycle(); 132 | 133 | this.stars.push(circleInstance); 134 | this.application.stage.addChild(circleInstance); 135 | } 136 | 137 | 138 | // Generate sparks 139 | let sparkTexture = PIXI.Texture.fromImage('src/img/gl/spark.png'); 140 | 141 | for (let i = 0; i < starCount; i++) { 142 | // Generate a new self managing particle instance 143 | let options = { 144 | 'parent': this.stars[i], 145 | 'startColor': startColor, 146 | 'endColor': endColor, 147 | 'minDecay': sparkMinDecay, 148 | 'maxDecay': sparkMaxDecay, 149 | 'startScale': sparkStartScale, 150 | 'endScale': sparkEndScale, 151 | 'gradientGenerator': this.gradientGenerator 152 | }; 153 | 154 | for (let _i = 0; _i < sparkCount; _i++) { 155 | this.sparks[i] = this.sparks[i] || []; 156 | this.sparks[i].push(new Particle(sparkTexture, this.application, options)); 157 | } 158 | } 159 | 160 | //let smoke = PIXI.Sprite.fromImage('src/img/gl/smoke.png'); 161 | 162 | this.application.ticker.add(() => { 163 | const now = Number(new Date()); 164 | 165 | for (let i = 0; i < this.stars.length; i++) { 166 | let currentItem = this.stars[i]; 167 | let star = currentItem; 168 | 169 | if (now > star.startTime) { 170 | if (star.alpha == 0) { 171 | star.alpha = randomRange(0.5, 1, 2); 172 | } 173 | 174 | if (star.x == -20) { 175 | star.x = star.startX; 176 | star.y = star.startY; 177 | } 178 | 179 | star.lastX = star.x; 180 | star.lastY = star.y; 181 | star.x += star.velocity.x; 182 | star.y += star.velocity.y; 183 | 184 | if (star.x < -10 || star.y > star.deathY) { 185 | star.recycle(); 186 | } 187 | } else { 188 | star.alpha = 0; 189 | } 190 | } 191 | }); 192 | } 193 | 194 | 195 | render() { 196 | return ReactDOM.createPortal( 197 |
this.nodes.screen = ref } />, 198 | document.getElementById("experimental-mount") 199 | ); 200 | } 201 | } 202 | 203 | 204 | ExperimentalStars.propTypes = { 205 | 'settings': PropTypes.object.isRequired 206 | }; 207 | 208 | 209 | export default connect( 210 | (state) => { 211 | return { 212 | 'settings': state.settings 213 | }; 214 | }, 215 | null 216 | )(ExperimentalStars); 217 | -------------------------------------------------------------------------------- /src/js/Components/ExperimentalStars/Particle.js: -------------------------------------------------------------------------------- 1 | import { scale, randomRange } from 'Utils/Utils'; 2 | import * as PIXI from 'pixi.js'; 3 | 4 | 5 | export default class Particle { 6 | constructor(texture, application, options) { 7 | this.sprite = new PIXI.Sprite(texture); 8 | this.application = application; 9 | this.options = options; 10 | 11 | this.birthDate = undefined; 12 | this.recycle(); 13 | 14 | // Initialization 15 | this.sprite.anchor.set(0, 1); 16 | this.application.stage.addChild(this.sprite); 17 | this.application.ticker.add(this.step.bind(this)); 18 | } 19 | 20 | 21 | recycle() { 22 | const parentVelocity = this.options.parent.velocity; 23 | 24 | // Reset Particle 25 | this.birthDate = Number(new Date()); 26 | this.velocity = [ -(parentVelocity.x) / 100, -(parentVelocity.y) / 100 ]; 27 | this.lifetime = randomRange(this.options.minDecay, this.options.maxDecay, 0); 28 | this.elapsed = 0; 29 | 30 | // Reset Sprite 31 | this.sprite.scale.set(this.options.startScale); 32 | this.sprite.alpha = 0; 33 | this.sprite.tint = this.options.startColor; 34 | this.sprite.rotation = randomRange(-10, 10, 0) * Math.PI / 180; 35 | 36 | let ratio = randomRange(0.5, 1, 2); 37 | let interpolatedPosition = this.options.parent.interpolate(ratio, 1.25); 38 | 39 | this.sprite.x = interpolatedPosition.x + 2; 40 | this.sprite.y = interpolatedPosition.y; 41 | } 42 | 43 | 44 | step() { 45 | this.elapsed += this.application.ticker.elapsedMS; 46 | 47 | if (this.elapsed >= this.lifetime) { 48 | this.recycle(); 49 | } else { 50 | const percentage = (this.elapsed / this.lifetime); 51 | 52 | let scaledSize = scale(this.options.startScale, this.options.endScale, percentage, false); 53 | this.sprite.scale.set(scaledSize); 54 | 55 | this.sprite.x += this.velocity[0]; 56 | this.sprite.y += this.velocity[1]; 57 | 58 | let scaledPercentage = scale(0, 3.14, percentage, false); 59 | let rawAlpha = Math.sin(scaledPercentage); 60 | let scaledAlpha = scale(0, 0.15, rawAlpha, false); 61 | 62 | this.sprite.alpha = scaledAlpha; 63 | 64 | this.sprite.tint = this.options.gradientGenerator(percentage); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/Main.jsx: -------------------------------------------------------------------------------- 1 | // LoginWindow -> Required by Main 2 | // -------------------------------------- 3 | // Style / Composition wrapper. 4 | 5 | import cxs from 'cxs'; 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | import Sidebar from './Sidebar'; 12 | import UserPicker from './UserPicker'; 13 | import Settings from 'Components/Settings'; 14 | import DateDisplay from 'Components/DateDisplay'; 15 | import SettingsToggler from 'Components/SettingsToggler'; 16 | import ExperimentalStars from 'Components/ExperimentalStars'; 17 | 18 | 19 | class LoginWindow extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | } 23 | 24 | 25 | componentDidMount() { 26 | document.getElementById('preloader').className += 'loaded'; 27 | } 28 | 29 | 30 | render() { 31 | const settings = this.props.settings; 32 | 33 | let style = cxs({ 34 | "border-radius": settings.window_border_radius, 35 | "font-size": settings.window_font_size 36 | }); 37 | 38 | return [ 39 |
40 | 41 | 42 |
, 43 | 44 | , 45 | , 46 | , 47 | 48 | 49 | , 50 | ]; 51 | } 52 | } 53 | 54 | 55 | LoginWindow.propTypes = { 56 | 'settings': PropTypes.object.isRequired, 57 | }; 58 | 59 | 60 | export default connect( 61 | (state) => { 62 | return { 63 | 'settings': state.settings 64 | }; 65 | }, 66 | null 67 | )(LoginWindow); 68 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/Sidebar/Clock.jsx: -------------------------------------------------------------------------------- 1 | // Clock -> Required by Components/CommandPanel 2 | // -------------------------------------- 3 | // Just a clock. 4 | 5 | import React from 'react'; 6 | import Strftime from 'strftime'; 7 | import PropTypes from 'prop-types'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | 12 | class Clock extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | this.state = { 17 | "initialized": false, 18 | "formattedTime": "", 19 | }; 20 | } 21 | 22 | 23 | componentDidMount() { 24 | setTimeout(() => { 25 | this.updateClock(); 26 | this.setState({ 27 | "initialized": true 28 | }); 29 | }, 1000); 30 | } 31 | 32 | 33 | updateClock() { 34 | this.setState({ 35 | "formattedTime": Strftime(this.props.settings.time_format) 36 | }); 37 | 38 | setTimeout(() => { 39 | this.updateClock(); 40 | }, 1000); 41 | } 42 | 43 | 44 | render() { 45 | let classes = ['right', 'clock']; 46 | let currentTime = this.state.formattedTime; 47 | 48 | if (this.state.initialized === true && this.props.settings.time_enabled === true) { 49 | classes.push('loaded'); 50 | } else if (this.props.settings.time_enabled === false) { 51 | classes.push('invisible'); 52 | } 53 | 54 | return ( 55 |
56 | { currentTime } 57 |
58 | ); 59 | } 60 | } 61 | 62 | 63 | Clock.propTypes = { 64 | 'settings': PropTypes.object.isRequired 65 | }; 66 | 67 | 68 | export default connect( 69 | (state) => { 70 | return { 71 | 'settings': state.settings 72 | }; 73 | }, 74 | null 75 | )(Clock); 76 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/Sidebar/Item.jsx: -------------------------------------------------------------------------------- 1 | // CommandItem -> Required by Components/CommandPanel/CommandList 2 | // -------------------------------------- 3 | // CommandList item. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | import cxs from 'cxs'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | 12 | export const SVGMap = { 13 | 'hibernate': require('img/hibernate.svg'), 14 | 'reboot': require('img/reboot.svg'), 15 | 'shutdown': require('img/shutdown.svg'), 16 | 'sleep': require('img/sleep.svg') 17 | }; 18 | 19 | 20 | export const Item = ({ command, handleCommand, settings }) => { 21 | let disabled = command.toLowerCase().split('.')[1] || false; 22 | command = command.toLowerCase().split('.')[0]; 23 | 24 | let classes = ['command', command, disabled].filter((e) => e); 25 | let iconWrapperClasses = ['icon-wrapper']; 26 | 27 | if (settings.style_command_icons_enabled === false) { 28 | iconWrapperClasses.push('hidden'); 29 | } 30 | 31 | let iconStyle = cxs({ 32 | "color": settings.style_command_icon_color 33 | }); 34 | 35 | iconWrapperClasses.push(iconStyle); 36 | 37 | let textStyle = cxs({ 38 | "color": settings.style_command_text_color, 39 | "text-align": settings.style_command_text_align 40 | }); 41 | 42 | return ( 43 |
44 |
45 |
{ command }
46 |
47 | ); 48 | }; 49 | 50 | 51 | Item.propTypes = { 52 | 'command': PropTypes.string.isRequired, 53 | 'handleCommand': PropTypes.func.isRequired, 54 | 'settings': PropTypes.object.isRequired 55 | }; 56 | 57 | 58 | export default connect( 59 | (state) => { 60 | return { 61 | 'settings': state.settings 62 | }; 63 | }, 64 | null 65 | )(Item); 66 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/Sidebar/List.jsx: -------------------------------------------------------------------------------- 1 | // CommandList -> Required by CommandPanel 2 | // -------------------------------------- 3 | // Displays system commands. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | 8 | import Item from './Item'; 9 | 10 | 11 | export const List = ({ enabledCommands, handleCommand }) => { 12 | let items = enabledCommands.map((command) => 13 | 18 | ); 19 | 20 | return ( 21 |
22 | { items } 23 |
24 | ); 25 | }; 26 | 27 | 28 | List.propTypes = { 29 | 'enabledCommands': PropTypes.arrayOf(PropTypes.string).isRequired, 30 | 'handleCommand': PropTypes.func.isRequired, 31 | }; 32 | 33 | 34 | export default List; 35 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/Sidebar/Main.jsx: -------------------------------------------------------------------------------- 1 | // CommandPanel -> Required by Main 2 | // -------------------------------------- 3 | // The system management half of the greeter logic. 4 | // Displays system info and handles Sleep, Shutdown, etc. 5 | 6 | import cxs from 'cxs'; 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | 10 | import { connect } from 'react-redux'; 11 | 12 | import * as SystemOperations from 'Logic/SystemOperations'; 13 | import WallpaperSwitcher from './WallpaperSwitcher'; 14 | import Clock from './Clock'; 15 | import List from './List'; 16 | 17 | 18 | class Sidebar extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | } 22 | 23 | 24 | handleCommand(command, disabled, event) { 25 | event.preventDefault(); 26 | 27 | if (disabled !== false) { 28 | window.notifications.generate(`${ command } is disabled on this system.`, "error"); 29 | return false; 30 | } 31 | 32 | SystemOperations.handleCommand(command); 33 | } 34 | 35 | 36 | getEnabledCommands() { 37 | let commands = { 38 | "Shutdown": (window.lightdm.can_shutdown && this.props.settings.command_shutdown_enabled), 39 | "Reboot": (window.lightdm.can_restart && this.props.settings.command_reboot_enabled), 40 | "Hibernate": (window.lightdm.can_hibernate && this.props.settings.command_hibernate_enabled), 41 | "Sleep": (window.lightdm.can_suspend && this.props.settings.command_sleep_enabled) 42 | }; 43 | 44 | // Filter out commands we can't execute. 45 | let enabledCommands = ( 46 | Object.keys(commands) 47 | .map((key) => commands[key] ? key : false) 48 | .filter((command) => command !== false) 49 | ); 50 | 51 | // Are both hibernation and suspend disabled? 52 | // Add the row back and disable it so that the user is aware of what's happening. 53 | if (window.lightdm.can_suspend === false && window.lightdm.can_hibernate === false) { 54 | enabledCommands.push("Sleep.disabled"); 55 | } 56 | 57 | return enabledCommands; 58 | } 59 | 60 | 61 | render() { 62 | let settings = this.props.settings; 63 | 64 | let hostname = window.lightdm.hostname; 65 | let hostnameClasses = ['left', 'hostname']; 66 | let hostNameDisabled = (settings.hostname_enabled === false); 67 | 68 | let commands = this.getEnabledCommands(); 69 | 70 | if (hostNameDisabled) { 71 | hostnameClasses.push('invisible'); 72 | } 73 | 74 | let styles = cxs({ 75 | 'background': settings.style_command_background_color 76 | }); 77 | 78 | return ( 79 |
80 | 81 | 85 |
86 |
{ hostname }
87 | 88 |
89 |
90 | ); 91 | } 92 | } 93 | 94 | 95 | Sidebar.propTypes = { 96 | 'settings': PropTypes.object.isRequired 97 | }; 98 | 99 | 100 | export default connect( 101 | (state) => { 102 | return { 103 | 'settings': state.settings 104 | }; 105 | }, 106 | null 107 | )(Sidebar); 108 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/Sidebar/WallpaperSwitcher.jsx: -------------------------------------------------------------------------------- 1 | // WallpaperSwitcher -> Required by Components/CommandPanel 2 | // -------------------------------------- 3 | // Serves to handle wallpaper switching through DOM manipulation. 4 | 5 | import cxs from 'cxs'; 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | 12 | import * as FileOperations from 'Logic/FileOperations'; 13 | import * as Settings from 'Logic/Settings'; 14 | 15 | const FADEOUT_TIME = 600; 16 | 17 | 18 | class WallpaperSwitcher extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | 22 | let wallpaperDirectory = FileOperations.getWallpaperDirectory(); 23 | let wallpapers = FileOperations.getWallpapers(wallpaperDirectory); 24 | 25 | this.defaultStarsEnabled = undefined; 26 | 27 | this.cyclerBackground = undefined; 28 | this.cyclerForeground = undefined; 29 | this.cyclerPreloader = undefined; 30 | 31 | this.state = { 32 | "directory": wallpaperDirectory, 33 | "wallpapers": wallpapers, 34 | "selectedWallpaper": undefined, 35 | "savedWallpaper": undefined, 36 | "switcher": { 37 | "active": false, 38 | "currentlyFading": false, 39 | "index": 0 40 | }, 41 | }; 42 | } 43 | 44 | 45 | componentDidMount() { 46 | // Set background wallpaper 47 | let directory = this.state.directory; 48 | let image = Settings.requestSetting('wallpaper', 'space-1.jpg'); 49 | this.cyclerBackground = document.querySelectorAll('.wallpaper-background')[0]; 50 | this.cyclerForeground = document.querySelectorAll('.wallpaper-foreground')[0]; 51 | this.cyclerPreloader = document.querySelectorAll('.wallpaper-preload')[0]; 52 | 53 | this.cyclerForeground.style.background = `url('${ directory }${ image }')`; 54 | this.cyclerForeground.style.backgroundPosition = 'center'; 55 | this.cyclerForeground.style.backgroundSize = "cover"; 56 | document.body.style.background = `url('${ directory }${ image }')`; 57 | document.body.style.backgroundPosition = 'center'; 58 | document.body.style.backgroundSize = "cover"; 59 | 60 | this.setState({ 61 | "savedWallpaper": image 62 | }); 63 | } 64 | 65 | 66 | acceptWallpaper() { 67 | let selectedWallpaper = this.state.selectedWallpaper; 68 | let switcher = this.state.switcher; 69 | 70 | // Due diligence. 71 | Settings.saveSetting('wallpaper', selectedWallpaper); 72 | window.notifications.generate("This wallpaper has been saved as your default background.", 'success'); 73 | 74 | // Reset switcher state 75 | switcher.active = false; 76 | switcher.index = 0; 77 | 78 | this.setState({ 79 | "selectedWallpaper": selectedWallpaper, 80 | "savedWallpaper": selectedWallpaper, 81 | "switcher": switcher 82 | }); 83 | 84 | this.restoreStarsSetting(); 85 | } 86 | 87 | 88 | cycleWallpaper() { 89 | // Prevent animation transitions stacking and causing issues. 90 | if (this.state.switcher.currentlyFading === true) { 91 | return false; 92 | } 93 | 94 | let wallpapers = this.state.wallpapers; 95 | let switcher = this.state.switcher; 96 | 97 | const nextIndex = (index) => (index + wallpapers.length + 1) % wallpapers.length; 98 | 99 | let newIndex = nextIndex(switcher.index); 100 | let newWallpaper = wallpapers[newIndex]; 101 | 102 | let preloadedIndex = nextIndex(newIndex); 103 | let preloadedWallpaper = wallpapers[preloadedIndex]; 104 | 105 | this.setWallpaper(newWallpaper, preloadedWallpaper); 106 | 107 | switcher.index = newIndex; 108 | 109 | this.setState({ 110 | "switcher": switcher 111 | }); 112 | } 113 | 114 | 115 | handleSwitcherActivation() { 116 | if (this.state.switcher.active === false) { 117 | this.defaultStarsEnabled = this.props.starsEnabled; 118 | 119 | this.props.dispatch({ 120 | 'type': 'SETTINGS_SET_VALUE', 121 | 'name': 'experimental_stars_enabled', 122 | 'value': false 123 | }); 124 | } 125 | 126 | setTimeout(() => { 127 | let switcher = this.state.switcher; 128 | switcher.active = true; 129 | this.cycleWallpaper(); 130 | 131 | this.setState({ 132 | "switcher": switcher 133 | }); 134 | }, 100); 135 | } 136 | 137 | 138 | rejectWallpaper() { 139 | let savedWallpaper = this.state.savedWallpaper; 140 | let switcher = this.state.switcher; 141 | 142 | // Reset switcher state 143 | switcher.active = false; 144 | switcher.index = 0; 145 | 146 | this.setState({ 147 | "switcher": switcher 148 | }); 149 | 150 | this.setWallpaper(savedWallpaper); 151 | this.restoreStarsSetting(); 152 | 153 | window.notifications.generate("Wallpaper reset to default, no changes saved."); 154 | } 155 | 156 | 157 | setWallpaper(newWallpaper, preloadedWallpaper=false) { 158 | let switcher = this.state.switcher; 159 | 160 | // Fadeout foreground wallpaper to new wallpaper 161 | let directory = this.state.directory; 162 | this.cyclerBackground.style.background = `url('${ directory }${ newWallpaper }')`; 163 | this.cyclerBackground.style.backgroundPosition = 'center'; 164 | this.cyclerBackground.style.backgroundSize = 'cover'; 165 | this.cyclerForeground.className += " fadeout"; 166 | 167 | switcher.currentlyFading = true; 168 | 169 | if (preloadedWallpaper !== false) { 170 | // Preload the next image 171 | this.cyclerPreloader.style.background = `url('${ directory }${ preloadedWallpaper }')`; 172 | } 173 | 174 | setTimeout(() => { 175 | // Cycle new wallpaper back to the front, make it visible again. 176 | this.cyclerForeground.style.background = `url('${ directory }${ newWallpaper }')`; 177 | this.cyclerForeground.style.backgroundPosition = 'center'; 178 | this.cyclerForeground.style.backgroundSize = 'cover'; 179 | this.cyclerForeground.className = this.cyclerForeground.className.replace(" fadeout", ""); 180 | document.body.style.background = `url('${ directory }${ newWallpaper }')`; 181 | document.body.style.backgroundPosition = 'center'; 182 | document.body.style.backgroundSize = 'cover'; 183 | 184 | let switcher = this.state.switcher; 185 | switcher.currentlyFading = false; 186 | 187 | this.setState({ 188 | "selectedWallpaper": newWallpaper, 189 | "switcher": switcher 190 | }); 191 | }, FADEOUT_TIME); 192 | } 193 | 194 | 195 | restoreStarsSetting() { 196 | this.props.dispatch({ 197 | 'type': 'SETTINGS_SET_VALUE', 198 | 'name': 'experimental_stars_enabled', 199 | 'value': this.defaultStarsEnabled 200 | }); 201 | } 202 | 203 | 204 | generateOptions() { 205 | let classes = ['options']; 206 | 207 | if (this.state.switcher.active === true) { 208 | classes.push('active'); 209 | } 210 | 211 | return ( 212 |
213 |
214 |
215 |
216 |
217 |
218 | ); 219 | } 220 | 221 | 222 | render() { 223 | let options = this.generateOptions(); 224 | let classes = [ 'distro-logo' ]; 225 | 226 | classes.push(cxs({ 227 | "background-image": `url(${ this.props.distroImage }) !important`, 228 | })); 229 | 230 | if (this.props.desaturate) { 231 | classes.push(cxs({ 232 | "filter": `grayscale(1) brightness(${ (this.props.brightness * 200) / 100 }%)` 233 | })); 234 | } 235 | 236 | return ( 237 |
238 |
239 | { options } 240 |
241 | ); 242 | } 243 | } 244 | 245 | 246 | WallpaperSwitcher.propTypes = { 247 | 'distroImage': PropTypes.string.isRequired, 248 | 'starsEnabled': PropTypes.bool.isRequired, 249 | 'desaturate': PropTypes.bool.isRequired, 250 | 'brightness': PropTypes.string.isRequired, 251 | 252 | 'dispatch': PropTypes.func.isRequired 253 | }; 254 | 255 | 256 | export default connect( 257 | (state) => { 258 | return { 259 | 'distroImage': state.settings.distro, 260 | 'starsEnabled': state.settings.experimental_stars_enabled, 261 | 'desaturate': state.settings.style_command_logo_desaturate, 262 | 'brightness': state.settings.style_command_logo_brightness 263 | }; 264 | }, 265 | null 266 | )(WallpaperSwitcher); 267 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/UserPicker/Form.jsx: -------------------------------------------------------------------------------- 1 | // UserPanelForm -> Required by Components/UserPanel 2 | // -------------------------------------- 3 | // The form displayed within the User Panel to handle 4 | // user input for the authentication process. 5 | 6 | import PropTypes from 'prop-types'; 7 | import React from 'react'; 8 | import cxs from 'cxs'; 9 | 10 | import { connect } from 'react-redux'; 11 | 12 | import PasswordField from './PasswordField'; 13 | import SessionSwitcher from './SessionSwitcher'; 14 | 15 | 16 | const submitIcon = require('img/arrow.svg'); 17 | const dropdownIcon = require('img/dropdown-caret.svg'); 18 | 19 | 20 | class UserPanelForm extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | 24 | this.state = { 25 | 'sessionSwitcherActive': false, 26 | 'enableAnimation': false 27 | }; 28 | } 29 | 30 | toggleSessionSwitcher() { 31 | this.setState({ 32 | 'sessionSwitcherActive': !this.state.sessionSwitcherActive, 33 | 'enableAnimation': true 34 | }); 35 | } 36 | 37 | render() { 38 | const textTransformDict = { 39 | 'default': 'initial', 40 | 'uppercase': 'uppercase', 41 | 'lowercase': 'lowercase', 42 | 'capitalize': 'capitalize' 43 | }; 44 | 45 | const textTransformStyle = this.props.settings.style_login_username_capitalization; 46 | 47 | let usernameClasses = ['user-username']; 48 | usernameClasses.push(cxs({ 49 | "color": this.props.settings.style_login_username_color, 50 | "font-style": (this.props.settings.style_login_username_italic) ? 'italic' : 'initial', 51 | "font-weight": (this.props.settings.style_login_username_bold) ? 'bold' : 'initial', 52 | "text-transform": textTransformDict[textTransformStyle.toLowerCase()] 53 | })); 54 | 55 | let submitButtonWrapperClasses = cxs({ "background-color": this.props.settings.style_login_button_color }); 56 | 57 | let submitButtonClasses = ['submit-button']; 58 | submitButtonClasses.push(cxs({ 59 | "color": this.props.settings.style_login_button_text_color 60 | })); 61 | 62 | let dropdownCaretWrapperClasses = cxs({ "color": this.props.settings.style_login_button_text_color }); 63 | 64 | let sessionSelectButtonClasses = ['left', 'session-select']; 65 | sessionSelectButtonClasses.push(cxs({ 66 | "background-color": this.props.settings.style_login_button_color, 67 | "color": this.props.settings.style_login_button_text_color 68 | })); 69 | 70 | let inputContainerClasses = ['user-input-container']; 71 | 72 | if (!this.state.enableAnimation) { 73 | inputContainerClasses.push('animation-enabled'); 74 | } 75 | 76 | if (this.state.sessionSwitcherActive) { 77 | inputContainerClasses.push('animate-out'); 78 | } 79 | 80 | return ( 81 |
82 |
{ this.props.activeUser.display_name }
83 |
84 |
85 | 90 |
91 |
92 |
93 |
{ this.props.activeSession.name }
94 |
95 |
96 |
97 |
103 |
104 | 109 | 110 | ); 111 | } 112 | } 113 | 114 | 115 | UserPanelForm.propTypes = { 116 | 'activeUser': PropTypes.object, 117 | 'activeSession': PropTypes.object, 118 | 'settings': PropTypes.object.isRequired, 119 | 120 | 'password': PropTypes.string.isRequired, 121 | 'passwordFailed': PropTypes.bool.isRequired, 122 | 123 | 'handleLoginSubmit': PropTypes.func.isRequired, 124 | 'handlePasswordInput': PropTypes.func.isRequired, 125 | 'setActiveSession': PropTypes.func.isRequired 126 | }; 127 | 128 | 129 | export default connect( 130 | (state) => { 131 | return { 132 | 'activeUser': state.user, 133 | 'activeSession': state.session, 134 | 'settings': state.settings 135 | }; 136 | }, 137 | null 138 | )(UserPanelForm); 139 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/UserPicker/Main.jsx: -------------------------------------------------------------------------------- 1 | // UserPanel -> Required by Main 2 | // -------------------------------------- 3 | // The login management half of the greeter logic. 4 | 5 | import cxs from 'cxs'; 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | import UserSwitchButton from './UserSwitcher/UserSwitchButton'; 12 | import UserSwitcher from './UserSwitcher'; 13 | import UserPanelForm from './Form'; 14 | 15 | const FADE_IN_DURATION = 200; 16 | const ERROR_SHAKE_DURATION = 600; 17 | 18 | const CTRL_KEYCODE = 17; 19 | const A_KEYCODE = 65; 20 | 21 | 22 | class UserPicker extends React.Component { 23 | constructor(props) { 24 | super(props); 25 | 26 | this.state = { 27 | "fadeIn": false, 28 | "password": "", 29 | "passwordFailed": false, 30 | "switcherActive": false, 31 | }; 32 | 33 | this.CTRL_Pressed = false; 34 | this.A_Pressed = false; 35 | } 36 | 37 | 38 | componentDidMount() { 39 | // Define functions required in the global scope by LightDM. 40 | window.show_prompt = (text, type) => { 41 | if (type === 'text') { 42 | window.notifications.generate(text); 43 | } else if (type === 'password') { 44 | window.lightdm.respond(this.state.password); 45 | } 46 | }; 47 | 48 | window.show_message = (text, type) => { 49 | window.notifications.generate(text, type); 50 | }; 51 | 52 | window.authentication_complete = () => { 53 | if (window.lightdm.is_authenticated) { 54 | window.lightdm.start_session_sync(this.props.activeSession.key); 55 | } else { 56 | this.rejectPassword(); 57 | } 58 | }; 59 | 60 | window.autologin_timer_expired = () => { 61 | window.notifications.generate("Autologin expired."); 62 | }; 63 | 64 | // Add a handler for Ctrl+A to prevent selection issues. 65 | document.onkeydown = this.onKeyDown.bind(this); 66 | document.onkeyup = this.onKeyUp.bind(this); 67 | } 68 | 69 | 70 | onKeyDown(e) { 71 | if (e.keyCode === CTRL_KEYCODE) { 72 | this.CTRL_Pressed = true; 73 | } 74 | 75 | if (e.keyCode === A_KEYCODE) { 76 | this.A_Pressed = true; 77 | } 78 | 79 | if (this.CTRL_Pressed && this.A_Pressed) { 80 | if (this.props.settings.active) { 81 | return; 82 | } 83 | 84 | e.preventDefault(); 85 | 86 | let target = document.getElementById('password-field'); 87 | target.focus(); 88 | target.select(); 89 | } 90 | } 91 | 92 | 93 | onKeyUp(e) { 94 | if (e.keyCode === CTRL_KEYCODE) { 95 | this.CTRL_Pressed = false; 96 | } 97 | 98 | if (e.keyCode === A_KEYCODE) { 99 | this.A_Pressed = false; 100 | } 101 | } 102 | 103 | 104 | handleLoginSubmit(event) { 105 | event.preventDefault(); 106 | 107 | if (window.__debug === true) { 108 | if (this.state.password.toLowerCase() !== 'password') { 109 | this.rejectPassword(); 110 | } else { 111 | window.notifications.generate(`You are now logged in as ${ this.props.activeUser.display_name } to ${ this.props.activeSession.name }.`, 'success'); 112 | this.setState({ 113 | "password": "" 114 | }); 115 | } 116 | } 117 | 118 | else { 119 | window.lightdm.authenticate(this.props.activeUser.username || this.props.activeUser.name); 120 | } 121 | } 122 | 123 | 124 | handleSwitcherClick(event) { 125 | if (window.lightdm.users.length < 2) { 126 | window.notifications.generate("You are the only user that is able to log in on this system.", 'error'); 127 | return false; 128 | } else if (window.lightdm.users.length === 2) { 129 | // No point in showing them the switcher if there is only one other user. Switch immediately. 130 | let otherUser = window.lightdm.users.filter((user) => { 131 | return user.username !== this.props.activeUser.username; 132 | })[0]; 133 | 134 | this.setActiveUser(otherUser, true); 135 | window.notifications.generate("User has been automatically switched to the only other user on this system."); 136 | } else { 137 | this.setState({ 138 | "switcherActive": true 139 | }); 140 | } 141 | } 142 | 143 | 144 | handlePasswordInput(event) { 145 | this.setState({ 146 | "password": event.target.value 147 | }); 148 | } 149 | 150 | 151 | setActiveSession(session) { 152 | this.props.dispatch({ 153 | 'type': 'AUTH_SET_ACTIVE_SESSION', 154 | 'session': session 155 | }); 156 | } 157 | 158 | 159 | setActiveUser(user, isBypass) { 160 | this.props.dispatch({ 161 | "type": 'AUTH_SET_ACTIVE_USER', 162 | "user": user 163 | }); 164 | 165 | // Fade in, except when switching between 1 of 2 users. 166 | if (isBypass === false || isBypass === undefined) { 167 | this.setState({ 168 | "fadeIn": true, 169 | "switcherActive": false 170 | }); 171 | 172 | setTimeout(() => { 173 | this.setState({ 174 | "fadeIn": false 175 | }); 176 | }, FADE_IN_DURATION); 177 | } else { 178 | this.setState({ 179 | "switcherActive": false 180 | }); 181 | } 182 | } 183 | 184 | 185 | rejectPassword() { 186 | window.notifications.generate("Password incorrect, please try again.", 'error'); 187 | 188 | this.setState({ 189 | "password": "", 190 | "passwordFailed": true 191 | }); 192 | 193 | setTimeout(() => { 194 | this.setState({ 195 | "passwordFailed": false 196 | }); 197 | }, ERROR_SHAKE_DURATION); 198 | } 199 | 200 | 201 | render() { 202 | let loginPanelClasses = ['login-panel-main']; 203 | let avatarClasses = ['avatar-container']; 204 | let avatarBackgroundClasses = ['avatar-background']; 205 | let settings = this.props.settings; 206 | 207 | if (this.state.fadeIn === true) { 208 | loginPanelClasses.push('fadein'); 209 | } 210 | 211 | if (this.state.switcherActive === true) { 212 | loginPanelClasses.push('fadeout'); 213 | } 214 | 215 | if (settings.avatar_enabled === false) { 216 | avatarClasses.push('invisible'); 217 | } 218 | 219 | if (settings.avatar_background_enabled === false) { 220 | avatarBackgroundClasses.push('avatar-background-hidden'); 221 | } 222 | 223 | let _styles = { 224 | "background": `linear-gradient(to bottom, ${ settings.style_login_gradient_top_color } 0%, ${ settings.style_login_gradient_bottom_color } 100%)`, 225 | "border-color": settings.style_login_border_color 226 | }; 227 | 228 | if (settings.style_login_border_enabled === false) { 229 | _styles['border'] = 'none !important'; 230 | } 231 | 232 | let style = cxs(_styles); 233 | 234 | return ( 235 |
236 |
237 |
238 |
239 |
240 | 241 |
242 |
243 |
244 | 251 |
252 | 253 | 254 | 255 |
256 |
257 | 261 |
262 | ); 263 | } 264 | } 265 | 266 | 267 | UserPicker.propTypes = { 268 | 'dispatch': PropTypes.func.isRequired, 269 | 'settings': PropTypes.object.isRequired, 270 | 'activeUser': PropTypes.object.isRequired, 271 | 'activeSession': PropTypes.object.isRequired 272 | }; 273 | 274 | 275 | export default connect( 276 | (state) => { 277 | return { 278 | 'activeUser': state.user, 279 | 'activeSession': state.session, 280 | 'settings': state.settings 281 | }; 282 | }, 283 | null 284 | )(UserPicker); 285 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/UserPicker/PasswordField.jsx: -------------------------------------------------------------------------------- 1 | // PasswordField -> Required by Components/UserPanel/Form 2 | // -------------------------------------- 3 | // Simple password input field. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | 8 | 9 | const PasswordField = (props) => { 10 | let classes = ['user-password']; 11 | 12 | if (props.passwordFailed === true) { 13 | classes.push('error'); 14 | } 15 | 16 | return ( 17 | 25 | ); 26 | }; 27 | 28 | 29 | PasswordField.propTypes = { 30 | 'password': PropTypes.string.isRequired, 31 | 'passwordFailed': PropTypes.bool.isRequired, 32 | 'handlePasswordInput': PropTypes.func.isRequired 33 | }; 34 | 35 | 36 | export default PasswordField; 37 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/UserPicker/SessionSwitcher/Main.jsx: -------------------------------------------------------------------------------- 1 | // SessionSelector -> Required by Components/UserPanel/Form 2 | // -------------------------------------- 3 | // Displays session as a grid for session switching. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | import SessionItem from './SessionItem'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | 12 | class SessionSelector extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | handleClick(sessionKey) { 18 | this.props.setActiveSession(sessionKey); 19 | this.props.close(); 20 | } 21 | 22 | render() { 23 | // Sort by active, then alphabetical. 24 | // Then filter out duplicate entries 25 | // Doing this requires using sort in reverse. 26 | let classes = ['login-session-switcher']; 27 | 28 | if (this.props.active) { 29 | classes.push('active'); 30 | } 31 | 32 | if (window.lightdm.sessions.length < 4) { 33 | classes.push('no-justify'); 34 | } 35 | 36 | let rows = ( 37 | window.lightdm.sessions 38 | .sort((a, b) => { 39 | return a.name.toUpperCase() > b.name.toUpperCase(); 40 | }) 41 | .map((session, index) => ( 42 | 50 | )) 51 | ); 52 | 53 | return ( 54 |
55 | { rows } 56 |
57 | ); 58 | } 59 | } 60 | 61 | SessionSelector.propTypes = { 62 | 'setActiveSession': PropTypes.func.isRequired, 63 | 'close': PropTypes.func.isRequired, 64 | 'buttonBackgroundColor': PropTypes.string.isRequired, 65 | 'buttonTextColor': PropTypes.string.isRequired, 66 | 'active': PropTypes.bool.isRequired, 67 | }; 68 | 69 | 70 | export default connect( 71 | (state) => { 72 | return { 73 | 'buttonBackgroundColor': state.settings.style_login_button_color, 74 | 'buttonTextColor': state.settings.style_login_button_text_color 75 | }; 76 | }, 77 | null 78 | )(SessionSelector); 79 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/UserPicker/SessionSwitcher/SessionItem.jsx: -------------------------------------------------------------------------------- 1 | // SessionItem -> Required by Components/UserPanel/SessionSelector 2 | // -------------------------------------- 3 | // Just one session to choose from 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | import cxs from "cxs"; 8 | 9 | const ANIMATION_DURATION = 100; 10 | 11 | 12 | class SessionItem extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | handleClick(e) { 18 | e.preventDefault(); 19 | this.props.handleClick(this.props.session.key); 20 | } 21 | 22 | render() { 23 | let classes = ['login-session-item']; 24 | 25 | classes.push(cxs({ 26 | "color": this.props.buttonTextColor, 27 | "background-color": this.props.buttonBackgroundColor, 28 | "animation-delay": `${ this.props.index * ANIMATION_DURATION }ms !important` 29 | })); 30 | 31 | return ( 32 |
33 |
34 |
{ this.props.session.name }
35 |
36 |
37 | ); 38 | } 39 | } 40 | 41 | SessionItem.propTypes = { 42 | 'buttonBackgroundColor': PropTypes.string.isRequired, 43 | 'buttonTextColor': PropTypes.string.isRequired, 44 | 'session': PropTypes.object.isRequired, 45 | 'handleClick': PropTypes.func.isRequired, 46 | 'index': PropTypes.number.isRequired, 47 | 'settings': PropTypes.object.isRequired 48 | }; 49 | 50 | export default SessionItem; 51 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/UserPicker/UserSwitcher/Main.jsx: -------------------------------------------------------------------------------- 1 | // UserSwitcher -> Required by Components/UserPanel 2 | // -------------------------------------- 3 | // Handles (poorly) the task of switching between 4 | // multiple users on the same system. 5 | 6 | import React from 'react'; 7 | import PropTypes from 'prop-types'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | const FADE_DURATION = 200; 12 | 13 | 14 | class UserSwitcher extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | 18 | this.state = { 19 | "fadeOut": false, 20 | "selectedUser": this.props.activeUser, 21 | "selectedUserIndex": window.lightdm.users.indexOf(this.props.activeUser) 22 | }; 23 | } 24 | 25 | 26 | handleBackButton(event) { 27 | this.props.setActiveUser(this.state.selectedUser); 28 | 29 | this.setState({ 30 | "fadeOut": true 31 | }); 32 | 33 | setTimeout(() => { 34 | this.setState({ 35 | "fadeOut": false 36 | }); 37 | }, FADE_DURATION); 38 | } 39 | 40 | 41 | handleUserClick(index) { 42 | this.setState({ 43 | "selectedUser": window.lightdm.users[index], 44 | "selectedUserIndex": index 45 | }); 46 | } 47 | 48 | 49 | generateUserList() { 50 | let activeIndex = this.state.selectedUserIndex; 51 | 52 | let avatarBackgroundClasses; 53 | 54 | if(this.props.avatarEnabled) { 55 | avatarBackgroundClasses = 'avatar-background'; 56 | } else { 57 | avatarBackgroundClasses = 'avatar-background avatar-background-hidden'; 58 | } 59 | 60 | let avatars = window.lightdm.users.map((user, index) => { 61 | let classes = ['avatar-container']; 62 | 63 | if (index === activeIndex) { 64 | classes.push('active'); 65 | } 66 | 67 | if (index === activeIndex - 1) { 68 | classes.push('previous'); 69 | } 70 | 71 | if (index === activeIndex + 1) { 72 | classes.push('next'); 73 | } 74 | 75 | return ( 76 |
  • 77 |
    78 |
    79 | 80 |
    81 |
    82 |
    83 |
    { user.display_name }
    84 |
    { user.real_name }
    85 |
    86 |
  • 87 | ); 88 | }); 89 | 90 | // Very hacky. Add an extra copy of the last element to the beginning of the list 91 | // if the first element in the list is currently selected. 92 | if (activeIndex === 0) { 93 | let user = window.lightdm.users[window.lightdm.users.length - 1]; 94 | avatars.splice(0, 0, 95 |
  • 96 |
    97 |
    98 | 99 |
    100 |
    101 |
    102 |
    { user.display_name }
    103 |
    { user.real_name }
    104 |
    105 |
  • 106 | ); 107 | } 108 | 109 | // Very hacky. Add an extra copy of the first element to the end of the list 110 | // if the last element in the list is currently selected. 111 | if (activeIndex === window.lightdm.users.length - 1) { 112 | let user = window.lightdm.users[0]; 113 | avatars.push( 114 |
  • 115 |
    116 |
    117 | 118 |
    119 |
    120 |
    121 |
    { user.display_name }
    122 |
    { user.real_name }
    123 |
    124 |
  • 125 | ); 126 | } 127 | 128 | return ( 129 |
      130 | { avatars } 131 |
    132 | ); 133 | } 134 | 135 | 136 | render() { 137 | let classes = ['login-panel-switcher']; 138 | 139 | let userList = this.generateUserList(); 140 | let userCount = window.lightdm.users.length; 141 | 142 | if (this.props.active === true) { 143 | classes.push('active'); 144 | } else if (this.state.fadeOut === true) { 145 | classes.push('fadeout'); 146 | } 147 | 148 | return ( 149 |
    150 |
    User { this.state.selectedUserIndex + 1 } of { userCount }
    151 | { userList } 152 |
    153 |
    BACK
    154 |
    155 |
    156 | ); 157 | } 158 | } 159 | 160 | 161 | UserSwitcher.propTypes = { 162 | 'active': PropTypes.bool.isRequired, 163 | 'activeUser': PropTypes.object.isRequired, 164 | 'setActiveUser': PropTypes.func.isRequired, 165 | 'avatarEnabled': PropTypes.bool.isRequired 166 | }; 167 | 168 | 169 | export default connect( 170 | (state) => { 171 | return { 172 | 'activeUser': state.user, 173 | 'avatarEnabled': state.settings.avatar_background_enabled 174 | }; 175 | }, 176 | null 177 | )(UserSwitcher); 178 | -------------------------------------------------------------------------------- /src/js/Components/LoginWindow/UserPicker/UserSwitcher/UserSwitchButton.jsx: -------------------------------------------------------------------------------- 1 | // UserSwitchButton -> Required by Components/UserPanel 2 | // -------------------------------------- 3 | // Toggles the UserSwitcher. 4 | 5 | import React from 'react'; 6 | import PropTypes from 'prop-types'; 7 | 8 | 9 | export const UserSwitchButton = ({ handleSwitcherClick }) => { 10 | let classes = ['left']; 11 | 12 | if (window.lightdm.users.length < 2) { 13 | classes.push('disabled'); 14 | } 15 | 16 | return ( 17 |
    SWITCH USER
    18 | ); 19 | }; 20 | 21 | 22 | UserSwitchButton.propTypes = { 23 | 'handleSwitcherClick': PropTypes.func.isRequired 24 | }; 25 | 26 | 27 | export default UserSwitchButton; 28 | -------------------------------------------------------------------------------- /src/js/Components/Settings/DefaultThemes.js: -------------------------------------------------------------------------------- 1 | const Glass = { 2 | 'avatar_enabled': false, 3 | 'avatar_size': '200px', 4 | 'avatar_shape': 'circle', 5 | 'style_command_text_align': 'left', 6 | 'style_command_background_color': 'hsla(0, 2%, 98%, 0.29)', 7 | 'style_command_icon_color': 'hsl(182, 79%, 58%)', 8 | 'style_command_text_color': 'hsla(0, 0%, 35%, 0.9)', 9 | 'style_login_border_color': 'hsla(0, 0%, 0%, 0)', 10 | 'style_login_border_enabled': false, 11 | 'style_login_button_color': 'hsla(16, 96%, 11%, 0.88)', 12 | 'style_login_gradient_top_color': 'hsla(0, 0%, 100%, 0)', 13 | 'style_login_gradient_bottom_color': 'hsla(0, 0%, 100%, 0.32)', 14 | 'style_login_username_color': 'hsla(0, 100%, 100%, 1)', 15 | 'window_border_radius': '4px', 16 | 'window_font_size': '1em' 17 | }; 18 | 19 | const Default = { 20 | 'avatar_enabled': false, 21 | 'avatar_size': '200px', 22 | 'avatar_shape': 'circle', 23 | 'style_command_text_align': 'left', 24 | 'style_command_background_color': 'hsla(0, 0%, 22%, 1)', 25 | 'style_command_icon_color': 'hsla(349, 98%, 65%, 1)', 26 | 'style_command_text_color': 'hsla(0, 100%, 100%, 1)', 27 | 'style_login_border_color': 'hsla(0, 100%, 100%, 0.1)', 28 | 'style_login_border_enabled': true, 29 | 'style_login_button_color': 'hsla(0, 100%, 100%, 0.15)', 30 | 'style_login_gradient_top_color': 'hsla(18, 100%, 61%, 0.66)', 31 | 'style_login_gradient_bottom_color': 'hsla(339, 94%, 64%, 1)', 32 | 'style_login_username_color': 'hsla(0, 100%, 100%, 1)', 33 | 'window_border_radius': '4px', 34 | 'window_font_size': '1em' 35 | }; 36 | 37 | const Arch = { 38 | 'avatar_enabled': true, 39 | 'avatar_size': '200px', 40 | 'avatar_shape': 'circle', 41 | 'style_command_text_align': 'left', 42 | 'style_command_background_color': 'hsl(201, 48%, 15%)', 43 | 'style_command_icon_color': 'hsl(199, 66%, 65%)', 44 | 'style_command_text_color': 'hsl(0, 100%, 96%)', 45 | 'style_login_border_color': 'hsla(0, 100%, 50%, 0.1)', 46 | 'style_login_border_enabled': false, 47 | 'style_login_button_color': 'hsla(0, 0%, 100%, 0.08)', 48 | 'style_login_gradient_top_color': 'hsl(32, 76%, 76%)', 49 | 'style_login_gradient_bottom_color': 'hsl(193, 80%, 71%)', 50 | 'style_login_username_color': 'hsla(0, 100%, 100%, 1)', 51 | 'window_border_radius': '4px', 52 | 'window_font_size': '1em' 53 | }; 54 | 55 | const Capitan = { 56 | 'avatar_enabled': true, 57 | 'avatar_size': '200px', 58 | 'avatar_shape': 'circle', 59 | 'style_command_text_align': 'center', 60 | 'style_command_icons_enabled': false, 61 | 'style_command_background_color': 'hsla(236, 4%, 12%, 0.8)', 62 | 'style_command_icon_color': 'hsl(0, 0%, 0%)', 63 | 'style_command_text_color': 'hsla(0, 0%, 100%, 0.85)', 64 | 'style_login_border_color': 'hsla(0, 100%, 50%, 0.1)', 65 | 'style_login_border_enabled': false, 66 | 'style_login_button_color': 'hsl(0, 0%, 0%)', 67 | 'style_login_gradient_top_color': 'hsla(0, 76%, 76%, 0.94)', 68 | 'style_login_gradient_bottom_color': 'hsl(193, 80%, 71%)', 69 | 'style_login_username_color': 'hsla(0, 100%, 100%, 1)', 70 | 'window_border_radius': '4px', 71 | 'window_font_size': '1em' 72 | }; 73 | 74 | 75 | const Ember = { 76 | 'avatar_enabled': false, 77 | 'avatar_size': '200px', 78 | 'avatar_shape': 'circle', 79 | 'style_command_text_align': 'left', 80 | 'style_command_background_color': 'hsl(0, 80%, 26%)', 81 | 'style_command_icon_color': 'hsl(13, 100%, 53%)', 82 | 'style_command_text_color': 'hsl(0, 0%, 100%)', 83 | 'style_login_border_color': 'hsla(46, 82%, 48%, 0.24)', 84 | 'style_login_border_enabled': true, 85 | 'style_login_button_color': 'hsla(0, 100%, 85%, 0.22)', 86 | 'style_login_gradient_top_color': 'hsl(14, 100%, 53%)', 87 | 'style_login_gradient_bottom_color': 'hsl(35, 95%, 56%)', 88 | 'style_login_username_color': 'hsla(0, 100%, 100%, 1)', 89 | 'window_border_radius': '4px', 90 | 'window_font_size': '1em' 91 | }; 92 | 93 | const MaterialPink = { 94 | 'avatar_enabled': true, 95 | 'avatar_size': '200px', 96 | 'avatar_shape': 'circle', 97 | 'font_scale': '1', 98 | 'style_command_text_align': 'left', 99 | 'style_command_background_color': 'hsl(336, 78%, 43%)', 100 | 'style_command_icon_color': 'hsl(340, 100%, 63%)', 101 | 'style_command_text_color': 'hsl(0, 0%, 100%)', 102 | 'style_login_border_color': 'hsla(0, 0%, 68%, 0.1)', 103 | 'style_login_border_enabled': true, 104 | 'style_login_button_color': 'hsla(0, 0%, 0%, 0.49)', 105 | 'style_login_gradient_top_color': 'hsl(340, 82%, 52%)', 106 | 'style_login_gradient_bottom_color': 'hsl(340, 82%, 52%)', 107 | 'style_login_username_color': 'hsla(0, 100%, 100%, 1)', 108 | 'window_border_radius': '4px', 109 | 'window_font_size': '1em' 110 | }; 111 | 112 | const MaterialGreen = { 113 | 'avatar_enabled': true, 114 | 'avatar_size': '200px', 115 | 'avatar_shape': 'circle', 116 | 'font_scale': '1', 117 | 'style_command_text_align': 'left', 118 | 'style_command_background_color': 'hsl(123, 43%, 39%)', 119 | 'style_command_icon_color': 'hsl(88, 50%, 53%)', 120 | 'style_command_text_color': 'hsl(0, 0%, 100%)', 121 | 'style_login_border_color': 'hsla(0, 0%, 68%, 0.1)', 122 | 'style_login_border_enabled': true, 123 | 'style_login_button_color': 'hsla(0, 0%, 0%, 0.49)', 124 | 'style_login_gradient_top_color': 'hsl(122, 39%, 49%)', 125 | 'style_login_gradient_bottom_color': 'hsl(122, 39%, 49%)', 126 | 'style_login_username_color': 'hsla(0, 100%, 100%, 1)', 127 | 'window_border_radius': '4px', 128 | 'window_font_size': '1em' 129 | }; 130 | 131 | const Running = { 132 | 'avatar_enabled': false, 133 | 'avatar_size': '200px', 134 | 'avatar_shape': 'circle', 135 | 'avatar_background_enabled': true, 136 | 'font_scale': '1', 137 | 'style_command_text_align': 'left', 138 | 'style_command_background_color': 'hsl(169, 10%, 41%)', 139 | 'style_command_icon_color': 'hsl(98, 6%, 73%)', 140 | 'style_command_text_color': 'hsla(0, 0%, 100%, 0.18)', 141 | 'style_login_border_color': 'hsla(0, 0%, 95%, 0.1)', 142 | 'style_login_border_enabled': true, 143 | 'style_login_button_color': 'hsl(77, 11%, 87%)', 144 | 'style_login_button_text_color': 'hsl(0, 0%, 44%)', 145 | 'style_login_gradient_top_color': 'hsl(180, 6%, 56%)', 146 | 'style_login_gradient_bottom_color': 'hsl(180, 6%, 56%)', 147 | 'style_login_username_bold': false, 148 | 'style_login_username_capitalization': 'lowercase', 149 | 'style_login_username_color': 'hsla(0, 0%, 100%, 0.73)', 150 | 'style_login_username_italic': false, 151 | 'window_border_radius': '4px', 152 | 'window_font_size': '1em' 153 | }; 154 | 155 | const Seattle = { 156 | 'page_zoom': '1', 157 | 'avatar_enabled': false, 158 | 'avatar_size': '200px', 159 | 'avatar_shape': 'circle', 160 | 'avatar_background_enabled': false, 161 | 'font_scale': '1', 162 | 'style_command_text_align': 'left', 163 | 'style_command_background_color': 'hsl(190, 100%, 16%)', 164 | 'style_command_icon_color': 'hsl(207, 52%, 70%)', 165 | 'style_command_text_color': 'hsla(203, 14%, 75%, 0.68)', 166 | 'style_login_border_color': 'hsla(203, 76%, 82%, 0.06)', 167 | 'style_login_border_enabled': true, 168 | 'style_login_button_color': 'hsla(180, 47%, 47%, 0.51)', 169 | 'style_login_button_text_color': 'hsl(202, 100%, 84%)', 170 | 'style_login_gradient_top_color': 'hsla(189, 87%, 25%, 0.83)', 171 | 'style_login_gradient_bottom_color': 'hsl(188, 35%, 59%)', 172 | 'style_login_username_bold': true, 173 | 'style_login_username_capitalization': 'default', 174 | 'style_login_username_color': 'hsla(0, 0%, 100%, 0.73)', 175 | 'style_login_username_italic': false, 176 | 'window_border_radius': '4px', 177 | 'window_font_size': '1em' 178 | }; 179 | 180 | export const DefaultThemes = { 181 | Arch, 182 | Default, 183 | 'El Capitan': Capitan, 184 | Glass, 185 | Ember, 186 | Seattle, 187 | 'Running in the 90\'s': Running, 188 | 'Material Green': MaterialGreen, 189 | 'Material Pink': MaterialPink 190 | }; 191 | 192 | export default DefaultThemes; 193 | -------------------------------------------------------------------------------- /src/js/Components/Settings/Main.jsx: -------------------------------------------------------------------------------- 1 | // Settings -> Required by Main 2 | // -------------------------------------- 3 | // Handles greeter configuration. 4 | 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import Draggable from 'draggable'; 8 | import PropTypes from 'prop-types'; 9 | 10 | import { connect } from 'react-redux'; 11 | 12 | import SectionGeneral from './sections/General'; 13 | import SectionStyle from './sections/Style'; 14 | import SectionThemes from './sections/Themes'; 15 | import SaveDialogue from './SaveDialogue'; 16 | 17 | import { setPageZoom } from 'Utils/Utils'; 18 | 19 | 20 | const SETTINGS_HEIGHT = 300; 21 | const SETTINGS_WIDTH = 600; 22 | 23 | 24 | class Settings extends React.Component { 25 | constructor(props) { 26 | super(props); 27 | 28 | this.state = { 29 | "active": this.props.settings.active, 30 | "selectedCategory": 'general', 31 | }; 32 | } 33 | 34 | 35 | componentDidMount() { 36 | let draggable = new Draggable(document.getElementById("settings"), { 37 | "handle": this.handle 38 | }); 39 | 40 | let centerX = ((window.innerWidth - SETTINGS_WIDTH) / 2); 41 | let centerY = ((window.innerHeight - SETTINGS_HEIGHT) / 2); 42 | 43 | draggable.set(centerX, centerY); 44 | 45 | // Set default zoom 46 | let defaultZoom = this.props.settings.page_zoom; 47 | setPageZoom(defaultZoom); 48 | } 49 | 50 | 51 | handleCategoryClick(category, e) { 52 | this.setState({ 53 | "selectedCategory": category.toLowerCase() 54 | }); 55 | } 56 | 57 | 58 | handleSettingsBinary(name) { 59 | this.props.dispatch({ 60 | "type": 'SETTINGS_TOGGLE_VALUE', 61 | "name": name 62 | }); 63 | } 64 | 65 | 66 | handleSettingsClose() { 67 | this.props.dispatch({ 68 | "type": 'SETTINGS_TOGGLE_ACTIVE' 69 | }); 70 | } 71 | 72 | 73 | handleSettingsMinimize() { 74 | this.props.dispatch({ 75 | "type": 'SETTINGS_WINDOW_MINIMIZE' 76 | }); 77 | } 78 | 79 | 80 | handleSettingsText(name, event) { 81 | let value; 82 | 83 | try { 84 | value = event.target.value; 85 | } catch (err) { 86 | value = event; 87 | } 88 | 89 | this.props.dispatch({ 90 | "type": 'SETTINGS_SET_VALUE', 91 | "name": name, 92 | "value": value 93 | }); 94 | } 95 | 96 | 97 | generateCategories() { 98 | let categories = [ 99 | 'General', 100 | 'Style', 101 | 'Themes' 102 | ]; 103 | 104 | let listItems = categories.map((category) => { 105 | let classes = []; 106 | 107 | if (category.toLowerCase() === this.state.selectedCategory) { 108 | classes.push('active'); 109 | } 110 | 111 | return ( 112 |
  • 113 | { category } 114 |
  • 115 | ); 116 | }); 117 | 118 | return ( 119 |
      120 | { listItems } 121 |
    122 | ); 123 | } 124 | 125 | 126 | generateSection(_category) { 127 | let category = _category.toLowerCase(); 128 | let componentProps = { 129 | "settingsToggleBinary": this.handleSettingsBinary.bind(this), 130 | "settingsSetValue": this.handleSettingsText.bind(this) 131 | }; 132 | 133 | if (category === "general") { 134 | return (); 135 | } else if (category === "style") { 136 | return (); 137 | } else if (category === "themes") { 138 | return (); 139 | } 140 | } 141 | 142 | 143 | render() { 144 | let categories = this.generateCategories(); 145 | let section = this.generateSection(this.state.selectedCategory); 146 | 147 | return ReactDOM.createPortal( 148 |
    149 |
    this.handle = node }> 150 |
      151 |
    • 152 |
    • ×
    • 153 |
    154 |
    155 |
    156 | { categories } 157 |
    158 |
    159 | { section } 160 | 161 |
    162 |
    , 163 | document.getElementById("settings") 164 | ); 165 | } 166 | } 167 | 168 | 169 | Settings.propTypes = { 170 | 'dispatch': PropTypes.func.isRequired, 171 | 'settings': PropTypes.object.isRequired 172 | }; 173 | 174 | 175 | export default connect( 176 | (state) => { 177 | return { 178 | 'settings': state.settings 179 | }; 180 | }, 181 | null 182 | )(Settings); 183 | -------------------------------------------------------------------------------- /src/js/Components/Settings/SaveDialogue.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { connect } from 'react-redux'; 5 | 6 | 7 | const rejectSettings = (props) => { 8 | props.dispatch({ 9 | 'type': "SETTINGS_REJECT" 10 | }); 11 | }; 12 | 13 | 14 | const saveSettings = (props) => { 15 | props.dispatch({ 16 | 'type': "SETTINGS_SAVE" 17 | }); 18 | }; 19 | 20 | 21 | export const SaveDialogue = (props) => { 22 | return ( 23 |
    24 | 25 | 26 |
    27 | ); 28 | }; 29 | 30 | 31 | SaveDialogue.propTypes = { 32 | 'dispatch': PropTypes.func.isRequired 33 | }; 34 | 35 | 36 | export default connect( 37 | (state) => { 38 | return {}; 39 | }, 40 | null 41 | )(SaveDialogue); 42 | -------------------------------------------------------------------------------- /src/js/Components/Settings/inputs/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | // FormCheckbox -> Required by Settings/Settings* 2 | // -------------------------------------- 3 | // Provides a basic binary form checkbox. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | 8 | 9 | export const Checkbox = ({ name, value, boundFunction }) => { 10 | let elementID = `option-${ name.replace(" ", "-")}`; 11 | 12 | return ( 13 |
  • 14 | 20 | 24 |
  • 25 | ); 26 | }; 27 | 28 | 29 | Checkbox.propTypes = { 30 | 'name': PropTypes.string.isRequired, 31 | 'value': PropTypes.bool.isRequired, 32 | 'boundFunction': PropTypes.func.isRequired 33 | }; 34 | 35 | 36 | export default Checkbox; 37 | -------------------------------------------------------------------------------- /src/js/Components/Settings/inputs/ColorPicker.jsx: -------------------------------------------------------------------------------- 1 | // FormColorPicker -> Required by Settings/Settings* 2 | // -------------------------------------- 3 | // Wraps the jsColorPicker lib to provide a color picker. 4 | 5 | import React from 'react'; 6 | import tinycolor from 'tinycolor2'; 7 | import PropTypes from 'prop-types'; 8 | 9 | import { ChromePicker } from 'react-color'; 10 | 11 | 12 | export class ColorPicker extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | this.state = { 17 | 'active': false, 18 | 'color': tinycolor(props.value).toHsl() 19 | }; 20 | } 21 | 22 | 23 | handleChange(color, event) { 24 | let colorString = tinycolor(color[color.source]).toHslString(); 25 | 26 | this.setState({ 27 | 'color': color[color.source] 28 | }); 29 | 30 | this.props.boundFunction(colorString); 31 | } 32 | 33 | 34 | handleClose(e) { 35 | this.setState({ 'active': false }); 36 | } 37 | 38 | 39 | handleOpen(e) { 40 | this.setState({ 'active': true }); 41 | } 42 | 43 | 44 | render () { 45 | let elementID = `option-${ this.props.name.replace(" ", "-")}`; 46 | let swatchContainerClasses = ['swatch-container']; 47 | let colorPicker = false; 48 | 49 | if (this.state.active === true) { 50 | swatchContainerClasses.push("active"); 51 | 52 | colorPicker = ( 53 | 54 | ); 55 | } 56 | 57 | return ( 58 |
  • 59 | 60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 |
    67 | { colorPicker } 68 |
    69 |
    70 |
  • 71 | ); 72 | } 73 | } 74 | 75 | 76 | ColorPicker.propTypes = { 77 | 'value': PropTypes.string.isRequired, 78 | 'name': PropTypes.string.isRequired, 79 | 'boundFunction': PropTypes.func.isRequired 80 | }; 81 | 82 | 83 | export default ColorPicker; 84 | -------------------------------------------------------------------------------- /src/js/Components/Settings/inputs/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | // FormDropdown -> Required by Settings/Settings* 2 | // -------------------------------------- 3 | // Provides a basic form dropdown. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | 8 | 9 | export const DropdownOption = (option) => { 10 | let name = option.name || option; 11 | let value = option.value || option; 12 | 13 | return ( 14 | 15 | ); 16 | }; 17 | 18 | 19 | export const Dropdown = ({ name, value, options, boundFunction }) => { 20 | let elementID = `option-${ name.replace(" ", "-") }`; 21 | let items = options.map((option) => DropdownOption(option)); 22 | 23 | return ( 24 |
  • 25 | 26 | 27 | 28 | 31 |
  • 32 | ); 33 | }; 34 | 35 | 36 | Dropdown.propTypes = { 37 | 'name': PropTypes.string, 38 | 'value': PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 39 | 'options': PropTypes.arrayOf( 40 | PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.number]) 41 | ).isRequired, 42 | 'boundFunction': PropTypes.func.isRequired 43 | }; 44 | 45 | 46 | export default Dropdown; 47 | -------------------------------------------------------------------------------- /src/js/Components/Settings/inputs/TextField.jsx: -------------------------------------------------------------------------------- 1 | // FormTextField -> Required by Settings/Settings* 2 | // -------------------------------------- 3 | // Provides a basic binary form checkbox. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | 8 | export const TextField = ({ name, value, disabled, boundFunction }) => { 9 | let elementID = `option-${ name.replace(" ", "-")}`; 10 | 11 | return ( 12 |
  • 13 | 14 | 21 |
  • 22 | ); 23 | }; 24 | 25 | 26 | TextField.propTypes = { 27 | 'name': PropTypes.string.isRequired, 28 | 'disabled': PropTypes.bool, 29 | 'value': PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 30 | 'boundFunction': PropTypes.func.isRequired 31 | }; 32 | 33 | 34 | export default TextField; 35 | -------------------------------------------------------------------------------- /src/js/Components/Settings/sections/General.jsx: -------------------------------------------------------------------------------- 1 | // SettingsGeneral -> Required by Components/Settings 2 | // -------------------------------------- 3 | // Basic distro / visibility / date & time formatting settings. 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | 8 | import { connect } from 'react-redux'; 9 | 10 | import * as FileOperations from "Logic/FileOperations"; 11 | import TextField from "../inputs/TextField"; 12 | import Dropdown from "../inputs/Dropdown"; 13 | import Checkbox from "../inputs/Checkbox"; 14 | 15 | 16 | const onLogoChange = (props, e) => { 17 | props.dispatch({ 18 | "type": 'SETTINGS_LOGO_CHANGE', 19 | "path": e.target.value 20 | }); 21 | }; 22 | 23 | 24 | const LogoChooser = (props) => { 25 | let logos = FileOperations.getLogos(); 26 | let activeLogo = props.settings.distro; 27 | 28 | let items = logos.map((e) => { 29 | let [path, fileName] = e; 30 | 31 | return ( 32 | 33 | ); 34 | }); 35 | 36 | let selectedItem = logos.filter((e) => (e[0] === activeLogo)); 37 | selectedItem = selectedItem[0] || [""]; 38 | 39 | return ( 40 |
    41 |
    42 | 43 |
    44 | 47 |
    48 | ); 49 | }; 50 | 51 | 52 | LogoChooser.propTypes = { 53 | 'settings': PropTypes.object.isRequired 54 | }; 55 | 56 | 57 | export const GeneralSection = (props) => { 58 | const settings = props.settings; 59 | const users = window.lightdm.users 60 | .map(e => e.name); 61 | 62 | return ( 63 |
    64 |
    65 | { LogoChooser(props) } 66 |
    67 |
    68 |
      69 |

      User Functionality

      70 |
      71 | 77 | 82 | 83 |

      Date & Time

      84 |
      85 | 90 | 95 | 100 | 105 | 106 |

      Command Visibility

      107 |
      108 | 113 | 118 | 123 | 128 | 129 |

      Avatar Visibility

      130 |
      131 | 136 | 137 | 142 | 143 |

      Hostname Visibility

      144 |
      145 | 150 |
    151 |
    152 |
    153 | ); 154 | }; 155 | 156 | 157 | GeneralSection.propTypes = { 158 | 'settings': PropTypes.object.isRequired, 159 | 'settingsSetValue': PropTypes.func.isRequired, 160 | 'settingsToggleBinary': PropTypes.func.isRequired 161 | }; 162 | 163 | 164 | export default connect( 165 | (state) => { 166 | return { 167 | 'settings': state.settings 168 | }; 169 | }, 170 | null 171 | )(GeneralSection); 172 | -------------------------------------------------------------------------------- /src/js/Components/Settings/sections/Style.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { connect } from 'react-redux'; 5 | 6 | import ColorPicker from '../inputs/ColorPicker'; 7 | import TextField from '../inputs/TextField'; 8 | import Checkbox from '../inputs/Checkbox'; 9 | import Dropdown from '../inputs/Dropdown'; 10 | 11 | 12 | export const StyleSection = (props) => { 13 | const settings = props.settings; 14 | 15 | return ( 16 |
    17 |
    18 |
      19 |

      Window Appearance

      20 |
      21 | 26 | 31 | 36 | 37 |

      Eye Candy

      38 |
      39 | 44 |
    45 |
    46 |
    47 |
      48 |

      Command Panel

      49 |
      50 | 55 | 61 |
      62 | 67 | 72 | 77 |
      78 |

      Distro Logo

      79 |
      80 | 85 | 91 |
    92 | 93 |
      94 |

      Login Panel

      95 |
      96 | 101 |
      102 | 107 | 112 | 117 | 122 | 127 | 132 |
      133 |

      Username

      134 |
      135 | 141 | 146 | 151 |
    152 |
    153 |
    154 | ); 155 | }; 156 | 157 | 158 | StyleSection.propTypes = { 159 | 'settings': PropTypes.object.isRequired, 160 | 'settingsSetValue': PropTypes.func.isRequired, 161 | 'settingsToggleBinary': PropTypes.func.isRequired 162 | }; 163 | 164 | 165 | export default connect( 166 | (state) => { 167 | return { 168 | 'settings': state.settings 169 | }; 170 | }, 171 | null 172 | )(StyleSection); 173 | -------------------------------------------------------------------------------- /src/js/Components/Settings/sections/Themes.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import { connect } from 'react-redux'; 5 | 6 | import * as Settings from 'Logic/Settings'; 7 | import DefaultThemes from '../DefaultThemes'; 8 | 9 | 10 | class Theme extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.state = {}; 15 | } 16 | 17 | 18 | getColors() { 19 | let colors = []; 20 | 21 | for (let setting of Object.keys(this.props.theme)) { 22 | if (setting.startsWith('style') && setting.indexOf('color') !== -1) { 23 | colors.push([setting, this.props.theme[setting]]); 24 | } 25 | } 26 | 27 | return colors; 28 | } 29 | 30 | 31 | render() { 32 | let colorItems = this.getColors().map(([name, color]) => 33 |
  • 40 |   41 |
  • 42 | ); 43 | 44 | let isDefaultTheme = !(Object.keys(DefaultThemes).indexOf(this.props.name) !== -1); 45 | 46 | return ( 47 |
    48 |
    49 |
    { this.props.name}
    50 | 51 | 52 | 53 | 54 |
    55 |
      56 | { colorItems } 57 |
    58 |
    59 | ); 60 | } 61 | } 62 | 63 | 64 | Theme.propTypes = { 65 | 'name': PropTypes.string.isRequired, 66 | 'theme': PropTypes.object.isRequired, 67 | 'loadTheme': PropTypes.func.isRequired, 68 | 'deleteTheme': PropTypes.func.isRequired 69 | }; 70 | 71 | 72 | export class SettingsThemes extends React.Component { 73 | constructor(props) { 74 | super(props); 75 | 76 | this.state = { 77 | 'themes': { ...Settings.getUserThemes(), ...DefaultThemes } 78 | }; 79 | 80 | this.nodes = {}; 81 | } 82 | 83 | handleDeleteTheme(themeName) { 84 | Settings.deleteTheme(themeName); 85 | 86 | this.setState({ 87 | 'themes': { ...Settings.getUserThemes(), ...DefaultThemes } 88 | }); 89 | 90 | window.notifications.generate(`Theme has been deleted!`, "success"); 91 | } 92 | 93 | 94 | handleLoadTheme(themeName, theme) { 95 | this.props.dispatch({ 96 | 'type': 'SETTINGS_APPLY_THEME', 97 | 'name': themeName, 98 | 'theme': theme 99 | }); 100 | } 101 | 102 | 103 | handleSaveTheme(e) { 104 | e.preventDefault(); 105 | e.stopPropagation(); 106 | 107 | let themeName = this.nodes.themeName.value; 108 | 109 | Settings.saveTheme(themeName, this.props.settings); 110 | 111 | this.setState({ 112 | 'themes': { ...Settings.getUserThemes(), ...DefaultThemes } 113 | }); 114 | 115 | window.notifications.generate(`Your theme has been saved.`, "success"); 116 | } 117 | 118 | 119 | render() { 120 | let themes = this.state.themes; 121 | let themeItems = Object.keys(themes).map(themeName => 122 | 129 | ); 130 | 131 | return ( 132 |
    133 |
    134 |

    Save current settings as a theme?

    135 | this.nodes.themeName = node } /> 136 | 139 |
    140 |
    141 | { themeItems } 142 |
    143 |
    144 | ); 145 | } 146 | } 147 | 148 | 149 | SettingsThemes.propTypes = { 150 | 'settings': PropTypes.object.isRequired, 151 | 'dispatch': PropTypes.func.isRequired 152 | }; 153 | 154 | 155 | export default connect( 156 | (state) => { 157 | return { 158 | 'settings': state.settings 159 | }; 160 | }, 161 | null 162 | )(SettingsThemes); 163 | -------------------------------------------------------------------------------- /src/js/Components/SettingsToggler/Main.jsx: -------------------------------------------------------------------------------- 1 | // SettingsToggler -> Required by Main 2 | // -------------------------------------- 3 | // Handles Settings toggling. Straightforward stuff. 4 | 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | 8 | import { connect } from 'react-redux'; 9 | 10 | 11 | const toggleSettings = (props) => { 12 | props.dispatch({ 13 | 'type': "SETTINGS_TOGGLE_ACTIVE" 14 | }); 15 | }; 16 | 17 | 18 | export const SettingsToggler = (props) => { 19 | let classes = ['settings-toggler']; 20 | 21 | return ReactDOM.createPortal( 22 |
    26 | ≡ 27 |
    , 28 | document.getElementById("settings-toggler-mount") 29 | ); 30 | }; 31 | 32 | 33 | export default connect( 34 | (state) => { return {}; }, 35 | null 36 | )(SettingsToggler); 37 | -------------------------------------------------------------------------------- /src/js/Logic/FileOperations.js: -------------------------------------------------------------------------------- 1 | // FileOperations -> Required by Components/WallpaperSwitcher 2 | // -------------------------------------- 3 | // LightDM related file / config fetching. 4 | 5 | export function getWallpaperDirectory() { 6 | // Return the test folder when debugging. 7 | if (window.__debug === true) { 8 | return "src/test/wallpapers/"; 9 | } 10 | 11 | let wallpapersDirectory = window.config.get_str("branding", "background_images"); 12 | 13 | // Do NOT allow the default wallpaper directory to set, as this will prevent the default provided backgrounds from 14 | // being used 100% of the time in a stock install. 15 | if (wallpapersDirectory == "/usr/share/backgrounds" || wallpapersDirectory == "/usr/share/backgrounds/") { 16 | wallpapersDirectory = "/usr/share/lightdm-webkit/themes/lightdm-webkit-theme-aether/src/img/wallpapers/"; 17 | } 18 | 19 | return wallpapersDirectory; 20 | } 21 | 22 | 23 | export function getWallpapers(directory) { 24 | // If we're in test mode, we stick to a static rotation of three default wallpapers. 25 | // In production, it is possible that a user will change what wallpapers are available. 26 | if (window.__debug === true) { 27 | return ['boko.jpg', 'mountains-2.png', 'space-1.jpg']; 28 | } 29 | 30 | let wallpapers; 31 | 32 | wallpapers = window.greeterutil.dirlist(directory); 33 | wallpapers = wallpapers.map((e) => e.split("/").pop()); 34 | 35 | return wallpapers; 36 | } 37 | 38 | 39 | export function getLogos() { 40 | // If we're in test mode, just return the default three. 41 | if (window.__debug === true) { 42 | return [ 43 | ["src/test/logos/archlinux.png", "archlinux.png"], 44 | ["src/test/logos/antergos.png", "antergos.png"], 45 | ["src/test/logos/ubuntu.png", "ubuntu.png"], 46 | ["src/test/logos/debian.png", "debian.png"], 47 | ["src/test/logos/generic.png", "generic.png"], 48 | ["src/test/logos/tux.png", "tux.png"], 49 | ["src/test/logos/tux-silhouette.png", "tux-silhouette.png"] 50 | ]; 51 | } 52 | 53 | // Return a tuple of the path and filename for usage in the Settings dialogue. 54 | let userLogo = window.config.get_str("branding", "logo"); 55 | let themeLogos = window.greeterutil.dirlist("/usr/share/lightdm-webkit/themes/lightdm-webkit-theme-aether/src/img/logos/"); 56 | 57 | themeLogos.push(userLogo); 58 | 59 | return themeLogos.map((e) => [e, e.split("/").pop()]); 60 | } 61 | 62 | 63 | export function getEnvironments() { 64 | return window.lightdm.sessions.map((session) => { 65 | return { 66 | 'name': session.name, 67 | 'value': session.key 68 | }; 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /src/js/Logic/Settings.js: -------------------------------------------------------------------------------- 1 | // Settings -> Required by Reducers/PrimaryReducer 2 | // -------------------------------------- 3 | // Handles manipulation of greeter settings, and 4 | // provides wrapper functions around localstorage. 5 | 6 | export const LOCALSTORAGE_ENABLED = (typeof(Storage) !== "undefined"); 7 | 8 | if (!LOCALSTORAGE_ENABLED) { 9 | window.notifications.generate("localStorage not supported. Theme unable to function!", 'error'); 10 | throw("localStorage not supported. Theme unable to function!"); 11 | } 12 | 13 | 14 | export function requestSetting(setting, defaultSetting=undefined) { 15 | // Always return 'active' as false when initializing. 16 | if (setting === 'active') { 17 | return false; 18 | } 19 | 20 | // Continue as usual 21 | let result = localStorage.getItem(setting); 22 | 23 | if (result === null || result === undefined) { 24 | return defaultSetting; 25 | } else { 26 | // Cast string values to booleans if necessary. 27 | if (result === "true" || result === "false") { 28 | return (result === "true") ? true : false; 29 | } 30 | 31 | return result; 32 | } 33 | } 34 | 35 | 36 | export function saveSetting(setting, value=undefined) { 37 | localStorage.setItem(setting, value); 38 | } 39 | 40 | 41 | export function getUserThemes() { 42 | let themes = localStorage.getItem('themes'); 43 | 44 | if (themes === null || themes === undefined) { 45 | themes = {}; 46 | } else { 47 | themes = JSON.parse(themes); 48 | } 49 | 50 | return themes; 51 | } 52 | 53 | 54 | export function saveTheme(name, settings) { 55 | let themes = getUserThemes(); 56 | themes[name] = settings; 57 | 58 | localStorage.setItem('themes', JSON.stringify(themes)); 59 | } 60 | 61 | 62 | export function deleteTheme(name) { 63 | let themes = getUserThemes(); 64 | 65 | delete themes[name]; 66 | 67 | localStorage.setItem('themes', JSON.stringify(themes)); 68 | } 69 | -------------------------------------------------------------------------------- /src/js/Logic/SystemOperations.js: -------------------------------------------------------------------------------- 1 | // SystemOperations -> Required by Reducers/PrimaryReducer 2 | // -------------------------------------- 3 | // Wraps LightDM system operations, and handles the heavy 4 | // lifting of the more complex LightDM requests. 5 | 6 | import * as Settings from './Settings'; 7 | 8 | function executeCommand(message, boundFunction) { 9 | window.notifications.generate(message); 10 | 11 | setTimeout(() => { 12 | boundFunction(); 13 | }, 1000); 14 | 15 | return true; 16 | } 17 | 18 | 19 | export function handleCommand(command) { 20 | // What the hell is this, right? 21 | if (command === "shutdown" && window.lightdm.can_shutdown) { 22 | return executeCommand("Shutting down", window.lightdm.shutdown); 23 | } else if (command === "hibernate" && window.lightdm.can_hibernate) { 24 | return executeCommand("Hibernating system.", window.lightdm.hibernate); 25 | } else if (command === "reboot" && window.lightdm.can_restart) { 26 | return executeCommand("Rebooting system.", window.lightdm.restart); 27 | } else if (command === "sleep" && window.lightdm.can_suspend) { 28 | return executeCommand("Suspending system.", window.lightdm.suspend); 29 | } 30 | 31 | // If we have gotten this far, it's because the command is disabled or doesn't exist. 32 | window.notifications.generate(`${ command } is disabled on this system.`, "error"); 33 | } 34 | 35 | 36 | export function findInitialUser() { 37 | // Are we currently in a lock screen? 38 | if (window.lightdm.lock_hint === true) { 39 | // Default to the very first logged in user. 40 | return window.lightdm.users.filter((user) => user.logged_in)[0]; 41 | } 42 | 43 | if (Settings.requestSetting('default_user')) { 44 | let settingsDefaultUser = Settings.requestSetting('default_user'); 45 | let match = window.lightdm.users.filter((user) => user.name === settingsDefaultUser); 46 | 47 | if (match.length) { 48 | return match[0]; 49 | } 50 | } 51 | 52 | 53 | if (window.lightdm.select_user_hint !== undefined && window.lightdm.select_user_hint !== null) { 54 | let match = window.lightdm.users.filter((user) => user.name === window.lightdm.select_user_hint); 55 | 56 | if (match.length) { 57 | return match[0]; 58 | } 59 | } 60 | 61 | return window.lightdm.users[0]; 62 | } 63 | 64 | 65 | export function findInitialSession(user) { 66 | // Try to default to the initial user 67 | if (user === undefined) { 68 | user = findInitialUser(); 69 | } 70 | 71 | let userSession = (user === undefined) ? undefined : user.session; 72 | 73 | return ( 74 | findSession(userSession) || 75 | findSession(window.lightdm.default_session) || 76 | window.lightdm.sessions[0] 77 | ); 78 | } 79 | 80 | 81 | export function findSession(sessionName) { 82 | if (sessionName === undefined || sessionName === null) { 83 | return false; 84 | } 85 | 86 | return window.lightdm.sessions.filter((session) => 87 | (session.name.toLowerCase() === sessionName.toLowerCase()) || 88 | (session.key.toLowerCase() === sessionName.toLowerCase()) 89 | )[0]; 90 | } 91 | -------------------------------------------------------------------------------- /src/js/Main.jsx: -------------------------------------------------------------------------------- 1 | import 'sass/style.sass'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import { createStore } from 'redux'; 7 | import { Provider } from 'react-redux'; 8 | 9 | import LoginWindow from './Components/LoginWindow'; 10 | import Notifications from './Utils/Notifications'; 11 | 12 | import { getDefaultState, PrimaryReducer } from './Reducers/PrimaryReducer'; 13 | import { addAdditionalSettings } from './Reducers/SettingsReducer'; 14 | 15 | 16 | export default function Main() { 17 | let initialState = getDefaultState(); 18 | initialState = addAdditionalSettings(initialState); 19 | 20 | let store; 21 | 22 | if (window.__REDUX_DEVTOOLS_EXTENSION__) { 23 | store = createStore( 24 | PrimaryReducer, 25 | initialState, 26 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 27 | ); 28 | } else { 29 | store = createStore(PrimaryReducer, initialState); 30 | } 31 | 32 | ReactDOM.render( 33 | 34 | 35 | , 36 | document.getElementById('login-window-mount') 37 | ); 38 | } 39 | 40 | 41 | window.onload = (e) => { 42 | // Add notifications to the global scope for error handling 43 | window.notifications = new Notifications(); 44 | 45 | let init = () => { 46 | Main(); 47 | document.getElementById("password-field").focus(); 48 | }; 49 | 50 | // Horribly convoluted for necessity because reasons 51 | if (window.__debug === false) { 52 | if (window.lightdm === undefined) { 53 | document.addEventListener('GreeterReady', () => { 54 | init(); 55 | }); 56 | } else { 57 | init(); 58 | } 59 | } else { 60 | init(); 61 | } 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /src/js/Reducers/PrimaryReducer.js: -------------------------------------------------------------------------------- 1 | import { SettingsReducer } from "./SettingsReducer"; 2 | import * as SystemOperations from "../Logic/SystemOperations"; 3 | 4 | export function getDefaultState() { 5 | return { 6 | "info": { 7 | "hostname": window.lightdm.hostname, 8 | "language": window.lightdm.language 9 | }, 10 | "user": SystemOperations.findInitialUser(), 11 | "session": SystemOperations.findInitialSession() 12 | }; 13 | } 14 | 15 | export const PrimaryReducer = (state, action) => { 16 | if (action.type.startsWith("SETTINGS")) { 17 | return SettingsReducer(state, action); 18 | } 19 | 20 | switch (action.type) { 21 | case "AUTH_SET_ACTIVE_SESSION": 22 | var session = action.session; 23 | 24 | if (typeof session === typeof String()) { 25 | session = SystemOperations.findSession(session); 26 | } 27 | 28 | return { ...state, "session": session }; 29 | 30 | case "AUTH_SET_ACTIVE_USER": 31 | return { ...state, "user": action.user }; 32 | 33 | default: 34 | return state; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/js/Reducers/SettingsReducer.js: -------------------------------------------------------------------------------- 1 | /* eslint { no-redeclare: 0 } */ 2 | import * as Settings from '../Logic/Settings'; 3 | import { setPageZoom } from '../Utils/Utils'; 4 | 5 | export function addAdditionalSettings(state) { 6 | // Define our defaults 7 | 8 | let distroDefault = (window.__debug === true) ? "src/test/logos/archlinux.png" : "/usr/share/lightdm-webkit/themes/lightdm-webkit-theme-aether/src/img/logos/archlinux.png"; 9 | 10 | let defaults = { 11 | "active": false, 12 | "minimized": false, 13 | "distro": distroDefault, 14 | "default_user": "", 15 | "page_zoom": 1.0, 16 | 17 | "avatar_enabled": true, 18 | "avatar_size": "200px", 19 | "avatar_shape": "circle", 20 | "avatar_background_enabled": true, 21 | 22 | "font_scale": 1.0, 23 | 24 | "date_enabled": true, 25 | "date_format": "%A, the %o of %B", 26 | 27 | "experimental_stars_enabled": false, 28 | 29 | "time_enabled": true, 30 | "time_format": "%H:%M", 31 | 32 | "hostname_enabled": true, 33 | 34 | "user_switcher_enabled": true, 35 | 36 | "command_shutdown_enabled": true, 37 | "command_reboot_enabled": true, 38 | "command_hibernate_enabled": true, 39 | "command_sleep_enabled": true, 40 | 41 | "style_command_logo_desaturate": false, 42 | "style_command_logo_brightness": 100, 43 | "style_command_icons_enabled": true, 44 | "style_command_text_align": "left", 45 | "style_command_background_color": "hsla(0, 0%, 22%, 1)", 46 | "style_command_icon_color": "hsla(349, 98%, 65%, 1)", 47 | "style_command_text_color": "hsla(0, 100%, 100%, 1)", 48 | "style_login_border_color": "hsla(0, 100%, 100%, 0.1)", 49 | "style_login_border_enabled": true, 50 | "style_login_button_color": "hsla(0, 100%, 100%, 0.22)", 51 | "style_login_button_text_color": "hsla(0, 100%, 100%, 1)", 52 | "style_login_gradient_top_color": "hsla(18, 100%, 61%, 0.66)", 53 | "style_login_gradient_bottom_color": "hsla(339, 94%, 64%, 1)", 54 | "style_login_username_bold": true, 55 | "style_login_username_capitalization": "default", 56 | "style_login_username_color": "hsla(0, 100%, 100%, 1)", 57 | "style_login_username_italic": true, 58 | 59 | "window_border_radius": "4px", 60 | "window_font_size": "1em" 61 | }; 62 | 63 | let settings = {}; 64 | 65 | for (let key of Object.keys(defaults)) { 66 | settings[key] = Settings.requestSetting(key, defaults[key]); 67 | } 68 | 69 | return { ...state, "settings": settings, "cachedSettings": settings }; 70 | } 71 | 72 | 73 | export const SettingsReducer = (state, action) => { 74 | switch(action.type) { 75 | case 'SETTINGS_LOGO_CHANGE': 76 | var newSettings = { ...state.settings, "distro": action.path }; 77 | 78 | return { ...state, "settings": newSettings }; 79 | 80 | case 'SETTINGS_REJECT': 81 | // Restore settings from the 'default' state. 82 | var newSettings = { ...state.cachedSettings }; 83 | 84 | // Create a notification 85 | window.notifications.generate("Reverted to previous settings, no changes saved.", "success"); 86 | 87 | // This shouldn't be here. It is, though. 88 | setPageZoom(newSettings.page_zoom); 89 | 90 | return { ...state, "settings": newSettings }; 91 | 92 | case 'SETTINGS_APPLY_THEME': 93 | var newSettings = { ...state.cachedSettings, ...action.theme }; 94 | 95 | // Create a notification 96 | window.notifications.generate(`Loaded ${ action.name } theme. Remember to save!`, "success"); 97 | 98 | // This shouldn't be here. It is, though. 99 | setPageZoom(newSettings.page_zoom); 100 | 101 | return { ...state, "settings": newSettings }; 102 | 103 | case 'SETTINGS_SAVE': 104 | // Cycle to localStorage for persistence. 105 | for (let key of Object.keys(state.settings)) { 106 | Settings.saveSetting(key, state.settings[key]); 107 | } 108 | 109 | // Save our new settings as the 'default' state. 110 | var newCache = { ...state.settings }; 111 | 112 | // Create a notification 113 | window.notifications.generate("Settings saved.", "success"); 114 | 115 | return { ...state, "cachedSettings": newCache }; 116 | 117 | case 'SETTINGS_SET_VALUE': 118 | var newSettings = { ...state.settings }; 119 | 120 | newSettings[action.name] = action.value; 121 | 122 | // This shouldn't be here. It is, though. 123 | setPageZoom(newSettings.page_zoom); 124 | 125 | return { ...state, "settings": newSettings }; 126 | 127 | case 'SETTINGS_TOGGLE_ACTIVE': 128 | var newSettings = { ...state.settings, "active": !state.settings.active }; 129 | 130 | // This shouldn't be here. It is, though. 131 | var el = document.getElementById("settings"); 132 | 133 | if (newSettings.active === true) { 134 | el.className = el.className.replace(" hidden", ""); 135 | } else { 136 | el.className += " hidden"; 137 | } 138 | 139 | return { ...state, "settings": newSettings }; 140 | 141 | case 'SETTINGS_TOGGLE_VALUE': 142 | var newSettings = { ...state.settings }; 143 | 144 | newSettings[action.name] = !newSettings[action.name]; 145 | 146 | return { ...state, "settings": newSettings }; 147 | 148 | case 'SETTINGS_WINDOW_MINIMIZE': 149 | // This shouldn't be here. It is, though. 150 | var categories = document.querySelectorAll(".settings-categories")[0]; 151 | var section = document.querySelectorAll(".settings-section")[0]; 152 | 153 | // Check if the window is already minimized. 154 | if (categories.className.indexOf('minimize') !== -1) { 155 | categories.className = categories.className.replace('minimize', ''); 156 | section.className = section.className.replace('minimize', ''); 157 | } else { 158 | categories.className = categories.className + ' minimize'; 159 | section.className = section.className + ' minimize'; 160 | } 161 | 162 | return state; 163 | 164 | default: 165 | return state; 166 | } 167 | }; 168 | -------------------------------------------------------------------------------- /src/js/Themes/ThemeMatcher.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { connect } from 'react-redux'; 5 | 6 | // TODO: Decide whether to unify or split themes. 7 | 8 | class ThemeMatcher extends React.Component { 9 | render() { 10 | return ; 11 | } 12 | } 13 | 14 | 15 | ThemeMatcher.propTypes = { 16 | 'currentTheme': PropTypes.string.isRequired 17 | }; 18 | 19 | 20 | export default connect( 21 | (state) => { 22 | return { 23 | 'currentTheme': state.settings.theme 24 | }; 25 | }, 26 | null 27 | )(ThemeMatcher); 28 | -------------------------------------------------------------------------------- /src/js/Utils/Notifications.js: -------------------------------------------------------------------------------- 1 | export default class Notifications { 2 | constructor() { 3 | this.container = document.querySelectorAll('.notifications-container')[0]; 4 | 5 | if (window.__debug === true) { 6 | this.generate("Hey there!", "success"); 7 | 8 | setTimeout(() => { 9 | this.generate("TIP: Click the logo to switch wallpapers."); 10 | }, 2000); 11 | 12 | setTimeout(() => { 13 | this.generate("TIP: Access settings by hovering over the bottom left of your screen!"); 14 | }, 5 * 1000); 15 | } 16 | } 17 | 18 | generate(message, type) { 19 | if (type === undefined) { 20 | type = ""; 21 | } 22 | 23 | let notification = document.createElement('div'); 24 | notification.className = `notification ${ type }`; 25 | notification.innerText = message; 26 | this.container.appendChild(notification); 27 | 28 | setTimeout(() => { 29 | notification.className += " fadeout"; 30 | setTimeout(() => { 31 | this.container.removeChild(notification); 32 | }, 500); 33 | }, 5000); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/js/Utils/Utils.js: -------------------------------------------------------------------------------- 1 | export const scale = (start, end, percentage, round) => { 2 | if (round === undefined) { 3 | round = true; 4 | } 5 | 6 | let scaledValue = start + ((end - start) * percentage); 7 | if (round) { 8 | return Math.round(scaledValue); 9 | } else { 10 | return scaledValue; 11 | } 12 | }; 13 | 14 | 15 | export function randomRange(min, max, decimals=0) { 16 | let result = Math.random() * (max - min) + min; 17 | 18 | if (decimals === 0) { 19 | result = Math.floor(result); 20 | } else { 21 | result = Math.floor(result * Math.pow(10, decimals)) / Math.pow(10, decimals); 22 | } 23 | 24 | return result; 25 | } 26 | 27 | 28 | export const padZeroes = (i) => { 29 | return (i < 10) ? "0" + i : i; 30 | }; 31 | 32 | 33 | export const setPageZoom = (value) => { 34 | document.getElementById("root").style.zoom = value; 35 | 36 | return true; 37 | }; 38 | 39 | 40 | export const interpolatePoints = (a, b, ratio) => { 41 | let x = a.x + (b.x - a.x) * ratio; 42 | let y = a.y + (b.y - a.y) * ratio; 43 | 44 | return { x, y }; 45 | }; 46 | -------------------------------------------------------------------------------- /src/sass/_animations.sass: -------------------------------------------------------------------------------- 1 | $material-curve: cubic-bezier(0.4, 0.0, 0.2, 1) 2 | 3 | @keyframes drop-in-notifications 4 | 0% 5 | top: -200px 6 | 7 | 100% 8 | top: 0px 9 | 10 | @keyframes drop-in-options 11 | 0% 12 | top: -100px 13 | opacity: 0 14 | 15 | 100% 16 | top: 0px 17 | opacity: 1 18 | 19 | @keyframes error-shake 20 | 0% 21 | transform: translateX(0px) scale(1) 22 | 23 | 11% 24 | transform: translateX(-10px) scale(1.05) 25 | 26 | 22% 27 | transform: translateX(10px) scale(1.05) 28 | 29 | 33% 30 | transform: translateX(-10px) scale(1.05) 31 | 32 | 44% 33 | transform: translateX(10px) scale(1.05) 34 | 35 | 55% 36 | transform: translateX(-10px) scale(1.05) 37 | 38 | 66% 39 | transform: translateX(10px) scale(1.05) 40 | 41 | 77% 42 | transform: translateX(10px) scale(1.05) 43 | 44 | 88% 45 | transform: translateX(10px) scale(1) 46 | 47 | 100% 48 | transform: translateX(0px) 49 | 50 | @keyframes login-fade-out 51 | 0% 52 | opacity: 1 53 | transform: translateY(0) 54 | 55 | 90% 56 | transform: translateY(10px) 57 | 58 | 100% 59 | opacity: 0 60 | 61 | @keyframes login-fade-in 62 | 0% 63 | opacity: 0 64 | transform: translateY(5px) 65 | 66 | 100% 67 | opacity: 1 68 | transform: translateY(0px) 69 | 70 | @keyframes switcher-fade-in 71 | 0% 72 | opacity: 0 73 | transform: translateY(5px) scale(0.95) 74 | 75 | 100% 76 | opacity: 1 77 | transform: translateY(0px) scale(1) 78 | 79 | @keyframes switcher-fade-out 80 | 0% 81 | opacity: 1 82 | transform: translateY(0px) scale(1) 83 | 84 | 100% 85 | opacity: 0 86 | transform: translateY(-10px) scale(0.95) 87 | 88 | 89 | 90 | @keyframes slide-in-left 91 | 0% 92 | transform: translateX(-50px) 93 | opacity: 0 94 | 95 | 100% 96 | transform: translateX(0) 97 | opacity: 1 98 | 99 | @keyframes slide-in-right 100 | 0% 101 | transform: translateX(50px) 102 | opacity: 0 103 | 104 | 100% 105 | transform: translateX(0) 106 | opacity: 1 107 | 108 | @keyframes slide-out-left 109 | 0% 110 | transform: translateX(0) 111 | opacity: 1 112 | 113 | 100% 114 | transform: translateX(-50px) 115 | opacity: 0 116 | 117 | @keyframes slide-out-right 118 | 0% 119 | transform: translateX(0) 120 | opacity: 1 121 | 122 | 99% 123 | transform: translateX(50px) 124 | opacity: 0 125 | 126 | 100% 127 | transform: translateX(50px) 128 | opacity: 0 129 | display: none 130 | 131 | 132 | @keyframes wallpaper-fade-out 133 | 0% 134 | opacity: 1 135 | 136 | 100% 137 | opacity: 0 138 | 139 | @keyframes window-fade-in 140 | 0% 141 | opacity: 0 142 | transform: scale(0.7) 143 | 144 | 100% 145 | opacity: 1 146 | transform: scale(1) 147 | -------------------------------------------------------------------------------- /src/sass/_config.sass: -------------------------------------------------------------------------------- 1 | // Images 2 | $distro-logo: url("src/img/logos/archlinux.png") 3 | 4 | $avatar-background: url("src/img/avatar-background.png") 5 | 6 | $dropdown-caret: url("src/img/dropdown-caret.png") 7 | 8 | $dropdown-caret-inverse: url("src/img/dropdown-caret-inverse.png") 9 | 10 | $check-mark: url("src/img/check-mark.png") 11 | 12 | // Text 13 | $primary-color: #fff 14 | 15 | // Dimensions, Positioning 16 | $panel-padding-x: 35px 17 | $panel-padding-y: 15px 18 | 19 | $command-panel-width: 255px 20 | $login-panel-width: 595px 21 | -------------------------------------------------------------------------------- /src/sass/_fonts.sass: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family: 'Open Sans' 3 | src: url('src/font/OpenSans-Regular-webfont.woff') format('woff') 4 | font-weight: normal 5 | font-style: normal 6 | 7 | @font-face 8 | font-family: 'Open Sans' 9 | src: url('src/font/OpenSans-Bold-webfont.woff') format('woff') 10 | font-weight: bold 11 | font-style: normal 12 | 13 | @font-face 14 | font-family: 'Open Sans' 15 | src: url('src/font/OpenSans-Italic-webfont.woff') format('woff') 16 | font-weight: normal 17 | font-style: italic 18 | 19 | @font-face 20 | font-family: 'Open Sans' 21 | src: url('src/font/OpenSans-BoldItalic-webfont.woff') format('woff') 22 | font-weight: bold 23 | font-style: italic 24 | -------------------------------------------------------------------------------- /src/sass/_reset.sass: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) */ 4 | 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, hgroup, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video 18 | margin: 0 19 | padding: 0 20 | border: 0 21 | font-size: 100% 22 | font: inherit 23 | vertical-align: baseline 24 | 25 | /* HTML5 display-role reset for older browsers */ 26 | article, aside, details, figcaption, figure, 27 | footer, header, hgroup, menu, nav, section 28 | display: block 29 | 30 | body 31 | line-height: 1 32 | 33 | ol, ul 34 | list-style: none 35 | 36 | blockquote, q 37 | quotes: none 38 | 39 | blockquote:before, blockquote:after, 40 | q:before, q:after 41 | content: '' 42 | content: none 43 | 44 | table 45 | border-collapse: collapse 46 | border-spacing: 0 -------------------------------------------------------------------------------- /src/sass/_scroll.sass: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar 2 | width: 6px 3 | background-color: rgba(0, 0, 0, 0) 4 | border-radius: 100px 5 | 6 | opacity: 0.2 7 | 8 | transition: background-color 300ms ease, opacity 500ms ease 9 | 10 | &:hover 11 | background-color: rgba(0, 0, 0, 0.09) 12 | opacity: 1 13 | 14 | ::-webkit-scrollbar-thumb:vertical 15 | /* This is the EXACT color of Mac OS scrollbars. */ 16 | background-color: rgba(0, 0, 0, 0.5) 17 | border-radius: 100px 18 | 19 | opacity: 0.2 20 | 21 | transition: background-color 300ms ease, opacity 500ms ease 22 | 23 | &:active 24 | background-color: rgba(0, 0, 0, 0.61) 25 | border-radius: 100px 26 | 27 | opacity: 1 28 | -------------------------------------------------------------------------------- /src/sass/_utility.sass: -------------------------------------------------------------------------------- 1 | .disabled 2 | opacity: 0.4 3 | 4 | .disabled:hover 5 | cursor: not-allowed !important 6 | 7 | .noselect 8 | -webkit-user-select: none 9 | 10 | .hidden 11 | display: none !important 12 | 13 | .invisible 14 | visibility: hidden !important 15 | -------------------------------------------------------------------------------- /src/sass/style.sass: -------------------------------------------------------------------------------- 1 | /* **************************************************************** 2 | /* d8888 888 888 3 | /* d88888 888 888 4 | /* d88P888 888 888 5 | /* d88P 888 .d88b. 888888 88888b. .d88b. 888d888 6 | /* d88P 888 d8P Y8b 888 888 "88b d8P Y8b 888P" 7 | /* d88P 888 88888888 888 888 888 88888888 888 8 | /* d8888888888 Y8b. Y88b. 888 888 Y8b. 888 9 | /* d88P 888 "Y8888 "Y888 888 888 "Y8888 888 10 | /* 11 | /* =================================================== 12 | /* https://github.com/noisek/aether | Noi Sek 13 | /* 14 | /* TABLE OF CONTENTS: 15 | /* -- #0.1a Root Styles (You are here) 16 | /* -- #0.2a Page Body 17 | /* -- #0.3a Login Window 18 | /* -- #0.3b Command Panel 19 | /* -- #0.3c User Panel 20 | /* -- #0.4a Settings 21 | /* -- #0.4b Settings General 22 | /* -- #0.4c Settings Style 23 | /* -- #0.4d Settings Themes 24 | /* -- #0.5a Misc. 25 | /* 26 | /****************************************************************/ 27 | 28 | @import 'reset' 29 | @import 'fonts' 30 | @import 'config' 31 | @import 'scroll' 32 | @import 'utility' 33 | @import 'animations' 34 | 35 | html 36 | box-sizing: border-box 37 | 38 | *, *:before, *:after 39 | box-sizing: inherit 40 | 41 | body 42 | background: #222 43 | 44 | font-family: "Open Sans" 45 | 46 | overflow: hidden 47 | 48 | position: fixed 49 | top: 0 50 | left: 0 51 | 52 | height: 100vh 53 | width: 100vw 54 | 55 | text-rendering: optimizeLegibility !important 56 | -webkit-font-smoothing: antialiased !important 57 | -moz-osx-font-smoothing: grayscale !important 58 | 59 | 60 | /***************************************************************** 61 | -- #0.2a Page Body 62 | =============================================================== 63 | Body elements external to the login window itself. 64 | /*****************************************************************/ 65 | 66 | .date 67 | color: #fff 68 | opacity: 0 69 | 70 | font-size: 0.75em 71 | font-style: italic 72 | text-align: right 73 | letter-spacing: 1px 74 | 75 | position: absolute 76 | top: 50% 77 | left: 50% 78 | 79 | margin: 265px 0px 0px -425px 80 | 81 | width: 850px 82 | 83 | transition: all 1s 84 | 85 | @extend .noselect 86 | 87 | &.loaded 88 | opacity: 1 89 | 90 | em 91 | font-weight: bold 92 | 93 | 94 | .notifications-container 95 | position: absolute 96 | top: 10px 97 | 98 | overflow: hidden 99 | 100 | width: 100% 101 | 102 | .notification 103 | color: #2f0909 104 | background: rgba(252,237,238,0.75) 105 | border: 1px solid rgba(255, 255, 255, 0.15) 106 | 107 | position: relative 108 | top: -200px 109 | 110 | padding: 15px 111 | margin: 0 auto 25px auto 112 | 113 | width: 80vw 114 | 115 | z-index: 3 116 | 117 | animation: drop-in-notifications 0.5s forwards 118 | border-radius: 2px 119 | transition: opacity 0.5s 120 | 121 | &.error 122 | color: #fff 123 | background: rgba(180, 30, 30, 0.65) 124 | 125 | &.success 126 | background: rgba(120, 255, 64, 0.65) 127 | 128 | &.fadeout 129 | opacity: 0 130 | 131 | .wallpaper-background, .wallpaper-foreground 132 | background-size: cover 133 | 134 | overflow: hidden 135 | position: fixed 136 | 137 | height: 100vh 138 | width: 100vw 139 | 140 | z-index: -1 141 | 142 | 143 | .wallpaper-foreground 144 | opacity: 1 145 | 146 | &.fadeout 147 | animation: wallpaper-fade-out 600ms forwards 148 | animation-timing-function: ease-out 149 | 150 | .cycler-preloader 151 | display: none 152 | 153 | /***************************************************************** 154 | -- #0.3a Login Window 155 | =============================================================== 156 | Contains the actual login dialogue. 157 | /*****************************************************************/ 158 | 159 | .login-window-container 160 | position: relative 161 | 162 | overflow: hidden 163 | 164 | height: 100vh 165 | width: 100vw 166 | 167 | 168 | .login-window 169 | display: flex 170 | 171 | .command-panel 172 | display: flex 173 | flex-direction: column 174 | 175 | width: 30% 176 | 177 | .user-panel 178 | border-style: solid 179 | border-bottom: none 180 | border-width: 1px 181 | 182 | height: 100% 183 | width: 70% 184 | 185 | 186 | .login-window 187 | overflow: hidden 188 | 189 | opacity: 0 190 | 191 | // Vertical Center 192 | position: absolute 193 | top: 50% 194 | left: 50% 195 | 196 | margin: -250px 0px 0px -425px 197 | 198 | height: 500px 199 | width: 850px 200 | 201 | border-radius: 4px 202 | transform: scale(0.7) 203 | 204 | animation: window-fade-in 0.5s forwards 205 | animation-delay: 200ms 206 | animation-timing-function: ease-out 207 | 208 | 209 | .command-panel, .user-panel 210 | padding: $panel-padding-y $panel-padding-x 211 | 212 | 213 | .user-panel 214 | color: #fff 215 | border-bottom: none 216 | 217 | & > div 218 | height: 100% 219 | 220 | .login-panel-main 221 | position: relative 222 | z-index: 2 223 | 224 | height: 100% 225 | 226 | &.fadeout 227 | animation: login-fade-out 500ms ease forwards 228 | z-index: 1 229 | 230 | &.fadein 231 | animation: login-fade-in 200ms ease-out forwards 232 | 233 | .avatar-container 234 | height: 40% 235 | padding-top: 15px 236 | padding-bottom: 75px 237 | 238 | .avatar-background 239 | background: $avatar-background no-repeat center center 240 | 241 | margin: 0 auto 242 | 243 | height: 155px 244 | width: 154px 245 | 246 | transform: translateX(-10px) 247 | transition: all 1s 248 | 249 | &.avatar-background-hidden 250 | background: transparent 251 | 252 | .avatar-mask 253 | border-radius: 100% 254 | overflow: hidden 255 | 256 | height: 125px 257 | width: 125px 258 | 259 | transform: translate(20px, 15px) 260 | 261 | .user-avatar 262 | margin: 0 auto 263 | 264 | height: 125px 265 | width: 125px 266 | 267 | .session-button 268 | font-size: 0.75em 269 | height: 30px 270 | 271 | font-weight: bold 272 | font-style: italic 273 | line-height: 1.2em 274 | vertical-align: middle 275 | 276 | text-overflow: ellipsis 277 | overflow: hidden 278 | white-space: nowrap 279 | 280 | padding-top: 8px 281 | padding-left: 10px 282 | padding-right: 30px 283 | 284 | border-radius: 2px 285 | 286 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.05) 287 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.12) 288 | 289 | .text 290 | display: inline-block 291 | transform: translateX(0px) scale(1.0) 292 | transition: transform 200ms ease-out 293 | 294 | &:hover 295 | cursor: pointer 296 | 297 | @extend .noselect 298 | 299 | .login-form 300 | height: 55% 301 | display: block 302 | 303 | .user-username 304 | text-align: center 305 | 306 | font-size: 2.66em 307 | 308 | @extend .noselect 309 | 310 | .user-input-container 311 | animation: slide-in-right 200ms cubic-bezier(0.215, 0.610, 0.355, 1.000) both 312 | 313 | &.animate-out 314 | animation: slide-out-right 200ms cubic-bezier(0.550, 0.055, 0.675, 0.190) both 315 | height: 0px 316 | 317 | .user-password-container 318 | margin: 15px auto 319 | width: 200px 320 | 321 | .user-password 322 | color: #fff 323 | 324 | background: none 325 | outline: none 326 | border: none 327 | border-bottom: 2px solid rgba(0, 0, 0, 0.06) 328 | border-radius: 3px 329 | 330 | font-size: 14pt 331 | font-weight: bold 332 | letter-spacing: 2px 333 | 334 | padding-left: 0.5em 335 | padding-right: 0.5em 336 | 337 | width: 100% 338 | 339 | transition: opacity 1s, border-bottom 150ms ease 340 | 341 | &:focus, &:active 342 | border-bottom: 2px solid rgba(255, 255, 255, 0.6) 343 | 344 | &::-webkit-input-placeholder 345 | color: rgba(255, 255, 255, 0.4) 346 | @extend .noselect 347 | 348 | &.error 349 | animation: error-shake 600ms forwards 350 | 351 | &.error, &.error:focus, &.error:active 352 | border-bottom: 2px solid rgba(175, 3, 3, 0.6) 353 | 354 | &.error::-webkit-input-placeholder 355 | color: #af0303 356 | 357 | .submit-row 358 | display: flex 359 | 360 | margin: 0 auto 361 | width: 200px 362 | 363 | .left 364 | display: flex 365 | justify-content: space-between 366 | flex-grow: 1 367 | 368 | padding-right: 15px 369 | margin-right: 15px 370 | 371 | @extend .session-button 372 | 373 | &:hover 374 | background-position: 90% 55% 375 | 376 | div svg 377 | transform: translateY(2px) 378 | 379 | & > div svg 380 | height: 5px 381 | 382 | transform: translateY(0px) 383 | transition: transform 200ms ease-in-out 384 | 385 | path 386 | fill: currentColor 387 | stroke: currentColor 388 | stroke-width: 2px 389 | 390 | 391 | .right .submit-button 392 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.05) 393 | 394 | &:hover 395 | cursor: pointer 396 | 397 | polygon 398 | transform: translateX(2px) 399 | 400 | & > div 401 | border-radius: 4px 402 | height: 30px 403 | 404 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.05) 405 | 406 | input[type=submit] 407 | display: none 408 | 409 | svg 410 | height: 30px 411 | 412 | filter: drop-shadow(0px 2px 1px rgba(0, 0, 0, 0.10)) 413 | 414 | circle 415 | // fill: rgba(255, 255, 255, 0.35) 416 | r: 0 417 | 418 | polygon 419 | fill: currentColor 420 | 421 | stroke: rgba(42, 19, 15, 0.05) 422 | stroke-width: 1px 423 | 424 | transform: scale(1) translateX(0px) 425 | transform-origin: center 426 | 427 | transition: all 200ms cubic-bezier(0.64, 0.15, 0.18, 0.52) 428 | 429 | .login-session-switcher 430 | display: flex 431 | flex-wrap: wrap 432 | justify-content: space-between 433 | 434 | &.no-justify 435 | justify-content: initial 436 | 437 | .login-session-item 438 | margin-right: 10px 439 | 440 | margin-top: 20px 441 | 442 | ._transform-wrapper 443 | transform: translateY(0px) 444 | transition: transform 350ms $material-curve 445 | 446 | .login-session-item 447 | @extend .session-button 448 | 449 | opacity: 0 450 | 451 | margin-bottom: 10px 452 | width: 125px 453 | 454 | &.active 455 | .login-session-item 456 | animation: slide-in-left 300ms cubic-bezier(0.215, 0.610, 0.355, 1.000) forwards 457 | 458 | ._transform-wrapper:hover 459 | transform: translateY(-2px) 460 | 461 | ._transform-wrapper:active 462 | transform: translateY(2px) 463 | 464 | .login-panel-switcher 465 | display: block 466 | opacity: 0 467 | 468 | position: relative 469 | top: -100% 470 | 471 | z-index: 1 472 | 473 | height: 100% 474 | 475 | &.active 476 | animation: 200ms switcher-fade-in ease-out forwards 477 | animation-delay: 150ms 478 | 479 | z-index: 2 480 | 481 | &.fadeout 482 | animation: 200ms switcher-fade-out ease-out forwards 483 | 484 | ul 485 | list-style-type: none 486 | transform-style: preserve-3d 487 | 488 | padding-top: 100px 489 | height: 95% 490 | 491 | li 492 | display: inline-block 493 | padding-top: 25px 494 | 495 | width: 33.33% 496 | 497 | transition: -webkit-filter 200ms, opacity 300ms 498 | 499 | .avatar-background 500 | background: $avatar-background no-repeat center center 501 | 502 | margin: 0 auto 503 | 504 | height: 155px 505 | width: 154px 506 | 507 | transform: translateX(-10px) 508 | transition: all 1s 509 | 510 | &.avatar-background-hidden 511 | background: transparent 512 | 513 | .avatar-mask 514 | border-radius: 100% 515 | overflow: hidden 516 | 517 | height: 125px 518 | width: 125px 519 | 520 | transform: translate(20px, 15px) 521 | 522 | .avatar-name 523 | text-align: center 524 | 525 | .username 526 | font-size: 16pt 527 | font-weight: bold 528 | .real-name 529 | font-size: 12pt 530 | font-style: italic 531 | 532 | .user-avatar 533 | margin: 0 auto 534 | 535 | height: 125px 536 | width: 125px 537 | 538 | li:not(.active) 539 | display: none 540 | -webkit-filter: blur(1px) 541 | 542 | &:hover 543 | cursor: pointer 544 | opacity: 0.9 545 | -webkit-filter: blur(0.5px) 546 | 547 | li.active 548 | transform: translateZ(10px) 549 | 550 | li.previous, li.next 551 | display: inline-block 552 | opacity: 0.8 553 | 554 | li.previous 555 | transform: translate3d(50px, 0px, -10px) scale(0.9) 556 | 557 | li.next 558 | transform: translate3d(-50px, 0px, -10px) scale(0.9) 559 | 560 | .header 561 | font-size: 11pt 562 | font-style: italic 563 | text-align: center 564 | letter-spacing: 1px 565 | 566 | position: absolute 567 | 568 | height: 5% 569 | width: 100% 570 | 571 | em 572 | font-weight: bold 573 | 574 | .login-panel-main .bottom, .login-panel-switcher .bottom 575 | padding-top: 15px 576 | height: 5% 577 | 578 | .left 579 | color: #fff 580 | float: left 581 | 582 | font-size: 0.75em 583 | letter-spacing: 1px 584 | 585 | .left 586 | font-weight: bold 587 | width: 30% 588 | 589 | @extend .noselect 590 | 591 | &:hover 592 | cursor: pointer 593 | 594 | /***************************************************************** 595 | -- #0.3b Command Panel 596 | =============================================================== 597 | Contains the system 'commands'. 598 | /*****************************************************************/ 599 | 600 | .command-panel 601 | color: #fff 602 | height: 100% 603 | 604 | .distro-logo 605 | background: $distro-logo no-repeat center center 606 | background-size: contain 607 | 608 | margin: 0px auto 609 | 610 | height: 125px 611 | width: 125px 612 | 613 | transition: opacity 400ms 614 | 615 | &:hover 616 | cursor: pointer 617 | 618 | &:active 619 | opacity: 0.8 620 | 621 | .distro-wrapper 622 | padding-top: 35px 623 | 624 | height: 52% 625 | 626 | .options 627 | position: relative 628 | top: -100px 629 | opacity: 0 630 | 631 | &.active 632 | animation: drop-in-options 200ms forwards 633 | animation-timing-function: ease-out 634 | 635 | .button-accept, .button-reject 636 | background: rgba(0, 0, 0, 0.2) 637 | position: relative 638 | opacity: 0.4 639 | 640 | text-align: center 641 | 642 | margin-top: 10px 643 | padding: 7px 8px 644 | 645 | height: 30px 646 | width: 30px 647 | 648 | border-radius: 100% 649 | 650 | &:hover 651 | cursor: pointer 652 | opacity: 1 653 | 654 | 655 | .button-accept 656 | float: right 657 | left: -15% 658 | 659 | &:hover 660 | background: #6e9200 661 | 662 | .button-reject 663 | float: left 664 | left: 15% 665 | 666 | &:hover 667 | background: #b93300 668 | 669 | .options-wrapper 670 | overflow: hidden 671 | 672 | height: 45px 673 | width: 100% 674 | 675 | 676 | .command 677 | display: flex 678 | margin-bottom: 25px 679 | 680 | transition: all 0.5s 681 | 682 | @extend .noselect 683 | 684 | &:hover 685 | cursor: pointer 686 | opacity: 0.6 687 | 688 | .commands-wrapper 689 | height: 43% 690 | 691 | .command .icon-wrapper, .command .text 692 | display: inline-block 693 | 694 | @extend .noselect 695 | 696 | .command .icon-wrapper 697 | margin-right: 10px 698 | width: 30px 699 | 700 | .command .icon-wrapper svg 701 | height: 20px 702 | width: 30px 703 | 704 | .command .icon-wrapper svg > * 705 | fill: currentColor 706 | 707 | .command .text 708 | font-size: 1.33em 709 | letter-spacing: 2px 710 | text-transform: uppercase 711 | vertical-align: 2px 712 | 713 | width: 100% 714 | 715 | .bottom 716 | height: 5% 717 | padding-top: 15px 718 | 719 | .bottom .left, .bottom .right 720 | float: left 721 | 722 | font-size: 0.75em 723 | font-weight: bold 724 | letter-spacing: 1px 725 | 726 | .bottom .left 727 | color: rgba(255, 255, 255, 0.65) 728 | 729 | overflow: hidden 730 | 731 | text-overflow: ellipsis 732 | text-transform: uppercase 733 | white-space: nowrap 734 | 735 | width: 70% 736 | 737 | @extend .noselect 738 | 739 | .bottom .right 740 | text-align: right 741 | 742 | opacity: 0 743 | 744 | width: 30% 745 | 746 | transition: opacity 1s 747 | 748 | @extend .noselect 749 | 750 | .bottom .clock.loaded 751 | opacity: 1 752 | 753 | 754 | /***************************************************************** 755 | -- #0.4a Settings 756 | =============================================================== 757 | Settings dialogue. 758 | /*****************************************************************/ 759 | 760 | .settings 761 | display: flex 762 | position: absolute 763 | 764 | max-width: 900px 765 | 766 | opacity: 1 767 | 768 | & > div 769 | display: flex 770 | flex-wrap: wrap 771 | 772 | width: 100% 773 | 774 | .settings-categories 775 | width: 17% 776 | 777 | .settings-section 778 | display: flex 779 | flex-direction: column 780 | 781 | width: 83% 782 | 783 | .settings-section .save-dialogue 784 | display: flex 785 | justify-content: flex-end 786 | 787 | 788 | .settings .settings-categories 789 | ul li 790 | color: hsl(0, 0%, 61%) 791 | background: hsl(0, 0%, 88%) 792 | 793 | font-weight: bold 794 | font-variant: small-caps 795 | 796 | padding: 15px 25px 797 | 798 | transition: color 300ms ease, background-color 300ms ease 799 | 800 | ul li:hover 801 | cursor: pointer 802 | 803 | color: hsl(0, 0%, 30%) 804 | background: hsl(0, 0%, 91%) 805 | 806 | ul li.active, ul li.active:hover 807 | color: hsl(0, 0%, 30%) 808 | background: hsla(0, 0%, 98%, 1) 809 | 810 | 811 | .settings .settings-handle 812 | background: #222 813 | height: 25px 814 | width: 100% 815 | 816 | &:hover 817 | cursor: -webkit-grab 818 | 819 | &:active 820 | cursor: -webkit-grabbing !important 821 | 822 | &:active:hover 823 | cursor: -webkit-grabbing !important 824 | 825 | ul 826 | display: flex 827 | justify-content: flex-end 828 | 829 | overflow: hidden 830 | 831 | height: 100% 832 | width: 100% 833 | 834 | li 835 | color: #000 836 | font-weight: bold 837 | 838 | padding: 5px 15px 839 | height: 100% 840 | 841 | li:hover 842 | cursor: pointer 843 | 844 | li.settings-minimize 845 | color: #fff 846 | background: #424242 847 | 848 | li.settings-close 849 | background: #de3838 850 | 851 | 852 | .settings .settings-section 853 | background: hsla(0, 0%, 98%, 1) 854 | 855 | ul h4 856 | font-weight: bold 857 | 858 | ul hr 859 | background: #ececec 860 | color: #ececec 861 | border: 1px solid #ececec 862 | 863 | ul li 864 | list-style-type: none 865 | margin-bottom: 10px 866 | 867 | ul li + h4 868 | margin-top: 35px 869 | 870 | ul li input[type='checkbox'] 871 | position: absolute 872 | left: -9999px 873 | 874 | ul li input[type='text'] 875 | display: inline-block 876 | 877 | padding: 5px 878 | 879 | margin-top: 15px 880 | margin-left: 15px 881 | 882 | width: 50% 883 | 884 | border: 1px solid #e0e0e0 885 | 886 | ul li label:hover 887 | cursor: pointer 888 | 889 | ul li input[type='checkbox'] + label .fake-checkbox 890 | color: hsl(0, 45%, 21%) 891 | 892 | display: inline-block 893 | background: hsl(0, 49%, 90%) 894 | 895 | font-weight: bold 896 | 897 | padding: 10px 898 | margin-left: 10px 899 | 900 | position: relative 901 | top: 5px 902 | 903 | height: 15px 904 | width: 15px 905 | 906 | border: 1px solid #e8e8e8 907 | border-radius: 4px 908 | 909 | &:hover 910 | cursor: pointer 911 | 912 | ul li input[type='checkbox']:checked + label .fake-checkbox 913 | color: hsl(101, 45%, 21%) 914 | background: hsl(100, 50%, 90%) 915 | 916 | ul li input[type='checkbox'] + label .fake-checkbox:after 917 | display: inline-block 918 | content: "×" 919 | 920 | font-size: 1.4em 921 | 922 | position: absolute 923 | top: -0.05rem 924 | left: 0.25rem 925 | 926 | ul li input[type='checkbox']:checked + label .fake-checkbox:after 927 | display: inline-block 928 | content: "✔" 929 | 930 | font-size: 1em 931 | 932 | position: absolute 933 | top: 0.1rem 934 | left: 0.2rem 935 | 936 | label + select 937 | margin-left: 10px 938 | width: 50% 939 | 940 | select 941 | padding: 5px 942 | 943 | 944 | .settings-section .save-dialogue 945 | background: #f3f3f3 946 | border-top: 1px solid #eee 947 | 948 | padding: 10px 15px 949 | 950 | button 951 | outline: none 952 | border: none 953 | 954 | padding: 5px 8px 8px 8px 955 | margin-left: 10px 956 | 957 | border-radius: 2px 958 | 959 | button:hover 960 | cursor: pointer 961 | 962 | button.settings-reject 963 | color: #581212 964 | background: #f35e5e 965 | 966 | button.settings-save 967 | color: #145a07 968 | background: #84ff93 969 | 970 | 971 | .settings-toggler 972 | color: #222 973 | background: #fff 974 | opacity: 0 975 | 976 | font-weight: bold 977 | font-size: 4em 978 | text-align: center 979 | 980 | position: fixed 981 | bottom: 25px 982 | left: 25px 983 | 984 | padding-top: 8px 985 | 986 | height: 85px 987 | width: 85px 988 | 989 | border-radius: 100px 990 | user-select: none 991 | transition: opacity 300ms ease 992 | 993 | &:hover 994 | cursor: pointer 995 | 996 | opacity: 1 997 | 998 | 999 | /***************************************************************** 1000 | -- #0.4b Settings General 1001 | =============================================================== 1002 | General Settings section. 1003 | /*****************************************************************/ 1004 | 1005 | .settings-general 1006 | display: flex 1007 | 1008 | padding: 25px 0px 1009 | 1010 | .left 1011 | padding: 15px 1012 | width: 40% 1013 | 1014 | .right 1015 | padding: 15px 1016 | width: 60% 1017 | 1018 | 1019 | .settings-general .left 1020 | border-right: 2px solid #eee 1021 | 1022 | h6 1023 | font-size: 0.8em 1024 | font-weight: bold 1025 | 1026 | margin-top: 30px 1027 | 1028 | li 1029 | list-style-type: none 1030 | 1031 | select 1032 | margin: 15px 0px 0px 0px 1033 | padding: 5px 1034 | 1035 | width: 100% 1036 | 1037 | select:hover 1038 | cursor: pointer 1039 | 1040 | select option:hover 1041 | cursor: pointer 1042 | 1043 | .preview-logo img 1044 | display: block 1045 | margin: 0 auto 1046 | 1047 | height: 150px 1048 | 1049 | 1050 | .settings-general .right 1051 | height: 400px 1052 | overflow-y: scroll 1053 | 1054 | /***************************************************************** 1055 | -- #0.4c Settings Style 1056 | =============================================================== 1057 | Style Settings section. 1058 | /*****************************************************************/ 1059 | 1060 | .settings-style 1061 | display: flex 1062 | 1063 | padding: 25px 0px 1064 | 1065 | .left 1066 | padding: 25px 1067 | width: 40% 1068 | 1069 | .right 1070 | display: flex 1071 | justify-content: space-between 1072 | 1073 | padding: 25px 1074 | 1075 | width: 100% 1076 | 1077 | & > ul 1078 | width: 47% 1079 | 1080 | .right .color-group 1081 | display: flex 1082 | flex-wrap: wrap 1083 | justify-content: space-between 1084 | 1085 | .right .swatch-container 1086 | display: flex 1087 | justify-content: space-between 1088 | width: 100% 1089 | 1090 | .right .swatch-container .swatch 1091 | display: flex 1092 | flex-wrap: wrap 1093 | 1094 | .right .swatch-container .swatch .swatch-bg 1095 | flex-grow: 1 1096 | 1097 | 1098 | .settings-style 1099 | .left 1100 | border-right: 2px solid #eee 1101 | 1102 | .left li 1103 | display: flex 1104 | align-items: center 1105 | 1106 | .left li label 1107 | flex-grow: 2 1108 | width: 100% 1109 | 1110 | display: flex 1111 | line-height: 1.2em 1112 | 1113 | .left ul li input[type='text'] 1114 | flex-shrink: 2 1115 | margin-top: 0px 1116 | 1117 | .right .color-group .settings-color:last-of-type 1118 | margin-bottom: 35px 1119 | 1120 | .right .settings-item.settings-color 1121 | margin-top: 15px 1122 | width: 48% 1123 | 1124 | .right .settings-item.settings-color label 1125 | display: block 1126 | overflow: hidden 1127 | 1128 | font-size: 0.8em 1129 | text-transform: uppercase 1130 | text-overflow: ellipsis 1131 | white-space: nowrap 1132 | 1133 | width: 100% 1134 | 1135 | .right .settings-item.settings-color .swatch-container 1136 | position: relative 1137 | margin-top: 10px 1138 | 1139 | input[type='text'] 1140 | display: inline-block 1141 | 1142 | margin-left: 0 1143 | margin-top: 0 1144 | 1145 | width: 46% 1146 | 1147 | .swatch 1148 | border: 5px solid #ffffff 1149 | 1150 | height: 35px 1151 | width: 100% 1152 | 1153 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.15) 1154 | transition: box-shadow 1000ms ease 1155 | 1156 | &:hover 1157 | cursor: pointer 1158 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.35) 1159 | 1160 | .swatch-bg 1161 | flex-grow: 1 1162 | 1163 | position: relative 1164 | top: -12.5px 1165 | 1166 | height: 12.5px 1167 | width: 1% 1168 | 1169 | z-index: 1 1170 | 1171 | .swatch-bg-black 1172 | background: #000 1173 | 1174 | .swatch-bg-white 1175 | background: #fff 1176 | 1177 | .swatch-bg-gray 1178 | background: #808080 1179 | 1180 | .swatch-fg 1181 | height: 25px 1182 | width: 100% 1183 | 1184 | z-index: 2 1185 | 1186 | .right .settings-item.settings-color .swatch-container.active 1187 | .chrome-picker 1188 | position: absolute 1189 | left: 90px 1190 | z-index: 4 1191 | 1192 | .colorpicker-background 1193 | position: fixed 1194 | 1195 | top: 25px 1196 | right: 0 1197 | bottom: 0 1198 | left: 100px 1199 | 1200 | height: 100vh 1201 | width: 100vw 1202 | 1203 | z-index: 3 1204 | 1205 | 1206 | /***************************************************************** 1207 | -- #0.4d Settings Themes 1208 | =============================================================== 1209 | Themes Settings section. 1210 | /*****************************************************************/ 1211 | 1212 | .settings-themes 1213 | max-height: 450px 1214 | overflow-y: scroll 1215 | 1216 | .theme 1217 | margin: 15px 0px 1218 | padding: 10px 1219 | 1220 | ul 1221 | display: flex 1222 | list-style-type: none 1223 | 1224 | margin-top: 10px 1225 | 1226 | li.theme-color-block 1227 | display: inline-block 1228 | 1229 | border: 2.5px solid #fff 1230 | 1231 | margin: 5px 1232 | 1233 | height: 50px 1234 | width: 50px 1235 | 1236 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.05) 1237 | 1238 | transform: translateY(0px) 1239 | transition: transform 250ms ease-in, box-shadow 250ms ease-in, border 250ms ease-in 1240 | 1241 | &:hover 1242 | border: 2.5px solid #222 1243 | cursor: pointer 1244 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4) 1245 | transform: translateY(-3px) 1246 | 1247 | .upper 1248 | button 1249 | color: #222 1250 | background: #eee 1251 | outline: none 1252 | border: none 1253 | 1254 | padding: 5px 8px 8px 8px 1255 | margin-left: 10px 1256 | 1257 | border-radius: 2px 1258 | 1259 | &:hover 1260 | cursor: pointer 1261 | 1262 | button.delete 1263 | color: #581212 1264 | background: #f35e5e 1265 | 1266 | .theme-name 1267 | display: inline-block 1268 | 1269 | font-weight: bold 1270 | 1271 | margin-right: 15px 1272 | 1273 | .theme-saver 1274 | background: #eee 1275 | border-bottom: 1px solid #eaeaea 1276 | 1277 | padding: 15px 1278 | 1279 | p 1280 | margin-bottom: 12px 1281 | 1282 | input 1283 | border: 1px solid #e0e0e0 1284 | 1285 | margin-right: 5px 1286 | padding: 5px 1287 | 1288 | button 1289 | color: #222 1290 | outline: none 1291 | border: none 1292 | 1293 | padding: 5px 8px 8px 8px 1294 | margin-left: 10px 1295 | 1296 | border-radius: 2px 1297 | 1298 | &:hover 1299 | cursor: pointer 1300 | 1301 | 1302 | /***************************************************************** 1303 | -- #0.5a Misc. Styles 1304 | =============================================================== 1305 | Misc. 1306 | /*****************************************************************/ 1307 | 1308 | .minimize 1309 | height: 0px !important 1310 | overflow: hidden !important 1311 | -------------------------------------------------------------------------------- /src/test/logos/antergos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/logos/antergos.png -------------------------------------------------------------------------------- /src/test/logos/archlinux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/logos/archlinux.png -------------------------------------------------------------------------------- /src/test/logos/debian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/logos/debian.png -------------------------------------------------------------------------------- /src/test/logos/generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/logos/generic.png -------------------------------------------------------------------------------- /src/test/logos/tux-silhouette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/logos/tux-silhouette.png -------------------------------------------------------------------------------- /src/test/logos/tux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/logos/tux.png -------------------------------------------------------------------------------- /src/test/logos/ubuntu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/logos/ubuntu.png -------------------------------------------------------------------------------- /src/test/wallpapers/boko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/wallpapers/boko.jpg -------------------------------------------------------------------------------- /src/test/wallpapers/mountains-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/wallpapers/mountains-2.png -------------------------------------------------------------------------------- /src/test/wallpapers/space-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NoiSek/Aether/76802308e1f64cffa1d0d392a0f953e99a797496/src/test/wallpapers/space-1.jpg -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require("webpack"); 3 | 4 | const uncompressedPostCSSConfig = [ require('autoprefixer')() ]; 5 | const compressedPostCSSConfig = [ ...uncompressedPostCSSConfig, require('cssnano')({ 'preset': 'default' }) ]; 6 | 7 | 8 | module.exports = function(env) { 9 | env.NODE_ENV = (env.production) ? 'production' : 'development'; 10 | process.env.NODE_ENV = env.NODE_ENV; 11 | 12 | const isProduction = (env.NODE_ENV === 'production'); 13 | 14 | return { 15 | "entry": "./src/js/Main.jsx", 16 | "mode": env.NODE_ENV, 17 | "output": { 18 | "path": path.resolve("./dist/js"), 19 | "filename": "Aether.js" 20 | }, 21 | "module": { 22 | "rules": [ 23 | { 24 | "test": /\.(js|jsx)$/, 25 | "use": [ 26 | "babel-loader", 27 | "eslint-loader" 28 | ] 29 | }, 30 | { 31 | "test": /\.(scss|sass)$/, 32 | "use": [ 33 | { 34 | "loader": "style-loader" 35 | }, 36 | { 37 | "loader": "css-loader", 38 | "options": { 39 | "url": false, 40 | "sourceMap": !isProduction 41 | } 42 | }, 43 | { 44 | "loader": "postcss-loader", 45 | "options": { 46 | "plugins": isProduction ? compressedPostCSSConfig : uncompressedPostCSSConfig 47 | } 48 | }, 49 | { 50 | "loader": "sass-loader", 51 | "options": { 52 | "sourceMap": !isProduction 53 | } 54 | } 55 | ] 56 | }, 57 | { 58 | "test": /\.svg$/, 59 | "use": [{ 60 | "loader": "svg-inline-loader", 61 | "options": { 62 | "removeTags": true 63 | } 64 | }] 65 | } 66 | ] 67 | }, 68 | "devtool": (isProduction) ? 'source-map' : 'eval-source-map', 69 | "plugins": [ 70 | new webpack.DefinePlugin({ 71 | "process.env.NODE_ENV": JSON.stringify(env.NODE_ENV) 72 | }) 73 | ], 74 | "resolve": { 75 | "extensions": [ ".js", ".min.js", ".jsx" ], 76 | "mainFiles": [ 'index', 'Main' ], 77 | "modules": [ "./dist/js", "./node_modules", "./src", "./src/js" ] 78 | } 79 | }; 80 | }; 81 | --------------------------------------------------------------------------------