& analytics, Push Notifications, ES6, a11y, <meta name=“theme-color”> , HTML Imports, app install banner, Polymer, <template>, Google Sheets API , Google Sign-in, Gulp, Firebase, Google Cloud Messaging , App Engine, YouTube, material design, Sass , Promises, Go , Web Animations API, Vulcanize , Google Web Components, Add to Homescreen, <canvas> I/O web app specs
let masthead = sec.querySelector(‘.masthead’); let start = {transform: ‘translate(0,0)', opacity: 1}; let end = {transform: ‘translate(0,-100px)', opacity: 0}; let opts = {duration: 400, easing: ‘cubic-bezier(.4, 0, .2, 1)’}; let opts_delay = {duration: 400, delay: 200}; return new GroupEffect([ new KeyframeEffect(masthead, [start, end], opts), new KeyframeEffect(main, [{opacity: 1}, {opacity: 0}], opts_delay) ]); } Expandable. Just add another animation to the group if one is needed later. polyfill: github.com/web-animations/web-animations-js
// properties are ready, Shady DOM is stamped. }, attachedCallback() { // element is attached to the DOM. }, detachedCallback() { // element is removed from the DOM. }, onPageTransitionDone() { // page transition animations are complete. }, onSubpageTransitionDone() { // sub nav/tab page transitions are complete. }
app.isTabletSize, app.isDesktopSize Schedule app.scheduleData, app.savedSessions, app.watchedVideos User app.currentUser Device app.isIOS, app.isAndroid Shared state across pages
this.firebaseRef.child(path); // data/<UID>/my_sessions/<SESSION_ID> return this._enqueueIDB(path, value) // 1. Stash update in IDB. .then(() => ref.set(value)) // 2. Update live Firebase DB. .then(() => this._dequeueIDB(path), error => { // 3. Undo #1. // If Firebase returns an error, remove the queued operation from IDB. return this._dequeueOperation(path).then(() => Promise.reject(error)); }); } } Making Firebase work offline put IndexedDB between you and the fire
{ let value = {timestamp: Date.now() + this.clockOffset, in_schedule: inSchedule}; if (this.isAuthed()) { let userId = this.firebaseRef.getAuth().uid; // Online. Use the fresh uid. return this._setFirebaseData(`data/${userId}/my_sessions/${sessionUUID}`, value); } else if (currentUser && currentUser.id) { // Offline. Queue the user’s update, to be replayed when they start up online. return this._queueOperation( `data/google:${currentUser.id}/my_sessions/${sessionUUID}`, value); } return Promise.reject('Not currently authorized with Firebase.'); } }
anything until main schedule is fetched. IOWA.Schedule.getSchedule().then(() => { let replayFromCache = true; IOWA.Schedule.loadUserSchedule(replayFromCache); }); }
task generate different sw.js for dev / stage /prod. ‣ ⌘+⇧+R always reloads page without registering a SW. ‣ about:serviceworker-internals Debugging Service Worker
task generate different sw.js for dev / stage /prod. ‣ ⌘+⇧+R always reloads page without registering a SW. ‣ about:serviceworker-internals ‣ DevTools Resources > Service Workers panel. Debugging Service Worker
window.addEventListener('unhandledrejection', e => { // Keep track of rejected promises. unhandled.push({promise: e, reason: e.reason}); // Wait to log a rejected promise. There's a chance it'll be caught later! if (!timeoutId) { timeoutId = setTimeout(logRejectedPromises, 10000); } }); window.addEventListener('rejectionhandled', e => { // A previously rejected promise was handled. Remove it from the list. unhandled = unhandled.filter(rej => rej.promise !== e.promise); });