├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example ├── misc │ ├── drive.dart │ ├── html_docs.dart │ ├── menu.dart │ ├── sheet.dart │ └── src │ │ └── data.dart ├── readme.md └── tournament │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── analysis_options.yaml │ ├── bin │ └── main.dart │ ├── lib │ ├── menu.dart │ └── src │ │ ├── brackets.dart │ │ ├── cache.dart │ │ ├── config.dart │ │ ├── next_round.dart │ │ ├── reports.dart │ │ ├── tournament.dart │ │ └── utils.dart │ └── pubspec.yaml ├── lib ├── cache.dart ├── document.dart ├── drive.dart ├── google_apps.dart ├── html.dart ├── lock.dart ├── properties.dart ├── spreadsheet.dart ├── tasks.dart ├── ui.dart └── url_fetch.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .packages 3 | .pub/ 4 | build/ 5 | # Remove the following pattern if you wish to check in your lock file 6 | pubspec.lock 7 | 8 | # Directory created by dartdoc 9 | doc/api/ 10 | 11 | out 12 | 13 | .idea 14 | google-apps.iml 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.2 4 | - Run dartfmt. 5 | 6 | ## 0.1.1 7 | - Add link to medium article in README. 8 | 9 | ## 0.1.0 10 | 11 | - Add a big tournament example. 12 | - Add a tiny drive example. 13 | - Fix some linter complaints. 14 | 15 | ## 0.0.1+3 16 | 17 | - Fix the sheet example. 18 | 19 | ## 0.0.1+2 20 | 21 | - Update README (removing the example and adding the issue tracker). 22 | 23 | ## 0.0.1+1 24 | 25 | - Rename the example file so it's recognized by pub.dartlang.org. 26 | 27 | ## 0.0.1 28 | 29 | - Initial version. 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /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. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dart APIs for Google Apps Script 2 | 3 | This is not an official Google product. It is not supported by the Dart team. 4 | 5 | See [article] for a blog post explaining how to use this package. 6 | 7 | [article]: https://medium.com/@florian_32814/google-apps-scripts-with-dart-402c042fa606 8 | 9 | This package is still in an experimental state. 10 | 11 | A library to write Google Apps Script. 12 | 13 | This library has been written on a per-need basis. As such it is missing lots 14 | of useful functionality that I just hadn't needed yet. Until the API coverage 15 | is nearing completeness I recommend to checkout the GIT repository during 16 | development and to use this library with a `path` directive, adding the missing 17 | functions when they are encountered. 18 | 19 | Consider contributing your changes back to the original repository. 20 | 21 | ## Features and bugs 22 | 23 | Please file feature requests and bugs at the [issue tracker][tracker]. 24 | 25 | [tracker]: https://github.com/google/dart_google_apps/issues 26 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 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 | # 7 | # https:#www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | analyzer: 16 | strong-mode: true 17 | # exclude: 18 | # - path/to/excluded/files/** 19 | 20 | # Lint rules and documentation, see http://dart-lang.github.io/linter/lints 21 | linter: 22 | rules: 23 | - hash_and_equals 24 | - iterable_contains_unrelated_type 25 | - list_remove_unrelated_type 26 | - test_types_in_equals 27 | - unrelated_type_equality_checks 28 | - valid_regexps 29 | -------------------------------------------------------------------------------- /example/misc/drive.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Compile this example with 16 | /// `dart2js --csp -o drive.js example/drive.dart`. 17 | /// 18 | /// See [apps_script_tools](https://pub.dartlang.org/packages/apps_script_tools) 19 | /// for a description on how to execute the generated program. 20 | 21 | import 'package:google_apps/drive.dart'; 22 | 23 | main() { 24 | DriveApp.createFile("hello.txt", "Hello from Dart"); 25 | } 26 | -------------------------------------------------------------------------------- /example/misc/html_docs.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Compile this example with 16 | /// `dart2js --csp -o html_docs.js example/html_docs.dart`. 17 | /// 18 | /// See [apps_script_tools](https://pub.dartlang.org/packages/apps_script_tools) 19 | /// for a description on how to execute the generated program. 20 | 21 | @JS() 22 | library html_docs; 23 | 24 | import 'package:js/js.dart'; 25 | import 'package:google_apps/google_apps.dart'; 26 | 27 | @JS() 28 | external set onOpen(value); 29 | 30 | @JS() 31 | external set demo(value); 32 | 33 | @JS() 34 | external set modal(value); 35 | 36 | void onOpenDart(e) { 37 | SpreadsheetApp 38 | .getUi() 39 | .createMenu("Dart") 40 | .addItem("demo", "demo") 41 | .addToUi(); 42 | } 43 | 44 | void exportToJs() { 45 | onOpen = allowInterop(onOpenDart); 46 | demo = allowInterop(demoDart); 47 | modal = allowInterop(modalDart); 48 | } 49 | 50 | String sideBar = r""" 51 | 52 | 53 |
54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 | 64 |
65 |
66 | 67 | 68 |
69 |
70 | 71 | 72 |
73 |
74 | 75 | 76 |
77 | 78 | 79 | 94 | 95 | 96 | """; 97 | 98 | void demoDart() { 99 | HtmlOutput sideBarHtml = HtmlService.createHtmlOutput(sideBar); 100 | SpreadsheetApp.getUi().showSidebar(sideBarHtml); 101 | } 102 | 103 | void modalDart(selected) { 104 | var document = DocumentApp.create("doc-demo - ${new DateTime.now()}"); 105 | var body = document.getBody(); 106 | body.appendParagraph("Created from Dart").editAsText().setFontSize(32); 107 | body 108 | .appendParagraph("Selected items: ${selected}") 109 | .editAsText() 110 | .setFontSize(20); 111 | 112 | var id = document.getId(); 113 | String html = """ 114 | 115 |
Created a document.
116 | 117 | 118 | """; 119 | HtmlOutput userInterface = HtmlService.createHtmlOutput(html.toString()); 120 | SpreadsheetApp.getUi().showModalDialog(userInterface, "Document Ready"); 121 | } 122 | 123 | main() { 124 | exportToJs(); 125 | } 126 | -------------------------------------------------------------------------------- /example/misc/menu.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Compile this example with 16 | /// `dart2js --csp -o hello.js example/hello_docs.dart`. 17 | /// 18 | /// See [apps_script_tools](https://pub.dartlang.org/packages/apps_script_tools) 19 | /// for a description on how to execute the generated program. 20 | 21 | @JS() 22 | library hello_docs; 23 | 24 | import 'package:js/js.dart'; 25 | import 'package:google_apps/document.dart'; 26 | 27 | @JS() 28 | external set sayHello(value); 29 | 30 | @JS() 31 | external set onOpen(value); 32 | 33 | void sayHelloDart() { 34 | DocumentApp.getUi().alert("Hello world"); 35 | } 36 | 37 | void onOpenDart(e) { 38 | DocumentApp 39 | .getUi() 40 | .createMenu("from dart") 41 | .addItem("say hello", "sayHello") 42 | .addToUi(); 43 | } 44 | 45 | main(List arguments) { 46 | onOpen = allowInterop(onOpenDart); 47 | sayHello = allowInterop(sayHelloDart); 48 | } 49 | -------------------------------------------------------------------------------- /example/misc/sheet.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// Compile this example with 16 | /// `dart2js --csp -o sheet.js example/sheet.dart`. 17 | /// 18 | /// See [apps_script_tools](https://pub.dartlang.org/packages/apps_script_tools) 19 | /// for a description on how to execute the generated program. 20 | 21 | @JS() 22 | library sheet; 23 | 24 | import 'package:js/js.dart'; 25 | import 'package:google_apps/spreadsheet.dart'; 26 | import 'src/data.dart'; 27 | 28 | @JS() 29 | external set onOpen(value); 30 | 31 | @JS() 32 | external set demo(value); 33 | 34 | void onOpenDart(e) { 35 | SpreadsheetApp 36 | .getUi() 37 | .createMenu("Dart") 38 | .addItem("demo", "demo") 39 | .addToUi(); 40 | } 41 | 42 | void exportToJs() { 43 | onOpen = allowInterop(onOpenDart); 44 | demo = allowInterop(demoDart); 45 | } 46 | 47 | void demoDart() { 48 | var sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(); 49 | var imageHeight = imageData.length ~/ imageWidth; 50 | if (sheet.getMaxRows() < imageHeight + 1) { 51 | sheet.insertRows(1, imageHeight - sheet.getMaxRows()); 52 | } 53 | if (sheet.getMaxColumns() < imageWidth + 1) { 54 | sheet.insertColumns(1, imageWidth - sheet.getMaxColumns()); 55 | } 56 | for (int i = 0; i < imageWidth; i++) { 57 | sheet.setColumnWidth(i + 1, 2); 58 | } 59 | for (int i = 0; i < imageHeight; i++) { 60 | sheet.setRowHeight(i + 1, 3); 61 | } 62 | var colors = []; 63 | var index = 0; 64 | for (int i = 0; i < imageHeight; i++) { 65 | var row = []; 66 | colors.add(row); 67 | for (int j = 0; j < imageWidth; j++) { 68 | var color = imageData[index++]; 69 | if (color == 0) { 70 | row.add(null); 71 | continue; 72 | } 73 | var r = (color & 0xFF000000) >> 32; 74 | var r2 = r.toRadixString(16); 75 | if (r2.length != 2) r2 = "0$r2"; 76 | var g = (color & 0xFF00) >> 8; 77 | var g2 = g.toRadixString(16); 78 | if (g2.length != 2) g2 = "0$g2"; 79 | var b = (color & 0xFF0000) >> 16; 80 | var b2 = b.toRadixString(16); 81 | if (b2.length != 2) b2 = "0$b2"; 82 | row.add("#$r2$g2$b2"); 83 | } 84 | } 85 | sheet.getRange(1, 1, imageHeight, imageWidth).setBackgrounds(colors); 86 | } 87 | 88 | main() { 89 | exportToJs(); 90 | } 91 | -------------------------------------------------------------------------------- /example/misc/src/data.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const imageWidth = 128; 16 | const imageData = const [0,568894464,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2313199360,4226393344,3085082624,234127616,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,788736,3672744960,4293502208,4259947776,4025066496,1474010112,4276480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85069056,4041843968,4293502208,4293502208,4293502208,4125729792,2950339072,323695616,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,466523392,4192838656,4293502208,4293502208,4293502208,4293502208,4226393344,3790185472,1473024256,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1523224320,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4025066752,2849544448,359419136,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2800526592,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4209616128,3605570816,1455523840,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52829184,3471352832,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3941115136,2748816128,327704320,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,220863744,3773342720,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4209616128,3454706944,1420983552,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,531989760,4025066752,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276724992,3874071808,2632488704,262106880,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1035305216,4209616128,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4243170560,3337069568,1369732352,525824,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1706196224,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3806962944,2460251136,295726592,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,177763328,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2398010368,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4226393344,3186205696,1318480384,19208192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,74073856,2664337920,4209616128,3135874304,681012224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37036800,2867373312,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3739854080,2292807680,361393408,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1082942976,3756631296,4293502208,4293502208,4293502208,4209616128,2800591360,479625216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,120989184,3186205696,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4192838912,3035211008,1250516992,21112832,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24990464,2480248576,4209616128,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4108887296,2461959680,273758208,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271984128,3488195840,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3706299392,2142469888,394744320,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,914579968,3672744960,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3991512064,2109901312,123026432,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,456467712,3756565760,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4176061696,2900993280,1182488320,5658112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2279513856,4226393344,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3857294336,1757908992,23413504,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,682389760,3974735104,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3689522176,1992460800,374618880,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,745755136,3588793344,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3655902464,1400132096,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,933918720,4142441728,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4176061696,2783618048,1080904960,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34014464,2045157888,4276724992,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3370755328,1080970496,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1252554240,4243170560,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3672745216,1809423104,308101376,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,628380416,3437798656,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276724992,3035145472,795166720,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51646208,406267906,792209410,1194862337,1597515777,2021613314,2004835075,1602181891,1044853506,373568259,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1621718784,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4243170304,2666112256,979387136,197120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68226048,1843895808,4276724992,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4292319232,4290676481,4290807809,4292516352,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259947776,2649335296,509822720,0,0,0,0,0,0,338896129,791946497,1244931329,1683046913,2155698947,2692700932,3229571844,3766442756,4219427588,4269759236,3732888324,2860407811,1766470914,541734401,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2057992192,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3655902464,1641916160,291061504,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1445376,812535296,3270026240,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4290347778,4286536452,4286536452,4286536452,4286601988,4290808065,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4292911872,4291993088,2261554177,1513169665,2071878404,2759809795,3430898435,4101987076,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,3850328835,2927581699,1749956610,491665921,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3154944,2531828736,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276724992,2565448704,879840768,34737408,0,0,0,0,0,0,0,0,0,0,85528832,1181961472,3184560896,4291922689,4275606272,4293436416,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4291990784,4286536452,4286536452,4286536452,4286536452,4286536452,4286667780,4292516608,4293502208,4293502208,4293305088,4292582400,4291728640,4291005185,4289691394,4288050178,4286931971,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4135541508,3380566788,2321827842,793918467,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52960512,2984879360,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3588859136,1509200384,290141440,0,0,0,0,0,0,306392320,1656788992,3822160641,4291922689,4291922689,4291922689,4291922689,4241920256,4293436416,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4288902146,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4288507650,4289099522,4287588867,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,3816774403,2961136643,1481981187,136254977,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,119806208,3421086720,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4243170560,2464720128,759443456,35394816,2631680,624765184,2328988160,4208036609,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4224945664,4293370624,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4288507650,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286800387,4286932483,4286734595,4286536708,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,3833551619,2843040003,945438466,986880,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,186520832,3672745216,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,3588529920,3151467008,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4224879616,4293304832,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4292779264,4289887746,4236467971,4286536452,4286536452,4286536452,4286536452,4286536452,4286536452,4289111809,4290366464,4290366464,4290300672,4289904640,4289442304,4288979969,4288517634,4288055298,4287592706,4287329283,4287196931,3666109955,1499481603,17631488,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202969344,3840517376,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4276329984,4258565889,3805515008,4293502208,3940391168,4241591041,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4275145472,4259750144,4293502208,4293502208,4293502208,4292779264,4289821954,4186334467,4003236865,4239837184,4287064835,4286536452,4286536452,4286536452,4286536452,4287461379,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4273589248,2730085632,17303808,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,186126848,3974735104,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293370624,4276198400,4275211521,4291922689,4291922689,3957102336,4293502208,4293502208,3991183360,4191390976,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4226064384,4292845056,4289756418,4203178243,3936525057,4290300672,4290366464,4290366464,4290168576,4287725058,4286932739,4286998531,4288055554,4290300416,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4256812032,2043008512,132096,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,198413568,4075398400,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293304832,4276001024,4275145473,4291922689,4291922689,4291922689,4291922689,3856768000,4293502208,4293502208,4293502208,4159218688,3990393344,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4136462851,4037717505,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4256812032,1905303040,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,214270720,4142507008,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293304832,4292317440,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4191259393,4192773120,4293502208,4293502208,4293502208,4293502208,4293502208,3823213568,4224813825,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3971792896,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4256812032,1906360320,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,298291200,4159284224,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4293304832,4292120064,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3654783488,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4058489344,4208168192,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4206544384,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4273589248,2495007232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,516460544,4192773376,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4276461824,4275211521,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4108360448,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4243170304,3705378048,4275145473,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4189702400,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4273589248,3031877888,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,751341056,4226393344,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4293107200,4275145473,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4275145473,3940917248,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3823476992,4241591041,4291922689,4291922689,4291922689,4291922689,4258302721,4256811776,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3602303232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1036290560,4259947776,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4292909824,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3771828992,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4159218688,4057173248,4291922689,4291922689,4291922689,3452867073,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3971533824,23249664,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1371572224,4276724992,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4292515328,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3856439296,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276724992,3353320192,4291922689,4291922689,3351489280,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1015716096,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1740210944,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436160,4292251904,4291922688,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3940719872,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276659200,4008158208,4275145473,4156278272,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,2696531200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2075295232,4293502208,4293502208,4293502208,4293502208,4293502208,4293304576,4292120064,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4241591041,4192773120,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4226327552,3334986044,3301168426,3619541011,4223257601,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3602500608,34015232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1050624,2330894592,4293502208,4293502208,4292778496,4291988480,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3570831616,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259882240,4107066186,4291550798,4291550798,4291550798,4274773582,4224376141,4190821708,4190821706,4241087560,4039695174,3519535679,3318011439,3502166298,4005219590,4290366464,4290366464,4206480384,528583680,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,714959616,3906639104,4291922688,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4125203456,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259882240,3872184390,4274773326,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4274773582,4224376141,4174044492,2848710217,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1254460672,3133574144,4041712384,4276659200,4293436416,4292383488,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3991249152,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276724992,3604075070,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,1904249905,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,262912,1052739584,2864678912,3773342720,4159284480,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4292449280,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3805449217,4276659200,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3486894129,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,746687537,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2365440,867730432,2596111872,3639125248,4092175616,4276724992,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4275803392,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3755841536,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3638409758,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4006338126,18488586,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,665878272,2343730432,3504907264,4025066752,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4275803648,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4058226432,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3890328083,4241219150,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3284852301,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,531396864,2125166848,3353912320,3941180672,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436416,4275869696,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4241591041,4175930112,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4058230542,4174110029,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,2530928438,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,413824512,1906603008,3169297408,3840517120,4276724992,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293501952,4275935232,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3520499712,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4091849739,4073512011,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,1584956206,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,293098240,1688039168,2983301888,3739788544,4259947776,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293436672,4275935488,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4058160640,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4091980038,4023244870,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,595100205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143483136,1486318080,2816187136,3622413568,4243170560,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276066816,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4058423552,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4159153923,3956331584,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3989560910,2307345,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,368088320,3135874304,4243170304,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276132352,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3872558081,4259881984,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4243105024,3855798075,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3251297614,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,476070912,1197425408,2027131904,3051988224,4058621184,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276132608,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3621623808,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3755330099,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,2496189770,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22427904,610682880,1399212032,2246550528,3236537600,4125730048,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4276198400,4275145473,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4142243840,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3755655720,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,1601206827,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71313920,762137856,1600998656,2481562368,3404244224,4209616128,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259421440,4275145473,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,4258368257,4175930112,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3873290781,4257996366,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,678525739,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,125529600,863261184,1651790336,2448008448,3219760128,3991512064,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259552768,4258368257,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3503656960,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,3957372182,4174044493,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3838500430,20658967,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1380352,528111104,1367037440,2196950016,2951324672,3689456640,4276724992,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259553024,4241591041,4291922689,4291922689,4291922689,4291922689,4291922689,3974340096,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4007833873,4073446476,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3754614350,982955069,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,175658240,1031953152,1904368640,2699601152,3404243968,4092175616,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259553024,4241591041,4291922689,4291922689,4291922689,4291922689,4142309888,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4024806410,4006468168,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,2949438798,288766484,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,596140032,1435066624,2261492480,2917704960,3488195840,4075398400,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259618816,4224813825,4291922689,4291922689,3956378369,4259881984,4293502208,4293502208,4293502208,4293502208,4293502208,4108822533,4006597954,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4140555854,1838786362,4276480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5329152,546268928,1401906688,2276887552,2951324672,3488195584,4025066752,4293502208,4293502208,4293502208,4293502208,4293502208,4293502208,4259684608,4208036608,4291922689,3487340288,4293502208,4293502208,4293502208,4293502208,4293502208,4209550849,3956396349,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3620461902,746490161,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3026432,479619328,1385654784,2292544000,3001591040,3504973056,4008289536,4293502208,4259684608,4208036608,4209418752,4293502208,4293502208,4293502208,4293502208,4276724992,3822243642,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,2781667403,153693711,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,525312,396194048,1352560640,2358930432,3940588800,4142375680,4293502208,4293502208,4293502208,3688220721,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4073446990,1520019258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142990848,4073428992,4258368257,3587740160,4041383168,3671768354,4274773582,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3553287758,429431604,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1068350720,3703292928,4275079937,4291922689,4291922689,3771705886,4257996109,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4274773582,2630407755,51911176,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2091629824,4290366464,4174287104,4291922689,4291922689,4291922689,3889021234,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4023115342,1084206652,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3081551104,4290366464,3989284608,4291922689,4291922689,4291922689,4174416385,4073444421,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4274773582,3570130254,113822051,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3937783296,4290366464,4172925440,4275080193,4291922689,4291922689,4291922689,3704657933,4224441420,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4241219150,2359801408,65792,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4055419648,4290366464,4290366464,4207712000,4291922689,4291922689,4291922689,4291922689,3905860647,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,3956006222,664908093,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4105751296,4290366464,4290366464,3904878848,4291857153,4291922689,4291922689,4291922689,4291922689,3855403322,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4274773582,3368540749,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4172925952,4290366464,4290366464,4290366464,4258238464,4291922689,4291922689,4291922689,4291922689,3922824451,4190886474,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4291550798,4207664718,1907870019,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4239969280,4290366464,4290366464,4290366464,4274561024,4291922689,4291922689,4291922689,4291922689,4291922689,3838748443,4274773325,4291550798,4291550798,4291550798,4291550798,4274773582,3905674574,412263224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41809152,4273589248,4290366464,4290366464,4290366464,4105881600,4291857153,4291922689,4291922689,4291922689,4291922689,4291922689,3821975595,4291550798,4291550798,4291550798,4274773581,3049904973,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,682673920,4290366464,4290366464,4290366464,4290366464,4290366464,4207712000,4291922689,4291922689,4291922689,4291922689,4291922689,4208036609,3972781125,4291550798,4257930571,1524295767,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1353958400,4290366464,4290366464,4290366464,4290366464,4290366464,4257460480,4291922689,4291922689,4291922689,4291922689,4291922689,4291922689,3805254923,4173518121,4273589248,3786984448,59345152,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2024981760,4290366464,4290366464,4290366464,4290366464,4290366464,4273588992,4291792641,4291922689,4291922689,4291922689,4291922689,4291922689,4275080449,4273589248,4290366464,4290366464,3820538880,125800448,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2528364032,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4257849088,4291922689,4291922689,4291922689,4291922689,4291922689,4291727361,4290366464,4290366464,4290366464,4290366464,4038642432,646024960,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2931017216,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4156408064,4291922689,4291922689,4291922689,4291922689,4291922689,4291728129,4290366464,4290366464,4290366464,4290366464,4290366464,4189702912,1839575808,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3333670144,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4258108928,4291922689,4291922689,4291922689,4291922689,4258043393,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4256812032,3333801728,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3736323328,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4274301952,4291922689,4291922689,4291922689,4291922689,4157315329,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3988376320,1385540352,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,256,4105620224,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4240099584,4291857665,4291922689,4291922689,4291922689,4090141952,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4273589248,3451374080,143565312,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65792,4156082944,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4224294656,4291922689,4291922689,4291922689,4056457216,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4156148736,2559548928,51121408,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65792,4156148736,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4223580672,4291922689,4291922689,4291922689,4073169664,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3954821888,2039389184,35002368,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65792,4156082944,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4273588992,4291662593,4291922689,4291922689,4123436544,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3753495296,1821219840,52437504,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65792,4156148480,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4257589760,4291922689,4291922689,4207323136,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3820604416,2173541376,153561856,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65792,4156083200,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4223386880,4291857153,4291922689,4274366720,4290366464,4290366464,4290366464,4290366464,4290366464,4256812032,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3971599360,2475662592,221197568,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65792,4156083200,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4207517184,4291922689,4291079168,4290366464,4290366464,4290366464,4290366464,4290366464,4089039872,2914634752,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,558452992,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4038444800,4290366464,4290366464,4290366464,4290366464,3636055040,4290366464,4290366464,4290366464,4290366464,4290366464,4223710720,4291922689,4291014656,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,2881014528,2797063168,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3485060096,2697472,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3585328384,4290366464,4290366464,4290366464,2981743616,2543890688,4290366464,4290366464,4290366464,4290366464,4290366464,4240034560,4274950401,4240552960,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4172925952,694447616,2763639808,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1685488128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3115500800,4290366464,4290366464,4290366464,694579456,2123341312,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4173703680,4106270720,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3585723136,50924032,2511915776,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4072262656,255804160,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2629027072,4290366464,4290366464,3954822144,85005056,1720687872,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4005348608,4022254592,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4273589248,1554233088,17172224,2092551680,4273589248,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1890897408,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2125710592,4290366464,4290366464,1622854144,0,1318034944,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3652896768,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4072197120,38427904,197376,1622918400,4240034816,4290366464,4290366464,4290366464,4290366464,4290366464,3837381632,203366912,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1270072832,4290366464,4290366464,370875904,0,797875456,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,2881080320,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,2763179008,0,0,1063414784,3988376320,4290366464,4290366464,4290366464,4290366464,1132301312,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,295491584,4273589248,3787049984,17830144,0,193499648,4273589248,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1958333440,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4223257600,457789440,0,0,207973120,3233140224,4273589248,4290366464,2662845440,2039552,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4223257600,1404553728,0,0,0,4139371520,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1387050496,3770272512,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3770272512,65792,0,0,0,2293153792,3636055040,203103744,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4105751296,252974592,0,0,0,3921267456,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1566862592,2360920576,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4273589248,1320930816,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3015363584,855808,0,0,0,3636054784,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1734569216,1179538944,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,105071616,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3283667968,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,1902341376,188169216,4172925952,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,2629422080,263424,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2575865088,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,2070113536,0,2881014528,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4256812032,168759552,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1401394688,4273589248,4290366464,4290366464,4290366464,4290366464,4290366464,2237885696,0,1435999744,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4223257600,1083154176,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3518614272,4290366464,4290366464,4290366464,4290366464,4290366464,2394802432,0,457592320,4273589248,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,4156082944,680698368,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,241792000,3803761408,4290366464,4290366464,4290366464,4290366464,2545404416,0,0,3367619584,4290366464,4290366464,4290366464,4290366464,4290366464,4290366464,3971599360,395550976,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,291861248,3854158592,4290366464,4290366464,4290366464,2679688192,0,0,1638184704,4290366464,4290366464,4290366464,4290366464,4273589248,3702966272,23448064,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,342256896,3921201920,4290366464,4290366464,2830683136,0,0,524240384,4273589248,4290366464,4290366464,4256812032,3098920960,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,291596544,3870870272,4290366464,2981743616,0,0,0,3350842112,4290366464,4189637376,1756018432,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,190801152,3787049728,3115895552,0,0,0,1370669824,3568683008,159419904,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123363840,2629356544,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; 17 | -------------------------------------------------------------------------------- /example/readme.md: -------------------------------------------------------------------------------- 1 | # Google Apps Libraries 2 | 3 | This library wraps the existing Google Apps APIs so they can be used 4 | in Dart. 5 | 6 | A simple example creates a new file with content "Hello from Dart": 7 | 8 | ``` dart 9 | import 'package:google_apps/drive.dart'; 10 | 11 | main() { 12 | DriveApp.createFile("hello.txt", "Hello from Dart"); 13 | } 14 | ``` 15 | 16 | This example must be compiled with the `--csp` flag and uploaded with 17 | apps_script_tools (https://pub.dartlang.org/packages/apps_script_tools): 18 | ``` 19 | `dart2js --csp -o drive.js example/drive.dart`. 20 | apps_script_watch /tmp/drive.js drive 21 | ``` 22 | 23 | It can then be executed by opening the script and running the script. Since 24 | the example doesn't expose any entry point, simply running the `dartPrint` function 25 | is good enough. 26 | 27 | ## Other examples 28 | There are other small examples in the "misc" directory. 29 | A big example can be found in the "tournament" directory. -------------------------------------------------------------------------------- /example/tournament/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | tournament.iml 3 | .idea 4 | .packages 5 | .pub 6 | .dart_tool 7 | build 8 | -------------------------------------------------------------------------------- /example/tournament/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.0 4 | 5 | - Initial release. 6 | -------------------------------------------------------------------------------- /example/tournament/README.md: -------------------------------------------------------------------------------- 1 | # Tournament Google Apps Script 2 | 3 | This is not an official Google product. It is not supported by the Dart team. 4 | 5 | This Example creates a Google Apps Script that can be used to run a 6 | tasting tournament. 7 | 8 | It adds menu items to the Spreadsheet to create brackets, run the event, and 9 | generate final reports. 10 | 11 | See https://docs.google.com/document/d/1d9pvdwD9OixXPOGs2PWC9J5M-YrP3qsJ29xkRg6xkBc 12 | for a detailed explanation on how to use this program. 13 | 14 | ## Features and bugs 15 | 16 | Please file feature requests and bugs at the [issue tracker][tracker]. 17 | 18 | [tracker]: https://github.com/google/dart_google_apps/issues 19 | -------------------------------------------------------------------------------- /example/tournament/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Lint rules and documentation, see http://dart-lang.github.io/linter/lints 2 | linter: 3 | rules: 4 | - hash_and_equals 5 | - iterable_contains_unrelated_type 6 | - list_remove_unrelated_type 7 | - test_types_in_equals 8 | - unrelated_type_equality_checks 9 | - valid_regexps 10 | -------------------------------------------------------------------------------- /example/tournament/bin/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library main; 17 | 18 | import 'package:js/js.dart'; 19 | import 'package:tournament/menu.dart' as tournament_menu; 20 | import 'package:google_apps/google_apps.dart'; 21 | 22 | @JS() 23 | external set onOpen(value); 24 | 25 | void onOpenDart(e, [String prefix]) { 26 | String withPrefix(String funName) { 27 | if (prefix == null) return funName; 28 | return '$prefix.$funName'; 29 | } 30 | 31 | var menu = SpreadsheetApp.getUi().createAddonMenu(); 32 | var dartMenu = tournament_menu.createMenuEntries(); 33 | dartMenu.forEach((caption, value) { 34 | if (caption == null) { 35 | menu = menu.addSeparator(); 36 | } else if (value is Map) { 37 | var subMenu = SpreadsheetApp.getUi().createMenu(caption); 38 | value.forEach((caption, funName) { 39 | subMenu = subMenu.addItem(caption, withPrefix(funName)); 40 | }); 41 | menu = menu.addSubMenu(subMenu); 42 | } else { 43 | assert(value is String); 44 | menu = menu.addItem(caption, withPrefix(value)); 45 | } 46 | }); 47 | menu.addToUi(); 48 | } 49 | 50 | void exportToJs() { 51 | onOpen = allowInterop(onOpenDart); 52 | } 53 | 54 | void main() { 55 | exportToJs(); 56 | tournament_menu.exportToJs(); 57 | } 58 | -------------------------------------------------------------------------------- /example/tournament/lib/menu.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library brackets; 17 | 18 | import 'src/brackets.dart'; 19 | import 'src/cache.dart'; 20 | import 'src/next_round.dart'; 21 | import 'src/reports.dart'; 22 | 23 | import 'package:js/js.dart'; 24 | 25 | @JS() 26 | external set createBracketForSelected(value); 27 | 28 | @JS() 29 | external set createBracketForAll(value); 30 | 31 | @JS() 32 | external set deleteAllBracketSheets(value); 33 | 34 | @JS() 35 | external set prepareNextRound(value); 36 | 37 | @JS() 38 | external set competitorRanking(value); 39 | 40 | @JS() 41 | external set generateFinalReports(value); 42 | 43 | @JS() 44 | external set generateCompetitorReports(value); 45 | 46 | @JS() 47 | external set rebuildCache(value); 48 | 49 | @JS() 50 | external set test(value); 51 | 52 | void exportToJs() { 53 | createBracketForSelected = allowInterop(createBracketForSelectedDart); 54 | createBracketForAll = allowInterop(createBracketForAllDart); 55 | deleteAllBracketSheets = allowInterop(deleteAllBracketSheetsDart); 56 | prepareNextRound = allowInterop(prepareNextRoundDart); 57 | competitorRanking = allowInterop(competitorRankingDart); 58 | rebuildCache = allowInterop(rebuildCacheDart); 59 | generateFinalReports = allowInterop(generateFinalReportsDart); 60 | generateCompetitorReports = allowInterop(generateCompetitorReportsDart); 61 | test = allowInterop(testDart); 62 | } 63 | 64 | Map createMenuEntries() { 65 | return { 66 | 'Setup': { 67 | 'Create Bracket for Selected': 'createBracketForSelected', 68 | 'Create Bracket for All': 'createBracketForAll', 69 | }, 70 | 'Next Round': 'prepareNextRound', 71 | 'Reports': { 72 | 'Ranking': 'competitorRanking', 73 | 'Participants': 'generateFinalReports', 74 | 'Competitors': 'generateCompetitorReports', 75 | }, 76 | 'Maintenance': { 77 | 'Rebuild Cache': 'rebuildCache', 78 | 'Delete all Bracket-sheets': 'deleteAllBracketSheets', 79 | }, 80 | // 'Debug': 'test', 81 | }; 82 | } 83 | 84 | void testDart() {} 85 | -------------------------------------------------------------------------------- /example/tournament/lib/src/brackets.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:math' as math; 16 | 17 | import 'config.dart'; 18 | import 'cache.dart'; 19 | import 'utils.dart'; 20 | 21 | import 'package:google_apps/google_apps.dart'; 22 | 23 | /// Creates (or replaces) a sheet with brackets for the selected participant. 24 | /// 25 | /// This function is primarily used when new participants join at a later moment. 26 | void createBracketForSelectedDart() { 27 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 28 | var sheet = ss.getSheetByName(configSheetName); 29 | // Returns the active cell 30 | var cell = sheet.getActiveCell(); 31 | var participant = cell.getValue(); 32 | createBracketFor(participant); 33 | rebuildCacheDart(); 34 | } 35 | 36 | /// Creates (or replaces) a sheet with brackets for all participants. 37 | void createBracketForAllDart() { 38 | allParticipants.forEach(createBracketFor); 39 | rebuildCacheDart(); 40 | } 41 | 42 | /// Deletes all bracket sheets. 43 | /// 44 | /// This function uses the participant names to find the sheets. As a 45 | /// consequence, removing participants from the list before invoking this 46 | /// function would not remove their bracket sheets. 47 | /// 48 | /// This function is mostly useful for development. 49 | void deleteAllBracketSheetsDart() { 50 | var participants = allParticipants; 51 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 52 | 53 | for (var participant in participants) { 54 | var name = sheetNameFromParticipant(participant); 55 | var sheet = ss.getSheetByName(name); 56 | if (sheet != null) ss.deleteSheet(sheet); 57 | } 58 | var cacheSheet = ss.getSheetByName(cacheSheetName); 59 | if (cacheSheet != null) ss.deleteSheet(cacheSheet); 60 | } 61 | 62 | void createBracketFor(String participant) { 63 | var participants = allParticipants; 64 | if (!participants.contains(participant)) { 65 | error('Not a valid participant: $participant'); 66 | } 67 | 68 | var sheet = _createParticipantSheet(participant); 69 | _fillWithBrackets(sheet, participant); 70 | } 71 | 72 | /// The location of the name of the participant, used to find the corresponding 73 | /// battle-results in the results-sheet. 74 | const String participantLocation = r'$B$1'; 75 | 76 | /// Creates a fresh sheet for the given participant. 77 | /// 78 | /// If a sheet already exists overwrites it. 79 | Sheet _createParticipantSheet(String participant) { 80 | var sheetName = sheetNameFromParticipant(participant); 81 | var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 82 | var sheet = activeSpreadsheet.getSheetByName(sheetName); 83 | 84 | // Remove it first, if it already exists. 85 | if (sheet != null) activeSpreadsheet.deleteSheet(sheet); 86 | 87 | sheet = activeSpreadsheet.insertSheet(); 88 | sheet.setName(sheetName); 89 | sheet.getRange(participantLocation).offset(0, -1).setValue('Participant:'); 90 | sheet.getRange(participantLocation).setValue(participant); 91 | return sheet; 92 | } 93 | 94 | /// Fills the given sheet with a new bracket. 95 | /// 96 | /// Depending on the [isDoubleElimination] the generated sheets will be 97 | /// double elimination or not. 98 | /// 99 | /// If only some participants want to do double-elimination, then it is safe 100 | /// to delete the double-elimination entries from the sheet *and* then to 101 | /// rebuild the cache. 102 | /// 103 | /// This code was inspired by 104 | /// [https://developers.google.com/apps-script/articles/bracket_maker] 105 | void _fillWithBrackets(Sheet sheet, String participant) { 106 | var competitorNames = allCompetitors; 107 | var competitorCount = competitorNames.length; 108 | var competitorsRange = competitorsConfig.range; 109 | var competitorsColumnANotation = 110 | String.fromCharCode('A'.codeUnitAt(0) + competitorsRange.getColumn() - 1); 111 | var competitorsRow = competitorsRange.getRow(); 112 | 113 | if (competitorCount < 3) { 114 | error('You must have at least 3 competitors.'); 115 | } 116 | 117 | var competitorIndices = 118 | List.generate(competitorCount, (x) => x).toList(); 119 | 120 | var upperPower = (math.log(competitorCount) / math.ln2).ceil(); 121 | 122 | // Find out what is the number that is a power of 2 and lower than 123 | // numCompetitors. 124 | var withByesCount = math.pow(2, upperPower); 125 | 126 | competitorIndices.shuffle(); 127 | 128 | if (competitorCount < withByesCount) { 129 | // Fill with Byes. 130 | var missing = withByesCount - competitorCount; 131 | var newCompetitorIndices = []; 132 | 133 | var remainingCompetitors = competitorCount; 134 | 135 | // We are avoiding having two pairings containing a bye next to each 136 | // other, as the byes would meet in the loser-bracket. 137 | var lastContainedBye = false; 138 | for (var i = 0; i < withByesCount ~/ 2; i++) { 139 | if (!lastContainedBye && missing > 0 || missing == remainingCompetitors) { 140 | newCompetitorIndices.add(-1); 141 | newCompetitorIndices.add(competitorIndices[--remainingCompetitors]); 142 | missing--; 143 | lastContainedBye = true; 144 | } else { 145 | newCompetitorIndices.add(competitorIndices[--remainingCompetitors]); 146 | newCompetitorIndices.add(competitorIndices[--remainingCompetitors]); 147 | lastContainedBye = false; 148 | } 149 | } 150 | competitorIndices = newCompetitorIndices.reversed.toList(); 151 | } 152 | 153 | // Enter the competitors for the 1st round 154 | var cells = <_SheetCell>[]; 155 | for (var i = 0; i < competitorIndices.length; i++) { 156 | var competitorIndex = competitorIndices[i]; 157 | String formula; 158 | if (competitorIndex != -1) { 159 | formula = '=$configSheetName!' 160 | '\$$competitorsColumnANotation' 161 | '\$${competitorsRow + competitorIndex}'; 162 | } 163 | var row = 3 + i * 2; 164 | var cell = _SheetCell(sheet, row, 1, 165 | isBye: competitorIndex == -1, formula: formula); 166 | cells.add(cell); 167 | } 168 | 169 | // Connects the given list of cells and connects them pairwise. 170 | // 171 | // Returns two lists: one with the cells of all winners, and one 172 | // for all the losers. The loser cells don't have any position yet, and 173 | // can be discarded. For example, the loser-bracket doesn't care for 174 | // any losers anymore. Similarly, losers are always discarded, if there is 175 | // no double-elimination. 176 | List> connectAll(List<_SheetCell> cells, 177 | {bool isWinnerBracket}) { 178 | var winners = <_SheetCell>[]; 179 | var losers = <_SheetCell>[]; 180 | for (var i = 0; i < cells.length; i += 2) { 181 | var cellA = cells[i]; 182 | var cellB = cells[i + 1]; 183 | var winnerLoser = 184 | cellA.connectTo(cellB, isWinnerBracket: isWinnerBracket); 185 | winners.add(winnerLoser[0]); 186 | losers.add(winnerLoser[1]); 187 | } 188 | return [winners, losers]; 189 | } 190 | 191 | var winners = cells; 192 | var allLosers = >[]; 193 | while (winners.length > 1) { 194 | var winnersLosers = connectAll(winners, isWinnerBracket: true); 195 | winners = winnersLosers[0]; 196 | allLosers.add(winnersLosers[1]); 197 | } 198 | 199 | if (!isDoubleElimination) return; 200 | 201 | // 2 for the header, and 3 to have some space. 202 | var loserPos = competitorIndices.length * 2 + 6; 203 | 204 | var losersIndex = 0; 205 | var firstRoundLosers = allLosers[losersIndex++]; 206 | // Sets the positions of the losers. 207 | for (var i = 0; i < firstRoundLosers.length; i += 2) { 208 | // Add 3 for the incoming loser from phase 2. 209 | loserPos += 3; 210 | 211 | firstRoundLosers[i] = firstRoundLosers[i].withPos(loserPos, 1); 212 | loserPos += 2; 213 | 214 | firstRoundLosers[i + 1] = firstRoundLosers[i + 1].withPos(loserPos, 1); 215 | loserPos += 2; 216 | } 217 | 218 | var shouldDoubleUp = false; 219 | var shouldInvertIncoming = true; 220 | var losers = firstRoundLosers; 221 | var previousLosers; 222 | while (losers.length > 1 || shouldDoubleUp) { 223 | if (shouldDoubleUp) { 224 | var newLosers = <_SheetCell>[]; 225 | var incoming = allLosers[losersIndex++]; 226 | if (shouldInvertIncoming) { 227 | incoming = incoming.reversed.toList(); 228 | } 229 | shouldInvertIncoming = !shouldInvertIncoming; 230 | for (var i = 0; i < losers.length; i++) { 231 | // We use the positions of the previous phase to compute the 232 | // position of the incoming losers. 233 | var previousLoser = previousLosers[i * 2]; 234 | // Introduce a new competitor (coming from the winner's bracket). 235 | var incomingCell = incoming[i] 236 | .withPos(previousLoser.row - 2, previousLoser.column + 2); 237 | newLosers.add(incomingCell); 238 | newLosers.add(losers[i]); 239 | } 240 | losers = newLosers; 241 | } 242 | shouldDoubleUp = !shouldDoubleUp; 243 | // Keep track of the previous losers to make it easier to compute the 244 | // position of incoming losers. 245 | previousLosers = losers; 246 | var winnersLosers = connectAll(losers, isWinnerBracket: false); 247 | // The winners advance. The losers are out now. 248 | losers = winnersLosers[0]; 249 | } 250 | 251 | // Create a final pairing between the winner's and loser's bracket winners. 252 | var winnersWinner = winners[0]; 253 | var losersWinner = losers[0]; 254 | var column = winnersWinner.column + 2; 255 | var winnersRef = winnersWinner._a1Notation; 256 | var losersRef = losersWinner._a1Notation; 257 | var cellA = _SheetCell(sheet, 3, column, formula: '=$winnersRef'); 258 | var cellB = _SheetCell(sheet, 5, column, formula: '=$losersRef'); 259 | cellA.connectTo(cellB, isWinnerBracket: true); 260 | } 261 | 262 | /// A cell in the participant's sheet. 263 | /// 264 | /// When building the bracket, we use this class to refer to already 265 | /// generated cells. 266 | class _SheetCell { 267 | final Sheet sheet; 268 | final int row; 269 | final int column; 270 | 271 | /// Whether this cell is a bye. 272 | final bool isBye; 273 | 274 | /// A position-independent formula (which means that we can write at any 275 | /// place in the sheet). 276 | final String formula; 277 | 278 | _SheetCell(this.sheet, this.row, this.column, 279 | {this.formula, this.isBye = false}); 280 | 281 | _SheetCell withPos(int row, int column) { 282 | return _SheetCell(sheet, row, column, formula: formula, isBye: isBye); 283 | } 284 | 285 | Range get _range => sheet.getRange(row, column); 286 | 287 | String get _a1Notation => _range.getA1Notation(); 288 | 289 | /// Writes the cells data into the sheet. 290 | void markActive() { 291 | _range.setFormula(formula); 292 | _range.setBackground(competitorColor); 293 | } 294 | 295 | /// Connects this cell to [cell2]. 296 | /// 297 | /// Returns two cells. 298 | /// The first entry is the winner-cell. The second, the loser cell *without* 299 | /// the correct location. 300 | /// 301 | /// The loser cell is only relevant for double-elimination tournaments. 302 | List<_SheetCell> connectTo(_SheetCell cell2, {bool isWinnerBracket}) { 303 | assert(column == cell2.column); 304 | var newRow = (row + cell2.row) ~/ 2; 305 | if (isBye && cell2.isBye) { 306 | var winner = _SheetCell(sheet, newRow, column + 2, isBye: true); 307 | var loser = _SheetCell(sheet, -1, -1, isBye: true); 308 | return [winner, loser]; 309 | } else if (isBye || cell2.isBye) { 310 | var dataCell = isBye ? cell2 : this; 311 | var winner = 312 | _SheetCell(sheet, newRow, column + 2, formula: dataCell.formula); 313 | var loser = _SheetCell(sheet, -1, -1, isBye: true); 314 | return [winner, loser]; 315 | } else { 316 | markActive(); 317 | cell2.markActive(); 318 | var fromRow = row; 319 | var toRow = cell2.row; 320 | var connector = 321 | sheet.getRange(fromRow, column + 1, toRow - fromRow + 1, 1); 322 | sheet.setColumnWidth(connector.getColumn(), connectorWidth); 323 | connector.setBackground(connectorColor); 324 | var outcomeCell = sheet.getRange(newRow, column + 1); 325 | outcomeCell.setFormula(_outcomeFormula); 326 | var roundCell = sheet.getRange(newRow, column); 327 | roundCell 328 | .setBackground(isWinnerBracket ? winnerRoundColor : loserRoundColor); 329 | var winnerFormula = 330 | computeNextRoundCompetitorFormula(this, cell2, outcomeCell); 331 | var winner = 332 | _SheetCell(sheet, newRow, column + 2, formula: winnerFormula); 333 | winner.markActive(); 334 | var loserFormula = computeNextRoundCompetitorFormula( 335 | this, cell2, outcomeCell, 336 | invert: true); 337 | var loser = _SheetCell(sheet, newRow, column + 2, formula: loserFormula); 338 | return [winner, loser]; 339 | } 340 | } 341 | 342 | /// Creates the formula that queries the results sheet for the result 343 | /// of this battle. 344 | /// 345 | /// If a result is available writes "A" or "B" into the cell. 346 | String get _outcomeFormula { 347 | var participant = participantLocation; 348 | var query = '"select $resultsWinColumn ' 349 | '''where $resultsParticipantColumn = '"&$participant&"' and ''' 350 | '$resultsRoundColumn = "&INDIRECT("R[0]C[-1]", false)'; 351 | return '=IFERROR(QUERY($resultsQualifiedRange, $query))'; 352 | } 353 | 354 | /// Computes the formula that finds the competitor for the next round, using 355 | /// the found outcome (see [_outcomeFormula]). 356 | String computeNextRoundCompetitorFormula( 357 | _SheetCell cellA, _SheetCell cellB, Range outcomeCell, 358 | {bool invert = false}) { 359 | var outcome = outcomeCell.getA1Notation(); 360 | var a = cellA._a1Notation; 361 | var b = cellB._a1Notation; 362 | 363 | if (invert) { 364 | return '=IFS($outcome = "", "", $outcome = "A", $b, $outcome = "B", $a)'; 365 | } 366 | return '=IFS($outcome = "", "", $outcome = "A", $a, $outcome = "B", $b)'; 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /example/tournament/lib/src/cache.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'brackets.dart' show participantLocation; 16 | import 'config.dart'; 17 | import 'tournament.dart'; 18 | import 'utils.dart'; 19 | 20 | import 'package:google_apps/google_apps.dart'; 21 | 22 | /// (Re)Builds the cache. 23 | /// 24 | /// Runtime calls to the Google Apps service are very expensive, and it is 25 | /// hence advisable to limit the number of API calls. 26 | /// 27 | /// The cache sheet groups all interesting properties of the individual 28 | /// tournament sheets (of each participant) into one sheet. This way, many 29 | /// operation scan be performed without even looking at the participant sheets. 30 | /// 31 | /// The cache is built based on the *colors* of the tournament sheets. 32 | /// Conceptually it runs through the tournament sheet and finds two 33 | /// competitors by detecting their colors. The corresponding round-number (and 34 | /// results) are similarly found by detecting cells with the correct colors. 35 | void rebuildCacheDart() { 36 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 37 | 38 | var cacheSheet = spreadsheet.getSheetByName(cacheSheetName); 39 | if (cacheSheet != null) spreadsheet.deleteSheet(cacheSheet); 40 | cacheSheet = spreadsheet.insertSheet(); 41 | cacheSheet.setName(cacheSheetName); 42 | cacheSheet.hideSheet(); 43 | 44 | var sheets = allParticipants.map((participant) { 45 | var name = sheetNameFromParticipant(participant); 46 | return spreadsheet.getSheetByName(name); 47 | }).toList(); 48 | 49 | var formulas = >[]; 50 | 51 | for (var sheet in sheets) { 52 | if (sheet == null) continue; // Not all sheets have been built yet. 53 | var range = sheet.getDataRange(); 54 | var rowCount = range.getNumRows(); 55 | var columnCount = range.getNumColumns(); 56 | 57 | var colors = range.getBackgrounds(); 58 | 59 | var sheetName = sheet.getName(); 60 | 61 | String ref(int row, int column) { 62 | var alphaColumn = String.fromCharCode('A'.codeUnitAt(0) + column); 63 | return '=$sheetName!\$$alphaColumn\$${row + 1}'; 64 | } 65 | 66 | var participantNameReference = '=$sheetName!$participantLocation'; 67 | 68 | for (var j = 0; j < columnCount; j += 2) { 69 | String competitorARef; 70 | String roundRef; 71 | int roundRow; 72 | int roundColumn; 73 | BattleType type; 74 | String resultRef; 75 | for (var i = 0; i < rowCount; i++) { 76 | var color = colors[i][j]; 77 | switch (color) { 78 | case competitorColor: 79 | if (roundRef == null) { 80 | // This might overwrite the competitorA, if it was referring to the 81 | // tournament winner cell. That's fine, since the winner isn't 82 | // paired anymore. 83 | competitorARef = ref(i, j); 84 | } else { 85 | var competitorBRef = ref(i, j); 86 | formulas.add([ 87 | participantNameReference, 88 | competitorARef, 89 | competitorBRef, 90 | '"${battleTypeToString[type]}"', 91 | resultRef, 92 | roundRef, 93 | '$roundRow', 94 | '$roundColumn' 95 | ]); 96 | roundRef = null; 97 | } 98 | break; 99 | case winnerRoundColor: 100 | case loserRoundColor: 101 | case bonusRoundColor: 102 | if (color == winnerRoundColor) type = BattleType.winner; 103 | if (color == loserRoundColor) type = BattleType.loser; 104 | if (color == bonusRoundColor) type = BattleType.bonus; 105 | 106 | roundRef = ref(i, j); 107 | roundRow = i + 1; 108 | roundColumn = j + 1; 109 | resultRef = ref(i, j + 1); 110 | break; 111 | } 112 | } 113 | } 114 | } 115 | var maxRows = cacheSheet.getMaxRows(); 116 | if (maxRows < formulas.length) { 117 | cacheSheet.insertRows(maxRows - 1, (formulas.length - maxRows)); 118 | } 119 | var range = cacheSheet.getRange(1, 1, formulas.length, formulas.first.length); 120 | range.setFormulas(formulas); 121 | } 122 | -------------------------------------------------------------------------------- /example/tournament/lib/src/config.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'utils.dart'; 16 | import 'package:google_apps/google_apps.dart'; 17 | 18 | /// A list of all participants. 19 | final List allParticipants = participantsConfig.read(); 20 | 21 | /// A list of boolean flags whether participants are active. 22 | /// 23 | /// This returns the boolean flags and must be matched up with 24 | /// [allParticipants]. Use [activeParticipants] to get a list of filtered 25 | /// participants. 26 | /// 27 | /// No checks are done to ensure that this list has the same length as the 28 | /// number of [allParticipants]. 29 | final List allActives = activesConfig.read(); 30 | 31 | /// A list of active participants. 32 | /// 33 | /// This combines [allParticipants] and [allActives] to filter out participants 34 | /// that are not active. 35 | final List activeParticipants = () { 36 | var participants = allParticipants; 37 | var activeFlags = allActives; 38 | if (participants.length != activeFlags.length) { 39 | error('Participants and Actives have different lengths.'); 40 | } 41 | var activeParticipants = []; 42 | for (var i = 0; i < participants.length; i++) { 43 | if (boolify(activeFlags[i])) { 44 | activeParticipants.add(participants[i]); 45 | } 46 | } 47 | return activeParticipants; 48 | }(); 49 | 50 | final Map urlMapping = () { 51 | var participants = allParticipants; 52 | var urls = urlConfig.read(); 53 | var result = {}; 54 | for (var i = 0; i < participants.length; i++) { 55 | result[participants[i]] = urls[i]; 56 | } 57 | return result; 58 | }(); 59 | 60 | /// A list of all competitors. 61 | final List allCompetitors = competitorsConfig.read(); 62 | 63 | /// A list of boolean flags whether competitors are in stock. 64 | /// 65 | /// This returns the boolean flags and must be matched up with [allCompetitors]. 66 | /// Use [inStockCompetitors] to get a filtered set of the competitors. 67 | final List allInStocks = inStockConfig.read(); 68 | 69 | /// A set of competitors that are in stock. 70 | /// 71 | /// This combines [allCompetitors] and [allInStock]. 72 | final Set inStockCompetitors = () { 73 | var competitors = allCompetitors; 74 | var inStock = allInStocks; 75 | if (competitors.length != inStock.length) { 76 | error('Competitors and In-stock have different lengths.'); 77 | } 78 | 79 | var result = {}; 80 | for (var i = 0; i < competitors.length; i++) { 81 | if (boolify(inStock[i])) result.add(competitors[i]); 82 | } 83 | return result; 84 | }(); 85 | 86 | /// Whether two next-round pages are printed on one paper sheet. 87 | /// 88 | /// See [isPrinting2PagesPerSheetConfig]. 89 | final bool isPrinting2PagesPerSheet = 90 | boolify(isPrinting2PagesPerSheetConfig.read()); 91 | 92 | /// Whether the tournament for each participant is double-elimination. 93 | final bool isDoubleElimination = boolify(isDoubleEliminationConfig.read()); 94 | 95 | /// The name of the sheet that contains all configurations. 96 | const String configSheetName = 'Config'; 97 | 98 | /// The name of the sheet that contains all results. 99 | /// 100 | /// Usually, the results are filled in through a form, but they can also be 101 | /// filled in by hand. See below ([resultsParticipantColumn], ...) to see how 102 | /// the results sheet must be filled. 103 | const String resultsSheetName = 'Results'; 104 | 105 | /// The name of the sheet that contains the cache. 106 | /// 107 | /// When there are many participants and competitors, running the script can 108 | /// take a significant time. The cache-sheet speeds up the computations 109 | /// significantly because it reduces the number of API calls. 110 | const String cacheSheetName = 'Cache'; 111 | 112 | /// An list-entry in the config sheet. 113 | /// 114 | /// Config entries are found by their header. 115 | /// 116 | /// The library searches the header in the first column of the config sheet and 117 | /// returns all entries that follow the header. 118 | class ListConfigEntry { 119 | final String header; 120 | Range _range; 121 | List _values; 122 | 123 | ListConfigEntry(this.header); 124 | 125 | /// Returns the range for this config. 126 | Range get range { 127 | if (_range == null) read(); 128 | return _range; 129 | } 130 | 131 | /// Returns the entries for this config. 132 | List read() { 133 | if (_values != null) return _values; 134 | 135 | var configSheet = _configSheet; 136 | var lastRow = configSheet.getLastRow(); 137 | var lastColumn = configSheet.getLastColumn(); 138 | var configValues = 139 | configSheet.getRange(1, 1, lastRow, lastColumn).getDisplayValues(); 140 | 141 | var nextIsHeader = true; 142 | for (var i = 0; i < lastRow; i++) { 143 | if (nextIsHeader && configValues[i][0] == header) { 144 | var result = []; 145 | for (var j = i + 1; j < lastRow; j++) { 146 | if (configValues[j][0] == '') break; 147 | result.add(configValues[j][0]); 148 | } 149 | _values = result; 150 | _range = configSheet.getRange(i + 2, 1, result.length, 1); 151 | return _values; 152 | } 153 | nextIsHeader = (configValues[i][0] == ''); 154 | } 155 | error("Couldn't find ${header}"); 156 | return []; 157 | } 158 | } 159 | 160 | /// An secondary entry in the config sheet. 161 | /// 162 | /// Secondary entries are in a column next to the primary configuration. 163 | /// They are identified by a primary configuration, and a secondary header. 164 | /// 165 | /// The library searches the primary configuration, and then finds the header in 166 | /// the same row. 167 | class SecondaryConfigEntry { 168 | final ListConfigEntry primary; 169 | final String header; 170 | 171 | List _values; 172 | 173 | SecondaryConfigEntry(this.primary, this.header); 174 | 175 | /// Returns the range for this config. 176 | List read() { 177 | if (_values != null) return _values; 178 | 179 | var primaryRange = primary.range; 180 | // We search at most 10 columns. 181 | var headerRow = primaryRange.offset(-1, 0, 1, 10); 182 | var headerValuesRow = headerRow.getDisplayValues()[0]; 183 | var secondaryOffset = -1; 184 | for (var i = 0; i < headerValuesRow.length; i++) { 185 | if (headerValuesRow[i] == header) { 186 | secondaryOffset = i; 187 | break; 188 | } 189 | } 190 | if (secondaryOffset == -1) error("Couldn't find ${header}"); 191 | 192 | var secondaryRange = primaryRange.offset(0, secondaryOffset); 193 | var result = []; 194 | var displayValues = secondaryRange.getDisplayValues(); 195 | for (var i = 0; i < displayValues.length; i++) { 196 | result.add(displayValues[i][0]); 197 | } 198 | _values = result; 199 | return result; 200 | } 201 | } 202 | 203 | /// A single config entry in the config sheet. 204 | /// 205 | /// Single entries are in a section and have just one value. 206 | /// They are identified by a list configuration (the section), and a header 207 | /// (in the section). 208 | /// 209 | /// The library searches the primary configuration, and then finds the header in 210 | /// the same column. The returned value is the entry next to the found header. 211 | class SingleConfigEntry { 212 | final ListConfigEntry section; 213 | final String header; 214 | String _value; 215 | 216 | SingleConfigEntry(this.section, this.header); 217 | 218 | String read() { 219 | if (_value != null) return _value; 220 | var sectionValues = section.read(); 221 | var sectionRange = section.range; 222 | for (var i = 0; i < sectionValues.length; i++) { 223 | if (sectionValues[i] == header) { 224 | _value = sectionRange.offset(i, 1, 1, 1).getDisplayValue(); 225 | return _value; 226 | } 227 | } 228 | error("Couldn't find ${header}"); 229 | return ''; 230 | } 231 | } 232 | 233 | /// The configuration for all participating "judges". 234 | /// 235 | /// These are the humans deciding which of the competitors win each sampling. 236 | final participantsConfig = ListConfigEntry('Participants'); 237 | 238 | /// The configuration, whether a given participant is active. 239 | /// 240 | /// When participants are (temporarily) out, then this flag can be set to false. 241 | final activesConfig = SecondaryConfigEntry(participantsConfig, 'Active'); 242 | 243 | /// The URL that should be printed on the battle sheet. 244 | /// 245 | /// Each participant may have a different one. For example, when the URL points 246 | /// to a prefilled form sheet. 247 | final urlConfig = SecondaryConfigEntry(participantsConfig, 'URL'); 248 | 249 | /// The configuration for the competitors. 250 | /// 251 | /// These are the samples (chocolate, wine, ...) that are judged. 252 | final competitorsConfig = ListConfigEntry('Competitors'); 253 | 254 | /// The configuration, whether a competitor is in stock. 255 | /// 256 | /// Since the amount of samples is dependent on the choices of the participants 257 | /// it can happen that not enough competitors have been bought/stocked. 258 | /// 259 | /// When this configuration is set to false, the script will not create 260 | /// pairings that require the out-of-stock competitor. 261 | final inStockConfig = SecondaryConfigEntry(competitorsConfig, 'In Stock'); 262 | 263 | final miscSection = ListConfigEntry('Misc'); 264 | 265 | /// The configuration whether two next-round pages are printed on one sheet. 266 | /// 267 | /// This reorders the order in which the pages are printed. 268 | /// 269 | /// Print-outs are done in the order of the sheets. This allows the game-master 270 | /// to organize the samples in such a way that the distribution is most 271 | /// efficient. 272 | /// 273 | /// Often, it's not necessary to have a full A4 (or letter) paper used for the 274 | /// print-out, but half of it is enough. When printing two pages per paper-sheet 275 | /// one can then cut all papers in the middle. When this flag is set to true, 276 | /// it is then possible to put the left side (of the cut pages) on top of the 277 | /// right side to get back the original order. 278 | final isPrinting2PagesPerSheetConfig = 279 | SingleConfigEntry(miscSection, '2 Sheets per Page'); 280 | 281 | /// The configuration whether two next-round pages are printed on one sheet. 282 | /// 283 | /// This reorders the order in which the pages are printed. 284 | /// 285 | /// Print-outs are done in the order of the sheets. This allows the game-master 286 | /// to organize the samples in such a way that the distribution is most 287 | /// efficient. 288 | /// 289 | /// Often, it's not necessary to have a full A4 (or letter) paper used for the 290 | /// print-out, but half of it is enough. When printing two pages per paper-sheet 291 | /// one can then cut all papers in the middle. When this flag is set to true, 292 | /// it is then possible to put the left side (of the cut pages) on top of the 293 | /// right side to get back the original order. 294 | final isDoubleEliminationConfig = 295 | SingleConfigEntry(miscSection, 'Double Elimination'); 296 | 297 | /// The column of the results sheet that contains the participant. 298 | const resultsParticipantColumn = 'B'; 299 | 300 | /// The column of the results sheet that contains the round number. 301 | const resultsRoundColumn = 'C'; 302 | 303 | /// The column of the results sheet that contains the winner of the battle. 304 | const resultsWinColumn = 'D'; 305 | 306 | /// The column of the results sheet that contains the comment for competitor A. 307 | const resultsCommentsAColumn = 'E'; 308 | 309 | /// The column of the results sheet that contains the comment for competitor B. 310 | const resultsCommentsBColumn = 'F'; 311 | 312 | /// The range of results. 313 | /// 314 | /// Just skips the first row, since it contains the headers. 315 | const resultsRange = r'$A$2:$D$9999'; 316 | 317 | /// Fully qualified range that includes the sheet-name. 318 | const resultsQualifiedRange = '$resultsSheetName!$resultsRange'; 319 | 320 | // We are using "#ffffff' notation for colors, because this is, what 321 | // 'getBackground` returns. 322 | 323 | /// The color for competitors 324 | const competitorColor = '#ffff00'; // yellow 325 | /// The color of connectors. 326 | const connectorColor = '#40C040'; // green 327 | /// The color of the round/battle in the winner bracket. 328 | const winnerRoundColor = '#e8f8f5'; 329 | 330 | /// The color of the round/battle in the loser bracket. 331 | const loserRoundColor = '#ebf5fb'; 332 | 333 | /// The color of the round/battle for bonus rounds. 334 | const bonusRoundColor = '#fce5cd'; 335 | 336 | /// Width of the connector column. 337 | const connectorWidth = 15; 338 | 339 | /// The config sheet. 340 | final Sheet _configSheet = 341 | SpreadsheetApp.getActiveSpreadsheet().getSheetByName(configSheetName); 342 | 343 | bool boolify(dynamic value) { 344 | if (value == null) return false; 345 | if (value is bool) return value; 346 | if (value == 'TRUE') return true; 347 | return false; 348 | } 349 | -------------------------------------------------------------------------------- /example/tournament/lib/src/next_round.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:convert'; 16 | 17 | import 'config.dart'; 18 | import 'tournament.dart'; 19 | import 'utils.dart'; 20 | 21 | import 'package:google_apps/google_apps.dart'; 22 | 23 | /// Computes the percentage of how often 'A' was picked for the given pairings. 24 | /// 25 | /// Returns -1, if there is no completed battle. 26 | /// 27 | /// This stat is only useful if participants taste the competitors in a 28 | /// specific order ("A" first). 29 | int computeAPercentage(List pairings) { 30 | var selectedACount = 0; 31 | var completedPairingsCount = 0; 32 | for (var pairing in pairings) { 33 | var result = pairing.result; 34 | if (result != null) { 35 | completedPairingsCount++; 36 | if (result == 'A') selectedACount++; 37 | } 38 | } 39 | if (completedPairingsCount == 0) return -1; 40 | return selectedACount * 100 ~/ completedPairingsCount; 41 | } 42 | 43 | /// Computes the percentage of how often 'A' was picked for all pairings. 44 | /// 45 | /// Returns -1, if there is no completed battle. 46 | /// 47 | /// This stat is only useful if participants taste the competitors in a 48 | /// specific order ("A" first). 49 | int computeTotalAPercentage(List> allPairings) { 50 | var selectedACount = 0; 51 | var completedPairingsCount = 0; 52 | for (var pairings in allPairings) { 53 | for (var pairing in pairings) { 54 | var result = pairing.result; 55 | if (result != null) { 56 | completedPairingsCount++; 57 | if (result == 'A') selectedACount++; 58 | } 59 | } 60 | } 61 | if (completedPairingsCount == 0) return -1; 62 | return selectedACount * 100 ~/ completedPairingsCount; 63 | } 64 | 65 | /// Computes the ids of participants for the selection-buttons. 66 | /// 67 | /// When preparing a new round, we allow the administrator to select 68 | /// participants that are behind, or that do double-elimination. 69 | /// 70 | /// We compute these ids here and return them in a list: 71 | /// - the first entry of the returned list contains the participants that are 72 | /// behind. That is, participants that have played fewer rounds than other 73 | /// participants. 74 | /// - the second entry contains the participants that play double-elimination. 75 | /// When not all participants do the same 76 | List> computeSelections(List states) { 77 | var behindParticipants = {}; 78 | var doubleEliminationParticipants = []; 79 | 80 | var maxWinnerRounds = 0; 81 | var maxLoserRounds = 0; 82 | for (var i = 0; i < states.length; i++) { 83 | var state = states[i]; 84 | if (state.playedWinnersCount < maxWinnerRounds || 85 | state.playedLosersCount < maxLoserRounds) { 86 | behindParticipants.add(i); 87 | } 88 | 89 | if (state.playedWinnersCount > maxWinnerRounds) { 90 | maxWinnerRounds = state.playedWinnersCount; 91 | // All participants so far are behind. 92 | for (var j = 0; j < i; j++) { 93 | behindParticipants.add(j); 94 | } 95 | if (maxLoserRounds < state.playedLosersCount) { 96 | maxLoserRounds = state.playedLosersCount; 97 | } 98 | } 99 | if (state.playedLosersCount > maxLoserRounds) { 100 | maxLoserRounds = state.playedLosersCount; 101 | // Insert all existing doubleEliminationParticipants in the behind list. 102 | behindParticipants.addAll(doubleEliminationParticipants); 103 | } 104 | if (state.hasLoserBracket) { 105 | doubleEliminationParticipants.add(i); 106 | } 107 | } 108 | return [behindParticipants.toList()..sort(), doubleEliminationParticipants]; 109 | } 110 | 111 | /// Prepares the next round. 112 | /// 113 | /// This function is called in two different ways: 114 | /// 1. from the menu, which opens a side-bar. 115 | /// 2. as a callback from the side-bar. 116 | void prepareNextRoundDart([List userData]) { 117 | if (userData == null) { 118 | // Menu click. 119 | openNextRoundWindow(); 120 | } else { 121 | // Callback from the opened window. 122 | createNextRoundDocument(userData); 123 | } 124 | } 125 | 126 | /// Opens a side-bar with the participants. 127 | /// 128 | /// The administrator can then select the active participants. The callback 129 | /// of the window then invokes [prepareNextRoundDart] again, which then 130 | /// creates the documents for the next round. 131 | void openNextRoundWindow() { 132 | var participants = activeParticipants; 133 | var urls = urlMapping; 134 | 135 | var allPairings = computeAllPairings(); 136 | 137 | var states = participants.map((participant) { 138 | var pairings = allPairings[participant]; 139 | var isActive = true; 140 | return State(participant, isActive, urls[participant], pairings); 141 | }).toList(); 142 | 143 | var totalPlayed = 0; 144 | var totalASelected = 0; 145 | for (var state in states) { 146 | totalPlayed += state.finishedCount; 147 | totalASelected += state.aSelectionCount; 148 | } 149 | var totalAPercentage = -1; 150 | if (totalPlayed != 0) { 151 | totalAPercentage = 100 * totalASelected ~/ totalPlayed; 152 | } 153 | 154 | var selections = computeSelections(states); 155 | 156 | var behindSelection = selections[0]; 157 | var doubleEliminationSelection = selections[1]; 158 | 159 | var html = StringBuffer(); 160 | html.write(''); 161 | html.write("" 164 | '
'); 165 | if (doubleEliminationSelection.isNotEmpty) { 166 | html.write("" 169 | '
'); 170 | } 171 | html.write("" 174 | '
'); 175 | html.write("" 178 | '
'); 179 | 180 | for (var i = 0; i < states.length; i++) { 181 | var state = states[i]; 182 | var isEnabled = state.isActive && 183 | (state.availableWinnerPairings.isNotEmpty || 184 | state.availableLoserPairings.isNotEmpty); 185 | var participant = state.participant; 186 | var participantName = state.participantName; 187 | var checkBoxValue = json.encode(participant); 188 | var playedWinnersCount = state.playedWinnersCount; 189 | var playedLosersCount = state.playedLosersCount; 190 | var playedCount = playedWinnersCount + playedLosersCount; 191 | var hasLoserBracket = state.hasLoserBracket; 192 | 193 | var countSuffix = '$playedCount'; 194 | if (hasLoserBracket) { 195 | countSuffix += ' ($playedWinnersCount | $playedLosersCount)'; 196 | } 197 | var missing = state.missingResults; 198 | var missingSuffix = missing.isEmpty ? '' : ' - [${missing.join(', ')}]'; 199 | var suffix = '$countSuffix$missingSuffix'; 200 | var escapedParticipantName = htmlEscape.convert(participantName); 201 | var escapedCheckBoxValue = htmlEscape.convert(checkBoxValue); 202 | html.write('
" 207 | '' 208 | '
'); 209 | } 210 | html.write( 211 | ""); 212 | html.write(""""""); 257 | 258 | html.write(''); 259 | var userInterface = HtmlService.createHtmlOutput(html.toString()); 260 | userInterface.setTitle('Next Round'); 261 | SpreadsheetApp.getUi().showSidebar(userInterface); 262 | } 263 | 264 | /// Finds the next pairing. 265 | /// 266 | /// The pairings in a state are sorted lower rounds first, top-to-bottom. 267 | /// 268 | /// This method only needs to decide whether to pick a winner or loser bracket 269 | /// pairing. We pick loser bracket, unless there are winner brackets that feed 270 | /// into the unplayed loser-bracket column. 271 | Pairing findBestNextPairing(State state) { 272 | var winnerPairings = state.availableWinnerPairings; 273 | var loserPairings = state.availableLoserPairings; 274 | 275 | if (winnerPairings.isEmpty) return loserPairings.first; 276 | if (loserPairings.isEmpty) return winnerPairings.first; 277 | 278 | var winnerColumn = winnerPairings.first.column; 279 | var loserColumn = loserPairings.first.column; 280 | if (winnerColumn == 0) return winnerPairings.first; 281 | if (loserColumn == 0) return loserPairings.first; 282 | if ((loserColumn - 1) < (winnerColumn - 2) * 2) return loserPairings.first; 283 | return winnerPairings.first; 284 | } 285 | 286 | void createNextRoundDocument(List userData) { 287 | // Might require more permissions: https://developers.google.com/apps-script/concepts/scopes 288 | var document = DocumentApp.create('round - ${DateTime.now()}'); 289 | var body = document.getBody(); 290 | 291 | var isFirst = true; 292 | var totalAPercentage = userData.last; 293 | var checkBoxValues = userData.sublist(0, userData.length - 1); 294 | if (checkBoxValues.isEmpty) return; 295 | 296 | var selectedParticipants = 297 | checkBoxValues.map((x) => x as String).map(json.decode).toList(); 298 | var urls = urlMapping; 299 | var allPairings = computeAllPairings(); 300 | var states = selectedParticipants.map((participant) { 301 | var isActive = true; 302 | return State( 303 | participant, isActive, urls[participant], allPairings[participant]); 304 | }).toList(); 305 | 306 | var leftIndex = 0; 307 | var rightIndex = (states.length + 1) ~/ 2; 308 | var lastPageTable = List>(states.length); 309 | 310 | for (var j = 0; j < states.length; j++) { 311 | var i = j; 312 | if (isPrinting2PagesPerSheet) { 313 | // We reorder the pages so that we can cut in the middle and then 314 | // put the two piles of papers on top of each other while still having 315 | // the order we want. 316 | if (j.isEven) { 317 | i = leftIndex++; 318 | } else { 319 | i = rightIndex++; 320 | } 321 | } 322 | var state = states[i]; 323 | var participant = state.participantName; 324 | var selectedPairing = findBestNextPairing(state); 325 | var row = selectedPairing.row; 326 | var column = selectedPairing.column; 327 | var competitorA = selectedPairing.competitorA; 328 | var competitorB = selectedPairing.competitorB; 329 | var roundNumber = state.nextRoundNumber; 330 | var missingResults = state.missingResults; 331 | var selectedAPercentage = state.finishedCount == 0 332 | ? -1 333 | : 100 * state.aSelectionCount ~/ state.finishedCount; 334 | 335 | var inTestMode = false; 336 | if (inTestMode) { 337 | body.appendParagraph('IN TEST MODE. NOT SETTING THE SHEET'); 338 | } else { 339 | var sheetName = sheetNameFromParticipant(participant); 340 | SpreadsheetApp.getActiveSpreadsheet() 341 | .getSheetByName(sheetName) 342 | .getRange(row, column) 343 | .setValue(roundNumber); 344 | } 345 | 346 | Paragraph paragraph; 347 | if (isFirst) { 348 | isFirst = false; 349 | // Reuse already existing paragraph. 350 | paragraph = body.getChild(0); 351 | } else { 352 | paragraph = body.appendParagraph(''); 353 | } 354 | paragraph.setText('$participant - $roundNumber'); 355 | paragraph 356 | .setAlignment(DocumentApp.HorizontalAlignment.CENTER) 357 | .editAsText() 358 | .setFontSize(32); 359 | body.appendParagraph(state.url).editAsText().setFontSize(20); 360 | if (missingResults.isNotEmpty) { 361 | body.appendParagraph('Missing results for: ${missingResults.join(', ')}'); 362 | } 363 | if (selectedAPercentage != -1) { 364 | body 365 | .appendParagraph('Your A-percentage: $selectedAPercentage%') 366 | .editAsText() 367 | .setFontSize(12); 368 | } 369 | if (totalAPercentage != -1) { 370 | body 371 | .appendParagraph('Total A-percentage: $totalAPercentage%') 372 | .editAsText() 373 | .setFontSize(12); 374 | } 375 | var table = body.appendTable([ 376 | ['A', 'B'] 377 | ]); 378 | table.setBorderColor('#ffffff'); 379 | Paragraph cellA = table.getCell(0, 0).getChild(0); 380 | Paragraph cellB = table.getCell(0, 1).getChild(0); 381 | cellA 382 | .setAlignment(DocumentApp.HorizontalAlignment.LEFT) 383 | .editAsText() 384 | .setFontSize(27); 385 | cellB 386 | .setAlignment(DocumentApp.HorizontalAlignment.RIGHT) 387 | .editAsText() 388 | .setFontSize(27); 389 | body.appendPageBreak(); 390 | lastPageTable[i] = [participant, competitorA, competitorB]; 391 | } 392 | if (states.length.isEven && isPrinting2PagesPerSheet) { 393 | // Add an empty page to move the table to the right side. 394 | body.appendPageBreak(); 395 | } 396 | // Add a small paragraph, to reset the default font size. 397 | body.appendParagraph('').editAsText().setFontSize(12); 398 | body.appendTable(lastPageTable); 399 | 400 | var id = document.getId(); 401 | var html = """ 402 | 403 |
Created a document.
404 | 405 | 406 | """; 407 | var userInterface = HtmlService.createHtmlOutput(html.toString()); 408 | SpreadsheetApp.getUi().showModalDialog(userInterface, 'Next Round Ready'); 409 | } 410 | -------------------------------------------------------------------------------- /example/tournament/lib/src/reports.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:convert'; 16 | 17 | import 'config.dart'; 18 | import 'tournament.dart'; 19 | import 'utils.dart'; 20 | 21 | import 'package:google_apps/google_apps.dart'; 22 | import 'package:ranking/ranking.dart'; 23 | 24 | // Used both as target for the menu and the callback from the selection sidebar. 25 | void generateFinalReportsDart([List selectedParticipants]) { 26 | if (selectedParticipants == null) { 27 | // Menu click 28 | var html = StringBuffer(); 29 | html.write(''); 30 | html.write( 31 | ""); 32 | html.write( 33 | ""); 34 | 35 | for (var participant in allParticipants) { 36 | var participantName = normalizeName(participant); 37 | var escapedParticipant = htmlEscape.convert(participant); 38 | var escapedParticipantName = htmlEscape.convert(participantName); 39 | html.write('
" 43 | '' 44 | '
'); 45 | } 46 | html.write( 47 | ""); 48 | html.write(""""""); 74 | 75 | html.write(''); 76 | var userInterface = HtmlService.createHtmlOutput(html.toString()); 77 | userInterface.setTitle('Final Reports'); 78 | SpreadsheetApp.getUi().showSidebar(userInterface); 79 | return; 80 | } 81 | // Callback from sidebar. 82 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 83 | 84 | var activeSet = Set.from(activeParticipants); 85 | var allPairings = computeAllPairings(); 86 | var urls = urlMapping; 87 | var participantStates = {}; 88 | for (var participant in selectedParticipants) { 89 | var pairings = allPairings[participant]; 90 | var isActive = activeSet.contains(participant); 91 | participantStates[participant] = 92 | State(participant, isActive, urls[participant], pairings); 93 | } 94 | 95 | var responses = readResponses(); 96 | 97 | combineStatesAndResponses(participantStates.values.toList(), responses); 98 | 99 | // Might require more permissions: https://developers.google.com/apps-script/concepts/scopes 100 | var folder = 101 | DriveApp.getRootFolder().createFolder('Final Report - ${DateTime.now()}'); 102 | 103 | for (var participant in selectedParticipants) { 104 | var participantName = normalizeName(participant); 105 | var name = sheetNameFromParticipant(participant); 106 | var sheet = spreadsheet.getSheetByName(name); 107 | var data = sheet.getDataRange().getValues(); 108 | 109 | var individualReportSpreadsheet = SpreadsheetApp.create(participantName); 110 | moveFileToFolder(individualReportSpreadsheet, folder); 111 | var tmpSheet = individualReportSpreadsheet.getSheets()[0]; 112 | var individualSheet = sheet.copyTo(individualReportSpreadsheet); 113 | individualSheet.setName(sheet.getName()); 114 | individualSheet.getRange(1, 1, data.length, data[0].length).setValues(data); 115 | individualReportSpreadsheet.deleteSheet(tmpSheet); 116 | 117 | var state = participantStates[participant]; 118 | var pairings = state.allPairings.toList(); 119 | pairings.sort((a, b) => a.round.compareTo(b.round)); 120 | 121 | for (var pairing in pairings) { 122 | var response = pairing.response; 123 | if (response == null) continue; 124 | if (response.aComment == '' && response.bComment == '') { 125 | continue; 126 | } 127 | 128 | var text = ''; 129 | if (response.aComment != '') { 130 | text += '\nA: ${response.aComment}'; 131 | } 132 | if (response.bComment != '') { 133 | text += '\nB: ${response.bComment}'; 134 | } 135 | 136 | individualSheet 137 | .getRange(pairing.row, pairing.column, 1, 1) 138 | .setNote(text.trim()); 139 | } 140 | } 141 | 142 | var id = folder.getId(); 143 | var html = """ 144 | 145 |
Created a folder.
146 | 147 | 148 | """; 149 | var userInterface = HtmlService.createHtmlOutput(html.toString()); 150 | SpreadsheetApp.getUi().showModalDialog(userInterface, 'Final Reports Ready'); 151 | } 152 | 153 | Map get rankedCompetitors { 154 | print('1'); 155 | var competitors = allCompetitors; 156 | print('2'); 157 | 158 | var pairings = computeAllPairings(); 159 | print('3'); 160 | 161 | var games = >[]; 162 | 163 | for (var pairs in pairings.values) { 164 | for (var pair in pairs) { 165 | if (!pair.hasResult) continue; 166 | if (pair.result == 'A') { 167 | games.add([pair.competitorA, pair.competitorB]); 168 | } else { 169 | games.add([pair.competitorB, pair.competitorA]); 170 | } 171 | } 172 | } 173 | 174 | var scores = computeBradleyTerryScores(games); 175 | var ranked = competitors.toList() 176 | ..sort((a, b) { 177 | var scoreA = scores[a] ?? 0; 178 | var scoreB = scores[b] ?? 0; 179 | return -scoreA.compareTo(scoreB); 180 | }); 181 | var result = {}; 182 | for (var competitor in ranked) { 183 | result[competitor] = scores[competitor] ?? 0; 184 | } 185 | return result; 186 | } 187 | 188 | // Used both as target for the menu and the callback from the selection sidebar. 189 | void generateCompetitorReportsDart([List selectedCompetitors]) { 190 | if (selectedCompetitors == null) { 191 | // Menu click 192 | var html = StringBuffer(); 193 | html.write(''); 194 | html.write( 195 | ""); 196 | html.write( 197 | ""); 198 | 199 | for (var competitor in rankedCompetitors.keys) { 200 | var escapedCompetitor = htmlEscape.convert(competitor); 201 | html.write('
" 205 | '' 206 | '
'); 207 | } 208 | html.write( 209 | ""); 210 | html.write(""""""); 236 | 237 | html.write(''); 238 | var userInterface = HtmlService.createHtmlOutput(html.toString()); 239 | userInterface.setTitle('Competitor Reports'); 240 | SpreadsheetApp.getUi().showSidebar(userInterface); 241 | return; 242 | } 243 | // Callback from sidebar. 244 | 245 | var activeSet = Set.from(allCompetitors); 246 | var urls = urlMapping; 247 | var allPairings = computeAllPairings(); 248 | var allStates = allParticipants.map((participant) { 249 | var pairings = allPairings[participant]; 250 | var isActive = activeSet.contains(participant); 251 | return State(participant, isActive, urls[participant], pairings); 252 | }).toList(); 253 | 254 | var responses = readResponses(); 255 | 256 | combineStatesAndResponses(allStates, responses); 257 | 258 | // Map from competitor to all responses for each participant. 259 | var collectedResponses = >>{}; 260 | 261 | for (var response in responses) { 262 | var perCompetitor = 263 | collectedResponses.putIfAbsent(response.competitorA, () => {}); 264 | perCompetitor.putIfAbsent(response.participant, () => []).add(response); 265 | perCompetitor = 266 | collectedResponses.putIfAbsent(response.competitorB, () => {}); 267 | perCompetitor.putIfAbsent(response.participant, () => []).add(response); 268 | } 269 | 270 | // Might require more permissions: https://developers.google.com/apps-script/concepts/scopes 271 | var document = DocumentApp.create('Report - ${DateTime.now()}'); 272 | var body = document.getBody(); 273 | 274 | var isFirst = true; 275 | for (String competitor in selectedCompetitors) { 276 | Paragraph paragraph; 277 | if (isFirst) { 278 | isFirst = false; 279 | // Reuse already existing paragraph. 280 | paragraph = body.getChild(0); 281 | } else { 282 | paragraph = body.appendParagraph(''); 283 | } 284 | paragraph.setText(competitor); 285 | paragraph 286 | .setAlignment(DocumentApp.HorizontalAlignment.CENTER) 287 | .setHeading(DocumentApp.ParagraphHeading.TITLE); 288 | 289 | void writeComments(Response response) { 290 | var participantName = normalizeName(response.participant); 291 | String comment; 292 | bool wasWinner; 293 | if (competitor == response.competitorA) { 294 | comment = response.aComment; 295 | wasWinner = response.result == 'A'; 296 | } else { 297 | comment = response.bComment; 298 | wasWinner = response.result == 'B'; 299 | } 300 | if (comment != '') { 301 | var winnerTag = wasWinner ? ' (W)' : ''; 302 | body 303 | .appendParagraph('$participantName$winnerTag: $comment') 304 | .editAsText() 305 | .setBold(0, participantName.length, true); 306 | } 307 | } 308 | 309 | var participantResponses = collectedResponses[competitor]; 310 | if (participantResponses != null) { 311 | participantResponses.forEach((_, responses) { 312 | responses.forEach(writeComments); 313 | }); 314 | } 315 | 316 | body.appendPageBreak(); 317 | } 318 | 319 | var id = document.getId(); 320 | var html = """ 321 | 322 |
Created a document.
323 | 324 | 325 | """; 326 | var userInterface = HtmlService.createHtmlOutput(html.toString()); 327 | SpreadsheetApp.getUi().showModalDialog(userInterface, 'Reports Ready'); 328 | } 329 | 330 | void competitorRankingDart() { 331 | var ranked = rankedCompetitors; 332 | var table = []; 333 | ranked.forEach((name, score) { 334 | table.add([name, (score * 1000).toStringAsFixed(3)]); 335 | }); 336 | 337 | var html = StringBuffer(); 338 | html.writeln(''); 339 | html.writeln(''); 340 | html.writeln(''); 341 | table.forEach((entry) { 342 | html.writeln(''); 343 | html.write(''); 346 | html.write(''); 349 | html.writeln(''); 350 | }); 351 | html.writeln('
CompetitorBradley-Terry Score
'); 344 | html.write(entry[0]); 345 | html.write(''); 347 | html.write(entry[1]); 348 | html.write('
'); 352 | html.write(''); 353 | var userInterface = HtmlService.createHtmlOutput(html.toString()); 354 | userInterface.setTitle('Ranking'); 355 | SpreadsheetApp.getUi().showModalDialog(userInterface, 'Competitor Ranking'); 356 | } 357 | -------------------------------------------------------------------------------- /example/tournament/lib/src/tournament.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:math' as math; 16 | import 'config.dart'; 17 | import 'utils.dart'; 18 | 19 | import 'package:google_apps/google_apps.dart'; 20 | 21 | enum BattleType { 22 | winner, 23 | loser, 24 | bonus, 25 | } 26 | 27 | const battleTypeToString = { 28 | BattleType.winner: 'winner', 29 | BattleType.loser: 'loser', 30 | BattleType.bonus: 'bonus', 31 | }; 32 | const stringToBattleType = { 33 | 'winner': BattleType.winner, 34 | 'loser': BattleType.loser, 35 | 'bonus': BattleType.bonus, 36 | }; 37 | 38 | int compareBattleTypes(BattleType a, BattleType b) { 39 | if (a == b) return 0; 40 | if (a == BattleType.winner) return 1; 41 | if (b == BattleType.winner) return -1; 42 | if (a == BattleType.loser) return 1; 43 | return -1; 44 | } 45 | 46 | void combineStatesAndResponses(List states, List responses) { 47 | // Per participant and indexed by round number. 48 | var structured = >{}; 49 | for (var response in responses) { 50 | var perParticipant = structured.putIfAbsent(response.participant, () => {}); 51 | perParticipant[response.roundNumber] = response; 52 | } 53 | 54 | for (var state in states) { 55 | var participant = state.participant; 56 | 57 | for (var pairing in state.allPairings) { 58 | if (pairing.hasResult) { 59 | var response = structured[participant][pairing.round]; 60 | response.competitorA = pairing.competitorA; 61 | response.competitorB = pairing.competitorB; 62 | pairing.response = response; 63 | } 64 | } 65 | } 66 | } 67 | 68 | class Response { 69 | final String participant; 70 | final int roundNumber; 71 | final String result; 72 | final String aComment; 73 | final String bComment; 74 | 75 | /// [competitorA] and [competitorB] are not set automatically. 76 | /// 77 | /// Use [combineStatesAndResponses] to fill them. 78 | String competitorA; 79 | String competitorB; 80 | 81 | Response(this.participant, this.roundNumber, this.result, this.aComment, 82 | this.bComment); 83 | 84 | String get winnerComment => result == 'A' ? aComment : bComment; 85 | 86 | String get loserComment => result == 'A' ? bComment : aComment; 87 | 88 | String get winner => result == 'A' ? competitorA : competitorB; 89 | 90 | String get loser => result == 'A' ? competitorB : competitorA; 91 | } 92 | 93 | List readResponses() { 94 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 95 | var responsesSheet = spreadsheet.getSheetByName(resultsSheetName); 96 | var entries = responsesSheet.getDataRange().getValues().cast(); 97 | 98 | int toIndex(String columnDescription) => 99 | columnDescription.codeUnitAt(0) - 'A'.codeUnitAt(0); 100 | 101 | var participantIndex = toIndex(resultsParticipantColumn); 102 | var roundIndex = toIndex(resultsRoundColumn); 103 | var resultIndex = toIndex(resultsWinColumn); 104 | var commentAIndex = toIndex(resultsCommentsAColumn); 105 | var commentBIndex = toIndex(resultsCommentsBColumn); 106 | // Skip the header row. 107 | return entries.skip(1).map((List row) { 108 | return Response(row[participantIndex], row[roundIndex], row[resultIndex], 109 | row[commentAIndex], row[commentBIndex]); 110 | }).toList(); 111 | } 112 | 113 | class Pairing implements Comparable { 114 | /// The row of the round-number. 115 | final int row; 116 | 117 | /// The column of the round-number. 118 | final int column; 119 | final String competitorA; 120 | final String competitorB; 121 | final BattleType type; 122 | 123 | /// -1 if the round hasn't been run yet. 124 | final int round; 125 | 126 | // Either "A", "B" or null. 127 | final String result; 128 | 129 | /// This field isn't set automatically. Use [combineStatesAndResponses] to 130 | /// fill it. 131 | Response response; 132 | 133 | Pairing(this.row, this.column, this.competitorA, this.competitorB, 134 | {this.type, this.round, this.result}); 135 | 136 | bool get hasPlayed => round != -1; 137 | 138 | bool get canBePlayed => !hasPlayed && competitorA != '' && competitorB != ''; 139 | 140 | bool get hasResult => result != null; 141 | 142 | String get winner { 143 | if (!hasResult) throw 'Winner is only available when there is a result'; 144 | return result == 'A' ? competitorA : competitorB; 145 | } 146 | 147 | String get loser { 148 | if (!hasResult) throw 'Loser is only available when there is a result'; 149 | return result == 'A' ? competitorB : competitorA; 150 | } 151 | 152 | @override 153 | int compareTo(Pairing other) { 154 | var typeComp = compareBattleTypes(type, other.type); 155 | if (typeComp != 0) return typeComp; 156 | if (column != other.column) return column.compareTo(other.column); 157 | return row.compareTo(other.row); 158 | } 159 | 160 | @override 161 | String toString() { 162 | return '<$competitorA - $competitorB>'; 163 | } 164 | } 165 | 166 | Map> computeAllPairings() { 167 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 168 | var participants = allParticipants; 169 | var cacheSheet = spreadsheet.getSheetByName(cacheSheetName); 170 | var cache = cacheSheet.getDataRange().getValues(); 171 | 172 | var pairingsPerParticipant = >{}; 173 | for (var participant in participants) { 174 | pairingsPerParticipant[participant] = []; 175 | } 176 | 177 | for (var row in cache) { 178 | var column = 0; 179 | String participant = row[column++]; 180 | String competitorA = row[column++]; 181 | String competitorB = row[column++]; 182 | var type = stringToBattleType[row[column++]]; 183 | String result = row[column++]; 184 | if (result == '') result = null; 185 | int roundNumber = row[column] == '' ? -1 : row[column]; 186 | column++; 187 | int roundRow = row[column++]; 188 | int roundColumn = row[column++]; 189 | 190 | pairingsPerParticipant[participant].add(Pairing( 191 | roundRow, roundColumn, competitorA, competitorB, 192 | type: type, result: result, round: roundNumber)); 193 | } 194 | 195 | return pairingsPerParticipant; 196 | } 197 | 198 | class State { 199 | final String participant; 200 | final bool isActive; 201 | final String url; 202 | final List allPairings; 203 | final bool hasLoserBracket; 204 | final List winnerPairings; 205 | final List loserPairings; 206 | final List bonusPairings; 207 | final List missingResults; 208 | final List availableWinnerPairings; 209 | final List availableLoserPairings; 210 | final int nextRoundNumber; 211 | final int playedWinnersCount; 212 | final int playedLosersCount; 213 | final int playedBonusCount; 214 | final int finishedWinnersCount; 215 | final int finishedLosersCount; 216 | final int finishedBonusCount; 217 | final int aSelectionCount; 218 | 219 | factory State(String participant, bool isActive, String url, 220 | List allPairings) { 221 | var inStock = inStockCompetitors; 222 | 223 | bool canBePlayed(Pairing pairing) { 224 | return pairing.canBePlayed && 225 | inStock.contains(pairing.competitorA) && 226 | inStock.contains(pairing.competitorB); 227 | } 228 | 229 | var winnerPairings = []; 230 | var loserPairings = []; 231 | var bonusPairings = []; 232 | var availableWinnerPairings = []; 233 | var availableLoserPairings = []; 234 | var playedWinnersCount = 0; 235 | var playedLosersCount = 0; 236 | var playedBonusCount = 0; 237 | var finishedWinnersCount = 0; 238 | var finishedLosersCount = 0; 239 | var finishedBonusCount = 0; 240 | var missingResults = []; 241 | var maxRoundNumber = 0; 242 | var aSelectionCount = 0; 243 | for (var pairing in allPairings) { 244 | switch (pairing.type) { 245 | case BattleType.winner: 246 | winnerPairings.add(pairing); 247 | if (pairing.round > 0) playedWinnersCount++; 248 | if (pairing.hasResult) finishedWinnersCount++; 249 | if (canBePlayed(pairing)) availableWinnerPairings.add(pairing); 250 | break; 251 | case BattleType.loser: 252 | loserPairings.add(pairing); 253 | if (pairing.round > 0) playedLosersCount++; 254 | if (pairing.hasResult) finishedLosersCount++; 255 | if (canBePlayed(pairing)) availableLoserPairings.add(pairing); 256 | break; 257 | default: 258 | assert(pairing.type == BattleType.bonus); 259 | bonusPairings.add(pairing); 260 | if (pairing.round > 0) playedBonusCount++; 261 | if (pairing.hasResult) finishedBonusCount++; 262 | } 263 | if (pairing.hasPlayed && !pairing.hasResult) { 264 | if (pairing.round != 0) missingResults.add(pairing.round); 265 | } 266 | if (pairing.hasPlayed) { 267 | maxRoundNumber = math.max(pairing.round, maxRoundNumber); 268 | } 269 | if (pairing.result == 'A') aSelectionCount++; 270 | } 271 | var hasLoserBracket = loserPairings.isNotEmpty; 272 | var nextRoundNumber = maxRoundNumber + 1; 273 | winnerPairings.sort(); 274 | loserPairings.sort(); 275 | bonusPairings.sort(); 276 | availableWinnerPairings.sort(); 277 | availableLoserPairings.sort(); 278 | 279 | return State._( 280 | participant, 281 | isActive, 282 | url, 283 | allPairings, 284 | hasLoserBracket, 285 | winnerPairings, 286 | loserPairings, 287 | bonusPairings, 288 | missingResults, 289 | availableWinnerPairings, 290 | availableLoserPairings, 291 | nextRoundNumber, 292 | playedWinnersCount, 293 | playedLosersCount, 294 | playedBonusCount, 295 | finishedWinnersCount, 296 | finishedLosersCount, 297 | finishedBonusCount, 298 | aSelectionCount); 299 | } 300 | 301 | State._( 302 | this.participant, 303 | this.isActive, 304 | this.url, 305 | this.allPairings, 306 | this.hasLoserBracket, 307 | this.winnerPairings, 308 | this.loserPairings, 309 | this.bonusPairings, 310 | this.missingResults, 311 | this.availableWinnerPairings, 312 | this.availableLoserPairings, 313 | this.nextRoundNumber, 314 | this.playedWinnersCount, 315 | this.playedLosersCount, 316 | this.playedBonusCount, 317 | this.finishedWinnersCount, 318 | this.finishedLosersCount, 319 | this.finishedBonusCount, 320 | this.aSelectionCount); 321 | 322 | int get finishedCount => 323 | finishedWinnersCount + finishedLosersCount + finishedBonusCount; 324 | 325 | String get participantName => normalizeName(participant); 326 | } 327 | -------------------------------------------------------------------------------- /example/tournament/lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:google_apps/google_apps.dart'; 16 | 17 | /// Alerts the user with a popup. 18 | void alert(String msg) { 19 | SpreadsheetApp.getUi().alert(msg); 20 | } 21 | 22 | /// Alerts the user of an error and throws the error. 23 | void error(String msg) { 24 | alert(msg); 25 | throw msg; 26 | } 27 | 28 | /// Takes a participant [name] and normalizes it. 29 | /// 30 | /// If the [name] is an email address returns the local part (everything before 31 | /// the '@'). 32 | String normalizeName(String name) { 33 | if (name.contains('@')) { 34 | return name.substring(0, name.indexOf('@')); 35 | } 36 | return name; 37 | } 38 | 39 | /// Returns the name of the sheet for the given [participant]. 40 | String sheetNameFromParticipant(String participant) { 41 | return normalizeName(participant); 42 | } 43 | 44 | 45 | /// Moves [file] into [folder]. 46 | /// 47 | /// The [file] argument must have an id (accessed via `getId`). As such, 48 | /// [Spreadsheet]s and [Document]s work. 49 | void moveFileToFolder(var file, Folder folder) { 50 | file = DriveApp.getFileById(file.getId()); 51 | folder.addFile(file); 52 | DriveApp.getRootFolder().removeFile(file); 53 | } 54 | -------------------------------------------------------------------------------- /example/tournament/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: apps_tournament 2 | description: > 3 | A Google Apps Script program to run tournaments. 4 | The script handles the creation of brackets, running the event, as 5 | well as the generation of final reports. 6 | version: 1.0.0 7 | author: Florian Loitsch 8 | homepage: https://github.com/google/dart_google_apps 9 | 10 | environment: 11 | sdk: '>=2.3.1 <3.0.0' 12 | 13 | dependencies: 14 | js: ^0.6.1 15 | ranking: ^0.2.0 16 | google_apps: ^0.3.0 17 | 18 | dev_dependencies: 19 | test: ^1.6.4 20 | -------------------------------------------------------------------------------- /lib/cache.dart: -------------------------------------------------------------------------------- 1 | @JS() 2 | library cache; 3 | 4 | import 'package:js/js.dart'; 5 | 6 | @JS() 7 | class CacheService { 8 | external static Cache getDocumentCache(); 9 | 10 | external static Cache getScriptCache(); 11 | 12 | external static Cache getUserCache(); 13 | } 14 | 15 | @JS() 16 | class Cache { 17 | external String get(String key); 18 | 19 | external Object getAll(List keys); 20 | 21 | external void put(String key, String value, [num expirationInSeconds]); 22 | 23 | external void putAll(Map values, [num expirationInSeconds]); 24 | 25 | external void remove(String key); 26 | 27 | external void removeAll(List keys); 28 | } 29 | -------------------------------------------------------------------------------- /lib/document.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library document; 17 | 18 | import 'package:js/js.dart'; 19 | import 'ui.dart'; 20 | 21 | export 'html.dart'; 22 | export 'ui.dart'; 23 | 24 | @JS() 25 | class DocumentApp { 26 | external static UI getUi(); 27 | external static Document create(String name); 28 | external static HorizontalAlignmentContainer get HorizontalAlignment; 29 | external static ParagraphHeadingContainer get ParagraphHeading; 30 | } 31 | 32 | @JS() 33 | class Document { 34 | external Body getBody(); 35 | external String getId(); 36 | } 37 | 38 | @JS() 39 | class Element {} 40 | 41 | @JS() 42 | class Body implements Element { 43 | external Paragraph appendParagraph(String text); 44 | external PageBreak appendPageBreak(); 45 | external Table appendTable([List> cells]); 46 | external Element getChild(int childIndex); 47 | } 48 | 49 | @JS() 50 | class Paragraph implements Element { 51 | external Paragraph setAlignment(HorizontalAlignment alignment); 52 | external Text editAsText(); 53 | external void setText(String text); 54 | external Paragraph setHeading(ParagraphHeading heading); 55 | external ParagraphHeading getHeading(); 56 | } 57 | 58 | // This class doesn't really exist in JS. Not sure if this will lead to 59 | // problems. 60 | @JS() 61 | class HorizontalAlignmentContainer { 62 | external HorizontalAlignment get LEFT; 63 | external HorizontalAlignment get CENTER; 64 | external HorizontalAlignment get RIGHT; 65 | external HorizontalAlignment get JUSTIFY; 66 | } 67 | 68 | @JS() 69 | class HorizontalAlignment {} 70 | 71 | // This class doesn't really exist in JS. Not sure if this will lead to 72 | // problems. 73 | @JS() 74 | class ParagraphHeadingContainer { 75 | external ParagraphHeading get NORMAL; 76 | external ParagraphHeading get HEADING1; 77 | external ParagraphHeading get HEADING2; 78 | external ParagraphHeading get HEADING3; 79 | external ParagraphHeading get HEADING4; 80 | external ParagraphHeading get HEADING5; 81 | external ParagraphHeading get HEADING6; 82 | external ParagraphHeading get TITLE; 83 | external ParagraphHeading get SUBTITLE; 84 | } 85 | 86 | @JS() 87 | class ParagraphHeading {} 88 | 89 | @JS() 90 | class Text implements Element { 91 | external Text setFontSize(int sizeOrStart, [int endInclusive, int size]); 92 | external Text setBold(dynamic valueOrStart, [int endInclusive, bool value]); 93 | } 94 | 95 | @JS() 96 | class Table implements Element { 97 | external TableCell getCell(int rowIndex, int cellIndex); 98 | external Table setBorderColor(String color); 99 | } 100 | 101 | @JS() 102 | class TableCell implements Element { 103 | external Element getChild(int childIndex); 104 | external Text editAsText(); 105 | } 106 | 107 | @JS() 108 | class PageBreak implements Element {} 109 | -------------------------------------------------------------------------------- /lib/drive.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library drive; 17 | 18 | import 'package:js/js.dart'; 19 | 20 | @JS() 21 | class DriveApp { 22 | external static File createFile(String name, String content); 23 | external static Folder getRootFolder(); 24 | external static File getFileById(String id); 25 | } 26 | 27 | @JS() 28 | class File { 29 | external String getId(); 30 | external String getDownloadUrl(); 31 | } 32 | 33 | @JS() 34 | class Folder { 35 | external String getId(); 36 | external Folder createFolder(String name); 37 | external Folder addFile(File child); 38 | external Folder removeFile(File file); 39 | } 40 | -------------------------------------------------------------------------------- /lib/google_apps.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library apps; 17 | 18 | import 'package:js/js.dart'; 19 | export 'document.dart'; 20 | export 'drive.dart'; 21 | export 'spreadsheet.dart'; 22 | export 'html.dart'; 23 | export 'ui.dart'; 24 | -------------------------------------------------------------------------------- /lib/html.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library html; 17 | 18 | import 'package:js/js.dart'; 19 | 20 | @JS() 21 | class HtmlService { 22 | // TODO: argument could also be a `BlobSource`. Don't need it yet. 23 | external static HtmlOutput createHtmlOutput([String html]); 24 | } 25 | 26 | @JS() 27 | class HtmlOutput { 28 | external HtmlOutput setContent(String content); 29 | external HtmlOutput setWidth(int width); 30 | external HtmlOutput setHeight(int height); 31 | external HtmlOutput setTitle(String title); 32 | external int getWidth(); 33 | external int getHeight(); 34 | external String getTitle(); 35 | } 36 | -------------------------------------------------------------------------------- /lib/lock.dart: -------------------------------------------------------------------------------- 1 | @JS() 2 | library lock; 3 | 4 | import 'package:js/js.dart'; 5 | 6 | @JS() 7 | class LockService { 8 | external static Lock getDocumentLock(); 9 | 10 | external static Lock getScriptLock(); 11 | 12 | external static Lock getUserLock(); 13 | } 14 | 15 | @JS() 16 | class Lock { 17 | external bool hasLock(); 18 | 19 | external void releaseLock(); 20 | 21 | external bool tryLock(num timeoutInMillis); 22 | 23 | external void waitLock(num timeoutInMillis); 24 | } 25 | -------------------------------------------------------------------------------- /lib/properties.dart: -------------------------------------------------------------------------------- 1 | @JS() 2 | library properties; 3 | 4 | import 'package:js/js.dart'; 5 | 6 | @JS() 7 | class PropertiesService { 8 | external static Properties getDocumentProperties(); 9 | external static Properties getScriptProperties(); 10 | external static Properties getUserProperties(); 11 | } 12 | 13 | @JS() 14 | class Properties { 15 | external Properties deleteAllProperties(); 16 | external Properties deleteProperty(String key); 17 | external List getKeys(); 18 | external Object getProperties(); 19 | external String getProperty(String key); 20 | external Properties setProperties(Properties properties); 21 | external Properties setProperty(String key, String value); 22 | } 23 | -------------------------------------------------------------------------------- /lib/spreadsheet.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library spreadsheet; 17 | 18 | import 'package:js/js.dart'; 19 | import 'ui.dart'; 20 | 21 | export 'html.dart'; 22 | export 'ui.dart'; 23 | 24 | @JS() 25 | class SpreadsheetApp { 26 | external static UI getUi(); 27 | external static Spreadsheet getActiveSpreadsheet(); 28 | external static Sheet getActiveSheet(); 29 | external static Range getActiveRange(); 30 | external static Spreadsheet create(String name); 31 | } 32 | 33 | @JS() 34 | class Spreadsheet { 35 | external Sheet getSheetByName(String name); 36 | external Range getRangeByName(String name); 37 | external void setNamedRange(String name, Range range); 38 | external Sheet insertSheet([int index]); 39 | external void deleteSheet(Sheet sheet); 40 | external void deleteActiveSheet(); 41 | external List getSheets(); 42 | external String getId(); 43 | external Sheet setActiveSheet(Sheet sheet); 44 | external void moveActiveSheet(int pos); 45 | } 46 | 47 | @JS() 48 | class Sheet { 49 | external Range getActiveCell(); 50 | external Range getRange(dynamic /*String or int*/ rowOrA1Notation, 51 | [int column, int rumRows, int numColumns]); 52 | external Range getDataRange(); 53 | external int getMaxRows(); 54 | external int getMaxColumns(); 55 | external int getLastColumn(); 56 | external int getLastRow(); 57 | external String getName(); 58 | external Sheet setName(String name); 59 | external Sheet clear(); 60 | external Sheet clearContents(); 61 | external Sheet clearFormats(); 62 | external Sheet setColumnWidth(int columnIndex, int width); 63 | external Sheet setRowHeight(int rowIndex, int height); 64 | external Sheet insertRowAfter(int afterPosition); 65 | external Sheet insertRowBefore(int beforePosition); 66 | 67 | /// [numRows] is defaulting to 1. 68 | external void insertRows(rowIndex, [int numRows]); 69 | external Sheet insertRowsAfter(int afterPosition, int howMany); 70 | external Sheet insertRowsBefore(int beforePosition, int howMany); 71 | external void insertColumns(columnIndex, [int numColumns]); 72 | external int getIndex(); 73 | external Sheet appendRow(List rowContents); 74 | external Sheet hideSheet(); 75 | external bool isSheetHidden(); 76 | external Sheet copyTo(Spreadsheet targetSpreadsheet); 77 | } 78 | 79 | @JS() 80 | class Range { 81 | external int getRow(); 82 | external int getColumn(); 83 | external dynamic getValue(); 84 | external Range setValue(dynamic value); 85 | external List> getValues(); 86 | external Range setValues(List> values); 87 | external String getDisplayValue(); 88 | external List> getDisplayValues(); 89 | external String getFormula(); 90 | external List> getFormulas(); 91 | external String getFormulaR1C1(); 92 | external List> getFormulasR1C1(); 93 | external Range setFormula(String formula); 94 | 95 | /// [options] may be a JS-map with entries 'formatOnly' or 'contentsOnly'. 96 | external void copyTo(Range destination, [options]); 97 | 98 | /// The size of the two-dimensional array must match the size of the range. 99 | /// ``` 100 | /// var formulas = [ 101 | /// ["=SUM(B2:B4)", "=SUM(C2:C4)", "=SUM(D2:D4)"], 102 | /// ["=AVERAGE(B2:B4)", "=AVERAGE(C2:C4)", "=AVERAGE(D2:D4)"] 103 | /// ]; 104 | /// var cell = sheet.getRange("B5:D6"); 105 | /// cell.setFormulas(formulas); 106 | /// ``` 107 | external Range setFormulas(List> formulas); 108 | external Range setFormulaR1C1(String formula); 109 | 110 | /// This creates formulas for a row of sums, followed by a row of averages. 111 | /// ``` 112 | /// var sumOfRowsAbove = "=SUM(R[-3]C[0]:R[-1]C[0])"; 113 | /// var averageOfRowsAbove = "=AVERAGE(R[-4]C[0]:R[-2]C[0])"; 114 | /// ``` 115 | /// 116 | /// The size of the two-dimensional array must match the size of the range. 117 | /// ``` 118 | /// var formulas = [ 119 | /// [sumOfRowsAbove, sumOfRowsAbove, sumOfRowsAbove], 120 | /// [averageOfRowsAbove, averageOfRowsAbove, averageOfRowsAbove] 121 | /// ]; 122 | /// var cell = sheet.getRange("B5:D6"); 123 | /// cell.setFormulasR1C1(formulas); 124 | /// ``` 125 | external Range setFormulasR1C1(List> formulas); 126 | external Range offset(int row, int column, [int numRows, int numColumns]); 127 | external Sheet getSheet(); 128 | external Range setBackground(String color); 129 | external Range setBackgroundRGB(int red, int green, int blue); 130 | external Range setBackgrounds(List> colors); 131 | external String getBackgroundColor(); 132 | external List> getBackgrounds(); 133 | external String getA1Notation(); 134 | 135 | /// Relative to this range. 136 | external Range getCell(int row, int column); 137 | external int getNumRows(); 138 | external int getNumColumns(); 139 | external Range clear(); 140 | external Range setNote(String note); 141 | 142 | /// Sets the font weight. 143 | /// 144 | /// The argument [fontWeight] must be either "bold" or "normal". 145 | external Range setFontWeight(String fontWeight); 146 | } 147 | -------------------------------------------------------------------------------- /lib/tasks.dart: -------------------------------------------------------------------------------- 1 | @JS('Tasks') 2 | library tasks; 3 | 4 | import 'package:js/js.dart'; 5 | 6 | // https://developers.google.com/tasks/v1/reference/tasklists 7 | @JS() 8 | class Tasklists { 9 | external String get kind; 10 | 11 | external String get etag; 12 | 13 | external String get nextPageToken; 14 | 15 | external List get items; 16 | 17 | external static Tasklists list([num maxResults, String pageToken]); 18 | 19 | external static Tasklist get(String tasklist); 20 | 21 | external static Tasklist insert(Tasklist resource); 22 | 23 | external static Tasklist update(Tasklist resource, String tasklist); 24 | 25 | external static void remove(String id); 26 | } 27 | 28 | @JS() 29 | class Tasklist { 30 | external String get etag; 31 | 32 | external set etag(String etag); 33 | 34 | external String get id; 35 | 36 | external set id(String id); 37 | 38 | external String get kind; 39 | 40 | external set kind(String kind); 41 | 42 | external String get selfLink; 43 | 44 | external set selfLink(String selfLink); 45 | 46 | external String get title; 47 | 48 | external set title(String title); 49 | 50 | external String get updated; 51 | 52 | external set updated(String updated); 53 | } 54 | 55 | // https://developers.google.com/tasks/v1/reference/tasks 56 | @JS() 57 | class Tasks { 58 | external String get etag; 59 | 60 | external String get kind; 61 | 62 | external String get nextPageToken; 63 | 64 | external List get items; 65 | 66 | external static void clear(String tasklist); 67 | 68 | external static Task get(String tasklist, String task); 69 | 70 | external static Task insert(Task resource, String tasklist, 71 | [TaskOptions taskOptions]); 72 | 73 | external static Tasks list(String tasklist, [TaskOptions taskOptions]); 74 | 75 | external static Task move(String tasklist, String task, 76 | [TaskOptions taskOptions]); 77 | 78 | external static Task patch(Task resource, String tasklist, String task); 79 | 80 | external static void remove(String tasklist, String task); 81 | 82 | external static Task update(Task resource, String tasklist, String task); 83 | } 84 | 85 | @JS() 86 | @anonymous 87 | class TaskOptions { 88 | external String get completedMax; 89 | 90 | external String get completedMin; 91 | 92 | external String get dueMax; 93 | 94 | external String get dueMin; 95 | 96 | external num get maxResults; 97 | 98 | external String get pageToken; 99 | 100 | external bool get showCompleted; 101 | 102 | external bool get showDeleted; 103 | 104 | external bool get showHidden; 105 | 106 | external String get updatedMin; 107 | 108 | external String get parent; 109 | 110 | external String get previous; 111 | 112 | external factory TaskOptions( 113 | {String completedMax, 114 | String completedMin, 115 | String dueMax, 116 | String dueMin, 117 | num maxResults, 118 | String pageToken, 119 | bool showCompleted, 120 | bool showDeleted, 121 | bool showHidden, 122 | String updatedMin, 123 | String parent, 124 | String previous}); 125 | } 126 | 127 | @JS() 128 | class Task { 129 | external String get etag; 130 | 131 | external set etag(String etag); 132 | 133 | external String get kind; 134 | 135 | external set kind(String kind); 136 | 137 | external String get id; 138 | 139 | external set id(String id); 140 | 141 | external String get title; 142 | 143 | external set title(String title); 144 | 145 | external String get parent; 146 | 147 | external set parent(String parent); 148 | 149 | external String get position; 150 | 151 | external set position(String position); 152 | 153 | external String get notes; 154 | 155 | external set notes(String notes); 156 | 157 | external String get status; 158 | 159 | external set status(String status); 160 | 161 | external bool get deleted; 162 | 163 | external set deleted(bool deleted); 164 | 165 | external bool get hidden; 166 | 167 | external List get links; 168 | 169 | external String get selfLink; 170 | 171 | external set selfLink(String selfLink); 172 | 173 | external String get due; 174 | 175 | external set due(String due); 176 | 177 | external String get completed; 178 | 179 | external set completed(String completed); 180 | 181 | external String get updated; 182 | 183 | external set updated(String updated); 184 | } 185 | 186 | @JS() 187 | class TaskLinks { 188 | external String get description; 189 | 190 | external String get link; 191 | 192 | external String get type; 193 | } 194 | -------------------------------------------------------------------------------- /lib/ui.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 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 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @JS() 16 | library ui; 17 | 18 | import 'package:js/js.dart'; 19 | import 'html.dart'; 20 | export 'html.dart'; 21 | 22 | @JS() 23 | class UI { 24 | external void prompt(String msg); 25 | external void alert(String msg); 26 | external Menu createMenu(String caption); 27 | external Menu createAddonMenu(); 28 | external void showModalDialog(HtmlOutput userInterface, String title); 29 | external void showSidebar(HtmlOutput userInterface); 30 | } 31 | 32 | @JS() 33 | class Menu { 34 | external Menu addItem(String caption, String functionName); 35 | external Menu addSeparator(); 36 | external Menu addSubMenu(Menu menu); 37 | external void addToUi(); 38 | } 39 | -------------------------------------------------------------------------------- /lib/url_fetch.dart: -------------------------------------------------------------------------------- 1 | @JS() 2 | library url_fetch; 3 | 4 | import 'dart:html'; 5 | 6 | import 'package:js/js.dart'; 7 | 8 | @JS() 9 | class HTTPResponse { 10 | external Object getAllHeaders(); 11 | external Blob getAs(String contentType); 12 | external Blob getBlob(); 13 | external List getContent(); 14 | external String getContentText([String charset]); 15 | external Object getHeaders(); 16 | external int getResponseCode(); 17 | } 18 | 19 | @JS() 20 | class UrlFetchApp { 21 | external static HTTPResponse fetch(String url, [Object params]); 22 | external static List fetchAll(List url); 23 | external static Object getRequest(String url, [Object params]); 24 | } 25 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 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 | # 7 | # https:#www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: google_apps 16 | description: > 17 | Google Apps Scripts wrapper libraries. These libraries expose the Google 18 | Apps APIs so they can be used in Dart. 19 | version: 0.1.2 20 | author: Florian Loitsch 21 | homepage: https://github.com/google/dart_google_apps 22 | 23 | environment: 24 | sdk: ">=2.0.0 <3.0.0" 25 | 26 | dependencies: 27 | js: ^0.6.1 28 | --------------------------------------------------------------------------------