diff --git a/public/index.html b/public/index.html index d5e5753..3c1a87a 100644 --- a/public/index.html +++ b/public/index.html @@ -114,6 +114,8 @@
Share your Moments
+ + diff --git a/public/src/js/feed.js b/public/src/js/feed.js index ad036af..c3a5e2f 100644 --- a/public/src/js/feed.js +++ b/public/src/js/feed.js @@ -57,23 +57,24 @@ function clearCards() { } } -function createCard() { +function createCard(data) { var cardWrapper = document.createElement('div'); - cardWrapper.className = 'shared-moment-card mdl-card mdl-shadow--2dp'; + cardWrapper.className = + 'shared-moment-card mdl-cardnep-aus.jpg mdl-shadow--2dp'; var cardTitle = document.createElement('div'); cardTitle.className = 'mdl-card__title'; - cardTitle.style.backgroundImage = 'url("/src/images/sf-boat.jpg")'; + cardTitle.style.backgroundImage = `url(${data.image})`; cardTitle.style.backgroundSize = 'cover'; cardTitle.style.height = '180px'; cardWrapper.appendChild(cardTitle); var cardTitleTextElement = document.createElement('h2'); cardTitleTextElement.className = 'mdl-card__title-text'; - cardTitleTextElement.textContent = 'San Francisco Trip'; + cardTitleTextElement.textContent = data.title; cardTitleTextElement.style.color = 'white'; cardTitle.appendChild(cardTitleTextElement); var cardSupportingText = document.createElement('div'); cardSupportingText.className = 'mdl-card__supporting-text'; - cardSupportingText.textContent = 'In San Francisco'; + cardSupportingText.textContent = data.location; cardSupportingText.style.textAlign = 'center'; // var cardSaveButon = document.createElement('button'); // cardSaveButon.textContent = 'Save'; @@ -84,40 +85,40 @@ function createCard() { sharedMomentsArea.appendChild(cardWrapper); } -const url = 'https://httpbin.org/post'; +function extractArray(data) { + const dataArray = []; + for (let key in data) { + dataArray.push(data[key]); + } + return dataArray; +} + +function updateUI(data) { + extractArray(data).forEach(datum => { + createCard(datum); + }); +} + +const url = 'https://pwagram-e7d99.firebaseio.com/posts.json'; let networkDataReceived = false; -fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/JSON', - }, - body: JSON.stringify({message: 'Some message'}), -}) +fetch(url) .then(function(res) { return res.json(); }) .then(function(data) { networkDataReceived = true; - console.log(`From web ${data}`); + console.log(`From web ${JSON.stringify(data)}`); clearCards(); - createCard(); + updateUI(data); }); -if ('caches' in window) { - caches - .match(url) - .then(res => { - if (res) { - return res.json(); - } - }) - .then(data => { - console.log(`From cache ${data}`); - if (!networkDataReceived) { - clearCards(); - createCard(); - } - }); +if ('indexedDB' in window) { + readAllData('posts').then(data => { + if (!networkDataReceived) { + console.log(`From cache ${JSON.stringify(data)}`); + clearCards(); + updateUI(data); + } + }); } diff --git a/public/src/js/idb.js b/public/src/js/idb.js new file mode 100644 index 0000000..9835513 --- /dev/null +++ b/public/src/js/idb.js @@ -0,0 +1,311 @@ +'use strict'; + +(function() { + function toArray(arr) { + return Array.prototype.slice.call(arr); + } + + function promisifyRequest(request) { + return new Promise(function(resolve, reject) { + request.onsuccess = function() { + resolve(request.result); + }; + + request.onerror = function() { + reject(request.error); + }; + }); + } + + function promisifyRequestCall(obj, method, args) { + var request; + var p = new Promise(function(resolve, reject) { + request = obj[method].apply(obj, args); + promisifyRequest(request).then(resolve, reject); + }); + + p.request = request; + return p; + } + + function promisifyCursorRequestCall(obj, method, args) { + var p = promisifyRequestCall(obj, method, args); + return p.then(function(value) { + if (!value) return; + return new Cursor(value, p.request); + }); + } + + function proxyProperties(ProxyClass, targetProp, properties) { + properties.forEach(function(prop) { + Object.defineProperty(ProxyClass.prototype, prop, { + get: function() { + return this[targetProp][prop]; + }, + set: function(val) { + this[targetProp][prop] = val; + } + }); + }); + } + + function proxyRequestMethods(ProxyClass, targetProp, Constructor, properties) { + properties.forEach(function(prop) { + if (!(prop in Constructor.prototype)) return; + ProxyClass.prototype[prop] = function() { + return promisifyRequestCall(this[targetProp], prop, arguments); + }; + }); + } + + function proxyMethods(ProxyClass, targetProp, Constructor, properties) { + properties.forEach(function(prop) { + if (!(prop in Constructor.prototype)) return; + ProxyClass.prototype[prop] = function() { + return this[targetProp][prop].apply(this[targetProp], arguments); + }; + }); + } + + function proxyCursorRequestMethods(ProxyClass, targetProp, Constructor, properties) { + properties.forEach(function(prop) { + if (!(prop in Constructor.prototype)) return; + ProxyClass.prototype[prop] = function() { + return promisifyCursorRequestCall(this[targetProp], prop, arguments); + }; + }); + } + + function Index(index) { + this._index = index; + } + + proxyProperties(Index, '_index', [ + 'name', + 'keyPath', + 'multiEntry', + 'unique' + ]); + + proxyRequestMethods(Index, '_index', IDBIndex, [ + 'get', + 'getKey', + 'getAll', + 'getAllKeys', + 'count' + ]); + + proxyCursorRequestMethods(Index, '_index', IDBIndex, [ + 'openCursor', + 'openKeyCursor' + ]); + + function Cursor(cursor, request) { + this._cursor = cursor; + this._request = request; + } + + proxyProperties(Cursor, '_cursor', [ + 'direction', + 'key', + 'primaryKey', + 'value' + ]); + + proxyRequestMethods(Cursor, '_cursor', IDBCursor, [ + 'update', + 'delete' + ]); + + // proxy 'next' methods + ['advance', 'continue', 'continuePrimaryKey'].forEach(function(methodName) { + if (!(methodName in IDBCursor.prototype)) return; + Cursor.prototype[methodName] = function() { + var cursor = this; + var args = arguments; + return Promise.resolve().then(function() { + cursor._cursor[methodName].apply(cursor._cursor, args); + return promisifyRequest(cursor._request).then(function(value) { + if (!value) return; + return new Cursor(value, cursor._request); + }); + }); + }; + }); + + function ObjectStore(store) { + this._store = store; + } + + ObjectStore.prototype.createIndex = function() { + return new Index(this._store.createIndex.apply(this._store, arguments)); + }; + + ObjectStore.prototype.index = function() { + return new Index(this._store.index.apply(this._store, arguments)); + }; + + proxyProperties(ObjectStore, '_store', [ + 'name', + 'keyPath', + 'indexNames', + 'autoIncrement' + ]); + + proxyRequestMethods(ObjectStore, '_store', IDBObjectStore, [ + 'put', + 'add', + 'delete', + 'clear', + 'get', + 'getAll', + 'getKey', + 'getAllKeys', + 'count' + ]); + + proxyCursorRequestMethods(ObjectStore, '_store', IDBObjectStore, [ + 'openCursor', + 'openKeyCursor' + ]); + + proxyMethods(ObjectStore, '_store', IDBObjectStore, [ + 'deleteIndex' + ]); + + function Transaction(idbTransaction) { + this._tx = idbTransaction; + this.complete = new Promise(function(resolve, reject) { + idbTransaction.oncomplete = function() { + resolve(); + }; + idbTransaction.onerror = function() { + reject(idbTransaction.error); + }; + idbTransaction.onabort = function() { + reject(idbTransaction.error); + }; + }); + } + + Transaction.prototype.objectStore = function() { + return new ObjectStore(this._tx.objectStore.apply(this._tx, arguments)); + }; + + proxyProperties(Transaction, '_tx', [ + 'objectStoreNames', + 'mode' + ]); + + proxyMethods(Transaction, '_tx', IDBTransaction, [ + 'abort' + ]); + + function UpgradeDB(db, oldVersion, transaction) { + this._db = db; + this.oldVersion = oldVersion; + this.transaction = new Transaction(transaction); + } + + UpgradeDB.prototype.createObjectStore = function() { + return new ObjectStore(this._db.createObjectStore.apply(this._db, arguments)); + }; + + proxyProperties(UpgradeDB, '_db', [ + 'name', + 'version', + 'objectStoreNames' + ]); + + proxyMethods(UpgradeDB, '_db', IDBDatabase, [ + 'deleteObjectStore', + 'close' + ]); + + function DB(db) { + this._db = db; + } + + DB.prototype.transaction = function() { + return new Transaction(this._db.transaction.apply(this._db, arguments)); + }; + + proxyProperties(DB, '_db', [ + 'name', + 'version', + 'objectStoreNames' + ]); + + proxyMethods(DB, '_db', IDBDatabase, [ + 'close' + ]); + + // Add cursor iterators + // TODO: remove this once browsers do the right thing with promises + ['openCursor', 'openKeyCursor'].forEach(function(funcName) { + [ObjectStore, Index].forEach(function(Constructor) { + Constructor.prototype[funcName.replace('open', 'iterate')] = function() { + var args = toArray(arguments); + var callback = args[args.length - 1]; + var nativeObject = this._store || this._index; + var request = nativeObject[funcName].apply(nativeObject, args.slice(0, -1)); + request.onsuccess = function() { + callback(request.result); + }; + }; + }); + }); + + // polyfill getAll + [Index, ObjectStore].forEach(function(Constructor) { + if (Constructor.prototype.getAll) return; + Constructor.prototype.getAll = function(query, count) { + var instance = this; + var items = []; + + return new Promise(function(resolve) { + instance.iterateCursor(query, function(cursor) { + if (!cursor) { + resolve(items); + return; + } + items.push(cursor.value); + + if (count !== undefined && items.length == count) { + resolve(items); + return; + } + cursor.continue(); + }); + }); + }; + }); + + var exp = { + open: function(name, version, upgradeCallback) { + var p = promisifyRequestCall(indexedDB, 'open', [name, version]); + var request = p.request; + + request.onupgradeneeded = function(event) { + if (upgradeCallback) { + upgradeCallback(new UpgradeDB(request.result, event.oldVersion, request.transaction)); + } + }; + + return p.then(function(db) { + return new DB(db); + }); + }, + delete: function(name) { + return promisifyRequestCall(indexedDB, 'deleteDatabase', [name]); + } + }; + + if (typeof module !== 'undefined') { + module.exports = exp; + module.exports.default = module.exports; + } + else { + self.idb = exp; + } +}()); diff --git a/public/src/js/utility.js b/public/src/js/utility.js new file mode 100644 index 0000000..24ae3ef --- /dev/null +++ b/public/src/js/utility.js @@ -0,0 +1,44 @@ +const dbPromise = idb.open('posts-store', 1, db => { + if (!db.objectStoreNames.contains('posts')) { + db.createObjectStore('posts', {keyPath: 'id'}); + } +}); + +function writeData(st, data) { + return dbPromise.then(db => { + const tx = db.transaction(st, 'readwrite'); + const store = tx.objectStore(st); + store.put(data); + return tx.complete; + }); +} + +function readAllData(st) { + return dbPromise.then(db => { + const tx = db.transaction(st, 'readonly'); + const store = tx.objectStore(st); + return store.getAll(); + }); +} + +function clearAllData(st) { + return dbPromise.then(db => { + const tx = db.transaction(st, 'readwrite'); + const store = tx.objectStore(st); + store.clear(); + return tx.complete; + }); +} + +function deleteItemFromData(st, id) { + return dbPromise + .then(db => { + const tx = db.transaction(st, 'readwrite'); + const store = tx.objectStore(st); + store.delete(id); + return tx.complete; + }) + .then(() => { + console.log('Item deleted!'); + }); +} diff --git a/public/sw.js b/public/sw.js index 50c7d2b..dcf735e 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,7 @@ -const CACHE_STATIC_NAME = 'static-v8'; +importScripts('/src/js/idb.js'); +importScripts('/src/js/utility.js'); + +const CACHE_STATIC_NAME = 'static-v11'; const CACHE_DYNAMIC_NAME = 'dynamic-v3'; const STATIC_FILES = [ '/', @@ -8,6 +11,7 @@ const STATIC_FILES = [ '/src/js/feed.js', '/src/js/promise.js', '/src/js/fetch.js', + '/src/js/idb.js', '/src/js/material.min.js', '/src/css/app.css', '/src/css/feed.css', @@ -68,17 +72,23 @@ self.addEventListener('activate', event => { }); self.addEventListener('fetch', event => { - const url = 'https://httpbin.org/get'; + const url = 'https://pwagram-e7d99.firebaseio.com/posts'; if (event.request.url.indexOf(url) > -1) { event.respondWith( - caches.open(CACHE_DYNAMIC_NAME).then(cache => - fetch(event.request).then(res => { - // trimCache(CACHE_DYNAMIC_NAME, 3); - cache.put(event.request, res.clone()); - return res; - }) - ) + fetch(event.request).then(res => { + const clonedRes = res.clone(); + clearAllData('posts') + .then(() => { + return clonedRes.json(); + }) + .then(data => { + for (let key in data) { + writeData('posts', data[key]); + } + }); + return res; + }) ); } else if (isInArray(event.request.url, STATIC_FILES)) { event.respondWith(caches.match(event.request));