├── .editorconfig ├── .eslintrc ├── .gitignore ├── 3d-card-flip ├── card-flip.js ├── images │ ├── penumbra.svg │ ├── supercharged.jpg │ └── umbra.svg ├── index.html ├── inert.js └── styles.css ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── accordion ├── index.html ├── sc-accordion.js ├── sc-pane.js └── styles.css ├── animated-blur ├── badblur.html ├── goodblur.html ├── images │ ├── android.png │ ├── chrome.png │ ├── gmail.png │ ├── maps.png │ ├── photos.png │ ├── search.png │ ├── translate.png │ └── youtube.png ├── index.html ├── reallybadblur.html └── scripts │ └── animated_blur.js ├── animated-clip ├── advanced │ ├── expando.css │ ├── expando.js │ └── index.html ├── index.html └── menu.js ├── code-splitting ├── .gitignore ├── README.md ├── app │ ├── about │ │ └── index.html │ ├── contact │ │ └── index.html │ ├── favicon.ico │ ├── footer.partial.html │ ├── header.partial.html │ ├── index.html │ ├── misc │ │ └── index.html │ ├── sitemap.xml │ └── static │ │ ├── about.js │ │ ├── app.js │ │ ├── contact.js │ │ ├── database.js │ │ ├── images │ │ └── spinner.png │ │ ├── index.js │ │ ├── misc.js │ │ ├── mp4.js │ │ ├── sc-router.js │ │ ├── sc-view.js │ │ ├── superstyles.css │ │ └── videoencoder.js ├── build.js ├── index.js └── package.json ├── custom-scrollbar ├── cat.gif ├── index.html ├── scrollbar.js ├── step-1.html ├── step-2.html ├── step-3.html ├── step-4.html ├── step-5.html └── step-6.html ├── expand-collapse ├── demo.js ├── flip.js ├── images │ └── img.jpg ├── index.html └── style.css ├── firebase-firestore-comments ├── .babelrc ├── .firebaserc ├── .gitignore ├── firebase.json ├── package.json ├── src │ ├── client │ │ └── index.js │ ├── components │ │ ├── sc-comment-form.js │ │ ├── sc-comment-list.js │ │ ├── sc-comment.js │ │ ├── sc-element.js │ │ └── sc-login.js │ └── server │ │ ├── index.html │ │ └── index.js └── yarn.lock ├── flip-switch ├── flip-switch.html ├── flip-switch.js ├── index.html └── inert.min.js ├── image-zoomer ├── gibraltar.jpg ├── image-zoomer.js ├── index.html └── styles.css ├── index.html ├── infinite-scroller ├── es6-promise.js ├── images │ ├── avatar0.jpg │ ├── avatar1.jpg │ ├── avatar2.jpg │ ├── avatar3.jpg │ ├── image0.jpg │ ├── image1.jpg │ ├── image10.jpg │ ├── image11.jpg │ ├── image12.jpg │ ├── image13.jpg │ ├── image14.jpg │ ├── image15.jpg │ ├── image16.jpg │ ├── image17.jpg │ ├── image18.jpg │ ├── image19.jpg │ ├── image2.jpg │ ├── image20.jpg │ ├── image21.jpg │ ├── image22.jpg │ ├── image23.jpg │ ├── image24.jpg │ ├── image25.jpg │ ├── image26.jpg │ ├── image27.jpg │ ├── image28.jpg │ ├── image29.jpg │ ├── image3.jpg │ ├── image30.jpg │ ├── image31.jpg │ ├── image32.jpg │ ├── image33.jpg │ ├── image34.jpg │ ├── image35.jpg │ ├── image36.jpg │ ├── image37.jpg │ ├── image38.jpg │ ├── image39.jpg │ ├── image4.jpg │ ├── image40.jpg │ ├── image41.jpg │ ├── image42.jpg │ ├── image43.jpg │ ├── image44.jpg │ ├── image45.jpg │ ├── image46.jpg │ ├── image47.jpg │ ├── image48.jpg │ ├── image49.jpg │ ├── image5.jpg │ ├── image50.jpg │ ├── image51.jpg │ ├── image52.jpg │ ├── image53.jpg │ ├── image54.jpg │ ├── image55.jpg │ ├── image56.jpg │ ├── image57.jpg │ ├── image58.jpg │ ├── image59.jpg │ ├── image6.jpg │ ├── image60.jpg │ ├── image61.jpg │ ├── image62.jpg │ ├── image63.jpg │ ├── image64.jpg │ ├── image65.jpg │ ├── image66.jpg │ ├── image67.jpg │ ├── image68.jpg │ ├── image69.jpg │ ├── image7.jpg │ ├── image70.jpg │ ├── image71.jpg │ ├── image72.jpg │ ├── image73.jpg │ ├── image74.jpg │ ├── image75.jpg │ ├── image76.jpg │ ├── image8.jpg │ ├── image9.jpg │ └── unknown.jpg ├── index.html ├── scripts │ ├── infinite-scroll.js │ └── messages.js ├── stats.min.js └── styles │ └── messages.css ├── lazy-image ├── index.js ├── package-lock.json ├── package.json └── static │ ├── images │ ├── a.jpg │ ├── b.jpg │ ├── c.jpg │ └── d.jpg │ ├── index.html │ ├── index_rendered.html │ ├── sc-img.js │ └── styles.css ├── parallax ├── header.html ├── images │ ├── screengrab.jpg │ ├── sea.jpg │ ├── stars.jpg │ └── sunset.jpg ├── index.html └── scripts │ └── parallax.js ├── router-advanced ├── README.md ├── about │ └── index.html ├── contact │ └── index.html ├── favicon.ico ├── index.html ├── misc │ └── index.html └── static │ ├── app.js │ ├── images │ └── spinner.png │ ├── sc-router.js │ ├── sc-view.js │ └── superstyles.css ├── router ├── README.md ├── app.yaml ├── favicon.ico ├── index.html ├── index.yaml ├── main.py └── static │ ├── sc-router.js │ ├── sc-view.js │ └── superstyles.css ├── server-side-rendering ├── README.md ├── app │ ├── about │ │ └── index.html │ ├── contact │ │ └── index.html │ ├── favicon.ico │ ├── footer.partial.html │ ├── header.partial.html │ ├── index.html │ ├── misc │ │ └── index.html │ └── static │ │ ├── app.js │ │ ├── images │ │ └── spinner.png │ │ ├── sc-router.js │ │ ├── sc-view.js │ │ └── superstyles.css ├── cert.pem ├── index.js ├── key.pem └── package.json ├── service-worker ├── README.md ├── app │ ├── about │ │ └── index.html │ ├── contact │ │ └── index.html │ ├── favicon.ico │ ├── footer.partial.html │ ├── header.partial.html │ ├── index.html │ ├── misc │ │ └── index.html │ ├── static │ │ ├── app.js │ │ ├── images │ │ │ └── spinner.png │ │ ├── sc-router.js │ │ ├── sc-view.js │ │ └── superstyles.css │ └── sw.js ├── cert.pem ├── index.js ├── key.pem └── package.json ├── side-nav ├── detabinator.js ├── index.html ├── side-nav.js └── styles.css ├── stream-progress ├── README.md ├── dial.js ├── index.html ├── stream.js ├── third_party │ └── transformstream.js └── tweets.json ├── streaming-service-worker ├── client │ └── sw.js ├── package-lock.json ├── package.json ├── posts │ ├── combining-fonts │ │ ├── content.md │ │ └── meta.json │ ├── es-modules-in-browsers │ │ ├── content.md │ │ └── meta.json │ └── h2-push-tougher-than-i-thought │ │ ├── content.md │ │ └── meta.json ├── server │ ├── error404.js │ ├── index.js │ ├── marked.js │ ├── rev.js │ └── routes.js ├── static │ ├── css │ │ ├── all.css │ │ └── imgs │ │ │ ├── check.png │ │ │ ├── graph-tile.png │ │ │ └── social-icons.png │ ├── favicon.ico │ └── imgs │ │ ├── browser-icons │ │ ├── chrome.png │ │ ├── edge.png │ │ ├── firefox.png │ │ └── safari.png │ │ ├── icon.png │ │ └── me.jpg └── templates │ ├── 404.njk │ ├── base-side.njk │ ├── base.njk │ ├── index.njk │ ├── offline-inc.njk │ ├── post-inc.njk │ ├── post.njk │ ├── shell.njk │ ├── who-inc.njk │ └── who.njk ├── swipeable-cards ├── cards.css ├── cards.js └── index.html ├── template ├── element.css ├── element.js └── index.html ├── web-workers ├── index.html ├── main.css └── worker.js └── webgl-image-processing ├── app.js ├── image.jpg └── index.html /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "google", 3 | "env": { 4 | "browser": true, 5 | "es6": true 6 | }, 7 | "rules": { 8 | "space-before-function-paren": [2, {"named": "always", "anonymous": "always"}], 9 | "max-len": [2, 100, { 10 | "ignoreComments": true, 11 | "ignoreUrls": true, 12 | "tabWidth": 2 13 | }], 14 | "no-implicit-coercion": [2, { 15 | "boolean": false, 16 | "number": true, 17 | "string": true 18 | }], 19 | "no-unused-expressions": [2, { 20 | "allowShortCircuit": true, 21 | "allowTernary": false 22 | }], 23 | "no-unused-vars": [2, { 24 | "vars": "all", 25 | "args": "after-used", 26 | "argsIgnorePattern": "(^reject$|^_$)", 27 | "varsIgnorePattern": "(^_$)" 28 | }], 29 | "quotes": [2, "single"], 30 | "require-jsdoc": 0, 31 | "valid-jsdoc": 0 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.pyc 4 | serve 5 | *.pem 6 | .vscode 7 | -------------------------------------------------------------------------------- /3d-card-flip/images/penumbra.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /3d-card-flip/images/supercharged.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/3d-card-flip/images/supercharged.jpg -------------------------------------------------------------------------------- /3d-card-flip/images/umbra.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /3d-card-flip/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 3D Card Flip 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |

Supercharged

28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /3d-card-flip/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | margin: 0; 24 | padding: 0; 25 | width: 100%; 26 | height: 100%; 27 | font-family: Arial, Helvetica, sans-serif; 28 | } 29 | 30 | body { 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | background: #E2E2E2; 35 | overflow-x: hidden; 36 | overflow-y: scroll; 37 | } 38 | 39 | sc-card { 40 | width: 260px; 41 | height: 380px; 42 | position: relative; 43 | perspective: 500px; 44 | will-change: transform; 45 | } 46 | 47 | sc-card .front, 48 | sc-card .back { 49 | backface-visibility: hidden; 50 | position: absolute; 51 | width: 100%; 52 | height: 100%; 53 | display: flex; 54 | align-items: center; 55 | justify-content: center; 56 | border-radius: 3px; 57 | overflow: hidden; 58 | } 59 | 60 | sc-card button { 61 | position: absolute; 62 | top: 0; 63 | left: 0; 64 | width: 100%; 65 | height: 100%; 66 | background: none; 67 | border: none; 68 | text-indent: -10000px; 69 | /*display: none;*/ 70 | } 71 | 72 | sc-card .front { 73 | background: #444; 74 | color: #FFF; 75 | } 76 | 77 | sc-card .front h1 { 78 | font-size: 24px; 79 | } 80 | 81 | sc-card .back { 82 | background: url(images/supercharged.jpg) center center no-repeat; 83 | color: #FFF; 84 | transform: rotateY(180deg); 85 | } 86 | 87 | sc-card .umbra, 88 | sc-card .penumbra { 89 | width: 100%; 90 | height: 100%; 91 | position: absolute; 92 | top: 0; 93 | left: 0; 94 | backface-visibility: visible; 95 | } 96 | 97 | sc-card .umbra { 98 | width: 270px; 99 | height: 390px; 100 | top: -5px; 101 | left: -5px; 102 | background: url(images/umbra.svg) center center no-repeat; 103 | transform: translateY(2px); 104 | opacity: 0.3; 105 | } 106 | 107 | sc-card .penumbra { 108 | width: 330px; 109 | height: 450px; 110 | top: -35px; 111 | left: -35px; 112 | background: url(images/penumbra.svg) center center no-repeat; 113 | transform: translateY(2px); 114 | opacity: 0; 115 | } 116 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we have to jump a couple of legal hurdles. 6 | 7 | Please fill out either the individual or corporate Contributor License Agreement (CLA). 8 | * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 9 | * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 10 | 11 | Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to 12 | accept your pull requests. 13 | 14 | ## Contributing A Patch 15 | 16 | 1. Submit an issue describing your proposed change to the repo in question. 17 | 1. The repo owner will respond to your issue promptly. 18 | 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 19 | 1. Fork the desired repo, develop and test your code changes. 20 | 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. 21 | 1. Submit a pull request. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI Elements 2 | 3 | A collection of UI element samples written with vanilla web platform features. 4 | 5 | **Please note: these are prototypes, and not production-ready. The aim is to demonstrate a solid approach for building performant UI elements.** 6 | 7 | ## Getting started 8 | 9 | 1. Clone the repo. 10 | 2. Open an element's subfolder. 11 | 3. Run index.html 12 | 13 | ## ES2015 and ES5 Transpilation 14 | 15 | Please note: these samples are written in ES2015 and, as such, require [direct browser support](https://kangax.github.io/compat-table/es6/). You can transpile the examples with [BabelJS](https://babeljs.io/docs/usage/cli/): 16 | 17 | ```bash 18 | babel --presets=es2015 swipeable-cards/cards.js --out-file swipeable-cards/cards-es5.js 19 | babel --presets=es2015 side-nav/side-nav.js --out-file side-nav/side-nav-es5.js 20 | ``` 21 | 22 | But to keep things as simple as possible for everyone, that's out of scope for this set of samples. 23 | 24 | ## License 25 | 26 | Copyright 2015 Google, Inc. 27 | 28 | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at 29 | 30 | http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 33 | 34 | Please note: this is not a Google product 35 | -------------------------------------------------------------------------------- /accordion/sc-pane.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class SCPane extends HTMLElement { 21 | 22 | get header () { 23 | if (!this._header) { 24 | this._header = this.querySelector('button[role="tab"]'); 25 | } 26 | 27 | return this._header; 28 | } 29 | 30 | get content () { 31 | if (!this._content) { 32 | this._content = this.querySelector('.content'); 33 | } 34 | 35 | return this._content; 36 | } 37 | 38 | attachedCallback () { 39 | this.header.addEventListener('click', _ => { 40 | const customEvent = new CustomEvent('panel-change', { 41 | bubbles: true 42 | }); 43 | 44 | this.dispatchEvent(customEvent); 45 | }); 46 | } 47 | } 48 | 49 | document.registerElement('sc-pane', SCPane); 50 | -------------------------------------------------------------------------------- /accordion/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | margin: 0; 24 | padding: 0; 25 | height: 100vh; 26 | width: 100vw; 27 | font-family: Arial, Helvetica, sans-serif; 28 | } 29 | 30 | body { 31 | display: flex; 32 | flex-direction: column; 33 | align-items: center; 34 | justify-content: center; 35 | background: #EEE; 36 | } 37 | 38 | sc-accordion { 39 | width: 100%; 40 | max-width: 450px; 41 | box-shadow: 0 3px 4px rgba(0,0,0,0.4); 42 | background: #FFF; 43 | border-radius: 3px; 44 | } 45 | 46 | sc-accordion[enhanced] { 47 | visibility: hidden; 48 | height: 600px; 49 | overflow: hidden; 50 | position: relative; 51 | } 52 | 53 | sc-accordion[enhanced] sc-pane { 54 | position: absolute; 55 | top: 0; 56 | width: 100%; 57 | } 58 | 59 | sc-accordion[active] { 60 | visibility: visible; 61 | } 62 | 63 | sc-accordion[active] sc-pane { 64 | transition: transform 0.3s cubic-bezier(0, 0, 0.3, 1); 65 | } 66 | 67 | sc-pane button[role="tab"] { 68 | width: 100%; 69 | height: 48px; 70 | line-height: 48px; 71 | border: none; 72 | font-size: 16px; 73 | background: #666; 74 | color: #FFF; 75 | border-bottom: 1px solid #444; 76 | } 77 | 78 | sc-pane button[role="tab"]:focus { 79 | background: #333; 80 | } 81 | 82 | sc-pane .content { 83 | padding: 16px; 84 | overflow-y: scroll; 85 | background-color: #FFF; 86 | } 87 | -------------------------------------------------------------------------------- /animated-blur/badblur.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 66 |
67 | 68 | 69 | 70 | 71 |
72 | -------------------------------------------------------------------------------- /animated-blur/goodblur.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 64 |
65 |
66 | 67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 |
75 | 76 |
77 |
78 | -------------------------------------------------------------------------------- /animated-blur/images/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/android.png -------------------------------------------------------------------------------- /animated-blur/images/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/chrome.png -------------------------------------------------------------------------------- /animated-blur/images/gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/gmail.png -------------------------------------------------------------------------------- /animated-blur/images/maps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/maps.png -------------------------------------------------------------------------------- /animated-blur/images/photos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/photos.png -------------------------------------------------------------------------------- /animated-blur/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/search.png -------------------------------------------------------------------------------- /animated-blur/images/translate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/translate.png -------------------------------------------------------------------------------- /animated-blur/images/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/animated-blur/images/youtube.png -------------------------------------------------------------------------------- /animated-blur/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 18 | 19 | 78 | -------------------------------------------------------------------------------- /animated-blur/reallybadblur.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 28 |
29 | 30 |
31 | -------------------------------------------------------------------------------- /animated-clip/advanced/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 |
    28 |
  • One
  • 29 |
  • Two
  • 30 |
  • Three
  • 31 |
  • Four
  • 32 |
33 | 34 | 35 |
36 | 37 | 43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /code-splitting/.gitignore: -------------------------------------------------------------------------------- 1 | _*.js 2 | -------------------------------------------------------------------------------- /code-splitting/README.md: -------------------------------------------------------------------------------- 1 | # Code Splitting 2 | 3 | This is mostly the code from the `server-side-rendering` sample. We have added `build.js` to perform code splitting and 4 | route-based chunking. 5 | 6 | Run `npm install` to install all dependencies. Run `node build.js` and then run the server with `node index.js`. 7 | -------------------------------------------------------------------------------- /code-splitting/app/about/index.html: -------------------------------------------------------------------------------- 1 | 2 | About 3 | 4 | 5 | -------------------------------------------------------------------------------- /code-splitting/app/contact/index.html: -------------------------------------------------------------------------------- 1 | 2 | Contact 3 | 4 | 5 | -------------------------------------------------------------------------------- /code-splitting/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/code-splitting/app/favicon.ico -------------------------------------------------------------------------------- /code-splitting/app/footer.partial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /code-splitting/app/header.partial.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 25 | 33 | 34 | {{#ifNotEq item ""}} 35 | 36 | {{/ifNotEq}} 37 | {{#ifNotEq item "about"}} 38 | 39 | {{/ifNotEq}} 40 | {{#ifNotEq item "contact"}} 41 | 42 | {{/ifNotEq}} 43 | {{#ifNotEq item "misc"}} 44 | 45 | {{/ifNotEq}} 46 | -------------------------------------------------------------------------------- /code-splitting/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | Home 3 | 4 | 5 | -------------------------------------------------------------------------------- /code-splitting/app/misc/index.html: -------------------------------------------------------------------------------- 1 | 2 | Misc 3 | 4 | 5 | -------------------------------------------------------------------------------- /code-splitting/app/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://www.example.com/ 5 | 6 | 7 | http://www.example.com/about.html 8 | 9 | 10 | http://www.example.com/contact.html 11 | 12 | 13 | http://www.example.com/misc.html 14 | 15 | 16 | -------------------------------------------------------------------------------- /code-splitting/app/static/about.js: -------------------------------------------------------------------------------- 1 | import '/static/sc-view.js'; 2 | import '/static/sc-router.js'; 3 | import '/static/app.js'; 4 | -------------------------------------------------------------------------------- /code-splitting/app/static/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class App { 21 | constructor () { 22 | const router = document.querySelector('sc-router'); 23 | const links = Array.from(document.querySelectorAll('a')); 24 | 25 | function onClick (evt) { 26 | evt.preventDefault(); 27 | router.go(evt.target.href); 28 | } 29 | 30 | links.forEach(link => { 31 | link.addEventListener('click', onClick); 32 | }); 33 | } 34 | } 35 | 36 | (_ => new App())(); 37 | -------------------------------------------------------------------------------- /code-splitting/app/static/contact.js: -------------------------------------------------------------------------------- 1 | import '/static/sc-view.js'; 2 | import '/static/sc-router.js'; 3 | import '/static/app.js'; 4 | import '/static/database.js'; 5 | -------------------------------------------------------------------------------- /code-splitting/app/static/database.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/code-splitting/app/static/database.js -------------------------------------------------------------------------------- /code-splitting/app/static/images/spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/code-splitting/app/static/images/spinner.png -------------------------------------------------------------------------------- /code-splitting/app/static/index.js: -------------------------------------------------------------------------------- 1 | import '/static/sc-view.js'; 2 | import '/static/sc-router.js'; 3 | import '/static/app.js'; 4 | -------------------------------------------------------------------------------- /code-splitting/app/static/misc.js: -------------------------------------------------------------------------------- 1 | import '/static/sc-view.js'; 2 | import '/static/sc-router.js'; 3 | import '/static/app.js'; 4 | import '/static/database.js'; 5 | import '/static/videoencoder.js'; 6 | -------------------------------------------------------------------------------- /code-splitting/app/static/mp4.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/code-splitting/app/static/mp4.js -------------------------------------------------------------------------------- /code-splitting/app/static/sc-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class SCView extends HTMLElement { 21 | 22 | createdCallback () { 23 | this._view = null; 24 | this._isRemote = (this.getAttribute('remote') !== null); 25 | } 26 | 27 | get route () { 28 | return this.getAttribute('route') || null; 29 | } 30 | 31 | _hideSpinner () { 32 | this.classList.remove('pending'); 33 | } 34 | 35 | _showSpinner () { 36 | this.classList.add('pending'); 37 | } 38 | 39 | _loadView (data) { 40 | // Wait for half a second then show the spinner. 41 | const spinnerTimeout = setTimeout(_ => this._showSpinner(), 500); 42 | 43 | this._view = new DocumentFragment(); 44 | const xhr = new XMLHttpRequest(); 45 | 46 | xhr.onload = evt => { 47 | const newDoc = evt.target.response; 48 | const newView = newDoc.querySelector('sc-view.visible'); 49 | 50 | // Copy in the child nodes from the parent. 51 | newView.childNodes.forEach(node => { 52 | this._view.appendChild(node); 53 | }); 54 | 55 | // Add the fragment to the page. 56 | this.appendChild(this._view); 57 | const s = document.createElement('script'); 58 | s.type = 'module'; 59 | s.src = newDoc.querySelector('body > script').src; 60 | this.appendChild(s); 61 | 62 | // Clear the timeout and remove the spinner if needed. 63 | clearTimeout(spinnerTimeout); 64 | this._hideSpinner(); 65 | }; 66 | xhr.responseType = 'document'; 67 | xhr.open('GET', `${data[0]}?partial`); 68 | xhr.send(); 69 | } 70 | 71 | in (data) { 72 | if (this._isRemote && !this._view) { 73 | this._loadView(data); 74 | } 75 | 76 | return new Promise((resolve, reject) => { 77 | const onTransitionEnd = () => { 78 | this.removeEventListener('transitionend', onTransitionEnd); 79 | resolve(); 80 | }; 81 | 82 | this.classList.add('visible'); 83 | this.addEventListener('transitionend', onTransitionEnd); 84 | }); 85 | } 86 | 87 | out () { 88 | return new Promise((resolve, reject) => { 89 | const onTransitionEnd = () => { 90 | this.removeEventListener('transitionend', onTransitionEnd); 91 | resolve(); 92 | }; 93 | 94 | this.classList.remove('visible'); 95 | this.addEventListener('transitionend', onTransitionEnd); 96 | }); 97 | } 98 | 99 | update () { 100 | return Promise.resolve(); 101 | } 102 | } 103 | 104 | document.registerElement('sc-view', SCView); 105 | -------------------------------------------------------------------------------- /code-splitting/app/static/superstyles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | padding: 0; 24 | margin: 0; 25 | font-size: 24px; 26 | font-family: Arial, sans-serif; 27 | } 28 | 29 | sc-view { 30 | contain: strict; 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | width: 100%; 35 | height: 100%; 36 | opacity: 0; 37 | pointer-events: none; 38 | transform: scale(0.95); 39 | transition: transform 0.3s cubic-bezier(0,0,0.3,1), 40 | opacity 0.3s cubic-bezier(0,0,0.3,1); 41 | font-size: 20vh; 42 | display: flex; 43 | flex-direction: row; 44 | justify-content: center; 45 | align-items: center; 46 | color: white; 47 | } 48 | 49 | sc-view.visible { 50 | opacity: 1; 51 | pointer-events: auto; 52 | transform: none; 53 | } 54 | 55 | sc-view[remote]::before { 56 | opacity: 0; 57 | pointer-events: none; 58 | content: ''; 59 | position: fixed; 60 | top: 0; 61 | left: 0; 62 | right: 0; 63 | bottom: 0; 64 | background: inherit; 65 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 66 | } 67 | 68 | sc-view[remote]::after { 69 | opacity: 0; 70 | pointer-events: none; 71 | content: ''; 72 | position: fixed; 73 | left: 50%; 74 | top: 50%; 75 | width: 40px; 76 | height: 40px; 77 | background: url(images/spinner.png) center center no-repeat; 78 | background-size: 40px 40px; 79 | animation-name: spin; 80 | animation-duration: 1s; 81 | animation-timing-function: linear; 82 | animation-iteration-count: infinite; 83 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 84 | } 85 | 86 | sc-view[remote].pending::before, 87 | sc-view[remote].pending::after { 88 | opacity: 1; 89 | } 90 | 91 | @keyframes spin { 92 | from { 93 | transform: translate(-50%, -50%) rotate(0deg); 94 | } 95 | 96 | to { 97 | transform: translate(-50%, -50%) rotate(360deg); 98 | } 99 | } 100 | 101 | .view-home { 102 | background: rgb(128,00,64); 103 | } 104 | 105 | .view-contact { 106 | background: rgb(00,128,64); 107 | } 108 | 109 | .view-about { 110 | background: rgb(00,64,128); 111 | } 112 | 113 | .view-misc { 114 | background: rgb(128,64,0); 115 | } 116 | 117 | nav { 118 | position: fixed; 119 | right: 10px; 120 | top: 10px; 121 | background: #FFF; 122 | box-shadow: 0 2px 4px rgba(0,0,0,0.3); 123 | padding: 10px 30px 10px 10px; 124 | border-radius: 3px; 125 | z-index: 1; 126 | } 127 | -------------------------------------------------------------------------------- /code-splitting/app/static/videoencoder.js: -------------------------------------------------------------------------------- 1 | import '/static/mp4.js'; 2 | -------------------------------------------------------------------------------- /code-splitting/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * 5 | * Copyright 2016 Google Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | 'use strict'; 21 | 22 | const express = require('express'); 23 | const fs = require('mz/fs'); 24 | const handlebars = require('handlebars'); 25 | const crypto = require('crypto'); 26 | 27 | // Handlebars `if` only checks for truthy and falsy values, 28 | // so we have to write our own helper to check for equality (or inequality). 29 | handlebars.registerHelper('ifNotEq', function (a, b, opts) { 30 | if (a !== b) { 31 | return opts.fn(this); 32 | } 33 | }); 34 | 35 | handlebars.registerHelper('ifEq', function (a, b, opts) { 36 | if (a === b) { 37 | return opts.fn(this); 38 | } 39 | }); 40 | 41 | const app = express(); 42 | // Matches paths like `/`, `/index.html`, `/about/` or `/about/index.html`. 43 | const toplevelSection = /([^/]*)(\/|\/index.html)$/; 44 | app.get(toplevelSection, (req, res) => { 45 | // Extract the menu item name from the path and attach it to 46 | // the request to have it available for template rendering. 47 | req.item = req.params[0]; 48 | 49 | // If the request has `?partial`, don't render header and footer. 50 | let files; 51 | if ('partial' in req.query) { 52 | files = [fs.readFile(`app/${req.item}/index.html`)]; 53 | } else { 54 | files = [ 55 | fs.readFile('app/header.partial.html'), 56 | fs.readFile(`app/${req.item}/index.html`), 57 | fs.readFile('app/footer.partial.html') 58 | ]; 59 | } 60 | 61 | Promise.all(files) 62 | .then(files => files.map(f => f.toString('utf-8'))) 63 | .then(files => files.map(f => handlebars.compile(f)(req))) 64 | .then(files => { 65 | const content = files.join(''); 66 | // Let's use sha256 as a means to get an ETag 67 | const hash = crypto 68 | .createHash('sha256') 69 | .update(content) 70 | .digest('hex'); 71 | 72 | res.set({ 73 | 'ETag': hash, 74 | 'Cache-Control': 'public, no-cache' 75 | }); 76 | res.send(content); 77 | }) 78 | .catch(error => res.status(500).send(error.toString())); 79 | }); 80 | app.use(express.static('app')); 81 | 82 | // Self-signed certificate generated by `simplehttp2server` 83 | // @see https://github.com/GoogleChrome/simplehttp2server 84 | const options = { 85 | key: fs.readFileSync('key.pem'), 86 | cert: fs.readFileSync('cert.pem') 87 | }; 88 | // It says spdy, but it's actually HTTP/2 :) 89 | require('spdy').createServer(options, app).listen(8081); 90 | -------------------------------------------------------------------------------- /code-splitting/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server-side-rendering", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "babel-core": "^6.25.0", 7 | "express": "^4.14.0", 8 | "handlebars": "^4.0.5", 9 | "http2": "^3.3.5", 10 | "mz": "^2.4.0", 11 | "spdy": "^3.4.0", 12 | "xml2js": "^0.4.17" 13 | }, 14 | "devDependencies": {} 15 | } 16 | -------------------------------------------------------------------------------- /custom-scrollbar/cat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/custom-scrollbar/cat.gif -------------------------------------------------------------------------------- /custom-scrollbar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 36 |
37 |
Lorem
38 |
ipsum
39 |
dolor
40 |
sit
41 |
amet,
42 |
consectetur
43 |
adipiscing
44 |
elit
45 |
46 | 47 | -------------------------------------------------------------------------------- /custom-scrollbar/step-1.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /custom-scrollbar/step-2.html: -------------------------------------------------------------------------------- 1 | 2 | 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /custom-scrollbar/step-3.html: -------------------------------------------------------------------------------- 1 | 2 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /custom-scrollbar/step-4.html: -------------------------------------------------------------------------------- 1 | 2 | 32 |
33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /custom-scrollbar/step-5.html: -------------------------------------------------------------------------------- 1 | 2 | 37 |
38 |
39 | 40 |
41 | 48 | -------------------------------------------------------------------------------- /custom-scrollbar/step-6.html: -------------------------------------------------------------------------------- 1 | 2 | 38 |
39 |
40 | 41 |
42 | 63 | -------------------------------------------------------------------------------- /expand-collapse/images/img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/expand-collapse/images/img.jpg -------------------------------------------------------------------------------- /expand-collapse/index.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Expand + Collapse 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 |
38 |

Item Title

39 |
40 | 41 |
42 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /firebase-firestore-comments/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-es2015-modules-commonjs" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /firebase-firestore-comments/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "supercharged-comments" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /firebase-firestore-comments/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | functions 3 | node_modules 4 | public 5 | firebase-debug.log 6 | -------------------------------------------------------------------------------- /firebase-firestore-comments/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "public", 4 | "rewrites": [ 5 | { 6 | "source": "**", 7 | "function": "supercharged" 8 | } 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /firebase-firestore-comments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supercharged", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "yarn build:client && yarn build:server && yarn build:components && yarn babel:components", 8 | "build:server": "cpx src/server/**/* functions", 9 | "build:client": "cpx src/client/**/* public", 10 | "build:components": "cpx src/components/**/* public/components", 11 | "babel:components": "babel src/components -d functions/components", 12 | "serve": "firebase serve --only functions,hosting" 13 | }, 14 | "dependencies": { 15 | "express": "^4.16.2", 16 | "firebase": "^4.9.0", 17 | "firebase-functions": "^0.8.1" 18 | }, 19 | "devDependencies": { 20 | "babel-cli": "^6.26.0", 21 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 22 | "cpx": "^1.5.0", 23 | "firebase-tools": "~3.17.4" 24 | } 25 | } -------------------------------------------------------------------------------- /firebase-firestore-comments/src/client/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 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 | import { LoginButton } from './components/sc-login.js'; 18 | import { CommentForm } from './components/sc-comment-form.js'; 19 | import { CommentList } from './components/sc-comment-list.js'; 20 | import { Comment } from './components/sc-comment.js'; 21 | 22 | customElements.define('sc-login', LoginButton); 23 | customElements.define('sc-comment-form', CommentForm); 24 | customElements.define('sc-comment-list', CommentList); 25 | customElements.define('sc-comment', Comment); 26 | 27 | const scLogin = document.querySelector('sc-login'); 28 | const scForm = document.querySelector('sc-comment-form'); 29 | 30 | scForm.addEventListener('comment-sent', e => { 31 | const commentsRef = firebase.firestore().collection('comments'); 32 | commentsRef.add({ 33 | text: e.detail.text, 34 | photoUrl: scLogin.user.photoURL, 35 | authorName: scLogin.user.displayName, 36 | timestamp: firebase.firestore.FieldValue.serverTimestamp() 37 | }); 38 | }); 39 | 40 | scLogin.addEventListener('on-auth', e => { 41 | if(e.detail) { 42 | scLogin.classList.add('sc-hidden'); 43 | } else { 44 | scLogin.classList.remove('sc-hidden'); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /firebase-firestore-comments/src/components/sc-comment-form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 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 | import { SCElement } from './sc-element.js'; 18 | 19 | const html = String.raw; 20 | 21 | export class CommentForm extends SCElement { 22 | static template() { 23 | return html` 24 | 25 | 26 | `; 27 | } 28 | static component() { 29 | return html` 30 | 31 | ${this.template()} 32 | 33 | `; 34 | } 35 | connectedCallback() { 36 | this.btnSend = this.querySelector('#btnSend'); 37 | this.textarea = this.querySelector('textarea'); 38 | this.btnSend.addEventListener('click', e => { 39 | const text = this.textarea.value; 40 | const detail = { text }; 41 | const event = new CustomEvent('comment-sent', { detail }); 42 | this.dispatchEvent(event); 43 | this.textarea.value = ''; 44 | }); 45 | } 46 | } -------------------------------------------------------------------------------- /firebase-firestore-comments/src/components/sc-comment-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 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 | import { SCElement } from './sc-element.js'; 18 | import { Comment } from './sc-comment.js'; 19 | 20 | const html = String.raw; 21 | 22 | export class CommentList extends SCElement { 23 | static component(state) { 24 | return html` 25 | 26 | ${state} 27 | 28 | `; 29 | } 30 | 31 | connectedCallback() { 32 | this.commentsRef = firebase.firestore().collection('comments'); 33 | this.commentsRef.orderBy('timestamp').onSnapshot(snap => { 34 | snap.docChanges.forEach(change => { 35 | const elInDOM = this.querySelector(`#_${change.doc.id}`); 36 | switch(change.type) { 37 | case 'added': 38 | if(elInDOM) { return; } 39 | this.addComment(change.doc); 40 | break; 41 | case 'removed': 42 | elInDOM.remove(); 43 | break; 44 | } 45 | }); 46 | }); 47 | } 48 | 49 | addComment(doc) { 50 | const element = document.createElement('sc-comment'); 51 | element.id = `_${doc.id}`; 52 | element.innerHTML = Comment.template(doc.data()); 53 | this.appendChild(element); 54 | } 55 | } -------------------------------------------------------------------------------- /firebase-firestore-comments/src/components/sc-comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 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 | import { SCElement } from './sc-element.js'; 18 | 19 | const html = String.raw; 20 | 21 | export class Comment extends SCElement { 22 | static template(state) { 23 | return html` 24 |
25 | Profile Photo 26 |
${state.authorName}
27 |
28 |
${state.text}
29 | `; 30 | } 31 | static component(state, id) { 32 | return html` 33 | 34 | ${this.template(state)} 35 | 36 | `; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /firebase-firestore-comments/src/components/sc-element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 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 | // are you node? 18 | // yah 19 | // umm... use this HTMLElement class I made 20 | // okay... sure? 21 | 22 | // are you browser? 23 | // yah 24 | // umm... use your HTMLElement class 25 | // okay... I was gonna do that anyways? thx? 26 | 27 | let _HTMLElement; 28 | if(typeof process !== 'undefined') { 29 | _HTMLElement = class HTMLElement { } 30 | } else { 31 | _HTMLElement = HTMLElement; 32 | } 33 | 34 | export class SCElement extends _HTMLElement { } 35 | -------------------------------------------------------------------------------- /firebase-firestore-comments/src/components/sc-login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 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 | const html = String.raw; 19 | 20 | export class LoginButton extends HTMLElement { 21 | static template() { 22 | return html` 23 | 24 | `; 25 | } 26 | connectedCallback() { 27 | this.appendChild(template.content.cloneNode(true)); 28 | this.auth = firebase.auth(); 29 | this.btnLogin = this.querySelector('#btnLogin'); 30 | 31 | this.auth.onAuthStateChanged(user => { 32 | const event = new CustomEvent('on-auth', { detail: user }); 33 | this.user = user; 34 | this.dispatchEvent(event); 35 | }); 36 | 37 | this.btnLogin.addEventListener('click', e => { 38 | const google = new firebase.auth.GoogleAuthProvider(); 39 | this.auth.signInWithRedirect(google); 40 | }); 41 | } 42 | } 43 | 44 | let template = document.createElement('template'); 45 | template.innerHTML = LoginButton.template(); 46 | 47 | -------------------------------------------------------------------------------- /firebase-firestore-comments/src/server/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Supercharged Comments 20 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 | 105 |
106 | 107 | 108 | 109 |

Comments

110 | 111 | 112 | 113 | 114 | 115 |
116 | 117 |
118 | 119 | 120 | -------------------------------------------------------------------------------- /firebase-firestore-comments/src/server/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 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 | const functions = require('firebase-functions'); 18 | const express = require('express'); 19 | const firebase = require('firebase/app'); 20 | const fs = require('fs'); 21 | require('firebase/firestore'); 22 | 23 | const router = express.Router(); 24 | const indexHtml = fs.readFileSync(`${__dirname}/index.html`, 'utf8'); 25 | const firebaseApp = firebase.initializeApp({ 26 | apiKey: "AIzaSyAs7CmNcvBSO-h14AgE8O_Ii9durQHvx0c", 27 | authDomain: "supercharged-comments.firebaseapp.com", 28 | databaseURL: "https://supercharged-comments.firebaseio.com", 29 | projectId: "supercharged-comments", 30 | storageBucket: "supercharged-comments.appspot.com", 31 | messagingSenderId: "116806762306" 32 | }); 33 | 34 | const { CommentForm } = require('./components/sc-comment-form'); 35 | const { CommentList } = require('./components/sc-comment-list'); 36 | const { Comment } = require('./components/sc-comment'); 37 | 38 | function pageBuilder(page) { 39 | return { 40 | page, 41 | replace(holder, replacement) { 42 | this.page = this.page.replace(holder, replacement); 43 | }, 44 | addCommentForm() { 45 | this.replace('', CommentForm.component()); 46 | return this; 47 | }, 48 | addCommentList(state) { 49 | const comments = state.map(c => Comment.component(c, c.id)).join(''); 50 | this.replace('', CommentList.component(comments)); 51 | return this; 52 | }, 53 | build() { 54 | return this.page; 55 | } 56 | } 57 | } 58 | 59 | router.get('/', (req, res) => { 60 | const commentsRef = firebase.firestore().collection('comments'); 61 | commentsRef.orderBy('timestamp').get().then(snap => { 62 | const state = snap.docs.map(d => Object.assign(d.data(), { id: d.id })); 63 | const page = pageBuilder(indexHtml) 64 | .addCommentForm() 65 | .addCommentList(state) 66 | .build(); 67 | res.send(page); 68 | }); 69 | }); 70 | 71 | exports.supercharged = functions.https.onRequest(router); 72 | -------------------------------------------------------------------------------- /flip-switch/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | Polymer Summit 19 | 20 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /image-zoomer/gibraltar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/image-zoomer/gibraltar.jpg -------------------------------------------------------------------------------- /image-zoomer/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Image Zoomer 22 | 23 | 24 | 25 | 26 | 27 |
28 |

Image Zoomer

29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /image-zoomer/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | padding: 0; 24 | margin: 0; 25 | background-color: #FAFAFA; 26 | width: 100%; 27 | height: 100%; 28 | overflow: hidden; 29 | font-family: 'Roboto', Arial, sans-serif; 30 | } 31 | 32 | body { 33 | display: flex; 34 | flex-direction: column; 35 | } 36 | 37 | .header { 38 | width: 100%; 39 | height: 56px; 40 | color: #FFF; 41 | background: #607D8B; 42 | font-size: 20px; 43 | box-shadow: 0 4px 5px 0 rgba(0,0,0,.14), 44 | 0 2px 9px 1px rgba(0,0,0,.12), 45 | 0 4px 2px -2px rgba(0,0,0,.2); 46 | padding: 16px 16px 0; 47 | position: relative; 48 | z-index: 1; 49 | } 50 | 51 | .header__title { 52 | font-weight: 400; 53 | font-size: 20px; 54 | margin: 0; 55 | } 56 | 57 | .main { 58 | flex: 1; 59 | padding: 16px; 60 | overflow-x: hidden; 61 | overflow-y: scroll; 62 | position: relative; 63 | width: 100%; 64 | display: flex; 65 | justify-content: space-around; 66 | } 67 | 68 | .main img { 69 | width: 100%; 70 | max-width: 650px; 71 | cursor: zoom-in; 72 | } 73 | 74 | .zoomer { 75 | width: 1px; 76 | height: 1px; 77 | position: fixed; 78 | left: 0; 79 | top: 0; 80 | pointer-events: none; 81 | } 82 | 83 | .zoomer__canvas { 84 | position: absolute; 85 | left: -64px; 86 | top: -134px; 87 | } 88 | -------------------------------------------------------------------------------- /infinite-scroller/images/avatar0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/avatar0.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/avatar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/avatar1.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/avatar2.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/avatar3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/avatar3.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image0.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image1.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image10.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image11.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image12.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image13.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image14.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image15.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image16.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image17.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image18.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image19.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image2.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image20.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image21.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image22.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image23.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image24.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image25.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image26.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image27.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image28.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image29.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image29.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image3.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image30.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image30.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image31.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image31.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image32.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image33.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image33.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image34.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image34.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image35.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image35.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image36.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image36.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image37.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image37.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image38.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image38.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image39.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image39.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image4.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image40.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image40.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image41.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image42.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image42.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image43.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image43.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image44.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image44.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image45.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image45.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image46.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image46.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image47.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image48.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image48.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image49.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image5.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image50.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image50.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image51.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image51.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image52.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image52.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image53.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image53.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image54.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image54.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image55.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image55.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image56.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image56.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image57.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image58.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image58.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image59.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image59.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image6.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image60.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image61.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image61.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image62.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image62.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image63.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image63.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image64.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image64.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image65.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image65.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image66.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image66.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image67.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image67.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image68.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image68.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image69.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image69.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image7.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image70.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image70.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image71.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image71.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image72.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image72.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image73.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image73.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image74.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image74.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image75.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image75.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image76.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image76.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image8.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/image9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/image9.jpg -------------------------------------------------------------------------------- /infinite-scroller/images/unknown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/infinite-scroller/images/unknown.jpg -------------------------------------------------------------------------------- /infinite-scroller/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function h(a){c.appendChild(a.dom);return a}function k(a){for(var d=0;de+1E3&&(r.update(1E3*a/(c-e),100),e=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){g=this.end()},domElement:c,setMode:k}}; 4 | Stats.Panel=function(h,k,l){var c=Infinity,g=0,e=Math.round,a=e(window.devicePixelRatio||1),r=80*a,f=48*a,t=3*a,u=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=f;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,f);b.fillStyle=k;b.fillText(h,t,u);b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(f, 5 | v){c=Math.min(c,f);g=Math.max(g,f);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=k;b.fillText(e(f)+" "+h+" ("+e(c)+"-"+e(g)+")",t,u);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,e((1-f/v)*p))}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /infinite-scroller/styles/messages.css: -------------------------------------------------------------------------------- 1 | #chat-timeline { 2 | margin: 0; 3 | padding: 0; 4 | overflow-x: hidden; 5 | overflow-y: scroll; 6 | -webkit-overflow-scrolling: touch; 7 | width: 100%; 8 | height: 100%; 9 | position: absolute; 10 | box-sizing: border-box; 11 | contain: layout; 12 | will-change: transform; 13 | } 14 | 15 | .chat-item { 16 | display: flex; 17 | padding: 10px 0; 18 | width: 100%; 19 | contain: layout; 20 | will-change: transform; 21 | } 22 | 23 | .avatar { 24 | border-radius: 500px; 25 | margin-left: 20px; 26 | margin-right: 6px; 27 | min-width: 48px; 28 | } 29 | 30 | .chat-item p { 31 | margin: 0; 32 | word-wrap: break-word; 33 | font-size: 13px; 34 | } 35 | 36 | .chat-item.tombstone p { 37 | width: 100%; 38 | height: 0.5em; 39 | background-color: #ccc; 40 | margin: 0.5em 0; 41 | } 42 | 43 | .chat-item .bubble img { 44 | max-width: 100%; 45 | height: auto; 46 | } 47 | 48 | .bubble { 49 | padding: 7px 10px; 50 | color: #333; 51 | background: #fff; 52 | box-shadow: 0 3px 2px rgba(0,0,0,0.1); 53 | position: relative; 54 | max-width: 420px; 55 | min-width: 80px; 56 | margin: 0 5px; 57 | } 58 | 59 | .bubble::before { 60 | content: ''; 61 | border-style: solid; 62 | border-width: 0 10px 10px 0; 63 | border-color: transparent #fff transparent transparent; 64 | position: absolute; 65 | top: 0; 66 | left: -10px; 67 | } 68 | 69 | /*.bubble img { 70 | width: 80%; 71 | }*/ 72 | 73 | .meta { 74 | font-size: 0.8rem; 75 | color: #999; 76 | margin-top: 3px; 77 | } 78 | 79 | .from-me { 80 | justify-content: flex-end; 81 | } 82 | 83 | .from-me .avatar { 84 | order: 1; 85 | margin-left: 6px; 86 | margin-right: 20px; 87 | } 88 | 89 | .from-me .bubble { 90 | background: #F9D7FF; 91 | } 92 | 93 | .from-me .bubble::before { 94 | left: 100%; 95 | border-width: 10px 10px 0 0; 96 | border-color: #F9D7FF transparent transparent transparent; 97 | } 98 | .state { 99 | display: none; 100 | } 101 | .invisible { 102 | display: none; 103 | } 104 | -------------------------------------------------------------------------------- /lazy-image/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2017 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 | const Express = require('express'); 18 | const fs = require('fs'); 19 | const {promisify} = require('util'); 20 | const readFile = promisify(fs.readFile); 21 | const im = require('gm').subClass({imageMagick: true}); 22 | const app = Express(); 23 | 24 | const thumbSize = 16; 25 | 26 | app.get('/', async (req, res) => { 27 | let filepath = req.url; 28 | if(filepath.endsWith('/')) 29 | filepath += 'index.html'; 30 | const buffer = await readFile('./static/'+filepath); 31 | const content = buffer.toString(); 32 | const newContent = await Promise.all( 33 | content 34 | .split(/(]+><\/sc-img>)/) 35 | .map(async item => { 36 | if(!item.startsWith('', `style="padding-top: ${height/width*100}%; background-image: url(${thumbURL});">`); 46 | }) 47 | ); 48 | res.send(newContent.join('')); 49 | }); 50 | app.use(Express.static('static')); 51 | app.listen(8080); 52 | -------------------------------------------------------------------------------- /lazy-image/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polysum", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Surma ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "express": "^4.15.4", 14 | "gm": "^1.23.0", 15 | "nodemon": "^1.11.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lazy-image/static/images/a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/lazy-image/static/images/a.jpg -------------------------------------------------------------------------------- /lazy-image/static/images/b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/lazy-image/static/images/b.jpg -------------------------------------------------------------------------------- /lazy-image/static/images/c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/lazy-image/static/images/c.jpg -------------------------------------------------------------------------------- /lazy-image/static/images/d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/lazy-image/static/images/d.jpg -------------------------------------------------------------------------------- /lazy-image/static/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /lazy-image/static/sc-img.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2017 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 | const template = document.createElement('template'); 18 | template.innerHTML = ` 19 | 40 | `; 41 | 42 | const io = new IntersectionObserver(entries => { 43 | for(const entry of entries) { 44 | if(entry.isIntersecting) { 45 | entry.target.setAttribute('full', ''); 46 | } 47 | } 48 | }); 49 | 50 | class SCImg extends HTMLElement { 51 | static get observedAttributes() { 52 | return ['full']; 53 | } 54 | 55 | constructor() { 56 | super(); 57 | this.attachShadow({mode: 'open'}); 58 | this.shadowRoot.appendChild(template.content.cloneNode(true)); 59 | } 60 | 61 | connectedCallback() { 62 | io.observe(this); 63 | } 64 | 65 | disconnectedCallback() { 66 | io.unobserve(this); 67 | } 68 | 69 | get full() { 70 | return this.hasAttribute('full'); 71 | } 72 | 73 | get src() { 74 | return this.getAttribute('src'); 75 | } 76 | 77 | attributeChangedCallback() { 78 | if(this.loaded) 79 | return; 80 | const img = document.createElement('img'); 81 | img.src = this.src; 82 | img.onload = _ => { 83 | this.loaded = true; 84 | this.shadowRoot.appendChild(img); 85 | }; 86 | } 87 | } 88 | 89 | customElements.define('sc-img', SCImg); 90 | -------------------------------------------------------------------------------- /lazy-image/static/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2017 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 | body { 18 | padding: 50px; 19 | max-width: 720px; 20 | margin: 0 auto; 21 | } 22 | 23 | .spacer { 24 | height: 120vh; 25 | } 26 | 27 | img { 28 | display: block; 29 | width: 100%; 30 | } 31 | -------------------------------------------------------------------------------- /parallax/images/screengrab.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/parallax/images/screengrab.jpg -------------------------------------------------------------------------------- /parallax/images/sea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/parallax/images/sea.jpg -------------------------------------------------------------------------------- /parallax/images/stars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/parallax/images/stars.jpg -------------------------------------------------------------------------------- /parallax/images/sunset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/parallax/images/sunset.jpg -------------------------------------------------------------------------------- /router-advanced/README.md: -------------------------------------------------------------------------------- 1 | # Router (Advanced) 2 | 3 | This router build on the previous SPA-like one, but does not require a server-side component, 4 | and works if the user does not have JavaScript. 5 | 6 | It also loads content over XHR rather than baking it into the page. 7 | -------------------------------------------------------------------------------- /router-advanced/about/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 |
About
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /router-advanced/contact/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | Contact 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /router-advanced/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/router-advanced/favicon.ico -------------------------------------------------------------------------------- /router-advanced/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 |
Home
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /router-advanced/misc/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | Misc 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /router-advanced/static/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class App { 21 | constructor () { 22 | const router = document.querySelector('sc-router'); 23 | const links = Array.from(document.querySelectorAll('a')); 24 | 25 | function onClick (evt) { 26 | evt.preventDefault(); 27 | router.go(evt.target.href); 28 | } 29 | 30 | links.forEach(link => { 31 | link.addEventListener('click', onClick); 32 | }); 33 | } 34 | } 35 | 36 | (_ => new App())(); 37 | -------------------------------------------------------------------------------- /router-advanced/static/images/spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/router-advanced/static/images/spinner.png -------------------------------------------------------------------------------- /router-advanced/static/sc-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class SCView extends HTMLElement { 21 | 22 | createdCallback () { 23 | this._view = null; 24 | this._isRemote = (this.getAttribute('remote') !== null); 25 | } 26 | 27 | get route () { 28 | return this.getAttribute('route') || null; 29 | } 30 | 31 | _hideSpinner () { 32 | this.classList.remove('pending'); 33 | } 34 | 35 | _showSpinner () { 36 | this.classList.add('pending'); 37 | } 38 | 39 | _loadView (data) { 40 | // Wait for half a second then show the spinner. 41 | const spinnerTimeout = setTimeout(_ => this._showSpinner(), 500); 42 | 43 | this._view = new DocumentFragment(); 44 | const xhr = new XMLHttpRequest(); 45 | 46 | xhr.onload = evt => { 47 | const newDoc = evt.target.response; 48 | const newView = newDoc.querySelector('sc-view.visible'); 49 | 50 | // Copy in the child nodes from the parent. 51 | while(newView.firstChild) { 52 | this._view.appendChild(newView.firstChild); 53 | } 54 | 55 | // Add the fragment to the page. 56 | this.appendChild(this._view); 57 | 58 | // Clear the timeout and remove the spinner if needed. 59 | clearTimeout(spinnerTimeout); 60 | this._hideSpinner(); 61 | }; 62 | xhr.responseType = 'document'; 63 | xhr.open('GET', `${data[0]}`); 64 | xhr.send(); 65 | } 66 | 67 | in (data) { 68 | if (this._isRemote && !this._view) { 69 | this._loadView(data); 70 | } 71 | 72 | return new Promise((resolve, reject) => { 73 | const onTransitionEnd = () => { 74 | this.removeEventListener('transitionend', onTransitionEnd); 75 | resolve(); 76 | }; 77 | 78 | this.classList.add('visible'); 79 | this.addEventListener('transitionend', onTransitionEnd); 80 | }); 81 | } 82 | 83 | out () { 84 | return new Promise((resolve, reject) => { 85 | const onTransitionEnd = () => { 86 | this.removeEventListener('transitionend', onTransitionEnd); 87 | resolve(); 88 | }; 89 | 90 | this.classList.remove('visible'); 91 | this.addEventListener('transitionend', onTransitionEnd); 92 | }); 93 | } 94 | 95 | update () { 96 | return Promise.resolve(); 97 | } 98 | } 99 | 100 | document.registerElement('sc-view', SCView); 101 | -------------------------------------------------------------------------------- /router-advanced/static/superstyles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | padding: 0; 24 | margin: 0; 25 | font-size: 24px; 26 | font-family: Arial, sans-serif; 27 | } 28 | 29 | sc-view { 30 | contain: strict; 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | width: 100%; 35 | height: 100%; 36 | opacity: 0; 37 | pointer-events: none; 38 | transform: scale(0.95); 39 | transition: transform 0.3s cubic-bezier(0,0,0.3,1), 40 | opacity 0.3s cubic-bezier(0,0,0.3,1); 41 | font-size: 20vh; 42 | display: flex; 43 | flex-direction: row; 44 | justify-content: center; 45 | align-items: center; 46 | color: white; 47 | } 48 | 49 | sc-view.visible { 50 | opacity: 1; 51 | pointer-events: auto; 52 | transform: none; 53 | } 54 | 55 | sc-view[remote]::before { 56 | opacity: 0; 57 | pointer-events: none; 58 | content: ''; 59 | position: fixed; 60 | top: 0; 61 | left: 0; 62 | right: 0; 63 | bottom: 0; 64 | background: inherit; 65 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 66 | } 67 | 68 | sc-view[remote]::after { 69 | opacity: 0; 70 | pointer-events: none; 71 | content: ''; 72 | position: fixed; 73 | left: 50%; 74 | top: 50%; 75 | width: 40px; 76 | height: 40px; 77 | background: url(images/spinner.png) center center no-repeat; 78 | background-size: 40px 40px; 79 | animation-name: spin; 80 | animation-duration: 1s; 81 | animation-timing-function: linear; 82 | animation-iteration-count: infinite; 83 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 84 | } 85 | 86 | sc-view[remote].pending::before, 87 | sc-view[remote].pending::after { 88 | opacity: 1; 89 | } 90 | 91 | @keyframes spin { 92 | from { 93 | transform: translate(-50%, -50%) rotate(0deg); 94 | } 95 | 96 | to { 97 | transform: translate(-50%, -50%) rotate(360deg); 98 | } 99 | } 100 | 101 | .view-home { 102 | background: rgb(128,00,64); 103 | } 104 | 105 | .view-contact { 106 | background: rgb(00,128,64); 107 | } 108 | 109 | .view-about { 110 | background: rgb(00,64,128); 111 | } 112 | 113 | .view-misc { 114 | background: rgb(128,64,0); 115 | } 116 | 117 | nav { 118 | position: fixed; 119 | right: 10px; 120 | top: 10px; 121 | background: #FFF; 122 | box-shadow: 0 2px 4px rgba(0,0,0,0.3); 123 | padding: 10px 30px 10px 10px; 124 | border-radius: 3px; 125 | z-index: 1; 126 | } 127 | -------------------------------------------------------------------------------- /router/README.md: -------------------------------------------------------------------------------- 1 | # Router 2 | 3 | The router requires a server-side component because of the deeplinking. In order to get that to work, you should clone the repo and do the following: 4 | 5 | 1. Clone the project. 6 | 1. Install the [Google App Engine SDK For Python](https://cloud.google.com/appengine/downloads#Google_App_Engine_SDK_for_Python). 7 | 1. Run the SDK app to create symlinks. 8 | 1. Add it to the GAE Launcher you downloaded (Existing app, (Ctrl|Cmd)+Shift+N, /path/to/router). 9 | 1. Enjoy your local version of the router sample, probably at [localhost:8080](http://localhost:8080). 10 | -------------------------------------------------------------------------------- /router/app.yaml: -------------------------------------------------------------------------------- 1 | application: router 2 | version: 1 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: yes 6 | 7 | handlers: 8 | - url: /favicon\.ico 9 | static_files: favicon.ico 10 | upload: favicon\.ico 11 | 12 | - url: /static/(.*) 13 | static_files: static/\1 14 | upload: static/(.*) 15 | application_readable: true 16 | 17 | - url: .* 18 | script: main.app 19 | 20 | libraries: 21 | - name: webapp2 22 | version: "2.5.2" 23 | - name: jinja2 24 | version: latest 25 | -------------------------------------------------------------------------------- /router/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/router/favicon.ico -------------------------------------------------------------------------------- /router/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 32 | 33 | 34 | Home 35 | 36 | 37 | 38 | About 39 | 40 | 41 | 42 | Contact 43 | 44 | 45 | 46 | Misc 47 | 48 | 49 | 50 | 51 | 52 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /router/index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | # AUTOGENERATED 4 | 5 | # This index.yaml is automatically updated whenever the dev_appserver 6 | # detects that a new type of query is run. If you want to manage the 7 | # index.yaml file manually, remove the above marker line (the line 8 | # saying "# AUTOGENERATED"). If you want to manage some indexes 9 | # manually, move them above the marker line. The index.yaml file is 10 | # automatically uploaded to the admin console when you next deploy 11 | # your application using appcfg.py. 12 | 13 | -------------------------------------------------------------------------------- /router/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2007 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 | import webapp2 18 | import jinja2 19 | import os 20 | 21 | JINJA_ENVIRONMENT = jinja2.Environment( 22 | loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), 23 | extensions=['jinja2.ext.autoescape'], 24 | autoescape=True) 25 | 26 | class MainHandler(webapp2.RequestHandler): 27 | def get(self, req): 28 | template = JINJA_ENVIRONMENT.get_template('index.html') 29 | self.response.write(template.render()) 30 | 31 | app = webapp2.WSGIApplication([ 32 | ('/(.*)', MainHandler) 33 | ], debug=True) 34 | -------------------------------------------------------------------------------- /router/static/sc-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class SCView extends HTMLElement { 21 | get route () { 22 | return this.getAttribute('route') || null; 23 | } 24 | 25 | in (data) { 26 | return new Promise((resolve, reject) => { 27 | const onTransitionEnd = () => { 28 | this.removeEventListener('transitionend', onTransitionEnd); 29 | resolve(); 30 | }; 31 | 32 | this.classList.add('visible'); 33 | this.addEventListener('transitionend', onTransitionEnd); 34 | }); 35 | } 36 | 37 | out (data) { 38 | return new Promise((resolve, reject) => { 39 | const onTransitionEnd = () => { 40 | this.removeEventListener('transitionend', onTransitionEnd); 41 | resolve(); 42 | }; 43 | 44 | this.classList.remove('visible'); 45 | this.addEventListener('transitionend', onTransitionEnd); 46 | }); 47 | } 48 | 49 | update (data) { 50 | console.log(data); 51 | return Promise.resolve(); 52 | } 53 | } 54 | 55 | document.registerElement('sc-view', SCView); 56 | -------------------------------------------------------------------------------- /router/static/superstyles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | padding: 0; 24 | margin: 0; 25 | font-size: 24px; 26 | font-family: Arial, sans-serif; 27 | } 28 | 29 | sc-view { 30 | contain: strict; 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | width: 100%; 35 | height: 100%; 36 | opacity: 0; 37 | pointer-events: none; 38 | transform: scale(0.95); 39 | transition: transform 0.3s cubic-bezier(0,0,0.3,1), 40 | opacity 0.3s cubic-bezier(0,0,0.3,1); 41 | font-size: 20vh; 42 | display: flex; 43 | flex-direction: row; 44 | justify-content: center; 45 | align-items: center; 46 | color: white; 47 | } 48 | 49 | sc-view.visible { 50 | opacity: 1; 51 | pointer-events: auto; 52 | transform: none; 53 | } 54 | 55 | .view-home { 56 | background: rgb(128,00,64); 57 | } 58 | 59 | .view-contact { 60 | background: rgb(00,128,64); 61 | } 62 | 63 | .view-about { 64 | background: rgb(00,64,128); 65 | } 66 | 67 | .view-misc { 68 | background: rgb(128,64,0); 69 | } 70 | 71 | nav { 72 | position: fixed; 73 | right: 10px; 74 | top: 10px; 75 | background: #FFF; 76 | box-shadow: 0 2px 4px rgba(0,0,0,0.3); 77 | padding: 10px 30px 10px 10px; 78 | border-radius: 3px; 79 | z-index: 1; 80 | } 81 | -------------------------------------------------------------------------------- /server-side-rendering/README.md: -------------------------------------------------------------------------------- 1 | # Server-side Rendering 2 | 3 | This is a small backend for the `router-advances` using Node. Make sure you have node5 for HTTP/2 support! 4 | 5 | Run `npm install` to install all dependencies and run the server with `node index.js`. 6 | -------------------------------------------------------------------------------- /server-side-rendering/app/about/index.html: -------------------------------------------------------------------------------- 1 | About 2 | -------------------------------------------------------------------------------- /server-side-rendering/app/contact/index.html: -------------------------------------------------------------------------------- 1 | Contact 2 | -------------------------------------------------------------------------------- /server-side-rendering/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/server-side-rendering/app/favicon.ico -------------------------------------------------------------------------------- /server-side-rendering/app/footer.partial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server-side-rendering/app/header.partial.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | {{#ifNotEq item ""}} 38 | 39 | {{/ifNotEq}} 40 | {{#ifNotEq item "about"}} 41 | 42 | {{/ifNotEq}} 43 | {{#ifNotEq item "contact"}} 44 | 45 | {{/ifNotEq}} 46 | {{#ifNotEq item "misc"}} 47 | 48 | {{/ifNotEq}} 49 | -------------------------------------------------------------------------------- /server-side-rendering/app/index.html: -------------------------------------------------------------------------------- 1 | Home 2 | -------------------------------------------------------------------------------- /server-side-rendering/app/misc/index.html: -------------------------------------------------------------------------------- 1 | Misc 2 | -------------------------------------------------------------------------------- /server-side-rendering/app/static/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class App { 21 | constructor () { 22 | const router = document.querySelector('sc-router'); 23 | const links = Array.from(document.querySelectorAll('a')); 24 | 25 | function onClick (evt) { 26 | evt.preventDefault(); 27 | router.go(evt.target.href); 28 | } 29 | 30 | links.forEach(link => { 31 | link.addEventListener('click', onClick); 32 | }); 33 | } 34 | } 35 | 36 | (_ => new App())(); 37 | -------------------------------------------------------------------------------- /server-side-rendering/app/static/images/spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/server-side-rendering/app/static/images/spinner.png -------------------------------------------------------------------------------- /server-side-rendering/app/static/sc-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class SCView extends HTMLElement { 21 | 22 | createdCallback () { 23 | this._view = null; 24 | this._isRemote = (this.getAttribute('remote') !== null); 25 | } 26 | 27 | get route () { 28 | return this.getAttribute('route') || null; 29 | } 30 | 31 | _hideSpinner () { 32 | this.classList.remove('pending'); 33 | } 34 | 35 | _showSpinner () { 36 | this.classList.add('pending'); 37 | } 38 | 39 | _loadView (data) { 40 | // Wait for half a second then show the spinner. 41 | const spinnerTimeout = setTimeout(_ => this._showSpinner(), 500); 42 | 43 | this._view = new DocumentFragment(); 44 | const xhr = new XMLHttpRequest(); 45 | 46 | xhr.onload = evt => { 47 | const newDoc = evt.target.response; 48 | const newView = newDoc.querySelector('sc-view.visible'); 49 | 50 | // Copy in the child nodes from the parent. 51 | newView.childNodes.forEach(node => { 52 | this._view.appendChild(node); 53 | }); 54 | 55 | // Add the fragment to the page. 56 | this.appendChild(this._view); 57 | 58 | // Clear the timeout and remove the spinner if needed. 59 | clearTimeout(spinnerTimeout); 60 | this._hideSpinner(); 61 | }; 62 | xhr.responseType = 'document'; 63 | xhr.open('GET', `${data[0]}?partial`); 64 | xhr.send(); 65 | } 66 | 67 | in (data) { 68 | if (this._isRemote && !this._view) { 69 | this._loadView(data); 70 | } 71 | 72 | return new Promise((resolve, reject) => { 73 | const onTransitionEnd = () => { 74 | this.removeEventListener('transitionend', onTransitionEnd); 75 | resolve(); 76 | }; 77 | 78 | this.classList.add('visible'); 79 | this.addEventListener('transitionend', onTransitionEnd); 80 | }); 81 | } 82 | 83 | out () { 84 | return new Promise((resolve, reject) => { 85 | const onTransitionEnd = () => { 86 | this.removeEventListener('transitionend', onTransitionEnd); 87 | resolve(); 88 | }; 89 | 90 | this.classList.remove('visible'); 91 | this.addEventListener('transitionend', onTransitionEnd); 92 | }); 93 | } 94 | 95 | update () { 96 | return Promise.resolve(); 97 | } 98 | } 99 | 100 | document.registerElement('sc-view', SCView); 101 | -------------------------------------------------------------------------------- /server-side-rendering/app/static/superstyles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | padding: 0; 24 | margin: 0; 25 | font-size: 24px; 26 | font-family: Arial, sans-serif; 27 | } 28 | 29 | sc-view { 30 | contain: strict; 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | width: 100%; 35 | height: 100%; 36 | opacity: 0; 37 | pointer-events: none; 38 | transform: scale(0.95); 39 | transition: transform 0.3s cubic-bezier(0,0,0.3,1), 40 | opacity 0.3s cubic-bezier(0,0,0.3,1); 41 | font-size: 20vh; 42 | display: flex; 43 | flex-direction: row; 44 | justify-content: center; 45 | align-items: center; 46 | color: white; 47 | } 48 | 49 | sc-view.visible { 50 | opacity: 1; 51 | pointer-events: auto; 52 | transform: none; 53 | } 54 | 55 | sc-view[remote]::before { 56 | opacity: 0; 57 | pointer-events: none; 58 | content: ''; 59 | position: fixed; 60 | top: 0; 61 | left: 0; 62 | right: 0; 63 | bottom: 0; 64 | background: inherit; 65 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 66 | } 67 | 68 | sc-view[remote]::after { 69 | opacity: 0; 70 | pointer-events: none; 71 | content: ''; 72 | position: fixed; 73 | left: 50%; 74 | top: 50%; 75 | width: 40px; 76 | height: 40px; 77 | background: url(images/spinner.png) center center no-repeat; 78 | background-size: 40px 40px; 79 | animation-name: spin; 80 | animation-duration: 1s; 81 | animation-timing-function: linear; 82 | animation-iteration-count: infinite; 83 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 84 | } 85 | 86 | sc-view[remote].pending::before, 87 | sc-view[remote].pending::after { 88 | opacity: 1; 89 | } 90 | 91 | @keyframes spin { 92 | from { 93 | transform: translate(-50%, -50%) rotate(0deg); 94 | } 95 | 96 | to { 97 | transform: translate(-50%, -50%) rotate(360deg); 98 | } 99 | } 100 | 101 | .view-home { 102 | background: rgb(128,00,64); 103 | } 104 | 105 | .view-contact { 106 | background: rgb(00,128,64); 107 | } 108 | 109 | .view-about { 110 | background: rgb(00,64,128); 111 | } 112 | 113 | .view-misc { 114 | background: rgb(128,64,0); 115 | } 116 | 117 | nav { 118 | position: fixed; 119 | right: 10px; 120 | top: 10px; 121 | background: #FFF; 122 | box-shadow: 0 2px 4px rgba(0,0,0,0.3); 123 | padding: 10px 30px 10px 10px; 124 | border-radius: 3px; 125 | z-index: 1; 126 | } 127 | -------------------------------------------------------------------------------- /server-side-rendering/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC6DCCAdCgAwIBAgIRANPICMcz6V25HmlVLSMGZc8wDQYJKoZIhvcNAQELBQAw 3 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjA5MTUxMzA3NDVaFw0xNzA5MTUxMzA3 4 | NDVaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 5 | ggEKAoIBAQC+m69KqEYeVECwOUgDqB1uER5UMgUTRBdYKN6LmWtqsaYCVttk6jBu 6 | 2sHpaXO4n1nbIh+vFpzIPlW8/yZEzjOhXbJfTjcjJ+OQIXFuLGoaYmZH+Oowh6Cw 7 | ToKxDiOGythEpBXkAMI9T/O3LuAz2PCvsRU90DX9mJ9uaBvpqaOe4Z7JpEXTxV2P 8 | uB2btMpUX74Jze99ldta7n+lxPDk7Btjtx8UC7VqAx0Sv6etXBMfhkIHKQgVAj1i 9 | C8ue05/eqS+VfxVcuZFQ2cb69d67tmjnruLlImjq1nI5RntytlakRb9mvUxti35d 10 | JFdRSYIjX1YpXs6EEY0BGXddnr1QZrm9AgMBAAGjOTA3MA4GA1UdDwEB/wQEAwIC 11 | pDAPBgNVHRMBAf8EBTADAQH/MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG 12 | 9w0BAQsFAAOCAQEABv2xV9MaI7ziuEkD7SxaR7m5jta67gRQA8HO/t2v+nq/hbRb 13 | FktF7v4kh7jCTRogn2bBaGUp8wpoHNCaEfoeCqFzeTOGXESS7/PzBiY6dozOHM35 14 | piuzHXbwcxo3y9EUi/RxEgL4JP4YafNsQ6hcIbYTPqW+c7oD2VZPrLWsF0YvU9TR 15 | 0EikiSsbVpXtbPRsQmKxRR+1oI2AbWpRLfJ367FvUH7y00KoikQkMYesk+ImdkHZ 16 | U2Q3ujGq7HyK38SWmWZWSfkOucpYHOiljRd+Nj4Nm3yJSnfZdy7+Npy/GrJsnl78 17 | xW/q8QyED0WEsoDhTzTSLG90df7V6pVz0i3F0A== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /server-side-rendering/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * 5 | * Copyright 2016 Google Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | 'use strict'; 21 | 22 | const express = require('express'); 23 | const fs = require('mz/fs'); 24 | const handlebars = require('handlebars'); 25 | const crypto = require('crypto'); 26 | 27 | // Handlebars `if` only checks for truthy and falsy values, 28 | // so we have to write our own helper to check for equality (or inequality). 29 | handlebars.registerHelper('ifNotEq', function (a, b, opts) { 30 | if (a !== b) { 31 | return opts.fn(this); 32 | } 33 | }); 34 | 35 | const app = express(); 36 | // Matches paths like `/`, `/index.html`, `/about/` or `/about/index.html`. 37 | const toplevelSection = /([^/]*)(\/|\/index.html)$/; 38 | app.get(toplevelSection, (req, res) => { 39 | // Extract the menu item name from the path and attach it to 40 | // the request to have it available for template rendering. 41 | req.item = req.params[0]; 42 | 43 | // If the request has `?partial`, don't render header and footer. 44 | let files; 45 | if ('partial' in req.query) { 46 | files = [fs.readFile(`app/${req.item}/index.html`)]; 47 | } else { 48 | files = [ 49 | fs.readFile('app/header.partial.html'), 50 | fs.readFile(`app/${req.item}/index.html`), 51 | fs.readFile('app/footer.partial.html') 52 | ]; 53 | } 54 | 55 | Promise.all(files) 56 | .then(files => files.map(f => f.toString('utf-8'))) 57 | .then(files => files.map(f => handlebars.compile(f)(req))) 58 | .then(files => { 59 | const content = files.join(''); 60 | // Let's use sha256 as a means to get an ETag 61 | const hash = crypto 62 | .createHash('sha256') 63 | .update(content) 64 | .digest('hex'); 65 | 66 | res.set({ 67 | 'ETag': hash, 68 | 'Cache-Control': 'public, no-cache' 69 | }); 70 | res.send(content); 71 | }) 72 | .catch(error => res.status(500).send(error.toString())); 73 | }); 74 | app.use(express.static('app')); 75 | 76 | // Self-signed certificate generated by `simplehttp2server` 77 | // @see https://github.com/GoogleChrome/simplehttp2server 78 | const options = { 79 | key: fs.readFileSync('key.pem'), 80 | cert: fs.readFileSync('cert.pem') 81 | }; 82 | // It says spdy, but it's actually HTTP/2 :) 83 | require('spdy').createServer(options, app).listen(8081); 84 | -------------------------------------------------------------------------------- /server-side-rendering/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEAvpuvSqhGHlRAsDlIA6gdbhEeVDIFE0QXWCjei5lrarGmAlbb 3 | ZOowbtrB6WlzuJ9Z2yIfrxacyD5VvP8mRM4zoV2yX043IyfjkCFxbixqGmJmR/jq 4 | MIegsE6CsQ4jhsrYRKQV5ADCPU/zty7gM9jwr7EVPdA1/Zifbmgb6amjnuGeyaRF 5 | 08Vdj7gdm7TKVF++Cc3vfZXbWu5/pcTw5OwbY7cfFAu1agMdEr+nrVwTH4ZCBykI 6 | FQI9YgvLntOf3qkvlX8VXLmRUNnG+vXeu7Zo567i5SJo6tZyOUZ7crZWpEW/Zr1M 7 | bYt+XSRXUUmCI19WKV7OhBGNARl3XZ69UGa5vQIDAQABAoIBAQCsDu8aLnI1OIq4 8 | SMzX+C6wx6UgDZMFRCbqfuH9E/2h70DSxcMAAmK7/p6ia315f+bl55TAQWI/Y/2T 9 | QKMz4ws6M9ErNPiStJQ36+hvsooIzSBVAb2tFxEXdZeF6iRprbuxoojcK08rd3uh 10 | tR/PzZnejrSE+ulxxQ7N5A6mS2qWpLbjkIKAVqu4iyRASdUNblZ5bjxv/eZ5n1U/ 11 | oXV6pyG3MxSItOsC9/x19IihkUWi2hRTTdOsx+sLgQNvJ6OULlHmnfOUuyWMqqZ7 12 | sZ6uCsgIICGndRCsTguX4cNeGmn/pi8yqmZyZ4zQ4PSkV7jmlNG/BrYHCeUHQB+L 13 | tzBRwwMBAoGBAOQ+DvTFA18FunD0+pSMes/iDPFd1QHcqf4v5/BUjYdRCG+U5jc5 14 | nWY/qhvUZKUBND0Ihr95FxVjQIQiXwCDerKVqATofjfXZd6prWM7JqY0pjBuIJvB 15 | vVxgA5O9eLVevCbpG9mHW4eKVoMhugQG5D0OXOzbI5Lb9FxPx+k1hOH9AoGBANXJ 16 | 8mfayTHXwxriM6RTUIVZO9U5vMemQLy27h7gWCeyt1vPv+ArM0F3xODOLc5vd7FB 17 | SFyq+rsjwxnS0wHAaB+e0V98OtrgB/LmkQ3n1MJll24xyvGCKOkqQjP4C//d2Yjx 18 | ffovYBd3h2C39MOM6znoQkWULRUI0VEPSjvUw+LBAoGBAKWW7IzinnaDFme7JE0/ 19 | uh42F0PJ2q8WI/K5WOHAxkllHeSuN3PbhflXuReluTsJK5gYJoKl3Hx03KrAsQIT 20 | YaJM93BQKLpkuJCZs6SplnnA+s1qKJg4MCTjt9SpAvk6+PCV8NGZ5Wrpj6hlgKpJ 21 | Qa+WSw7AUgfLMncCnrvwSy8VAoGBAK9U9AzgjejmvwgpQ5kdCwiR6lQxCfXjD2y1 22 | ygxgiWvlUiNl+kLqqxqiE0EbVs3a9RrWI3Z8cy1PYw6mrI7fMYXdRnE8/TfMTDiV 23 | h5kT7JWRo+OnynzO9qZjFfBxGcY6N9Hr3Bl3CSO1z70uoPpPdAsFxHQz1dVOafxE 24 | wejX0d8BAoGBALvbmjzPJVp4XZYw9VFpc2+7jxuc8WdYJGS+uYxtziJkoG31Aik3 25 | cjwTHtRRnaHPfGEqj0pTgvbm22OoQ5wlvV8vxUIVkO33C5Nljnfy0pSYoIodq8+4 26 | WNbp0PzZ77USIhe5XuEhEJX+NRpycNjCOI21psuPS1trjxqIkUw7r6JO 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /server-side-rendering/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server-side-rendering", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "express": "^4.14.0", 7 | "handlebars": "^4.0.5", 8 | "spdy": "^3.4.0", 9 | "http2": "^3.3.5", 10 | "mz": "^2.4.0" 11 | }, 12 | "devDependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /service-worker/README.md: -------------------------------------------------------------------------------- 1 | # Service-Worker 2 | 3 | This is a service-worker for our server-side-rendering session. 4 | 5 | Run `npm install` to install all dependencies and run the server with `node index.js`. 6 | Start Chrome Canary using `npm run chrome`, since HTTP/2 and ServiceWorker development 7 | on localhost doesn’t play nicely currently. 8 | -------------------------------------------------------------------------------- /service-worker/app/about/index.html: -------------------------------------------------------------------------------- 1 | About 2 | -------------------------------------------------------------------------------- /service-worker/app/contact/index.html: -------------------------------------------------------------------------------- 1 | Contact 2 | -------------------------------------------------------------------------------- /service-worker/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/service-worker/app/favicon.ico -------------------------------------------------------------------------------- /service-worker/app/footer.partial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /service-worker/app/header.partial.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Router 21 | 22 | 23 | 24 | 32 | 33 | {{? it.item !== ""}} 34 | 35 | {{?}} 36 | {{? it.item !== "about"}} 37 | 38 | {{?}} 39 | {{? it.item !== "contact"}} 40 | 41 | {{?}} 42 | {{? it.item !== "misc"}} 43 | 44 | {{?}} 45 | -------------------------------------------------------------------------------- /service-worker/app/index.html: -------------------------------------------------------------------------------- 1 | Home 2 | -------------------------------------------------------------------------------- /service-worker/app/misc/index.html: -------------------------------------------------------------------------------- 1 | Misc 2 | -------------------------------------------------------------------------------- /service-worker/app/static/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class App { 21 | constructor () { 22 | const router = document.querySelector('sc-router'); 23 | const links = Array.from(document.querySelectorAll('a')); 24 | 25 | function onClick (evt) { 26 | evt.preventDefault(); 27 | router.go(evt.target.href); 28 | } 29 | 30 | links.forEach(link => { 31 | link.addEventListener('click', onClick); 32 | }); 33 | } 34 | } 35 | 36 | (_ => new App())(); 37 | 38 | if ('serviceWorker' in navigator) { 39 | navigator.serviceWorker.register('/sw.js', {scope: '/'}); 40 | } 41 | 42 | window.addEventListener('offline', event => { 43 | document.body.classList.add('offline'); 44 | Array.from(document.querySelectorAll('nav a')) 45 | .forEach(link => { 46 | if(linkIsAvailableOffline(link)) 47 | link.classList.add('cached'); 48 | }); 49 | }); 50 | 51 | window.addEventListener('online', _ => { 52 | document.body.classList.remove('offline'); 53 | }); 54 | -------------------------------------------------------------------------------- /service-worker/app/static/images/spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/service-worker/app/static/images/spinner.png -------------------------------------------------------------------------------- /service-worker/app/static/sc-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 'use strict'; 19 | 20 | class SCView extends HTMLElement { 21 | 22 | createdCallback () { 23 | this._view = null; 24 | this._isRemote = (this.getAttribute('remote') !== null); 25 | } 26 | 27 | get route () { 28 | return this.getAttribute('route') || null; 29 | } 30 | 31 | _hideSpinner () { 32 | this.classList.remove('pending'); 33 | } 34 | 35 | _showSpinner () { 36 | this.classList.add('pending'); 37 | } 38 | 39 | _loadView (data) { 40 | // Wait for half a second then show the spinner. 41 | const spinnerTimeout = setTimeout(_ => this._showSpinner(), 500); 42 | 43 | this._view = new DocumentFragment(); 44 | const xhr = new XMLHttpRequest(); 45 | 46 | xhr.onload = evt => { 47 | const newDoc = evt.target.response; 48 | const newView = newDoc.querySelector('sc-view.visible'); 49 | 50 | // Copy in the child nodes from the parent. 51 | newView.childNodes.forEach(node => { 52 | this._view.appendChild(node); 53 | }); 54 | 55 | // Add the fragment to the page. 56 | this.appendChild(this._view); 57 | 58 | // Clear the timeout and remove the spinner if needed. 59 | clearTimeout(spinnerTimeout); 60 | this._hideSpinner(); 61 | }; 62 | xhr.responseType = 'document'; 63 | xhr.open('GET', `${data[0]}?partial=`); 64 | xhr.send(); 65 | } 66 | 67 | in (data) { 68 | if (this._isRemote && !this._view) { 69 | this._loadView(data); 70 | } 71 | 72 | return new Promise((resolve, reject) => { 73 | const onTransitionEnd = () => { 74 | this.removeEventListener('transitionend', onTransitionEnd); 75 | resolve(); 76 | }; 77 | 78 | this.classList.add('visible'); 79 | this.addEventListener('transitionend', onTransitionEnd); 80 | }); 81 | } 82 | 83 | out () { 84 | return new Promise((resolve, reject) => { 85 | const onTransitionEnd = () => { 86 | this.removeEventListener('transitionend', onTransitionEnd); 87 | resolve(); 88 | }; 89 | 90 | this.classList.remove('visible'); 91 | this.addEventListener('transitionend', onTransitionEnd); 92 | }); 93 | } 94 | 95 | update () { 96 | return Promise.resolve(); 97 | } 98 | } 99 | 100 | document.registerElement('sc-view', SCView); 101 | -------------------------------------------------------------------------------- /service-worker/app/static/superstyles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | padding: 0; 24 | margin: 0; 25 | font-size: 24px; 26 | font-family: Arial, sans-serif; 27 | } 28 | 29 | sc-view { 30 | contain: strict; 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | width: 100%; 35 | height: 100%; 36 | opacity: 0; 37 | pointer-events: none; 38 | transform: scale(0.95); 39 | transition: transform 0.3s cubic-bezier(0,0,0.3,1), 40 | opacity 0.3s cubic-bezier(0,0,0.3,1); 41 | font-size: 20vh; 42 | display: flex; 43 | flex-direction: row; 44 | justify-content: center; 45 | align-items: center; 46 | color: white; 47 | } 48 | 49 | sc-view.visible { 50 | opacity: 1; 51 | pointer-events: auto; 52 | transform: none; 53 | } 54 | 55 | sc-view[remote]::before { 56 | opacity: 0; 57 | pointer-events: none; 58 | content: ''; 59 | position: fixed; 60 | top: 0; 61 | left: 0; 62 | right: 0; 63 | bottom: 0; 64 | background: inherit; 65 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 66 | } 67 | 68 | sc-view[remote]::after { 69 | opacity: 0; 70 | pointer-events: none; 71 | content: ''; 72 | position: fixed; 73 | left: 50%; 74 | top: 50%; 75 | width: 40px; 76 | height: 40px; 77 | background: url(images/spinner.png) center center no-repeat; 78 | background-size: 40px 40px; 79 | animation-name: spin; 80 | animation-duration: 1s; 81 | animation-timing-function: linear; 82 | animation-iteration-count: infinite; 83 | transition: opacity 0.3s cubic-bezier(0,0,0.3,1); 84 | } 85 | 86 | sc-view[remote].pending::before, 87 | sc-view[remote].pending::after { 88 | opacity: 1; 89 | } 90 | 91 | @keyframes spin { 92 | from { 93 | transform: translate(-50%, -50%) rotate(0deg); 94 | } 95 | 96 | to { 97 | transform: translate(-50%, -50%) rotate(360deg); 98 | } 99 | } 100 | 101 | .view-home { 102 | background: rgb(128,00,64); 103 | } 104 | 105 | .view-contact { 106 | background: rgb(00,128,64); 107 | } 108 | 109 | .view-about { 110 | background: rgb(00,64,128); 111 | } 112 | 113 | .view-misc { 114 | background: rgb(128,64,0); 115 | } 116 | 117 | nav { 118 | position: fixed; 119 | right: 10px; 120 | top: 10px; 121 | background: #FFF; 122 | box-shadow: 0 2px 4px rgba(0,0,0,0.3); 123 | padding: 10px 30px 10px 10px; 124 | border-radius: 3px; 125 | z-index: 1; 126 | } 127 | 128 | body.offline nav a { 129 | opacity: 0.3; 130 | pointer-events: none; 131 | } 132 | 133 | body.offline nav a.cached { 134 | opacity: 1; 135 | pointer-events: initial; 136 | } 137 | -------------------------------------------------------------------------------- /service-worker/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC6DCCAdCgAwIBAgIRANPICMcz6V25HmlVLSMGZc8wDQYJKoZIhvcNAQELBQAw 3 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjA5MTUxMzA3NDVaFw0xNzA5MTUxMzA3 4 | NDVaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 5 | ggEKAoIBAQC+m69KqEYeVECwOUgDqB1uER5UMgUTRBdYKN6LmWtqsaYCVttk6jBu 6 | 2sHpaXO4n1nbIh+vFpzIPlW8/yZEzjOhXbJfTjcjJ+OQIXFuLGoaYmZH+Oowh6Cw 7 | ToKxDiOGythEpBXkAMI9T/O3LuAz2PCvsRU90DX9mJ9uaBvpqaOe4Z7JpEXTxV2P 8 | uB2btMpUX74Jze99ldta7n+lxPDk7Btjtx8UC7VqAx0Sv6etXBMfhkIHKQgVAj1i 9 | C8ue05/eqS+VfxVcuZFQ2cb69d67tmjnruLlImjq1nI5RntytlakRb9mvUxti35d 10 | JFdRSYIjX1YpXs6EEY0BGXddnr1QZrm9AgMBAAGjOTA3MA4GA1UdDwEB/wQEAwIC 11 | pDAPBgNVHRMBAf8EBTADAQH/MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG 12 | 9w0BAQsFAAOCAQEABv2xV9MaI7ziuEkD7SxaR7m5jta67gRQA8HO/t2v+nq/hbRb 13 | FktF7v4kh7jCTRogn2bBaGUp8wpoHNCaEfoeCqFzeTOGXESS7/PzBiY6dozOHM35 14 | piuzHXbwcxo3y9EUi/RxEgL4JP4YafNsQ6hcIbYTPqW+c7oD2VZPrLWsF0YvU9TR 15 | 0EikiSsbVpXtbPRsQmKxRR+1oI2AbWpRLfJ367FvUH7y00KoikQkMYesk+ImdkHZ 16 | U2Q3ujGq7HyK38SWmWZWSfkOucpYHOiljRd+Nj4Nm3yJSnfZdy7+Npy/GrJsnl78 17 | xW/q8QyED0WEsoDhTzTSLG90df7V6pVz0i3F0A== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /service-worker/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * 5 | * Copyright 2016 Google Inc. All rights reserved. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | 'use strict'; 21 | 22 | const express = require('express'); 23 | const fs = require('mz/fs'); 24 | const dot = require('dot'); 25 | dot.templateSettings.strip = false; 26 | 27 | const crypto = require('crypto'); 28 | 29 | const app = express(); 30 | app.use('/node_modules', express.static('node_modules')); 31 | // Matches paths like `/`, `/index.html`, `/about/` or `/about/index.html`. 32 | const toplevelSection = /([^/]*)(\/|\/index.html)$/; 33 | app.get(toplevelSection, (req, res) => { 34 | // Extract the menu item name from the path and attach it to 35 | // the request to have it available for template rendering. 36 | req.item = req.params[0]; 37 | 38 | // If the request has `?partial`, don't render header and footer. 39 | let files; 40 | if ('partial' in req.query) { 41 | files = [fs.readFile(`app/${req.item}/index.html`)]; 42 | } else { 43 | files = [ 44 | fs.readFile('app/header.partial.html'), 45 | fs.readFile(`app/${req.item}/index.html`), 46 | fs.readFile('app/footer.partial.html') 47 | ]; 48 | } 49 | 50 | Promise.all(files) 51 | .then(files => files.map(f => f.toString('utf-8'))) 52 | .then(files => files.map(f => dot.template(f)(req))) 53 | .then(files => { 54 | const content = files.join(''); 55 | // Let's use sha256 as a means to get an ETag 56 | const hash = crypto 57 | .createHash('sha256') 58 | .update(content) 59 | .digest('hex'); 60 | 61 | res.set({ 62 | 'ETag': hash, 63 | 'Cache-Control': 'public, no-cache' 64 | }); 65 | res.send(content); 66 | }) 67 | .catch(error => res.status(500).send(error.toString())); 68 | }); 69 | app.use(express.static('app')); 70 | 71 | // Self-signed certificate generated by `simplehttp2server` 72 | // @see https://github.com/GoogleChrome/simplehttp2server 73 | const options = { 74 | key: fs.readFileSync('key.pem'), 75 | cert: fs.readFileSync('cert.pem') 76 | }; 77 | // It says spdy, but it's actually HTTP/2 :) 78 | require('http').createServer(app).listen(8081); 79 | -------------------------------------------------------------------------------- /service-worker/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEAvpuvSqhGHlRAsDlIA6gdbhEeVDIFE0QXWCjei5lrarGmAlbb 3 | ZOowbtrB6WlzuJ9Z2yIfrxacyD5VvP8mRM4zoV2yX043IyfjkCFxbixqGmJmR/jq 4 | MIegsE6CsQ4jhsrYRKQV5ADCPU/zty7gM9jwr7EVPdA1/Zifbmgb6amjnuGeyaRF 5 | 08Vdj7gdm7TKVF++Cc3vfZXbWu5/pcTw5OwbY7cfFAu1agMdEr+nrVwTH4ZCBykI 6 | FQI9YgvLntOf3qkvlX8VXLmRUNnG+vXeu7Zo567i5SJo6tZyOUZ7crZWpEW/Zr1M 7 | bYt+XSRXUUmCI19WKV7OhBGNARl3XZ69UGa5vQIDAQABAoIBAQCsDu8aLnI1OIq4 8 | SMzX+C6wx6UgDZMFRCbqfuH9E/2h70DSxcMAAmK7/p6ia315f+bl55TAQWI/Y/2T 9 | QKMz4ws6M9ErNPiStJQ36+hvsooIzSBVAb2tFxEXdZeF6iRprbuxoojcK08rd3uh 10 | tR/PzZnejrSE+ulxxQ7N5A6mS2qWpLbjkIKAVqu4iyRASdUNblZ5bjxv/eZ5n1U/ 11 | oXV6pyG3MxSItOsC9/x19IihkUWi2hRTTdOsx+sLgQNvJ6OULlHmnfOUuyWMqqZ7 12 | sZ6uCsgIICGndRCsTguX4cNeGmn/pi8yqmZyZ4zQ4PSkV7jmlNG/BrYHCeUHQB+L 13 | tzBRwwMBAoGBAOQ+DvTFA18FunD0+pSMes/iDPFd1QHcqf4v5/BUjYdRCG+U5jc5 14 | nWY/qhvUZKUBND0Ihr95FxVjQIQiXwCDerKVqATofjfXZd6prWM7JqY0pjBuIJvB 15 | vVxgA5O9eLVevCbpG9mHW4eKVoMhugQG5D0OXOzbI5Lb9FxPx+k1hOH9AoGBANXJ 16 | 8mfayTHXwxriM6RTUIVZO9U5vMemQLy27h7gWCeyt1vPv+ArM0F3xODOLc5vd7FB 17 | SFyq+rsjwxnS0wHAaB+e0V98OtrgB/LmkQ3n1MJll24xyvGCKOkqQjP4C//d2Yjx 18 | ffovYBd3h2C39MOM6znoQkWULRUI0VEPSjvUw+LBAoGBAKWW7IzinnaDFme7JE0/ 19 | uh42F0PJ2q8WI/K5WOHAxkllHeSuN3PbhflXuReluTsJK5gYJoKl3Hx03KrAsQIT 20 | YaJM93BQKLpkuJCZs6SplnnA+s1qKJg4MCTjt9SpAvk6+PCV8NGZ5Wrpj6hlgKpJ 21 | Qa+WSw7AUgfLMncCnrvwSy8VAoGBAK9U9AzgjejmvwgpQ5kdCwiR6lQxCfXjD2y1 22 | ygxgiWvlUiNl+kLqqxqiE0EbVs3a9RrWI3Z8cy1PYw6mrI7fMYXdRnE8/TfMTDiV 23 | h5kT7JWRo+OnynzO9qZjFfBxGcY6N9Hr3Bl3CSO1z70uoPpPdAsFxHQz1dVOafxE 24 | wejX0d8BAoGBALvbmjzPJVp4XZYw9VFpc2+7jxuc8WdYJGS+uYxtziJkoG31Aik3 25 | cjwTHtRRnaHPfGEqj0pTgvbm22OoQ5wlvV8vxUIVkO33C5Nljnfy0pSYoIodq8+4 26 | WNbp0PzZ77USIhe5XuEhEJX+NRpycNjCOI21psuPS1trjxqIkUw7r6JO 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /service-worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server-side-rendering", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "dependencies": { 6 | "dot": "^1.0.3", 7 | "express": "^4.14.0", 8 | "http2": "^3.3.5", 9 | "mz": "^2.4.0", 10 | "spdy": "^3.4.0" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "chrome": "rm -rf /tmp/foo && open -a 'Google Chrome Canary' -n --args --user-data-dir=/tmp/foo --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost:8081 https://localhost:8081/" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /side-nav/detabinator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | 19 | /** 20 | * Usage: 21 | * const detabinator = new Detabinator(element); 22 | * detabinator.inert = true; // Sets all focusable children of element to tabindex=-1 23 | * detabinator.inert = false; // Restores all focusable children of element 24 | * Limitations: Doesn't support Shadow DOM v0 :P 25 | */ 26 | 27 | class Detabinator { 28 | constructor(element) { 29 | if (!element) { 30 | throw new Error('Missing required argument. new Detabinator needs an element reference'); 31 | } 32 | this._inert = false; 33 | this._focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex], [contenteditable]'; 34 | this._focusableElements = Array.from( 35 | element.querySelectorAll(this._focusableElementsString) 36 | ); 37 | } 38 | 39 | get inert() { 40 | return this._inert; 41 | } 42 | 43 | set inert(isInert) { 44 | if (this._inert === isInert) { 45 | return; 46 | } 47 | 48 | this._inert = isInert; 49 | 50 | this._focusableElements.forEach((child) => { 51 | if (isInert) { 52 | // If the child has an explict tabindex save it 53 | if (child.hasAttribute('tabindex')) { 54 | child.__savedTabindex = child.tabIndex; 55 | } 56 | // Set ALL focusable children to tabindex -1 57 | child.setAttribute('tabindex', -1); 58 | } else { 59 | // If the child has a saved tabindex, restore it 60 | // Because the value could be 0, explicitly check that it's not false 61 | if (child.__savedTabindex === 0 || child.__savedTabindex) { 62 | return child.setAttribute('tabindex', child.__savedTabindex); 63 | } else { 64 | // Remove tabindex from ANY REMAINING children 65 | child.removeAttribute('tabindex'); 66 | } 67 | } 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /side-nav/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Side Nav 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /stream-progress/README.md: -------------------------------------------------------------------------------- 1 | # Stream progress 2 | 3 | This sample loads a fairly big file via fetch, decodes it using TransformStream and displays a radial progress bar while loading. 4 | 5 | Probably best enjoyed with network throttling enabled in DevTools. 6 | -------------------------------------------------------------------------------- /stream-progress/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /stream-progress/stream.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | import {TransformStream} from './third_party/transformstream.js'; 19 | 20 | class JSONTransformer { 21 | constructor() { 22 | this.chunks = []; 23 | this.depth = 0; 24 | this.inString = false; 25 | this.skipNext = false; 26 | this.decoder = new TextDecoder(); 27 | } 28 | 29 | start() {} 30 | flush() {} 31 | transform(chunk, controller) { 32 | for(let i = 0; i < chunk.length; i++) { 33 | if(this.skipNext) { 34 | this.skipNext = false; 35 | continue; 36 | } 37 | const byte = chunk[i]; 38 | const char = String.fromCharCode(byte); 39 | switch(char) { 40 | case '{': 41 | if(this.inString) continue; 42 | this.depth++; 43 | break; 44 | case '}': 45 | if(this.inString) continue; 46 | this.depth--; 47 | if(this.depth === 0) { 48 | const tail = new Uint8Array(chunk.buffer, chunk.byteOffset, i + 1); 49 | chunk = new Uint8Array(chunk.buffer, chunk.byteOffset + i + 1); 50 | this.chunks.push(tail); 51 | 52 | const jsonStr = this.chunks.reduce((str, chunk) => 53 | str + this.decoder.decode(chunk, {stream: true}), ''); 54 | controller.enqueue(jsonStr); 55 | 56 | this.chunks.length = 0; 57 | i = -1; 58 | } 59 | break; 60 | case '"': 61 | this.inString = !this.inString; 62 | break; 63 | case '\\': 64 | this.skipNext = true; 65 | break; 66 | } 67 | } 68 | this.chunks.push(chunk); 69 | } 70 | } 71 | 72 | const dial = document.querySelector('sc-dial'); 73 | fetch('./tweets.json') 74 | .then(async resp => { 75 | 76 | if (resp.status != 200) { 77 | //Don't try to parse non JSON responses, such as a 404 error... 78 | return; 79 | } 80 | 81 | const bytesTotal = parseInt(resp.headers.get('Content-Length'), 10); 82 | const jsonStream = resp.body.pipeThrough(new TransformStream(new JSONTransformer())); 83 | const reader = jsonStream.getReader(); 84 | 85 | let bytesCounted = 0; 86 | while(true) { 87 | const {value, done} = await reader.read(); 88 | if(done) { 89 | dial.percentage = 1; 90 | return; 91 | } 92 | 93 | bytesCounted += value.length; 94 | dial.percentage = bytesCounted / bytesTotal; 95 | } 96 | }); 97 | -------------------------------------------------------------------------------- /streaming-service-worker/client/sw.js: -------------------------------------------------------------------------------- 1 | const toCache = require('static-to-cache')(); 2 | const version = require('static-version')(); 3 | const revGet = require('static-rev-get'); 4 | 5 | const staticCacheName = `static-${version}`; 6 | 7 | addEventListener('install', event => { 8 | skipWaiting(); 9 | 10 | event.waitUntil(async function () { 11 | const cache = await caches.open(staticCacheName); 12 | await cache.addAll(toCache); 13 | }()); 14 | }); 15 | 16 | addEventListener('activate', event => { 17 | event.waitUntil(async function () { 18 | const keys = await caches.keys(); 19 | await Promise.all( 20 | keys.map(key => { 21 | if (key !== staticCacheName) return caches.delete(key); 22 | }) 23 | ); 24 | }()); 25 | }); 26 | 27 | class IdentityStream { 28 | constructor() { 29 | let readableController; 30 | let writableController; 31 | 32 | this.readable = new ReadableStream({ 33 | start(controller) { 34 | readableController = controller; 35 | }, 36 | cancel(reason) { 37 | writableController.error(reason); 38 | } 39 | }); 40 | 41 | this.writable = new WritableStream({ 42 | start(controller) { 43 | writableController = controller; 44 | }, 45 | write(chunk) { 46 | readableController.enqueue(chunk); 47 | }, 48 | close() { 49 | readableController.close(); 50 | }, 51 | abort(reason) { 52 | readableController.error(reason); 53 | } 54 | }); 55 | } 56 | } 57 | 58 | async function streamArticle(event, url) { 59 | const includeUrl = new URL(url); 60 | includeUrl.pathname += 'include'; 61 | 62 | const parts = [ 63 | caches.match(revGet('/static/shell-start.html')), 64 | fetch(includeUrl).catch(() => caches.match(revGet('/static/offline-inc.html'))), 65 | caches.match(revGet('/static/shell-end.html')) 66 | ]; 67 | 68 | const identity = new IdentityStream(); 69 | 70 | event.waitUntil(async function() { 71 | for (const responsePromise of parts) { 72 | const response = await responsePromise; 73 | await response.body.pipeTo(identity.writable, { preventClose: true }); 74 | } 75 | identity.writable.getWriter().close(); 76 | }()); 77 | 78 | return new Response(identity.readable, { 79 | headers: { 'Content-Type': 'text/html; charset=utf-8' } 80 | }); 81 | } 82 | 83 | addEventListener('fetch', event => { 84 | if (event.request.method !== 'GET') return; 85 | const url = new URL(event.request.url); 86 | 87 | event.respondWith(async function () { 88 | if (url.origin === location.origin && /^\/\d{4}\/[\w-]+\/$/.test(url.pathname)) { 89 | return streamArticle(event, url); 90 | } 91 | 92 | const cachedReponse = await caches.match(event.request); 93 | if (cachedReponse) return cachedReponse; 94 | 95 | return await fetch(event.request); 96 | }()); 97 | }); -------------------------------------------------------------------------------- /streaming-service-worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sw-blog", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "nodemon -e js,njk server/index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "del": "^3.0.0", 13 | "escape-string-regexp": "^1.0.5", 14 | "express": "^4.15.3", 15 | "glob": "^7.1.2", 16 | "highlight.js": "^9.12.0", 17 | "marked": "^0.3.6", 18 | "mkdirp": "^0.5.1", 19 | "nodemon": "^1.11.0", 20 | "nunjucks": "^3.0.1", 21 | "nunjucks-date-filter": "^0.1.1", 22 | "static-module": "^1.4.0", 23 | "through2": "^2.0.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /streaming-service-worker/posts/combining-fonts/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Combining fonts", 3 | "posted": "2017-04-28", 4 | "summary": "I love the font [Just Another Hand](https://fonts.google.com/specimen/Just+Another+Hand), but I don't like the positioning of the hyphen & equals glyphs. Thankfully I can use CSS fonts to fix it!", 5 | "description": "Using @font-face to replace glyphs of one font with another." 6 | } -------------------------------------------------------------------------------- /streaming-service-worker/posts/es-modules-in-browsers/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "ECMAScript modules in browsers", 3 | "posted": "2017-05-02", 4 | "summary": "ES modules are starting to land in browsers! Modules in general are [pretty well documented](https://ponyfoo.com/articles/es6-modules-in-depth), but here are some browser-specific differences…", 5 | "description": "ES modules are landing in browsers! Here are the HTML-specific differences you need to be aware of." 6 | } -------------------------------------------------------------------------------- /streaming-service-worker/posts/h2-push-tougher-than-i-thought/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "HTTP/2 push is tougher than I thought", 3 | "posted": "2017-05-30", 4 | "summary": "\"HTTP/2 push will solve that\" is something I've heard a lot when it comes to page load performance problems, but I didn't know much about it, so I decided to dig in.\n HTTP/2 push is more complicated and low-level than I initially thought, but what really caught me off-guard is how inconsistent it is between browsers – I'd assumed it was a done deal & totally ready for production.", 5 | "description": "There are lots of edge cases I hadn't considered, and it's very inconsistent between browsers. Here's what I found…" 6 | } -------------------------------------------------------------------------------- /streaming-service-worker/server/error404.js: -------------------------------------------------------------------------------- 1 | class Error404 extends Error { 2 | constructor(message) { 3 | super(message); 4 | this.name = 'Error404'; 5 | } 6 | } 7 | 8 | module.exports = Error404; -------------------------------------------------------------------------------- /streaming-service-worker/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const del = require('del'); 3 | const nunjucks = require('nunjucks'); 4 | const app = express(); 5 | 6 | const rev = require('./rev'); 7 | 8 | app.set('view engine', 'njk'); 9 | 10 | const nunjucksEnv = nunjucks.configure(__dirname + '/../templates', { 11 | watch: true, 12 | express: app 13 | }); 14 | 15 | require('nunjucks-date-filter').install(nunjucksEnv); 16 | nunjucksEnv.addFilter('rev', str => rev.get(str)); 17 | 18 | app.use(require('./routes')); 19 | 20 | // Rev static files 21 | (async function() { 22 | const staticRevPath = `${__dirname}/../static-rev`; 23 | await del(staticRevPath); 24 | await rev.copyAndRev(`${__dirname}/../static`, '**', staticRevPath); 25 | await rev.addAndRev('offline-inc.html', staticRevPath, nunjucksEnv.render('offline-inc.njk')); 26 | 27 | const shell = nunjucksEnv.render('shell.njk'); 28 | const splitAt = ''; 29 | 30 | await rev.addAndRev('shell-start.html', staticRevPath, shell.slice(0, shell.indexOf(splitAt))); 31 | await rev.addAndRev('shell-end.html', staticRevPath, shell.slice(shell.indexOf(splitAt) + splitAt.length)); 32 | await rev.replaceInFiles(`${__dirname}/../static-rev/**/*.css`); 33 | 34 | app.listen(3000, () => { 35 | console.log('Example app listening on port 3000!'); 36 | }); 37 | })(); -------------------------------------------------------------------------------- /streaming-service-worker/server/marked.js: -------------------------------------------------------------------------------- 1 | const marked = require('marked'); 2 | const highlight = require('highlight.js'); 3 | 4 | const renderer = new marked.Renderer(); 5 | 6 | marked.setOptions({ 7 | renderer, 8 | highlight(code, lang) { 9 | if (lang) { 10 | return highlight.highlight(lang, code).value; 11 | } 12 | return highlight.highlightAuto(code).value; 13 | } 14 | }); 15 | 16 | renderer.heading = function (text, level, raw) { 17 | return marked.Renderer.prototype.heading.call(this, text, level + 1, raw); 18 | }; 19 | 20 | renderer.code = function (code, lang, escaped) { 21 | if (this.options.highlight) { 22 | var out = this.options.highlight(code, lang); 23 | if (out != null && out !== code) { 24 | escaped = true; 25 | code = out; 26 | } 27 | } 28 | 29 | if (!lang) { 30 | return '
'
31 |       + (escaped ? code : escape(code, true))
32 |       + '\n
'; 33 | } 34 | 35 | return '
'
39 |     + (escaped ? code : escape(code, true))
40 |     + '\n
\n'; 41 | }; 42 | 43 | module.exports = marked; -------------------------------------------------------------------------------- /streaming-service-worker/server/rev.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const util = require('util'); 4 | const crypto = require('crypto'); 5 | 6 | const rename = util.promisify(fs.rename); 7 | const readFile = util.promisify(fs.readFile); 8 | const writeFile = util.promisify(fs.writeFile); 9 | 10 | const glob = util.promisify(require('glob')); 11 | const mkdirp = util.promisify(require('mkdirp')); 12 | const through2 = require('through2'); 13 | const escapeStringRegexp = require('escape-string-regexp'); 14 | 15 | const hashes = new Map(); 16 | 17 | function hashExtension(extension, hash) { 18 | return '.' + hash.digest('hex').slice(0, 10) + extension; 19 | } 20 | 21 | module.exports.copyAndRev = async function copyAndRev(cwd, from, to) { 22 | cwd = path.normalize(cwd); 23 | to = path.normalize(to); 24 | 25 | const paths = await glob(from, { 26 | cwd, 27 | nodir: true 28 | }); 29 | 30 | await Promise.all( 31 | paths.map(async p => { 32 | const parsedPath = path.parse(p); 33 | const parsedOutputPath = { 34 | dir: parsedPath.dir ? `${to}/${parsedPath.dir}` : to, 35 | name: parsedPath.name, 36 | ext: parsedPath.ext 37 | }; 38 | 39 | const hash = crypto.createHash('md5'); 40 | const input = fs.createReadStream(`${cwd}/${p}`); 41 | const initialOutputPath = path.format(parsedOutputPath); 42 | 43 | await mkdirp(parsedOutputPath.dir); 44 | 45 | await new Promise(resolve => { 46 | input.pipe(through2((chunk, enc, callback) => { 47 | hash.update(chunk); 48 | callback(null, chunk); 49 | })).pipe(fs.createWriteStream(initialOutputPath)).on('finish', resolve); 50 | }); 51 | 52 | parsedOutputPath.ext = hashExtension(parsedOutputPath.ext, hash); 53 | 54 | hashes.set(`/static/${p}`, `/static-rev/${parsedPath.dir ? parsedPath.dir + "/" : ""}${parsedOutputPath.name}${parsedOutputPath.ext}`); 55 | await rename(initialOutputPath, path.format(parsedOutputPath)); 56 | }) 57 | ); 58 | } 59 | 60 | module.exports.addAndRev = async function addAndRev(p, destination, content) { 61 | const parsedPath = path.parse(p); 62 | const newExtension = hashExtension(parsedPath.ext, 63 | crypto.createHash('md5').update(content) 64 | ); 65 | 66 | let outputPath = `${parsedPath.name}${newExtension}`; 67 | if (parsedPath.dir) outputPath = `${parsedPath.dir}/` + outputPath; 68 | 69 | hashes.set(`/static/${p}`, `/static-rev/${outputPath}`); 70 | await writeFile(`${destination}/${outputPath}`, content); 71 | }; 72 | 73 | module.exports.replaceInFiles = async function replaceInFiles(inGlob) { 74 | const paths = await glob(inGlob, { nodir: true }); 75 | 76 | await Promise.all( 77 | paths.map(async p => { 78 | const content = await readFile(p, 'utf8'); 79 | await writeFile(p, replace(content)); 80 | }) 81 | ); 82 | }; 83 | 84 | function replace(content) { 85 | const re = new RegExp( 86 | [...hashes.keys()].map(s => escapeStringRegexp(s)).join('|'), 87 | 'g' 88 | ); 89 | 90 | return content.replace(re, match => hashes.get(match)); 91 | }; 92 | 93 | module.exports.replace = replace; 94 | 95 | module.exports.get = function get(key) { 96 | return hashes.get(key); 97 | }; 98 | -------------------------------------------------------------------------------- /streaming-service-worker/static/css/imgs/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/css/imgs/check.png -------------------------------------------------------------------------------- /streaming-service-worker/static/css/imgs/graph-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/css/imgs/graph-tile.png -------------------------------------------------------------------------------- /streaming-service-worker/static/css/imgs/social-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/css/imgs/social-icons.png -------------------------------------------------------------------------------- /streaming-service-worker/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/favicon.ico -------------------------------------------------------------------------------- /streaming-service-worker/static/imgs/browser-icons/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/imgs/browser-icons/chrome.png -------------------------------------------------------------------------------- /streaming-service-worker/static/imgs/browser-icons/edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/imgs/browser-icons/edge.png -------------------------------------------------------------------------------- /streaming-service-worker/static/imgs/browser-icons/firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/imgs/browser-icons/firefox.png -------------------------------------------------------------------------------- /streaming-service-worker/static/imgs/browser-icons/safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/imgs/browser-icons/safari.png -------------------------------------------------------------------------------- /streaming-service-worker/static/imgs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/imgs/icon.png -------------------------------------------------------------------------------- /streaming-service-worker/static/imgs/me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/streaming-service-worker/static/imgs/me.jpg -------------------------------------------------------------------------------- /streaming-service-worker/templates/404.njk: -------------------------------------------------------------------------------- 1 | {% extends 'base-side.njk' %} 2 | 3 | {% block title %}404{% endblock %} 4 | {% block author_action %} is sorry about the...{% endblock %} 5 | 6 | {% block mainContent %} 7 |
8 |

Alarming 404 situation

9 |

10 | Hmm, that page couldn't be found. You should leave a comment 11 | below letting me know how angry you are about this. The more sweary 12 | the better. 13 |

14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /streaming-service-worker/templates/base-side.njk: -------------------------------------------------------------------------------- 1 | {% extends 'base.njk' %} 2 | 3 | {% block content %} 4 |
5 |
6 | {% block mainContent %}{% endblock %} 7 |
8 |
{% include 'who-inc.njk' %}
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /streaming-service-worker/templates/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{% endblock %} - JakeArchibald.com 5 | 6 | 7 | {% block extra_head %}{% endblock %} 8 | 9 | 10 | 16 |
17 | {% block content %}{% endblock %} 18 |
19 | {% block extra_body %}{% endblock %} 20 | 23 | 24 | -------------------------------------------------------------------------------- /streaming-service-worker/templates/index.njk: -------------------------------------------------------------------------------- 1 | {% extends 'base-side.njk' %} 2 | 3 | {% block title %}Blog{% endblock %} 4 | {% block page_class %}blog-index{% endblock %} 5 | {% block author_action %} wrote...{% endblock %} 6 | 7 | {% block mainContent %} 8 | {% for post in posts %} 9 |
10 |
11 |

{{ post.title }}

12 | 15 |
16 |
17 | {{ post.summary|safe }} 18 |

Read on…

19 |
20 |
21 | {% endfor %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /streaming-service-worker/templates/offline-inc.njk: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |

Massive connectivity breakdown

7 |

Try refreshing and waving your phone around a bit.

8 |
-------------------------------------------------------------------------------- /streaming-service-worker/templates/post-inc.njk: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |

{{ meta.title }}

7 | 10 | {{ content|safe }} 11 |
-------------------------------------------------------------------------------- /streaming-service-worker/templates/post.njk: -------------------------------------------------------------------------------- 1 | {% extends 'base-side.njk' %} 2 | 3 | {% block title %}{{ meta.title }}{% endblock %} 4 | {% block author_action %} wrote...{% endblock %} 5 | {% block extra_head %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% endblock %} 13 | 14 | {% block mainContent %} 15 | {% include 'post-inc.njk' %} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /streaming-service-worker/templates/shell.njk: -------------------------------------------------------------------------------- 1 | {% extends 'base-side.njk' %} 2 | {% block title %}Loading{% endblock %} 3 | {% block author_action %} streamed…{% endblock %} 4 | 5 | {% block mainContent %} 6 | 7 | {% endblock %} -------------------------------------------------------------------------------- /streaming-service-worker/templates/who-inc.njk: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Jake Archibald

4 |
5 | 6 |

7 | Hello, I’m Jake and that is my face. I’m a developer advocate for Google Chrome. 8 |

9 | 10 |

Elsewhere

11 | 43 |

Contact

44 |

45 | Feel free to 46 | throw me an email, 47 | unless you're a recruiter, in which case destroy every email-capable 48 | device you own to prevent this possibility. 49 |

50 |
-------------------------------------------------------------------------------- /streaming-service-worker/templates/who.njk: -------------------------------------------------------------------------------- 1 | {% extends 'base.njk' %} 2 | 3 | {% block title %}Who?{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |

Who?

9 | {% include 'who-inc.njk' %} 10 |
11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /swipeable-cards/cards.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2016 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 | html, body { 18 | margin: 0; 19 | padding: 0; 20 | background: #FAFAFA; 21 | font-family: Arial; 22 | font-size: 30px; 23 | color: #333; 24 | } 25 | 26 | body { 27 | overflow-x: hidden; 28 | } 29 | 30 | * { 31 | box-sizing: border-box; 32 | } 33 | 34 | .card-container { 35 | width: 100%; 36 | max-width: 450px; 37 | padding: 16px; 38 | margin: 0 auto; 39 | } 40 | 41 | .card { 42 | background: #FFF; 43 | border-radius: 3px; 44 | box-shadow: 0 3px 4px rgba(0,0,0,0.3); 45 | margin: 20px 0; 46 | height: 120px; 47 | display: flex; 48 | align-items: center; 49 | justify-content: space-around; 50 | cursor: pointer; 51 | } 52 | -------------------------------------------------------------------------------- /swipeable-cards/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Swipeable cards 22 | 23 | 24 | 25 |
26 |
Das Surma
27 |
Aerotwist
28 |
Kinlanimus Maximus
29 |
Addyoooooooooo
30 |
Gaunty McGaunty Gaunt
31 |
Jack Archibungle
32 |
Sam "The Dutts" Dutton
33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /template/element.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2015 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 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | html, body { 23 | margin: 0; 24 | padding: 0; 25 | width: 100%; 26 | height: 100%; 27 | background: #FAFAFA; 28 | font-family: 'Roboto', Arial, sans-serif; 29 | } 30 | 31 | body { 32 | display: flex; 33 | flex-direction: column; 34 | } 35 | 36 | .header { 37 | width: 100%; 38 | height: 56px; 39 | color: #FFF; 40 | background: #607D8B; 41 | font-size: 20px; 42 | box-shadow: 0 4px 5px 0 rgba(0,0,0,.14), 43 | 0 2px 9px 1px rgba(0,0,0,.12), 44 | 0 4px 2px -2px rgba(0,0,0,.2); 45 | padding: 16px 16px 0; 46 | position: relative; 47 | z-index: 1; 48 | } 49 | 50 | .header__title { 51 | font-weight: 400; 52 | font-size: 20px; 53 | margin: 0; 54 | } 55 | 56 | .main { 57 | flex: 1; 58 | overflow-x: hidden; 59 | overflow-y: scroll; 60 | padding: 16px; 61 | position: relative; 62 | z-index: 0; 63 | } 64 | -------------------------------------------------------------------------------- /template/element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright 2015 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 | 'use strict'; 19 | 20 | class Element { 21 | 22 | constructor () { 23 | this.addEventListeners(); 24 | } 25 | 26 | addEventListeners () { 27 | } 28 | 29 | removeEventListeners () { 30 | } 31 | 32 | } 33 | 34 | new Element(); 35 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | UI Element: [Element name] 26 | 27 | 28 | 29 | 30 |
31 |

[Element name]

32 |
33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /web-workers/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | SuperCharged 💫 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 |
26 | 27 | 28 | 29 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /web-workers/main.css: -------------------------------------------------------------------------------- 1 | canvas { 2 | margin-top: 2em; 3 | } 4 | 5 | #input { 6 | height: 0; 7 | overflow: hidden; 8 | width: 0; 9 | } 10 | 11 | #input + label { 12 | background-color: #777; 13 | color: #f3f3f3; 14 | display: inline-block; 15 | font-family: sans-serif; 16 | font-size: 2em; 17 | padding: 5px; 18 | } 19 | 20 | #input:focus + label { 21 | outline: 5px solid teal; 22 | } 23 | -------------------------------------------------------------------------------- /web-workers/worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Google Inc. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | 15 | /** 16 | * Handles incoming messages in the web worker. 17 | * In this case: increases each pixel's red color by 20%. 18 | * @param {!Object} d Incoming data. 19 | */ 20 | addEventListener('message', (d) => { 21 | const imageData = d.data; 22 | const w = imageData.width; 23 | const h = imageData.height; 24 | const data = imageData.data; 25 | 26 | // Iterate pixel rows and columns to change red color of each. 27 | for (let x = 0; x < w; x++) { 28 | for (let y = 0; y < h; y++) { 29 | let index = (x + (y * w)) * 4; 30 | data[index] = data[index] * 1.2; 31 | } 32 | } 33 | 34 | postMessage(imageData, [imageData.data.buffer]) 35 | }); 36 | -------------------------------------------------------------------------------- /webgl-image-processing/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/ui-element-samples/cb21b7cb6b4de707cdd1856becb018d9b28c455b/webgl-image-processing/image.jpg -------------------------------------------------------------------------------- /webgl-image-processing/index.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------