├── .gitignore
├── BUILD.bazel
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── WORKSPACE
├── demo
├── BUILD.bazel
├── codelab.html
├── embed.html
├── hello.html
├── hello.js
├── hello_test.js
└── img
│ ├── 156b5e3cc8373d55.png
│ ├── 166c3b4982e4a0ad.png
│ ├── 1f454b6807700695.png
│ ├── 39b4e0371e9703e6.png
│ ├── 433870360ad308d4.png
│ ├── 7656372ff6c6a0f7.png
│ ├── 81347b12f83e4291.png
│ ├── 8a959b48e233bc93.png
│ ├── 9efdf0d1258b78e4.png
│ ├── aa64e93e8151b642.png
│ ├── ab9c361527825fac.png
│ ├── b1728ef310c444f5.png
│ ├── bf15c2f18d7f945c.png
│ ├── cbfdd0302b611ab0.png
│ ├── cf095c2153306fa7.png
│ ├── daefd30e8a290df5.png
│ ├── dc07bbc9fcfe7c5b.png
│ └── ed4633f91ec1389f.png
├── google-codelab-analytics
├── BUILD.bazel
├── google_codelab_analytics.js
├── google_codelab_analytics_def.js
└── google_codelab_analytics_test.js
├── google-codelab-index
├── BUILD.bazel
├── _cards.scss
├── _categories.scss
├── google_codelab_index.js
├── google_codelab_index.scss
├── google_codelab_index.soy
├── google_codelab_index_cards.js
├── google_codelab_index_cards_def.js
├── google_codelab_index_def.js
└── index.html
├── google-codelab-step
├── BUILD.bazel
├── _syntax.scss
├── google-codelab-step.html
├── google_codelab_step.js
├── google_codelab_step.scss
├── google_codelab_step.soy
├── google_codelab_step_def.js
├── google_codelab_step_test.js
├── img-1.png
├── img-2.png
├── img-3.png
├── img-4.png
├── img-5.png
├── img-6.png
├── img-7.png
└── img-8.png
├── google-codelab-survey
├── BUILD.bazel
├── google-codelab-survey.html
├── google_codelab_survey.js
├── google_codelab_survey.scss
├── google_codelab_survey.soy
├── google_codelab_survey_def.js
└── google_codelab_survey_test.js
├── google-codelab
├── BUILD.bazel
├── _drawer.scss
├── _steps.scss
├── google_codelab.js
├── google_codelab.scss
├── google_codelab.soy
├── google_codelab_def.js
├── img
│ ├── 25c5ac88e3641e75.png
│ ├── 350dceb89c6e3968.png
│ ├── 3f1ab21e1e5c772b.png
│ ├── 53b42d1efc0e0295.png
│ ├── 5c79e3f467c21ce6.png
│ ├── 7c7f4389428d02f9.png
│ ├── 9dec2e61f3d3b641.png
│ ├── a21ac67adf427ddc.png
│ ├── a322aaec88da31f0.png
│ ├── afb844ab04c5e37a.png
│ ├── b79cf053ec60b7a4.png
│ ├── dd9ae517d0d8e68f.png
│ ├── f43aa9981defd294.png
│ └── fb8ec99e99f182ac.png
└── index.html
├── third_party
├── BUILD.es6shim
├── BUILD.polyfill
└── BUILD.prettify
└── tools
├── BUILD.bazel
├── bazel.rc
├── ci-continuous.sh
├── ci-presubmit.sh
├── defs.bzl
├── gen_test_html.template
├── server.go
└── webtest.go
/.gitignore:
--------------------------------------------------------------------------------
1 | bazel-*
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("//tools:defs.bzl", "concat")
4 |
5 | licenses(["notice"])
6 |
7 | exports_files(["LICENSE", "README.md"])
8 |
9 | filegroup(
10 | name = "all_files",
11 | srcs = [
12 | ":codelab_elements_js",
13 | ":codelab_elements_css",
14 | ":codelab_index_js",
15 | ":codelab_index_css",
16 | ],
17 | )
18 |
19 | genrule(
20 | name = "bundle",
21 | outs = ["bundle.zip"],
22 | srcs = [
23 | "LICENSE",
24 | "README.md",
25 | ":all_files",
26 | "@prettify//:prettify",
27 | "@polyfill//:custom_elements",
28 | "@polyfill//:native_shim",
29 | ],
30 | cmd = "zip -j $@ $(SRCS)",
31 | )
32 |
33 | genrule(
34 | name = "codelab_elements_js",
35 | outs = ["codelab-elements.js"],
36 | srcs = [
37 | "//google-codelab-analytics:google_codelab_analytics_bin",
38 | "//google-codelab:google_codelab_bin",
39 | "//google-codelab-step:google_codelab_step_bin",
40 | "//google-codelab-survey:google_codelab_survey_bin",
41 | ],
42 | cmd = concat("js"),
43 | )
44 |
45 | genrule(
46 | name = "codelab_elements_css",
47 | outs = ["codelab-elements.css"],
48 | srcs = [
49 | "//google-codelab:google_codelab_scss_bin",
50 | "//google-codelab-step:google_codelab_step_scss_bin",
51 | "//google-codelab-survey:google_codelab_survey_scss_bin",
52 | ],
53 | cmd = concat("css"),
54 | )
55 |
56 | genrule(
57 | name = "codelab_index_js",
58 | outs = ["codelab-index.js"],
59 | srcs = [
60 | "//google-codelab-index:google_codelab_index_bin",
61 | ],
62 | cmd = concat("js"),
63 | )
64 |
65 | genrule(
66 | name = "codelab_index_css",
67 | outs = ["codelab-index.css"],
68 | srcs = [
69 | "//google-codelab-index:google_codelab_index_scss_bin",
70 | ],
71 | cmd = concat("css"),
72 | )
73 |
--------------------------------------------------------------------------------
/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 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/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 | # DEPRECATED
2 | This repository is deprecated - make needed changes to codelab-elements code in
3 | [googlecodelabs/tools/codelab-elements](https://github.com/googlecodelabs/tools/tree/master/codelab-elements).
4 |
5 | # Codelab Custom Elements
6 |
7 | The next generation of the codelab elements without any framework or library
8 | dependencies, only the [Custom Elements](https://html.spec.whatwg.org/multipage/custom-elements.html)
9 | standard spec.
10 |
11 | If this is a release bundle, produced with a `bazel build :bundle` command,
12 | you should see `codelab-elements.js`, `codelab-elements.css` and other files,
13 | ready to be added to an HTML page like the following. Only relevant parts are shown:
14 |
15 | ```html
16 |
17 |
18 |
19 |
20 |
21 | A codelab demo
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Contents of the first step.
31 |
32 |
33 | Contents of the second step.
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ```
43 |
44 | You can download the latest version
45 | from https://github.com/googlecodelabs/codelab-elements.
46 |
47 | ## Dev environment
48 |
49 | All you need is [bazel](https://docs.bazel.build/versions/master/install.html).
50 |
51 | After bazel is installed, try executing the following:
52 |
53 | bazel test --test_output=all //demo:hello_test
54 |
55 | It will take some time at the first run because bazel will download and compile
56 | all dependencies needed to work with the code and run tests. This includes
57 | Google Closure library and compiler, Go language and browsers to run local JS
58 | tests on.
59 |
60 | ### Building
61 |
62 | Check out a demo HelloElement target. To build the element, execute the following:
63 |
64 | bazel build //demo:hello_bin
65 |
66 | It should output something like this:
67 |
68 | INFO: Analysed target //demo:hello_bin (0 packages loaded).
69 | INFO: Found 1 target...
70 | Target //demo:hello_bin up-to-date:
71 | bazel-bin/demo/hello_bin.js
72 | bazel-bin/demo/hello_bin.js.map
73 | INFO: Elapsed time: 0.716s, Critical Path: 0.03s
74 | INFO: Build completed successfully, 1 total action
75 |
76 | ### Testing
77 |
78 | All elements should have their test targets.
79 | As a starting point, check out HelloElement tests:
80 |
81 | bazel test --test_output=errors //demo:hello_test
82 |
83 | You should see something like this:
84 |
85 | INFO: Elapsed time: 5.394s, Critical Path: 4.60s
86 | INFO: Build completed successfully, 2 total actions
87 | //demo:hello_test_chromium-local PASSED in 4.6s
88 |
89 | When things go wrong, it is usually easier to inspect and analyze output
90 | with debug enabled:
91 |
92 | bazel test -s --verbose_failures --test_output=all --test_arg=-debug demo/hello_test
93 |
94 | ### Manual inspection from a browser
95 |
96 | To browse things around manually with a real browser, execute the following:
97 |
98 | bazel run //tools:server
99 |
100 | and navigate to http://localhost:8080.
101 |
102 | ## Notes
103 |
104 | This is not an official Google product.
105 |
--------------------------------------------------------------------------------
/WORKSPACE:
--------------------------------------------------------------------------------
1 | workspace(name = "googlecodelabs_custom_elements")
2 |
3 | # Required by io_bazel_rules_webtesting.
4 | skylib_ver = "f9b0ff1dd3d119d19b9cacbbc425a9e61759f1f5"
5 | http_archive(
6 | name = "bazel_skylib",
7 | sha256 = "ce27a2007deda8a1de65df9de3d4cd93a5360ead43c5ff3017ae6b3a2abe485e",
8 | strip_prefix = "bazel-skylib-{v}".format(v=skylib_ver),
9 | urls = [
10 | "https://github.com/bazelbuild/bazel-skylib/archive/{v}.tar.gz".format(v=skylib_ver),
11 | ],
12 | )
13 |
14 | rules_closure_ver = "0.9.0"
15 | http_archive(
16 | name = "io_bazel_rules_closure",
17 | sha256 = "054717a2e6a415001bc4c608b208723526bdf6cace3592ca6efb3749ba18ce21",
18 | strip_prefix = "rules_closure-{v}".format(v=rules_closure_ver),
19 | url = "https://github.com/shawnbuso/rules_closure/archive/{v}.zip".format(v=rules_closure_ver),
20 | )
21 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories")
22 | closure_repositories()
23 |
24 | http_archive(
25 | name = "io_bazel_rules_go",
26 | sha256 = "53c8222c6eab05dd49c40184c361493705d4234e60c42c4cd13ab4898da4c6be",
27 | url = "https://github.com/bazelbuild/rules_go/releases/download/0.10.0/rules_go-0.10.0.tar.gz",
28 | )
29 | load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
30 | go_rules_dependencies()
31 | go_register_toolchains()
32 |
33 | rules_webtesting_ver = "936c760cff973a63031be0d0518b40a228e224e3"
34 | http_archive(
35 | name = "io_bazel_rules_webtesting",
36 | sha256 = "797b75e792a34728a6a3846c7c3d3ad669f12cd8490b888cc969bad93d236b1b",
37 | strip_prefix = "rules_webtesting-{v}".format(v=rules_webtesting_ver),
38 | url = "https://github.com/bazelbuild/rules_webtesting/archive/{v}.zip".format(v=rules_webtesting_ver),
39 | )
40 | load(
41 | "@io_bazel_rules_webtesting//web:repositories.bzl",
42 | "browser_repositories",
43 | "web_test_repositories",
44 | )
45 | web_test_repositories()
46 | browser_repositories(chromium = True)
47 |
48 | prettify_ver = "2013-03-04"
49 | new_http_archive(
50 | name = "prettify",
51 | build_file = "third_party/BUILD.prettify",
52 | strip_prefix = "code-prettify-{v}".format(v=prettify_ver),
53 | url = "https://github.com/google/code-prettify/archive/{v}.zip".format(v=prettify_ver),
54 | )
55 |
56 | new_http_archive(
57 | name = "polyfill",
58 | build_file = "third_party/BUILD.polyfill",
59 | sha256 = "9606cdeacbb67f21fb495a4b0a0e5ea6a137fc453945907822e1b930e77124d4",
60 | strip_prefix = "custom-elements-1.0.8",
61 | url = "https://github.com/webcomponents/custom-elements/archive/v1.0.8.zip",
62 | )
63 |
64 | es6shim_ver = "8d7aec1403751686dbbd3c4fa13a7bb584a75bf3"
65 | new_http_archive(
66 | name = "es6shim",
67 | build_file = "third_party/BUILD.es6shim",
68 | sha256 = "108e7de0edc041a36561e1518fc5d87569f5cfd5449977658cc9b1e2c84743b3",
69 | strip_prefix = "es6-shim-{v}".format(v=es6shim_ver),
70 | url = "https://github.com/es-shims/es6-shim/archive/{v}.zip".format(v=es6shim_ver),
71 | )
72 |
73 | git_repository(
74 | name = "io_bazel_rules_sass",
75 | remote = "https://github.com/bazelbuild/rules_sass.git",
76 | tag = "0.0.3",
77 | )
78 |
79 | load("@io_bazel_rules_sass//sass:sass.bzl", "sass_repositories")
80 |
81 | sass_repositories()
82 |
--------------------------------------------------------------------------------
/demo/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | exports_files(["LICENSE"])
6 |
7 | load("//tools:defs.bzl",
8 | "closure_js_library", "closure_js_binary", "closure_js_test")
9 |
10 | # All static artifacts needed for various demo files.
11 | # Used by //tools:server target.
12 | filegroup(
13 | name = "demo_files",
14 | srcs = glob([
15 | "*.html",
16 | "img/*.png",
17 | ]),
18 | )
19 |
20 | # A playground HelloElement.
21 | closure_js_library(
22 | name = "hello_lib",
23 | srcs = ["hello.js"],
24 | convention = "GOOGLE",
25 | )
26 |
27 | # Compiled version of HelloElement, suitable for distribution.
28 | closure_js_binary(
29 | name = "hello_bin",
30 | entry_points = ["googlecodelabs.HelloElement"],
31 | deps = [":hello_lib"],
32 | )
33 |
34 | # A few tests for HelloElement.
35 | # The following closure_js_test expands into something like this:
36 | #
37 | # js_test(
38 | # name = "hello_test",
39 | # srcs = ["hello_test.js"],
40 | # browsers = [
41 | # # For experimental purposes only. Eventually you should
42 | # # create your own browser definitions.
43 | # "@io_bazel_rules_webtesting//browsers:chromium-local",
44 | # ],
45 | # data = ["@polyfill//:custom_elements"],
46 | # entry_points = ["googlecodelabs.hello_test"],
47 | # suppress = ["JSC_EXTRA_REQUIRE_WARNING"],
48 | # deps = [
49 | # ":hello_lib",
50 | # "@io_bazel_rules_closure//closure/library:testing",
51 | # ],
52 | # )
53 | #
54 | # Additionally, a gen_hello_test.html file is generated,
55 | # declaring the hello_test_bin.js script source.
56 | closure_js_test(
57 | name = "hello_test",
58 | srcs = ["hello_test.js"],
59 | entry_points = ["googlecodelabs.hello_test"],
60 | deps = [":hello_lib"],
61 | )
62 |
--------------------------------------------------------------------------------
/demo/embed.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Embeddable codelab demo
23 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
54 |
55 |
56 |
57 |
58 |
59 |
Codelabs > Build Google Maps Using Web Components & No Code!
60 |
61 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quis dolor vel arcu blandit tristique. Proin vestibulum nec felis non fringilla. Pellentesque vulputate dui ut risus bibendum, sed egestas arcu ullamcorper. Quisque eget eros pellentesque, aliquet tortor placerat, vehicula lectus. Fusce sit amet mattis turpis, et tempus orci. Vestibulum mauris velit, vulputate a risus quis, imperdiet hendrerit ante. Nunc sollicitudin risus tortor, ac venenatis sem volutpat malesuada. Mauris neque metus, ornare eget porta id, tincidunt vitae magna. In scelerisque quam auctor maximus pellentesque. Sed laoreet ex mi, vel lacinia urna consectetur id. Sed est quam, finibus eget orci in, vulputate tempus diam
62 |
63 |
71 |
72 |
73 |
74 |
In this codelab, You'll create a fully working Google Maps app using elements in Polymer's Google Web Components collection. The app will be responsive, include driving directions, and transit a mode. Along the way, you'll also learn about Polymer's data-binding features and iron element set.
75 | What you'll learn
76 |
77 | How to start a new Polymer-based project using Chrome Dev Editor
78 | How to use elements in Polymer's iron, paper, and Google Web Component sets.
79 | How to use Polymer's data binding features to reduce boilerplate code.
80 |
81 | What you'll need
82 |
83 | Basic understanding of HTML, CSS, and web development.
84 | Install Chrome Dev Editor or use your own editor of choice.
85 |
86 | How would rate your experience with Polymer?
87 | Novice Intermediate Advanced
88 |
89 |
90 |
91 |
92 |
93 | Create a new project
94 |
95 | This codelab uses Chrome Dev Editor, a Chrome app IDE. If you don't have it installed yet, please install it from Chrome Web Store.
96 | Download Chrome Dev Editor
97 | The first time you run Chrome Dev Editor it will ask you to setup your workspace environment.
Fire up Chrome Dev Editor and start a new project:
98 | Click to start a new project.
99 | Enter "PolymerMapsCodelab" as the Project name .
100 | In the Project type dropdown, select "JavaScript web app (using Polymer paper elements)".
101 | Click the Create button.
102 |
Chrome Dev Editor creates a basic scaffold for your Polymer app. In the background, it also uses Bower to download and install a list of dependencies (including the Polymer core library) into the bower_components
/ folder. Fetching the components make take some time if your internet connection is slow . You'll learn more about using Bower in the next step.
103 | bower.json
104 |
105 | PolymerMapsCodelab/
106 | bower_components/ <!-- installed dependencies from Bower -->
107 | bower.json <!-- Bower metadata files used for managing deps -->
108 | index.html <!-- your app -->
109 | main.js
110 | styles.css
111 |
112 | Preview the app
113 | At any point, select the index.html file and hit the button in the top toolbar to run the app. Chrome Dev Editor fires up a web server and navigates to the index.html page. This is great way to preview changes as you make them.
114 | Next up
115 | At this point the app doesn't do much. Let's add a map!
116 |
117 |
118 |
119 |
120 |
121 | The Google Web Components provide the <google-map>
element for declaratively rendering a Google Map. To use it, you first need to install it using Bower.
122 | What is Bower?
123 | Bower is a client-side package management tool that can be used with any web app. When working with Polymer, it simplifies the hassles of dependency management. Every component defines its own set of dependencies. When you use Bower to install a component, the component's dependencies are installed alongside it under bower_components/
.
124 |
125 | Install the element
126 | Normally, you'd run bower install GoogleWebComponents/google-map --save
on the command line to install <google-map>
and save it as a dependency. However, Chrome Dev Editor does not have a command line for running Bower commands. Instead, you need to manually edit bower.json
to include google-map
, then run Chrome Dev Editor's Bower Update feature. Bower Update checks the dependencies in bower.json
and installs any missing ones.
Edit bower.json
and add google-map
to the dependencies
object:
127 | "dependencies": {
128 | "iron-elements": "PolymerElements/iron-elements#^1.0.0",
129 | "paper-elements": "PolymerElements/paper-elements#^1.0.1",
130 | "google-map": "GoogleWebComponents/google-map#^1.0.3"
131 | }
132 |
133 | Right-click the bower.json
filename in the editor.
134 | Select Bower Update from the context menu.
135 |
The download may take few seconds. You can verify that <google-map>
(and any dependencies) were installed by checking that bower_components/google-map/
was created and populated.
136 | Use the element
137 | To employ <google-map>
, you need to:
138 | Use an HTML Import to load it in index.html
.
139 | Declare an instance of the element on the page.
140 | In index.html
, remove all other HTML imports in the <head> and replace them with a single import that loads google-map.html
:
141 | index.html
142 |
143 | <head>
144 | ....
145 | <script src="bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
146 | <link rel="import" href="bower_components/google-map/google-map.html">
147 | </head>
148 |
149 | Important : all imports need to come after webcomponents-lite.min.js
so the polyfill can properly load them.
150 | Next, replace the contents of <body>
with an instance of <google-map>
:
151 | index.html
152 |
153 | <body unresolved>
154 | <google-map latitude="37.779" longitude="-122.3892" zoom="13"></google-map>
155 | </body>
156 | As you can see, using <google-map>
is completely declarative! The map is centered using the latitude
and longitude
attributes and its zoom level is set by the zoom
attribute.
157 | Style the map
158 | If you run the app right now, nothing will display. In order for the map to properly display itself, you need to set its container (in this case, <body>
) to have a fixed height.
Open styles.css
and replace its contents with default styling:
159 | styles.css
160 |
161 | body, html {
162 | font-family: 'Roboto', Arial, sans-serif;
163 | height: 100%;
164 | margin: 0;
165 | }
166 |
167 | Add a marker
168 | <google-map>
supports adding map markers to the map by declaring <google-map-marker>
elements as children. The marker locations are also set using latitude
and longitude
attributes.
Back in index.html , add a draggable <google-map-marker>
to the map:
169 | index.html
170 |
171 | <google-map latitude="37.779" longitude="-122.3892" zoom="13" disable-default-ui>
172 | <google-map-marker latitude="37.779" longitude="-122.3892"
173 | title="Go Giants!" draggable="true"></google-map-marker>
174 | </google-map>
175 | Notice that we've also disabled the map's controls by setting disableDefaultUi
to true. Since it's a boolean property, its presence as an HTML attribute makes it truthy.
176 | Run the app
177 | If you haven't already done so, hit the button. At this point, you should see a map that takes up the entire viewport and has a single marker pin.
178 | Frequently Asked Questions
179 |
181 |
182 |
183 |
184 |
185 |
186 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quis dolor vel arcu blandit tristique. Proin vestibulum nec felis non fringilla. Pellentesque vulputate dui ut risus bibendum, sed egestas arcu ullamcorper. Quisque eget eros pellentesque, aliquet tortor placerat, vehicula lectus. Fusce sit amet mattis turpis, et tempus orci. Vestibulum mauris velit, vulputate a risus quis, imperdiet hendrerit ante. Nunc sollicitudin risus tortor, ac venenatis sem volutpat malesuada. Mauris neque metus, ornare eget porta id, tincidunt vitae magna. In scelerisque quam auctor maximus pellentesque. Sed laoreet ex mi, vel lacinia urna consectetur id. Sed est quam, finibus eget orci in, vulputate tempus diam
187 |
188 |
189 |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/demo/hello.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 | Hello Custom Elements
21 |
22 |
23 |
24 | Hello, I'm a custom element.
25 | If you see this static text, it means I haven't been upgraded. :(
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/demo/hello.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.HelloElement');
19 |
20 | /**
21 | * @extends {HTMLElement}
22 | */
23 | class HelloElement extends HTMLElement {
24 | /**
25 | * @export
26 | * @override
27 | */
28 | connectedCallback() {
29 | console.log("HelloElement: connected");
30 | this.innerHTML = "Hello. I have just been upgraded !";
31 | }
32 | }
33 |
34 | window.customElements.define('hello-element', HelloElement);
35 |
36 | exports = HelloElement;
37 |
--------------------------------------------------------------------------------
/demo/hello_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.hello_test');
19 | goog.setTestOnly();
20 |
21 | const HelloElement = goog.require('googlecodelabs.HelloElement');
22 | const testSuite = goog.require('goog.testing.testSuite');
23 | goog.require('goog.testing.asserts');
24 | goog.require('goog.testing.jsunit');
25 |
26 | testSuite({
27 | testHelloEquals() {
28 | const x = 6;
29 | assertEquals(6, x);
30 | },
31 |
32 | testHelloUpgraded() {
33 | const div = document.createElement('div');
34 | div.innerHTML = "static ";
35 | document.body.appendChild(div);
36 | let text = div.textContent;
37 | assert(`"${text}" does not end with 'upgraded!'`, text.endsWith("upgraded!"));
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/demo/img/156b5e3cc8373d55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/156b5e3cc8373d55.png
--------------------------------------------------------------------------------
/demo/img/166c3b4982e4a0ad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/166c3b4982e4a0ad.png
--------------------------------------------------------------------------------
/demo/img/1f454b6807700695.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/1f454b6807700695.png
--------------------------------------------------------------------------------
/demo/img/39b4e0371e9703e6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/39b4e0371e9703e6.png
--------------------------------------------------------------------------------
/demo/img/433870360ad308d4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/433870360ad308d4.png
--------------------------------------------------------------------------------
/demo/img/7656372ff6c6a0f7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/7656372ff6c6a0f7.png
--------------------------------------------------------------------------------
/demo/img/81347b12f83e4291.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/81347b12f83e4291.png
--------------------------------------------------------------------------------
/demo/img/8a959b48e233bc93.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/8a959b48e233bc93.png
--------------------------------------------------------------------------------
/demo/img/9efdf0d1258b78e4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/9efdf0d1258b78e4.png
--------------------------------------------------------------------------------
/demo/img/aa64e93e8151b642.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/aa64e93e8151b642.png
--------------------------------------------------------------------------------
/demo/img/ab9c361527825fac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/ab9c361527825fac.png
--------------------------------------------------------------------------------
/demo/img/b1728ef310c444f5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/b1728ef310c444f5.png
--------------------------------------------------------------------------------
/demo/img/bf15c2f18d7f945c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/bf15c2f18d7f945c.png
--------------------------------------------------------------------------------
/demo/img/cbfdd0302b611ab0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/cbfdd0302b611ab0.png
--------------------------------------------------------------------------------
/demo/img/cf095c2153306fa7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/cf095c2153306fa7.png
--------------------------------------------------------------------------------
/demo/img/daefd30e8a290df5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/daefd30e8a290df5.png
--------------------------------------------------------------------------------
/demo/img/dc07bbc9fcfe7c5b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/dc07bbc9fcfe7c5b.png
--------------------------------------------------------------------------------
/demo/img/ed4633f91ec1389f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/demo/img/ed4633f91ec1389f.png
--------------------------------------------------------------------------------
/google-codelab-analytics/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | exports_files(["LICENSE"])
6 |
7 | load("//tools:defs.bzl",
8 | "closure_js_library", "closure_js_binary", "closure_js_test")
9 |
10 |
11 | filegroup(
12 | name = "google_codelab_analytics_files",
13 | srcs = [
14 | ":google_codelab_analytics_bin",
15 | ],
16 | )
17 |
18 | # Codelab Analytics.
19 | closure_js_library(
20 | name = "google_codelab_analytics",
21 | srcs = [
22 | "google_codelab_analytics.js",
23 | "google_codelab_analytics_def.js"
24 | ],
25 | deps = [
26 | "@io_bazel_rules_closure//closure/library",
27 | ],
28 | )
29 |
30 | # Codelab Analytics Tests
31 | closure_js_test(
32 | name = "google_codelab_analytics_test",
33 | srcs = ["google_codelab_analytics_test.js"],
34 | entry_points = ["googlecodelabs.CodelabAnalyticsTest"],
35 | deps = [
36 | "@io_bazel_rules_closure//closure/library",
37 | ":google_codelab_analytics"
38 | ],
39 | )
40 |
41 | # Compiled version of GoogleCodelabAnalytics element, suitable for distribution.
42 | closure_js_binary(
43 | name = "google_codelab_analytics_bin",
44 | entry_points = ["googlecodelabs.CodelabAnalyticsDef"],
45 | deps = [":google_codelab_analytics"],
46 | )
47 |
--------------------------------------------------------------------------------
/google-codelab-analytics/google_codelab_analytics.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabAnalytics');
19 |
20 | const EventHandler = goog.require('goog.events.EventHandler');
21 |
22 | /**
23 | * The general codelab action event fired for trackable interactions.
24 | * @const string
25 | */
26 | const ACTION_EVENT = 'google-codelab-action';
27 |
28 | /**
29 | * The general codelab pageview event fired for trackable pageviews.
30 | * @const string
31 | */
32 | const PAGEVIEW_EVENT = 'google-codelab-pageview';
33 |
34 | /**
35 | * The Google Analytics ID. Analytics CE will not complete initialization
36 | * without a valid Analytics ID value set for this.
37 | * @const {string}
38 | */
39 | const GAID_ATTR = 'gaid';
40 |
41 | /**
42 | * The GAID defined by the current codelab.
43 | * @const {string}
44 | */
45 | const CODELAB_GAID_ATTR = 'codelab-gaid';
46 |
47 | /** @const {string} */
48 | const CODELAB_ENV_ATTR = 'environment';
49 |
50 | /** @const {string} */
51 | const CODELAB_CATEGORY_ATTR = 'category';
52 |
53 | /** @const {string} */
54 | const ANALYTICS_READY_ATTR = 'anayltics-ready';
55 |
56 | /**
57 | * A list of selectors whose elements are waiting for this to be set up.
58 | * @const Array
59 | */
60 | const DEPENDENT_SELECTORS = ['google-codelab'];
61 |
62 |
63 | /**
64 | * Event detail passed when firing ACTION_EVENT.
65 | *
66 | * @typedef {{
67 | * category: string,
68 | * action: string,
69 | * label: (?string|undefined),
70 | * value: (?number|undefined)
71 | * }}
72 | */
73 | let AnalyticsTrackingEvent;
74 |
75 | /**
76 | * Event detail passed when firing ACTION_EVENT.
77 | *
78 | * @typedef {{
79 | * page: string,
80 | * title: string,
81 | * }}
82 | */
83 | let AnalyticsPageview;
84 |
85 |
86 | /**
87 | * @extends {HTMLElement}
88 | * @suppress {reportUnknownTypes}
89 | */
90 | class CodelabAnalytics extends HTMLElement {
91 | /** @return {string} */
92 | static getTagName() { return 'google-codelab-analytics'; }
93 |
94 | constructor() {
95 | super();
96 |
97 | /** @private {boolean} */
98 | this.hasSetup_ = false;
99 |
100 | /** @private {?string} */
101 | this.gaid_;
102 |
103 | /**
104 | * @private {!EventHandler}
105 | * @const
106 | */
107 | this.eventHandler_ = new EventHandler();
108 |
109 | /**
110 | * @private {!EventHandler}
111 | * @const
112 | */
113 | this.pageviewEventHandler_ = new EventHandler();
114 |
115 | /** @private {?string} */
116 | this.codelabCategory_ = this.getAttribute(CODELAB_CATEGORY_ATTR) || '';
117 |
118 | /** @private {?string} */
119 | this.codelabEnv_ = this.getAttribute(CODELAB_ENV_ATTR) || '';
120 | }
121 |
122 | /**
123 | * @export
124 | * @override
125 | */
126 | connectedCallback() {
127 | this.gaid_ = this.getAttribute(GAID_ATTR) || '';
128 |
129 | if (this.hasSetup_ || !this.gaid_) {
130 | return;
131 | }
132 |
133 | if (!goog.isDef(window['ga'])) {
134 | this.initGAScript_().then((response) => {
135 | if (response) {
136 | this.init_();
137 | }
138 | });
139 | } else {
140 | this.init_();
141 | }
142 | }
143 |
144 | /** @private */
145 | init_() {
146 | this.createTrackers_();
147 | this.addEventListeners_();
148 | this.setAnalyticsReadyAttrs_();
149 | this.hasSetup_ = true;
150 | }
151 |
152 | /** @private */
153 | addEventListeners_() {
154 | this.eventHandler_.listen(document.body, ACTION_EVENT,
155 | (e) => {
156 | const detail = /** @type {AnalyticsTrackingEvent} */ (
157 | e.getBrowserEvent().detail);
158 | // Add tracking...
159 | this.trackEvent_(
160 | detail['category'], detail['action'], detail['label'],
161 | detail['value']);
162 | });
163 |
164 | this.pageviewEventHandler_.listen(document.body, PAGEVIEW_EVENT,
165 | (e) => {
166 | const detail = /** @type {AnalyticsPageview} */ (
167 | e.getBrowserEvent().detail);
168 | this.trackPageview_(detail['page'], detail['title']);
169 | });
170 | }
171 |
172 | /**
173 | * @return {!Array}
174 | * @export
175 | */
176 | static get observedAttributes() {
177 | return [CODELAB_GAID_ATTR, CODELAB_ENV_ATTR, CODELAB_CATEGORY_ATTR];
178 | }
179 |
180 | /**
181 | * @param {string} attr
182 | * @param {?string} oldValue
183 | * @param {?string} newValue
184 | * @param {?string} namespace
185 | * @export
186 | * @override
187 | */
188 | attributeChangedCallback(attr, oldValue, newValue, namespace) {
189 | switch (attr) {
190 | case GAID_ATTR:
191 | this.gaid_ = newValue;
192 | break;
193 | case CODELAB_GAID_ATTR:
194 | if (newValue && this.hasSetup_) {
195 | this.createCodelabGATracker_();
196 | }
197 | break;
198 | case CODELAB_ENV_ATTR:
199 | this.codelabEnv_ = newValue;
200 | break;
201 | case CODELAB_CATEGORY_ATTR:
202 | this.codelabCategory_ = newValue;
203 | break;
204 | }
205 | }
206 |
207 | /**
208 | * Fires an analytics tracking event to all configured trackers.
209 | * @param {string} category The event category.
210 | * @param {string=} opt_action The event action.
211 | * @param {?string=} opt_label The event label.
212 | * @param {?number=} opt_value The event value.
213 | * @private
214 | */
215 | trackEvent_(category, opt_action, opt_label, opt_value) {
216 | const params = {
217 | // Always event for trackEvent_ method
218 | 'hitType': 'event',
219 | 'dimension1': this.codelabEnv_,
220 | 'dimension2': this.codelabCategory_,
221 | 'eventCategory': category,
222 | 'eventAction': opt_action || '',
223 | 'eventLabel': opt_label || '',
224 | 'eventValue': opt_value || '',
225 | };
226 | this.gaSend_(params);
227 | }
228 |
229 | /**
230 | * @param {?string=} opt_page The page to track.
231 | * @param {?string=} opt_title The codelabs title.
232 | * @private
233 | */
234 | trackPageview_(opt_page, opt_title) {
235 | const params = {
236 | 'hitType': 'pageview',
237 | 'dimension1': this.codelabEnv_,
238 | 'dimension2': this.codelabCategory_,
239 | 'page': opt_page || '',
240 | 'title': opt_title || ''
241 | };
242 | this.gaSend_(params);
243 | }
244 |
245 | /**
246 | * Sets analytics ready attributes on dependent elements.
247 | */
248 | setAnalyticsReadyAttrs_() {
249 | DEPENDENT_SELECTORS.forEach((selector) => {
250 | document.querySelectorAll(selector).forEach((element) => {
251 | element.setAttribute(ANALYTICS_READY_ATTR, ANALYTICS_READY_ATTR);
252 | });
253 | });
254 | }
255 |
256 | /** @private */
257 | gaSend_(params) {
258 | window['ga'](function() {
259 | const trackers = window['ga'].getAll();
260 | trackers.forEach((tracker) => {
261 | tracker.send(params);
262 | });
263 | });
264 | }
265 |
266 | /**
267 | * @export
268 | * @override
269 | */
270 | disconnectedCallback() {
271 | this.eventHandler_.removeAll();
272 | }
273 |
274 | /**
275 | * @return {string}
276 | * @private
277 | */
278 | getGAView_() {
279 | let parts = location.search.substring(1).split('&');
280 | for (let i = 0; i < parts.length; i++) {
281 | let param = parts[i].split('=');
282 | if (param[0] === 'viewga') {
283 | return param[1];
284 | }
285 | }
286 | return '';
287 | }
288 |
289 | /**
290 | * @return {!Promise}
291 | * @export
292 | */
293 | static injectGAScript() {
294 | /** @type {!HTMLScriptElement} */
295 | const resource = /** @type {!HTMLScriptElement} */ (
296 | document.createElement('script'));
297 | resource.src = '//www.google-analytics.com/analytics.js';
298 | resource.async = false;
299 | return new Promise((resolve, reject) => {
300 | resource.onload = () => resolve(resource);
301 | resource.onerror = (event) => {
302 | // remove on error
303 | if (resource.parentNode) {
304 | resource.parentNode.removeChild(resource);
305 | }
306 | reject();
307 | };
308 | if (document.head) {
309 | document.head.appendChild(resource);
310 | }
311 | });
312 | }
313 |
314 | /**
315 | * @return {!Promise}
316 | * @private
317 | */
318 | async initGAScript_() {
319 | // This is a pretty-printed version of the function(i,s,o,g,r,a,m) script
320 | // provided by Google Analytics.
321 | window['GoogleAnalyticsObject'] = 'ga';
322 | window['ga'] = window['ga'] || function() {
323 | (window['ga']['q'] = window['ga']['q'] || []).push(arguments);
324 | };
325 | window['ga']['l'] = (new Date()).valueOf();
326 |
327 | try {
328 | return await CodelabAnalytics.injectGAScript();
329 | } catch(e) {
330 | return;
331 | }
332 | }
333 |
334 | /** @private */
335 | createTrackers_() {
336 | // The default tracker is given name 't0' per analytics.js dev docs.
337 | if (this.gaid_ && !this.isTrackerCreated_(this.gaid_)) {
338 | window['ga']('create', this.gaid_, 'auto');
339 | }
340 |
341 | const gaView = this.getGAView_();
342 | if (gaView && !this.isTrackerCreated_(gaView)) {
343 | window['ga']('create', gaView, 'auto', 'view');
344 | window['ga']('view.send', 'pageview');
345 | }
346 |
347 | this.createCodelabGATracker_();
348 | }
349 |
350 | /**
351 | * Creates a GA tracker specific to the codelab.
352 | * @private
353 | */
354 | createCodelabGATracker_() {
355 | const codelabGAId = this.getAttribute(CODELAB_GAID_ATTR);
356 | if (codelabGAId && !this.isTrackerCreated_(codelabGAId)) {
357 | window['ga']('create', codelabGAId, 'auto', 'codelabAccount');
358 | }
359 | }
360 |
361 | /**
362 | * @param {string} trackerId The tracker ID to check for.
363 | * @return {boolean}
364 | * @private
365 | */
366 | isTrackerCreated_(trackerId) {
367 | const allTrackers = window['ga'].getAll();
368 | let isCreated = false;
369 | allTrackers.forEach((tracker) => {
370 | if (tracker.get('trackingId') == trackerId) {
371 | isCreated = true;
372 | }
373 | });
374 | return isCreated;
375 | }
376 | }
377 |
378 | exports = CodelabAnalytics;
379 |
--------------------------------------------------------------------------------
/google-codelab-analytics/google_codelab_analytics_def.js:
--------------------------------------------------------------------------------
1 | goog.module('googlecodelabs.CodelabAnalyticsDef');
2 | const CodelabAnalytics = goog.require('googlecodelabs.CodelabAnalytics');
3 |
4 | try {
5 | window.customElements.define(CodelabAnalytics.getTagName(), CodelabAnalytics);
6 | } catch (e) {
7 | console.warn('googlecodelabs.CodelabAnalytics', e);
8 | }
--------------------------------------------------------------------------------
/google-codelab-analytics/google_codelab_analytics_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabAnalyticsTest');
19 | goog.setTestOnly();
20 |
21 | const CodelabAnalytics = goog.require('googlecodelabs.CodelabAnalytics');
22 | window.customElements.define(CodelabAnalytics.getTagName(), CodelabAnalytics);
23 | const MockControl = goog.require('goog.testing.MockControl');
24 | const dom = goog.require('goog.dom');
25 | const testSuite = goog.require('goog.testing.testSuite');
26 | goog.require('goog.testing.asserts');
27 | goog.require('goog.testing.jsunit');
28 |
29 | let mockControl;
30 | /**
31 | * Noop the inject function, we don't need to be going to the actual network
32 | * in tests
33 | */
34 | CodelabAnalytics.injectGAscript = () => {};
35 |
36 | testSuite({
37 |
38 | setUp() {
39 | mockControl = new MockControl();
40 | },
41 |
42 | tearDown() {
43 | dom.removeNode(document.body.querySelector('google-codelab-analytics'));
44 |
45 | mockControl.$resetAll();
46 | mockControl.$tearDown();
47 | },
48 |
49 | testGAIDAttr_InitsTracker() {
50 | const analytics = new CodelabAnalytics();
51 |
52 | // Need to mock as we don't have window.ga.getAll()
53 | const mockGetAll = mockControl.createFunctionMock('getAll');
54 | const mockCreate = mockControl.createFunctionMock('create');
55 | mockGetAll().$returns([]).$anyTimes();
56 | mockCreate().$once();
57 |
58 | analytics.setAttribute('gaid', 'UA-123');
59 |
60 | window['ga'] = (...args) => {
61 | if (['create', 'getAll'].indexOf(args[0]) !== -1) {
62 | window['ga'][args[0]]();
63 | }
64 | };
65 | window['ga']['getAll'] = mockGetAll;
66 | window['ga']['create'] = mockCreate;
67 |
68 | mockControl.$replayAll();
69 | document.body.appendChild(analytics);
70 | mockControl.$verifyAll();
71 | },
72 |
73 | testViewParam_InitsViewTracker() {
74 | const analytics = new CodelabAnalytics();
75 | analytics.setAttribute('gaid', 'UA-123');
76 |
77 | const loc = window.location;
78 | var newurl = loc.protocol + '//' + loc.host + loc.pathname +
79 | '?viewga=testView¶m2=hi';
80 | window.history.pushState({ path: newurl }, '', newurl);
81 |
82 | // Need to mock as we don't have window.ga.getAll()
83 | const mockGetAll = mockControl.createFunctionMock('getAll');
84 | const mockCreate = mockControl.createFunctionMock('create');
85 | mockGetAll().$returns([]).$anyTimes();
86 | // Creates 2 trackers (because of view param).
87 | mockCreate().$times(2);
88 |
89 | window['ga'] = (...args) => {
90 | if (['create', 'getAll'].indexOf(args[0]) !== -1) {
91 | window['ga'][args[0]]();
92 | }
93 | };
94 | window['ga']['getAll'] = mockGetAll;
95 | window['ga']['create'] = mockCreate;
96 |
97 | mockControl.$replayAll();
98 |
99 | document.body.appendChild(analytics);
100 |
101 | mockControl.$verifyAll();
102 | window.history.back();
103 | },
104 |
105 | testCodelabGAIDAttr_InitsCodelabTracker() {
106 | const analytics = new CodelabAnalytics();
107 | analytics.setAttribute('gaid', 'UA-123');
108 | // Need to mock as we don't have window.ga.getAll()
109 | const mockGetAll = mockControl.createFunctionMock('getAll');
110 | const mockCreate = mockControl.createFunctionMock('create');
111 | mockGetAll().$returns([]).$anyTimes();
112 | // Creates 2 trackers (because of codelab gaid attribute).
113 | mockCreate().$times(2);
114 |
115 | window['ga'] = (...args) => {
116 | if (['create', 'getAll'].indexOf(args[0]) !== -1) {
117 | window['ga'][args[0]]();
118 | }
119 | };
120 | window['ga']['getAll'] = mockGetAll;
121 | window['ga']['create'] = mockCreate;
122 |
123 | mockControl.$replayAll();
124 |
125 | document.body.appendChild(analytics);
126 | analytics.setAttribute('codelab-gaid', 'UA-456');
127 | mockControl.$verifyAll();
128 | },
129 |
130 | async testSetAnalyticsReadyAttrs() {
131 | const analytics = new CodelabAnalytics();
132 | analytics.setAttribute('gaid', 'UA-123');
133 | // Need to mock as we don't have window.ga.getAll()
134 | const mockGetAll = mockControl.createFunctionMock('getAll');
135 | const mockCreate = mockControl.createFunctionMock('create');
136 | mockGetAll().$returns([]).$anyTimes();
137 | // Creates 2 trackers (because of codelab gaid attribute).
138 | mockCreate().$times(2);
139 |
140 | window['ga'] = (...args) => {
141 | if (['create', 'getAll'].indexOf(args[0]) !== -1) {
142 | window['ga'][args[0]]();
143 | }
144 | };
145 | window['ga']['getAll'] = mockGetAll;
146 | window['ga']['create'] = mockCreate;
147 |
148 | mockControl.$replayAll();
149 |
150 | const codelabElement = document.createElement('google-codelab');
151 | document.body.appendChild(codelabElement);
152 | document.body.appendChild(analytics);
153 |
154 | // This is obviously awful, but for some reason
155 | // mockControl.$waitAndVerifyAll() isn't working with closure_js_test. See
156 | // https://github.com/bazelbuild/rules_closure/issues/316. Once that's
157 | // resolved we can use it in place of the timeout.
158 | setTimeout(() => {
159 | assertEquals('', codelabElement.getAttribute('analytics-ready'));
160 | }, 5000);
161 |
162 | },
163 |
164 | testPageviewEventDispatch_SendsPageViewTracking() {
165 |
166 | },
167 |
168 | testEventDispatch_SendsEventTracking() {
169 |
170 | },
171 |
172 | testCodelabAttributes_UpdatesTrackingParams() {
173 |
174 | }
175 | });
176 |
--------------------------------------------------------------------------------
/google-codelab-index/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | exports_files(["LICENSE"])
6 |
7 | load("//tools:defs.bzl",
8 | "closure_js_library", "closure_js_binary", "closure_js_test")
9 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_template_library")
10 | load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary", "sass_library")
11 |
12 | filegroup(
13 | name = "google_codelab_index_files",
14 | srcs = glob([
15 | "*.html",
16 | ]) + [
17 | ":google_codelab_index_scss_bin",
18 | ":google_codelab_index_bin",
19 | ],
20 | )
21 |
22 | # Codelabs
23 | closure_js_library(
24 | name = "google_codelab_index",
25 | srcs = [
26 | "google_codelab_index.js",
27 | "google_codelab_index_def.js"
28 | ],
29 | deps = [
30 | "@io_bazel_rules_closure//closure/library",
31 | ":google_codelab_index_soy",
32 | ":google_codelab_index_cards",
33 | ]
34 | )
35 |
36 | closure_js_library(
37 | name = "google_codelab_index_cards",
38 | srcs = [
39 | "google_codelab_index_cards.js",
40 | "google_codelab_index_cards_def.js"
41 | ],
42 | deps = [
43 | "@io_bazel_rules_closure//closure/library",
44 | ":google_codelab_index_soy",
45 | ]
46 | )
47 |
48 | # Compiled version of GoogleCodelabStep element, suitable for distribution.
49 | closure_js_binary(
50 | name = "google_codelab_index_bin",
51 | entry_points = [
52 | "googlecodelabs.CodelabIndex.CardsDef",
53 | "googlecodelabs.CodelabIndexDef",
54 | ],
55 | deps = [":google_codelab_index"],
56 | )
57 |
58 | sass_library(
59 | name = "categories_scss",
60 | srcs = ["_categories.scss"],
61 | )
62 |
63 | sass_library(
64 | name = "cards_scss",
65 | srcs = ["_cards.scss"],
66 | )
67 |
68 | sass_library(
69 | name = "google_codelab_scss",
70 | srcs = ["google_codelab.scss"],
71 | )
72 |
73 | sass_binary(
74 | name = "google_codelab_index_scss_bin",
75 | src = "google_codelab_index.scss",
76 | deps = [
77 | ":cards_scss",
78 | ":categories_scss",
79 | ]
80 | )
81 |
82 | closure_js_template_library(
83 | name = "google_codelab_index_soy",
84 | srcs = ["google_codelab_index.soy"]
85 | )
86 |
--------------------------------------------------------------------------------
/google-codelab-index/_cards.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | google-codelab-index-cards {
19 | display: flex;
20 | flex-wrap: wrap;
21 | justify-content: space-between;
22 | margin: 0 0 24px 0;
23 | }
24 |
25 | google-codelab-index-cards[num="1"],
26 | google-codelab-index-cards[num="2"] {
27 | justify-content: flex-start;
28 | }
29 |
30 | google-codelab-index-cards .card {
31 | position: relative;
32 | display: flex;
33 | flex-direction: column;
34 | flex: 1 0 330px;
35 | margin: 0 0 16px 0;
36 | background-color: #fff;
37 | max-width: 330px;
38 | flex-basis: 330px;
39 | flex-grow: 0;
40 | flex-shrink: 0;
41 | }
42 |
43 | google-codelab-index-cards[num="1"] .card,
44 | google-codelab-index-cards[num="2"] .card {
45 | margin-right: 8px;
46 | }
47 |
48 | google-codelab-index-cards .card .card-header {
49 | display: flex;
50 | justify-content: space-between;
51 | align-items: center;
52 | padding: 16px;
53 | }
54 |
55 | google-codelab-index-cards .card-header .category-icon {
56 | width: 40px;
57 | height: 40px;
58 | border-radius: 50%;
59 | background-color: white;
60 | background-size: 70%;
61 | background-position: 50% 50%;
62 | background-repeat: no-repeat;
63 | }
64 |
65 | google-codelab-index-cards .card-header .card-duration {
66 | display: flex;
67 | align-items: center;
68 | color: #FFFFFF;
69 | }
70 |
71 | google-codelab-index-cards .card-header .card-duration img {
72 | margin-right: 4px;
73 | }
74 |
75 | google-codelab-index-cards .card .card-description {
76 | padding: 16px 32px;
77 | flex: 1 0 auto;
78 | color: #3C4043
79 | }
80 |
81 | google-codelab-index-cards .card .card-footer {
82 | display: flex;
83 | align-items: center;
84 | justify-content: space-between;
85 | border-top: 1px solid #DADCE0;
86 | padding: 8px 16px;
87 | }
88 |
89 | google-codelab-index-cards .card .card-start {
90 | color: #5c5c5c;
91 | line-height: 1;
92 | letter-spacing: 0.01em;
93 | min-width: 5.14em;
94 | margin: 0 0.29em;
95 | font: inherit;
96 | text-transform: uppercase;
97 | outline-width: 0;
98 | border-radius: 3px;
99 | user-select: none;
100 | cursor: pointer;
101 | z-index: 0;
102 | padding: 0.7em 0.57em;
103 | font-family: 'Roboto', 'Noto', sans-serif;
104 | -webkit-font-smoothing: antialiased;
105 | font-size: 14px;
106 | font-weight: 500;
107 | display: inline-flex;
108 | align-items: center;
109 | justify-content: center;
110 | }
111 |
112 | google-codelab-index-cards .card .card-updated {
113 | font-size: 12px;
114 | color: #9AA0A6;
115 | line-height: 16px;
116 | text-align: right;
117 | }
118 |
119 | @import "categories";
120 |
121 | @media (min-width: 768px) {
122 | google-codelab-index-cards .card {
123 | min-height: 250px;
124 | }
125 | }
126 |
127 | @media (max-width: 1136px) and (min-width: 767px) {
128 | google-codelab-index-cards .card {
129 | flex: 0 0 33%;
130 | margin: 0 0% 2% 0;
131 | min-height: 200px;
132 | }
133 | }
134 |
135 | @media (max-width: 767px) {
136 | google-codelab-index-cards {
137 | flex-direction: column;
138 | align-items: center;
139 | margin: 0;
140 | }
141 |
142 | google-codelab-index-cards .card {
143 | margin: 0 0 16px;
144 | min-width: 330px;
145 | max-width: 100%;
146 | width: 100%;
147 | flex: 1 0 200px;
148 | }
149 | google-codelab-index-cards[num="1"] .card,
150 | google-codelab-index-cards[num="2"] .card {
151 | margin-right: 0;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/google-codelab-index/_categories.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /* Common colors used in more than one category. */
19 | $appsBackgroundColor: #D93025;
20 | $unityBackgroundColor: #5F6368;
21 | $webBackgroundColor: #1A73E8;
22 | /* Common icons used in more than one category. */
23 | $webIcon: 'web.svg';
24 |
25 | /* Mixins. */
26 | @mixin colorize-card($name, $color, $icon:false) {
27 | .#{$name}-bg {
28 | background-color: $color;
29 | }
30 |
31 | .#{$name}-icon {
32 | @if $icon {
33 | background-image: url(//codelabs.developers.google.com/images/icons/#{$icon});
34 | } @else {
35 | opacity: 0;
36 | }
37 | }
38 |
39 | google-codelab-index-cards .card .#{$name}-start {
40 | color: $color;
41 | &:hover,
42 | &:active {
43 | background: rgba($color, 0.1);
44 | }
45 | }
46 | }
47 |
48 | @include colorize-card('default', #5F6368);
49 | @include colorize-card('about', #9AA0A6, 'google_g.svg');
50 | @include colorize-card('ads', #188038, 'ads.png');
51 | @include colorize-card('analytics', #E37400, 'analytics.png');
52 | @include colorize-card('android', #71B958, 'android.svg');
53 | @include colorize-card('android-auto', #71B958, 'android.svg');
54 | @include colorize-card('android-tv', #71B958, 'android.svg');
55 | @include colorize-card('android-wear', #71B958, 'android.svg');
56 | @include colorize-card('android-things', #4B830D, 'android-things.svg');
57 | @include colorize-card('apps', $appsBackgroundColor, 'apps.png');
58 | @include colorize-card('assistant', $webBackgroundColor, 'assistant.png');
59 | @include colorize-card('blockly', #A142FA, 'blockly.png');
60 | @include colorize-card('brillo', $webBackgroundColor, 'brillo.png');
61 | @include colorize-card('cast', #5F6368, 'cast.png');
62 | @include colorize-card('chrome', $webBackgroundColor, 'chrome.svg');
63 | @include colorize-card('cloud', $webBackgroundColor, 'cloud.png');
64 | @include colorize-card('cloud-about', $webBackgroundColor, 'cloud.png');
65 | @include colorize-card('cloud-app-engine', $webBackgroundColor, 'appengine.png');
66 | @include colorize-card('cloud-appengine', $webBackgroundColor, 'appengine.png');
67 | @include colorize-card('cloud-cloud-tools', $webBackgroundColor, 'cloud.png');
68 | @include colorize-card('cloud-compute', $webBackgroundColor, 'cloud-compute.png');
69 | @include colorize-card('cloud-data', $webBackgroundColor, 'cloud-data.png');
70 | @include colorize-card('cloud-general', $webBackgroundColor, 'cloud.png');
71 | @include colorize-card('cloud-machine-learning', $webBackgroundColor, 'cloud-ml.png');
72 | @include colorize-card('cloud-mobile', $webBackgroundColor, 'cloud-mobile.png');
73 | @include colorize-card('cloud-monitoring', $webBackgroundColor, 'cloud-monitoring.png');
74 | @include colorize-card('cloud-networking', $webBackgroundColor, 'cloud-networking.png');
75 | @include colorize-card('cloud-others', $webBackgroundColor, 'cloud.png');
76 | @include colorize-card('cloud-security', $webBackgroundColor, 'cloud-security.png');
77 | @include colorize-card('cloud-web', $webBackgroundColor, $webIcon);
78 | @include colorize-card('design', #212121, 'design.svg');
79 | @include colorize-card('firebase', #E37400, 'firebase.png');
80 | @include colorize-card('firebase-web', $webBackgroundColor, $webIcon);
81 | @include colorize-card('flutter', #02569B, 'flutter.png');
82 | @include colorize-card('games', #A142FA, 'cardboard.png');
83 | @include colorize-card('iot', $unityBackgroundColor, 'iot.svg');
84 | @include colorize-card('nest', #5F6368, 'nest.png');
85 | @include colorize-card('openthread', #174EA6, 'openthread.png');
86 | @include colorize-card('play-games', #A142FA, 'cardboard.png');
87 | @include colorize-card('geo', $webBackgroundColor, 'geo.png');
88 | @include colorize-card('search', $webBackgroundColor, 'google_g.svg');
89 | @include colorize-card('sheets', $appsBackgroundColor, 'sheets.png');
90 | @include colorize-card('tensorflow', #E37400, 'tensorflow.png');
91 | @include colorize-card('unity', $unityBackgroundColor, 'unity.png');
92 | @include colorize-card('vr', #A142FA, 'cardboard.png');
93 | @include colorize-card('virtual-reality', #A142FA, 'vr.svg');
94 | @include colorize-card('weave', #188038, 'weave.svg');
95 | @include colorize-card('web', $webBackgroundColor, $webIcon);
96 |
--------------------------------------------------------------------------------
/google-codelab-index/google_codelab_index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabIndex');
19 |
20 | const Cards = goog.require('googlecodelabs.CodelabIndex.Cards');
21 | const Debouncer = goog.require('goog.async.Debouncer');
22 | const EventHandler = goog.require('goog.events.EventHandler');
23 | const Templates = goog.require('googlecodelabs.CodelabIndex.Templates');
24 | const dom = goog.require('goog.dom');
25 | const events = goog.require('goog.events');
26 | const soy = goog.require('goog.soy');
27 |
28 | /** @const {string} */
29 | const CATEGORY_ATTR = 'category';
30 |
31 | /** @const {string} */
32 | const CATEGORY_PARAM = 'cat';
33 |
34 | /** @const {string} */
35 | const SORT_ATTR = 'sort';
36 |
37 | /** @const {string} */
38 | const FILTER_ATTR = 'filter';
39 |
40 | /** @const {string} */
41 | const TAGS_ATTR = 'tags';
42 |
43 | /**
44 | * Interval over which to debounce in ms.
45 | * @const {number}
46 | */
47 | const SEARCH_DEBOUNCE_INTERVAL = 20;
48 |
49 | /**
50 | * @extends {HTMLElement}
51 | */
52 | class CodelabIndex extends HTMLElement {
53 | /** @return {string} */
54 | static getTagName() { return 'google-codelab-index'; }
55 |
56 | constructor() {
57 | super();
58 |
59 | /**
60 | * @private {!EventHandler}
61 | * @const
62 | */
63 | this.eventHandler_ = new EventHandler();
64 |
65 | /** @private {boolean} */
66 | this.hasSetup_ = false;
67 |
68 | /** @private {?Cards} */
69 | this.cards_ = null;
70 |
71 | /** @private {?Element} */
72 | this.sortBy_ = null;
73 |
74 | /** @private {?HTMLInputElement} */
75 | this.search_ = null;
76 |
77 | /** @private {?Element} */
78 | this.clearSearchBtn_ = null;
79 |
80 | /** @private {?HTMLSelectElement} */
81 | this.categoriesSelect_ = null;
82 |
83 | /**
84 | * @private {!Debouncer}
85 | * @const
86 | */
87 | this.searchDebouncer_ = new Debouncer(
88 | () => this.handleSearchDebounced_(), SEARCH_DEBOUNCE_INTERVAL);
89 |
90 | }
91 |
92 | /**
93 | * @export
94 | * @override
95 | */
96 | connectedCallback() {
97 | if (!this.hasSetup_) {
98 | this.setupDom_();
99 | }
100 |
101 | this.addEvents_();
102 |
103 | window.requestAnimationFrame(() => {
104 | document.body.removeAttribute('unresolved');
105 | });
106 | }
107 |
108 | /**
109 | * @export
110 | * @override
111 | */
112 | disconnectedCallback() {
113 | this.eventHandler_.removeAll();
114 | }
115 |
116 | /**
117 | * @private
118 | */
119 | addEvents_() {
120 | if (this.sortBy_) {
121 | const tabs = this.sortBy_.querySelector('#sort-by-tabs');
122 | if (tabs) {
123 | this.eventHandler_.listen(tabs, events.EventType.CLICK,
124 | (e) => {
125 | e.preventDefault();
126 | this.handleSortByClick_(e);
127 | });
128 | }
129 | }
130 |
131 | if (this.search_) {
132 | this.eventHandler_.listen(this.search_, events.EventType.KEYUP,
133 | () => this.handleSearch_());
134 | }
135 |
136 | if (this.clearSearchBtn_) {
137 | this.eventHandler_.listen(this.clearSearchBtn_, events.EventType.CLICK,
138 | () => this.clearSearch_());
139 | }
140 |
141 | if (this.categoriesSelect_) {
142 | this.eventHandler_.listen(this.categoriesSelect_, events.EventType.CHANGE,
143 | () => this.selectCategories_());
144 | }
145 | }
146 |
147 | /**
148 | * @private
149 | */
150 | selectCategories_() {
151 | if (this.cards_ && this.categoriesSelect_) {
152 | this.cards_.setAttribute(CATEGORY_ATTR, this.categoriesSelect_.value);
153 | }
154 | }
155 |
156 | /**
157 | * @private
158 | */
159 | clearSearch_() {
160 | if (this.search_) {
161 | this.search_.value = '';
162 | }
163 | this.handleSearch_();
164 | }
165 |
166 | /**
167 | * @private
168 | */
169 | handleSearch_() {
170 | window.requestAnimationFrame(() => this.searchDebouncer_.fire());
171 | }
172 |
173 | /**
174 | * @private
175 | */
176 | handleSearchDebounced_() {
177 | const search = /** @type {!HTMLInputElement} */ (this.search_);
178 | const val = search.value.trim();
179 | if (this.clearSearchBtn_) {
180 | if (val === '') {
181 | this.clearSearchBtn_.setAttribute('hide', '');
182 | } else {
183 | this.clearSearchBtn_.removeAttribute('hide');
184 | }
185 | }
186 |
187 | if (this.cards_) {
188 | this.cards_.setAttribute(FILTER_ATTR, val);
189 | }
190 | }
191 |
192 | /**
193 | * @param {!Event} e
194 | * @private
195 | */
196 | handleSortByClick_(e) {
197 | const target = /** @type {!Element} */ (e.target);
198 | const sort = target.getAttribute(SORT_ATTR);
199 | if (this.cards_) {
200 | this.cards_.setAttribute(SORT_ATTR, sort);
201 | }
202 | const selected = this.querySelector('[selected]');
203 | if (selected) {
204 | selected.removeAttribute('selected');
205 | }
206 | target.setAttribute('selected', '');
207 | }
208 |
209 | /**
210 | * @private
211 | */
212 | setupDom_() {
213 | const mainInner = this.querySelector('main .main-inner');
214 | if (!mainInner) {
215 | return;
216 | }
217 |
218 | this.search_ = /** @type {?HTMLInputElement} */ (
219 | document.querySelector('#search-field'));
220 | this.clearSearchBtn_ = document.querySelector('#clear-icon');
221 |
222 | const list = this.querySelector('main ul');
223 | let cards = /** @type {!Cards} */ (
224 | document.createElement('google-codelab-index-cards'));
225 |
226 | const url = new URL(document.location.toString());
227 | if (url.searchParams.has(TAGS_ATTR)) {
228 | cards.setAttribute(TAGS_ATTR,
229 | url.searchParams.getAll(TAGS_ATTR).join(','));
230 | }
231 |
232 | let selectedCategory = '';
233 | if (url.searchParams.has(CATEGORY_PARAM)) {
234 | const categories = url.searchParams.getAll(CATEGORY_PARAM);
235 | selectedCategory = categories[0].trim().toLowerCase();
236 | cards.setAttribute(CATEGORY_ATTR, categories.join(','));
237 | }
238 |
239 | let sort = 'alpha';
240 | if (url.searchParams.has(SORT_ATTR)) {
241 | sort = /** @type {string} */ (url.searchParams.get(SORT_ATTR));
242 | cards.setAttribute(SORT_ATTR, sort);
243 | }
244 |
245 | if (list) {
246 | [...list.querySelectorAll('a')].forEach((link) => {
247 | cards['addCard'](link);
248 | });
249 | dom.removeNode(list);
250 | dom.appendChild(mainInner, cards);
251 | } else {
252 | cards = mainInner.querySelector('google-codelab-index-cards');
253 | }
254 |
255 | if (cards) {
256 | const categories = new Set();
257 | [...cards.querySelectorAll('.card')].forEach((card) => {
258 | const category = card.getAttribute(CATEGORY_ATTR);
259 | if (category) {
260 | category.split(',').forEach((c) => {
261 | categories.add(c.trim());
262 | });
263 | }
264 | });
265 |
266 | const sortBy = soy.renderAsElement(Templates.sortby, {
267 | categories: Array.from(categories).sort(),
268 | selectedCategory: selectedCategory,
269 | sort: sort
270 | });
271 | sortBy.setAttribute('id', 'sort-by');
272 | dom.insertSiblingBefore(sortBy, cards);
273 |
274 | this.sortBy_ = sortBy;
275 | this.cards_ = /** @type {!Cards} */ (cards);
276 | this.categoriesSelect_ = /** @type {?HTMLSelectElement} */ (
277 | this.sortBy_.querySelector('#codelab-categories'));
278 |
279 | if (selectedCategory && this.categoriesSelect_) {
280 | [...this.categoriesSelect_.options].forEach((option) => {
281 | if (option.value.toLowerCase() === selectedCategory) {
282 | option.selected = true;
283 | }
284 | });
285 | }
286 | }
287 |
288 | if (url.searchParams.has(FILTER_ATTR)) {
289 | const filter = /** @type {string} */ (url.searchParams.get(FILTER_ATTR));
290 | if (this.search_) {
291 | this.search_.value = filter;
292 | this.handleSearch_();
293 | }
294 | }
295 |
296 | this.hasSetup_ = true;
297 | }
298 | }
299 |
300 | exports = CodelabIndex;
301 |
--------------------------------------------------------------------------------
/google-codelab-index/google_codelab_index.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | html, body {
19 | height: 100%;
20 | width: 100%;
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | body {
26 | transition: opacity ease-in 0.2s;
27 | padding-top: 64px;
28 | background: #ECEFF1;
29 | font-family: 'Roboto', 'Noto', sans-serif;
30 | -webkit-font-smoothing: antialiased;
31 | font-size: 16px;
32 | font-weight: 400;
33 | line-height: 24px;
34 | color: #5c5c5c;
35 | }
36 |
37 | * {
38 | box-sizing: border-box;
39 | margin: 0;
40 | }
41 |
42 | a {
43 | text-decoration: none;
44 | }
45 |
46 | [hidden] {
47 | display: none !important;
48 | }
49 |
50 | #toolbar {
51 | display: flex;
52 | align-items: center;
53 | height: 64px;
54 | background-color: #455a64;
55 | color: #fff;
56 | padding: 16px;
57 | position: fixed;
58 | left: 0;
59 | right: 0;
60 | top: 0;
61 | z-index: 10000;
62 | }
63 |
64 | #toolbar .site-width {
65 | display: flex;
66 | align-items: center;
67 | }
68 |
69 | #toolbar .logo {
70 | display: flex;
71 | align-items: center;
72 | }
73 |
74 | #toolbar .logo-icon {
75 | margin-right: 16px;
76 | width: 30px;
77 | height: 24px;
78 | }
79 |
80 | #toolbar .logo-devs {
81 | width: 216px;
82 | height: 36px;
83 | margin-top: 5px;
84 | }
85 |
86 | #searchbar {
87 | background-color: #546E7A;
88 | transition: background-color 400ms cubic-bezier(0, 0, 0.2, 1);
89 | flex-grow: 1;
90 | display: flex;
91 | align-items: center;
92 | }
93 |
94 | #searchbar:hover {
95 | background-color: #78909C;
96 | }
97 |
98 | #searchbar input {
99 | border: 0;
100 | background: none;
101 | outline: 0;
102 | width: 100%;
103 | font-size: 16px;
104 | font-weight: 400;
105 | color: #fff;
106 | line-height: 40px;
107 | height: 40px;
108 | flex-grow: 1;
109 | z-index: 1;
110 | }
111 |
112 | #searchbar input::-webkit-input-placeholder {
113 | color: rgba(255,255,255,.5);
114 | }
115 | #searchbar input:-ms-input-placeholder {
116 | color: rgba(255,255,255,.5);
117 | }
118 |
119 | #search-icon,
120 | #clear-icon {
121 | width: 40px;
122 | height: 40px;
123 | display: flex;
124 | align-items: center;
125 | justify-content: center;
126 | flex-shrink: 0;
127 | color: #fff;
128 | }
129 |
130 | #clear-icon[hide] {
131 | visibility: hidden;
132 | pointer-events: none;
133 | }
134 |
135 | .site-width {
136 | margin: 0 auto;
137 | width: 90vw;
138 | max-width: 1024px;
139 | }
140 |
141 | #banner {
142 | background-color: #fff;
143 | padding: 0 40px 0 40px;
144 | padding: 40px 0 48px 0;
145 | box-shadow: 0px 3px 6px -3px #BDBDBD;
146 | }
147 |
148 | #banner p {
149 | line-height: 32px;
150 | }
151 |
152 | h2 {
153 | margin: 0;
154 | font-family: 'Roboto', 'Noto', sans-serif;
155 | -webkit-font-smoothing: antialiased;
156 | font-size: 45px;
157 | font-weight: 400;
158 | letter-spacing: -.018em;
159 | line-height: 48px;
160 | }
161 |
162 | #banner h2, #banner h3 {
163 | font-weight: 300;
164 | }
165 |
166 | #sort-by {
167 | padding: 24px 0;
168 | display: flex;
169 | justify-content: space-between;
170 | align-items: center;
171 | }
172 |
173 | #sort-by .sort-by-inner {
174 | display: flex;
175 | align-items: center;
176 | flex-shrink: 0;
177 | }
178 |
179 | #sort-by-tabs a {
180 | color: #98999a;
181 | text-transform: uppercase;
182 | line-height: 48px;
183 | font-weight: 500;
184 | font-size: 14px;
185 | padding: 0 12px;
186 | display: flex;
187 | border-bottom: 2px solid transparent;
188 | }
189 |
190 | #sort-by-tabs a:hover {
191 | background-color: #e0e0e0;
192 | }
193 |
194 | #sort-by-tabs a[selected] {
195 | color: #616161;
196 | border-bottom: 2px solid #616161;
197 | }
198 |
199 | #codelab-categories {
200 | border: none;
201 | background: #fff;
202 | padding: 8px;
203 | appearance: none;
204 | -webkit-appearance:none;
205 | font-size: 16px;
206 | line-height: 24px;
207 | color: #212121;
208 | border-radius: 0;
209 | position: relative;
210 | }
211 |
212 | #codelab-categories:focus {
213 | outline: 0;
214 | }
215 |
216 | footer {
217 | background-color: #424242;
218 | font-size: 13px;
219 | }
220 |
221 | footer .footer-wrapper {
222 | display: flex;
223 | padding: 40px 0;
224 | }
225 |
226 | footer .link-list {
227 | border-top: 1px solid #616161;
228 | flex: 1;
229 | margin-right: 32px;
230 | }
231 |
232 | footer .link-list label {
233 | display: block;
234 | color: #fff;
235 | margin: 8px 0 16px;
236 | }
237 |
238 | footer ul, footer li {
239 | list-style: none;
240 | margin: 0;
241 | padding: 0;
242 | }
243 |
244 | footer a {
245 | color: #9e9e9e;
246 | }
247 |
248 | footer .footerbar {
249 | background-color: #616161;
250 | color: #fff;
251 | font-size: 13px;
252 | padding: 10px 0;
253 | }
254 |
255 | footer .footerbar a {
256 | color: currentcolor;
257 | }
258 |
259 | @import "cards";
260 |
261 | @media (min-width: 768px) {
262 | #toolbar .logo-devs {
263 | margin: 5px 32px 0 0;
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/google-codelab-index/google_codelab_index.soy:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | {namespace googlecodelabs.CodelabIndex.Templates}
19 |
20 | /**
21 | * Renders the card
22 | */
23 | {template .card}
24 | {@param title: string}
25 | {@param category: string}
26 | {@param duration: number}
27 | {@param updated: string}
28 | {@param author: string}
29 |
38 |
39 | {$title}
40 |
41 |
48 | {/template}
49 |
50 |
51 | {template .sortby}
52 | {@param sort: string}
53 | {@param categories: list}
54 |
59 |
60 |
61 | Category
62 | {for $i in range(length($categories))}
63 | {$categories[$i]}
64 | {/for}
65 |
66 |
67 | {/template}
68 |
--------------------------------------------------------------------------------
/google-codelab-index/google_codelab_index_cards.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabIndex.Cards');
19 |
20 | const HTML5LocalStorage = goog.require('goog.storage.mechanism.HTML5LocalStorage');
21 | const Templates = goog.require('googlecodelabs.CodelabIndex.Templates');
22 | const dom = goog.require('goog.dom');
23 | const soy = goog.require('goog.soy');
24 |
25 | /** @const {string} */
26 | const AUTHOR_ATTR = 'author';
27 |
28 | /** @const {string} */
29 | const CATEGORY_ATTR = 'category';
30 |
31 | /** @const {string} */
32 | const CATEGORY_PARAM = 'cat';
33 |
34 | /** @const {string} */
35 | const TITLE_ATTR = 'title';
36 |
37 | /** @const {string} */
38 | const DURATION_ATTR = 'duration';
39 |
40 | /** @const {string} */
41 | const UPDATED_ATTR = 'updated';
42 |
43 | /** @const {string} */
44 | const TAGS_ATTR = 'tags';
45 |
46 | /** @const {string} */
47 | const SORT_ATTR = 'sort';
48 |
49 | /** @const {string} */
50 | const FILTER_ATTR = 'filter';
51 |
52 | /** @const {string} */
53 | const SORT_ALPHA = 'alpha';
54 |
55 | /** @const {string} */
56 | const SORT_RECENT = 'recent';
57 |
58 | /** @const {string} */
59 | const SORT_DURATION = 'duration';
60 |
61 | /** @const {string} */
62 | const HIDDEN_ATTR = 'hidden';
63 |
64 | /** @const {string} */
65 | const PROGRESS_ATTR = 'progress';
66 |
67 | /** @const {string} */
68 | const STEPS_ATTR = 'steps';
69 |
70 | /** @const {string} */
71 | const NUM_ATTR = 'num';
72 |
73 | /**
74 | * @extends {HTMLElement}
75 | */
76 | class Cards extends HTMLElement {
77 | /** @return {string} */
78 | static getTagName() { return 'google-codelab-index-cards'; }
79 |
80 | constructor() {
81 | super();
82 |
83 | /**
84 | * @private {!HTML5LocalStorage}
85 | * @const
86 | */
87 | this.storage_ = new HTML5LocalStorage();
88 | }
89 |
90 | /**
91 | * @export
92 | * @override
93 | */
94 | connectedCallback() {
95 | if (!this.hasAttribute(SORT_ATTR)) {
96 | this.setAttribute(SORT_ATTR, SORT_ALPHA);
97 | } else {
98 | this.sort_();
99 | }
100 |
101 | if (this.hasAttribute(FILTER_ATTR) ||
102 | this.hasAttribute(CATEGORY_ATTR) ||
103 | this.hasAttribute(TAGS_ATTR)) {
104 | this.filter_();
105 | }
106 | }
107 |
108 | /**
109 | * @return {!Array}
110 | * @export
111 | */
112 | static get observedAttributes() {
113 | return [SORT_ATTR, FILTER_ATTR, CATEGORY_ATTR, TAGS_ATTR];
114 | }
115 |
116 | /**
117 | * @param {string} attr
118 | * @param {?string} oldValue
119 | * @param {?string} newValue
120 | * @param {?string} namespace
121 | * @export
122 | * @override
123 | */
124 | attributeChangedCallback(attr, oldValue, newValue, namespace) {
125 | switch (attr) {
126 | case SORT_ATTR:
127 | this.sort_();
128 | break;
129 | case FILTER_ATTR:
130 | case CATEGORY_ATTR:
131 | case TAGS_ATTR:
132 | this.filter_();
133 | break;
134 | }
135 | }
136 |
137 |
138 | /**
139 | * @private
140 | */
141 | sort_() {
142 | let sort = this.getAttribute(SORT_ATTR) || SORT_ALPHA;
143 | const cards = [...this.querySelectorAll('.card')];
144 | if (cards.length < 2) {
145 | // No point sorting 0 or 1 items.
146 | return;
147 | }
148 |
149 | switch (sort) {
150 | case SORT_DURATION:
151 | cards.sort(this.sortDuration_.bind(this));
152 | break;
153 | case SORT_RECENT:
154 | cards.sort(this.sortRecent_.bind(this));
155 | break;
156 | case SORT_ALPHA:
157 | default:
158 | sort = SORT_ALPHA;
159 | cards.sort(this.sortAlpha_.bind(this));
160 | break;
161 | }
162 |
163 | cards.forEach((card) => this.appendChild(card));
164 |
165 | const url = new URL(document.location.toString());
166 | if (!sort || sort === SORT_ALPHA) {
167 | url.searchParams.delete(SORT_ATTR);
168 | } else {
169 | url.searchParams.set(SORT_ATTR, sort);
170 | }
171 |
172 | this.setNum_();
173 |
174 | const path = `${url.pathname}${url.search}`;
175 | window.history.replaceState({path}, document.title, path);
176 | }
177 |
178 | /**
179 | * @private
180 | */
181 | setNum_() {
182 | const num = this.querySelectorAll(`.card:not([${HIDDEN_ATTR}])`).length;
183 | this.setAttribute(NUM_ATTR, num);
184 | }
185 |
186 | /**
187 | * @param {!Element} a
188 | * @param {!Element} b
189 | * @return {number}
190 | * @private
191 | */
192 | sortDuration_(a, b) {
193 | if (!a || !b) {
194 | return 0;
195 | }
196 | const aDuration = parseFloat(a.getAttribute(DURATION_ATTR)) || 0;
197 | const bDuration = parseFloat(b.getAttribute(DURATION_ATTR)) || 0;
198 | const diff = aDuration - bDuration;
199 | if (diff === 0) {
200 | return this.sortRecent_(a, b);
201 | } else {
202 | return diff;
203 | }
204 | }
205 |
206 | /**
207 | * @param {!Element} a
208 | * @param {!Element} b
209 | * @return {number}
210 | * @private
211 | */
212 | sortRecent_(a, b) {
213 | if (!a || !b) {
214 | return 0;
215 | }
216 | const aUpdated = new Date(a.getAttribute(UPDATED_ATTR) || 0);
217 | const bUpdated = new Date(b.getAttribute(UPDATED_ATTR) || 0);
218 | const diff = bUpdated.getTime() - aUpdated.getTime();
219 | if (diff === 0) {
220 | return this.sortAlpha_(a, b);
221 | } else {
222 | return diff;
223 | }
224 | }
225 |
226 | /**
227 | * @param {!Element} a
228 | * @param {!Element} b
229 | * @return {number}
230 | * @private
231 | */
232 | sortAlpha_(a, b) {
233 | if (!a || !b) {
234 | return 0;
235 | }
236 | const aTitle = a.getAttribute(TITLE_ATTR);
237 | const bTitle = b.getAttribute(TITLE_ATTR);
238 | if (aTitle < bTitle) {
239 | return -1;
240 | } else if (aTitle > bTitle) {
241 | return 1;
242 | } else {
243 | return 0;
244 | }
245 | }
246 |
247 | /**
248 | * @private
249 | */
250 | filter_() {
251 | const filter = this.normalizeValue_(this.getAttribute(FILTER_ATTR));
252 | const tags = this.cleanStrings_(
253 | (this.getAttribute(TAGS_ATTR) || '').split(','));
254 | const categories = this.cleanStrings_(
255 | (this.getAttribute(CATEGORY_ATTR) || '').split(','));
256 |
257 | const cards = [...this.querySelectorAll('.card')];
258 | cards.forEach((card) => {
259 | const title = this.normalizeValue_(card.getAttribute(TITLE_ATTR));
260 | const cardCategories = this.cleanStrings_(
261 | (card.getAttribute(CATEGORY_ATTR) || '').split(','));
262 | const cardTags = this.cleanStrings_(
263 | (card.getAttribute(TAGS_ATTR) || '').split(','));
264 |
265 | let matchesFilter = true;
266 | let matchesTags = true;
267 | let matchesCategory = true;
268 |
269 | if (filter) {
270 | matchesFilter = title.indexOf(filter) !== -1;
271 | }
272 |
273 | if (tags.length) {
274 | matchesTags = this.arrayContains_(cardTags, tags);
275 | }
276 |
277 | if (categories.length) {
278 | matchesCategory = this.arrayContains_(cardCategories, categories);
279 | }
280 |
281 | if (matchesFilter && matchesTags && matchesCategory) {
282 | card.removeAttribute(HIDDEN_ATTR);
283 | } else {
284 | card.setAttribute(HIDDEN_ATTR, '');
285 | }
286 | });
287 |
288 | const url = new URL(document.location.toString());
289 | if (tags.length) {
290 | url.searchParams.set(TAGS_ATTR, tags.join(','));
291 | } else {
292 | url.searchParams.delete(TAGS_ATTR);
293 | }
294 |
295 | if (categories.length) {
296 | url.searchParams.set(CATEGORY_PARAM, categories.join(','));
297 | } else {
298 | url.searchParams.delete(CATEGORY_PARAM);
299 | }
300 |
301 | if (filter) {
302 | url.searchParams.set(FILTER_ATTR, filter);
303 | } else {
304 | url.searchParams.delete(FILTER_ATTR);
305 | }
306 |
307 | this.setNum_();
308 |
309 | const path = `${url.pathname}${url.search}`;
310 | window.history.replaceState({path}, document.title, path);
311 | }
312 |
313 | /**
314 | * Returns true if any of the items in A are in B.
315 | * @param {!Array} a
316 | * @param {!Array} b
317 | * @return {boolean}
318 | * @private
319 | */
320 | arrayContains_(a, b) {
321 | for (let i = 0; i < a.length; i++) {
322 | if (b.includes(a[i])) {
323 | return true;
324 | }
325 | }
326 | return false;
327 | }
328 |
329 | /**
330 | * @param {string} category
331 | * @return {string}
332 | * @private
333 | */
334 | normalizeCategory_(category) {
335 | return category.toLowerCase()
336 | .replace(/\s+/g, '-') // Replace spaces with -
337 | .replace(/--+/g, '-') // Replace multiple - with single -
338 | .trim().split(',').shift();
339 | }
340 |
341 | /**
342 | * Trims whitespace and converts to lower case.
343 | * @param {string|undefined} v
344 | * @return {string}
345 | * @private
346 | */
347 | normalizeValue_(v) {
348 | return (v || '').trim().toLowerCase()
349 | .replace('\n', '')
350 | .replace(/\s+/g, ' ');
351 | }
352 |
353 | /**
354 | * @param {!Array} strings
355 | * @return {!Array}
356 | * @private
357 | */
358 | cleanStrings_(strings) {
359 | strings = strings || [];
360 | const a = [];
361 | strings.forEach((s) => {
362 | const v = this.normalizeValue_(s);
363 | if (v) {
364 | a.push(v);
365 | }
366 | });
367 | return a.sort();
368 | }
369 |
370 | /**
371 | * @param {!HTMLAnchorElement} link
372 | * @export
373 | */
374 | addCard(link) {
375 | const info = {
376 | category: this.normalizeCategory_(link.getAttribute(CATEGORY_ATTR) || ''),
377 | title: link.getAttribute(TITLE_ATTR) || '',
378 | duration: parseInt(link.getAttribute(DURATION_ATTR), 10) || 0,
379 | updated: this.prettyDate_(link.getAttribute(UPDATED_ATTR)) || '',
380 | tags: link.getAttribute(TAGS_ATTR) || '',
381 | author: link.getAttribute(AUTHOR_ATTR) || ''
382 | };
383 | soy.renderElement(link, Templates.card, info);
384 | link.classList.add('card');
385 | this.addHomeLinkForCard_(link);
386 | this.showProgressForCard_(link);
387 | this.appendChild(link);
388 | }
389 |
390 | /**
391 | * @param {!HTMLAnchorElement} link
392 | * @private
393 | */
394 | addHomeLinkForCard_(link) {
395 | const url = new URL(link.href, document.location.origin);
396 | if (!url.searchParams.has('index')) {
397 | url.searchParams.set('index', document.location.pathname);
398 | }
399 | dom.safe.setAnchorHref(link, url.href);
400 | }
401 |
402 | /**
403 | * @param {!Element} link
404 | * @private
405 | */
406 | showProgressForCard_(link) {
407 | const id = link.getAttribute('id');
408 | if (id) {
409 | const progress = this.storage_.get(`progress_${id}`);
410 | const steps = link.getAttribute(STEPS_ATTR);
411 | if (progress && steps) {
412 | link.setAttribute(PROGRESS_ATTR,
413 | (parseFloat(progress) / parseFloat(steps) - 1).toFixed(2));
414 | }
415 | }
416 | }
417 |
418 | /**
419 | * @param {string} updated
420 | * @returns {string}
421 | * @private
422 | */
423 | prettyDate_(updated) {
424 | if (!updated) {
425 | return '';
426 | }
427 | const mNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
428 | 'Sep', 'Oct', 'Nov', 'Dec'];
429 | const d = new Date(updated);
430 | return mNames[d.getMonth()] + ' ' + d.getUTCDate() + ', ' + d.getFullYear();
431 | };
432 | }
433 |
434 | exports = Cards;
435 |
--------------------------------------------------------------------------------
/google-codelab-index/google_codelab_index_cards_def.js:
--------------------------------------------------------------------------------
1 | goog.module('googlecodelabs.CodelabIndex.CardsDef');
2 | const CodelabIndexCards = goog.require('googlecodelabs.CodelabIndex.Cards');
3 |
4 | try {
5 | window.customElements.define(CodelabIndexCards.getTagName(), CodelabIndexCards);
6 | } catch (e) {
7 | console.warn('googlecodelabs.CodelabIndex.Cards', e);
8 | }
--------------------------------------------------------------------------------
/google-codelab-index/google_codelab_index_def.js:
--------------------------------------------------------------------------------
1 | goog.module('googlecodelabs.CodelabIndexDef');
2 | const CodelabIndex = goog.require('googlecodelabs.CodelabIndex');
3 |
4 | try {
5 | window.customElements.define(CodelabIndex.getTagName(), CodelabIndex);
6 | } catch (e) {
7 | console.warn('googlecodelabs.CodelabIndex', e);
8 | }
--------------------------------------------------------------------------------
/google-codelab-step/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | exports_files(["LICENSE"])
6 |
7 | load("//tools:defs.bzl",
8 | "closure_js_library", "closure_js_binary", "closure_js_test")
9 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_template_library")
10 | load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary", "sass_library")
11 |
12 |
13 | filegroup(
14 | name = "google_codelab_step_files",
15 | srcs = glob([
16 | "*.html",
17 | "*.png",
18 | ]) + [
19 | ":google_codelab_step_scss_bin",
20 | ":google_codelab_step_bin",
21 | ],
22 | )
23 |
24 | # Codelab step.
25 | closure_js_library(
26 | name = "google_codelab_step",
27 | srcs = [
28 | "google_codelab_step.js",
29 | "google_codelab_step_def.js"
30 | ],
31 | deps = [
32 | "@io_bazel_rules_closure//closure/library",
33 | ":google_codelab_step_soy",
34 | ],
35 | )
36 |
37 | # Compiled version of GoogleCodelabStep element, suitable for distribution.
38 | closure_js_binary(
39 | name = "google_codelab_step_bin",
40 | entry_points = ["googlecodelabs.CodelabStepDef"],
41 | deps = [":google_codelab_step"],
42 | )
43 |
44 | sass_library(
45 | name = "syntax",
46 | srcs = ["_syntax.scss"],
47 | )
48 |
49 | sass_binary(
50 | name = "google_codelab_step_scss_bin",
51 | src = "google_codelab_step.scss",
52 | deps = [
53 | ":syntax",
54 | ]
55 | )
56 |
57 | closure_js_template_library(
58 | name = "google_codelab_step_soy",
59 | srcs = ["google_codelab_step.soy"]
60 | )
61 |
62 | closure_js_test(
63 | name = "google_codelab_step_test",
64 | srcs = ["google_codelab_step_test.js"],
65 | entry_points = ["googlecodelabs.CodelabStepTest"],
66 | deps = [":google_codelab_step"],
67 | )
68 |
--------------------------------------------------------------------------------
/google-codelab-step/_syntax.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | pre, code {
19 | font-family: 'Source Code Pro', Helvetica, Arial;
20 | font-size: inherit;
21 | border-radius: 4px;
22 | overflow-x: auto;
23 | overflow-y: visible;
24 | }
25 | code {
26 | background-color: #E8EAEd;
27 | padding: 0.1em 0.3em;
28 | }
29 | pre {
30 | display: block;
31 | color: white;
32 | background-color: #28323f;
33 | padding: 14px;
34 | -webkit-text-size-adjust: none;
35 | line-height: 1.4;
36 | }
37 | pre > code {
38 | padding: 0;
39 | background-color: transparent;
40 | }
41 | code em {
42 | color: #97C8F2;
43 | }
44 | pre .str, code .str { color: #34A853; } /* string - green */
45 | pre .kwd, code .kwd { color: #F538A0; } /* keyword - dark pink */
46 | pre .com, code .com { color: #BDC1C6; font-style: italic; } /* comment - gray */
47 | pre .typ, code .typ { color: #24C1E0; } /* type - light blue */
48 | pre .lit, code .lit { color: #4285F4; } /* literal - blue */
49 | pre .pun, code .pun { color: #F8F9FA; } /* punctuation - white */
50 | pre .pln, code .pln { color: #F8F9FA; } /* plaintext - white */
51 | pre .tag, code .tag { color: #24C1E0; } /* html/xml tag - light blue */
52 | pre .atn, code .atn { color: #EDA912; } /* html/xml attribute name - khaki */
53 | pre .atv, code .atv { color: #34A853; } /* html/xml attribute value - green */
54 | pre .dec, code .dec { color: #5195EA; } /* decimal - blue */
55 |
56 | paper-button {
57 | display: inline-flex;
58 | align-items: center;
59 | justify-content: center;
60 | position: relative;
61 | box-sizing: border-box;
62 | min-width: 5.14em;
63 | margin: 0 0.29em;
64 | background: transparent;
65 | -webkit-tap-highlight-color: transparent;
66 | font: inherit;
67 | text-transform: uppercase;
68 | outline-width: 0;
69 | border-radius: 3px;
70 | user-select: none;
71 | cursor: pointer;
72 | z-index: 0;
73 | padding: 0.7em 0.57em;
74 | font-family: 'Roboto', 'Noto', sans-serif;
75 | -webkit-font-smoothing: antialiased;
76 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
77 | }
78 |
--------------------------------------------------------------------------------
/google-codelab-step/google-codelab-step.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | Google Codelab Step
22 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | What you'll learn
37 |
38 | How to start a new Polymer-based project using Chrome Dev Editor
39 | How to use elements in Polymer's iron, paper, and Google Web Component sets.
40 | How to use Polymer's data binding features to reduce boilerplate code.
41 |
42 |
43 | The
44 | Google Web Components provide the
45 | <google-map>
element for declaratively rendering a Google Map. To use it, you first need to install it using Bower.
46 |
47 |
48 | What is Bower?
49 |
50 |
51 | Bower is a client-side package management tool that can be used with any web app. When working with Polymer,
52 | it simplifies the hassles of dependency management. Every component defines its own set of dependencies. When you
53 | use Bower to install a component, the component's dependencies are installed alongside it under
54 | bower_components/
.
55 |
56 |
57 | Create a new project
58 |
59 |
60 | This codelab uses Chrome Dev Editor, a Chrome app IDE. If you don't have it installed yet, please install it from Chrome
61 | Web Store.
62 |
63 |
64 |
65 | Download Chrome Dev Editor
66 |
67 |
68 |
69 |
70 | The first time you run Chrome Dev Editor it will ask you to setup your workspace environment.
71 | Fire up Chrome Dev Editor and start a new project:
72 |
73 | Click
74 | to start a new project.
75 | Enter
76 | "PolymerMapsCodelab" as the
77 | Project name .
78 | In the
79 | Project type dropdown, select "JavaScript web app (using Polymer paper elements)".
80 | Click the
81 | Create button.
82 |
83 |
84 |
85 |
86 | Chrome Dev Editor creates a basic scaffold for your Polymer app. In the background, it also uses
87 | Bower to download and install a list of dependencies (including the Polymer core library) into the
88 | bower_components
/ folder.
89 | Fetching the components make take some time if your internet connection is slow . You'll learn more about using
90 | Bower in the next step.
91 | bower.json
92 |
93 | PolymerMapsCodelab /
94 | bower_components / <!-- installed dependencies from Bower -->
95 | bower . json <!-- Bower metadata files used for managing deps -->
96 | index . html <!-- your app -->
97 | main . js
98 | styles . css
99 |
100 | Install the
101 | element
102 | Normally, you'd run
103 | bower install GoogleWebComponents/google-map --save
on the command line to install
104 | <google-map>
and save it as a dependency. However, Chrome Dev Editor does not have a command line for running Bower commands. Instead,
105 | you need to manually edit
106 | bower.json
to include
107 | google-map
, then run Chrome Dev Editor's
108 | Bower Update feature.
109 | Bower Update checks the dependencies in
110 | bower.json
and installs any missing ones.
111 |
112 | Edit
113 | bower.json
and add
114 | google-map
to the
115 | dependencies
object:
116 |
117 |
118 | "dependencies": {
119 | "iron-elements": "PolymerElements/iron-elements#^1.0.0",
120 | "paper-elements": "PolymerElements/paper-elements#^1.0.1",
121 | "google-map": "GoogleWebComponents/google-map#^1.0.3"
122 | }
123 |
124 |
125 | Right-click the
126 | bower.json
filename in the editor.
127 | Select
128 | Bower Update from the context menu.
129 |
130 |
131 |
132 |
133 | The download may take few seconds. You can verify that
134 | <google-map>
(and any dependencies) were installed by checking that
135 | bower_components/google-map/
was created and populated.
136 | Use the
137 | element
138 | To employ
139 | <google-map>
, you need to:
140 |
141 | Use an
142 | HTML Import to load it in
143 | index.html
.
144 | Declare an instance of the element on the page.
145 |
146 | In
147 | index.html
,
148 | remove all other HTML imports
149 | in the
150 | <head>
and replace them with a single import that loads
151 | google-map.html
:
152 |
155 |
156 | <head>
157 | ....
158 | <script src="bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
159 | <link rel="import" href="bower_components/google-map/google-map.html">
160 | </head>
161 |
162 |
163 |
164 | Important : all imports need to come
165 | after
166 | webcomponents-lite.min.js
so the polyfill can properly load them.
167 |
168 | Next, replace the contents of
169 | <body>
with an instance of
170 | <google-map>
:
171 | index.html
172 |
173 | <body unresolved>
174 | <google-map latitude="37.779" longitude="-122.3892" zoom="13"></google-map>
175 | </body>
176 |
177 | Here's a pre block without code:
178 | $ node client.js list wdfasdf asdf asdf asdf asdf asdf asdf asdf asdf asdfa sdf asdfa sdf asdfa sdfasdfasdfasdf
179 | { books:
180 | [ { id: 123,
181 | title: 'A Tale of Two Cities',
182 | author: 'Charles Dickens' } ] }
183 |
184 | As you can see, using
185 | <google-map>
is completely declarative! The map is centered using the
186 | latitude
and
187 | longitude
attributes and its zoom level is set by the
188 | zoom
attribute.
189 | Style the map
190 | If you run the app right now, nothing will display. In order for the map to properly display itself, you need to set
191 | its container (in this case,
192 | <body>
) to have a fixed height.
193 | Open
194 | styles.css
and replace its contents with default styling:
195 | styles.css
196 |
197 | body, html {
198 | font-family: 'Roboto', Arial, sans-serif;
199 | height: 100%;
200 | margin: 0;
201 | }
202 |
203 | Add a marker
204 |
205 | <google-map>
supports adding map markers to the map by declaring
206 | <google-map-marker>
elements as children. The marker locations are also set using
207 | latitude
and
208 | longitude
attributes.
209 | Back in
210 | index.html , add a draggable
211 | <google-map-marker>
to the map:
212 | index.html
213 |
214 | <google-map latitude="37.779" longitude="-122.3892" zoom="13" disable-default-ui>
215 | <google-map-marker latitude="37.779" longitude="-122.3892"
216 | title="Go Giants!" draggable="true"></google-map-marker>
217 | </google-map>
218 |
219 | Notice that we've also disabled the map's controls by setting
220 | disableDefaultUi
to true. Since it's a boolean property, its presence as an HTML attribute makes it truthy.
221 | Run the app
222 | If you haven't already done so, hit the
223 | button. At this point, you should see a map that takes up the entire viewport and has a single marker pin.
224 |
225 |
226 |
227 | Frequently Asked Questions
228 |
240 |
241 |
242 | How would rate your experience with Polymer?
243 |
244 | Novice
245 | Intermediate
246 | Advanced
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------
/google-codelab-step/google_codelab_step.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabStep');
19 |
20 | const EventHandler = goog.require('goog.events.EventHandler');
21 | const Templates = goog.require('googlecodelabs.CodelabStep.Templates');
22 | const dom = goog.require('goog.dom');
23 | const soy = goog.require('goog.soy');
24 |
25 | /** @const {string} */
26 | const LABEL_ATTR = 'label';
27 |
28 | /** @const {string} */
29 | const STEP_ATTR = 'step';
30 |
31 | /**
32 | * The general codelab action event fired for trackable interactions.
33 | * @const {string}
34 | */
35 | const CODELAB_ACTION_EVENT = 'google-codelab-action';
36 |
37 | /**
38 | * @extends {HTMLElement}
39 | * @suppress {reportUnknownTypes}
40 | */
41 | class CodelabStep extends HTMLElement {
42 | /** @return {string} */
43 | static getTagName() { return 'google-codelab-step'; }
44 |
45 | constructor() {
46 | super();
47 |
48 | /**
49 | * @private {?Element}
50 | */
51 | this.instructions_ = null;
52 |
53 | /**
54 | * @private {?Element}
55 | */
56 | this.inner_ = null;
57 |
58 | /** @private {boolean} */
59 | this.hasSetup_ = false;
60 |
61 | /**
62 | * @private {string}
63 | */
64 | this.step_ = '0';
65 |
66 | /**
67 | * @private {string}
68 | */
69 | this.label_ = '';
70 |
71 | /**
72 | * @private {?Element}
73 | */
74 | this.title_ = null;
75 |
76 | /**
77 | * @private {!EventHandler}
78 | * @const
79 | */
80 | this.eventHandler_ = new EventHandler();
81 | }
82 |
83 | /**
84 | * @export
85 | * @override
86 | */
87 | connectedCallback() {
88 | this.setupDom_();
89 | }
90 |
91 | /**
92 | * @return {!Array}
93 | * @export
94 | */
95 | static get observedAttributes() {
96 | return [LABEL_ATTR, STEP_ATTR];
97 | }
98 |
99 | /**
100 | * @param {string} attr
101 | * @param {?string} oldValue
102 | * @param {?string} newValue
103 | * @param {?string} namespace
104 | * @export
105 | * @override
106 | */
107 | attributeChangedCallback(attr, oldValue, newValue, namespace) {
108 | if (attr === LABEL_ATTR || attr === STEP_ATTR) {
109 | this.updateTitle_();
110 | }
111 | }
112 |
113 | /**
114 | * @private
115 | */
116 | updateTitle_() {
117 | if (this.hasAttribute(LABEL_ATTR)) {
118 | this.label_ = this.getAttribute(LABEL_ATTR);
119 | }
120 |
121 | if (this.hasAttribute(STEP_ATTR)) {
122 | this.step_ = this.getAttribute(STEP_ATTR);
123 | }
124 |
125 | if (!this.title_) {
126 | return;
127 | }
128 |
129 | const title = soy.renderAsElement(Templates.title, {
130 | step: this.step_,
131 | label: this.label_
132 | });
133 |
134 | dom.replaceNode(title, this.title_);
135 | this.title_ = title;
136 | }
137 |
138 | /**
139 | * @private
140 | */
141 | setupDom_() {
142 | if (this.hasSetup_) {
143 | return;
144 | }
145 |
146 | this.instructions_ = dom.createElement('div');
147 | this.instructions_.classList.add('instructions');
148 | this.inner_ = dom.createElement('div');
149 | this.inner_.classList.add('inner');
150 | this.inner_.innerHTML = this.innerHTML;
151 | dom.removeChildren(this);
152 |
153 | const title = soy.renderAsElement(Templates.title, {
154 | step: this.step_,
155 | label: this.label_,
156 | });
157 | this.title_ = title;
158 |
159 | dom.insertChildAt(this.inner_, title, 0);
160 |
161 | const codeElements = this.inner_.querySelectorAll('pre code');
162 | codeElements.forEach((el) => {
163 | const code = window['prettyPrintOne'](el.innerHTML);
164 | el.innerHTML = code;
165 | this.eventHandler_.listen(
166 | el, 'copy', () => this.handleSnippetCopy_(el));
167 | });
168 |
169 | dom.appendChild(this.instructions_, this.inner_);
170 | dom.appendChild(this, this.instructions_);
171 |
172 | this.hasSetup_ = true;
173 | }
174 |
175 | /**
176 | * @param {!Element} el The element on which we added this event listener.
177 | * This is not the same as the target of the event, because the event
178 | * target can be a child of this element.
179 | * @private
180 | */
181 | handleSnippetCopy_(el) {
182 | const event = new CustomEvent(CODELAB_ACTION_EVENT, {
183 | detail: {
184 | 'category': 'codelab',
185 | 'action': 'copy',
186 | 'label': el.textContent.substring(0, 500)
187 | }
188 | });
189 | document.body.dispatchEvent(event);
190 | }
191 | }
192 |
193 | exports = CodelabStep;
194 |
--------------------------------------------------------------------------------
/google-codelab-step/google_codelab_step.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | $google-material-blue-600: #1A73E8;
19 | $google-material-blue-800: #185ABC;
20 | $google-material-green-600: #1E8E3E;
21 | $google-material-grey-900: #212124;
22 | $google-material-grey-800: #3C4043;
23 | $google-material-grey-700: #5f6368;
24 | $google-material-grey-600: #80868B;
25 | $google-material-grey-300: #DADCE0;
26 | $google-material-grey-100: #F1F3F4;
27 |
28 | $google-codelab-step-background: #fff;
29 | $google-codelab-step-link-color: $google-material-blue-600;
30 |
31 |
32 | google-codelab-step {
33 | line-height: 24px;
34 | display: block;
35 | }
36 |
37 | google-codelab-step {
38 | @import "syntax";
39 | }
40 |
41 | google-codelab-step h2.step-title {
42 | font-family: 'Google Sans', Arial, sans-serif !important;
43 | font-size: 28px !important;
44 | font-weight: 400 !important;
45 | line-height: 1em !important;
46 | margin: 0 0 30px !important;
47 | }
48 |
49 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions {
50 | box-shadow: 0px 1px 2px 0px rgba(60, 64, 67, 0.3), 0px 2px 6px 2px rgba(60, 64, 67, 0.15);
51 | background: $google-codelab-step-background;
52 | max-width: 800px;
53 | font-size: 14px;
54 | margin: 0 auto;
55 | margin-bottom: 32px;
56 | border-radius: 4px;
57 | }
58 |
59 | google-codelab-step .instructions .inner {
60 | padding: 24px;
61 | }
62 |
63 | google-codelab[theme="minimal"] google-codelab-step .instructions .inner {
64 | padding: 0 24px;
65 | }
66 |
67 | @media (max-width: 800px) {
68 | google-codelab .instructions {
69 | margin: 0 0 16px 0;
70 | }
71 | }
72 |
73 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions a,
74 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions a:visited {
75 | color: $google-codelab-step-link-color;
76 | }
77 |
78 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions h2,
79 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions h3,
80 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions h4 {
81 | font-weight: 400;
82 | margin: 0;
83 | }
84 |
85 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions h2 {
86 | font-weight: 300;
87 | line-height: 1em;
88 | font-size: 22px;
89 | }
90 |
91 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions {
92 | line-height: 24px;
93 | }
94 |
95 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions li {
96 | margin: 0.5em 0;
97 | }
98 |
99 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions h2 {
100 | font-weight: 500;
101 | margin: 1.5em 0 0 0;
102 | font-size: 20px;
103 | }
104 |
105 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions h3 {
106 | font-weight: 500;
107 | margin: 20px 0 0 0;
108 | }
109 |
110 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions aside {
111 | padding: 0.5em 1em;
112 | margin: 2em 0;
113 | border-left: 4px solid;
114 | border-radius: 4px;
115 | }
116 |
117 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions aside p {
118 | margin: 0.5em 0;
119 | }
120 |
121 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions aside.note,
122 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions aside.notice {
123 | border-color: #EA8600;
124 | background: #FEF7E0;
125 | color: $google-material-grey-900;
126 | }
127 |
128 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions aside.tip,
129 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions aside.special {
130 | border-color: #137333;
131 | background: #E6F4EA;
132 | color: $google-material-grey-900;
133 | }
134 |
135 | google-codelab:not([theme="minimal"]) google-codelab-step .instructions aside.warning {
136 | border-color: #EA8600;
137 | background: #FEF7E0;
138 | color: $google-material-grey-900;
139 | }
140 |
141 | google-codelab-step .instructions aside.callout {
142 | background-color: #E8F0FE;
143 | margin: 20px 0;
144 | padding: 15px;
145 | border-left: 3px solid $google-material-blue-800;
146 | border-radius: 4px;
147 | color: $google-material-grey-900;
148 | font-size: 14px;
149 | line-height: 1.5;
150 | }
151 |
152 | google-codelab-step aside.callout b {
153 | color: $google-material-blue-800;
154 | }
155 |
156 | google-codelab-step .instructions ul.checklist {
157 | list-style: none;
158 | padding: 0 0 0 1em;
159 | }
160 |
161 | google-codelab-step .instructions ul.checklist li,
162 | google-codelab-step .instructions ::content ul.checklist li {
163 | padding-left: 24px;
164 | background-size: 20px;
165 | background-repeat: no-repeat;
166 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAWlBMVEUAAAAxokwwoks1pFAxokwxokwxokwxokwxokwnnkQnnkQnnkRou3y84cTS69cxokwonkQxokwnnkRqvH1VsmtluXlVsmsnnkRdtnLw+PIxokwqn0YinEAfmj3goh/UAAAAGnRSTlMA2CcEo+6AQT7+2IOBJxPl27alhoBnX15SCCe258UAAAB+SURBVEjH7dA5EoAgEERR3BcQ923Q+1/T0SqKlNbMouP3gxkRFvZpyQb64VSQT4mOcYc8mU5DnqIG8zXoozj4d34tML+YrET8XBFx4e2F4oAL4N7J3EUB/EfSUwD/zG3hvFdROu9XtL31vgXguQA9F6DnAvM8WbOHpkXYD3cBBCcPjtASYjwAAAAASUVORK5CYII=');
167 | }
168 |
169 | google-codelab-step .instructions table code,
170 | google-codelab-step .instructions h2 code {
171 | background: white;
172 | }
173 |
174 | google-codelab-step .instructions .indented {
175 | margin-left: 40px;
176 | }
177 |
178 | google-codelab-step .instructions strong {
179 | font-weight: 600;
180 | }
181 |
182 | google-codelab-step .instructions paper-button {
183 | border-radius: 4px;
184 | color: #FFFFFF;
185 | font-family: 'Google Sans', Arial, sans-serif;
186 | font-size: 14px;
187 | font-weight: 600;
188 | letter-spacing: .6px;
189 | padding-bottom: 6px;
190 | padding-left: 12px;
191 | padding-right: 16px;
192 | padding-top: 6px;
193 | text-transform: none;
194 | }
195 |
196 | google-codelab-step .instructions paper-button a {
197 | text-decoration: none;
198 | color: inherit !important;
199 | }
200 |
201 | google-codelab-step a paper-button {
202 | /* The text-decoration styling doesn't apply to inline blocks.
203 | We use this trick to remove underline when paper-button
204 | is wrapped in the anchor element. */
205 | display: inline-block;
206 | }
207 |
208 | google-codelab-step .instructions paper-button.colored {
209 | background-color: $google-material-green-600;
210 | }
211 |
212 | google-codelab-step .instructions paper-button.red {
213 | background-color: #D93025;
214 | }
215 |
216 | google-codelab-step .instructions iron-icon {
217 | padding-right: 8px;
218 | max-height: 16px;
219 | max-width: 16px;
220 | }
221 |
222 | google-codelab-step .instructions img {
223 | max-width: 100%;
224 | vertical-align: bottom;
225 | }
226 |
227 | google-codelab-step .instructions table {
228 | border-spacing: 0;
229 | }
230 |
231 | google-codelab-step .instructions td {
232 | vertical-align: top;
233 | border-bottom: 1px solid #CCC;
234 | padding: 8px;
235 | }
236 |
237 | google-codelab-step .instructions table p {
238 | margin: 0;
239 | }
240 |
241 | google-codelab:not([theme="minimal"]) .instructions h3.faq {
242 | border-bottom: 1px solid #ddd;
243 | }
244 |
245 | google-codelab:not([theme="minimal"]) .instructions ul.faq {
246 | list-style: none;
247 | padding-left: 1em;
248 | }
249 |
250 | google-codelab:not([theme="minimal"]) .instructions .faq li {
251 | font-size: 1.1em;
252 | margin-bottom: 0.8em;
253 | }
254 |
255 | google-codelab:not([theme="minimal"]) .instructions .faq a {
256 | color: inherit;
257 | text-decoration: none;
258 | }
259 |
260 | google-codelab:not([theme="minimal"]) .instructions .faq a:hover {
261 | text-decoration: underline;
262 | }
263 |
264 | google-codelab-step .instructions .faq a[href*="cloud.google.com"] {
265 | padding-left: 22px;
266 | background-size: 20px;
267 | background-repeat: no-repeat;
268 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAC9FBMVEX////u7u7v7+/ZRDf/zEH/zkPbRTlJifT/z0XYRDhRj/XWRDdPjvVOjfX/zkH/z0fXQzdGiPXUQzdMi/VKi/Xr6+3t7e1SkPXbRDdLjPW70fPaQjVOjPXx8PD9/f1nnff19fXRQzdHifVJhOxEh/Tu7/JLifH9y0DUQjVtofhOi/NKh+9HgelGf+b+7saaWXX/zDz+yTrZRzrRQjT7+/vxx8PeqkTsuj32uzrOQzdqn/j39/fr6Oj9yT/3vjvaQDPUPTBJivVKifPq7O/z0Mz968T4wj7zwD3NPTHGPDD0+P9Bifrv8/Xx8vJJhu5EfOPo3Nv/0Ez8y0rxvT3ZPjBDi/tBhPT55ePkysj+3Hr/0VLHVErcpkPcTUH7xD35wTzztznpsTjKRTjo8P3U4/z6+vr//PmQt/mBrff//fayy/ZUkfZjmfSXufP78/L18+//+Oj66Of+9eBCdt3t59f/89TvxcPsurXasKztqKL+4Y7jjIT0033ShH3hc2ngaV7/1F3/0ljjv1fbWU37xkDaoEDCSj/xujvptjvutjrxtjn8xDflrDb5vjXJQjX2tzP4+v+bwf3I3Pyiw/t0pvhel/VZlPW90fP/+/DV3/Cdu+/e4uv33Nk+btP+8Mz+8Mniwb7+7L3u371Vdrv+6bDusq3+56j+5aTx2Z+Gjpj+4pbolI3RjYehnIT+2XPedW370mbHaGDMZl3bY1jQVUvbU0f/00PYTkLKSj/tsjjwpjfeXjfnfjbEQjbXPjTSPS/w9vu50vs7hfr5+flypPf89vb89fTF1fHM2vBsnfB3ou/67+6swOpekert6uNdi+M5deLq4OA/dt83bt80a9xmjdby19VKc8jx5cf12cP/7r/gwr+pnr/v4b7xwLtffbf+6rHx3KnorKjYqKTXpqHXo57aop3ZkovTwIboh3+jX3f30XDgenCWUm3Kc2zIsGnMcGj4y1zgYFXgX1TfrEXWlz7mhjfsrTbkljbabDbcVTbsmTBDSJ02AAAFFklEQVRYw+2WV1jTUABGkyZpi6W0tKUWC0hBsAporbMWrCgqKA6WCooK7r333nvvvffee++99957b33x3uTetIEGnnzx83y89Tunf25SWuI/f4Oqk6cU4ijLkr9sfp5Ckyfk6h8o5JkH4umpVPr5+fnqdLro6GiZTBYkk3l71z5SNbdA8zyt8wLYhKefUunr66vV6WQsMDExF3/StLytka/0VPopfbUgoEWBoCBv76lNcj6AFnnQ+6MB0BcsKNA8x2M4iPbjAThQgVsAA545TWg8RQlc5MMBflrg8wHgg3NsIX6OAa10Lr6SDQgHgAXetSeJD5iqRTrytfAKdHwgiAu0EZ3QvEBhYLLAu6+LDoIOtCApKSnVUqoBDondyiahoTggazt9ej4hd94Wxbya4z5wskZoYVgA0/NXlGSlVr3iJpOpOMsbt36r4BphsACQHSezBfSP4zQQs8Zs3n7P3QmeDQ8OAwVwdkEt0ADhhI3jFCxms7lfu+yBthHhwcFhoaCgnXZC4obqPYbZPABqgHVpNj/yak1QABdRQatriwcIiXy312alrBRFqff175jFb9lrZecIVGgz09+NTurjbw/LNFmtUqnVw8PRM0AY6JowaHFN7iIKz2rvtCIjfbBPkvF992aapABKakzvKByQymxbO5e7iDb8BfjoA5uWsPv7kJxPSi4Oy7RpgA/+kl8KArdolUq1uDMshLXCA8jD6NXqpEQPAmDCszibTSFlccS4+DMGMCrVtvdzO0eEh59CPulfgsDYoQ/Rb4q1mbiCsX8zZ2AVrQLIV16uGRGMTxD6PIEkh2TBOPA4og09nSeYJoe+fND1SxdmSRCBgg+6PypE9o01mTRq6Bs+1yEQqcCHJK69cn62xDnA3YS6W4qD51kBA9LV6MX5aXKuIB90/wyJnztCQFMJKuif2zQANfANn9CnchlYzyWYF7OFV8ATgAPV624BAzQKCgTSSxIsDxIYOQR00m7kFgjpsQP6CgUlNWxFd7LDAEaOoPvoc74ESa1NsUBXK9QelLHfafTqclZmKwXvsjpJihyi/mkc9j3Su/P7BngxeMJHPXoru9vbCP4vYZ9aT/DMpxkAW7CsCCFZ3D5I5BowAPnp1wgnqRaGQ84kduEKemchwC5BJ1jX6Tt6Ey50SvBiMH1AgCvYA1i9BP8UkRtjsW8c3E5wjcstfCBxUQgW/APt9sBAfxIR/2ScBvnS5O6EgA4baL7w+hzpRELyhNSqZ1Jgf/0xQsgSuXPCQzxBSPwaG/YpawyRhRnrvBh0DvSGLkITn2A9M/Kljt5HiawsTGAwxVbohS7+JGPfOBgNEJDK3wg60d2EBTvM2KdWE27opPLCWPqEZP9W+BCLfIoa3JFwxyoLX4haVNFHSMUecQrsGx8RbmmZxgfoIV9KIeoDhg4d+jXTzPubCREWWmhOB0SV5hgxYkRGxpgxY36i+2cEgRixQLN1FpqnYJWkKklJScNLjxy5MyPjhwf0Kegn9yZEmZdAuxQqVwEkDR8+auTO77uhD3XKsbWOeKBZL4ugULlyuXJlyjQaNeqbFfhG6BuSwdeqOF0H0q6FBmyhUaNdu6FPQd/RHw4Qpxfj4heMatCwIZywh1JTLAYD+mkhSoeBLoGoqCENYGDXL943oB834iyhLdjHgfF79mHfsb0bkRvLBhZDjB49euzYsePH/96/vzyHeXN3Inc6zSuCqcRys1tJjm4xdYj//JP8AXE5S/JuAn7MAAAAAElFTkSuQmCC');
269 | }
270 |
271 | google-codelab-step .instructions .faq a[href*="stackoverflow.com"] {
272 | padding-left: 22px;
273 | background-size: 24px;
274 | background-repeat: no-repeat;
275 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATwAAAE8CAMAAABq2/00AAAA4VBMVEUAAACCg4aCg4aCg4bIj0P1fx/2hh+ojHCojHCojHD2ih/UjCn0eiD0eiD2hB/2hR/RjjDUjCnKkkPUjCnUjCn2ih/UjCnCllPCllP2ih/CllPCllPCllP2ih/CllPCllPCllP2ih/2ih/CllPCllP2ih/2ih/CllPUjCnCllPUjCn0eiD0eiD2ih+ojHD0eiDUjCn0eiD0eiCojHDUjCn0eiDUjCn0eiDUjCnUjCn0eiCojHD0eiCojHD2ih+ojHD2ih+ojHCojHCdiXaViHuCg4aojHD0eiDCllPUjCn2ih8XYwy7AAAARXRSTlMAv0CAEEAQv4BA7++/gCBQQN8wIIDPv2Dfv5+PUDDPryCAcO+/r49wn4Bg79/frJ9QcGDPr6+Pj3DPz2AwIJ9wYOaPVDAbIL/gAAAGhElEQVR42uzbsYrCQBDG8WmyxaJJlcIixQmxMSIRgiBB7Bbm/R/oPAnH3e0SxwSvyf/3DLt8zDeMAAAAAAAAAAAAAAAAAAAAAAAAAAAA4F9k7Xm1Xwlek53WqyY87AVWfbHumvCTE5hkIZYJTFyIrQU2+xA5CmxWIULcWq1DTGBzCrFcMDluC4ENcTtDEyKdwKYLkYuAuH27ggFtuj7ETgLiNo0B7Z3c1YnNMUQaWbTC6/W1uKUPHbha7wr60GnP7ot3YpGHWCsL5Wod1JP70LMsU+H12424nfDsBj4XgzMD2kN20F82YtDSh965nf61JW7Nzy7iKwY067OLlfJcw/qx1KQPQ9zSh1aaVtGHGnxoUummrB97WZhSk3byRM/6UST3mpQRtwZbTTo4GXdhQLvbaNJVxnX0oSMftyBuDQpN8o4BzaDWpFrG5KwfH5zXpJuMCfShox83pw812GnSxrx+XPI5hjvowF7ttawfB5km+Yq4nf5xS84x5lR7nGPMqfaI2znVHgPajGqPc4w51R7nGJ/sm29P2zAQh49h1kJgJHGWlrbQItAIebNO67owadpgwOzv/4Wm8S8NLsXxGSTu/HyEk/O78+McRu0FH4pQe2EdA6P2wjoGRu2FdotQe2EdA6P2wjoGRu2FdotQe+GChlF7DNcxKulN7bFbx4h1lPlSe+x8aKK1Tqae1B6zdYxM35BLP2qP1TqGjPQdmfCi9ji125l+ICp9qD1G6xixXiSPPag9PusYiW5SCLza49JuK/2YKEOrPSYXNBFpkyTGqj0e6xiFXkoukWqPwzpGrJ8iEyi1x8GHnuknSUqU2qO/jlHpVeQpwhB8oL6OISK9mkK4qz3q7bbQzxFV7mqP9jpGqi1IYme1R3sdI060BTPpaAiot9ss0hZkwk3tUb+gyUJbkEzd1B55HxqfaQvy1Ent0V/HKCNtwVy0NwSfGPhQMdcWRJWD2uOwjiFzbcFZ3F7tsVjHmDqMLTZqj3S7bTm2RJloZwh2maxjyJnd2NJS7XFZx7AcW2QrtUe93dZUDmOLjdrbaTTa7e9fNgj1i7ZjS9lK7dU+dPvzzgaRZ8elpPZji73a2/j749vOLuWytRtbCmmv9jghLMcWuw936zfwwnZssTAEexw+VidTmstn1N4Wr2/2gcrBlO6HY3eHKNqPLYfh2FmPLeZ/aXvh2D1Q2o0tYkHt8ZxQliIybTm2NNXex30IWI8tcePDPYSAywPv+61w7NwfeHfJHrvJS44tSQmUGan+oCfAgTi3fOCly1D952A4eqkH3gTocqDuOT5NX2JsmQJZhFrkaHwy8fzAmwNdeuoRRgQiTakEugyUSR2B+LFlDoTpK4M6AvH/pUUC6JIqAyMCMQ+8pMe8E2XgGIFlxKxbAIyVgUUEWj/wUh6QAZQBIgJlzqlbQKwMUBE4Tdh0C4ChMsBFoMgiJt2ivpvVoCNQznh0CxDKAB2B9dgSA2l6ygAbgfUDbwG0GSgDPxEo5tS7xYq7GT4CU8Im6pbRsNkxMBFIex5ezqQ3qM8fPgL5kZ6Mj1QNLgKpR90y4tNjpbxFID+ExwjsAUN8ReAYmOIjAlmePF8RyLHv+orAPgScI3AIb531tQZ/XjECR3DL5VoTeDOsXTVYf8UIFHDLu6sm8GZAFA8ZgccQimcgLCPwNBRvOROLCExD8Z4mXR2BRxCK5+wCx6F47hHYC8Vzj8BJKJ5zBPYhFM85Agf0i3dx3ulcdLubm5e+I3BEv3id65r7Ov70EoGCV/EW+NrpdLu/cHWkX7zz65pn6hiK9689O8hJKIaiMCxK0nYzTBgSYxDs/pekiTohEq/v0eRKv28J/+zkXMbrF2IdxQvEC3ScOF7r6+3LrtZDa1vxVinfHaeId+qjTBCv9kGO4i1XJoi365/EWxCv9EF24i1XJ4jXv4iXKd7p/uNt+yjt/uO1PspZvOVmmGcPrZ1q3ZUi3qoDaNva4WYdj5PFu2nHMm+8nzqKF4y3vuOreL923PcrqngRz6291FrKXrxbdWzifXB6iydehHh5iDco3maQpxnivQ2yEU888cQLEi8R8cSLES8R8cSLES8R8cSLES8R8cSLES8R8QbFexzkPEO86xxA4on3F+LlIZ54QeLlIZ54QeLlIZ54QeLlId6qeOk8/BviiRciXibiiRciXibiiRciXibiiRciXibiiQcAAAAAAAAAAAAAAAAAwHTeAe21evvWi2VXAAAAAElFTkSuQmCC');
276 | }
277 |
278 | google-codelab-step .instructions .faq a[href*="support.google.com/webmasters/"] {
279 | padding-left: 24px;
280 | background-size: 24px;
281 | background-repeat: no-repeat;
282 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAolBMVEUAAADW1tbW1tZ6enrQ0dJ6enrMzMzq6+zq6+x6enru7+/m5+ju7+/m5+j////S09REi/XW1tbm5+ju7+9PT0/Q0dK8vLxGjfVZWVlNTU16enrMzMzIyMj09PS/0uuSkpJpaWmbm5tim/e0tLSEhIStx+3e3+GsrKxwcHBZk/dPk/R1p/KPtu/U3eqmpqZlZWVfmO5woeqHreWvwN6nu9qLi4vZSE73AAAADnRSTlMAEcxmzO7MzDMz7u6IiHn/rpYAAALMSURBVHja7dXpjtowFIZhUkqZlSV1ncQdEsK+zD7T+7+1xjTiwxVOAmeORyP5+2UspPchEtDy8/Pz8/Pz8/M7cZeduHKdyxbL0FfVAMUraHdUHUB12i22tZWqBRSDgKFfC4CApV8PgIClXw/gE6hyTd/lAR7gAV8fEHwbWtc/ul/WfQ8IfQKAIECfCoCA3sdq+nQB+nQABIQ+CQABtY9Z+lQB+gwACBj6AFAE6DM9AL0fAalPB/yGgNAfntwHAAJKnwCAwNLneQAAVAvM/ttzVLlB4z2/A6AFVoDRf43IfewVgGKNAG/Uvrl39BsCXqh9cy8nA/51tmk63+jDZp6m9wTA4DzANiyW6lOqTxAMHAD22XASReuwpKDvELCNookBGJwx9E8B3OvsQp8e9GmOviNANA/Dh4k+TApBukHfFSBa7/I7whqf3xWA/gOAoU8DDJwBCHk+wIAw9CsAPw/W++CFB/MAD/hCgL59PXPS8toRYKHEndkTauEQIIUQ0hToG2eAXOicKdBXmSPAo9CTpkDo5U4Ad6IEGILy0gFgFJctCACIh/yATABwIChvVyE3IBR7AAQAiOmIGZADAMEBQMkRL+AJAAgOALGUI07AQgBgCPb3uRbwAaYAGAIAZlIL2AA5AIYAgJXUAjaAAsAQADCWO4EjAAQGQAvYAUsDsDQAeuwAMZvu89PZ/jZhBmQCi2dl60+MPiMAf8UgZEUpK/LoJ4oX0C9LIIyF0U8yXkBPVCzRWzIDZjX9sWQG9Kv7ScYN6KnKfizZAf2qfpLzA3p5RV9JboDeytp/kk4Ao5WtP+UFQKCO9sdTSQQ03+OR/kxKdgAWZv/11VI6AWBmP5HSNQD5zwQknwxIqIBu468hBgDqNYCuFXBDASSNAbdWQHB1PiBpDLgOWtZdXPEDri/QswjYAOjbF9x0OQHd26Dl5+fn5+fn5+dn7i/3LEaKJNV/0wAAAABJRU5ErkJggg==');
283 | }
284 |
285 | google-codelab-step .instructions .faq a[href*="developer.android.com"],
286 | google-codelab-step .instructions .faq a[href*="android-developer"] {
287 | padding-left: 20px;
288 | background-repeat: no-repeat;
289 | background-size: 20px;
290 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAq1BMVEX///+lyjmfxyGhyCqkyjakyTP+/vuiyS7z+OWlyjKgxyTX57ChyCfa6bTx9uLy9+Sz0l6szk/D24Tt9NrN4ZnK4JSiyCyexh34+/D6/PTp8dPh7cjf7L7k78vc6rnW5qzU5ajS5KPP4p+21GWnzD3F3Yi813Kx0ViqzUWexhr8/vjv9d7Z6LKy0lyZxADh7cLA2n2rzkr1+erJ35C61m6uz1KcxRHn8M6/2XgEePWtAAACr0lEQVRo3u3Z2XKiQBiG4Y9ebNO2aCCyzOC+7zH7/V/ZRJZQZaJpWnKS4TlJUItXbX4pFJXK71DTusvccI6z5oMSAutdhDP2uzpK4CxwxsRBGYJdA19qLAOU4s4LkyUN7G4Ude2glmzuRihHKDqAPZhNuJRKSckms37rvctrKMnG728l5cxKMU7JuCF7KEvf87h1gnu0iXK4B2p9Sc3dUgZNMusMLvu42r20LiBXT8JMWRepN1xlGu+fK8/6xFPxwsvH697/eP/j7uDTOvB+dxwX/AaM2SQ+6icrYHDyGmgfWE3irNeCqTaPAwcArydroboAbuMAb8NQRNJn20SYtHK8HaKZzoffhZl5tlNxsD5PsnUQ2b9jwwnO35WvRo3lN/o2TDwKS5M3gokF0w2wZxgIlpY2FpgsgdQPkLXJQar0A/LVIDB6Idp2GxQQrJI/rbq2Voijlc5S9LZs8uYCqBVyXLTZhLW/fSn3hDMmZAQ05I020gT2PmeM+w4uGpLksFs+oEEtbaqJVnpUkz4uyebf6xQK0CY6XjoSC1xQ9/PPr4KB52zuvTrOa5EssC0auM0C0tYKtI0DqgpUgSpQBapAFfifAj90Tn5YZoGnooH8eutB56KDDosGhlTrYiTyk0epVdFAKJLn9rLHRSNfcE4tF0UDcAXlXPhDfGPtjGejEMUDCEdPY8eFHoOADvPAxiDQLBBQkUHAJfoB34UBS/9CfAETe6lZYKZft/QIFe94vicRy7tcvKOkB0PhYOo4zlNWYNvjpjOdZwU+O24OQlynK62EGCF2J9IbpIsy/P0I3CHW+Qj8qQJVoAr8VOCx3EB+YvDSwIDmXzaW4oZZseU6Lfr5aaAUPT8uyPuT33VeIpRkI5RHyRQfHEKFUj2UJuh1GjaQsxudKECl8jv8A6GtQkKSkMLrAAAAAElFTkSuQmCC');
291 | }
292 |
293 | google-codelab-step .instructions h3 > a[href*="github"],
294 | google-codelab-step .instructions h3 > a[href*="github"]:visited {
295 | color: black;
296 | text-decoration: none;
297 | padding-left: 24px;
298 | background-repeat: no-repeat;
299 | background-size: 18px;
300 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAMAAAAOusbgAAAAflBMVEUAAACXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZaXlZbf2s+YAAAAKXRSTlMA+SAC7QrIBPXROWMX17rCSmusXEaAD9zNeXFWJqOLMx3mtJJAK5xP4Jw4LyAAAAT6SURBVGje1NbdcqJAEAXg0zJAQEFA0QhojJHE8/4vuBfrJlvQGHSGbO13CzVT09M/g//HIssPRZAYIUkxSVAc8myBSfnrMjVUmbRc+5jEsUqFN0lazeDYogo4SlC5jPq6EI4mxRpOhKuId4pWof228y0fsJ1bbp0lfFCS4XFNSgtpg8eEO6EV2YV4QBvRWtTiXl4udEByD3fxCzpS+LhDk9CZpMFosaFDJsZItdApqTHKnM7NMULOCeQW5534zDUnUuOmWDgRiXFDYzgZ02CQn3BCiY8BXsFJFd7YQjLPkeGDTPRsRhZVK+zIABxX+yfe6Wm/OgL4YIe0UIQRO8x1jm/yiHeI8s11RdP7FKJvx649PmUBRwoyfNqza6dUktzsNt7HliNsa+9m95UGXSl7XvQn2OV0qOpzO1tsNotZe66rw+lyXbb08bcX9qToyNgjYTcoS3PaxT4UflydzLJ7nFDYk3V+SdhzQZfnQTP89VW5jPDbmRTAWvDdnAq1zFnC2pLfHHnFn9uYK3yJ+HOhZoRPa2peYe2VmjX+KKjawNKGqgJXC6HqDEtnqmSB3yrqSlgqqau+UkAVwVLEKz1tZxxQw1LNATObSNvHOqUqCGEtDKhKAcAXqlo40Ag14g92D77BiTcO9pByqNbsDfeIcvCK3+HI++AlG2piOBJTY4AjNVs4s6XmiMwitSzSK0Nu0bQs2leOAzUzODOj5qDPYvHgTCj6TA703HLoog+oxGIgWgzHBMbigWnx1DSQyTcOqBDw34Sav9q1sx1HYSAKoNcYE4cl0AlhIB22rF3//4PTI43ULbrMYjzKy5zHROiGyNhlyqAXDC5z8Acc6mcFu3+OpeCD+Y87ONMRR1isik6qet+wah3gzMEwfIPXLIsB9sQp4UxJnL2pDFRwRBHrauoEZHDkaeoV5PSKKpNydMTSCk4oTawO8O07Nva9JN+8Z0skHNgk5l3bkf7hLUcjW+CceH2B1YqeeDmzTXU5sB/EE4rZmDucsA9ksBtvJuoQq4SaDE5sqc8n2+eaNyqBOTmHtcycG8xo3B4lrMgjmZ0GrwtYaQsLbUBmImZeojKuHRbyHjRmz5ZFQRUdByfGxCPEfPJW07gbVyVoiU9xIwYz97GVs1LD5oMmbPknvcKntLmXNNDvT2GBEV1WXXyadjA0Q64bIMygLsTo00aCsbmWfCbfDOEnt6QF8EzjlBi6BasVdscU5HYwbdz9LO65y9ae4tjKwTzDTJUZfxlPJjRLhoELfdPHgNrl9ZIFK6I5LhjyxOB75QdFsqAEVIKmCW+qcZ0B9xvOPft7eReaVnFPRPmj1lPvbcJdx6toUrkB46zpmyeAnFLViMGDZvSkKfo8Y3ikAORjB8RVqonIT4+hxJjW/qRRzezNvd1VoegKoG1uGOPRhBomaktfAok/PJ9a3PykQEk1xsQ0bqtg5Pk/+04qBtryotDlG4wpaJTvzS7Q3sCwDdbhgu1sw9yi7V/9XFSEb3MwbAbXYWn5H7zF3+5J2gZHNhuPX5frsaqaOvWpsAw+rC3FY6tg/cRMYU88zya4DzGbV7oL3npYQNWugmuFZSLtIlhHWOwcrA8OzrAgT3pdsD5J2PH2a4L3HuzdStvgMscq8j2hL/e5wcm7BLA2OuBbFebVKWBirYR/D6OWc16JizqEO0WUCrHzMMrbCZFGBf5j/QYa/td1VlNoIAAAAABJRU5ErkJggg==');
301 | }
302 |
--------------------------------------------------------------------------------
/google-codelab-step/google_codelab_step.soy:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | {namespace googlecodelabs.CodelabStep.Templates}
19 |
20 | /**
21 | * Renders the step title
22 | */
23 | {template .title}
24 | {@param step: string}
25 | {@param label: string}
26 | {$step}. {$label}
27 | {/template}
28 |
--------------------------------------------------------------------------------
/google-codelab-step/google_codelab_step_def.js:
--------------------------------------------------------------------------------
1 | goog.module('googlecodelabs.CodelabStepDef');
2 | const CodelabStep = goog.require('googlecodelabs.CodelabStep');
3 |
4 | try {
5 | window.customElements.define(CodelabStep.getTagName(), CodelabStep);
6 | } catch (e) {
7 | console.warn('googlecodelabs.CodelabStep', e);
8 | }
--------------------------------------------------------------------------------
/google-codelab-step/google_codelab_step_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabStepTest');
19 | goog.setTestOnly();
20 |
21 | const CodelabStep = goog.require('googlecodelabs.CodelabStep');
22 | window.customElements.define(CodelabStep.getTagName(), CodelabStep);
23 | const MockControl = goog.require('goog.testing.MockControl');
24 | const testSuite = goog.require('goog.testing.testSuite');
25 | goog.require('goog.testing.asserts');
26 | goog.require('goog.testing.jsunit');
27 |
28 | let mockControl;
29 |
30 | /**
31 | * @param {string} s
32 | * @return {string}
33 | */
34 | window['prettyPrintOne'] = (s) => {return s;};
35 |
36 | testSuite({
37 | setUp() {
38 | mockControl = new MockControl();
39 | },
40 |
41 | tearDown() {
42 | mockControl.$resetAll();
43 | mockControl.$tearDown();
44 | },
45 |
46 | testDomIsSetUpCorrectly() {
47 | const codelabStep = new CodelabStep();
48 | codelabStep.innerHTML = 'Test ';
49 |
50 | document.body.appendChild(codelabStep);
51 |
52 | assertNotUndefined(codelabStep.querySelector('.instructions'));
53 | assertNotUndefined(codelabStep.querySelector('.inner'));
54 | assertNotUndefined(codelabStep.querySelector('h2.step-title'));
55 | assertEquals('Test', codelabStep.querySelector('h1').innerHTML);
56 |
57 | document.body.removeChild(codelabStep);
58 | },
59 |
60 | testCodePrettyprint() {
61 | const mockPrettyPrint = mockControl.createMethodMock(window, 'prettyPrintOne');
62 | mockPrettyPrint('Code').$returns('MockCodeTest').$once();
63 |
64 | mockControl.$replayAll();
65 |
66 | const codelabStep = new CodelabStep();
67 | codelabStep.innerHTML = 'Testing Code
';
68 | document.body.appendChild(codelabStep);
69 |
70 | mockControl.$verifyAll();
71 |
72 | assertNotEquals(-1, codelabStep.innerHTML.indexOf('MockCodeTest
'));
73 |
74 | document.body.removeChild(codelabStep);
75 | },
76 |
77 | testSnippetCopy() {
78 | const codelabStep = new CodelabStep();
79 | codelabStep.innerHTML = 'Testing Code
';
80 | document.body.appendChild(codelabStep);
81 |
82 | document.body.addEventListener('google-codelab-action', (e) => {
83 | const detail = e.detail;
84 | assertEquals('codelab', detail['category']);
85 | assertEquals('copy', detail['action']);
86 | assertEquals('Code', detail['label']);
87 | });
88 |
89 | const copyEvent = new ClipboardEvent('copy', {
90 | view: window,
91 | bubbles: true,
92 | cancelable: true
93 | });
94 | document.body.querySelector('.test-code').dispatchEvent(copyEvent);
95 |
96 | document.body.removeChild(codelabStep);
97 | },
98 |
99 | testUpdateTitle() {
100 | const codelabStep = new CodelabStep();
101 |
102 | document.body.appendChild(codelabStep);
103 |
104 | let title = codelabStep.querySelector('h2.step-title');
105 | assertEquals('0. ', title.textContent);
106 |
107 | codelabStep.setAttribute('step', '3');
108 | title = codelabStep.querySelector('h2.step-title');
109 | assertEquals('3. ', title.textContent);
110 |
111 | codelabStep.setAttribute('label', 'test label');
112 | title = codelabStep.querySelector('h2.step-title');
113 | assertEquals('3. test label', title.textContent);
114 |
115 | document.body.removeChild(codelabStep);
116 | }
117 | });
118 |
--------------------------------------------------------------------------------
/google-codelab-step/img-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-1.png
--------------------------------------------------------------------------------
/google-codelab-step/img-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-2.png
--------------------------------------------------------------------------------
/google-codelab-step/img-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-3.png
--------------------------------------------------------------------------------
/google-codelab-step/img-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-4.png
--------------------------------------------------------------------------------
/google-codelab-step/img-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-5.png
--------------------------------------------------------------------------------
/google-codelab-step/img-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-6.png
--------------------------------------------------------------------------------
/google-codelab-step/img-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-7.png
--------------------------------------------------------------------------------
/google-codelab-step/img-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab-step/img-8.png
--------------------------------------------------------------------------------
/google-codelab-survey/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | exports_files(["LICENSE"])
6 |
7 | load("//tools:defs.bzl",
8 | "closure_js_library", "closure_js_binary", "closure_js_test")
9 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_template_library")
10 | load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary", "sass_library")
11 |
12 | filegroup(
13 | name = "google_codelab_survey_files",
14 | srcs = glob([
15 | "*.html",
16 | "*.png",
17 | ]) + [
18 | ":google_codelab_survey_scss_bin",
19 | ":google_codelab_survey_bin",
20 | ],
21 | )
22 |
23 | # Codelab survey.
24 | closure_js_library(
25 | name = "google_codelab_survey",
26 | srcs = [
27 | "google_codelab_survey.js",
28 | "google_codelab_survey_def.js"
29 | ],
30 | deps = [
31 | "@io_bazel_rules_closure//closure/library",
32 | ":google_codelab_survey_soy",
33 | ],
34 | )
35 |
36 | # Compiled version of CodelabSurvey element, suitable for distribution.
37 | closure_js_binary(
38 | name = "google_codelab_survey_bin",
39 | entry_points = ["googlecodelabs.CodelabSurveyDef"],
40 | deps = [":google_codelab_survey"],
41 | )
42 |
43 | closure_js_test(
44 | name = "google_codelab_survey_test",
45 | srcs = ["google_codelab_survey_test.js"],
46 | entry_points = ["googlecodelabs.CodelabSurveyTest"],
47 | deps = [
48 | "@io_bazel_rules_closure//closure/library",
49 | ":google_codelab_survey"
50 | ],
51 | )
52 |
53 | closure_js_template_library(
54 | name = "google_codelab_survey_soy",
55 | srcs = ["google_codelab_survey.soy"]
56 | )
57 |
58 | sass_binary(
59 | name = "google_codelab_survey_scss_bin",
60 | src = "google_codelab_survey.scss",
61 | )
62 |
--------------------------------------------------------------------------------
/google-codelab-survey/google-codelab-survey.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | Google Codelab Survey
22 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | How would rate your experience with Polymer?
36 |
37 | Novice
38 | Intermediate
39 | Advanced
40 |
41 |
42 | How will you use use this tutorial?
43 |
44 | I won't
45 | Complete it
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/google-codelab-survey/google_codelab_survey.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabSurvey');
19 |
20 | const EventHandler = goog.require('goog.events.EventHandler');
21 | const HTML5LocalStorage =
22 | goog.require('goog.storage.mechanism.HTML5LocalStorage');
23 | const Templates = goog.require('googlecodelabs.CodelabSurvey.Templates');
24 | const dom = goog.require('goog.dom');
25 | const events = goog.require('goog.events');
26 | const soy = goog.require('goog.soy');
27 |
28 |
29 | /**
30 | * The prefix for all survey keys in local storage.
31 | * @const {string}
32 | */
33 | const STORAGE_KEY_PREFIX = 'codelab-survey-';
34 |
35 | /**
36 | * The id for the current survey.
37 | * @const {string}
38 | */
39 | const SURVEY_ID_ATTR = 'survey-id';
40 |
41 |
42 | /**
43 | * The upgraded id (to prevent FUOC).
44 | * @const {string}
45 | */
46 | const SURVEY_UPGRADED_ATTR = 'upgraded';
47 |
48 |
49 | /** @const {string} */
50 | const DEFAULT_SURVEY_NAME = 'default-codelabs-survey';
51 |
52 |
53 | /** @enum {string} */
54 | const CssClass = {
55 | 'OPTIONS_WRAPPER': 'survey-question-options',
56 | 'RADIO_WRAPPER': 'survey-option-wrapper',
57 | 'RADIO_TEXT': 'option-text'
58 | };
59 |
60 |
61 | /**
62 | * @extends {HTMLElement}
63 | */
64 | class CodelabSurvey extends HTMLElement {
65 | /** @return {string} */
66 | static getTagName() { return 'google-codelab-survey'; }
67 |
68 | constructor() {
69 | super();
70 |
71 | /**
72 | * The name of the survey
73 | * @private {string}
74 | * @const
75 | */
76 | this.surveyName_ = this.getAttribute(SURVEY_ID_ATTR) || DEFAULT_SURVEY_NAME;
77 |
78 | /**
79 | * @private {!HTML5LocalStorage}
80 | * @const
81 | */
82 | this.storage_ = new HTML5LocalStorage();
83 |
84 | /**
85 | * @private {string}
86 | * @const
87 | */
88 | this.storageKey_ = STORAGE_KEY_PREFIX + this.surveyName_;
89 |
90 | /**
91 | * @private {!Object}
92 | * @const
93 | */
94 | this.storedData_ = {};
95 |
96 | /**
97 | * @private {!EventHandler}
98 | * @const
99 | */
100 | this.eventHandler_ = new EventHandler();
101 | }
102 |
103 | /**
104 | * @export
105 | * @override
106 | */
107 | connectedCallback() {
108 | this.checkStoredData_();
109 | this.updateDom_();
110 | this.bindEvents_();
111 | }
112 |
113 | /** @private */
114 | bindEvents_() {
115 | this.eventHandler_.listen(document.body, events.EventType.CLICK,
116 | (e) => this.handleClick_(e.target));
117 | }
118 |
119 | /**
120 | * @param {!Element} el
121 | * @private
122 | */
123 | handleClick_(el) {
124 | const isOptionWrapper = el.classList.contains(
125 | CssClass.RADIO_WRAPPER);
126 | const elParent = el.parentElement;
127 | let isOptionChild = false;
128 | if (elParent) {
129 | isOptionChild = elParent.classList.contains(CssClass.RADIO_WRAPPER);
130 | }
131 |
132 | if (isOptionWrapper || isOptionChild) {
133 | let optionEl = el;
134 | if (isOptionChild) {
135 | optionEl = /** @type {!Element} */ (elParent);
136 | }
137 | if (optionEl) {
138 | this.handleOptionSelected_(optionEl);
139 | }
140 | }
141 | }
142 |
143 | /**
144 | * @param {!Element} optionEl
145 | * @private
146 | */
147 | handleOptionSelected_(optionEl) {
148 | const optionTextEl = optionEl.querySelector(`.${CssClass.RADIO_TEXT}`);
149 | let answer = '';
150 | if (optionTextEl) {
151 | answer = optionTextEl.textContent;
152 | }
153 | /** @type {?HTMLInputElement} */
154 | const inputEl = /** @type {?HTMLInputElement} */ (
155 | optionEl.querySelector('input'));
156 | if (inputEl) {
157 | inputEl.checked = true;
158 | const question = inputEl.name;
159 | this.storedData_[this.surveyName_][question] = answer;
160 | this.storage_.set(
161 | this.storageKey_, JSON.stringify(this.storedData_[this.surveyName_]));
162 | const event = new CustomEvent('google-codelab-action', {
163 | detail: {
164 | 'category': 'survey',
165 | 'action': question.substring(0, 500),
166 | 'label': answer.substring(0, 500)
167 | }
168 | });
169 | document.body.dispatchEvent(event);
170 | }
171 | }
172 |
173 | /** @private */
174 | checkStoredData_() {
175 | const storedData = this.storage_.get(this.storageKey_);
176 | if (storedData) {
177 | this.storedData_[this.surveyName_] = /** @type {!Object} */ (
178 | JSON.parse(storedData));
179 | } else {
180 | this.storedData_[this.surveyName_] = {};
181 | }
182 | }
183 |
184 | /** @private */
185 | updateDom_() {
186 | const radioGroupEls = this.querySelectorAll('paper-radio-group');
187 | const questionEls = this.querySelectorAll('h4');
188 | const surveyQuestions = [];
189 | if (radioGroupEls.length && (questionEls.length == radioGroupEls.length)) {
190 | radioGroupEls.forEach((radioGroupEl, index) => {
191 | const surveyOptions = [];
192 | const polymerRadioEls = radioGroupEl.querySelectorAll(
193 | 'paper-radio-button');
194 | dom.removeNode(radioGroupEl);
195 | polymerRadioEls.forEach(radioEl => {
196 | const title = radioEl.textContent;
197 | surveyOptions.push({
198 | radioId: this.normalizeIdAttr_(title),
199 | radioTitle: title
200 | });
201 | });
202 | surveyQuestions.push({
203 | question: questionEls[index].textContent,
204 | options: surveyOptions
205 | });
206 | dom.removeNode(questionEls[index]);
207 | });
208 | const updatedDom = soy.renderAsElement(Templates.survey, {
209 | surveyName: this.surveyName_,
210 | surveyQuestions: surveyQuestions
211 | });
212 | this.appendChild(updatedDom);
213 | }
214 | this.setAnsweredQuestions_();
215 | this.setAttribute(SURVEY_UPGRADED_ATTR, '');
216 | }
217 |
218 | /** @private */
219 | setAnsweredQuestions_() {
220 | const surveyData = this.storedData_[this.surveyName_];
221 | if (surveyData) {
222 | Object.keys(surveyData).forEach(key => {
223 | const id = this.normalizeIdAttr_(surveyData[key]);
224 | /** @type {?HTMLInputElement} */
225 | const inp = /** @type {?HTMLInputElement} */ (
226 | this.querySelector(`#${id}`));
227 | if (inp) {
228 | inp.checked = true;
229 | }
230 | });
231 | }
232 | }
233 |
234 | /**
235 | * @param {string} id
236 | * @return {string}
237 | * @private
238 | */
239 | normalizeIdAttr_(id) {
240 | return id.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9 \-]/g, '').toLowerCase();
241 | }
242 |
243 | /**
244 | * @export
245 | * @override
246 | */
247 | disconnectedCallback() {
248 | this.eventHandler_.removeAll();
249 | }
250 | }
251 |
252 | exports = CodelabSurvey;
253 |
--------------------------------------------------------------------------------
/google-codelab-survey/google_codelab_survey.scss:
--------------------------------------------------------------------------------
1 | google-codelab-survey {
2 | visibility: hidden;
3 | }
4 |
5 | google-codelab-survey[upgraded] {
6 | visibility: visible;
7 | }
8 |
9 | google-codelab-survey {
10 | display: block;
11 | margin: 2em 0;
12 | padding: 0;
13 | background: #e8f0fe;
14 | border-left: 4px solid #185abc;
15 | border-radius: 4px;
16 | color: #3c4043;
17 | }
18 |
19 | google-codelab-survey h4 {
20 | font-size: 16px;
21 | font-weight: 400;
22 | padding: .8em 0 0;
23 | margin: 0;
24 | }
25 |
26 | google-codelab-survey .survey-question-wrapper {
27 | padding: .4em 0 1.1em 30px;
28 | }
29 |
30 | google-codelab-survey .survey-question-options {
31 | padding: .8em 0 0;
32 | }
33 |
34 | .survey-option-wrapper {
35 | cursor: pointer;
36 | display: block;
37 | padding: 0 0 4px;
38 | position: relative;
39 | -webkit-user-select: none;
40 | -moz-user-select: none;
41 | -ms-user-select: none;
42 | user-select: none;
43 | vertical-align: middle;
44 | }
45 |
46 | google-codelab-survey .option-text {
47 | color: #212121;
48 | font-size: 16px;
49 | padding-left: 24px;
50 | }
51 |
52 | .survey-option-wrapper input {
53 | position: absolute;
54 | opacity: 0;
55 | }
56 |
57 | .custom-radio-button {
58 | position: absolute;
59 | top: 5px;
60 | left: 0;
61 | height: 13px;
62 | width: 13px;
63 | background-color: #fff;
64 | border: 2px solid #3f51b5;
65 | border-radius: 50%;
66 | }
67 |
68 | .custom-radio-button:after {
69 | content: "";
70 | position: absolute;
71 | display: none;
72 | }
73 |
74 | .survey-option-wrapper input:checked ~ .custom-radio-button:after {
75 | display: block;
76 | }
77 |
78 | .survey-option-wrapper .custom-radio-button:after {
79 | top: 1px;
80 | left: 1px;
81 | width: 7px;
82 | height: 7px;
83 | border-radius: 50%;
84 | background: #3f51b5;
85 | }
86 |
--------------------------------------------------------------------------------
/google-codelab-survey/google_codelab_survey.soy:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | {namespace googlecodelabs.CodelabSurvey.Templates}
19 |
20 | /**
21 | * Renders questions with mdc radio groups for a codelabs survey.
22 | */
23 | {template .survey}
24 | {@param surveyName: string }
25 | {@param surveyQuestions: list<[question:string, options:list<[radioId:string, radioTitle:string]>]>}
26 |
27 | {for $surveyQuestion in $surveyQuestions}
28 |
29 |
{$surveyQuestion.question}
30 | {if length($surveyQuestion.options)}
31 |
32 | {for $option in $surveyQuestion.options}
33 |
36 |
37 | {$option.radioTitle}
38 |
39 |
42 |
43 |
44 | {/for}
45 |
46 | {/if}
47 |
48 | {/for}
49 |
50 | {/template}
51 |
--------------------------------------------------------------------------------
/google-codelab-survey/google_codelab_survey_def.js:
--------------------------------------------------------------------------------
1 | goog.module('googlecodelabs.CodelabSurveyDef');
2 | const CodelabSurvey = goog.require('googlecodelabs.CodelabSurvey');
3 |
4 | try {
5 | window.customElements.define(CodelabSurvey.getTagName(), CodelabSurvey);
6 | } catch (e) {
7 | console.warn('googlecodelabs.CodelabSurvey', e);
8 | }
--------------------------------------------------------------------------------
/google-codelab-survey/google_codelab_survey_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | goog.module('googlecodelabs.CodelabSurveyTest');
19 | goog.setTestOnly();
20 |
21 | const CodelabSurvey = goog.require('googlecodelabs.CodelabSurvey');
22 | window.customElements.define(CodelabSurvey.getTagName(), CodelabSurvey);
23 | const HTML5LocalStorage =
24 | goog.require('goog.storage.mechanism.HTML5LocalStorage');
25 | const testSuite = goog.require('goog.testing.testSuite');
26 | goog.require('goog.testing.asserts');
27 | goog.require('goog.testing.jsunit');
28 |
29 | let div;
30 |
31 | /** @const {!HTML5LocalStorage} */
32 | const localStorage = new HTML5LocalStorage();
33 |
34 | const polymerHtml = '' +
35 | 'Question? ' +
36 | 'Title Text ' +
37 | 'Second Option ' +
38 | ' ';
39 |
40 | const polymerHtmlInvalid = '' +
41 | 'Title Text ' +
42 | ' ';
43 |
44 | testSuite({
45 |
46 | setUp() {
47 | if (localStorage.isAvailable()) {
48 | localStorage.clear();
49 | }
50 | div = document.createElement('div');
51 | div.innerHTML = polymerHtml;
52 | },
53 |
54 | tearDown() {
55 | if (localStorage.isAvailable()) {
56 | localStorage.clear();
57 | }
58 | document.body.innerHTML = '';
59 | div = null;
60 | },
61 |
62 | testCodelabSurveyUpgraded() {
63 | document.body.appendChild(div);
64 | const surveyCE = div.querySelector('google-codelab-survey');
65 | const radioInputEl = surveyCE.querySelector('input#title-text');
66 | const radioLabelEl = surveyCE.querySelector('label#title-text-label');
67 | const radioTextEl = surveyCE.querySelector('.option-text');
68 | const surveyWrapperEl = surveyCE.querySelector('.survey-questions');
69 | assertNotNull(radioInputEl);
70 | assertEquals('Question?', radioInputEl.name);
71 | assertNotNull(radioLabelEl);
72 | assertEquals('test', surveyWrapperEl.getAttribute('survey-name', ''));
73 | assertEquals('Title Text', radioTextEl.textContent);
74 | assertEquals('title-text', radioLabelEl.getAttribute('for', ''));
75 | assertTrue(surveyCE.hasAttribute('upgraded'));
76 | },
77 |
78 | testCodelabSurveyIncorrectFormatNotUpgraded() {
79 | div.innerHTML = polymerHtmlInvalid;
80 | document.body.appendChild(div);
81 | const radioInputEl = div.querySelector('input#title-text');
82 | const radioLabelEl = div.querySelector('label#title-text-label');
83 | assertNull(radioInputEl);
84 | assertNull(radioLabelEl);
85 | },
86 |
87 | testCodelabSurveyOptionClick() {
88 | document.body.appendChild(div);
89 | const optionEls = div.querySelectorAll('.survey-option-wrapper');
90 | // If nothing is in local storage no options should be set.
91 | assertFalse(optionEls[0].querySelector('input').checked);
92 | assertFalse(optionEls[1].querySelector('input').checked);
93 |
94 | optionEls[0].click();
95 | assertEquals('{"Question?":"Title Text"}', localStorage.get('codelab-survey-test'));
96 | optionEls[1].click();
97 | assertEquals('{"Question?":"Second Option"}', localStorage.get('codelab-survey-test'));
98 | },
99 |
100 | testCodelabSurveyLoadsStoredAnswers() {
101 | localStorage.set('codelab-survey-test', '{"Question?":"Second Option"}');
102 | document.body.appendChild(div);
103 | const optionEls = div.querySelectorAll('.survey-option-wrapper');
104 |
105 | // Second option should be selected (answer loaded from local storage)
106 | assertFalse(optionEls[0].querySelector('input').checked);
107 | assertTrue(optionEls[1].querySelector('input').checked);
108 | },
109 | });
110 |
--------------------------------------------------------------------------------
/google-codelab/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | licenses(["notice"])
4 |
5 | exports_files(["LICENSE"])
6 |
7 | load("//tools:defs.bzl",
8 | "closure_js_library", "closure_js_binary", "closure_js_test")
9 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_template_library")
10 | load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary", "sass_library")
11 |
12 | filegroup(
13 | name = "google_codelab_files",
14 | srcs = glob([
15 | "*.html",
16 | "img/**",
17 | ]) + [
18 | ":google_codelab_scss_bin",
19 | ":google_codelab_bin",
20 | ],
21 | )
22 |
23 | # Codelab step.
24 | closure_js_library(
25 | name = "google_codelab",
26 | srcs = [
27 | "google_codelab.js",
28 | "google_codelab_def.js"
29 | ],
30 | deps = [
31 | "@io_bazel_rules_closure//closure/library",
32 | ":google_codelab_soy",
33 | ]
34 | )
35 |
36 | # Compiled version of GoogleCodelabStep element, suitable for distribution.
37 | closure_js_binary(
38 | name = "google_codelab_bin",
39 | entry_points = ["googlecodelabs.CodelabDef"],
40 | deps = [":google_codelab"],
41 | )
42 |
43 | sass_library(
44 | name = "google_codelab_scss",
45 | srcs = ["google_codelab.scss"],
46 | )
47 |
48 | sass_library(
49 | name = "google_codelab_drawer_scss",
50 | srcs = ["_drawer.scss"],
51 | )
52 |
53 | sass_library(
54 | name = "google_codelab_steps_scss",
55 | srcs = ["_steps.scss"],
56 | )
57 |
58 | sass_binary(
59 | name = "google_codelab_scss_bin",
60 | src = "google_codelab.scss",
61 | deps = [
62 | ":google_codelab_drawer_scss",
63 | ":google_codelab_steps_scss",
64 | ]
65 | )
66 |
67 | closure_js_template_library(
68 | name = "google_codelab_soy",
69 | srcs = ["google_codelab.soy"]
70 | )
71 |
--------------------------------------------------------------------------------
/google-codelab/_drawer.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | google-codelab #drawer {
19 | background: #fff;
20 | width: 256px;
21 | flex-shrink: 0;
22 | position: relative;
23 | border-right: 1px solid #DADCE0;
24 | z-index: 100;
25 | display: flex;
26 | flex-direction: column;
27 | }
28 |
29 | google-codelab #drawer .steps {
30 | flex-shrink: 1;
31 | flex-grow: 1;
32 | overflow-x: visible;
33 | display: flex;
34 | }
35 |
36 | google-codelab #drawer ol {
37 | margin: 0;
38 | padding: 16px 12px;
39 | counter-reset: li-count;
40 | list-style: none;
41 | overflow-x: visible;
42 | overflow-y: auto;
43 | flex-grow: 1;
44 | }
45 |
46 | google-codelab #drawer ol li {
47 | display: block;
48 | counter-increment: li-count;
49 | }
50 |
51 | google-codelab #drawer ol li a {
52 | text-decoration: none;
53 | display: flex;
54 | align-items: center;
55 | font-size: 14px;
56 | color: #80868B;
57 | border-radius: 4px;
58 | padding: 6px 16px;
59 | min-height: 48px;
60 | font-weight: 400;
61 | line-height: 20px;
62 | box-sizing: content-box;
63 | position: relative;
64 | font-family: 'Roboto', 'Noto', sans-serif;
65 | -webkit-font-smoothing: antialiased;
66 | transition: all 300ms ease-in-out;
67 | }
68 |
69 | google-codelab #drawer ol li a:active,
70 | google-codelab #drawer ol li a:focus {
71 | background: #c6c6c6;
72 | -webkit-tap-highlight-color: transparent;
73 | outline: 0;
74 | }
75 |
76 | google-codelab #drawer ol li a .step {
77 | display: flex;
78 | align-items: center;
79 | }
80 |
81 | google-codelab #drawer ol li .step:before {
82 | content: counter(li-count);
83 | display: inline-block;
84 | font-style: normal;
85 | width: 26px;
86 | min-width: 26px;
87 | color: #fff;
88 | background: #80868B;
89 | border-radius: 50%;
90 | text-align: center;
91 | height: 26px;
92 | vertical-align: middle;
93 | line-height: 26px;
94 | margin-right: 8px;
95 | font-weight: 400;
96 | position: relative;
97 | z-index: 2;
98 | transition: all 300ms ease-in-out;
99 | }
100 |
101 | google-codelab #drawer ol li a:before,
102 | google-codelab #drawer ol li a:after {
103 | content: '';
104 | display: block;
105 | background-color: #80868B;
106 | width: 2px;
107 | height: 50%;
108 | z-index: 1;
109 | position: absolute;
110 | margin-left: 12px;
111 | }
112 |
113 | google-codelab #drawer ol li a:before {
114 | top: 0;
115 | }
116 |
117 | google-codelab #drawer ol li a:after {
118 | bottom: 0;
119 | }
120 |
121 | google-codelab #drawer ol li:first-child a:before,
122 | google-codelab #drawer ol li:last-child a:after {
123 | display: none;
124 | }
125 |
126 | google-codelab #drawer ol li[selected] a,
127 | google-codelab #drawer ol li a:focus {
128 | color: #212121;
129 | font-weight: 600;
130 | }
131 |
132 | google-codelab #drawer ol li[selected] a {
133 | background-color: #e0e0e0;
134 | }
135 |
136 | google-codelab #drawer ol li[selected] .step:before {
137 | font-weight: 600;
138 | }
139 |
140 | google-codelab #drawer ol li[completed] a {
141 | color: #212121;
142 | }
143 |
144 | google-codelab #drawer ol li[completed] a:before,
145 | google-codelab #drawer ol li[completed] a:after,
146 | google-codelab #drawer ol li[completed] .step:before {
147 | background-color: #1A73E8;
148 | color: #fff;
149 | }
150 |
151 | google-codelab #drawer ol li[selected] a:after {
152 | background-color: #80868B;
153 | }
154 |
155 |
156 | google-codelab #drawer .metadata {
157 | color: #777;
158 | font-size: 0.7em;
159 | padding: 16px;
160 | flex-shrink: 0;
161 | }
162 |
163 | google-codelab #drawer .metadata a {
164 | color: currentcolor;
165 | margin-left: 4px;
166 | }
167 |
168 | google-codelab #codelab-nav-buttons #menu {
169 | display: none;
170 | }
171 |
172 | google-codelab #drawer {
173 | ::-webkit-scrollbar {
174 | -webkit-appearance: none;
175 | width: 7px;
176 | }
177 | ::-webkit-scrollbar-thumb {
178 | border-radius: 4px;
179 | background-color: rgba(0,0,0,.5);
180 | -webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
181 | }
182 | }
183 |
184 |
185 | @media (max-width: 768px) {
186 | google-codelab {
187 | display: block;
188 | position: relative;
189 | }
190 |
191 | google-codelab #main {
192 | height: 100%;
193 | }
194 |
195 | google-codelab #codelab-nav-buttons #arrow-back {
196 | display: none;
197 | }
198 |
199 | google-codelab #codelab-nav-buttons #menu {
200 | display: flex;
201 | }
202 |
203 | google-codelab #drawer {
204 | width: 256px;
205 | position: absolute;
206 | left: 0;
207 | top: 0;
208 | bottom: 0;
209 | z-index: 1000;
210 | will-change: transform;
211 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0);
212 | pointer-events: none;
213 | transform: translate3d(-100%, 0, 0);
214 | transition: transform ease-in-out 0.3s, box-shadow 0.3s;
215 | }
216 |
217 | google-codelab[drawer--open] #drawer {
218 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15);
219 | transform: translate3d(0, 0, 0);
220 | pointer-events: all;
221 | }
222 |
223 | google-codelab #main::before {
224 | content: '';
225 | top: 0;
226 | left: 0;
227 | right: 0;
228 | bottom: 0;
229 | position: absolute;
230 | transition: opacity ease-in-out 0.38s;
231 | background-color: rgba(0, 0, 0, 0.3);
232 | z-index: 10;
233 | pointer-events: none;
234 | opacity: 0;
235 | }
236 |
237 | google-codelab[drawer--open] #main::before {
238 | opacity: 1;
239 | pointer-events: all;
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/google-codelab/_steps.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | google-codelab #steps {
19 | overflow: hidden;
20 | flex-direction: column;
21 | position: relative;
22 | flex-grow: 1;
23 | }
24 |
25 | google-codelab google-codelab-step {
26 | display: none;
27 | width: 100%;
28 | transform: translate3d(0, 0, 0);
29 | position: absolute;
30 | top: 0;
31 | left: 0;
32 | right: 0;
33 | bottom: 0;
34 | padding-top: 32px;
35 | overflow-y: auto;
36 | overflow-x: hidden;
37 | }
38 |
39 | google-codelab google-codelab-step[animating],
40 | google-codelab google-codelab-step[selected] {
41 | display: block;
42 | transform-origin: 0 50% 0;
43 | animation-fill-mode: both;
44 | }
45 |
46 | google-codelab google-codelab-step[animating] {
47 | pointer-events: none;
48 | position: absolute;
49 | overflow: hidden;
50 | }
51 |
--------------------------------------------------------------------------------
/google-codelab/google_codelab.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | html, body {
19 | height: 100%;
20 | width: 100%;
21 | margin: 0;
22 | padding: 0;
23 | }
24 | body {
25 | font-family: "Roboto",sans-serif;
26 | transition: opacity ease-in 0.2s;
27 | }
28 |
29 | * {
30 | box-sizing: border-box;
31 | }
32 |
33 | [hidden] {
34 | display: none !important;
35 | }
36 |
37 | google-codelab {
38 | display: flex;
39 | width: 100%;
40 | height: 100%;
41 | }
42 |
43 | google-codelab #main {
44 | display: flex;
45 | flex-direction: column;
46 | flex-grow: 1;
47 | position: relative;
48 | background: #F8F9FA;
49 | }
50 |
51 | google-codelab #codelab-title {
52 | background: #FFFFFF;
53 | box-shadow: 0px 1px 2px 0px rgba(60, 64, 67, 0.3), 0px 2px 6px 2px rgba(60, 64, 67, 0.15);
54 | color: #3C4043;
55 | display: flex;
56 | align-items: center;
57 | justify-content: space-between;
58 | height: 64px;
59 | padding: 0 16px;
60 | -webkit-font-smoothing: antialiased;
61 | z-index: 99;
62 | }
63 |
64 | google-codelab #codelab-title h1 {
65 | font-size: 20px;
66 | font-weight: 400;
67 | margin: 0 8px;
68 | font-family: 'Roboto', 'Noto', sans-serif;
69 | flex-grow: 1;
70 | flex-shrink: 1;
71 | white-space: nowrap;
72 | text-overflow: ellipsis;
73 | overflow: hidden;
74 | width: 0;
75 | }
76 |
77 | google-codelab #codelab-title #time-remaining {
78 | flex-shrink: 0;
79 | flex-grow: 0;
80 | display: flex;
81 | align-items: center;
82 | font-size: 16px;
83 | font-weight: 400;
84 | }
85 |
86 | google-codelab #codelab-title #time-remaining i {
87 | margin-right: 3px;
88 | }
89 |
90 | google-codelab #codelab-nav-buttons {
91 | display: flex;
92 | align-items: center;
93 | flex-grow: 0;
94 | flex-shrink: 0;
95 | }
96 |
97 | google-codelab #codelab-nav-buttons #arrow-back,
98 | google-codelab #codelab-nav-buttons #menu {
99 | display: flex;
100 | text-decoration: none;
101 | color: #3C4043;
102 | width: 40px;
103 | height: 40px;
104 | justify-content: center;
105 | align-items: center;
106 | }
107 |
108 | google-codelab #controls {
109 | position: absolute;
110 | bottom: 32px;
111 | left: 0;
112 | right: 0;
113 | display: flex;
114 | justify-content: center;
115 | z-index: 10;
116 | padding: 0 32px;
117 | }
118 |
119 | google-codelab #fabs {
120 | display: flex;
121 | flex-grow: 1;
122 | max-width: 1025px;
123 | }
124 |
125 | google-codelab #fabs .spacer {
126 | flex-grow: 1;
127 | }
128 |
129 | #previous-step, #next-step, #done {
130 | border-radius: 4px;
131 | font-family: 'Google Sans', Arial, sans-serif;
132 | font-size: 14px;
133 | font-weight: 600;
134 | letter-spacing: .6px;
135 | line-height: 24px;
136 | padding-bottom: 6px;
137 | padding-left: 24px;
138 | padding-right: 24px;
139 | padding-top: 6px;
140 | pointer-events: initial;
141 | text-transform: none;
142 | background: #FFFFFF;
143 | color: #1A73E8;
144 | transform: scale(1, 1);
145 | transition: transform 300ms ease-in-out;
146 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
147 | text-decoration: none;
148 | -webkit-font-smoothing: antialiased;
149 | }
150 |
151 | #next-step {
152 | color: #fff;
153 | background: #1A73E8;
154 | }
155 |
156 | #done {
157 | background: #1E8E3E;
158 | color: #fff;
159 | }
160 |
161 | google-codelab #fabs a[disappear] {
162 | transform: scale(0, 0);
163 | }
164 |
165 | #done {
166 | background: #0f9d58;
167 | }
168 |
169 | @import "drawer";
170 | @import "steps";
171 |
--------------------------------------------------------------------------------
/google-codelab/google_codelab.soy:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | {namespace googlecodelabs.Codelab.Templates}
19 |
20 | /**
21 | * Renders the main structure
22 | */
23 | {template .structure}
24 | {@param homeUrl: string}
25 | Drawer
26 |
43 | {/template}
44 |
45 |
46 | /**
47 | * Renders the title
48 | */
49 | {template .title}
50 | {@param title: string}
51 | {$title}
52 | {/template}
53 |
54 |
55 | /**
56 | * Renders the time remaining
57 | */
58 | {template .timeRemaining}
59 | {@param time: number}
60 |
61 | access_time
62 | {if $time == 1}
63 | {$time} min remaining
64 | {else}
65 | {$time} mins remaining
66 | {/if}
67 |
68 | {/template}
69 |
70 |
71 | /**
72 | * Renders the drawer
73 | */
74 | {template .drawer}
75 | {@param steps: list}
76 | {@param? feedback: string}
77 |
78 |
79 | {for $step in $steps}
80 | {$step}
81 | {/for}
82 |
83 |
84 | {if $feedback}
85 |
89 | {/if}
90 | {/template}
91 |
--------------------------------------------------------------------------------
/google-codelab/google_codelab_def.js:
--------------------------------------------------------------------------------
1 | goog.module('googlecodelabs.CodelabDef');
2 | const Codelab = goog.require('googlecodelabs.Codelab');
3 |
4 | try {
5 | window.customElements.define(Codelab.getTagName(), Codelab);
6 | } catch (e) {
7 | console.warn('googlecodelabs.Codelab', e);
8 | }
--------------------------------------------------------------------------------
/google-codelab/img/25c5ac88e3641e75.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/25c5ac88e3641e75.png
--------------------------------------------------------------------------------
/google-codelab/img/350dceb89c6e3968.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/350dceb89c6e3968.png
--------------------------------------------------------------------------------
/google-codelab/img/3f1ab21e1e5c772b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/3f1ab21e1e5c772b.png
--------------------------------------------------------------------------------
/google-codelab/img/53b42d1efc0e0295.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/53b42d1efc0e0295.png
--------------------------------------------------------------------------------
/google-codelab/img/5c79e3f467c21ce6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/5c79e3f467c21ce6.png
--------------------------------------------------------------------------------
/google-codelab/img/7c7f4389428d02f9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/7c7f4389428d02f9.png
--------------------------------------------------------------------------------
/google-codelab/img/9dec2e61f3d3b641.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/9dec2e61f3d3b641.png
--------------------------------------------------------------------------------
/google-codelab/img/a21ac67adf427ddc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/a21ac67adf427ddc.png
--------------------------------------------------------------------------------
/google-codelab/img/a322aaec88da31f0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/a322aaec88da31f0.png
--------------------------------------------------------------------------------
/google-codelab/img/afb844ab04c5e37a.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/afb844ab04c5e37a.png
--------------------------------------------------------------------------------
/google-codelab/img/b79cf053ec60b7a4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/b79cf053ec60b7a4.png
--------------------------------------------------------------------------------
/google-codelab/img/dd9ae517d0d8e68f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/dd9ae517d0d8e68f.png
--------------------------------------------------------------------------------
/google-codelab/img/f43aa9981defd294.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/f43aa9981defd294.png
--------------------------------------------------------------------------------
/google-codelab/img/fb8ec99e99f182ac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/codelab-elements/716b16f56a0be02abdf3c51b9ab4eb0ab28f15b6/google-codelab/img/fb8ec99e99f182ac.png
--------------------------------------------------------------------------------
/third_party/BUILD.es6shim:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | filegroup(
4 | name = "es6all",
5 | srcs = [
6 | "es6-sham.min.js",
7 | "es6-sham.map",
8 | "es6-shim.min.js",
9 | "es6-shim.map",
10 | ]
11 | )
12 |
--------------------------------------------------------------------------------
/third_party/BUILD.polyfill:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | filegroup(
4 | name = "custom_elements",
5 | srcs = [
6 | "custom-elements.min.js",
7 | "custom-elements.min.js.map",
8 | ]
9 | )
10 |
11 | filegroup(
12 | name = "native_shim",
13 | srcs = ["src/native-shim.js"],
14 | )
15 |
--------------------------------------------------------------------------------
/third_party/BUILD.prettify:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | filegroup(
4 | name = "prettify",
5 | srcs = ["src/prettify.js"],
6 | )
--------------------------------------------------------------------------------
/tools/BUILD.bazel:
--------------------------------------------------------------------------------
1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary")
2 |
3 | licenses(["notice"])
4 |
5 | # The gen_test_html.template is used by js_test rule.
6 | # See defs.bzl for details.
7 | exports_files(["LICENSE", "gen_test_html.template"])
8 |
9 | # The JS tests runner.
10 | # See defs.bzl for details.
11 | go_binary(
12 | name = "webtest",
13 | testonly = 1,
14 | srcs = ["webtest.go"],
15 | visibility = ["//:__subpackages__"],
16 | deps = [
17 | "@com_github_tebeka_selenium//:go_default_library",
18 | "@io_bazel_rules_webtesting//go/webtest:go_default_library",
19 | ],
20 | )
21 |
22 | # A simple static file server. Handy for running tests manually or inspecting
23 | # content from a browser.
24 | # Execute "bazel run //tools:server" from command line
25 | # and open http://localhost:8080 in a browser.
26 | go_binary(
27 | name = "server",
28 | testonly = 1,
29 | srcs = ["server.go"],
30 | data = [
31 | "//demo:demo_files",
32 | "//demo:hello_bin",
33 | "//demo:hello_test",
34 | "//google-codelab:google_codelab_files",
35 | "//google-codelab-analytics:google_codelab_analytics_files",
36 | "//google-codelab-index:google_codelab_index_files",
37 | "//google-codelab-step:google_codelab_step_files",
38 | "//google-codelab-survey:google_codelab_survey_files",
39 | "@polyfill//:custom_elements",
40 | "@polyfill//:native_shim",
41 | "@prettify//:prettify",
42 | ],
43 | )
44 |
--------------------------------------------------------------------------------
/tools/bazel.rc:
--------------------------------------------------------------------------------
1 | build --strategy=Closure=worker
2 |
--------------------------------------------------------------------------------
/tools/ci-continuous.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright 2018 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | set -ex
17 |
18 | use_bazel.sh 0.19.1
19 |
20 | # Make this repo's bazel workspace the current work dir.
21 | # Relative to this script location.
22 | cd $(dirname $0)/..
23 | bazel info
24 |
25 | BAZEL_FLAGS="--color=no \
26 | --curses=no \
27 | --verbose_failures \
28 | --show_task_finish \
29 | --show_timestamps"
30 |
31 | bazel build -s $BAZEL_FLAGS ...
32 | bazel test -s $BAZEL_FLAGS --test_output=all --test_arg=-debug ...
33 |
--------------------------------------------------------------------------------
/tools/ci-presubmit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright 2018 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | set -ex
17 |
18 | use_bazel.sh 0.19.1
19 |
20 | # Make this repo's bazel workspace the current work dir.
21 | # Relative to this script location.
22 | cd $(dirname $0)/..
23 | bazel info
24 |
25 | BAZEL_FLAGS="--color=no \
26 | --curses=no \
27 | --verbose_failures \
28 | --show_task_finish \
29 | --show_timestamps"
30 |
31 | # TODO(#2): Use more sensitive build/test targets when CI is working.
32 | bazel build -s $BAZEL_FLAGS ...
33 | bazel test -s $BAZEL_FLAGS --test_output=all --test_arg=-debug ...
34 |
--------------------------------------------------------------------------------
/tools/defs.bzl:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google Inc.
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 | # http://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 | """Repo's bazel rules and macros."""
16 |
17 | load("@io_bazel_rules_closure//closure:defs.bzl",
18 | _closure_js_binary_alias="closure_js_binary")
19 | load("@io_bazel_rules_closure//closure:defs.bzl",
20 | _closure_js_library_alias="closure_js_library")
21 | load("@io_bazel_rules_webtesting//web:web.bzl", "web_test_suite")
22 |
23 | def concat(ext):
24 | """Returns a genrule command to concat files with the extension ext."""
25 | return "ls $(SRCS) | grep -E '\.{ext}$$' | xargs cat > $@".format(ext=ext)
26 |
27 | def closure_js_library(**kwargs):
28 | """Invokes actual closure_js_library with defaults suitable
29 | for non-test JS source files.
30 | """
31 | kwargs.setdefault("convention", "GOOGLE")
32 | suppress = kwargs.pop("suppress", [])
33 | suppress.append("JSC_UNKNOWN_EXPR_TYPE")
34 | kwargs.update(dict(suppress=suppress))
35 | _closure_js_library_alias(**kwargs)
36 |
37 | def closure_js_binary(**kwargs):
38 | """Invokes actual closure_js_binary with defaults suitable
39 | for non-test JS files compilation.
40 | """
41 | kwargs.setdefault("compilation_level", "ADVANCED")
42 | kwargs.setdefault("dependency_mode", "STRICT")
43 | kwargs.setdefault("language", "ECMASCRIPT5_STRICT")
44 | kwargs.setdefault("defs", [
45 | "--assume_function_wrapper",
46 | "--rewrite_polyfills=false",
47 | "--new_type_inf",
48 | "--export_local_property_definitions",
49 | "--language_out=ES5_STRICT",
50 | "--isolation_mode=IIFE",
51 | "--generate_exports",
52 | "--jscomp_warning=newCheckTypes",
53 | "--jscomp_off=newCheckTypesExtraChecks",
54 | "--hide_warnings_for=closure/goog",
55 | ])
56 | _closure_js_binary_alias(**kwargs)
57 |
58 | def _gen_test_html_impl(ctx):
59 | """Implementation of the gen_test_html rule."""
60 | ctx.actions.expand_template(
61 | template=ctx.file._template,
62 | output=ctx.outputs.html_file,
63 | substitutions={
64 | "{{TEST_FILE_JS}}": ctx.attr.test_file_js
65 | })
66 | runfiles = ctx.runfiles(files=[ctx.outputs.html_file], collect_default=True)
67 | return [DefaultInfo(runfiles=runfiles)]
68 |
69 | # A rule used by js_test to generate default test.html file
70 | # suitable for running Closure-based JS tests.
71 | # The test_file_js argument specifies the name of the JS file containing tests,
72 | # typically created with closure_js_binary.
73 | # The output is created from gen_test_html.template file.
74 | gen_test_html = rule(
75 | implementation=_gen_test_html_impl,
76 | attrs={
77 | "test_file_js": attr.string(mandatory=True),
78 | "_template": attr.label(default=Label("//tools:gen_test_html.template"),
79 | allow_files=True, single_file=True),
80 | },
81 | outputs={"html_file": "%{name}.html"},
82 | )
83 |
84 | def js_test(name,
85 | srcs,
86 | browsers,
87 | data=None,
88 | deps=None,
89 | compilation_level=None,
90 | css=None,
91 | entry_points=None,
92 | html=None,
93 | suppress=None,
94 | visibility=None,
95 | **kwargs):
96 | """A lower level macro which creates JS tests suite.
97 |
98 | It creates three targets: _lib closure_js_library,
99 | _bin closure_js_binary with the former as a dependencies,
100 | and web_test_suite with the _bin in its data dependencies.
101 | All targets have testonly attribute set to True.
102 |
103 | For more details about closure_js_library and closure_js_binary,
104 | see https://github.com/bazelbuild/rules_closure.
105 |
106 | Args:
107 | name: The name of the test target.
108 | srcs: A list of test source files with _test.js suffix.
109 | browsers: A list of browsers to run on. See rules_webtesting
110 | for list of supported browsers: https://goo.gl/VVH8tP.
111 | data: A list of data dependencies passed to closure_js_library.
112 | deps: list of code dependencies passed to closure_js_library.
113 | compilation_level: Closure compiler compilation level.
114 | css: A CSS class renaming target passed to closure_js_binary.
115 | It must point to a closure_css_binary rule.
116 | entry_points: List of unreferenced namespaces which should not
117 | be pruned by the compiler. See //demo:hello_test
118 | for a usage example.
119 | html: An HTML file which declares the generated closure_js_binary
120 | target it its
26 |
32 |
33 |