George Town, November 2014
+Vin Lim
@vinlim
#itshackademic@polymer
Agenda
Overview
Components
Join the revolution
Web Components: Overview
What problems are we solving?
@polymer #itshackademic
http://drbl.in/esYL
Building UI tabs
should be easy!
@polymer #itshackademic
@polymer #itshackademic
@polymer #itshackademic
@polymer #itshackademic
@polymer #itshackademic
<paper-tabs>
<paper-tab>KNOWLEDGE</paper-tab>
<paper-tab>HISTORY</paper-tab>
<paper-tab>FOOD</paper-tab>
</paper-tabs>
Les...
What are Web Components?
Custom Elements
define new HTML/DOM elements
<paper-tabs selected=“1”>
<paper-tab>Tab 1</paper-tab>
<paper-tab>Tab 2</paper-tab>
<paper-tab>Tab 3</paper-tab>
</paper-t...
declarative, readable
meaningful HTML
common way to extend → reusable
Custom Elements
define new
HTML
var tabs = document.q...
Templates
native client-side templating
<template>
<div class=“comment”>
<img src=“image.png”>
</div>
<script>...</script>
</template>
use DOM to scaffold DOM → n...
Shadow DOM
DOM/CSS scoping
<video src=“foo.webm” controls></video>
@polymer #itshackademic
<video src=“foo.webm” controls></video>
Actually Shadow DOM
@polymer #itshackademic
<video src=“foo.webm” controls></video>
HTML Imports
loading web components
@polymer #itshackademic
@polymer #itshackademic
Vulcanize
$ vulcanize -o build.html index.html
--csp --strip
npm install -g vulcanize
grunt-vulcanize
gulp-vulcanize
Custom Elements
Create new HTML elements and extend existing ones
Templates
Native templating in the browser
Shadow DOM
Sc...
@polymer #itshackademic
@polymer #itshackademic
@polymer #itshackademic
@polymer #itshackademic
Browser support
Summer 2014
Polyfills Web Components
with platform.js*
* soon to be called webcomponents.js
Adds syntactic “sugar”
with polymer.js
Browser support
Summer 2014 (without Polymer)
Browser support
Summer 2014 (with Polymer)
Sugaring: Custom Elements
vanilla
polymer
<polymer-element name=“paper-tabs”>
…
</polymer-element>
usag
e
<paper-tabs>…</paper-tabs>
// document.cre...
document.registerElement(‘paper-tabs’, {
prototype: Object.create(HTMLElement.prototype)
});
vanilla
polymer
<polymer-elem...
vanilla
polymer
<polymer-element name=“paper-tabs”>
…
</polymer-element>
usag
e
<paper-tabs>…</paper-tabs>
// document.cre...
vanilla
polymer
<polymer-element name=“paper-tabs”>
…
</polymer-element>
usag
e
<paper-tabs>…</paper-tabs>
// document.cre...
vanilla
polymer
<polymer-element name=“super-button” extends=“button”>
…
</polymer-element>
usag
e
<button is=“super-butto...
vanilla
polymer
<polymer-element name=“super-button” extends=“button”>
…
</polymer-element>
usag
e
<button is=“super-butto...
vanilla
polymer
<polymer-element name=“super-button” extends=“button”>
…
</polymer-element>
usag
e
<button is=“super-butto...
vanilla
polymer
<polymer-element name=“super-button” extends=“button”>
…
</polymer-element>
usag
e
<button is=“super-butto...
Sugaring: Templates
vanilla
polymer
<polymer-element name=“user-list” noscript>
<template>
<ul>
<template repeat=“{{user, i in users}}”>
<li>{...
vanilla
polymer
<polymer-element name=“user-list” noscript>
<template>
<ul>
<template repeat=“{{user, i in users}}”>
<li>{...
vanilla
polymer
<polymer-element name=“user-list” noscript>
<template>
<ul>
<template repeat=“{{user, i in users}}”>
<li>{...
Sugaring: Shadow DOM
var shadow = el.createShadowRoot();
shadow.innerHTML = “<style>h2 { color: red; }</style>” +
“<h2>I’m a profile-card</h2>”...
var shadow = el.createShadowRoot();
shadow.innerHTML = “<style>h2 { color: red; }</style>” +
“<h2>I’m a profile-card</h2>”...
var shadow = el.createShadowRoot();
shadow.innerHTML = “<style>h2 { color: red; }</style>” +
“<h2>I’m a profile-card</h2>”...
Components
<ul>
<p>
<h1>
<menu-button>
<page-scaffold>
<animated-pages>
What if we designed HTML
for the mobile web?
<core-icon>
<paper-fab>
<core-drawer-panel>
<core-field>
http://bit.ly/1jkTo5c
core-
elements
Image: http://bit.ly/1mZjnTu
<core-
toolbar>
A basic container for controls
like tabs or buttons
MY APP
@polymer #itshackademic
<link rel=“import”
href=“core-toolbar.html”>
<core-
toolbar>
A basic container for controls
like tabs or buttons
MY APP
@p...
<core-toolbar>
<div>MY APP</div>
</core-toolbar>
<link rel=“import”
href=“core-toolbar.html”>
<core-
toolbar>
A basic cont...
<core-toolbar>
<core-icon-button icon=“menu”>
</core-icon-button>
<div>MY APP</div>
</core-toolbar>
<link rel=“import”
hre...
A simple container with a header
section and a content section
<core-header-
panel>
MY APP
<core-header-panel flex>
<core-...
A simple container with a header
section and a content section
<core-header-
panel>
MY APP
<core-header-panel flex>
<core-...
A simple container with a header
section and a content section
<core-header-
panel>
MY APP
<core-header-panel flex>
<core-...
A simple container with a header
section and a content section
<core-header-
panel>
<core-header-panel flex>
<core-toolbar...
<core-header-panel mode=“scroll" flex>
<core-toolbar>
<core-icon-button icon=“menu">
</core-icon-button>
<div>MY APP</div>...
A responsive container that
combines a left- or right-side
drawer
panel and a main content area.
<core-drawer-
panel>
<cor...
<core-drawer-panel>
<div drawer> Drawer panel... </div>
<div main> Main panel... </div>
</core-drawer-panel>
A responsive ...
<core-drawer-panel>
<div drawer> Drawer panel... </div>
<div main> Main panel... </div>
</core-drawer-panel>
A responsive ...
paper-
elements
<paper-input floatinglabel
label="Type only numbers... (floating)"
validate="^[0-9]*$"
error="Input is not a number!">
</p...
<paper-checkbox></paper-checkbox>
<div class=“card”>
<img src=“science.svg”>
<paper-ripple fit></paper-ripple>
</div>
A reactive ink effect for indicating t...
<div class=“card”>
<paper-shadow z=“5” animated>
</paper-shadow>
</div>
A dynamic shadow for conveying
z-depth and spatial...
Styling
<paper-slider min=“0” max=“100”>
</paper-slider>
allows you to style nodes
internal to an element’s
shadow dom
::shadow
@p...
allows you to style nodes
internal to an element’s
shadow dom
::shadow
paper-slider::shadow #sliderKnobInner {
background-...
html /deep/ paper-ripple {
background-color: #E91E63;
}
styles will pierce all
shadow boundaries
/deep/
@polymer #itshacka...
With ::shadow and /deep/ you
can apply sitewide themes
source: ebidel.github.io/material-playground
polymer-project.org/apps/topeka/
polymer-project.org
We’re not alone
Mozilla Brick
<brick-appbar>
<brick-deck>
<brick-tabbar>
<core-icon>
<x-instagram>
(not shown)
Web Components
can work together
Not just browser makers
<app-router>
github.com/erikringsmuth/app-router
my-site.com/order/:id
<app-router>
<!-- matches an exact path -->
<app-ro...
<page-er>
github.com/addyosmani/page-er
<page-er perpage="5" previous=“<< Previous" next=“Next >>"></page-er>
var pager = ...
<ajax-
form>github.com/garstasio/ajax-form
Full Name
Country City
Join newsletter
<form is="ajax-form" action="my/form/han...
Apps
polymer-project.org
chromestatus.com
polymer-project.org/tools/designer/
github.com/ForceDotComLabs/mobile-ui-elements
github.com/ForceDotComLabs
APIs
APIs (as elements)
I want to add a marker
to a Google map.
@polymer #itshackademic
height: 400px;
}
</style>
<div id="map"></div>
<script src=“http://maps.googleapis.com/maps/api/js?callback=mapReady">
</s...
@polymer #itshackademic
googlewebcomponents.github.io
github.com/GoogleWebComponents
youtube.com/watch?v=eORqFaf_QzM
Join the revolution
Learn
polymer-project.org
goo.gl/Ji3WdW
Build
YO POLYMERnpm install -g generator-polymer
Start with <seed-element>
github.com/PolymerLabs/seed-element
youtube.com/watch?v=2toYLLcoY14
Share!
customelements.io
{
"name": "my-element",
"version": "0.0.0",
"description": "My awesome Custom Element",
"license": "MIT",
"keywords": [
"w...
EXPLORE
<thank-you>
+Vin Lim
@vinlim
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
Polymer Polytechnic George Town 2014
of 132

Polymer Polytechnic George Town 2014

Vin Lim presenting Polymer 0.5 & Web Components as part of Polymer Polytechnic in GDG DevFest George Town 2014
Published on: Mar 4, 2016
Published in: Technology      
Source: www.slideshare.net


Transcripts - Polymer Polytechnic George Town 2014

  • 1. George Town, November 2014
  • 2. +Vin Lim @vinlim #itshackademic@polymer
  • 3. Agenda Overview Components Join the revolution
  • 4. Web Components: Overview
  • 5. What problems are we solving?
  • 6. @polymer #itshackademic
  • 7. http://drbl.in/esYL Building UI tabs should be easy!
  • 8. @polymer #itshackademic
  • 9. @polymer #itshackademic
  • 10. @polymer #itshackademic
  • 11. @polymer #itshackademic
  • 12. @polymer #itshackademic
  • 13. <paper-tabs> <paper-tab>KNOWLEDGE</paper-tab> <paper-tab>HISTORY</paper-tab> <paper-tab>FOOD</paper-tab> </paper-tabs> Less Code. Less confusion. Web Components
  • 14. What are Web Components?
  • 15. Custom Elements define new HTML/DOM elements
  • 16. <paper-tabs selected=“1”> <paper-tab>Tab 1</paper-tab> <paper-tab>Tab 2</paper-tab> <paper-tab>Tab 3</paper-tab> </paper-tabs> declarative, readable meaningful HTML common way to extend → reusable Custom Elements define new HTML @polymer #itshackademic
  • 17. declarative, readable meaningful HTML common way to extend → reusable Custom Elements define new HTML var tabs = document.querySelector('paper-tabs'); tabs.addEventListener('core-activate', function() { console.log(this.selected); }); @polymer #itshackademic
  • 18. Templates native client-side templating
  • 19. <template> <div class=“comment”> <img src=“image.png”> </div> <script>...</script> </template> use DOM to scaffold DOM → no XSS content is inert until cloned/used doc fragment → not part of the page HTML Templates native client-side templates parsed, not rendered @polymer #itshackademic
  • 20. Shadow DOM DOM/CSS scoping
  • 21. <video src=“foo.webm” controls></video> @polymer #itshackademic
  • 22. <video src=“foo.webm” controls></video> Actually Shadow DOM @polymer #itshackademic
  • 23. <video src=“foo.webm” controls></video>
  • 24. HTML Imports loading web components
  • 25. @polymer #itshackademic
  • 26. @polymer #itshackademic
  • 27. Vulcanize $ vulcanize -o build.html index.html --csp --strip npm install -g vulcanize
  • 28. grunt-vulcanize
  • 29. gulp-vulcanize
  • 30. Custom Elements Create new HTML elements and extend existing ones Templates Native templating in the browser Shadow DOM Scoped CSS!!! + encapsulated markup HTML Imports Load custom element definitions and resources
  • 31. @polymer #itshackademic
  • 32. @polymer #itshackademic
  • 33. @polymer #itshackademic
  • 34. @polymer #itshackademic
  • 35. Browser support Summer 2014
  • 36. Polyfills Web Components with platform.js* * soon to be called webcomponents.js
  • 37. Adds syntactic “sugar” with polymer.js
  • 38. Browser support Summer 2014 (without Polymer)
  • 39. Browser support Summer 2014 (with Polymer)
  • 40. Sugaring: Custom Elements
  • 41. vanilla polymer <polymer-element name=“paper-tabs”> … </polymer-element> usag e <paper-tabs>…</paper-tabs> // document.createElement(‘paper-tabs’); document.registerElement(‘paper-tabs’, { prototype: Object.create(HTMLElement.prototype) }); @polymer #itshackademic
  • 42. document.registerElement(‘paper-tabs’, { prototype: Object.create(HTMLElement.prototype) }); vanilla polymer <polymer-element name=“paper-tabs”> … </polymer-element> usag e <paper-tabs>…</paper-tabs> // document.createElement(‘paper-tabs’); @polymer #itshackademic
  • 43. vanilla polymer <polymer-element name=“paper-tabs”> … </polymer-element> usag e <paper-tabs>…</paper-tabs> // document.createElement(‘paper-tabs’); document.registerElement(‘paper-tabs’, { prototype: Object.create(HTMLElement.prototype) }); @polymer #itshackademic
  • 44. vanilla polymer <polymer-element name=“paper-tabs”> … </polymer-element> usag e <paper-tabs>…</paper-tabs> // document.createElement(‘paper-tabs’); document.registerElement(‘paper-tabs’, { prototype: Object.create(HTMLElement.prototype) }); @polymer #itshackademic
  • 45. vanilla polymer <polymer-element name=“super-button” extends=“button”> … </polymer-element> usag e <button is=“super-button”>…</button> // document.createElement(‘button’, ‘super-button’); document.registerElement(‘super-button’, { prototype: Object.create(HTMLButtonElement.prototype), extends: ‘button’ }); @polymer #itshackademic
  • 46. vanilla polymer <polymer-element name=“super-button” extends=“button”> … </polymer-element> usag e <button is=“super-button”>…</button> // document.createElement(‘button’, ‘super-button’); document.registerElement(‘super-button’, { prototype: Object.create(HTMLButtonElement.prototype), extends: ‘button’ }); @polymer #itshackademic
  • 47. vanilla polymer <polymer-element name=“super-button” extends=“button”> … </polymer-element> usag e <button is=“super-button”>…</button> // document.createElement(‘button’, ‘super-button’); document.registerElement(‘super-button’, { prototype: Object.create(HTMLButtonElement.prototype), extends: ‘button’ }); @polymer #itshackademic
  • 48. vanilla polymer <polymer-element name=“super-button” extends=“button”> … </polymer-element> usag e <button is=“super-button”>…</button> // document.createElement(‘button’, ‘super-button’); document.registerElement(‘super-button’, { prototype: Object.create(HTMLButtonElement.prototype), extends: ‘button’ }); @polymer #itshackademic
  • 49. Sugaring: Templates
  • 50. vanilla polymer <polymer-element name=“user-list” noscript> <template> <ul> <template repeat=“{{user, i in users}}”> <li>{{user.name}}</li> </template> </ul> </template> </polymer-element> <template> … </template> @polymer #itshackademic
  • 51. vanilla polymer <polymer-element name=“user-list” noscript> <template> <ul> <template repeat=“{{user, i in users}}”> <li>{{user.name}}</li> </template> </ul> </template> </polymer-element> <template> … </template> @polymer #itshackademic
  • 52. vanilla polymer <polymer-element name=“user-list” noscript> <template> <ul> <template repeat=“{{user, i in users}}”> <li>{{user.name}}</li> </template> </ul> </template> </polymer-element> <template> … </template> @polymer #itshackademic
  • 53. Sugaring: Shadow DOM
  • 54. var shadow = el.createShadowRoot(); shadow.innerHTML = “<style>h2 { color: red; }</style>” + “<h2>I’m a profile-card</h2>”; vanilla <polymer-element name=“profile-card” noscript> <template> <link rel=“stylesheet” href=“styles.css”> <h2>I’m a profile-card</h2> </template> </polymer-element> polymer @polymer #itshackademic
  • 55. var shadow = el.createShadowRoot(); shadow.innerHTML = “<style>h2 { color: red; }</style>” + “<h2>I’m a profile-card</h2>”; vanilla <polymer-element name=“profile-card” noscript> <template> <link rel=“stylesheet” href=“styles.css”> <h2>I’m a profile-card</h2> </template> </polymer-element> polymer @polymer #itshackademic
  • 56. var shadow = el.createShadowRoot(); shadow.innerHTML = “<style>h2 { color: red; }</style>” + “<h2>I’m a profile-card</h2>”; vanilla <polymer-element name=“profile-card” noscript> <template> <link rel=“stylesheet” href=“styles.css”> <h2>I’m a profile-card</h2> </template> </polymer-element> polymer @polymer #itshackademic
  • 57. Components
  • 58. <ul> <p> <h1>
  • 59. <menu-button> <page-scaffold> <animated-pages>
  • 60. What if we designed HTML for the mobile web?
  • 61. <core-icon> <paper-fab> <core-drawer-panel> <core-field>
  • 62. http://bit.ly/1jkTo5c core- elements Image: http://bit.ly/1mZjnTu
  • 63. <core- toolbar> A basic container for controls like tabs or buttons MY APP @polymer #itshackademic
  • 64. <link rel=“import” href=“core-toolbar.html”> <core- toolbar> A basic container for controls like tabs or buttons MY APP @polymer #itshackademic
  • 65. <core-toolbar> <div>MY APP</div> </core-toolbar> <link rel=“import” href=“core-toolbar.html”> <core- toolbar> A basic container for controls like tabs or buttons MY APP @polymer #itshackademic
  • 66. <core-toolbar> <core-icon-button icon=“menu”> </core-icon-button> <div>MY APP</div> </core-toolbar> <link rel=“import” href=“core-toolbar.html”> <core- toolbar> A basic container for controls like tabs or buttons MY APP @polymer #itshackademic
  • 67. A simple container with a header section and a content section <core-header- panel> MY APP <core-header-panel flex> <core-toolbar> <core-icon-button icon=“menu"> </core-icon-button> <div>MY APP</div> </core-toolbar> <div class=“content”>…</div> </core-header-panel>
  • 68. A simple container with a header section and a content section <core-header- panel> MY APP <core-header-panel flex> <core-toolbar> <core-icon-button icon=“menu"> </core-icon-button> <div>MY APP</div> </core-toolbar> <div class=“content”>…</div> </core-header-panel>
  • 69. A simple container with a header section and a content section <core-header- panel> MY APP <core-header-panel flex> <core-toolbar> <core-icon-button icon=“menu"> </core-icon-button> <div>MY APP</div> </core-toolbar> <div class=“content”>…</div> </core-header-panel>
  • 70. A simple container with a header section and a content section <core-header- panel> <core-header-panel flex> <core-toolbar> <core-icon-button icon=“menu"> </core-icon-button> <div>MY APP</div> </core-toolbar> <div class=“content”>…</div> </core-header-panel> MY APP
  • 71. <core-header-panel mode=“scroll" flex> <core-toolbar> <core-icon-button icon=“menu"> </core-icon-button> <div>MY APP</div> </core-toolbar> <div class=“content”>…</div> </core-header-panel> <core-header- panel> Toolbar will scroll with the page
  • 72. A responsive container that combines a left- or right-side drawer panel and a main content area. <core-drawer- panel> <core-drawer-panel> <div drawer> Drawer panel... </div> <div main> Main panel... </div> </core-drawer-panel>
  • 73. <core-drawer-panel> <div drawer> Drawer panel... </div> <div main> Main panel... </div> </core-drawer-panel> A responsive container that combines a left- or right-side drawer panel and a main content area. <core-drawer- panel>
  • 74. <core-drawer-panel> <div drawer> Drawer panel... </div> <div main> Main panel... </div> </core-drawer-panel> A responsive container that combines a left- or right-side drawer panel and a main content area. <core-drawer- panel>
  • 75. paper- elements
  • 76. <paper-input floatinglabel label="Type only numbers... (floating)" validate="^[0-9]*$" error="Input is not a number!"> </paper-input> @polymer #itshackademic
  • 77. <paper-checkbox></paper-checkbox>
  • 78. <div class=“card”> <img src=“science.svg”> <paper-ripple fit></paper-ripple> </div> A reactive ink effect for indicating touch and mouse actions <paper- ripple>
  • 79. <div class=“card”> <paper-shadow z=“5” animated> </paper-shadow> </div> A dynamic shadow for conveying z-depth and spatial relationships <paper- shadow> @polymer #itshackademic
  • 80. Styling
  • 81. <paper-slider min=“0” max=“100”> </paper-slider> allows you to style nodes internal to an element’s shadow dom ::shadow @polymer #itshackademic
  • 82. allows you to style nodes internal to an element’s shadow dom ::shadow paper-slider::shadow #sliderKnobInner { background-color: #f4b400; } <paper-slider min=“0” max=“100”> </paper-slider> @polymer #itshackademic
  • 83. html /deep/ paper-ripple { background-color: #E91E63; } styles will pierce all shadow boundaries /deep/ @polymer #itshackademic
  • 84. With ::shadow and /deep/ you can apply sitewide themes source: ebidel.github.io/material-playground
  • 85. polymer-project.org/apps/topeka/
  • 86. polymer-project.org
  • 87. We’re not alone
  • 88. Mozilla Brick
  • 89. <brick-appbar> <brick-deck> <brick-tabbar>
  • 90. <core-icon> <x-instagram> (not shown)
  • 91. Web Components can work together
  • 92. Not just browser makers
  • 93. <app-router> github.com/erikringsmuth/app-router my-site.com/order/:id <app-router> <!-- matches an exact path --> <app-route path="/home" import="/pages/home-page.html"></app-route> <!-- matches using a path variable --> <app-route path="/order/:id" import=“/pages/order-page.html"></app-route> </app-router>
  • 94. <page-er> github.com/addyosmani/page-er <page-er perpage="5" previous=“<< Previous" next=“Next >>"></page-er> var pager = document.querySelector("page-er"); document.addEventListener("polymer-ready", function() { pager.data = model.items; });
  • 95. <ajax- form>github.com/garstasio/ajax-form Full Name Country City Join newsletter <form is="ajax-form" action="my/form/handler"> <label>Full Name <input type="text" name=“full_name"> </label> … </form> √
  • 96. Apps
  • 97. polymer-project.org
  • 98. chromestatus.com
  • 99. polymer-project.org/tools/designer/
  • 100. github.com/ForceDotComLabs/mobile-ui-elements github.com/ForceDotComLabs
  • 101. APIs
  • 102. APIs (as elements)
  • 103. I want to add a marker to a Google map. @polymer #itshackademic
  • 104. height: 400px; } </style> <div id="map"></div> <script src=“http://maps.googleapis.com/maps/api/js?callback=mapReady"> </script> <script> var marker = null; function getCurrentLocation(callback) { navigator.geolocation.watchPosition(callback); } function addMarker(opts, info) { var marker = new google.maps.Marker(opts); var infoWindow = new google.maps.InfoWindow({content: info}); google.maps.event.addListener(marker, 'click', function() { infoWindow.open(opts.map, marker); }); return marker; } function mapReady() { var container = document.querySelector('#map'); var map = new google.maps.Map(container, { zoom: 14, disableDefaultUI: true }); getCurrentLocation(function(pos) { var current = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude); map.setCenter(current); // Re-position marker or create new one. if (marker) { marker.setPosition(map.getCenter()); } else { marker = addMarker({ position: current, map: map, title: 'Your location' }, '<b>Your location</b>'); } }); So much code for one map marker! @polymer #itshackademic
  • 105. @polymer #itshackademic
  • 106. googlewebcomponents.github.io github.com/GoogleWebComponents
  • 107. youtube.com/watch?v=eORqFaf_QzM
  • 108. Join the revolution
  • 109. Learn
  • 110. polymer-project.org
  • 111. goo.gl/Ji3WdW
  • 112. Build
  • 113. YO POLYMERnpm install -g generator-polymer
  • 114. Start with <seed-element> github.com/PolymerLabs/seed-element
  • 115. youtube.com/watch?v=2toYLLcoY14
  • 116. Share!
  • 117. customelements.io
  • 118. { "name": "my-element", "version": "0.0.0", "description": "My awesome Custom Element", "license": "MIT", "keywords": [ "web-components" ], "ignore": [ "**/.*", "node_modules", "bower_components" ] } bower.json @polymer #itshackademic
  • 119. EXPLORE
  • 120. <thank-you> +Vin Lim @vinlim

Related Documents