├── .gitignore ├── .glitch-assets ├── CODEOWNERS ├── CONTRIBUTING ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js └── src ├── analytics.js ├── index.html ├── main.js ├── script.js ├── scroll-slider.js └── toggle-switch.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .public 3 | -------------------------------------------------------------------------------- /.glitch-assets: -------------------------------------------------------------------------------- 1 | {"name":"drag-in-files.svg","date":"2016-10-22T16:17:49.954Z","url":"https://cdn.hyperdev.com/drag-in-files.svg","type":"image/svg","size":7646,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/drag-in-files.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(102, 153, 205)","uuid":"adSBq97hhhpFNUna"} 2 | {"name":"click-me.svg","date":"2016-10-23T16:17:49.954Z","url":"https://cdn.hyperdev.com/click-me.svg","type":"image/svg","size":7116,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/click-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(243, 185, 186)","uuid":"adSBq97hhhpFNUnb"} 3 | {"name":"paste-me.svg","date":"2016-10-24T16:17:49.954Z","url":"https://cdn.hyperdev.com/paste-me.svg","type":"image/svg","size":7242,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/paste-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(42, 179, 185)","uuid":"adSBq97hhhpFNUnc"} 4 | {"uuid":"adSBq97hhhpFNUna","deleted":true} 5 | {"uuid":"adSBq97hhhpFNUnb","deleted":true} 6 | {"uuid":"adSBq97hhhpFNUnc","deleted":true} 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @surma 2 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement] 6 | (https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | Before you start working on a larger contribution, you should get in touch with 15 | us first through the issue tracker with your idea so that we can help out and 16 | possibly guide you. Coordinating up front makes it much easier to avoid 17 | frustration later on. 18 | 19 | ### Code reviews 20 | All submissions, including submissions by project members, require review. We 21 | use Github pull requests for this purpose. 22 | 23 | ### The small print 24 | Contributions made by corporations are covered by a different agreement than 25 | the one above, the 26 | [Software Grant and Corporate Contributor License Agreement] 27 | (https://cla.developers.google.com/about/google-corporate). 28 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | Surma 12 | -------------------------------------------------------------------------------- /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 2017 Google Inc. 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 | # [DoF Tool](https://dof-tool.surma.technology) 2 | 3 | A web app to calculate your depth of field when doing photography. 4 | 5 | Mostly a demo app for my [observables-with-streams library](https://npm.im/observables-with-streams). 6 | 7 | --- 8 | License Apache-2.0 -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dof-tool", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.5.5", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", 10 | "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", 11 | "requires": { 12 | "@babel/highlight": "^7.0.0" 13 | } 14 | }, 15 | "@babel/highlight": { 16 | "version": "7.5.0", 17 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", 18 | "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", 19 | "requires": { 20 | "chalk": "^2.0.0", 21 | "esutils": "^2.0.2", 22 | "js-tokens": "^4.0.0" 23 | } 24 | }, 25 | "@types/estree": { 26 | "version": "0.0.40", 27 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.40.tgz", 28 | "integrity": "sha512-p3KZgMto/JyxosKGmnLDJ/dG5wf+qTRMUjHJcspC2oQKa4jP7mz+tv0ND56lLBu3ojHlhzY33Ol+khLyNmilkA==" 29 | }, 30 | "@types/node": { 31 | "version": "12.12.7", 32 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.7.tgz", 33 | "integrity": "sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==" 34 | }, 35 | "@types/resolve": { 36 | "version": "0.0.8", 37 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", 38 | "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", 39 | "requires": { 40 | "@types/node": "*" 41 | } 42 | }, 43 | "acorn": { 44 | "version": "7.1.0", 45 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", 46 | "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==" 47 | }, 48 | "ansi-styles": { 49 | "version": "3.2.1", 50 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 51 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 52 | "requires": { 53 | "color-convert": "^1.9.0" 54 | } 55 | }, 56 | "buffer-from": { 57 | "version": "1.1.1", 58 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 59 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 60 | }, 61 | "builtin-modules": { 62 | "version": "3.1.0", 63 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", 64 | "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==" 65 | }, 66 | "camel-case": { 67 | "version": "3.0.0", 68 | "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", 69 | "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", 70 | "requires": { 71 | "no-case": "^2.2.0", 72 | "upper-case": "^1.1.1" 73 | } 74 | }, 75 | "chalk": { 76 | "version": "2.4.2", 77 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 78 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 79 | "requires": { 80 | "ansi-styles": "^3.2.1", 81 | "escape-string-regexp": "^1.0.5", 82 | "supports-color": "^5.3.0" 83 | }, 84 | "dependencies": { 85 | "has-flag": { 86 | "version": "3.0.0", 87 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 88 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 89 | }, 90 | "supports-color": { 91 | "version": "5.5.0", 92 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 93 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 94 | "requires": { 95 | "has-flag": "^3.0.0" 96 | } 97 | } 98 | } 99 | }, 100 | "clean-css": { 101 | "version": "4.2.1", 102 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", 103 | "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", 104 | "requires": { 105 | "source-map": "~0.6.0" 106 | }, 107 | "dependencies": { 108 | "source-map": { 109 | "version": "0.6.1", 110 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 111 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 112 | } 113 | } 114 | }, 115 | "color-convert": { 116 | "version": "1.9.3", 117 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 118 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 119 | "requires": { 120 | "color-name": "1.1.3" 121 | } 122 | }, 123 | "color-name": { 124 | "version": "1.1.3", 125 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 126 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 127 | }, 128 | "commander": { 129 | "version": "2.20.3", 130 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 131 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 132 | }, 133 | "escape-string-regexp": { 134 | "version": "1.0.5", 135 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 136 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 137 | }, 138 | "estree-walker": { 139 | "version": "0.6.1", 140 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 141 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" 142 | }, 143 | "esutils": { 144 | "version": "2.0.3", 145 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 146 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 147 | }, 148 | "he": { 149 | "version": "1.2.0", 150 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 151 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" 152 | }, 153 | "html-minifier": { 154 | "version": "4.0.0", 155 | "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", 156 | "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", 157 | "requires": { 158 | "camel-case": "^3.0.0", 159 | "clean-css": "^4.2.1", 160 | "commander": "^2.19.0", 161 | "he": "^1.2.0", 162 | "param-case": "^2.1.1", 163 | "relateurl": "^0.2.7", 164 | "uglify-js": "^3.5.1" 165 | } 166 | }, 167 | "idb-keyval": { 168 | "version": "3.2.0", 169 | "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz", 170 | "integrity": "sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==" 171 | }, 172 | "is-module": { 173 | "version": "1.0.0", 174 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 175 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" 176 | }, 177 | "jest-worker": { 178 | "version": "24.9.0", 179 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", 180 | "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", 181 | "requires": { 182 | "merge-stream": "^2.0.0", 183 | "supports-color": "^6.1.0" 184 | }, 185 | "dependencies": { 186 | "has-flag": { 187 | "version": "3.0.0", 188 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 189 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 190 | }, 191 | "supports-color": { 192 | "version": "6.1.0", 193 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", 194 | "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", 195 | "requires": { 196 | "has-flag": "^3.0.0" 197 | } 198 | } 199 | } 200 | }, 201 | "js-tokens": { 202 | "version": "4.0.0", 203 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 204 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 205 | }, 206 | "lower-case": { 207 | "version": "1.1.4", 208 | "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", 209 | "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" 210 | }, 211 | "merge-stream": { 212 | "version": "2.0.0", 213 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 214 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" 215 | }, 216 | "no-case": { 217 | "version": "2.3.2", 218 | "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", 219 | "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", 220 | "requires": { 221 | "lower-case": "^1.1.1" 222 | } 223 | }, 224 | "observables-with-streams": { 225 | "version": "0.5.0", 226 | "resolved": "https://registry.npmjs.org/observables-with-streams/-/observables-with-streams-0.5.0.tgz", 227 | "integrity": "sha512-hZnHWCyUnL4sHDYx00Bh1/Ymx25gCXuKt06PqwKHgU6i/OhkNQQgQK/lzKHxjJD1d6h4NrxdQZn4SbnEGT7I0A==" 228 | }, 229 | "param-case": { 230 | "version": "2.1.1", 231 | "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", 232 | "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", 233 | "requires": { 234 | "no-case": "^2.2.0" 235 | } 236 | }, 237 | "path-parse": { 238 | "version": "1.0.6", 239 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 240 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" 241 | }, 242 | "prettier": { 243 | "version": "1.19.1", 244 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", 245 | "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", 246 | "dev": true 247 | }, 248 | "relateurl": { 249 | "version": "0.2.7", 250 | "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", 251 | "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" 252 | }, 253 | "rollup": { 254 | "version": "1.27.8", 255 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.27.8.tgz", 256 | "integrity": "sha512-EVoEV5rAWl+5clnGznt1KY8PeVkzVQh/R0d2s3gHEkN7gfoyC4JmvIVuCtPbYE8NM5Ep/g+nAmvKXBjzaqTsHA==", 257 | "requires": { 258 | "@types/estree": "*", 259 | "@types/node": "*", 260 | "acorn": "^7.1.0" 261 | } 262 | }, 263 | "rollup-plugin-node-resolve": { 264 | "version": "5.2.0", 265 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", 266 | "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", 267 | "requires": { 268 | "@types/resolve": "0.0.8", 269 | "builtin-modules": "^3.1.0", 270 | "is-module": "^1.0.0", 271 | "resolve": "^1.11.1", 272 | "rollup-pluginutils": "^2.8.1" 273 | }, 274 | "dependencies": { 275 | "resolve": { 276 | "version": "1.12.0", 277 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", 278 | "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", 279 | "requires": { 280 | "path-parse": "^1.0.6" 281 | } 282 | } 283 | } 284 | }, 285 | "rollup-plugin-terser": { 286 | "version": "5.1.2", 287 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.1.2.tgz", 288 | "integrity": "sha512-sWKBCOS+vUkRtHtEiJPAf+WnBqk/C402fBD9AVHxSIXMqjsY7MnYWKYEUqGixtr0c8+1DjzUEPlNgOYQPVrS1g==", 289 | "requires": { 290 | "@babel/code-frame": "^7.0.0", 291 | "jest-worker": "^24.6.0", 292 | "rollup-pluginutils": "^2.8.1", 293 | "serialize-javascript": "^1.7.0", 294 | "terser": "^4.1.0" 295 | } 296 | }, 297 | "rollup-pluginutils": { 298 | "version": "2.8.2", 299 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 300 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 301 | "requires": { 302 | "estree-walker": "^0.6.1" 303 | } 304 | }, 305 | "serialize-javascript": { 306 | "version": "1.9.1", 307 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", 308 | "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==" 309 | }, 310 | "source-map-support": { 311 | "version": "0.5.16", 312 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", 313 | "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", 314 | "requires": { 315 | "buffer-from": "^1.0.0", 316 | "source-map": "^0.6.0" 317 | }, 318 | "dependencies": { 319 | "source-map": { 320 | "version": "0.6.1", 321 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 322 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 323 | } 324 | } 325 | }, 326 | "terser": { 327 | "version": "4.4.0", 328 | "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.0.tgz", 329 | "integrity": "sha512-oDG16n2WKm27JO8h4y/w3iqBGAOSCtq7k8dRmrn4Wf9NouL0b2WpMHGChFGZq4nFAQy1FsNJrVQHfurXOSTmOA==", 330 | "requires": { 331 | "commander": "^2.20.0", 332 | "source-map": "~0.6.1", 333 | "source-map-support": "~0.5.12" 334 | }, 335 | "dependencies": { 336 | "source-map": { 337 | "version": "0.6.1", 338 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 339 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 340 | } 341 | } 342 | }, 343 | "uglify-js": { 344 | "version": "3.6.8", 345 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz", 346 | "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==", 347 | "requires": { 348 | "commander": "~2.20.3", 349 | "source-map": "~0.6.1" 350 | }, 351 | "dependencies": { 352 | "source-map": { 353 | "version": "0.6.1", 354 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 355 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 356 | } 357 | } 358 | }, 359 | "upper-case": { 360 | "version": "1.1.3", 361 | "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", 362 | "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" 363 | }, 364 | "web-streams-polyfill": { 365 | "version": "2.0.6", 366 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-2.0.6.tgz", 367 | "integrity": "sha512-nXOi4fBykO4LzyQhZX3MAGib635KGZBoNTkNXrNIkz0zthEf2QokEWxRb0H632xNLDWtHFb1R6dFGzksjYMSDw==" 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dof-tool", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "build": "rollup -c", 7 | "fmt": "prettier --write ./src/**/*.{html,js} ./*.{json,js}" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/surma/dof-tool" 12 | }, 13 | "keywords": [], 14 | "author": "Surma ", 15 | "license": "Apache-2.0", 16 | "dependencies": { 17 | "html-minifier": "^4.0.0", 18 | "idb-keyval": "^3.2.0", 19 | "observables-with-streams": "^0.5.0", 20 | "rollup": "^1.27.8", 21 | "rollup-plugin-node-resolve": "^5.2.0", 22 | "rollup-plugin-terser": "^5.1.2", 23 | "web-streams-polyfill": "^2.0.6" 24 | }, 25 | "devDependencies": { 26 | "prettier": "^1.19.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | import nodeResolve from "rollup-plugin-node-resolve"; 15 | import { terser } from "rollup-plugin-terser"; 16 | import { promises as fsp } from "fs"; 17 | import { minify } from "html-minifier"; 18 | 19 | const minifierConfig = { 20 | collapseWhitespace: true, 21 | html5: true, 22 | minifyCSS: true, 23 | minifyJS: true, 24 | removeOptionalTags: true, 25 | removeRedundantAttributes: true 26 | }; 27 | 28 | const dir = ".public"; 29 | export default { 30 | input: "src/script.js", 31 | output: { 32 | format: "esm", 33 | dir 34 | }, 35 | plugins: [ 36 | nodeResolve(), 37 | terser(), 38 | { 39 | async writeBundle() { 40 | const html = await fsp.readFile("src/index.html", "utf8"); 41 | await fsp.writeFile(".public/index.html", minify(html, minifierConfig)); 42 | } 43 | } 44 | ] 45 | }; 46 | -------------------------------------------------------------------------------- /src/analytics.js: -------------------------------------------------------------------------------- 1 | (function(a, b, c) { 2 | var d = a.history, 3 | e = document, 4 | f = navigator || {}, 5 | g = localStorage, 6 | h = encodeURIComponent, 7 | i = d.pushState, 8 | k = function() { 9 | return Math.random().toString(36); 10 | }, 11 | l = function() { 12 | return g.cid || (g.cid = k()), g.cid; 13 | }, 14 | m = function(r) { 15 | var s = []; 16 | for (var t in r) 17 | r.hasOwnProperty(t) && void 0 !== r[t] && s.push(h(t) + "=" + h(r[t])); 18 | return s.join("&"); 19 | }, 20 | n = function(r, s, t, u, v, w, x) { 21 | var z = "https://www.google-analytics.com/collect", 22 | A = m({ 23 | v: "1", 24 | ds: "web", 25 | aip: c.anonymizeIp ? 1 : void 0, 26 | tid: b, 27 | cid: l(), 28 | t: r || "pageview", 29 | sd: 30 | c.colorDepth && screen.colorDepth 31 | ? screen.colorDepth + "-bits" 32 | : void 0, 33 | dr: e.referrer || void 0, 34 | dt: e.title, 35 | dl: e.location.origin + e.location.pathname + e.location.search, 36 | ul: c.language ? (f.language || "").toLowerCase() : void 0, 37 | de: c.characterSet ? e.characterSet : void 0, 38 | sr: c.screenSize 39 | ? (a.screen || {}).width + "x" + (a.screen || {}).height 40 | : void 0, 41 | vp: 42 | c.screenSize && a.visualViewport 43 | ? (a.visualViewport || {}).width + 44 | "x" + 45 | (a.visualViewport || {}).height 46 | : void 0, 47 | ec: s || void 0, 48 | ea: t || void 0, 49 | el: u || void 0, 50 | ev: v || void 0, 51 | exd: w || void 0, 52 | exf: "undefined" != typeof x && !1 == !!x ? 0 : void 0 53 | }); 54 | if (f.sendBeacon) f.sendBeacon(z, A); 55 | else { 56 | var y = new XMLHttpRequest(); 57 | y.open("POST", z, !0), y.send(A); 58 | } 59 | }; 60 | (d.pushState = function(r) { 61 | return ( 62 | "function" == typeof d.onpushstate && d.onpushstate({ state: r }), 63 | setTimeout(n, c.delay || 10), 64 | i.apply(d, arguments) 65 | ); 66 | }), 67 | n(), 68 | (a.ma = { 69 | trackEvent: function o(r, s, t, u) { 70 | return n("event", r, s, t, u); 71 | }, 72 | trackException: function q(r, s) { 73 | return n("exception", null, null, null, null, r, s); 74 | } 75 | }); 76 | })(window, "UA-154121460-1", { 77 | anonymizeIp: true, 78 | colorDepth: true, 79 | characterSet: true, 80 | screenSize: true, 81 | language: true 82 | }); 83 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 186 |
187 | 193 | 224 |
225 | 231 | 232 | 233 | 234 | 242 | 50mm 243 | 244 | 245 | 246 | 247 | 254 | 0° 255 | 256 | 257 | 258 | 259 | 260 | 268 | 276 | 277 | 278 | 279 |
280 | 283 | 286 | 289 | 296 |
297 | 298 | Show 299 | , show 300 | , source code on 301 | 306 | GitHub . 308 | 309 | Made with 📸 by 311 | Surma 318 |
319 |
320 | 321 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | import * as ows from "observables-with-streams"; 15 | import ScrollSlider from "./scroll-slider.js"; 16 | import ToggleSwitch from "./toggle-switch.js"; 17 | import * as idb from "idb-keyval"; 18 | 19 | customElements.define("scroll-slider", ScrollSlider); 20 | customElements.define("toggle-switch", ToggleSwitch); 21 | 22 | const focals = [ 23 | 1, 24 | 4, 25 | 8, 26 | 11, 27 | 15, 28 | 24, 29 | 35, 30 | 50, 31 | 70, 32 | 85, 33 | 100, 34 | 200, 35 | 300, 36 | 400, 37 | 500, 38 | 600 39 | ]; 40 | 41 | const apertures = [ 42 | 0.7, 43 | 0.8, 44 | 0.9, 45 | 1.0, 46 | 1.1, 47 | 1.2, 48 | 1.4, 49 | 1.6, 50 | 1.8, 51 | 2, 52 | 2.2, 53 | 2.5, 54 | 2.8, 55 | 3.2, 56 | 3.5, 57 | 4, 58 | 4.5, 59 | 5.0, 60 | 5.6, 61 | 6.3, 62 | 7.1, 63 | 8, 64 | 9, 65 | 10, 66 | 11, 67 | 13, 68 | 14, 69 | 16, 70 | 18, 71 | 20, 72 | 22, 73 | 25, 74 | 29, 75 | 32 76 | ]; 77 | 78 | const sensors = { 79 | "Full-Frame": { 80 | width: 36, 81 | height: 24 82 | }, 83 | "APS-H (Canon)": { 84 | width: 28.7, 85 | height: 19.0 86 | }, 87 | "APS-C (Nikon/Pentax/Sony)": { 88 | width: 23.6, 89 | height: 15.7 90 | }, 91 | "APS-C (Canon)": { 92 | width: 22.2, 93 | height: 14.8 94 | }, 95 | "APS-C": { 96 | width: 22.5, 97 | height: 15 98 | }, 99 | '4/3"': { 100 | width: 18, 101 | height: 13.5 102 | }, 103 | '1"': { 104 | width: 13.2, 105 | height: 8.8 106 | } 107 | }; 108 | 109 | // 1, 2, 5, 10, 20, 50, 100, 200, 500, ... 110 | function bankNoteSequence(v) { 111 | return [1, 2, 5][v % 3] * 10 ** Math.floor(v / 3); 112 | } 113 | 114 | async function idbGetWithDefault(key, def) { 115 | if (!(await idb.keys()).includes(key)) { 116 | return def; 117 | } 118 | return idb.get(key); 119 | } 120 | 121 | const elementsCache = new Map(); 122 | function memoizedQuerySelector(selector) { 123 | if (!elementsCache.has(selector)) { 124 | elementsCache.set(selector, [...document.querySelectorAll(selector)]); 125 | } 126 | return elementsCache.get(selector)[0]; 127 | } 128 | 129 | function memoizedQuerySelectorAll(selector) { 130 | if (!elementsCache.has(selector)) { 131 | elementsCache.set(selector, [...document.querySelectorAll(selector)]); 132 | } 133 | return elementsCache.get(selector); 134 | } 135 | 136 | export function fromInput(el) { 137 | const { next, observable } = ows.external(); 138 | el.addEventListener("input", () => next(el.value)); 139 | next(el.value); 140 | return observable; 141 | } 142 | 143 | export function fromCheckbox(el) { 144 | const { next, observable } = ows.external(); 145 | el.addEventListener("change", () => next(el.checked)); 146 | next(el.checked); 147 | return observable; 148 | } 149 | 150 | export function fromChange(el) { 151 | const { next, observable } = ows.external(); 152 | el.addEventListener("change", () => next(el.value)); 153 | next(el.value); 154 | return observable; 155 | } 156 | 157 | export function tee(f) { 158 | const { readable, writable } = new TransformStream(); 159 | const [rs1, rs2] = readable.tee(); 160 | f(rs2); 161 | return { 162 | writable, 163 | readable: rs1 164 | }; 165 | } 166 | 167 | function formatDistance(v, decimals = true) { 168 | if (v < 0 || !Number.isFinite(v)) { 169 | return "∞"; 170 | } 171 | if (v < 10) { 172 | return `${v.toFixed(0)}mm`; 173 | } 174 | if (v < 1000) { 175 | return `${decimals ? (v / 10).toFixed(1) : (v / 10).toFixed(0)}cm`; 176 | } 177 | return `${decimals ? (v / 1000).toFixed(2) : (v / 1000).toFixed(0)}m`; 178 | } 179 | 180 | function idbInput(el, key, def) { 181 | return ows 182 | .fromAsyncFunction(async () => { 183 | const v = await idbGetWithDefault(key, def); 184 | el.value = v; 185 | return fromInput(el); 186 | }) 187 | .pipeThrough(ows.switchAll()) 188 | .pipeThrough( 189 | tee(o => { 190 | o.pipeThrough(ows.debounce(1000)).pipeTo( 191 | ows.discard(v => idb.set(key, v)) 192 | ); 193 | }) 194 | ); 195 | } 196 | 197 | function idbCheckbox(el, key, def) { 198 | return ows 199 | .fromAsyncFunction(async () => { 200 | const v = await idbGetWithDefault(key, def); 201 | el.checked = v; 202 | return fromCheckbox(el); 203 | }) 204 | .pipeThrough(ows.switchAll()) 205 | .pipeThrough( 206 | tee(o => { 207 | o.pipeThrough(ows.debounce(1000)).pipeTo( 208 | ows.discard(v => idb.set(key, v)) 209 | ); 210 | }) 211 | ); 212 | } 213 | 214 | export function init() { 215 | ows 216 | .combineLatest( 217 | // Sensor 218 | ows 219 | .fromAsyncFunction(async () => { 220 | const sensor = await idbGetWithDefault("sensor", {}); 221 | for (const sensorName of Object.keys(sensors)) { 222 | const option = document.createElement("option"); 223 | option.value = sensorName; 224 | option.textContent = sensorName; 225 | memoizedQuerySelector("#sensor").appendChild(option); 226 | } 227 | if (sensor && sensor.name) { 228 | const sensorIdx = [ 229 | ...memoizedQuerySelector("#sensor").children 230 | ].findIndex(option => option.value === sensor.name); 231 | memoizedQuerySelector("#sensor").selectedIndex = sensorIdx; 232 | } 233 | return fromInput(memoizedQuerySelector("#sensor")).pipeThrough( 234 | ows.map(name => ({ name, ...sensors[name] })) 235 | ); 236 | }) 237 | .pipeThrough(ows.switchAll()) 238 | .pipeThrough( 239 | ows.map(sensor => ({ 240 | ...sensor, 241 | coc: Math.sqrt(sensor.width ** 2 + sensor.height ** 2) / 1500, 242 | cropFactor: 243 | Math.sqrt(36 ** 2 + 24 ** 2) / 244 | Math.sqrt(sensor.width ** 2 + sensor.height ** 2) 245 | })) 246 | ) 247 | .pipeThrough( 248 | ows.combineLatestWith( 249 | idbInput(memoizedQuerySelector("#megapixel"), "megapixel", 30), 250 | fromCheckbox(memoizedQuerySelector("#advcoc")) 251 | ) 252 | ) 253 | .pipeThrough( 254 | ows.map(([sensor, megapixel, shouldMerge]) => { 255 | if (!shouldMerge) { 256 | return sensor; 257 | } 258 | const numPixelsPerRow = Math.sqrt((3 / 2) * megapixel * 1000000); 259 | const pixelSize = 36 / numPixelsPerRow; 260 | return { 261 | ...sensor, 262 | coc: pixelSize 263 | }; 264 | }) 265 | ), 266 | // Aperture slider 267 | ows 268 | .fromAsyncFunction(async () => { 269 | const slider = memoizedQuerySelector("#aperture scroll-slider"); 270 | slider.valueFunction = v => { 271 | const left = apertures[Math.floor(v)]; 272 | const right = apertures[Math.ceil(v)]; 273 | return left + (right - left) * (v % 1); 274 | }; 275 | slider.labelFunction = v => `f/${v.toFixed(1)}`; 276 | slider.numItems = apertures.length; 277 | return idbInput(slider, "aperture", 2.8).pipeThrough(ows.distinct()); 278 | }) 279 | .pipeThrough(ows.switchAll()), 280 | // Focal length slider 281 | ows 282 | .fromAsyncFunction(async () => { 283 | const slider = document.querySelector("#focalin scroll-slider"); 284 | slider.valueFunction = v => { 285 | const left = focals[Math.floor(v)]; 286 | const right = focals[Math.ceil(v)]; 287 | return left + (right - left) * (v % 1); 288 | }; 289 | slider.labelFunction = v => `${v.toFixed(0)}mm`; 290 | slider.numItems = focals.length; 291 | slider.style = "--spacing: 5em"; 292 | 293 | return idbInput(slider, "focal", 50).pipeThrough(ows.distinct()); 294 | }) 295 | .pipeThrough(ows.switchAll()), 296 | // Distance slider 297 | ows 298 | .fromAsyncFunction(async () => { 299 | const slider = document.querySelector("#distance scroll-slider"); 300 | slider.valueFunction = v => { 301 | const left = bankNoteSequence(Math.floor(v)); 302 | const right = bankNoteSequence(Math.ceil(v)); 303 | return left + (right - left) * (v % 1); 304 | }; 305 | slider.labelFunction = v => formatDistance(v, false); 306 | slider.numItems = 16; 307 | slider.style = "--spacing: 5em"; 308 | 309 | return idbInput(slider, "distance", 1000).pipeThrough(ows.distinct()); 310 | }) 311 | .pipeThrough(ows.switchAll()) 312 | ) 313 | .pipeThrough( 314 | ows.map(([sensor, aperture, focal, distance]) => { 315 | const hyperfocal = focal ** 2 / (aperture * sensor.coc) + focal; 316 | const horizontalFoV = 2 * Math.atan(sensor.width / (2 * focal)); 317 | const verticalFoV = 2 * Math.atan(sensor.height / (2 * focal)); 318 | const nearFocusPlane = 319 | (distance * (hyperfocal - focal)) / 320 | (hyperfocal + distance - 2 * focal); 321 | const farFocusPlane = 322 | (distance * (hyperfocal - focal)) / (hyperfocal - distance); 323 | const dofNear = distance - nearFocusPlane; 324 | const dofFar = farFocusPlane - distance; 325 | const totalDof = dofNear + dofFar; 326 | return { 327 | sensor, 328 | aperture, 329 | focal, 330 | distance, 331 | hyperfocal, 332 | horizontalFoV, 333 | verticalFoV, 334 | nearFocusPlane, 335 | farFocusPlane, 336 | dofNear, 337 | dofFar, 338 | totalDof 339 | }; 340 | }) 341 | ) 342 | // Zoom view 343 | .pipeThrough( 344 | ows.forEach(({ distance }) => { 345 | memoizedQuerySelector("svg").setAttribute( 346 | "viewBox", 347 | `0 -50 ${Math.max((distance * 1.1 + 300 + 200 + 200) / 10, 100)} 100` 348 | ); 349 | memoizedQuerySelector("#world").setAttribute( 350 | "transform", 351 | `translate(${distance / 10}, 0)` 352 | ); 353 | }) 354 | ) 355 | // Move focus planes 356 | .pipeThrough( 357 | ows.forEach(({ nearFocusPlane, farFocusPlane, distance }) => { 358 | memoizedQuerySelector("#nearFocusPlane").setAttribute( 359 | "x1", 360 | (nearFocusPlane - distance) / 10 361 | ); 362 | memoizedQuerySelector("#nearFocusPlane").setAttribute( 363 | "x2", 364 | (nearFocusPlane - distance) / 10 365 | ); 366 | memoizedQuerySelector("#farFocusPlane").setAttribute( 367 | "x1", 368 | (farFocusPlane - distance) / 10 369 | ); 370 | memoizedQuerySelector("#farFocusPlane").setAttribute( 371 | "x2", 372 | (farFocusPlane - distance) / 10 373 | ); 374 | }) 375 | ) 376 | // Adjust view cone 377 | .pipeThrough( 378 | ows.forEach(({ horizontalFoV, verticalFoV, distance }) => { 379 | const hDeg = (horizontalFoV * 360) / (2 * Math.PI); 380 | const vDeg = (verticalFoV * 360) / (2 * Math.PI); 381 | memoizedQuerySelectorAll(".hfov").forEach( 382 | el => (el.textContent = `${hDeg.toFixed(0)}°`) 383 | ); 384 | memoizedQuerySelectorAll(".fov").forEach( 385 | el => 386 | (el.textContent = `H∢: ${hDeg.toFixed(0)}° V∢: ${vDeg.toFixed(0)}°`) 387 | ); 388 | const width = distance * Math.atan(horizontalFoV); 389 | memoizedQuerySelectorAll(".wdof").forEach( 390 | el => (el.textContent = formatDistance(width)) 391 | ); 392 | const r = 38; 393 | const x1 = 30 + Math.cos(-horizontalFoV / 2) * r; 394 | const y1 = Math.sin(-horizontalFoV / 2) * r; 395 | const x2 = 30 + Math.cos(horizontalFoV / 2) * r; 396 | const y2 = Math.sin(horizontalFoV / 2) * r; 397 | document 398 | .querySelector("#fov-arc") 399 | .setAttribute( 400 | "d", 401 | `M ${x1},${y1} A ${r},${r},${hDeg},0,1,${x2},${y2}` 402 | ); 403 | memoizedQuerySelector("#fov1").style.transform = `rotate(${(-1 * hDeg) / 404 | 2}deg)`; 405 | memoizedQuerySelector("#fov2").style.transform = `rotate(${hDeg / 406 | 2}deg)`; 407 | }) 408 | ) 409 | // Set numeric outputs 410 | .pipeThrough( 411 | ows.forEach(data => { 412 | memoizedQuerySelectorAll(".focal").forEach( 413 | el => (el.textContent = `${data.focal.toFixed(0)}mm`) 414 | ); 415 | memoizedQuerySelectorAll(".crop").forEach( 416 | el => 417 | (el.textContent = `${(data.focal * data.sensor.cropFactor).toFixed( 418 | 0 419 | )}mm (${data.sensor.cropFactor.toFixed(1)}x)`) 420 | ); 421 | memoizedQuerySelectorAll(".hyperfocal").forEach( 422 | el => (el.textContent = formatDistance(data.hyperfocal)) 423 | ); 424 | memoizedQuerySelectorAll(".distance").forEach( 425 | el => (el.textContent = formatDistance(data.distance)) 426 | ); 427 | memoizedQuerySelectorAll(".aperture").forEach( 428 | el => (el.textContent = `f/${data.aperture.toFixed(1)}`) 429 | ); 430 | const dofBefore = data.distance - data.nearFocusPlane; 431 | let dofAfter = data.farFocusPlane - data.distance; 432 | memoizedQuerySelectorAll(".dof").forEach( 433 | el => 434 | (el.textContent = `${formatDistance(dofBefore)} + ${formatDistance( 435 | dofAfter 436 | )} = ${formatDistance(dofBefore + dofAfter)}`) 437 | ); 438 | }) 439 | ) 440 | .pipeTo(ows.discard()); 441 | 442 | ows 443 | .fromNext(async next => { 444 | const showDetails = await idbGetWithDefault("showDetails", false); 445 | next(ows.just(showDetails)); 446 | next( 447 | ows 448 | .merge( 449 | ows 450 | .fromEvent(document, "keypress") 451 | .pipeThrough(ows.filter(ev => ev.key === "D")) 452 | .pipeThrough(ows.forEach(ev => ev.preventDefault())), 453 | ...memoizedQuerySelectorAll(".opendetails").map(el => 454 | ows.fromEvent(el, "click") 455 | ) 456 | ) 457 | .pipeThrough(ows.scan(v => !v, showDetails)) 458 | ); 459 | }) 460 | .pipeThrough(ows.concatAll()) 461 | .pipeThrough( 462 | ows.forEach(showDetails => 463 | memoizedQuerySelector("#factsheet").classList.toggle( 464 | "hidden", 465 | !showDetails 466 | ) 467 | ) 468 | ) 469 | .pipeThrough(ows.debounce(1000)) 470 | .pipeTo( 471 | ows.discard(async v => { 472 | await idb.set("showDetails", v); 473 | }) 474 | ); 475 | 476 | ows 477 | .merge( 478 | ows 479 | .fromEvent(document, "keypress") 480 | .pipeThrough(ows.filter(ev => ev.key === "?")) 481 | .pipeThrough(ows.forEach(ev => ev.preventDefault())), 482 | ...memoizedQuerySelectorAll(".openhelp").map(el => 483 | ows.fromEvent(el, "click") 484 | ) 485 | ) 486 | .pipeThrough(ows.scan(v => !v, false)) 487 | .pipeTo( 488 | ows.discard(async v => { 489 | console.log(v); 490 | memoizedQuerySelector("#help").classList.toggle("hidden", v); 491 | }) 492 | ); 493 | 494 | idbCheckbox(memoizedQuerySelector("#advcoc"), "advcoc", false).pipeTo( 495 | ows.discard(showAdvancedCoC => { 496 | memoizedQuerySelectorAll(".advcoc").forEach(el => 497 | el.classList.toggle("disabled", !showAdvancedCoC) 498 | ); 499 | }) 500 | ); 501 | } 502 | -------------------------------------------------------------------------------- /src/script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | import { init } from "./main.js"; 15 | 16 | (async function() { 17 | const hasReadableStream = typeof ReadableStream !== "undefined"; 18 | const hasWritableStream = typeof WritableStream !== "undefined"; 19 | const hasTransformStream = typeof TransformStream !== "undefined"; 20 | 21 | if (!hasReadableStream || !hasWritableStream || !hasTransformStream) { 22 | await import("web-streams-polyfill/dist/polyfill.es2018.mjs"); 23 | } 24 | init(); 25 | import("./analytics.js"); 26 | })(); 27 | -------------------------------------------------------------------------------- /src/scroll-slider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | const html = String.raw; 15 | 16 | export default class ScrollSlider extends HTMLElement { 17 | static get observedAttributes() { 18 | return ["snap", "num-items"]; 19 | } 20 | 21 | constructor() { 22 | super(); 23 | this.attachShadow({ mode: "open" }); 24 | this.shadowRoot.innerHTML = html` 25 | 97 |
98 | 104 | 105 | 106 | 112 | 113 | 114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | `; 122 | 123 | this._valueFunction = x => x; 124 | this._labelFunction = x => x; 125 | this._container = this.shadowRoot.querySelector("#container"); 126 | this._scroller = this.shadowRoot.querySelector("#scroller"); 127 | this._regenerateLabels(); 128 | this._processNumItemsAttribute(); 129 | 130 | this._scroller.addEventListener("scroll", this._onScroll.bind(this)); 131 | this._container.addEventListener("click", this._onClick.bind(this)); 132 | } 133 | 134 | attributeChangedCallback() { 135 | this._processSnapAttribute(); 136 | this._processNumItemsAttribute(); 137 | } 138 | 139 | get snap() { 140 | return this.hasAttribute("snap"); 141 | } 142 | 143 | set snap(value) { 144 | if (Boolean(value)) { 145 | this.setAttribute("snap", ""); 146 | } else { 147 | this.removeAttribute("snap"); 148 | } 149 | } 150 | 151 | get numItems() { 152 | return this._numItems; 153 | } 154 | 155 | set numItems(value) { 156 | this._numItems = value; 157 | this._regenerateLabels(); 158 | this._dispatchInputEvent(); 159 | } 160 | 161 | set valueFunction(f) { 162 | this._valueFunction = f; 163 | this._regenerateLabels(); 164 | } 165 | 166 | set labelFunction(f) { 167 | this._labelFunction = f; 168 | this._regenerateLabels(); 169 | } 170 | 171 | _offsetForScrollPos(pos = this._scroller.scrollLeft) { 172 | return ( 173 | (pos / (this._scroller.scrollWidth - this._scroller.clientWidth)) * 174 | (this._numItems - 1) 175 | ); 176 | } 177 | 178 | _valueForScrollPos(pos = this._scroller.scrollLeft) { 179 | const offset = this._offsetForScrollPos(pos); 180 | return this._valueFunction(offset); 181 | } 182 | 183 | get value() { 184 | return this._valueForScrollPos(); 185 | } 186 | 187 | set value(target) { 188 | const left = this._scrollPositionForValue(target); 189 | this._scroller.scrollTo({ left }); 190 | } 191 | 192 | _scrollPositionForValue(target) { 193 | // Binary search to find the scroll position that is equivalent 194 | // to the value provided. 195 | const max = this._scroller.scrollWidth - this._scroller.clientWidth; 196 | let current = max / 2; 197 | let delta = max / 4; 198 | while (delta > 1) { 199 | if (this._valueForScrollPos(current) > target) { 200 | current -= delta; 201 | } else { 202 | current += delta; 203 | } 204 | delta = Math.floor(delta / 2); 205 | } 206 | return current; 207 | } 208 | 209 | animateToValue(target) { 210 | const left = this._scrollPositionForValue(target); 211 | this._scroller.scrollTo({ left, behavior: "smooth" }); 212 | } 213 | 214 | _processSnapAttribute() { 215 | this._scroller.classList.toggle("snap", this.snap); 216 | } 217 | 218 | _processNumItemsAttribute() { 219 | const attrValue = parseInt(this.getAttribute("num-items") || "10", 10); 220 | if (attrValue !== this.numItems) { 221 | this.numItems = attrValue; 222 | this._regenerateLabels(); 223 | } 224 | } 225 | 226 | _regenerateLabels() { 227 | this._container 228 | .querySelectorAll("*:not(.padding)") 229 | .forEach(el => el.remove()); 230 | const lastChild = this._container.querySelector("*:last-of-type"); 231 | for (let i = 0; i < this.numItems; i++) { 232 | const span = document.createElement("span"); 233 | span.classList.add("label"); 234 | span.textContent = `${this._labelFunction(this._valueFunction(i))}`; 235 | this._container.insertBefore(span, lastChild); 236 | } 237 | } 238 | 239 | _labels() { 240 | return [...this._container.querySelectorAll(".label")]; 241 | } 242 | 243 | _onScroll() { 244 | this._dispatchInputEvent(); 245 | this._adjustLabelSize(); 246 | } 247 | 248 | _adjustLabelSize() { 249 | const offset = this._offsetForScrollPos(); 250 | const labels = this._labels(); 251 | labels.forEach(label => { 252 | label.style.opacity = "0.5"; 253 | label.style.transform = "scale(0.7)"; 254 | }); 255 | const left = Math.floor(offset); 256 | const right = Math.ceil(offset); 257 | const leftFactor = distanceMap(Math.abs(left - offset)); 258 | const rightFactor = distanceMap(Math.abs(right - offset)); 259 | labels[left].style.opacity = leftFactor * 0.5 + 0.5; 260 | labels[left].style.transform = `scale(${leftFactor * 0.3 + 0.7})`; 261 | labels[right].style.opacity = rightFactor * 0.5 + 0.5; 262 | labels[right].style.transform = `scale(${rightFactor * 0.3 + 0.7})`; 263 | } 264 | 265 | _dispatchInputEvent() { 266 | this.dispatchEvent(new InputEvent("input")); 267 | } 268 | 269 | _onClick(ev) { 270 | if (!ev.target.classList.contains("label")) { 271 | return; 272 | } 273 | const idx = this._labels().indexOf(ev.target); 274 | const value = this._valueFunction(idx); 275 | this.animateToValue(value); 276 | } 277 | } 278 | 279 | function distanceMap(v) { 280 | // Gamma correction formula as an easing curve 281 | return Math.pow(1 - v, 0.8); 282 | } 283 | -------------------------------------------------------------------------------- /src/toggle-switch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | const html = String.raw; 15 | 16 | export default class ToggleSwitch extends HTMLElement { 17 | static get observedAttributes() { 18 | return ["checked"]; 19 | } 20 | 21 | constructor() { 22 | super(); 23 | this.attachShadow({ mode: "open" }); 24 | this.shadowRoot.innerHTML = html` 25 | 63 |
64 | 65 |
66 | 70 |
71 | 72 |
73 | `; 74 | 75 | this._checkbox = this.shadowRoot.querySelector("#checkbox"); 76 | this.shadowRoot 77 | .querySelector(".label.left") 78 | .addEventListener("click", () => (this.checked = false)); 79 | this.shadowRoot 80 | .querySelector(".label.right") 81 | .addEventListener("click", () => (this.checked = true)); 82 | this._checkbox.addEventListener("change", this._onChange.bind(this)); 83 | } 84 | 85 | attributeChangedCallback(name) { 86 | if (name === "checked") { 87 | this.checked = this.hasAttribute("checked"); 88 | } 89 | } 90 | 91 | get active() { 92 | return this._checkbox.checked ? "right" : "left"; 93 | } 94 | 95 | set active(val) { 96 | if (val === "left") { 97 | this.checked = false; 98 | } 99 | if (val === "right") { 100 | this.checked = true; 101 | } 102 | } 103 | 104 | get checked() { 105 | return this._checkbox.checked; 106 | } 107 | 108 | set checked(val) { 109 | this._checkbox.checked = val; 110 | this._onChange(); 111 | } 112 | 113 | _onChange() { 114 | this.dispatchEvent(new Event("change")); 115 | } 116 | } 117 | --------------------------------------------------------------------------------