├── .editorconfig ├── .gitignore ├── .lesshintrc ├── .npmignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── assets │ ├── app_screenshot.png │ ├── lockup_remixer_for_web.svg │ └── remixer_logo_32x32.png ├── developing.md └── remixer-api.md ├── examples ├── README.md ├── index.html ├── package.json ├── src │ ├── demo-app.js │ ├── demo-styles.css │ ├── demo-template.html │ ├── faker.min.js │ └── favicon.ico ├── webpack.config.js └── yarn.lock ├── karma.conf.js ├── package.json ├── src ├── core │ ├── Remixer.ts │ ├── __tests__ │ │ ├── BooleanVariable_test.ts │ │ ├── ColorVariable_test.ts │ │ ├── NumberVariable_test.ts │ │ ├── RangeVariable_test.ts │ │ ├── Remixer_test.ts │ │ └── StringVariable_test.ts │ └── variables │ │ ├── BooleanVariable.ts │ │ ├── ColorVariable.ts │ │ ├── NumberVariable.ts │ │ ├── RangeVariable.ts │ │ ├── StringVariable.ts │ │ └── Variable.ts ├── lib │ ├── ColorUtils.ts │ ├── Constants.ts │ ├── LocalStorage.ts │ ├── Messaging.ts │ ├── Remote.ts │ └── __tests__ │ │ ├── ColorUtils_test.ts │ │ └── LocalStorage_test.ts └── ui │ ├── ListItem.tsx │ ├── OverlayController.tsx │ ├── OverlayShareMenu.tsx │ ├── OverlayVariables.tsx │ ├── __tests__ │ ├── ColorSwatchControl_test.tsx │ ├── DropdownControl_test.tsx │ ├── RadioListControl_test.tsx │ ├── SliderControl_test.tsx │ ├── SwitchControl_test.tsx │ └── TextFieldControl_test.tsx │ ├── controls │ ├── ColorSwatchControl.tsx │ ├── DropdownControl.tsx │ ├── RadioListControl.tsx │ ├── SliderControl.tsx │ ├── SwitchControl.tsx │ ├── TextFieldControl.tsx │ └── controlProps.tsx │ ├── render.tsx │ ├── styles │ ├── iframe.less │ └── overlay.less │ └── templates │ └── overlay_iframe.html ├── tsconfig.json ├── tslint.json ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | npm-debug.log 3 | node_modules 4 | build 5 | dist 6 | coverage 7 | -------------------------------------------------------------------------------- /.lesshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "fileExtensions": [".less", ".css"], 3 | "idSelector": false, 4 | "importantRule": false, 5 | "zeroUnit": false 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | npm-debug.log 3 | node_modules 4 | build 5 | coverage 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | sudo: false 6 | node_js: 7 | - "stable" 8 | before_script: 9 | - "export CHROME_BIN=chromium-browser" 10 | - "export DISPLAY=:99.0" 11 | - "sh -e /etc/init.d/xvfb start" 12 | install: 13 | - npm install 14 | after_success: 15 | - npm run codecov 16 | notifications: 17 | email: false 18 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of Material Remixer for Web authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history with git log. 6 | 7 | Google Inc. 8 | and other contributors 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 2 | 3 | Adds license blocks to tests. 4 | 5 | ## 1.0.0 6 | 7 | Remixer v1.0.0 Release. 8 | 9 | ## 0.7.0 10 | 11 | ### New Features 12 | 13 | * [Updates to latest dependencies.](https://github.com/material-foundation/material-remixer-js/commit/939b781618071ccac2da844d46cfbc742ad1df5a) 14 | * [Adds return value to remixer.cloneAndUpdateVariable(). Adds tests.](https://github.com/material-foundation/material-remixer-js/commit/673bc718305a5e94aba3a838dfdef26a435d830d) 15 | * [Updates from mdl-list to flexbox for display of variable controls.](https://github.com/material-foundation/material-remixer-js/commit/540a1a6ee9bea212a1d6b9e60d953722ad539236) 16 | * [Updates share menu to flexbox.](https://github.com/material-foundation/material-remixer-js/commit/ea7811b89392caa78e514d2ba87d08b3dc3388db) 17 | 18 | ## 0.6.4 19 | 20 | ### Bug Fixes 21 | 22 | * [Shows share menu only if firebase has been initialized properly.](https://github.com/material-foundation/material-remixer-js/commit/8ef2372332dba476f859652d1d7437cbcd103bc0) 23 | * [Requires remote to be initialized when checking if enabled.](https://github.com/material-foundation/material-remixer-js/commit/6612a2fddf1c50e42131ac09e10878cc6de7e620) 24 | * [Fixes color formatting bug.](https://github.com/material-foundation/material-remixer-js/commit/fe62f83238d94ec1f26b7b4d550c0b2ff1be655d) 25 | 26 | ## 0.6.0 27 | 28 | ### Bug Fixes 29 | 30 | * [Fixes colorVariable bug when changed remotely.](https://github.com/material-foundation/material-remixer-js/commit/3aa44628a3aa1e48d0c737ec369e777ccf6ab54e) 31 | * [Fixes colorVariable test](https://github.com/material-foundation/material-remixer-js/commit/177bdb59616ba446faa00208237b2d9b2989a63d) 32 | * [Updates firebase to prevent uninitialized app error.](https://github.com/material-foundation/material-remixer-js/commit/27c95fcf3d850a5b9a63cec37cdf9f49dd265a0b) 33 | 34 | ### New Features 35 | 36 | * [Updates npm package dependencies](https://github.com/material-foundation/material-remixer-js/commit/f80b5858023345a6a9c36b1145c8c5378e5d1719) 37 | * [Adds the share menu.](https://github.com/material-foundation/material-remixer-js/commit/13c59095de01c7e4e394758b795f06fddeecff71) 38 | * [Adds remixer logo icon.](https://github.com/material-foundation/material-remixer-js/commit/e9d005ced3a8528abbb2c08a9cdd915e766f6658) 39 | * [Adds new ColorUtils class to handle color alpha conversions.](https://github.com/material-foundation/material-remixer-js/commit/aaf59ee4cd9447b738e1f07424df62f61703c2c9) 40 | * [Adds app screenshot.](https://github.com/material-foundation/material-remixer-js/commit/ec4b998216be5240b9bec84243e5535ff4c3eb76) 41 | * [Adds new demo app.](https://github.com/material-foundation/material-remixer-js/commit/095254e86bf770091d53fb10cebc3f71820658bf) 42 | * [Adds Firebase configuration documentation.](https://github.com/material-foundation/material-remixer-js/commit/da41011993fc684b4bddb353a92ae3bf6bb5f405) 43 | 44 | ## 0.5.8 45 | 46 | ### Bug Fixes 47 | 48 | * [Renames defaultValue to initialValue throughout.](https://github.com/material-foundation/material-remixer-js/commit/bf4c7bfb5427e070aeb7b26fb8e66643696fffa6) 49 | * [Updates webpack config source mapping.](https://github.com/material-foundation/material-remixer-js/commit/7cb0c9b37ee614c1e3fa1ecc74cd196e4fec83e4) 50 | * [Fixes title when deserializing variable.](https://github.com/material-foundation/material-remixer-js/commit/46f241031fb91a1732721f4772bbeb2737ac0b1c) 51 | * [Updates localStorage schema](https://github.com/material-foundation/material-remixer-js/commit/a6ca1b2341af96635ef184ba1992ea1d745dbe00) 52 | * [Fixes Variable.clone() wrongly setting selectedValue to initialValue.](https://github.com/material-foundation/material-remixer-js/commit/19ef8e4af6b3eafe997a7d561740f6915a50202e) 53 | * [Updates overlay title bar background color.](https://github.com/material-foundation/material-remixer-js/commit/130026e9a356db0185ebee70b8f964f2248aa39b) 54 | * [Cleans up remote controller class.](https://github.com/material-foundation/material-remixer-js/commit/715812d56d61aa8daeadbba20abc72ed9eed0030) 55 | * [Makes deserialize publicly available.](https://github.com/material-foundation/material-remixer-js/commit/ded22f9c3da46bdc87250f141d5a1b15551d8057) 56 | 57 | ### New Features 58 | 59 | * [Updates MDL controls to allow remote updates.](https://github.com/material-foundation/material-remixer-js/commit/823d8e29320b790bc5331ce6b9b0f27ca1b0ce54) 60 | * [Adds ability to sync with remote controller.](https://github.com/material-foundation/material-remixer-js/commit/7a4847dc9e169f1742a94cb09941006f58a61a6c) 61 | 62 | ## 0.5.7 63 | 64 | * [Updates webpack configuration.](https://github.com/material-foundation/material-remixer-js/commit/fffca3cf29960defe0b280cbe60f37308dc1aaf5) 65 | * [Adds variable `constraintType` and updates `dataType`.](https://github.com/material-foundation/material-remixer-js/commit/b4be6daf176cf0f36a6fa9e32e94edbd6703d70a) 66 | * [Renames possibleValues to limitedToValues.](https://github.com/material-foundation/material-remixer-js/commit/c795fd8533f4ae5f85a1b1b857a70dfc59e6890f) 67 | * [Adds variable `controlType` property.](https://github.com/material-foundation/material-remixer-js/commit/83e326d8fbb947abfd59f5421b2ac0d43713659c) 68 | * [Adds clone tests and updates `dataType` tests for variables.](https://github.com/material-foundation/material-remixer-js/commit/8949d8d33d25f41ad45c70c7e1b8289ebe7664fa) 69 | * [Converts colors to rgba during serialization.](https://github.com/material-foundation/material-remixer-js/commit/40f673f5057776681e0fdee1db903cf789b6de2d) 70 | 71 | ## 0.5.6 72 | 73 | * [Adds editorconfig file.](https://github.com/material-foundation/material-remixer-js/commit/6925701f3a7c05cc2b0ac174331a1fd1d8539dd7) 74 | * [Adds tests for UI control classes.](https://github.com/material-foundation/material-remixer-js/commit/45b343608515871053382dfa6d0a8876d82374a5) 75 | 76 | ## 0.5.5 77 | 78 | * [Renames to material-remxer-js.](https://github.com/material-foundation/material-remixer-js/commit/6bcebcdbc2bed6b8a8bac62a32f4d6f2fe8f9d93) 79 | * Adds codecov. 80 | 81 | ## 0.5.4 82 | 83 | * [Adds `lesshint` CSS/LESS linter.](https://github.com/material-foundation/material-remixer-js/commit/c86ffdec4f8a3796abd94b18d095e4f00b874cea) 84 | 85 | ## 0.5.3 86 | 87 | * [Updates per tslint core rules.](https://github.com/material-foundation/material-remixer-js/commit/94e39ac7ae54d9d2549281558cf5160861b7386b) 88 | * [Adds additional tests for `Remixer` and `LocalStorage`](https://github.com/material-foundation/material-remixer-js/commit/3925ac91914cf1ea85c34f4aa2fd284c71c9aac3). 89 | 90 | ## 0.5.2 91 | 92 | * [Replaces gulp with NPM scripts.](https://github.com/material-foundation/material-remixer-js/commit/e3f97595016236cb24a60071ad8d41819590b52a) 93 | 94 | ## 0.5.1 95 | 96 | Initial release. 97 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ## Pull requests 4 | 5 | Pull requests can be hard to review if they try to tackle too many things 6 | at once. Phabricator's "[Writing Reviewable Code](https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/)" 7 | provides a set of guidelines that help increase the likelihood of your 8 | pull request getting merged. 9 | 10 | In short (slightly modified from the original article): 11 | 12 | - A pull request should be as small as possible, but no smaller. 13 | - The smallest a pull request can be is a single cohesive idea: don't 14 | make pull requests so small that they are meaningless on their own. 15 | - Turn large pull requests into small pull requests by dividing large 16 | problems into smaller problems and solving the small problems one at 17 | a time. 18 | - Write sensible pull request descriptions. 19 | 20 | ### Before you contribute 21 | 22 | Before we can use your code, you must sign the 23 | [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) 24 | (CLA), which you can do online. The CLA is necessary mainly because you own the 25 | copyright to your changes, even after your contribution becomes part of our 26 | codebase, so we need your permission to use and distribute your code. We also 27 | need to be sure of various other things—for instance that you'll tell us if you 28 | know that your code infringes on other people's patents. You don't have to sign 29 | the CLA until after you've submitted your code for review and a member has 30 | approved it, but you must do it before we can put your code into our codebase. 31 | Before you start working on a larger contribution, you should get in touch with 32 | us first through the issue tracker with your idea so that we can help out and 33 | possibly guide you. Coordinating up front makes it much easier to avoid 34 | frustration later on. 35 | 36 | ### Code reviews 37 | 38 | All submissions, including submissions by project members, require review. 39 | 40 | ### The small print 41 | 42 | Contributions made by corporations are covered by a different agreement than 43 | the one above, the [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remixer for JavaScript 2 | 3 | 4 | 5 | [![Build Status](https://travis-ci.org/material-foundation/material-remixer-js.svg?branch=develop)](https://travis-ci.org/material-foundation/material-remixer-js) [![codecov](https://codecov.io/gh/material-foundation/material-remixer-js/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-foundation/material-remixer-js) [![npm version](https://badge.fury.io/js/material-remixer.svg)](https://badge.fury.io/js/material-remixer) [![Chat](https://img.shields.io/discord/216272340709801984.svg)](https://discord.gg/material-remixer) 6 | 7 | 8 | Remixer is a framework to iterate quickly on UI changes by allowing you to adjust UI variables without needing to rebuild (or even restart) your app. You can adjust Numbers, Colors, Booleans, and Strings. To see it in action check out the [example app](https://github.com/material-foundation/material-remixer-js/tree/develop/examples). 9 | 10 | If you are interested in using Remixer in another platform, you may want to check out the [iOS](https://github.com/material-foundation/material-remixer-ios) and [Android](https://github.com/material-foundation/material-remixer-android) repos. With any of the three platforms you can use the [Remote Controller](https://github.com/material-foundation/material-remixer-remote-web) to change the variables from a web dashboard. 11 | 12 | ## Using Remixer in your app 13 | 14 | #### 1. Use [`npm`](https://www.npmjs.com/) to install as dependency. 15 | 16 | `npm install material-remixer --save` 17 | 18 | This will install the Remixer files in your project's `node_modules` folder. 19 | 20 | #### 2. Include the `remixer.js` script in your app. 21 | 22 | ```html 23 | 24 | ``` 25 | 26 | #### 3. Begin by starting Remixer. 27 | 28 | ```javascript 29 | remixer.start(); 30 | ``` 31 | 32 | #### 4. (Optional) Configure the Web Remote Controller 33 | 34 | This **optional** step is only needed if you wish to use the Web Remote Controller. If so, follow these guidelines: 35 | 36 | - Set up a new or existing [Firebase](https://firebase.google.com/) account as detailed in the [Web Remote Controller](https://github.com/material-foundation/material-remixer-remote-web) repository. 37 | - Add your Firebase account credentials to your app, and forward the param to the `remixer.start()` method. 38 | 39 | ```javascript 40 | // Replace with your project's Firebase info. 41 | var config = { 42 | apiKey: "", 43 | authDomain: ".firebaseapp.com", 44 | databaseURL: "https://.firebaseio.com", 45 | }; 46 | 47 | // Pass the config params to Remixer start method. 48 | remixer.start(config); 49 | ``` 50 | 51 | - You can then toggle on/off sharing to the remote controller from within the Remixer overlay. 52 | 53 | #### 5. Add variables. 54 | Now you can add any desired variables and use the callback method to assign the `selectedValue` property. 55 | 56 | ```javascript 57 | // Add a boolean variable to generate a toggle switch in the overlay. 58 | remixer.addBooleanVariable("show", true, function(variable) { 59 | document.getElementById("box").style.display = variable.selectedValue ? "block" : "none"; 60 | }); 61 | ``` 62 | 63 | ## API Documentation 64 | 65 | - Use these [common static methods](https://github.com/material-foundation/material-remixer-js/blob/develop/docs/remixer-api.md) to enable Remixer in your app. 66 | - Or refer to the full [API documentation](https://material-foundation.github.io/material-remixer-js/docs) for more information. 67 | 68 | ## Contributing to Remixer 69 | 70 | We're excited you want to contribute to the project! Please read these docs so we can get your contributions submitted quickly. 71 | 72 | - [Contribution policy and guidelines](https://github.com/material-foundation/material-remixer-js/blob/develop/CONTRIBUTING.md) 73 | - [Developing Remixer](https://github.com/material-foundation/material-remixer-js/blob/develop/docs/developing.md) 74 | 75 | ## Is material-foundation affiliated with Google? 76 | 77 | Yes, the [material-foundation](https://github.com/material-foundation) organization is one of Google's new homes for tools and frameworks related to our [Material Design](https://material.io) system. Please check out our blog post [Design is Never Done](https://design.google.com/articles/design-is-never-done/) for more information regarding Material Design and how Remixer integrates with the system. 78 | 79 | ## License 80 | 81 | © Google, 2016. Licensed under an [Apache-2](https://github.com/material-foundation/material-remixer-js/blob/develop/LICENSE) license. 82 | -------------------------------------------------------------------------------- /docs/assets/app_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-foundation/material-remixer-js/9ecd2cda79f68a307ac57f17e66b46f080afa3d0/docs/assets/app_screenshot.png -------------------------------------------------------------------------------- /docs/assets/lockup_remixer_for_web.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 31 | 32 | 34 | 35 | 41 | 42 | 43 | 45 | 46 | 55 | 56 | 57 | 58 | 71 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/assets/remixer_logo_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-foundation/material-remixer-js/9ecd2cda79f68a307ac57f17e66b46f080afa3d0/docs/assets/remixer_logo_32x32.png -------------------------------------------------------------------------------- /docs/developing.md: -------------------------------------------------------------------------------- 1 | # Developing Remixer 2 | 3 | ### Setting up your development environment 4 | 5 | First you'll need a recent version of [Node.js](https://nodejs.org) to work 6 | with Remixer-js. 7 | 8 | Once node is installed, simply clone our repo (or your fork of it) and 9 | run `npm install`. 10 | 11 | ```bash 12 | git clone git@github.com:material-foundation/material-remixer-js.git 13 | cd material-remixer-js && npm install 14 | ``` 15 | 16 | ### Typescript 17 | 18 | The source code for Remixer-js is written in 19 | [Typescript](https://www.typescriptlang.org), and compiled to es5 javascript for 20 | distribution. 21 | 22 | ### Building Remixer 23 | 24 | Use any of the following npm scripts to build dev or prod versions of Remixer-js. 25 | 26 | ```bash 27 | # Builds an unminified version of Remixer-js within the build folder. 28 | npm run build:dev 29 | 30 | # Builds a minified version. 31 | npm run build:prod 32 | 33 | # Builds both unminified and minified versions. 34 | npm run build 35 | ``` 36 | 37 | ### Running the development server 38 | 39 | We use [weback dev server](https://webpack.js.org/configuration/dev-server) for 40 | hosting and live-reloading of any code changes. 41 | 42 | ```bash 43 | npm run dev 44 | open http://localhost:8080 45 | ``` 46 | 47 | ### Linting 48 | 49 | ```bash 50 | # Lint both CSS/LESS using lesshint. 51 | npm run lint:css 52 | 53 | # Lint typescript using tslint. 54 | npm run lint:ts 55 | 56 | # Lints both css and ts. 57 | npm run lint 58 | ``` 59 | 60 | ### Testing and Code Coverage 61 | 62 | Testing uses the [karma](https://karma-runner.github.io) test runner with 63 | [mocha](https://mochajs.org/) flavored testing framework. Code coverage 64 | is reported by [istanbul](https://www.npmjs.com/package/istanbul-instrumenter-loader). 65 | 66 | ```bash 67 | # Run tests and code coverage once. 68 | npm run test 69 | 70 | # Run with auto-watch indefinitely. 71 | npm run test:watch 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/remixer-api.md: -------------------------------------------------------------------------------- 1 | # Remixer API 2 | The Remixer class is a singleton class that keeps track of all the Variables and deals with saving/syncing its values. 3 | 4 | The following methods are the most commonly used to enable Remixer in your app. 5 | 6 | - [remixer.start()](#start-static) 7 | - [remixer.stop()](#stop-static) 8 | - [remixer.addBooleanVariable()](#addbooleanvariable-static) 9 | - [remixer.addColorVariable()](#addcolorvariable-static) 10 | - [remixer.addNumberVariable()](#addnumbervariable-static) 11 | - [remixer.addRangeVariable()](#addrangevariable-static) 12 | - [remixer.addStringVariable()](#addstringvariable-static) 13 | 14 | --- 15 | 16 | ### start `STATIC` 17 | 18 | Appends the HTML iFrame to body of client app. Attaches key listener to toggle Overlay visibility. 19 | 20 | #### Syntax 21 | 22 | ```javascript 23 | remixer.start(); 24 | remixer.start(remoteConfig); 25 | ``` 26 | 27 | #### Parameters 28 | 29 | - **remoteConfig:** *object* 30 | 31 | The optional firebase configuration. Provide this configuration if you wish to use the remote controller. 32 | 33 | ```javascript 34 | var remoteConfig = { 35 | apiKey: "", 36 | authDomain: ".firebaseapp.com", 37 | databaseURL: "https://.firebaseio.com" 38 | ... 39 | }; 40 | ``` 41 | 42 | #### Returns void 43 | 44 | --- 45 | 46 | ### stop `STATIC` 47 | 48 | Removes iFrame and attached key listener. 49 | 50 | #### Syntax 51 | 52 | ```javascript 53 | remixer.stop(); 54 | ``` 55 | 56 | #### Returns void 57 | 58 | --- 59 | 60 | ### addBooleanVariable `STATIC` 61 | 62 | Adds a boolean Variable to array of Variables with optional callback. 63 | 64 | #### Syntax 65 | 66 | ```javascript 67 | remixer.addBooleanVariable(key, defaultValue); 68 | remixer.addBooleanVariable(key, defaultValue, callback(variable) { ... } ); 69 | ``` 70 | 71 | #### Parameters 72 | 73 | - **key:** *string* 74 | 75 | The key of the Variable. 76 | 77 | - **defaultValue:** *boolean* 78 | 79 | The initial default value of the variable. 80 | 81 | - `OPTIONAL` **callback:** *function* 82 | 83 | The callback method to be invoked when the Variable is updated. The function is passed with the updated variable argument. 84 | 85 | #### Returns [BooleanVariable](https://material-foundation.github.io/material-remixer-js/docs/classes/_core_variables_booleanvariable_.booleanvariable.html) 86 | 87 | --- 88 | 89 | ### addColorVariable `STATIC` 90 | 91 | Adds a color variable to array of variables with optional callback. 92 | 93 | #### Syntax 94 | 95 | ```javascript 96 | remixer.addColorVariable(key, defaultValue); 97 | remixer.addColorVariable(key, defaultValue, limitedToValues); 98 | remixer.addColorVariable(key, defaultValue, limitedToValues, callback(variable) { ... } ); 99 | ``` 100 | 101 | #### Parameters 102 | 103 | - **key:** *string* 104 | 105 | The key of the Variable. 106 | 107 | - **defaultValue:** *string* 108 | 109 | The initial default value of the variable. 110 | 111 | - `OPTIONAL` **limitedToValues:** *string[]* 112 | 113 | The optional array of allowed values. 114 | 115 | - `OPTIONAL` **callback:** *function* 116 | 117 | The callback method to be invoked when the Variable is updated. The function is passed with the updated variable argument. 118 | 119 | #### Returns [ColorVariable](https://material-foundation.github.io/material-remixer-js/docs/classes/_core_variables_colorvariable_.colorvariable.html) 120 | 121 | --- 122 | 123 | ### addNumberVariable `STATIC` 124 | 125 | Adds a number variable to array of variables with optional callback. 126 | 127 | #### Syntax 128 | 129 | ```javascript 130 | remixer.addNumberVariable(key, defaultValue); 131 | remixer.addNumberVariable(key, defaultValue, limitedToValues); 132 | remixer.addNumberVariable(key, defaultValue, limitedToValues, callback(variable) { ... } ); 133 | ``` 134 | 135 | #### Parameters 136 | 137 | - **key:** *string* 138 | 139 | The key of the Variable. 140 | 141 | - **defaultValue:** *number* 142 | 143 | The initial default value of the variable. 144 | 145 | - `OPTIONAL` **limitedToValues:** *number[]* 146 | 147 | The optional array of allowed values. 148 | 149 | - `OPTIONAL` **callback:** *function* 150 | 151 | The callback method to be invoked when the Variable is updated. The function is passed with the updated variable argument. 152 | 153 | #### Returns [NumberVariable](https://material-foundation.github.io/material-remixer-js/docs/classes/_core_variables_numbervariable_.numbervariable.html) 154 | 155 | --- 156 | 157 | ### addRangeVariable `STATIC` 158 | 159 | Adds a range Variable to array of Variables with optional callback. 160 | 161 | #### Syntax 162 | 163 | ```javascript 164 | remixer.addRangeVariable(key, defaultValue, minValue, maxValue, increment); 165 | remixer.addRangeVariable(key, defaultValue, minValue, maxValue, increment, callback(variable) { ... } ); 166 | ``` 167 | 168 | #### Parameters 169 | 170 | - **key:** *string* 171 | 172 | The key of the Variable. 173 | 174 | - **defaultValue:** *number* 175 | 176 | The initial default value of the variable. 177 | 178 | - **minValue:** *number* 179 | 180 | The allowed minimum value of the variable. 181 | 182 | - **maxValue:** *number* 183 | 184 | The allowed maximum value of the variable. 185 | 186 | - **increment:** *number* 187 | 188 | The amount to increment the value. 189 | 190 | - `OPTIONAL` **callback:** *function* 191 | 192 | The callback method to be invoked when the Variable is updated. The function is passed with the updated variable argument. 193 | 194 | #### Returns [RangeVariable](https://material-foundation.github.io/material-remixer-js/docs/classes/_core_variables_rangevariable_.rangevariable.html) 195 | 196 | --- 197 | 198 | ### addStringVariable `STATIC` 199 | 200 | Adds a string variable to array of variables with optional callback. 201 | 202 | #### Syntax 203 | 204 | ```javascript 205 | remixer.addStringVariable(key, defaultValue); 206 | remixer.addStringVariable(key, defaultValue, limitedToValues); 207 | remixer.addStringVariable(key, defaultValue, limitedToValues, callback(variable) { ... } ); 208 | ``` 209 | 210 | #### Parameters 211 | 212 | - **key:** *string* 213 | 214 | The key of the Variable. 215 | 216 | - **defaultValue:** *string* 217 | 218 | The initial default value of the variable. 219 | 220 | - `OPTIONAL` **limitedToValues:** *string[]* 221 | 222 | The optional array of allowed values. 223 | 224 | - `OPTIONAL` **callback:** *function* 225 | 226 | The callback method to be invoked when the Variable is updated. The function is passed with the updated variable argument. 227 | 228 | #### Returns [StringVariable](https://material-foundation.github.io/material-remixer-js/docs/classes/_core_variables_stringvariable_.stringvariable.html) 229 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Material Remixer Web Example 2 | 3 | This folder contains an example of using Remixer in a simple web app. 4 | 5 | ## Quickstart 6 | 7 | 1. Run `npm install` within this folder. 8 | 2. Run `npm start` to start the demo server. 9 | 3. The demo should open in new window. If not, navigate to [http://localhost:8080/](http://localhost:8080/) to view it. 10 | 4. Hit `ESC` key to toggle Remixer overlay visibility. 11 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | Remixer Example 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material-remixer-examples", 3 | "version": "1.0.0", 4 | "description": "Example app demonstrating usage of Remixer.", 5 | "author": "The Material Remixer Authors (see AUTHORS)", 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "start": "webpack-dev-server --open" 9 | }, 10 | "devDependencies": { 11 | "webpack": "^2.2.0", 12 | "webpack-dev-server": "^2.2.0" 13 | }, 14 | "dependencies": { 15 | "faker": "^4.1.0", 16 | "handlebars": "^4.0.6", 17 | "jquery": "^3.1.1", 18 | "material-design-lite": "^1.2.1", 19 | "material-remixer": "^1.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/src/demo-app.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** The handlebars page template. */ 18 | var template; 19 | 20 | /** The data view structure. */ 21 | var dataView = {}; 22 | 23 | /** 24 | * Loads the demo template. 25 | * @return {Promise} Returns promise for loading template as successful/failure. 26 | */ 27 | function loadTemplate() { 28 | generateData(); 29 | 30 | return new Promise(function(resolve, reject) { 31 | 32 | // Load handlebars page template. 33 | $.get('src/demo-template.html', function(response) { 34 | template = response; 35 | render(); 36 | resolve(); 37 | }).fail(function() { 38 | reject(); 39 | }); 40 | }); 41 | } 42 | 43 | /** Generates fake data using faker.js script. */ 44 | function generateData() { 45 | var totalTransactions = 20; 46 | var totalAmount = 0; 47 | var transactions = []; 48 | 49 | // Generate transactions 50 | for (var i = 0; i < totalTransactions; i++) { 51 | // Transactions 52 | var transaction = faker.helpers.createTransaction(); 53 | transaction.date = getDate(); 54 | totalAmount += parseFloat(transaction.amount); 55 | 56 | // Other transactions 57 | transaction.other = []; 58 | for (var j = 0; j < totalTransactions; j++) { 59 | other = faker.helpers.createTransaction(); 60 | other.date = getDate(); 61 | transaction.other.push(other); 62 | } 63 | 64 | transactions.push(transaction); 65 | } 66 | 67 | dataView.transactions = transactions.sort(compare); 68 | dataView.selectedTransaction = transactions[0]; 69 | dataView.totalAmount = toCurrency(totalAmount); 70 | } 71 | 72 | /** Sorts by date. */ 73 | function compare(a, b) { 74 | var date1 = new Date(a.date); 75 | var date2 = new Date(b.date); 76 | if (date1 < date2) { 77 | return -1; 78 | } 79 | if (date1 > date2) { 80 | return 1; 81 | } 82 | return 0; 83 | }; 84 | 85 | /** Returns a currency string for given value. */ 86 | function toCurrency(value) { 87 | return value.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,").toString(); 88 | } 89 | 90 | /** Returns a random date between Jan 1st, 2017 and now. */ 91 | function getDate() { 92 | var date = faker.date.between('2017-01-01', new Date()); 93 | return date.toLocaleDateString(); 94 | } 95 | 96 | /** Renders the tempate to the #demo-app div. */ 97 | function render() { 98 | var content = $($.parseHTML(template)); 99 | var compiler = Handlebars.compile(content.filter("#layout").html()); 100 | 101 | Handlebars.registerPartial({ 102 | leftPanel: content.filter("#left-panel").html(), 103 | rightPanel: content.filter("#right-panel").html(), 104 | transactionList: content.filter("#transaction-list").html(), 105 | transactionDetails: content.filter("#transaction-details").html(), 106 | transactionOther: content.filter("#transaction-other").html(), 107 | }); 108 | 109 | // Adds selected class to selected transaction item. 110 | Handlebars.registerHelper('isSelected', function(options) { 111 | var resp = 112 | (this.business === dataView.selectedTransaction.business) ? 113 | "selected" : ""; 114 | return options.fn(resp); 115 | }); 116 | 117 | // Returns fake detail monthly total. 118 | Handlebars.registerHelper('monthlyTotal', function(options) { 119 | var total = toCurrency(parseFloat(this.selectedTransaction.amount) * 12); 120 | return options.fn(total); 121 | }); 122 | 123 | // Returns fake detail yearly total. 124 | Handlebars.registerHelper('yearlyTotal', function(options) { 125 | var total = toCurrency(parseFloat(this.selectedTransaction.amount) * 40); 126 | return options.fn(total); 127 | }); 128 | 129 | var html = compiler(dataView); 130 | $("#demo-app").html(html); 131 | 132 | addClickHandler(); 133 | } 134 | 135 | // Adds a click listener for transaction list items. 136 | function addClickHandler() { 137 | $('.transaction-list-item').click(function() { 138 | var index = $('.transaction-list-item').index(this); 139 | dataView.selectedTransaction = dataView.transactions[index]; 140 | render(); 141 | }); 142 | } 143 | -------------------------------------------------------------------------------- /examples/src/demo-styles.css: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | h2 { 18 | font-size: 20px; 19 | font-weight: 200; 20 | letter-spacing: 0.04em; 21 | } 22 | 23 | h3 { 24 | color: rgba(0, 0, 0, 0.4); 25 | font-size: 16px; 26 | font-weight: 400; 27 | letter-spacing: 0.04em; 28 | margin: 10px; 29 | } 30 | 31 | h4 { 32 | font-size: 28px; 33 | font-weight: 200; 34 | letter-spacing: 0.04em; 35 | line-height: 40px; 36 | margin: 0; 37 | } 38 | 39 | h6 { 40 | color: rgba(255, 255, 255, 0.7); 41 | font-size: 10px; 42 | font-weight: 200; 43 | letter-spacing: 0.04em; 44 | margin: 0; 45 | text-transform: uppercase; 46 | } 47 | 48 | .date { 49 | color: rgba(0, 0, 0, 0.5); 50 | font-weight: lighter; 51 | } 52 | 53 | .amount { 54 | display: inline-block; 55 | min-width: 100px; 56 | text-align: right; 57 | } 58 | 59 | .page-content { 60 | background-color: #cccccc; 61 | display: flex; 62 | flex-direction: row; 63 | padding: 15px; 64 | } 65 | 66 | .left-panel { 67 | background-color: white; 68 | border-top-left-radius: 10px; 69 | flex-grow: 2; 70 | overflow: hidden; 71 | } 72 | 73 | .right-panel { 74 | background-color: #f9f9f9; 75 | border-top-right-radius: 10px; 76 | flex-grow: 1; 77 | overflow: hidden; 78 | } 79 | 80 | .panel-header { 81 | color: white; 82 | overflow: hidden; 83 | padding: 10px; 84 | text-align: center; 85 | } 86 | 87 | .panel-content { 88 | padding: 20px 40px; 89 | } 90 | 91 | .left-panel .panel-header-top { 92 | background-color: #3375be; 93 | } 94 | 95 | .left-panel .panel-header-bottom { 96 | background-color: #3369b1; 97 | } 98 | 99 | .right-panel .panel-header-top { 100 | background-color: #eba938; 101 | } 102 | 103 | .right-panel .panel-header-bottom { 104 | background-color: #ca8b24; 105 | display: flex; 106 | flex-direction: row; 107 | } 108 | 109 | .right-panel .panel-header-bottom h4 { 110 | font-size: 24px; 111 | } 112 | 113 | .right-panel .panel-header-bottom div { 114 | flex-grow: 1; 115 | } 116 | 117 | .mdl-list__item { 118 | border-radius: 6px; 119 | padding: 0 10px; 120 | } 121 | 122 | .transaction-list .material-icons { 123 | background-color: inherit; 124 | color: #3369b1; 125 | font-size: 24px; 126 | height: 24px; 127 | width: 24px; 128 | } 129 | 130 | .transaction-list-item:hover { 131 | background-color: #f1f1f1; 132 | } 133 | 134 | .transaction-list-item.selected { 135 | background-color: rgba(235, 169, 56, 0.4); 136 | } 137 | 138 | .transaction-details .mdl-list__item { 139 | min-height: 40px; 140 | padding: 0 10px; 141 | } 142 | 143 | .transaction-details .mdl-list__item-primary-content { 144 | color: rgba(0, 0, 0, 0.4); 145 | font-size: 14px; 146 | font-weight: bold; 147 | text-transform: uppercase; 148 | } 149 | 150 | .transaction-details .mdl-list__item:not(:last-child) { 151 | border-bottom: 1px solid #dddddd; 152 | } 153 | -------------------------------------------------------------------------------- /examples/src/demo-template.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 28 | 29 | 30 | 44 | 45 | 46 | 67 | 68 | 69 | 90 | 91 | 92 | 108 | 109 | 110 | 129 | -------------------------------------------------------------------------------- /examples/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-foundation/material-remixer-js/9ecd2cda79f68a307ac57f17e66b46f080afa3d0/examples/src/favicon.ico -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | module.exports = { 20 | entry: { 21 | remixer: './node_modules/material-remixer/dist/remixer.js', 22 | overlay: './node_modules/material-remixer/dist/overlay.js', 23 | }, 24 | output: { 25 | publicPath: '/assets/', 26 | filename: '[name].js', 27 | libraryTarget: 'umd', 28 | umdNamedDefine: true 29 | }, 30 | performance: { 31 | hints: false 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | const webpackConfig = require('./webpack.config.js'); 18 | 19 | module.exports = function(config) { 20 | config.set({ 21 | basePath: '', 22 | frameworks: ['mocha'], 23 | browsers: ['PhantomJS'], 24 | reporters: ['progress', 'coverage'], 25 | client: { 26 | mocha: { 27 | reporter: 'html', 28 | }, 29 | }, 30 | coverageReporter: { 31 | dir: 'coverage', 32 | reporters: [{ 33 | type: 'text' 34 | }, { 35 | type: 'lcov' 36 | }] 37 | }, 38 | files: [ 39 | 'src/**/__tests__/*.ts?(x)', 40 | ], 41 | preprocessors: { 42 | 'src/**/*.ts?(x)': ['webpack'], 43 | }, 44 | webpack: { 45 | devtool: webpackConfig.devtool, 46 | module: webpackConfig.module, 47 | resolve: webpackConfig.resolve, 48 | externals: webpackConfig.externals 49 | }, 50 | mime: { 51 | 'text/x-typescript': ['ts', 'tsx'], 52 | }, 53 | port: 9876, 54 | colors: true, 55 | logLevel: config.LOG_INFO, 56 | autoWatch: true, 57 | singleRun: true, 58 | concurrency: Infinity, 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material-remixer", 3 | "version": "1.0.1", 4 | "description": "A set of libraries and protocols to allow the sharing of design values and live refinement of apps during the development process.", 5 | "homepage": "https://github.com/material-foundation/material-remixer", 6 | "author": "The Material Remixer Authors (see AUTHORS)", 7 | "license": "Apache-2.0", 8 | "keywords": [ 9 | "material", 10 | "remixer" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/material-foundation/material-remixer-js.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/material-foundation/material-remixer-js/issues/" 18 | }, 19 | "scripts": { 20 | "build:dev": "mkdirp build && cross-env RMX_ENV=development webpack --progress --colors", 21 | "build:prod": "mkdirp build && cross-env RMX_ENV=production webpack -p --progress --colors", 22 | "build": "npm-run-all --parallel build:dev build:prod", 23 | "codecov": "cat coverage/*/lcov.info | codecov", 24 | "clean": "del-cli build/** dist/**", 25 | "dev": "npm run clean && npm run build && cross-env RMX_ENV=development webpack-dev-server", 26 | "docs": "typedoc --out docs/api --readme none --exclude '**/+(__tests__|node_modules)/**/*.ts?(x)' ", 27 | "dist": "npm run clean && npm run build && copyFiles -f build/*.* dist", 28 | "lint:ts": "tslint --fix --force 'src/**/*.ts?(x)'", 29 | "lint:css": "lesshint src/ui/styles/*.less examples/src/*.css", 30 | "lint": "npm-run-all --parallel lint:*", 31 | "postinstall": "cd examples && npm install", 32 | "test": "karma start", 33 | "test:watch": "karma start --no-single-run" 34 | }, 35 | "devDependencies": { 36 | "@types/chai": "^3.4.34", 37 | "@types/lodash.throttle": "^4.1.0", 38 | "@types/mocha": "^2.2.33", 39 | "@types/react": "^15.0.20", 40 | "@types/react-addons-test-utils": "^0.14.15", 41 | "@types/react-dom": "^0.14.19", 42 | "@types/sinon": "^1.16.32", 43 | "@types/sinon-chai": "^2.7.27", 44 | "@types/tinycolor2": "^1.1.0", 45 | "@types/uuid": "^2.0.29", 46 | "chai": "^3.5.0", 47 | "codecov.io": "^0.1.6", 48 | "copyfiles": "^1.0.0", 49 | "cross-env": "^3.1.3", 50 | "css-loader": "^0.27.3", 51 | "del-cli": "^0.2.0", 52 | "html-loader": "^0.4.4", 53 | "istanbul-instrumenter-loader": "^2.0.0", 54 | "karma": "^1.3.0", 55 | "karma-chai": "^0.1.0", 56 | "karma-chrome-launcher": "^2.0.0", 57 | "karma-coverage": "^1.1.1", 58 | "karma-mocha": "^1.3.0", 59 | "karma-phantomjs-launcher": "^1.0.2", 60 | "karma-safari-launcher": "^1.0.0", 61 | "karma-sourcemap-loader": "^0.3.7", 62 | "karma-webpack": "^2.0.2", 63 | "less": "^2.7.1", 64 | "less-loader": "^4.0.2", 65 | "lesshint": "^3.1.0", 66 | "mkdirp": "^0.5.1", 67 | "mocha": "^3.1.2", 68 | "npm-run-all": "^4.0.2", 69 | "react-addons-test-utils": "^15.4.1", 70 | "sinon": "^2.0.0-pre.4", 71 | "sinon-chai": "^2.8.0", 72 | "source-map-loader": "^0.2.0", 73 | "style-loader": "^0.16.0", 74 | "ts-loader": "^2.0.3", 75 | "tslint": "^4.0.2", 76 | "tslint-react": "^2.5.0", 77 | "typedoc": "^0.5.9", 78 | "typescript": "^2.1.6", 79 | "webpack": "^2.3.2", 80 | "webpack-dev-server": "^2.4.2" 81 | }, 82 | "dependencies": { 83 | "firebase": "^3.7.2", 84 | "lodash.throttle": "^4.1.1", 85 | "material-design-lite": "^1.2.1", 86 | "react": "^15.4.0", 87 | "react-dom": "^15.4.0", 88 | "tinycolor2": "^1.4.1", 89 | "uuid": "^3.0.1" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/core/Remixer.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { CSS, KeyCode, KeyEvent } from '../lib/Constants'; 18 | import { LocalStorage } from '../lib/LocalStorage'; 19 | import { Messaging } from '../lib/Messaging'; 20 | import { Remote } from '../lib/Remote'; 21 | import { BooleanVariable } from './variables/BooleanVariable'; 22 | import { ColorVariable } from './variables/ColorVariable'; 23 | import { NumberVariable } from './variables/NumberVariable'; 24 | import { IRangeVariableParams, RangeVariable } from './variables/RangeVariable'; 25 | import { StringVariable } from './variables/StringVariable'; 26 | import { IVariableCallback, IVariableKeyMap, Variable } from './variables/Variable'; 27 | 28 | import '../ui/styles/iframe.less'; 29 | 30 | /** 31 | * A declaration used for the webpack `html-loader` module to load string 32 | * content from a given path. 33 | * @param {string} path The url path of string to content to load. 34 | * @return {string} Returns the string at given path. 35 | */ 36 | declare function require(path: string): string; 37 | 38 | /** 39 | * The Remixer class is a singleton class that keeps track of all the Variables 40 | * and deals with saving/syncing its values. 41 | * @class 42 | */ 43 | class Remixer { 44 | 45 | /** 46 | * Initializes a new instance of Remixer. 47 | * @private 48 | * @static 49 | * @return {Remixer} A new instance of Remixer. 50 | */ 51 | private static _sharedInstance = new Remixer(); 52 | 53 | /** 54 | * Provides ability for Remixer HTML iFrame to access this instance of Remixer. 55 | * @return {Remixer} The attached instance of Remixer. 56 | */ 57 | static get attachedInstance(): Remixer { 58 | const parentRemixer = window.parent['remixer']; 59 | if (parentRemixer) { 60 | return parentRemixer._sharedInstance as Remixer; 61 | } 62 | // Simply return shared remixer instance if no parent. 63 | return this._sharedInstance; 64 | } 65 | 66 | private _frameElement: HTMLFrameElement; 67 | 68 | /** 69 | * Returns the HTML iFrame added for this instance of Remixer. 70 | * @static 71 | * @return {HTMLFrameElement} The Remixer HTML iFrame. 72 | */ 73 | static get frameElement(): HTMLFrameElement { 74 | return this._sharedInstance._frameElement; 75 | } 76 | 77 | /** 78 | * Appends an HTML iFrame to the body of client page. 79 | * @private 80 | */ 81 | private appendFrameToBody(): void { 82 | if (!this._frameElement) { 83 | const frame = document.createElement('IFRAME') as HTMLFrameElement; 84 | frame.id = CSS.RMX_OVERLAY_FRAME; 85 | frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups'); 86 | document.getElementsByTagName('body')[0].appendChild(frame); 87 | 88 | // Until `srcdoc` is fully compatible with all browsers, lets simply 89 | // write the overlay html content to the iframe content window. 90 | const iframeContent: string = require('../ui/templates/overlay_iframe.html'); 91 | frame.contentWindow.document.open(); 92 | frame.contentWindow.document.write(iframeContent); 93 | frame.contentWindow.document.close(); 94 | this._frameElement = frame; 95 | } 96 | } 97 | 98 | /** 99 | * Adds a key listener used to trigger the Remixer overlay. 100 | * @private 101 | */ 102 | private addKeyListener(): void { 103 | document.addEventListener(KeyEvent.DOWN, (e: KeyboardEvent) => { 104 | if (e.keyCode === KeyCode.ESC) { 105 | Messaging.postToFrame(Messaging.type.ToggleVisibility); 106 | } 107 | }); 108 | } 109 | 110 | /** 111 | * Appends the HTML iFrame to body of client app. Attaches key listener to 112 | * toggle Overlay visibility. 113 | * @static 114 | * @param {{}} remoteConfig The optional firebase configuration. Provide this 115 | * configuration if you wish to use the remote 116 | * controller. 117 | */ 118 | static start(remoteConfig: {} = {}): void { 119 | this._sharedInstance.appendFrameToBody(); 120 | this._sharedInstance.addKeyListener(); 121 | Remote.initializeRemote(remoteConfig); 122 | } 123 | 124 | /** 125 | * Removes iFrame and attached key listener. 126 | * @static 127 | */ 128 | static stop(): void { 129 | // TODO(cjcox): Remove iframe and key listeners. 130 | } 131 | 132 | /** 133 | * Adds a boolean Variable to array of Variables with optional callback. 134 | * @param {string} key The key of the Variable. 135 | * @param {boolean} defaultValue The initial default value of the variable. 136 | * @param {IVariableCallback} callback The callback method to be invoked 137 | * when the Variable is updated. 138 | * @return {BooleanVariable} 139 | */ 140 | static addBooleanVariable( 141 | key: string, 142 | defaultValue: boolean, 143 | callback?: IVariableCallback, 144 | ): BooleanVariable { 145 | const variable = new BooleanVariable(key, defaultValue, callback); 146 | this.addVariable(variable); 147 | return variable; 148 | } 149 | 150 | /** 151 | * Adds a range Variable to array of Variables with optional callback. 152 | * @param {string} key The key of the Variable. 153 | * @param {number} defaultValue The initial default value of the variable. 154 | * @param {number} minValue The allowed minimum value of the variable. 155 | * @param {number} maxValue The allowed maximum value of the variable. 156 | * @param {number} increment The amount to increment the value. 157 | * @param {IVariableCallback} callback The callback method to be invoked 158 | * when the Variable is updated. 159 | * @return {RangeVariable} 160 | */ 161 | static addRangeVariable( 162 | key: string, 163 | defaultValue: number, 164 | minValue: number, 165 | maxValue: number, 166 | increment: number, 167 | callback?: IVariableCallback, 168 | ): RangeVariable { 169 | const variable = new RangeVariable(key, defaultValue, minValue, maxValue, increment, callback); 170 | this.addVariable(variable); 171 | return variable; 172 | } 173 | 174 | /** 175 | * Adds a string Variable to array of variables with optional callback 176 | * @param {string} key The key of the Variable. 177 | * @param {string} defaultValue The initial default value of the variable. 178 | * @param {string[]} limitedToValues The optional array of allowed values. 179 | * @param {IVariableCallback} callback The callback method to be invoked 180 | * when the Variable is updated. 181 | * @return {StringVariable} 182 | */ 183 | static addStringVariable( 184 | key: string, 185 | defaultValue: string, 186 | limitedToValues?: string[], 187 | callback?: IVariableCallback, 188 | ): StringVariable { 189 | const variable = new StringVariable(key, defaultValue, limitedToValues, callback); 190 | this.addVariable(variable); 191 | return variable; 192 | } 193 | 194 | /** 195 | * Adds a number variable to array of variables with optional callback. 196 | * @param {string} key The key of the Variable. 197 | * @param {number} defaultValue The initial default value of the variable. 198 | * @param {number[]} limitedToValues The optional array of allowed values. 199 | * @param {IVariableCallback} callback The callback method to be invoked 200 | * when the Variable is updated. 201 | * @return {NumberVariable} 202 | */ 203 | static addNumberVariable( 204 | key: string, 205 | defaultValue: number, 206 | limitedToValues?: number[], 207 | callback?: IVariableCallback, 208 | ): NumberVariable { 209 | const variable = new NumberVariable(key, defaultValue, limitedToValues, callback); 210 | this.addVariable(variable); 211 | return variable; 212 | } 213 | 214 | /** 215 | * Adds a color variable to array of variables with optional callback. 216 | * @param {string} key The key of the Variable. 217 | * @param {string} defaultValue The initial default value of the variable. 218 | * @param {string[]} limitedToValues The optional array of allowed values. 219 | * @param {IVariableCallback} callback The callback method to be invoked 220 | * when the Variable is updated. 221 | * @return {ColorVariable} 222 | */ 223 | static addColorVariable( 224 | key: string, 225 | defaultValue: string, 226 | limitedToValues?: string[], 227 | callback?: IVariableCallback, 228 | ): ColorVariable { 229 | const variable = new ColorVariable(key, defaultValue, limitedToValues, callback); 230 | this.addVariable(variable); 231 | return variable; 232 | } 233 | 234 | /** 235 | * Adds a variable to the array of variables. Once added, will save and 236 | * execute the callback. 237 | * @private 238 | * @static 239 | * @param {Variable} variable The variable to add. 240 | */ 241 | private static addVariable(variable: Variable): void { 242 | const key: string = variable.key; 243 | const existingVariable = this.getVariable(key); 244 | if (existingVariable) { 245 | // Variable with key already exists, so only add callback. 246 | // TODO(cjcox:) Determine what to do if variable key already exists. 247 | } else { 248 | this._sharedInstance._variables[key] = variable; 249 | const storedVariable = LocalStorage.getVariable(key); 250 | if (storedVariable) { 251 | // Update variable if exists in storage. 252 | this.updateVariable(variable, storedVariable.selectedValue); 253 | Remote.saveVariable(variable, false); 254 | } else { 255 | // Save variable first time. 256 | this.saveVariable(variable); 257 | variable.executeCallbacks(); 258 | } 259 | } 260 | } 261 | 262 | private _variables: IVariableKeyMap = {}; 263 | 264 | /** 265 | * Returns the Variable-Key mapping from the Remixer shared instance. 266 | * @return {IVariableKeyMap} 267 | */ 268 | get variables(): IVariableKeyMap { 269 | return this._variables; 270 | } 271 | 272 | /** 273 | * Returns an array of Variables from the Remixer shared instance. 274 | * @return {Variable[]} Array of Variables. 275 | */ 276 | get variablesArray(): Variable[] { 277 | return Object.keys(this._variables).map((key) => this._variables[key]); 278 | } 279 | 280 | /** 281 | * Returns an Variable for a given key from the Remixer shared instance. 282 | * @static 283 | * @return {Variable[]} Array of Variables. 284 | */ 285 | static getVariable(key: string): Variable { 286 | return this._sharedInstance._variables[key]; 287 | } 288 | 289 | /** 290 | * Updates the selected value of a given Variable from the Remixer shared 291 | * instance. 292 | * @static 293 | * @param {Variable} variable The Variable to update. 294 | * @param {any} selectedValue The new selected value. 295 | */ 296 | static updateVariable(variable: Variable, selectedValue: any): void { 297 | if (variable.selectedValue !== selectedValue) { 298 | variable.selectedValue = selectedValue; 299 | } 300 | } 301 | 302 | /** 303 | * Clones and updates the selected value of a given Variable from the Remixer 304 | * shared instance. Allows immutability update required for React rendering. 305 | * @static 306 | * @param {Variable} variable The variable to clone and update. 307 | * @param {any} selectedValue The new selected value. 308 | * @return {Variable} The cloned variable with updated selected value. 309 | */ 310 | static cloneAndUpdateVariable(variable: Variable, selectedValue: any): Variable { 311 | // First make sure selected value is in proper format. 312 | selectedValue = variable.formatValue(selectedValue); 313 | 314 | if (variable.selectedValue === selectedValue) { 315 | return variable; 316 | } 317 | 318 | const clonedVariable = variable.clone(); 319 | this.attachedInstance._variables[variable.key] = clonedVariable; 320 | this.updateVariable(clonedVariable, selectedValue); 321 | return clonedVariable; 322 | } 323 | 324 | /** 325 | * Saves the Variable to both local storage and remote. 326 | * @static 327 | * @param {Variable} variable The Variable to save. 328 | */ 329 | static saveVariable(variable: Variable): void { 330 | LocalStorage.saveVariable(variable); 331 | 332 | // Save remotely. If remote sharing is disabled, a call to this method 333 | // will simply be a no-op. 334 | Remote.saveVariable(variable); 335 | } 336 | 337 | /** 338 | * Returns the current remote controller class. 339 | * @return {Remote} 340 | */ 341 | get remote(): Remote { 342 | return Remote.attachedInstance; 343 | } 344 | } 345 | 346 | // Export Remixer. 347 | export { Remixer as remixer }; 348 | -------------------------------------------------------------------------------- /src/core/__tests__/BooleanVariable_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as sinon from 'sinon'; 19 | import * as sinonChai from 'sinon-chai'; 20 | 21 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 22 | import { remixer } from '../Remixer'; 23 | import { BooleanVariable } from '../variables/BooleanVariable'; 24 | import { Variable } from '../variables/Variable'; 25 | 26 | const expect = chai.expect; 27 | chai.use(sinonChai); 28 | 29 | describe('BooleanVariable', () => { 30 | const key: string = 'test variable'; 31 | const sanitizedKey: string = 'test_variable'; 32 | const initialValue: boolean = true; 33 | let callbackSpy: sinon.SinonSpy; 34 | let variable: BooleanVariable; 35 | 36 | beforeEach(() => { 37 | callbackSpy = sinon.spy(); 38 | variable = remixer.addBooleanVariable(key, initialValue, callbackSpy); 39 | }); 40 | 41 | it('should create a new variable', () => { 42 | expect(variable).to.be.instanceof(Variable).and.instanceof(BooleanVariable); 43 | }); 44 | 45 | it('have the correct datatype', () => { 46 | expect(variable.dataType).to.equal(DataType.BOOLEAN); 47 | }); 48 | 49 | it('have the correct contraintType', () => { 50 | expect(variable.constraintType).to.equal(ConstraintType.NONE); 51 | }); 52 | 53 | it('have the correct controlType', () => { 54 | expect(variable.controlType).to.equal(ControlType.SWITCH); 55 | }); 56 | 57 | it('have the correct title', () => { 58 | expect(variable.title).to.equal(key); 59 | }); 60 | 61 | it('have the correct sanitized key', () => { 62 | expect(variable.key).to.equal(sanitizedKey); 63 | }); 64 | 65 | it('should trigger callback when selected value of variable changes', () => { 66 | const newValue = !variable.selectedValue; 67 | variable.selectedValue = newValue; 68 | 69 | const updatedVariable = callbackSpy.args[0][0]; 70 | expect(callbackSpy).to.have.been.calledOnce.and.calledWith(variable); 71 | expect(updatedVariable.selectedValue).to.equal(newValue); 72 | }); 73 | 74 | it('should clone properly', () => { 75 | const clone = variable.clone(); 76 | expect(JSON.stringify(clone)).to.equal(JSON.stringify(variable)); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /src/core/__tests__/ColorVariable_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as sinon from 'sinon'; 19 | import * as sinonChai from 'sinon-chai'; 20 | 21 | import { ColorUtils } from '../../lib/ColorUtils'; 22 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 23 | import { remixer } from '../Remixer'; 24 | import { ColorVariable } from '../variables/ColorVariable'; 25 | import { Variable } from '../variables/Variable'; 26 | 27 | const expect = chai.expect; 28 | chai.use(sinonChai); 29 | 30 | describe('ColorVariable', () => { 31 | const key: string = 'test variable'; 32 | const sanitizedKey: string = 'test_variable'; 33 | const initialValue: string = '#4285F4'; 34 | const limitedToValues: string[] = ['#4285F4', '#0F9D58', '#DB4437']; 35 | let callbackSpy: sinon.SinonSpy; 36 | let variable: ColorVariable; 37 | 38 | beforeEach(() => { 39 | callbackSpy = sinon.spy(); 40 | variable = remixer.addColorVariable( 41 | key, 42 | initialValue, 43 | limitedToValues, 44 | callbackSpy, 45 | ); 46 | }); 47 | 48 | it('should create a new variable', () => { 49 | expect(variable).to.be.instanceof(Variable).and.instanceof(ColorVariable); 50 | }); 51 | 52 | it('have the correct datatype', () => { 53 | expect(variable.dataType).to.equal(DataType.COLOR); 54 | }); 55 | 56 | it('have the correct contraintType', () => { 57 | expect(variable.constraintType).to.equal(ConstraintType.LIST); 58 | 59 | variable.limitedToValues = []; 60 | expect(variable.constraintType).to.equal(ConstraintType.NONE); 61 | }); 62 | 63 | it('have the correct controlType', () => { 64 | expect(variable.controlType).to.equal(ControlType.COLOR_LIST); 65 | }); 66 | 67 | it('should have correct controlType based on number of allowed values', () => { 68 | // List control. 69 | expect(variable.controlType).to.equal(ControlType.COLOR_LIST); 70 | 71 | // Input control. 72 | const var1 = remixer.addColorVariable('test_key', '#4285F4'); 73 | expect(var1.controlType).to.equal(ControlType.COLOR_INPUT); 74 | }); 75 | 76 | it('have the correct title', () => { 77 | expect(variable.title).to.equal(key); 78 | }); 79 | 80 | it('have the correct sanitized key', () => { 81 | expect(variable.key).to.equal(sanitizedKey); 82 | }); 83 | 84 | it('have the correct allowed values', () => { 85 | expect(variable.limitedToValues).to.equal(limitedToValues); 86 | }); 87 | 88 | it('should trigger callback when selected value of variable changes', () => { 89 | const newValue = '#0F9D58'; 90 | variable.selectedValue = newValue; 91 | 92 | const updatedVariable = callbackSpy.args[0][0]; 93 | expect(callbackSpy).to.have.been.calledOnce.and.calledWith(variable); 94 | expect(updatedVariable.selectedValue.toLowerCase()).to.equal(newValue.toLowerCase()); 95 | }); 96 | 97 | it('should clone properly', () => { 98 | const clone = variable.clone(); 99 | expect(JSON.stringify(clone)).to.equal(JSON.stringify(variable)); 100 | }); 101 | 102 | it('should return string color value after format', () => { 103 | const color = 'rgba(1, 1, 1, 0.8)'; 104 | const rgbaColor = ColorUtils.toRgba(color); 105 | const formattedColor = variable.formatValue(rgbaColor); 106 | 107 | expect(color).to.equal(formattedColor); 108 | expect(color).to.not.equal(rgbaColor); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /src/core/__tests__/NumberVariable_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as sinon from 'sinon'; 19 | import * as sinonChai from 'sinon-chai'; 20 | 21 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 22 | import { remixer } from '../Remixer'; 23 | import { NumberVariable } from '../variables/NumberVariable'; 24 | import { Variable } from '../variables/Variable'; 25 | 26 | const expect = chai.expect; 27 | chai.use(sinonChai); 28 | 29 | describe('NumberVariable', () => { 30 | const key: string = 'test variable'; 31 | const sanitizedKey: string = 'test_variable'; 32 | const initialValue: number = 20; 33 | const limitedToValues: number[] = [10, 20, 30, 40]; 34 | let callbackSpy: sinon.SinonSpy; 35 | let variable: NumberVariable; 36 | 37 | beforeEach(() => { 38 | callbackSpy = sinon.spy(); 39 | variable = remixer.addNumberVariable( 40 | key, 41 | initialValue, 42 | limitedToValues, 43 | callbackSpy, 44 | ); 45 | }); 46 | 47 | it('should create a new variable', () => { 48 | expect(variable).to.be.instanceof(Variable).and.instanceof(NumberVariable); 49 | }); 50 | 51 | it('have the correct datatype', () => { 52 | expect(variable.dataType).to.equal(DataType.NUMBER); 53 | }); 54 | 55 | it('have the correct contraintType', () => { 56 | expect(variable.constraintType).to.equal(ConstraintType.LIST); 57 | 58 | variable.limitedToValues = []; 59 | expect(variable.constraintType).to.equal(ConstraintType.NONE); 60 | }); 61 | 62 | it('should have correct controlType based on number of allowed values', () => { 63 | // List control. 64 | expect(variable.controlType).to.equal(ControlType.TEXT_LIST); 65 | 66 | // Segmented control. 67 | const var1 = remixer.addNumberVariable('test_key1', 1, [1, 2]); 68 | expect(var1.controlType).to.equal(ControlType.SEGMENTED); 69 | 70 | // Text input control. 71 | const var2 = remixer.addNumberVariable('test_key2', 1); 72 | expect(var2.controlType).to.equal(ControlType.TEXT_INPUT); 73 | }); 74 | 75 | it('have the correct title', () => { 76 | expect(variable.title).to.equal(key); 77 | }); 78 | 79 | it('have the correct sanitized key', () => { 80 | expect(variable.key).to.equal(sanitizedKey); 81 | }); 82 | 83 | it('have the correct allowed values', () => { 84 | expect(variable.limitedToValues).to.equal(limitedToValues); 85 | }); 86 | 87 | it('should trigger callback when selected value of variable changes', () => { 88 | const newValue = 100; 89 | variable.selectedValue = newValue; 90 | 91 | const updatedVariable = callbackSpy.args[0][0]; 92 | expect(callbackSpy).to.have.been.calledOnce.and.calledWith(variable); 93 | expect(updatedVariable.selectedValue).to.equal(newValue); 94 | }); 95 | 96 | it('should clone properly', () => { 97 | const clone = variable.clone(); 98 | expect(JSON.stringify(clone)).to.equal(JSON.stringify(variable)); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /src/core/__tests__/RangeVariable_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as sinon from 'sinon'; 19 | import * as sinonChai from 'sinon-chai'; 20 | 21 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 22 | import { remixer } from '../Remixer'; 23 | import { RangeVariable } from '../variables/RangeVariable'; 24 | import { Variable } from '../variables/Variable'; 25 | 26 | const expect = chai.expect; 27 | chai.use(sinonChai); 28 | 29 | describe('RangeVariable', () => { 30 | const key: string = 'test variable'; 31 | const sanitizedKey: string = 'test_variable'; 32 | const initialValue: number = 1; 33 | const minValue: number = 0; 34 | const maxValue: number = 1; 35 | const increment: number = 0.1; 36 | let callbackSpy: sinon.SinonSpy; 37 | let variable: RangeVariable; 38 | 39 | beforeEach(() => { 40 | callbackSpy = sinon.spy(); 41 | variable = remixer.addRangeVariable( 42 | key, 43 | initialValue, 44 | minValue, 45 | maxValue, 46 | increment, 47 | callbackSpy, 48 | ); 49 | }); 50 | 51 | it('should create a new variable', () => { 52 | expect(variable).to.be.instanceof(Variable).and.instanceof(RangeVariable); 53 | }); 54 | 55 | it('have the correct datatype', () => { 56 | expect(variable.dataType).to.equal(DataType.NUMBER); 57 | }); 58 | 59 | it('have the correct contraintType', () => { 60 | expect(variable.constraintType).to.equal(ConstraintType.RANGE); 61 | }); 62 | 63 | it('have the correct controlType', () => { 64 | expect(variable.controlType).to.equal(ControlType.SLIDER); 65 | }); 66 | 67 | it('have the correct title', () => { 68 | expect(variable.title).to.equal(key); 69 | }); 70 | 71 | it('have the correct sanitized key', () => { 72 | expect(variable.key).to.equal(sanitizedKey); 73 | }); 74 | 75 | it('have the correct min, max, and increment properties', () => { 76 | expect(variable.minValue).to.equal(minValue); 77 | expect(variable.maxValue).to.equal(maxValue); 78 | expect(variable.increment).to.equal(increment); 79 | }); 80 | 81 | it('should trigger callback when selected value of variable changes', () => { 82 | const newValue = 0.4; 83 | variable.selectedValue = newValue; 84 | 85 | const updatedVariable = callbackSpy.args[0][0]; 86 | expect(callbackSpy).to.have.been.calledOnce.and.calledWith(variable); 87 | expect(updatedVariable.selectedValue).to.equal(newValue); 88 | }); 89 | 90 | it('should clone properly', () => { 91 | const clone = variable.clone(); 92 | expect(JSON.stringify(clone)).to.equal(JSON.stringify(variable)); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /src/core/__tests__/Remixer_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as sinon from 'sinon'; 19 | import * as sinonChai from 'sinon-chai'; 20 | 21 | import { DataType } from '../../lib/Constants'; 22 | import { remixer } from '../Remixer'; 23 | 24 | const expect = chai.expect; 25 | chai.use(sinonChai); 26 | 27 | describe('Remixer', () => { 28 | 29 | beforeEach(() => { 30 | localStorage.clear(); 31 | remixer.addBooleanVariable('key1', true); 32 | remixer.addStringVariable('key2', 'testString'); 33 | remixer.addNumberVariable('key3', 40); 34 | remixer.addColorVariable('key4', '#4285F4', ['#4285F4', '#0F9D58']); 35 | remixer.addRangeVariable('key5', 24, 20, 40, 2); 36 | }); 37 | 38 | it('should create an iframe after start', () => { 39 | expect(remixer.frameElement).to.not.exist; 40 | remixer.start(); 41 | expect(remixer.frameElement).to.exist; 42 | }); 43 | 44 | it('have the correct number of variables in array', () => { 45 | const variablesArray = remixer.attachedInstance.variablesArray; 46 | expect(variablesArray).to.have.length(5); 47 | }); 48 | 49 | it('should retrieve variables from map', () => { 50 | const variablesMap = remixer.attachedInstance.variables; 51 | expect(variablesMap).to.have.all.keys('key1', 'key2', 'key3', 'key4', 'key5'); 52 | }); 53 | 54 | it('should retrieve correct variable from map by key', () => { 55 | const stringVariable = remixer.getVariable('key2'); 56 | expect(stringVariable.dataType).to.equal(DataType.STRING); 57 | }); 58 | 59 | it('should update selected value of variable', () => { 60 | const numberVariable = remixer.getVariable('key3'); 61 | expect(numberVariable.selectedValue).to.equal(40); 62 | 63 | remixer.updateVariable(numberVariable, 50); 64 | expect(numberVariable.selectedValue).to.equal(50); 65 | }); 66 | 67 | it('should clone and update selected value of variable', () => { 68 | const rangeVariable = remixer.getVariable('key5'); 69 | const clone = remixer.cloneAndUpdateVariable(rangeVariable, 30); 70 | 71 | expect(rangeVariable.dataType).to.equal(DataType.NUMBER); 72 | expect(rangeVariable.selectedValue).to.equal(24); 73 | expect(clone.dataType).to.equal(DataType.NUMBER); 74 | expect(clone.selectedValue).to.equal(30); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/core/__tests__/StringVariable_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as sinon from 'sinon'; 19 | import * as sinonChai from 'sinon-chai'; 20 | 21 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 22 | import { remixer } from '../Remixer'; 23 | import { StringVariable } from '../variables/StringVariable'; 24 | import { Variable } from '../variables/Variable'; 25 | 26 | const expect = chai.expect; 27 | chai.use(sinonChai); 28 | 29 | describe('StringVariable', () => { 30 | const key: string = 'test variable'; 31 | const sanitizedKey: string = 'test_variable'; 32 | const initialValue: string = 'B'; 33 | const limitedToValues: string[] = ['A', 'B', 'C', 'D']; 34 | let callbackSpy: sinon.SinonSpy; 35 | let variable: StringVariable; 36 | 37 | beforeEach(() => { 38 | callbackSpy = sinon.spy(); 39 | variable = remixer.addStringVariable( 40 | key, 41 | initialValue, 42 | limitedToValues, 43 | callbackSpy, 44 | ); 45 | }); 46 | 47 | it('should create a new variable', () => { 48 | expect(variable).to.be.instanceof(Variable).and.instanceof(StringVariable); 49 | }); 50 | 51 | it('have the correct datatype', () => { 52 | expect(variable.dataType).to.equal(DataType.STRING); 53 | }); 54 | 55 | it('have the correct contraintType', () => { 56 | expect(variable.constraintType).to.equal(ConstraintType.LIST); 57 | 58 | variable.limitedToValues = []; 59 | expect(variable.constraintType).to.equal(ConstraintType.NONE); 60 | }); 61 | 62 | it('should have correct controlType based on number of allowed values', () => { 63 | // List control. 64 | expect(variable.controlType).to.equal(ControlType.TEXT_LIST); 65 | 66 | // Segmented control. 67 | const var1 = remixer.addStringVariable('test_key1', 'a', ['a', 'b']); 68 | expect(var1.controlType).to.equal(ControlType.SEGMENTED); 69 | 70 | // Text input control. 71 | const var2 = remixer.addStringVariable('test_key2', 'a'); 72 | expect(var2.controlType).to.equal(ControlType.TEXT_INPUT); 73 | }); 74 | 75 | it('have the correct title', () => { 76 | expect(variable.title).to.equal(key); 77 | }); 78 | 79 | it('have the correct sanitized key', () => { 80 | expect(variable.key).to.equal(sanitizedKey); 81 | }); 82 | 83 | it('have the correct allowed values', () => { 84 | expect(variable.limitedToValues).to.equal(limitedToValues); 85 | }); 86 | 87 | it('should trigger callback when selected value of variable changes', () => { 88 | const newValue = 'B'; 89 | variable.selectedValue = newValue; 90 | 91 | const updatedVariable = callbackSpy.args[0][0]; 92 | expect(callbackSpy).to.have.been.calledOnce.and.calledWith(variable); 93 | expect(updatedVariable.selectedValue).to.equal(newValue); 94 | }); 95 | 96 | it('should clone properly', () => { 97 | const clone = variable.clone(); 98 | expect(JSON.stringify(clone)).to.equal(JSON.stringify(variable)); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /src/core/variables/BooleanVariable.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { ControlType, DataType } from '../../lib/Constants'; 18 | import { ISerializableData } from '../../lib/LocalStorage'; 19 | import { IVariableCallback, IVariableParams, Variable } from './Variable'; 20 | 21 | /** 22 | * Interface for a class that represents a type of Variable for boolean values. 23 | * @interface 24 | * @extends IVariableParams 25 | */ 26 | interface IBooleanVariableParams extends IVariableParams { 27 | initialValue: boolean; 28 | selectedValue: boolean; 29 | } 30 | 31 | /** 32 | * A class representing a type of Variable for boolean values. 33 | * @class 34 | * @extends Variable 35 | * @implements {IBooleanVariableParams} 36 | */ 37 | export class BooleanVariable extends Variable implements IBooleanVariableParams { 38 | 39 | /** 40 | * Creates an instance of a BooleanVariable. 41 | * @constructor 42 | * @param {string} key A unique key for the Variable. 43 | * @param {boolean} initialValue The initial selected value. 44 | * @param {IVariableCallback} callback The callback to invoke when updated. 45 | * @return {BooleanVariable} 46 | */ 47 | constructor( 48 | key: string, 49 | initialValue: boolean, 50 | callback?: IVariableCallback, 51 | ) { 52 | super(key, DataType.BOOLEAN, initialValue, callback); 53 | this.controlType = ControlType.SWITCH; 54 | } 55 | 56 | /** 57 | * Clones the variable. 58 | * @return {BooleanVariable} Returns the cloned variable. 59 | */ 60 | clone() { 61 | const cloned = new BooleanVariable(this.key, this.selectedValue, null); 62 | cloned.title = this.title; 63 | cloned._callbacks = this._callbacks.slice(); 64 | return cloned; 65 | } 66 | 67 | /** 68 | * Returns a serialized representation of this object. 69 | * @override 70 | * @return {ISerializableData} The serialized data. 71 | */ 72 | serialize(): ISerializableData { 73 | const data = super.serialize(); 74 | data.selectedValue = this.selectedValue; 75 | return data; 76 | } 77 | 78 | /** 79 | * Returns a new initialized BooleanVariable from serialized data. 80 | * @override 81 | * @param {ISerializableData} data The serialized data. 82 | * @return {BooleanVariable} A new initialized BooleanVariable. 83 | */ 84 | static deserialize(data: ISerializableData): BooleanVariable { 85 | const variable = new BooleanVariable(data.key, data.selectedValue); 86 | variable.title = data.title; 87 | return variable; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/core/variables/ColorVariable.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { ColorUtils, RgbaColor } from '../../lib/ColorUtils'; 18 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 19 | import { ISerializableData } from '../../lib/LocalStorage'; 20 | import { IVariableCallback, IVariableListParams, Variable } from './Variable'; 21 | 22 | /** 23 | * Interface for a class that represents a type of Variable for color values. 24 | * @interface 25 | * @extends IVariableListParams 26 | */ 27 | interface IColorVariableParams extends IVariableListParams { 28 | initialValue: string; 29 | selectedValue: string; 30 | limitedToValues?: string[]; 31 | } 32 | 33 | /** 34 | * A class representing a type of Variable for color values. 35 | * @class 36 | * @extends Variable 37 | * @implements {IColorVariableParams} 38 | */ 39 | export class ColorVariable extends Variable implements IColorVariableParams { 40 | 41 | /** 42 | * Creates an instance of a ColorVariable. 43 | * @constructor 44 | * @param {string} key A unique key for the Variable. 45 | * @param {string} initialValue The initial selected value. 46 | * @param {string[]} limitedToValues The array of allowed values. 47 | * @param {IVariableCallback} callback The callback to invoke when updated. 48 | * @return {ColorVariable} 49 | */ 50 | constructor( 51 | key: string, 52 | initialValue: string, 53 | limitedToValues?: string[], 54 | callback?: IVariableCallback, 55 | ) { 56 | super(key, DataType.COLOR, initialValue, callback); 57 | this.limitedToValues = limitedToValues ? limitedToValues : []; 58 | this.controlType = (this.limitedToValues.length > 0) ? 59 | ControlType.COLOR_LIST : ControlType.COLOR_INPUT; 60 | } 61 | 62 | /** 63 | * The data constraint type for this Variable. 64 | * @type {string} 65 | * @readonly 66 | */ 67 | get constraintType(): string { 68 | return this.limitedToValues.length > 0 ? 69 | ConstraintType.LIST : ConstraintType.NONE; 70 | } 71 | 72 | /** 73 | * Clones the variable. 74 | * @return {ColorVariable} Returns the cloned variable. 75 | */ 76 | clone() { 77 | const cloned = new ColorVariable( 78 | this.key, 79 | this.selectedValue, 80 | this.limitedToValues, 81 | ); 82 | cloned.title = this.title; 83 | cloned._callbacks = this._callbacks.slice(); 84 | return cloned; 85 | } 86 | 87 | /** 88 | * The array of allowed values for this Variable. 89 | * @override 90 | * @type {string[]} 91 | */ 92 | limitedToValues?: string[]; 93 | 94 | /** 95 | * Returns a serialized representation of this object. 96 | * @override 97 | * @return {ISerializableData} The serialized data. 98 | */ 99 | serialize(): ISerializableData { 100 | const data = super.serialize(); 101 | data.selectedValue = ColorUtils.toRgba(this.selectedValue); 102 | data.limitedToValues = this.limitedToValues.map((value: any) => { 103 | return ColorUtils.toRgba(value); 104 | }); 105 | return data; 106 | } 107 | 108 | /** 109 | * Returns a new initialized ColorVariable from serialized data. 110 | * @override 111 | * @param {ISerializableData} data The serialized data. 112 | * @return {ColorVariable} A new initialized ColorVariable. 113 | */ 114 | static deserialize(data: ISerializableData): Variable { 115 | const selectedValue = ColorUtils.toRgbaString(data.selectedValue); 116 | const limitedToValues = data.limitedToValues.map((color: RgbaColor) => { 117 | return ColorUtils.toRgbaString(color); 118 | }); 119 | const variable = new ColorVariable(data.key, selectedValue, limitedToValues); 120 | variable.title = data.title; 121 | return variable; 122 | } 123 | 124 | /** 125 | * Returns color value formatted as string if not already. 126 | * @override 127 | * @param {any} value The value that should be formatted. 128 | * @return {string} Return the original string or formatted string value. 129 | */ 130 | formatValue(value: any): string { 131 | if (typeof value === 'object') { 132 | return ColorUtils.toRgbaString(value); 133 | } 134 | return value; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/core/variables/NumberVariable.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 18 | import { ISerializableData } from '../../lib/LocalStorage'; 19 | import { IVariableCallback, IVariableListParams, Variable } from './Variable'; 20 | 21 | /** 22 | * Interface for a class that represents a type of Variable for number values. 23 | * @interface 24 | * @extends IVariableListParams 25 | */ 26 | interface INumberVariableParams extends IVariableListParams { 27 | initialValue: number; 28 | selectedValue: number; 29 | limitedToValues?: number[]; 30 | } 31 | 32 | /** 33 | * A class representing a type of Variable for number values. 34 | * @class 35 | * @extends Variable 36 | * @implements {INumberVariableParams} 37 | */ 38 | export class NumberVariable extends Variable implements INumberVariableParams { 39 | 40 | /** 41 | * Creates an instance of a ColorVariable. 42 | * @constructor 43 | * @param {string} key A unique key for the Variable. 44 | * @param {number} initialValue The initial selected value. 45 | * @param {number[]} limitedToValues The array of allowed values. 46 | * @param {IVariableCallback} callback The callback to invoke when updated. 47 | * @return {NumberVariable} 48 | */ 49 | constructor( 50 | key: string, 51 | initialValue: number, 52 | limitedToValues?: number[], 53 | callback?: IVariableCallback, 54 | ) { 55 | super(key, DataType.NUMBER, initialValue, callback); 56 | this.limitedToValues = limitedToValues ? limitedToValues : []; 57 | if (this.limitedToValues.length === 0) { 58 | this.controlType = ControlType.TEXT_INPUT; 59 | } else if (this.limitedToValues.length <= 2) { 60 | this.controlType = ControlType.SEGMENTED; 61 | } else { 62 | this.controlType = ControlType.TEXT_LIST; 63 | } 64 | } 65 | 66 | /** 67 | * The data constraint type for this Variable. 68 | * @type {string} 69 | * @readonly 70 | */ 71 | get constraintType(): string { 72 | return this.limitedToValues.length > 0 ? 73 | ConstraintType.LIST : ConstraintType.NONE; 74 | } 75 | 76 | /** 77 | * Clones the variable. 78 | * @return {NumberVariable} Returns the cloned variable. 79 | */ 80 | clone() { 81 | const cloned = new NumberVariable( 82 | this.key, 83 | this.selectedValue, 84 | this.limitedToValues, 85 | ); 86 | cloned.title = this.title; 87 | cloned._callbacks = this._callbacks.slice(); 88 | return cloned; 89 | } 90 | 91 | /** 92 | * The array of allowed values for this Variable. 93 | * @override 94 | * @type {number[]} 95 | */ 96 | limitedToValues?: number[]; 97 | 98 | /** 99 | * Returns a serialized representation of this object. 100 | * @override 101 | * @return {ISerializableData} The serialized data. 102 | */ 103 | serialize(): ISerializableData { 104 | const data = super.serialize(); 105 | data.selectedValue = this.selectedValue; 106 | data.limitedToValues = this.limitedToValues; 107 | return data; 108 | } 109 | 110 | /** 111 | * Returns a new initialized NumberVariable from serialized data. 112 | * @override 113 | * @param {ISerializableData} data The serialized data. 114 | * @return {NumberVariable} A new initialized NumberVariable. 115 | */ 116 | static deserialize(data: ISerializableData): Variable { 117 | const variable = new NumberVariable( 118 | data.key, 119 | data.selectedValue, 120 | data.limitedToValues, 121 | ); 122 | variable.title = data.title; 123 | return variable; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/core/variables/RangeVariable.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 18 | import { ISerializableData } from '../../lib/LocalStorage'; 19 | import { IVariableCallback, IVariableParams, Variable } from './Variable'; 20 | 21 | /** 22 | * Interface for a class that represents a type of Variable for a range 23 | * of values. 24 | * @interface 25 | * @extends IVariableParams 26 | */ 27 | export interface IRangeVariableParams extends IVariableParams { 28 | initialValue: number; 29 | selectedValue: number; 30 | minValue: number; 31 | maxValue: number; 32 | increment: number; 33 | callback?: IVariableCallback; 34 | } 35 | 36 | /** 37 | * A class representing a type of Variable for a range of values. 38 | * @class 39 | * @extends Variable 40 | * @implements {IRangeVariableParams} 41 | */ 42 | export class RangeVariable extends Variable implements IRangeVariableParams { 43 | 44 | /** 45 | * Creates an instance of a RangeVariable. 46 | * @constructor 47 | * @param {string} key A unique key for the Variable. 48 | * @param {number} initialValue The initial selected value. 49 | * @param {number} minValue The minimum value allowed. 50 | * @param {number} maxValue The maximum value allowed. 51 | * @param {IVariableCallback} callback The callback to invoke when updated. 52 | * @return {RangeVariable} 53 | */ 54 | constructor( 55 | key: string, 56 | initialValue: number, 57 | minValue: number, 58 | maxValue: number, 59 | increment: number, 60 | callback?: IVariableCallback, 61 | ) { 62 | super(key, DataType.NUMBER, initialValue, callback); 63 | this.minValue = minValue; 64 | this.maxValue = maxValue; 65 | this.increment = increment; 66 | this.controlType = ControlType.SLIDER; 67 | } 68 | 69 | /** 70 | * The data constraint type for this Variable. 71 | * @type {string} 72 | * @readonly 73 | */ 74 | get constraintType(): string { 75 | return ConstraintType.RANGE; 76 | } 77 | 78 | /** 79 | * Clones the variable. 80 | * @return {RangeVariable} Returns the cloned variable. 81 | */ 82 | clone() { 83 | const cloned = new RangeVariable( 84 | this.key, 85 | this.selectedValue, 86 | this.minValue, 87 | this.maxValue, 88 | this.increment, 89 | ); 90 | cloned.title = this.title; 91 | cloned._callbacks = this._callbacks.slice(); 92 | return cloned; 93 | } 94 | 95 | /** 96 | * The minimum value allowed for this Variable. 97 | * @override 98 | * @type {number} 99 | */ 100 | minValue: number; 101 | 102 | /** 103 | * The maximum value allowed for this Variable. 104 | * @override 105 | * @type {number} 106 | */ 107 | maxValue: number; 108 | 109 | /** 110 | * The increment value for this Variable. 111 | * @override 112 | * @type {number} 113 | */ 114 | increment: number; 115 | 116 | /** 117 | * Returns a serialized representation of this object. 118 | * @override 119 | * @return {ISerializableData} The serialized data. 120 | */ 121 | serialize(): ISerializableData { 122 | const data = super.serialize(); 123 | data.selectedValue = this.selectedValue; 124 | data.minValue = this.minValue; 125 | data.maxValue = this.maxValue; 126 | data.increment = this.increment; 127 | return data; 128 | } 129 | 130 | /** 131 | * Returns a new initialized RangeVariable from serialized data. 132 | * @override 133 | * @param {ISerializableData} data The serialized data. 134 | * @return {RangeVariable} A new initialized RangeVariable. 135 | */ 136 | static deserialize(data: ISerializableData): Variable { 137 | const selectedValue: number = data.selectedValue; 138 | const minValue: number = data.minValue; 139 | const maxValue: number = data.maxValue; 140 | const increment: number = data.increment; 141 | const variable = new RangeVariable( 142 | data.key, 143 | selectedValue, 144 | minValue, 145 | maxValue, 146 | increment, 147 | ); 148 | variable.title = data.title; 149 | return variable; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/core/variables/StringVariable.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { ConstraintType, ControlType, DataType } from '../../lib/Constants'; 18 | import { ISerializableData } from '../../lib/LocalStorage'; 19 | import { IVariableCallback, IVariableListParams, Variable } from './Variable'; 20 | 21 | /** 22 | * Interface for a class that represents a type of Variable for string values. 23 | * @interface 24 | * @extends IVariableListParams 25 | */ 26 | interface IStringVariableParams extends IVariableListParams { 27 | initialValue: string; 28 | selectedValue: string; 29 | limitedToValues?: string[]; 30 | } 31 | 32 | /** 33 | * A class representing a type of Variable for string values. 34 | * @class 35 | * @extends Variable 36 | * @implements {IStringVariableParams} 37 | */ 38 | export class StringVariable extends Variable implements IStringVariableParams { 39 | 40 | /** 41 | * Creates an instance of a StringVariable. 42 | * @constructor 43 | * @param {string} key [A unique key for the Variable. 44 | * @param {string} initialValue The initial selected value. 45 | * @param {string[]} limitedToValues The array of allowed values. 46 | * @param {IVariableCallback} callback The callback to invoke when updated. 47 | * @return {StringVariable} 48 | */ 49 | constructor( 50 | key: string, 51 | initialValue: string, 52 | limitedToValues?: string[], 53 | callback?: IVariableCallback, 54 | ) { 55 | super(key, DataType.STRING, initialValue, callback); 56 | this.limitedToValues = limitedToValues ? limitedToValues : []; 57 | if (this.limitedToValues.length === 0) { 58 | this.controlType = ControlType.TEXT_INPUT; 59 | } else if (this.limitedToValues.length <= 2) { 60 | this.controlType = ControlType.SEGMENTED; 61 | } else { 62 | this.controlType = ControlType.TEXT_LIST; 63 | } 64 | } 65 | 66 | /** 67 | * The data constraint type for this Variable. 68 | * @type {string} 69 | * @readonly 70 | */ 71 | get constraintType(): string { 72 | return this.limitedToValues.length > 0 ? 73 | ConstraintType.LIST : ConstraintType.NONE; 74 | } 75 | 76 | /** 77 | * Clones the variable. 78 | * @return {StringVariable} Returns the cloned variable. 79 | */ 80 | clone() { 81 | const cloned = new StringVariable( 82 | this.key, 83 | this.selectedValue, 84 | this.limitedToValues, 85 | ); 86 | cloned.title = this.title; 87 | cloned._callbacks = this._callbacks.slice(); 88 | return cloned; 89 | } 90 | 91 | /** 92 | * The array of allowed values for this Variable. 93 | * @override 94 | * @type {string[]} 95 | */ 96 | limitedToValues?: string[]; 97 | 98 | /** 99 | * Returns a serialized representation of this object. 100 | * @override 101 | * @return {ISerializableData} The serialized data. 102 | */ 103 | serialize(): ISerializableData { 104 | const data = super.serialize(); 105 | data.selectedValue = this.selectedValue; 106 | data.limitedToValues = this.limitedToValues; 107 | return data; 108 | } 109 | 110 | /** 111 | * Returns a new initialized StringVariable from serialized data. 112 | * @override 113 | * @param {ISerializableData} data The serialized data. 114 | * @return {StringVariable} A new initialized StringVariable. 115 | */ 116 | static deserialize(data: ISerializableData): StringVariable { 117 | const variable = new StringVariable( 118 | data.key, 119 | data.selectedValue, 120 | data.limitedToValues, 121 | ); 122 | variable.title = data.title; 123 | return variable; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/core/variables/Variable.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { ConstraintType } from '../../lib/Constants'; 18 | import { ISerializableData } from '../../lib/LocalStorage'; 19 | import { remixer } from '../Remixer'; 20 | 21 | /** 22 | * Interface for a class that represents a type a Variable. 23 | * @interface 24 | */ 25 | export interface IVariableParams { 26 | key: string; 27 | title: string; 28 | constraintType: string; 29 | controlType: string; 30 | dataType: string; 31 | initialValue: any; 32 | selectedValue: any; 33 | callbacks?: IVariableCallback[]; 34 | } 35 | 36 | /** 37 | * Interface for a class that represents a type of Variable that can only be 38 | * set to predefined values. 39 | * @interface 40 | * @extends IVariableParams 41 | */ 42 | export interface IVariableListParams extends IVariableParams { 43 | limitedToValues?: any[]; 44 | } 45 | 46 | /** 47 | * Interface for Function that returns a Variable. 48 | * @interface 49 | * @extends Function 50 | */ 51 | export interface IVariableCallback extends Function { 52 | variable?: Variable; 53 | } 54 | 55 | /** 56 | * Interface that maps a Variable to its key. 57 | * @interface 58 | */ 59 | export interface IVariableKeyMap { 60 | [key: string]: Variable; 61 | } 62 | 63 | /** 64 | * A class representing a type a Variable. 65 | * @class 66 | * @implements {IVariableParams} 67 | */ 68 | export class Variable implements IVariableParams { 69 | 70 | /** 71 | * Creates an instance of a Variable. 72 | * @param {string} key A unique key for the Variable. 73 | * @param {string} dataType The data type of this Variable. 74 | * @param {any} initialValue The initial selected value. 75 | * @param {IVariableCallback} callback The callback to invoke when updated. 76 | * @return {Variable} 77 | */ 78 | constructor( 79 | key: string, 80 | dataType: string, 81 | initialValue: any, 82 | callback?: IVariableCallback, 83 | ) { 84 | this.key = this.sanitizeKey(key); 85 | this.title = key; 86 | this.dataType = dataType; 87 | this.initialValue = initialValue; 88 | this._selectedValue = initialValue; 89 | if (callback) { 90 | this._callbacks.push(callback); 91 | } 92 | this._initialized = true; 93 | } 94 | 95 | /** 96 | * Clones the variable. 97 | * @return {Variable} Returns the cloned variable. 98 | */ 99 | clone() { 100 | const cloned = new Variable( 101 | this.key, 102 | this.dataType, 103 | this.selectedValue, 104 | null, 105 | ); 106 | cloned.title = this.title; 107 | cloned._callbacks = this._callbacks.slice(); 108 | return cloned; 109 | } 110 | 111 | /** 112 | * The data constraint type for this Variable. 113 | * @type {string} 114 | * @readonly 115 | */ 116 | get constraintType(): string { 117 | return ConstraintType.NONE; 118 | } 119 | 120 | /** 121 | * The rendered control type for this Variable. 122 | * @type {string} 123 | */ 124 | controlType: string; 125 | 126 | /** 127 | * The data type represented by this Variable. 128 | * @type {string} 129 | */ 130 | dataType: string; 131 | 132 | /** 133 | * The unique key for this Variable. 134 | * @type {string} 135 | */ 136 | key: string; 137 | 138 | /** 139 | * The title for this Variable. 140 | * @type {string} 141 | */ 142 | title: string; 143 | 144 | /** 145 | * The defalut value for this Variable. 146 | * @type {any} 147 | */ 148 | initialValue: any; 149 | 150 | /** 151 | * Whether this Variable has been initialized. 152 | * @private 153 | * @type {boolean} 154 | */ 155 | private _initialized: boolean = false; 156 | 157 | private _selectedValue: any; 158 | 159 | /** 160 | * The selected value. 161 | * @return {any} Returns the selected value. 162 | */ 163 | get selectedValue(): any { 164 | return this._selectedValue; 165 | } 166 | 167 | set selectedValue(value: any) { 168 | this._selectedValue = value; 169 | this.save(); 170 | if (this._initialized) { 171 | this.executeCallbacks(); 172 | } 173 | } 174 | 175 | protected _callbacks: IVariableCallback[] = new Array(); 176 | 177 | /** 178 | * The callback method to be invoked when the Variable is updated. 179 | * @return {IVariableCallback[]} The array of callback methods. 180 | */ 181 | get callbacks(): IVariableCallback[] { 182 | return this._callbacks; 183 | } 184 | 185 | /** 186 | * Adds a callback to array of callbacks. 187 | * @param {IVariableCallback} callback The callback to add. 188 | */ 189 | addCallback(callback: IVariableCallback): any { 190 | this._callbacks.push(callback); 191 | } 192 | 193 | /** 194 | * Invokes each of the callback methods. 195 | */ 196 | executeCallbacks(): void { 197 | for (const callback of this.callbacks) { 198 | callback(this); 199 | } 200 | } 201 | 202 | /** 203 | * First adds a callback to array, and then immediatly executes that callback. 204 | * @param {IVariableCallback} callback The callback to add and execute. 205 | */ 206 | addAndExecuteCallback(callback: IVariableCallback): void { 207 | this._callbacks.push(callback); 208 | callback(this); 209 | } 210 | 211 | /** 212 | * Saves the Variable. 213 | */ 214 | save(): void { 215 | remixer.saveVariable(this); 216 | } 217 | 218 | /** 219 | * Restores the Variable to its default value. 220 | */ 221 | restore(): void { 222 | this.selectedValue = this.initialValue; 223 | } 224 | 225 | /** 226 | * Returns a serialized representation of this object. 227 | * @return {ISerializableData} The serialized data. 228 | */ 229 | serialize(): ISerializableData { 230 | const data = {} as ISerializableData; 231 | data.key = this.key; 232 | data.constraintType = this.constraintType; 233 | data.controlType = this.controlType; 234 | data.dataType = this.dataType; 235 | data.title = this.title; 236 | return data; 237 | } 238 | 239 | /** 240 | * Subclass should override this method and return a new instance of the 241 | * Variable class from serialized data. 242 | * @param {ISerializableData} data The serialized data. 243 | * @return {Variable} A new initialized Variable subclass. 244 | */ 245 | static deserialize(data: ISerializableData): Variable { 246 | return null; 247 | } 248 | 249 | /** 250 | * Formats the key by removing any whitespaces. 251 | * @param {string} key The key to format 252 | * @return {string} The formatted key. 253 | */ 254 | private sanitizeKey(key: string): string { 255 | return key.split(' ').join('_'); 256 | } 257 | 258 | /** 259 | * Subclass should override this method and return a properly formatted value. 260 | * For example, a ColorVariable may choose to return an rgba string when 261 | * provided an RgbaColor object. 262 | * @param {any} value The value that should be formatted. 263 | * @return {any} Return either the original or formatted value. 264 | */ 265 | formatValue(value: any): any { 266 | return value; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/lib/ColorUtils.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as TinyColor from 'tinycolor2'; 18 | 19 | /** 20 | * A representation of RGBA color. 21 | * @interface 22 | */ 23 | export interface RgbaColor { 24 | // All values [0-255]. 25 | r: number; 26 | g: number; 27 | b: number; 28 | a: number; 29 | } 30 | 31 | /** 32 | * A class that provides utilities to interact with TinyColor library. 33 | * @class 34 | */ 35 | export class ColorUtils { 36 | 37 | /** 38 | * Converts a color string or RgbaColor to an RGBA string. 39 | * @param {any} color The color string or RgbaColor to convert. 40 | * @return {string} Returns the RGBA string. 41 | */ 42 | static toRgbaString(color: any): string { 43 | if (typeof color !== 'string') { 44 | // Convert alpha back from int[0-255] to [0-1] float. 45 | (color as RgbaColor).a /= 255; 46 | } 47 | return TinyColor(color).toRgbString(); 48 | } 49 | 50 | /** 51 | * Converts a color string to its RgbaColor equivalent. Any alpha values 52 | * will be normalized to int[0-255]. 53 | * @param {string} color The color string to convert. 54 | * @return {RgbaColor} Returns the RgbaColor. 55 | */ 56 | static toRgba(color: string): RgbaColor { 57 | const rgba = TinyColor(color).toRgb(); 58 | // Convert alpha from float to int[0-255]. 59 | rgba.a = Math.round(rgba.a * 255); 60 | return rgba; 61 | } 62 | 63 | /** 64 | * Returns whether to color strings are equal. 65 | * @param {string} color1 The first color to test if equal. 66 | * @param {string} color2 The second color to test if equal. 67 | * @return {boolean} Returns true if equal. Otherwise false. 68 | */ 69 | static areEqual(color1: string, color2: string): boolean { 70 | return TinyColor(color1).toRgbString() === TinyColor(color2).toRgbString(); 71 | } 72 | 73 | /** 74 | * Determine a readable color from a base color and list of possible colors. 75 | * @param {any} baseColor The base color of which to test for readable color. 76 | * @param {any[]} colorList A list of possible colors. 77 | * @return {string} Returns the most readable color. 78 | */ 79 | static mostReadable(baseColor: any, colorList: any[]): string { 80 | colorList.map((color) => { 81 | return TinyColor(color); 82 | }); 83 | return TinyColor.mostReadable(TinyColor(baseColor), colorList).toRgbString(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/lib/Constants.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** Keycode constants. */ 18 | export const KeyCode = { 19 | ESC: 27, 20 | }; 21 | 22 | /** Key event constants. */ 23 | export const KeyEvent = { 24 | DOWN: 'keydown', 25 | }; 26 | 27 | /** Storage keys constants. */ 28 | export const StorageKey = { 29 | KEY_REMIXER: 'remixer', 30 | KEY_REMOTE_ENABLED: 'remoteEnabled', 31 | KEY_REMOTE_ID: 'remoteId', 32 | KEY_VARIABLES: 'variables', 33 | PREFERENCES: '__remixer_preferences__', 34 | REMIXER: '__remixer__', 35 | }; 36 | 37 | /** Variable data constraints. */ 38 | export const ConstraintType = { 39 | LIST: '__ConstraintTypeList__', 40 | NONE: '__ConstraintTypeNone__', 41 | RANGE: '__ConstraintTypeRange__', 42 | }; 43 | 44 | /** Rendered variable control types. */ 45 | export const ControlType = { 46 | BUTTON: '__ControlTypeButton__', 47 | COLOR_INPUT: '__ControlTypeColorInput__', 48 | COLOR_LIST: '__ControlTypeColorList__', 49 | SEGMENTED: '__ControlTypeSegmented__', 50 | SLIDER: '__ControlTypeSlider__', 51 | STEPPER: '__ControlTypeStepper__', 52 | SWITCH: '__ControlTypeSwitch__', 53 | TEXT_INPUT: '__ControlTypeTextInput__', 54 | TEXT_LIST: '__ControlTypeTextList__', 55 | }; 56 | 57 | /** Variable data types. */ 58 | export const DataType = { 59 | BOOLEAN: '__DataTypeBoolean__', 60 | COLOR: '__DataTypeColor__', 61 | NUMBER: '__DataTypeNumber__', 62 | STRING: '__DataTypeString__', 63 | }; 64 | 65 | /** CSS class and id constants. */ 66 | export const CSS = { 67 | RMX_COLOR_SWATCH: 'rmx-color-swatch', 68 | RMX_COLOR_SWATCH_ITEM: 'rmx-color-swatch-item', 69 | RMX_DROPDOWN: 'rmx-dropdown', 70 | RMX_LIST_ITEM: 'rmx-list-item', 71 | RMX_OVERLAY_FRAME: '__remixer-overlay-frame__', 72 | RMX_OVERLAY_WRAPPER: 'rmx-overlay-wrapper', 73 | RMX_RADIO_LIST: 'rmx-radio-list', 74 | RMX_RADIO_LIST_ITEM: 'rmx-radio-list-item', 75 | RMX_SHARE_LINK: 'rmx-share-link', 76 | RMX_SHARE_MENU: 'rmx-share-menu', 77 | RMX_SHARE_STATUS: 'rmx-share-status', 78 | RMX_SLIDER: 'rmx-slider', 79 | RMX_SLIDER_MAX: 'rmx-slider-max-value', 80 | RMX_SLIDER_MIN: 'rmx-slider-min-value', 81 | RMX_SWITCH: 'rmx-switch', 82 | RMX_TEXTFIELD: 'rmx-textfield', 83 | RMX_VISIBLE: 'rmx-visible', 84 | }; 85 | -------------------------------------------------------------------------------- /src/lib/LocalStorage.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { BooleanVariable } from '../core/variables/BooleanVariable'; 18 | import { ColorVariable } from '../core/variables/ColorVariable'; 19 | import { NumberVariable } from '../core/variables/NumberVariable'; 20 | import { RangeVariable } from '../core/variables/RangeVariable'; 21 | import { StringVariable } from '../core/variables/StringVariable'; 22 | import { Variable } from '../core/variables/Variable'; 23 | import { ConstraintType, DataType, StorageKey } from '../lib/Constants'; 24 | 25 | /** 26 | * Interface for a class that represents serialized data. 27 | * @interface 28 | */ 29 | export interface ISerializableData { 30 | key: string; 31 | constraintType: string; 32 | controlType: string; 33 | dataType: string; 34 | title: string; 35 | initialValue: any; 36 | selectedValue: any; 37 | limitedToValues?: any[]; 38 | minValue?: number; 39 | maxValue?: number; 40 | increment?: number; 41 | } 42 | 43 | /** 44 | * Interface that maps a serialized data to a global "remixer" key. 45 | * @interface 46 | */ 47 | interface ISerializableDataMap { 48 | [remixer: string]: ISerializableData; 49 | } 50 | 51 | /** 52 | * Interface representing serialized preferences. 53 | * @interface 54 | */ 55 | interface ISerializablePreferences { 56 | remoteId: string; 57 | } 58 | 59 | /** 60 | * A class that provides utilities to interact with browser local storage. 61 | * @class 62 | */ 63 | export class LocalStorage { 64 | 65 | /** 66 | * Retrieves a single Variable from local storage. 67 | * @static 68 | * @param {string} key The key if the Variable to retrieve. 69 | * @return {Variable} 70 | */ 71 | static getVariable(key: string): Variable { 72 | const remixerData = this.getRawData(); 73 | const variableData = remixerData[key] as ISerializableData; 74 | if (variableData) { 75 | return this.deserialize(variableData); 76 | } 77 | return null; 78 | } 79 | 80 | /** 81 | * Saves a Variable to local storage. 82 | * @static 83 | * @param {Variable} variable The variable to save. 84 | */ 85 | static saveVariable(variable: Variable): void { 86 | const remixerData = this.getRawData(); 87 | remixerData[variable.key] = variable.serialize(); 88 | this.saveRawData(remixerData); 89 | } 90 | 91 | /** 92 | * Retrieves a preference from local storage. 93 | * @static 94 | * @param {string} key The key of the preference to retrieve. 95 | * @return {any} Returns the preference object. 96 | */ 97 | static getPreference(key: string): any { 98 | const prefs = this.getRawPreferences(); 99 | return prefs[key]; 100 | } 101 | 102 | /** 103 | * Saves a preference to local storage. 104 | * @static 105 | * @param {string} key The preference key. 106 | * @param {any} value The preference value. 107 | */ 108 | static savePreference(key: string, value: any): void { 109 | const prefs = this.getRawPreferences(); 110 | prefs[key] = value; 111 | this.saveRawPreferences(prefs); 112 | } 113 | 114 | /** 115 | * Returns an initialized Variable based on the data type. 116 | * @static 117 | * @param {ISerializableData} data The serialized data. 118 | * @return {Variable} 119 | */ 120 | static deserialize(data: ISerializableData): Variable { 121 | switch (data.dataType) { 122 | case DataType.BOOLEAN: 123 | return BooleanVariable.deserialize(data); 124 | case DataType.COLOR: 125 | return ColorVariable.deserialize(data); 126 | case DataType.NUMBER: 127 | if (data.constraintType === ConstraintType.RANGE) { 128 | return RangeVariable.deserialize(data); 129 | } 130 | return NumberVariable.deserialize(data); 131 | case DataType.STRING: 132 | return StringVariable.deserialize(data); 133 | default: 134 | return null; 135 | } 136 | } 137 | 138 | /** 139 | * Retrieves the raw JSON data from local storage. 140 | * @private 141 | * @static 142 | * @return {ISerializableDataMap} The json data from local storage. 143 | */ 144 | private static getRawData(): ISerializableDataMap { 145 | const data = JSON.parse(localStorage.getItem(StorageKey.REMIXER)); 146 | return data ? data[StorageKey.KEY_VARIABLES] : {}; 147 | } 148 | 149 | /** 150 | * Saves the raw JSON data to local storage. 151 | * @private 152 | * @static 153 | * @param {ISerializableDataMap} data The serialized data to save. 154 | */ 155 | private static saveRawData(data: ISerializableDataMap): void { 156 | localStorage.setItem(StorageKey.REMIXER, JSON.stringify({[StorageKey.KEY_VARIABLES]: data})); 157 | } 158 | 159 | /** 160 | * Retrieves the raw JSON preferences from local storage. 161 | * @private 162 | * @static 163 | * @return {ISerializablePreferences} The preferences from local storage. 164 | */ 165 | private static getRawPreferences(): ISerializablePreferences { 166 | const preferences = JSON.parse(localStorage.getItem(StorageKey.PREFERENCES)); 167 | return preferences || {}; 168 | } 169 | 170 | /** 171 | * Saves the raw JSON preferences to local storage. 172 | * @private 173 | * @static 174 | * @param {ISerializablePreferences} preferences The serialized preferences to save. 175 | */ 176 | private static saveRawPreferences(preferences: ISerializablePreferences): void { 177 | localStorage.setItem(StorageKey.PREFERENCES, JSON.stringify(preferences)); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/lib/Messaging.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { CSS } from '../lib/Constants'; 18 | 19 | /** 20 | * Available messaging types. 21 | * @enum 22 | */ 23 | enum MessagingType { 24 | ToggleVisibility, 25 | } 26 | 27 | /** 28 | * A class that provides ability to interact with the `window.postMessage` API. 29 | * 30 | * Under the hood, Remixer is appending an HTML iFrame to the body of a client's 31 | * page. The main purpose of this Message class is to simply wrap the 32 | * `window.postMessage` API to simplify passing messages between the root window 33 | * and the Remixer iFrame. 34 | */ 35 | export class Messaging { 36 | 37 | /** 38 | * The avaiable messaging types. 39 | * @static 40 | * @type {MessagingType} 41 | */ 42 | static type = MessagingType; 43 | 44 | /** 45 | * Registers an event listener to allow listening for window messages. 46 | * @static 47 | * @param {Function} callback A callback to invoke after registration. 48 | */ 49 | static register(callback?: Function): void { 50 | window.addEventListener('message', (e: MessageEvent) => { 51 | if (callback) { 52 | callback(e); 53 | } 54 | }); 55 | } 56 | 57 | /** 58 | * Unregisters an event listener that allowed listening for window messages. 59 | * @static 60 | * @param {Function} callback A callback to invoke after unregistration. 61 | */ 62 | static unregister(callback?: Function): void { 63 | window.removeEventListener('message', (e: MessageEvent) => { 64 | if (callback) { 65 | callback(e); 66 | } 67 | }); 68 | } 69 | 70 | /** 71 | * Sends a message from main app window to Remixer iFrame window. 72 | * @param {MessagingType} message The message type to send. 73 | */ 74 | static postToFrame(message: MessagingType): void { 75 | const frame = document.getElementById(CSS.RMX_OVERLAY_FRAME) as HTMLFrameElement; 76 | frame.contentWindow.postMessage(message, '*'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/lib/Remote.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as firebase from 'firebase'; 18 | import * as uuid from 'uuid'; 19 | 20 | import { throttle } from 'lodash'; 21 | import { remixer } from '../core/Remixer'; 22 | import { Variable } from '../core/variables/Variable'; 23 | import { StorageKey } from './Constants'; 24 | import { LocalStorage } from './LocalStorage'; 25 | import { Messaging } from './Messaging'; 26 | 27 | // The number of milliseconds to throttle invocations to. 28 | const THROTTLE_WAIT = 300; 29 | 30 | /** 31 | * The Remote class is a singleton class that provides the ability to store 32 | * and retrieve Variables on a remote controller. It also provides listeners 33 | * for Variable updates done remotely. 34 | * @class 35 | */ 36 | export class Remote { 37 | 38 | /** 39 | * Initializes a new instance of Remote. 40 | * @private 41 | * @static 42 | * @return {Remote} A new instance of Remote. 43 | */ 44 | private static _sharedInstance = new Remote(); 45 | 46 | /** 47 | * Provides ability for Remixer HTML iFrame to access this instance of Remote. 48 | * @return {Remote} The attached instance of Remote. 49 | */ 50 | static get attachedInstance(): Remote { 51 | return this._sharedInstance; 52 | } 53 | 54 | /** 55 | * Throttles network save calls. 56 | * @private 57 | * @static 58 | * @type {any} 59 | */ 60 | private _throttledSaveVariable: any; 61 | 62 | private _enabled: boolean; 63 | 64 | /** 65 | * Whether the remote controller is enabled. 66 | * @static 67 | * @readonly 68 | * @return {boolean} 69 | */ 70 | get isEnabled(): boolean { 71 | return this._initialized && this._enabled; 72 | } 73 | 74 | private _remoteId: string; 75 | 76 | /** 77 | * Returns the remote controller ID. 78 | * @readonly 79 | * @return {string} The remote controller ID. 80 | */ 81 | get remoteId(): string { 82 | return this._remoteId; 83 | } 84 | 85 | /** 86 | * Returns the remote controller URL. 87 | * 88 | * Example URL: 89 | * "https://.firebaseapp.com/" 90 | * @readonly 91 | * @return {string} The remote controller URL. 92 | */ 93 | get remoteUrl(): string { 94 | const authDomain = firebase.app().options['authDomain']; 95 | return `https://${authDomain}/${this._remoteId}`; 96 | } 97 | 98 | private _initialized: boolean = false; 99 | 100 | /** 101 | * Returns whether the remote has been initialized with Firebase credentials. 102 | * @readonly 103 | * @return {boolean} Returns true if has Firebase credentials. 104 | */ 105 | get initialized(): boolean { 106 | return this._initialized; 107 | } 108 | 109 | /** 110 | * Initializes the remote controller. 111 | * 112 | * A call to this method will allow you to share your Variables to the 113 | * remote controller being hosted as per your firebase configuration. 114 | * @static 115 | * @param {{}} config The firebase credentials. 116 | */ 117 | static initializeRemote(config: {}): void { 118 | // Get the locally stored remoteId. If doesn't exist, generate a new one 119 | // and store it. 120 | const instance = this._sharedInstance; 121 | let storedRemoteId = instance.getPreference(StorageKey.KEY_REMOTE_ID); 122 | if (!storedRemoteId) { 123 | storedRemoteId = instance.generateRemoteId(); 124 | instance.savePreference(StorageKey.KEY_REMOTE_ID, storedRemoteId); 125 | } 126 | instance._remoteId = storedRemoteId; 127 | 128 | // Check if enabled. 129 | let remoteEnabled = instance.getPreference(StorageKey.KEY_REMOTE_ENABLED); 130 | if (!remoteEnabled) { 131 | remoteEnabled = false; 132 | instance.savePreference(StorageKey.KEY_REMOTE_ENABLED, remoteEnabled); 133 | } 134 | instance._enabled = remoteEnabled; 135 | 136 | if (remoteEnabled) { 137 | instance.startSharing(); 138 | } 139 | 140 | firebase.initializeApp(config); 141 | instance._initialized = Object.keys(config).length !== 0; 142 | } 143 | 144 | /** 145 | * Generates a unique id consisting of 8 chars. 146 | * @private 147 | * @return {string} Returns the new remote id. 148 | */ 149 | private generateRemoteId(): string { 150 | return uuid().substring(0, 8); 151 | } 152 | 153 | /** 154 | * Returns a database reference to the remixer instance. 155 | * @private 156 | * @return {firebase.database.Reference} The firebase database reference. 157 | */ 158 | private dbReference(): firebase.database.Reference { 159 | return firebase.database().ref(`${StorageKey.KEY_REMIXER}/${this._remoteId}`); 160 | } 161 | 162 | /** 163 | * Start sharing the variable updates to the remote controller. 164 | */ 165 | startSharing(): void { 166 | this._throttledSaveVariable = throttle(this._save, THROTTLE_WAIT); 167 | this._enabled = true; 168 | this.savePreference(StorageKey.KEY_REMOTE_ENABLED, true); 169 | 170 | // Save each variable without throttling. 171 | for (const variable of remixer.attachedInstance.variablesArray) { 172 | Remote.saveVariable(variable, false); 173 | } 174 | } 175 | 176 | /** 177 | * Stops sharing the variable updates to the remote controller. 178 | */ 179 | stopSharing(): void { 180 | this._throttledSaveVariable.cancel(); 181 | this.savePreference(StorageKey.KEY_REMOTE_ENABLED, false); 182 | this.stopObservingUpdates(); 183 | Remote.removeAllVariables(); 184 | this._enabled = false; 185 | } 186 | 187 | /** 188 | * Saves a variable remotely. 189 | * 190 | * A control's UI allows very fast updating of the selected value. For 191 | * example the quick dragging of a slider, or keyboard input of a textbox. 192 | * These selected value updates should be throttled since we only care 193 | * about the final selected value and not intermittent changes. 194 | * 195 | * However adding a new Variable with params should not be throttled in 196 | * order to capture many Variables be adding in quick succession. 197 | * 198 | * Defaults to throttle for saves to prevent network jank. 199 | * @static 200 | * @param {Variable} variable The variable to save. 201 | * @param {boolean = true} Whether to throttle the saves. 202 | */ 203 | static saveVariable(variable: Variable, throttle: boolean = true): void { 204 | if (this._sharedInstance.isEnabled) { 205 | if (throttle) { 206 | this._sharedInstance._throttledSaveVariable(variable); 207 | } else { 208 | this._sharedInstance._save(variable); 209 | } 210 | } 211 | } 212 | 213 | /** 214 | * Removes all variables remotely. 215 | * @static 216 | */ 217 | static removeAllVariables(): void { 218 | if (this._sharedInstance.isEnabled) { 219 | this._sharedInstance.dbReference().remove(); 220 | } 221 | } 222 | 223 | /** 224 | * Performs the saving of the variable. 225 | * 226 | * When saving to remote, we first stop observing updates then restart after 227 | * the update. This prevents a cyclical error of network and local changes. 228 | * @private 229 | * @param {Variable} variable The variable to save. 230 | */ 231 | private _save(variable: Variable): void { 232 | if (this.isEnabled) { 233 | this.stopObservingUpdates(variable.key); 234 | this.dbReference().child(variable.key).set(variable.serialize()); 235 | this.startObservingUpdates(variable.key); 236 | } 237 | } 238 | 239 | /** 240 | * Starts a listener for any changes received for a variable. 241 | * @private 242 | * @param {string} variableKey The variable key. 243 | */ 244 | private startObservingUpdates(variableKey: string): void { 245 | const reference = this.dbReference().child(variableKey); 246 | reference.on('child_changed', (data) => { 247 | const variable = remixer.getVariable(data.ref.parent.key); 248 | remixer.cloneAndUpdateVariable(variable, data.val()); 249 | }); 250 | } 251 | 252 | /** 253 | * Stops all change listeners for a variable. 254 | * @private 255 | * @param {string} variableKey The optional variable key. 256 | */ 257 | private stopObservingUpdates(variableKey?: string): void { 258 | if (variableKey) { 259 | this.dbReference().child(variableKey).off(); 260 | } else { 261 | this.dbReference().off(); 262 | } 263 | } 264 | 265 | /** 266 | * Retrieves a preference from local storage. 267 | * @private 268 | * @param {string} key The key of the preference to retrieve. 269 | * @return {any} Returns the preference object. 270 | */ 271 | private getPreference(key: string): any { 272 | return LocalStorage.getPreference(key); 273 | } 274 | 275 | /** 276 | * Saves a preference to local storage. 277 | * @private 278 | * @param {string} key The preference key. 279 | * @param {any} value The preference value. 280 | */ 281 | private savePreference(key: string, value: any): void { 282 | LocalStorage.savePreference(key, value); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/lib/__tests__/ColorUtils_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | 19 | import { ColorUtils } from '../ColorUtils'; 20 | 21 | const expect = chai.expect; 22 | 23 | describe('ColorUtils', () => { 24 | 25 | const redRgbString: string = 'rgb(255, 0, 0)'; 26 | const alphaRgbaString: string = 'rgba(120, 13, 99, 0.6)'; 27 | 28 | it('should create proper rgba string from color strings', () => { 29 | expect(ColorUtils.toRgbaString('red')).to.equal(redRgbString); 30 | expect(ColorUtils.toRgbaString('FF0000')).to.equal(redRgbString); 31 | expect(ColorUtils.toRgbaString(alphaRgbaString)).to.equal(alphaRgbaString); 32 | }); 33 | 34 | it('should create proper rgba string from RgbaColor object', () => { 35 | const redRgbaColor = ColorUtils.toRgba('red'); 36 | expect(ColorUtils.toRgbaString(redRgbaColor)).to.equal(redRgbString); 37 | 38 | const alpgaRgbaColor = ColorUtils.toRgba(alphaRgbaString); 39 | expect(ColorUtils.toRgbaString(alpgaRgbaColor)).to.equal(alphaRgbaString); 40 | 41 | const blueRgbaColor = ColorUtils.toRgba('blue'); 42 | expect(ColorUtils.toRgbaString(blueRgbaColor)).to.not.equal(redRgbString); 43 | }); 44 | 45 | it('should properly compare color equality', () => { 46 | expect(ColorUtils.areEqual(redRgbString, 'red')).to.be.true; 47 | expect(ColorUtils.areEqual(redRgbString, 'blue')).to.not.be.true; 48 | }); 49 | 50 | it('should properly choose most readable color', () => { 51 | const readableColor = ColorUtils.mostReadable('black', ['white', 'black']); 52 | expect(readableColor).to.equal(ColorUtils.toRgbaString('white')); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/lib/__tests__/LocalStorage_test.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as sinon from 'sinon'; 19 | import * as sinonChai from 'sinon-chai'; 20 | 21 | import { remixer } from '../../core/Remixer'; 22 | import { ConstraintType, ControlType, DataType } from '../Constants'; 23 | import { LocalStorage } from '../LocalStorage'; 24 | 25 | const expect = chai.expect; 26 | chai.use(sinonChai); 27 | 28 | describe('LocalStorage', () => { 29 | 30 | function addVariables() { 31 | remixer.addBooleanVariable('test_key1', true); 32 | remixer.addStringVariable('test_key2', 'testString'); 33 | remixer.addNumberVariable('test_key3', 40); 34 | } 35 | 36 | it('should call saveVariable method', () => { 37 | const callbackSpy = sinon.spy(LocalStorage, 'saveVariable'); 38 | localStorage.clear(); 39 | addVariables(); 40 | 41 | expect(callbackSpy).to.have.been.calledThrice; 42 | }); 43 | 44 | it('should retrieve correct variable from storage', () => { 45 | addVariables(); 46 | const stringVariable = LocalStorage.getVariable('test_key2'); 47 | 48 | expect(stringVariable.dataType).to.equal(DataType.STRING); 49 | expect(stringVariable.constraintType).to.equal(ConstraintType.NONE); 50 | expect(stringVariable.controlType).to.equal(ControlType.TEXT_INPUT); 51 | expect(stringVariable.selectedValue).to.equal('testString'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/ui/ListItem.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { CSS } from '../lib/Constants'; 20 | 21 | /** 22 | * Interface for the properties assigned to the ListItem component. 23 | * @interface 24 | */ 25 | interface IListItemProps { 26 | title: string; 27 | subtitle?: string; 28 | inlineControl: boolean; 29 | controlClass?: string; 30 | children?: any; 31 | } 32 | 33 | /** Returns a stateless page layout template. */ 34 | export function ListItem(props: IListItemProps) { 35 | const inline = props.inlineControl ? 'inline' : ''; 36 | return ( 37 |
38 |
39 |
{props.title}
40 |
{props.subtitle}
41 |
42 |
{props.children}
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/ui/OverlayController.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { Variable } from '../core/variables/Variable'; 20 | import { CSS, KeyCode, KeyEvent } from '../lib/Constants'; 21 | import { Messaging } from '../lib/Messaging'; 22 | import { Remote } from '../lib/Remote'; 23 | import { OverlayShareMenu } from './OverlayShareMenu'; 24 | import { OverlayVariables } from './OverlayVariables'; 25 | 26 | /** 27 | * Interface for the properties assigned to the OverlayController component. 28 | * @interface 29 | */ 30 | interface IControllerProps { 31 | remote?: Remote; 32 | updateVariable(variable: Variable, selectedValue: any): void; 33 | toggleRemoteEnabled(): void; 34 | variables: Variable[]; 35 | wrapperElement: HTMLElement; 36 | } 37 | 38 | /** 39 | * Interface for the state assigned to the OverlayController component. 40 | * @interface 41 | */ 42 | interface IControllerState { 43 | shareMenuIsVisible: boolean; 44 | } 45 | 46 | /** 47 | * Renders an MDL card-styled overlay containing a child control for each 48 | * variable. 49 | * @class 50 | * @extends React.Component 51 | */ 52 | export class OverlayController extends React.Component { 53 | 54 | constructor(props: IControllerProps) { 55 | super(props); 56 | this.state = { shareMenuIsVisible: false }; 57 | } 58 | 59 | /** @override */ 60 | componentDidMount() { 61 | // Register for messaging and key events. 62 | Messaging.register(this.onMessageReceived.bind(this)); 63 | this.addKeyListener(); 64 | } 65 | 66 | /** @override */ 67 | componentWillUnmount() { 68 | // Unregister for messaging and key events. 69 | Messaging.unregister(); 70 | this.removeKeyListener(); 71 | } 72 | 73 | /** 74 | * Handles receiving of window message. 75 | * @param {MessageEvent} event The received message event. 76 | */ 77 | onMessageReceived(event: MessageEvent): void { 78 | if (event.data === Messaging.type.ToggleVisibility) { 79 | this.toggleVisibility(); 80 | } 81 | } 82 | 83 | /** Adds a key listener. */ 84 | addKeyListener(): void { 85 | document.addEventListener(KeyEvent.DOWN, (e: KeyboardEvent) => { 86 | if (e.keyCode === KeyCode.ESC) { 87 | this.toggleVisibility(); 88 | } 89 | }); 90 | } 91 | 92 | /** Removes a key listener. */ 93 | removeKeyListener(): void { 94 | document.removeEventListener(KeyEvent.DOWN); 95 | } 96 | 97 | /** Toggles the Remixer overlay visibility. */ 98 | toggleVisibility() { 99 | this.props.wrapperElement.classList.toggle(CSS.RMX_VISIBLE); 100 | } 101 | 102 | /** Toggles the share menu visibility. */ 103 | toggleShareMenu = () => { 104 | this.setState({ 105 | shareMenuIsVisible: !this.state.shareMenuIsVisible, 106 | }); 107 | } 108 | 109 | /** @override */ 110 | render() { 111 | const { 112 | remote, 113 | toggleRemoteEnabled, 114 | updateVariable, 115 | variables, 116 | } = this.props; 117 | const { shareMenuIsVisible } = this.state; 118 | const remoteInitialized = remote ? remote.initialized : false; 119 | 120 | const shareIcon: string = shareMenuIsVisible ? 'up' : 'down'; 121 | return ( 122 |
123 |
124 |

Remixer

125 |
126 | {remoteInitialized ? ( 127 | ) 134 | : '' 135 | } 136 |
137 | 141 |
142 | {remoteInitialized ? ( 143 |
144 | {remote.isEnabled ? 145 | SHARED 146 | : '' 147 | } 148 | 154 |
) 155 | : '' 156 | } 157 |
158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/ui/OverlayShareMenu.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { CSS } from '../lib/Constants'; 20 | import { ListItem } from './ListItem'; 21 | 22 | /** 23 | * Interface for the overlay share menu properties. 24 | * @interface 25 | */ 26 | export interface IOverlayShareMenuProps { 27 | visible: boolean; 28 | remoteId: string; 29 | remoteUrl: string; 30 | isEnabled: boolean; 31 | toggleRemoteEnabled(): void; 32 | } 33 | 34 | /** 35 | * Renders a collapsable remote share menu. 36 | * 37 | * @class 38 | * @extends React.Component 39 | */ 40 | export class OverlayShareMenu extends React.Component { 41 | 42 | /** @override */ 43 | render() { 44 | const { 45 | visible, 46 | remoteId, 47 | remoteUrl, 48 | isEnabled, 49 | } = this.props; 50 | 51 | const showMenu = visible ? 'active' : ''; 52 | const status = isEnabled ? 'on' : 'off'; 53 | 54 | return ( 55 |
56 | 62 | 74 | 75 | 80 |
81 | 82 | 87 |
88 |
89 |
90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/ui/OverlayVariables.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { StringVariable } from '../core/variables/StringVariable'; 20 | import { Variable } from '../core/variables/Variable'; 21 | import { ControlType, CSS } from '../lib/Constants'; 22 | import { ColorSwatchControl } from './controls/ColorSwatchControl'; 23 | import { IControlUpdateProps } from './controls/controlProps'; 24 | import { DropdownControl } from './controls/DropdownControl'; 25 | import { RadioListControl } from './controls/RadioListControl'; 26 | import { SliderControl } from './controls/SliderControl'; 27 | import { SwitchControl } from './controls/SwitchControl'; 28 | import { TextFieldControl } from './controls/TextFieldControl'; 29 | 30 | /** 31 | * Interface for a React class that requires an array of Variables. 32 | * @interface 33 | * @extends IControlUpdateProps 34 | */ 35 | export interface IOverlayVariableProps extends IControlUpdateProps { 36 | variables: Variable[]; 37 | } 38 | 39 | /** 40 | * Renders a list of remixer controls for each variable. 41 | * 42 | * @class 43 | * @extends React.Component 44 | */ 45 | export class OverlayVariables extends React.Component { 46 | 47 | /** @override */ 48 | render() { 49 | return ( 50 |
51 | {this.props.variables.map((variable) => { 52 | const Control = this.controlForVariable(variable); 53 | if (Control) { 54 | return ( 55 | 60 | ); 61 | } 62 | })} 63 |
64 | ); 65 | } 66 | 67 | /** 68 | * Returns a control as determined by the variable `dataType` property. 69 | * @private 70 | * @param {Variable} variable The variable to provide the control for. 71 | * @return {any} A control component. 72 | */ 73 | private controlForVariable(variable: Variable): any { 74 | // TODO(cjcox): Provide support for controls: 75 | // BUTTON, COLOR_INPUT, STEPPER 76 | switch (variable.controlType) { 77 | case ControlType.COLOR_LIST: 78 | return ColorSwatchControl; 79 | case ControlType.SEGMENTED: 80 | return RadioListControl; 81 | case ControlType.SLIDER: 82 | return SliderControl; 83 | case ControlType.SWITCH: 84 | return SwitchControl; 85 | case ControlType.TEXT_INPUT: 86 | return TextFieldControl; 87 | case ControlType.TEXT_LIST: 88 | return DropdownControl; 89 | default: 90 | return null; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ui/__tests__/ColorSwatchControl_test.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as React from 'react'; 19 | import * as TestUtils from 'react-addons-test-utils'; 20 | 21 | import { remixer } from '../../core/Remixer'; 22 | import { Variable } from '../../core/variables/Variable'; 23 | import { ColorUtils } from '../../lib/ColorUtils'; 24 | import { CSS } from '../../lib/Constants'; 25 | import { ColorSwatchControl } from '../controls/ColorSwatchControl'; 26 | 27 | const expect = chai.expect; 28 | 29 | describe('ColorSwatchControl', () => { 30 | const key: string = 'test_variable'; 31 | const initialValue: string = '#4285F4'; 32 | const limitedToValues: string[] = ['#4285F4', '#0F9D58', '#DB4437']; 33 | let variable: Variable; 34 | 35 | beforeEach(() => { 36 | variable = remixer.addColorVariable(key, initialValue, limitedToValues); 37 | this.component = TestUtils.renderIntoDocument( 38 | , 42 | ); 43 | }); 44 | 45 | it('should render with proper class name', () => { 46 | const control = TestUtils.findRenderedDOMComponentWithClass( 47 | this.component, CSS.RMX_COLOR_SWATCH, 48 | ); 49 | 50 | expect(TestUtils.isDOMComponent(control)).to.be.true; 51 | }); 52 | 53 | it('have correct number of children with proper data values', () => { 54 | const list = TestUtils.findRenderedDOMComponentWithClass( 55 | this.component, 'control', 56 | ); 57 | 58 | expect(list.children.length).to.equal(3); 59 | 60 | for (let i = 0; i < list.children.length; i++) { 61 | const element = list.children[i] as HTMLElement; 62 | expect(element.dataset.value).to.equal(ColorUtils.toRgbaString(limitedToValues[i])); 63 | } 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/ui/__tests__/DropdownControl_test.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as React from 'react'; 19 | import * as TestUtils from 'react-addons-test-utils'; 20 | 21 | import { remixer } from '../../core/Remixer'; 22 | import { Variable } from '../../core/variables/Variable'; 23 | import { CSS } from '../../lib/Constants'; 24 | import { DropdownControl } from '../controls/DropdownControl'; 25 | 26 | const expect = chai.expect; 27 | 28 | describe('DropdownControl', () => { 29 | const key: string = 'test_variable'; 30 | const initialValue: string = 'a'; 31 | const limitedToValues: string[] = ['a', 'b', 'c']; 32 | let variable: Variable; 33 | 34 | beforeEach(() => { 35 | variable = remixer.addStringVariable(key, initialValue, limitedToValues); 36 | this.component = TestUtils.renderIntoDocument( 37 | , 41 | ); 42 | }); 43 | 44 | it('should render with proper class name', () => { 45 | const control = TestUtils.findRenderedDOMComponentWithClass( 46 | this.component, CSS.RMX_DROPDOWN, 47 | ); 48 | 49 | expect(TestUtils.isDOMComponent(control)).to.be.true; 50 | }); 51 | 52 | it('have correct number of children with proper data values', () => { 53 | const list = TestUtils.findRenderedDOMComponentWithTag( 54 | this.component, 'ul', 55 | ); 56 | 57 | expect(list.children.length).to.equal(3); 58 | 59 | for (let i = 0; i < list.children.length; i++) { 60 | const element = list.children[i] as HTMLElement; 61 | expect(element.dataset.value).to.equal(limitedToValues[i]); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/ui/__tests__/RadioListControl_test.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as React from 'react'; 19 | import * as TestUtils from 'react-addons-test-utils'; 20 | 21 | import { remixer } from '../../core/Remixer'; 22 | import { Variable } from '../../core/variables/Variable'; 23 | import { CSS } from '../../lib/Constants'; 24 | import { RadioListControl } from '../controls/RadioListControl'; 25 | 26 | const expect = chai.expect; 27 | 28 | describe('RadioListControl', () => { 29 | const key: string = 'test_variable'; 30 | const initialValue: string = 'a'; 31 | const limitedToValues: string[] = ['a', 'b']; 32 | let variable: Variable; 33 | 34 | beforeEach(() => { 35 | variable = remixer.addStringVariable(key, initialValue, limitedToValues); 36 | this.component = TestUtils.renderIntoDocument( 37 | , 41 | ); 42 | }); 43 | 44 | it('should render with proper class name', () => { 45 | const control = TestUtils.findRenderedDOMComponentWithClass( 46 | this.component, CSS.RMX_RADIO_LIST, 47 | ); 48 | 49 | expect(TestUtils.isDOMComponent(control)).to.be.true; 50 | }); 51 | 52 | it('have correct number of children with proper data values', () => { 53 | const list = TestUtils.findRenderedDOMComponentWithClass( 54 | this.component, 'control', 55 | ); 56 | 57 | expect(list.children.length).to.equal(2); 58 | 59 | const elements = TestUtils.scryRenderedDOMComponentsWithClass( 60 | this.component, 'mdl-radio__button', 61 | ) as HTMLInputElement[]; 62 | 63 | for (let i = 0; i < elements.length; i++) { 64 | expect(elements[i].value).to.equal(limitedToValues[i]); 65 | } 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/ui/__tests__/SliderControl_test.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as React from 'react'; 19 | import * as TestUtils from 'react-addons-test-utils'; 20 | 21 | import { remixer } from '../../core/Remixer'; 22 | import { RangeVariable } from '../../core/variables/RangeVariable'; 23 | import { CSS } from '../../lib/Constants'; 24 | import { SliderControl } from '../controls/SliderControl'; 25 | 26 | const expect = chai.expect; 27 | 28 | describe('SliderControl', () => { 29 | const key: string = 'test_variable'; 30 | const initialValue: number = 0.5; 31 | const minValue: number = 0; 32 | const maxValue: number = 1; 33 | const increment: number = 0.1; 34 | let variable: RangeVariable; 35 | 36 | beforeEach(() => { 37 | variable = remixer.addRangeVariable(key, initialValue, minValue, maxValue, increment); 38 | this.component = TestUtils.renderIntoDocument( 39 | , 43 | ); 44 | }); 45 | 46 | it('should render with proper class name', () => { 47 | const control = TestUtils.findRenderedDOMComponentWithClass( 48 | this.component, CSS.RMX_SLIDER, 49 | ); 50 | 51 | expect(TestUtils.isDOMComponent(control)).to.be.true; 52 | }); 53 | 54 | it('have correct min label', () => { 55 | const label = TestUtils.findRenderedDOMComponentWithClass( 56 | this.component, CSS.RMX_SLIDER_MIN, 57 | ); 58 | 59 | expect(Number(label.textContent)).to.equal(minValue); 60 | }); 61 | 62 | it('have correct max label', () => { 63 | const label = TestUtils.findRenderedDOMComponentWithClass( 64 | this.component, CSS.RMX_SLIDER_MAX, 65 | ); 66 | 67 | expect(Number(label.textContent)).to.equal(maxValue); 68 | }); 69 | 70 | it('have correct slider input value and attributes', () => { 71 | const slider = TestUtils.findRenderedDOMComponentWithClass( 72 | this.component, 'mdl-slider', 73 | ) as HTMLInputElement; 74 | 75 | expect(Number(slider.min)).to.equal(minValue); 76 | expect(Number(slider.max)).to.equal(maxValue); 77 | expect(Number(slider.step)).to.equal(increment); 78 | expect(Number(slider.value)).to.equal(initialValue); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /src/ui/__tests__/SwitchControl_test.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as React from 'react'; 19 | import * as TestUtils from 'react-addons-test-utils'; 20 | 21 | import { remixer } from '../../core/Remixer'; 22 | import { Variable } from '../../core/variables/Variable'; 23 | import { CSS } from '../../lib/Constants'; 24 | import { SwitchControl } from '../controls/SwitchControl'; 25 | 26 | const expect = chai.expect; 27 | 28 | describe('SwitchControl', () => { 29 | const key: string = 'test_variable'; 30 | const initialValue: boolean = true; 31 | let variable: Variable; 32 | 33 | beforeEach(() => { 34 | variable = remixer.addBooleanVariable(key, initialValue); 35 | this.component = TestUtils.renderIntoDocument( 36 | , 40 | ); 41 | }); 42 | 43 | it('should render with proper class name', () => { 44 | const control = TestUtils.findRenderedDOMComponentWithClass( 45 | this.component, CSS.RMX_SWITCH, 46 | ); 47 | 48 | expect(TestUtils.isDOMComponent(control)).to.be.true; 49 | }); 50 | 51 | it('have correct switch checked value', () => { 52 | const control = TestUtils.findRenderedDOMComponentWithClass( 53 | this.component, 'mdl-switch__input', 54 | ) as HTMLInputElement; 55 | 56 | expect(control.checked).to.equal(initialValue); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/ui/__tests__/TextFieldControl_test.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as chai from 'chai'; 18 | import * as React from 'react'; 19 | import * as TestUtils from 'react-addons-test-utils'; 20 | 21 | import { remixer } from '../../core/Remixer'; 22 | import { Variable } from '../../core/variables/Variable'; 23 | import { CSS } from '../../lib/Constants'; 24 | import { TextFieldControl } from '../controls/TextFieldControl'; 25 | 26 | const expect = chai.expect; 27 | 28 | describe('TextFieldControl', () => { 29 | const key: string = 'test_variable'; 30 | const initialValue: string = 'test string value'; 31 | let variable: Variable; 32 | 33 | beforeEach(() => { 34 | variable = remixer.addStringVariable(key, initialValue); 35 | variable.selectedValue = initialValue; 36 | this.component = TestUtils.renderIntoDocument( 37 | , 41 | ); 42 | }); 43 | 44 | it('should render with proper class name', () => { 45 | const control = TestUtils.findRenderedDOMComponentWithClass( 46 | this.component, CSS.RMX_TEXTFIELD, 47 | ); 48 | 49 | expect(TestUtils.isDOMComponent(control)).to.be.true; 50 | }); 51 | 52 | it('have correct innertext checked value', () => { 53 | const textField = TestUtils.findRenderedDOMComponentWithClass( 54 | this.component, 'mdl-textfield__input', 55 | ) as HTMLInputElement; 56 | 57 | expect(textField.value).to.equal(initialValue); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/ui/controls/ColorSwatchControl.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { ColorUtils } from '../../lib/ColorUtils'; 20 | import { CSS } from '../../lib/Constants'; 21 | import { ListItem } from '../ListItem'; 22 | import { IColorControlProps } from './controlProps'; 23 | 24 | /** 25 | * A color swatch picker control consisting of a single color swatch for each 26 | * allowed value. 27 | * @class 28 | * @extends React.Component 29 | */ 30 | export class ColorSwatchControl extends React.Component { 31 | 32 | /** Handles the update event for this control. */ 33 | onClick = (event: React.FormEvent): void => { 34 | const value = (event.target as HTMLElement).dataset.value; 35 | if (value) { 36 | this.props.updateVariable(this.props.variable, value); 37 | } 38 | } 39 | 40 | /** @override */ 41 | shouldComponentUpdate(nextProps: IColorControlProps) { 42 | return nextProps.variable !== this.props.variable; 43 | } 44 | 45 | /** @override */ 46 | render() { 47 | const { 48 | title, 49 | limitedToValues, 50 | selectedValue, 51 | } = this.props.variable; 52 | return ( 53 | 59 | {limitedToValues.map((value: string) => ( 60 | 66 | ))} 67 | 68 | ); 69 | } 70 | } 71 | 72 | /** 73 | * Interface containing properties for a single color swatch. 74 | * @interface 75 | */ 76 | interface IColorSwatchProps { 77 | color: string; 78 | isSelected: boolean; 79 | onClick: any; 80 | } 81 | 82 | /** 83 | * Returns a single color swatch displayed within the `ColorSwatchControl`. 84 | * @param {IColorSwatchProps} props The color swatch properties. 85 | */ 86 | function ColorSwatch(props: IColorSwatchProps) { 87 | const { 88 | color, 89 | isSelected, 90 | onClick, 91 | } = props; 92 | 93 | // Determine a readable color to prevent a white checkmark on a light 94 | // color swatch. 95 | const checkColor = ColorUtils.mostReadable(color, ['white', 'gray']); 96 | return ( 97 |
103 | {isSelected ? check : ''} 104 |
105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /src/ui/controls/DropdownControl.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { CSS } from '../../lib/Constants'; 20 | import { ListItem } from '../ListItem'; 21 | import { IStringControlProps } from './controlProps'; 22 | 23 | /** 24 | * A dropdown control. 25 | * @class 26 | * @extends React.Component 27 | */ 28 | export class DropdownControl extends React.Component { 29 | 30 | /** Handles the update event for this control. */ 31 | onClick = (event: React.FormEvent): void => { 32 | this.props.updateVariable( 33 | this.props.variable, 34 | (event.target as HTMLElement).dataset.value, 35 | ); 36 | } 37 | 38 | /** @override */ 39 | shouldComponentUpdate(nextProps: IStringControlProps) { 40 | return nextProps.variable !== this.props.variable; 41 | } 42 | 43 | /** @override */ 44 | render() { 45 | const { 46 | title, 47 | key, 48 | limitedToValues, 49 | selectedValue, 50 | } = this.props.variable; 51 | const id = `${CSS.RMX_DROPDOWN}-${key}`; 52 | 53 | return ( 54 | 59 |
60 | 65 |
    69 | {limitedToValues.map((value: string) => ( 70 |
  • 76 | {value} 77 |
  • 78 | ))} 79 |
80 |
81 |
82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/ui/controls/RadioListControl.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { CSS } from '../../lib/Constants'; 20 | import { ListItem } from '../ListItem'; 21 | import { IStringControlProps } from './controlProps'; 22 | 23 | /** 24 | * A radio list control. 25 | * @class 26 | * @extends React.Component 27 | */ 28 | export class RadioListControl extends React.Component { 29 | 30 | /** Array of MDL components. */ 31 | radioItems: HTMLLabelElement[] = []; 32 | 33 | /** Handles the update event for this control. */ 34 | onChange = (event: React.FormEvent): void => { 35 | this.props.updateVariable( 36 | this.props.variable, 37 | (event.target as HTMLInputElement).value, 38 | ); 39 | } 40 | 41 | /** @override */ 42 | shouldComponentUpdate(nextProps: IStringControlProps) { 43 | return nextProps.variable !== this.props.variable; 44 | } 45 | 46 | componentDidUpdate() { 47 | const { limitedToValues, selectedValue } = this.props.variable; 48 | const index = limitedToValues.indexOf(selectedValue); 49 | const materialRadio = this.radioItems[index]['MaterialRadio']; 50 | materialRadio.check(); 51 | } 52 | 53 | /** @override */ 54 | render() { 55 | const { 56 | title, 57 | key, 58 | limitedToValues, 59 | selectedValue, 60 | } = this.props.variable; 61 | const id = `${CSS.RMX_RADIO_LIST_ITEM}-${key}`; 62 | 63 | return ( 64 | 69 | {limitedToValues.map((value: string, i: number) => ( 70 | 86 | ))} 87 | 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ui/controls/SliderControl.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { CSS } from '../../lib/Constants'; 20 | import { ListItem } from '../ListItem'; 21 | import { IRangeControlProps } from './controlProps'; 22 | 23 | /** 24 | * A slider control. 25 | * @class 26 | * @extends React.Component 27 | */ 28 | export class SliderControl extends React.Component { 29 | 30 | /** Handles the update event for this control. */ 31 | onChange = (event: React.FormEvent): void => { 32 | this.props.updateVariable( 33 | this.props.variable, 34 | parseFloat((event.target as HTMLInputElement).value), 35 | ); 36 | } 37 | 38 | /** @override */ 39 | shouldComponentUpdate(nextProps: IRangeControlProps) { 40 | return nextProps.variable !== this.props.variable; 41 | } 42 | 43 | /** @override */ 44 | render() { 45 | const { 46 | title, 47 | key, 48 | selectedValue, 49 | minValue, 50 | maxValue, 51 | increment, 52 | } = this.props.variable; 53 | const id = `${CSS.RMX_SLIDER}-${key}`; 54 | 55 | return ( 56 | 62 |
63 | {minValue} 64 | 74 | {maxValue} 75 |
76 |
77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ui/controls/SwitchControl.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { CSS } from '../../lib/Constants'; 20 | import { ListItem } from '../ListItem'; 21 | import { IBooleanControlProps } from './controlProps'; 22 | 23 | /** 24 | * A switch control. 25 | * @class 26 | * @extends React.Component 27 | */ 28 | export class SwitchControl extends React.Component { 29 | 30 | /** The MDL switch component. */ 31 | switchControl: HTMLLabelElement; 32 | 33 | /** Handles the update event for this control. */ 34 | onChange = (event: React.FormEvent): void => { 35 | const { selectedValue } = this.props.variable; 36 | this.props.updateVariable(this.props.variable, !selectedValue); 37 | } 38 | 39 | /** @override */ 40 | shouldComponentUpdate(nextProps: IBooleanControlProps) { 41 | return nextProps.variable !== this.props.variable; 42 | } 43 | 44 | componentDidUpdate() { 45 | const materialSwitch = this.switchControl['MaterialSwitch']; 46 | this.props.variable.selectedValue ? materialSwitch.on() : materialSwitch.off(); 47 | } 48 | 49 | /** @override */ 50 | render() { 51 | const { 52 | title, 53 | key, 54 | selectedValue, 55 | } = this.props.variable; 56 | const id = `${CSS.RMX_SWITCH}-${key}`; 57 | 58 | return ( 59 | 64 | 77 | 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ui/controls/TextFieldControl.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | 19 | import { CSS, DataType } from '../../lib/Constants'; 20 | import { ListItem } from '../ListItem'; 21 | import { IStringControlProps } from './controlProps'; 22 | 23 | /** 24 | * A textfield control. 25 | * @class 26 | * @extends React.Component 27 | */ 28 | export class TextFieldControl extends React.Component { 29 | 30 | /** Handles the update event for this control. */ 31 | onChange = (event: React.FormEvent): void => { 32 | this.props.updateVariable( 33 | this.props.variable, 34 | (event.target as HTMLInputElement).value, 35 | ); 36 | } 37 | 38 | /** @override */ 39 | shouldComponentUpdate(nextProps: IStringControlProps) { 40 | return nextProps.variable !== this.props.variable; 41 | } 42 | 43 | /** @override */ 44 | render() { 45 | const { 46 | title, 47 | key, 48 | dataType, 49 | selectedValue, 50 | } = this.props.variable; 51 | const id = `${CSS.RMX_TEXTFIELD}-${key}`; 52 | const isNumber: boolean = dataType === DataType.NUMBER; 53 | const pattern = isNumber ? '-?[0-9]*(\.[0-9]+)?' : '.*'; 54 | 55 | return ( 56 | 61 | 62 | 70 | 73 | Input is not a number! 74 | 75 | 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ui/controls/controlProps.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import { BooleanVariable } from '../../core/variables/BooleanVariable'; 18 | import { ColorVariable } from '../../core/variables/ColorVariable'; 19 | import { NumberVariable } from '../../core/variables/NumberVariable'; 20 | import { RangeVariable } from '../../core/variables/RangeVariable'; 21 | import { StringVariable } from '../../core/variables/StringVariable'; 22 | import { Variable } from '../../core/variables/Variable'; 23 | 24 | /** 25 | * Interface for variable controls properties and state. 26 | * @interface 27 | */ 28 | export interface IControlUpdateProps { 29 | updateVariable(variable: Variable, selectedValue: any): void; 30 | } 31 | 32 | /** 33 | * Interface for variable control properties. 34 | * @interface 35 | */ 36 | interface IControlVariableProps extends IControlUpdateProps { 37 | variable: Variable; 38 | } 39 | 40 | /** 41 | * Interface for the properties of a control that implements a Boolean variable. 42 | * @interface 43 | * @extends ControlInterface 44 | */ 45 | export interface IBooleanControlProps extends IControlVariableProps { 46 | variable: BooleanVariable; 47 | } 48 | 49 | /** 50 | * Interface for the properties of a control that implements a Color variable. 51 | * @interface 52 | * @extends ControlInterface 53 | */ 54 | export interface IColorControlProps extends IControlVariableProps { 55 | variable: ColorVariable; 56 | } 57 | 58 | /** 59 | * Interface for the properties of a control that implements a Number variable. 60 | * @interface 61 | * @extends ControlInterface 62 | */ 63 | export interface INumberControlProps extends IControlVariableProps { 64 | variable: NumberVariable; 65 | } 66 | 67 | /** 68 | * Interface for the properties of a control that implements a Range variable. 69 | * @interface 70 | * @extends ControlInterface 71 | */ 72 | export interface IRangeControlProps extends IControlVariableProps { 73 | variable: RangeVariable; 74 | } 75 | 76 | /** 77 | * Interface for the properties of a control that implements a String variable. 78 | * @interface 79 | * @extends ControlInterface 80 | */ 81 | export interface IStringControlProps extends IControlVariableProps { 82 | variable: StringVariable; 83 | } 84 | -------------------------------------------------------------------------------- /src/ui/render.tsx: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | import * as React from 'react'; 18 | import * as ReactDOM from 'react-dom'; 19 | import './styles/overlay.less'; 20 | 21 | import { remixer } from '../core/Remixer'; 22 | import { Variable } from '../core/variables/Variable'; 23 | import { CSS } from '../lib/Constants'; 24 | import { OverlayController } from './OverlayController'; 25 | 26 | // Get remixer variables from the current instance of remixer. 27 | let variables = remixer.attachedInstance.variablesArray; 28 | 29 | // The current instance of remixer remote. 30 | const remote = remixer.attachedInstance.remote; 31 | 32 | /** 33 | * Handles all control updates by setting a new selected value for the 34 | * variable. 35 | * 36 | * To maintain immutability for React, lets first clone the variable, 37 | * update its selected value, then set it back to the variables array. 38 | * Doing so allows each control to handle its own `shouldComponentUpdate` 39 | * method to determine if it should be re-rendered. 40 | * 41 | * @param {Variable} variable The variable to update. 42 | * @param {any} selectedValue The new selected value. 43 | */ 44 | function updateVariable(variable: Variable, selectedValue: any): void { 45 | remixer.cloneAndUpdateVariable(variable, selectedValue); 46 | } 47 | 48 | /** Toggles the enabled status of remote sharing. */ 49 | function toggleRemoteEnabled(): void { 50 | if (remote.isEnabled) { 51 | remote.stopSharing(); 52 | } else { 53 | remote.startSharing(); 54 | } 55 | redraw(); 56 | } 57 | 58 | // Renders the OverlayController component to the overlay wrapper element. 59 | const overlayWrapper = document.getElementById(CSS.RMX_OVERLAY_WRAPPER); 60 | function redraw(): void { 61 | variables = remixer.attachedInstance.variablesArray; 62 | 63 | ReactDOM.render( 64 | , 71 | overlayWrapper, 72 | ); 73 | } 74 | 75 | // Add `redraw()` as a callback when selected value changes on a variable. 76 | variables.forEach((variable) => { 77 | variable.addCallback(redraw); 78 | }); 79 | 80 | redraw(); 81 | -------------------------------------------------------------------------------- /src/ui/styles/iframe.less: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | #__remixer-overlay-frame__ { 17 | border: 0; 18 | height: 100%; 19 | position: fixed; 20 | right: 0; 21 | top: 0; 22 | width: 400px; 23 | z-index: 999999; 24 | } 25 | -------------------------------------------------------------------------------- /src/ui/styles/overlay.less: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | @wrapper-width: 320px; 17 | @wrapper-padding: 10px; 18 | @colorOffWhite: #f5f5f5; 19 | @colorMdlBlue: #3f51b5; 20 | @colorSwatchSize: 40px; 21 | @colorSwatchIconSize: 24px; 22 | @duration: 200ms; 23 | @fontSize: 14px; 24 | @fontSizeSmall: 12px; 25 | @fontLightWeight: 200; 26 | @fontBoldWeight: 500; 27 | @fontFamily: 'Roboto', 'Helvetica', 'Arial', sans-serif; 28 | 29 | .mdl-card { 30 | background-color: @colorOffWhite; 31 | font-size: @fontSize; 32 | overflow: visible; 33 | width: @wrapper-width; 34 | } 35 | 36 | .mdl-card__title { 37 | background-color: #ffffff; 38 | } 39 | 40 | .mdl-card__menu a { 41 | color: @colorMdlBlue; 42 | font-weight: @fontBoldWeight; 43 | } 44 | 45 | #rmx-overlay-wrapper { 46 | float: right; 47 | opacity: 0; 48 | padding: @wrapper-padding; 49 | position: relative; 50 | transform: translateX(@wrapper-width / 2); 51 | transition: transform @duration ease-out, opacity @duration linear; 52 | width: @wrapper-width; 53 | } 54 | 55 | #rmx-overlay-wrapper.rmx-visible { 56 | opacity: 1; 57 | transform: translateX(0); 58 | } 59 | 60 | .rmx-list-item { 61 | display: flex; 62 | flex-direction: column; 63 | font-family: @fontFamily; 64 | padding: 10px 20px; 65 | } 66 | 67 | .rmx-list-item.inline { 68 | flex-direction: row; 69 | justify-content: space-between; 70 | } 71 | 72 | .rmx-list-item { 73 | .meta { 74 | display: flex; 75 | flex-direction: row; 76 | padding-bottom: 4px; 77 | } 78 | 79 | .title { 80 | font-size: @fontSize; 81 | font-weight: @fontLightWeight; 82 | padding-right: 5px; 83 | } 84 | 85 | .subtitle { 86 | color: @colorMdlBlue; 87 | font-size: @fontSize; 88 | font-weight: @fontBoldWeight; 89 | } 90 | 91 | .control { 92 | position: relative; 93 | } 94 | } 95 | 96 | .rmx-list-item.inline .meta { 97 | align-items: center; 98 | padding-bottom: 0; 99 | } 100 | 101 | .rmx-color-swatch { 102 | .rmx-color-swatch-item { 103 | border-radius: @colorSwatchSize / 2; 104 | float: left; 105 | height: @colorSwatchSize; 106 | margin-right: 10px; 107 | width: @colorSwatchSize; 108 | 109 | .material-icons { 110 | color: white; 111 | height: @colorSwatchIconSize; 112 | margin: (@colorSwatchSize - @colorSwatchIconSize) / 2; 113 | width: @colorSwatchIconSize; 114 | } 115 | } 116 | } 117 | 118 | .rmx-radio-list-item { 119 | font-size: @fontSize; 120 | font-weight: @fontBoldWeight; 121 | margin-left: 20px; 122 | width: inherit; 123 | } 124 | 125 | .rmx-dropdown button { 126 | margin-right: -20px; 127 | text-transform: inherit; 128 | } 129 | 130 | .rmx-textfield { 131 | .mdl-textfield { 132 | font-size: @fontSize; 133 | padding: 0; 134 | width: 100%; 135 | 136 | .mdl-textfield__input { 137 | margin-top: -5px; 138 | padding: 0; 139 | } 140 | 141 | .mdl-textfield__label { 142 | top: -5px; 143 | } 144 | } 145 | } 146 | 147 | .mdl-switch.is-upgraded { 148 | padding-left: 38px; 149 | } 150 | 151 | .mdl-menu__container { 152 | right: 20px !important; 153 | } 154 | 155 | .rmx-slider { 156 | .mdl-slider__container { 157 | width: 100%; 158 | } 159 | 160 | .rmx-slider-min-value { 161 | left: 0; 162 | } 163 | 164 | .rmx-slider-max-value { 165 | right: 0; 166 | } 167 | 168 | .rmx-slider-max-value, 169 | .rmx-slider-min-value { 170 | font-size: @fontSizeSmall; 171 | font-weight: @fontBoldWeight; 172 | position: absolute; 173 | top: 0; 174 | } 175 | } 176 | 177 | .rmx-share-menu { 178 | background-color: #ffffff; 179 | max-height: 0; 180 | opacity: 0; 181 | overflow: hidden; 182 | padding: 0 8px; 183 | transition: max-height @duration ease-out, opacity @duration linear; 184 | 185 | .rmx-list-item.inline .meta { 186 | align-items: flex-start; 187 | flex-direction: column; 188 | } 189 | 190 | .rmx-share-status { 191 | .title { 192 | font-weight: @fontBoldWeight; 193 | } 194 | 195 | .subtitle { 196 | color: inherit; 197 | font-weight: @fontLightWeight; 198 | } 199 | } 200 | 201 | .rmx-share-link { 202 | transition: max-height @duration ease-out, padding @duration ease-out, opacity @duration linear; 203 | 204 | &.on { 205 | max-height: 200px; 206 | } 207 | 208 | &.off { 209 | max-height: 0; 210 | opacity: 0; 211 | padding: 0 20px; 212 | } 213 | 214 | div:first-child { 215 | display: flex; 216 | flex-direction: row; 217 | justify-content: space-between; 218 | } 219 | 220 | a, 221 | i { 222 | color: @colorMdlBlue; 223 | } 224 | } 225 | } 226 | 227 | .rmx-share-menu.active { 228 | max-height: 200px; 229 | opacity: 1; 230 | } 231 | -------------------------------------------------------------------------------- /src/ui/templates/overlay_iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "sourceMap": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "isolatedModules": false, 8 | "jsx": "react", 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "declaration": false, 12 | "noImplicitAny": true, 13 | "noImplicitUseStrict": false, 14 | "removeComments": true, 15 | "noLib": false, 16 | "preserveConstEnums": true, 17 | "suppressImplicitAnyIndexErrors": true 18 | }, 19 | "include": [ 20 | "./src/**/*" 21 | ], 22 | "compileOnSave": false, 23 | "buildOnSave": false 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-react"], 3 | "rules": { 4 | "class-name": true, 5 | "comment-format": [ 6 | true, 7 | "check-space" 8 | ], 9 | "indent": [ 10 | true, 11 | "spaces" 12 | ], 13 | "interface-name": false, 14 | "jsx-no-multiline-js": false, 15 | "member-access": false, 16 | "member-ordering": false, 17 | "no-duplicate-variable": true, 18 | "no-eval": true, 19 | "no-internal-module": true, 20 | "no-string-literal": false, 21 | "no-trailing-whitespace": true, 22 | "no-unsafe-finally": true, 23 | "no-var-keyword": true, 24 | "one-line": [ 25 | true, 26 | "check-open-brace", 27 | "check-whitespace" 28 | ], 29 | "quotemark": [ 30 | true, 31 | "single" 32 | ], 33 | "semicolon": [ 34 | true, 35 | "always" 36 | ], 37 | "triple-equals": [ 38 | true, 39 | "allow-null-check" 40 | ], 41 | "typedef-whitespace": [ 42 | true, 43 | { 44 | "call-signature": "nospace", 45 | "index-signature": "nospace", 46 | "parameter": "nospace", 47 | "property-declaration": "nospace", 48 | "variable-declaration": "nospace" 49 | } 50 | ], 51 | "variable-name": [ 52 | true, 53 | "ban-keywords" 54 | ], 55 | "whitespace": [ 56 | true, 57 | "check-branch", 58 | "check-decl", 59 | "check-operator", 60 | "check-separator", 61 | "check-type" 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const path = require('path'); 20 | const webpack = require('webpack'); 21 | const PACKAGE = require('./package.json'); 22 | 23 | const PUBLIC_PATH = '/assets/'; 24 | const IS_DEV = process.env.RMX_ENV === 'development'; 25 | const IS_PROD = process.env.RMX_ENV === 'production'; 26 | 27 | module.exports = { 28 | entry: { 29 | remixer: './src/core/Remixer.ts', 30 | overlay: './src/ui/render.tsx' 31 | }, 32 | output: { 33 | path: path.resolve('./build'), 34 | publicPath: PUBLIC_PATH, 35 | filename: '[name].' + (IS_PROD ? 'min.' : '') + 'js', 36 | libraryTarget: 'umd', 37 | umdNamedDefine: true 38 | }, 39 | devtool: IS_DEV ? 'source-map' : 'cheap-module-source-map', 40 | devServer: { 41 | contentBase: "examples", 42 | inline: true, 43 | }, 44 | performance: { 45 | hints: IS_DEV ? false : "warning" 46 | }, 47 | resolve: { 48 | extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js'] 49 | }, 50 | module: { 51 | loaders: [{ 52 | test: /\.tsx?$/, 53 | loader: 'ts-loader' 54 | }, { 55 | test: /\.less$/, 56 | loader: 'style-loader!css-loader!less-loader' 57 | }, { 58 | test: /\.html$/, 59 | loader: 'html-loader' 60 | }, { 61 | test: /\.tsx?$/, 62 | exclude: /(__tests__|node_modules)\//, 63 | loader: 'istanbul-instrumenter-loader', 64 | enforce: 'post' 65 | }], 66 | }, 67 | plugins: [ 68 | new webpack.HotModuleReplacementPlugin(), 69 | new webpack.BannerPlugin(`${PACKAGE.name} 70 | @version v${PACKAGE.version} 71 | @license ${PACKAGE.license} 72 | @copyright 2016 Google, Inc. 73 | @link ${PACKAGE.repository.url} 74 | `) 75 | ], 76 | }; 77 | --------------------------------------------------------------------------------