diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/pwa/https.py b/pwa/https.py
new file mode 100644
index 0000000..267aa0d
--- /dev/null
+++ b/pwa/https.py
@@ -0,0 +1,14 @@
+# taken from http://www.piware.de/2011/01/creating-an-https-server-in-python/
+# generate server.xml with the following command:
+# openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
+# run as follows:
+# python simple-https-server.py
+# then in your browser, visit:
+# https://localhost:4443
+
+import BaseHTTPServer, SimpleHTTPServer
+import ssl
+
+httpd = BaseHTTPServer.HTTPServer(('localhost', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler)
+httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True)
+httpd.serve_forever()
diff --git a/pwa/index.html b/pwa/index.html
new file mode 100644
index 0000000..40bd7c8
--- /dev/null
+++ b/pwa/index.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ Vue.js • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pwa/js/app.js b/pwa/js/app.js
new file mode 100644
index 0000000..eb06021
--- /dev/null
+++ b/pwa/js/app.js
@@ -0,0 +1,143 @@
+/*global Vue, todoStorage */
+
+(function (exports) {
+
+ 'use strict';
+
+ var filters = {
+ all: function (todos) {
+ return todos;
+ },
+ active: function (todos) {
+ return todos.filter(function (todo) {
+ return !todo.completed;
+ });
+ },
+ completed: function (todos) {
+ return todos.filter(function (todo) {
+ return todo.completed;
+ });
+ }
+ };
+
+ exports.app = new Vue({
+
+ // the root element that will be compiled
+ el: '.todoapp',
+
+ // app initial state
+ data: {
+ todos: todoStorage.fetch(),
+ newTodo: '',
+ editedTodo: null,
+ visibility: 'all'
+ },
+ // watch todos change for localStorage persistence
+ watch: {
+ todos: {
+ deep: true,
+ handler: todoStorage.save
+ }
+ },
+
+ // computed properties
+ // http://vuejs.org/guide/computed.html
+ computed: {
+ filteredTodos: function () {
+ return filters[this.visibility](this.todos);
+ },
+ remaining: function () {
+ return filters.active(this.todos).length;
+ },
+ allDone: {
+ get: function () {
+ return this.remaining === 0;
+ },
+ set: function (value) {
+ this.todos.forEach(function (todo) {
+ todo.completed = value;
+ });
+ }
+ }
+ },
+ mounted: function() {
+ this.sync();
+ },
+ // methods that implement data logic.
+ // note there's no DOM manipulation here at all.
+ methods: {
+
+ pluralize: function (word, count) {
+ return word + (count === 1 ? '' : 's');
+ },
+
+ addTodo: function () {
+ var value = this.newTodo && this.newTodo.trim();
+ if (!value) {
+ return;
+ }
+ this.todos.push({ title: value, completed: false, pendingOnlineCreation: true });
+ this.newTodo = '';
+ },
+
+ removeTodo: function (todo) {
+ var index = this.todos.indexOf(todo);
+ this.todos.splice(index, 1);
+ },
+
+ editTodo: function (todo) {
+ this.beforeEditCache = todo.title;
+ this.editedTodo = todo;
+ },
+
+ doneEdit: function (todo) {
+ if (!this.editedTodo) {
+ return;
+ }
+ this.editedTodo = null;
+ todo.title = todo.title.trim();
+ if (!todo.title) {
+ this.removeTodo(todo);
+ }
+ },
+
+ cancelEdit: function (todo) {
+ this.editedTodo = null;
+ todo.title = this.beforeEditCache;
+ },
+
+ removeCompleted: function () {
+ this.todos = filters.active(this.todos);
+ },
+
+ sync: function () {
+ Promise.all(
+ todoStorage
+ .fetch()
+ .filter((todo) => todo.pendingOnlineCreation)
+ .map(({ title, completed }) => {
+ return axios.post(
+ 'https://4b6aadd7.ngrok.io/todos', { title, completed }
+ );
+ })
+ ).then(() => {
+ axios.get('https://4b6aadd7.ngrok.io/todos').then(({ data }) => {
+ this.todos = data
+ });
+ });
+ }
+ },
+
+ // a custom directive to wait for the DOM to be updated
+ // before focusing on the input field.
+ // http://vuejs.org/guide/custom-directive.html
+ directives: {
+ 'todo-focus': function (el, binding) {
+ if (binding.value) {
+ el.focus();
+ }
+ }
+ }
+ });
+
+})(window);
diff --git a/pwa/js/routes.js b/pwa/js/routes.js
new file mode 100644
index 0000000..b555605
--- /dev/null
+++ b/pwa/js/routes.js
@@ -0,0 +1,24 @@
+/*global app, Router */
+
+(function (app, Router) {
+
+ 'use strict';
+
+ var router = new Router();
+
+ ['all', 'active', 'completed'].forEach(function (visibility) {
+ router.on(visibility, function () {
+ app.visibility = visibility;
+ });
+ });
+
+ router.configure({
+ notfound: function () {
+ window.location.hash = '';
+ app.visibility = 'all';
+ }
+ });
+
+ router.init();
+
+})(app, Router);
diff --git a/pwa/js/store.js b/pwa/js/store.js
new file mode 100644
index 0000000..4a4b12b
--- /dev/null
+++ b/pwa/js/store.js
@@ -0,0 +1,18 @@
+/*jshint unused:false */
+
+(function (exports) {
+
+ 'use strict';
+
+ var STORAGE_KEY = 'todos-vuejs';
+
+ exports.todoStorage = {
+ fetch: function () {
+ return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
+ },
+ save: function (todos) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
+ }
+ };
+
+})(window);
diff --git a/pwa/learn.json b/pwa/learn.json
new file mode 100644
index 0000000..e69de29
diff --git a/pwa/manifest.webmanifest b/pwa/manifest.webmanifest
new file mode 100644
index 0000000..a9a7b41
--- /dev/null
+++ b/pwa/manifest.webmanifest
@@ -0,0 +1,38 @@
+{
+ "name": "Whatsapp clone",
+ "short_name": "Whatsapp clone",
+ "start_url": "/",
+ "display": "standalone",
+ "background_color": "#fff",
+ "description": "Another todo list",
+ "theme_color": "#000000",
+ "icons": [{
+ "src": "https://image.freepik.com/free-icon/whatsapp-logo_318-49685.jpg",
+ "sizes": "48x48",
+ "type": "image/png"
+ }, {
+ "src": "https://image.freepik.com/free-icon/whatsapp-logo_318-49685.jpg",
+ "sizes": "72x72",
+ "type": "image/png"
+ }, {
+ "src": "https://image.freepik.com/free-icon/whatsapp-logo_318-49685.jpg",
+ "sizes": "96x96",
+ "type": "image/png"
+ }, {
+ "src": "https://image.freepik.com/free-icon/whatsapp-logo_318-49685.jpg",
+ "sizes": "144x144",
+ "type": "image/png"
+ }, {
+ "src": "https://image.freepik.com/free-icon/whatsapp-logo_318-49685.jpg",
+ "sizes": "168x168",
+ "type": "image/png"
+ }, {
+ "src": "https://image.freepik.com/free-icon/whatsapp-logo_318-49685.jpg",
+ "sizes": "192x192",
+ "type": "image/png"
+ }, {
+ "src": "https://image.freepik.com/free-icon/whatsapp-logo_318-49685.jpg",
+ "sizes": "512x512",
+ "type": "image/png"
+ }]
+}
diff --git a/pwa/package-lock.json b/pwa/package-lock.json
new file mode 100644
index 0000000..fdaf407
--- /dev/null
+++ b/pwa/package-lock.json
@@ -0,0 +1,61 @@
+{
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "axios": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz",
+ "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=",
+ "requires": {
+ "follow-redirects": "1.2.6",
+ "is-buffer": "1.1.6"
+ }
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "director": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/director/-/director-1.2.8.tgz",
+ "integrity": "sha1-xtm03YkOmv9TZRg/6cyOc5lM8tU="
+ },
+ "follow-redirects": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.6.tgz",
+ "integrity": "sha512-FrMqZ/FONtHnbqO651UPpfRUVukIEwJhXMfdr/JWAmrDbeYBu773b1J6gdWDyRIj4hvvzQEHoEOTrdR8o6KLYA==",
+ "requires": {
+ "debug": "3.1.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "todomvc-app-css": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.1.0.tgz",
+ "integrity": "sha1-tvJxbTOa+i5feZNH0qSLBTliQqU="
+ },
+ "todomvc-common": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.4.tgz",
+ "integrity": "sha512-AA0Z4exovEqubhbZCrzzn9roVT4zvOncS319p2zIc4CsNe5B9TLL7Sei1NIV6d+WrgR5rOi+y0I9Y6GE7xgNOw=="
+ },
+ "vue": {
+ "version": "2.5.8",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.8.tgz",
+ "integrity": "sha512-aY26SGDHJTCKM+mndzuiQ0dozPpNeWO5Mtq760OrHO0AOiqVHMhzvU5h0LdCkVF9A+vE+DMTm74xSi+sxnMEDg=="
+ }
+ }
+}
diff --git a/pwa/package.json b/pwa/package.json
new file mode 100644
index 0000000..8aafc00
--- /dev/null
+++ b/pwa/package.json
@@ -0,0 +1,10 @@
+{
+ "private": true,
+ "dependencies": {
+ "axios": "^0.17.1",
+ "director": "^1.2.0",
+ "todomvc-app-css": "^2.0.0",
+ "todomvc-common": "^1.0.1",
+ "vue": "^2.1.8"
+ }
+}
diff --git a/pwa/readme.md b/pwa/readme.md
new file mode 100644
index 0000000..de50031
--- /dev/null
+++ b/pwa/readme.md
@@ -0,0 +1,29 @@
+# Vue.js TodoMVC Example
+
+> Vue.js is a library for building interactive web interfaces.
+It provides data-driven, nestable view components with a simple and flexible API.
+
+> _[Vue.js - vuejs.org](http://vuejs.org)_
+
+## Learning Vue.js
+
+The [Vue.js website](http://vuejs.org/) is a great resource to get started.
+
+Here are some links you may find helpful:
+
+* [Official Guide](http://vuejs.org/guide/)
+* [API Reference](http://vuejs.org/api/)
+* [Examples](http://vuejs.org/examples/)
+* [Building Larger Apps with Vue.js](http://v1.vuejs.org/guide/application.html)
+
+Get help from other Vue.js users:
+
+* [Vue.js on Twitter](https://twitter.com/vuejs)
+* [Vue.js on Gitter](https://gitter.im/vuejs/vue)
+* [Vue.js Forum](http://forum.vuejs.org)
+
+_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
+
+## Credit
+
+This TodoMVC application was created by [Evan You](http://evanyou.me).
diff --git a/pwa/server.pem b/pwa/server.pem
new file mode 100644
index 0000000..9235471
--- /dev/null
+++ b/pwa/server.pem
@@ -0,0 +1,49 @@
+-----BEGIN PRIVATE KEY-----
+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC9I5+qzbL+J0Sr
+bc/DL1blFt1zB5b2voqydY1qtV8pJL/Ss3P9cIq1sndalJHc3Al7sqoPqwEZpsJQ
+W5pumpboHAI0ta9s90c4eMb42mJxCF0OWH7mEJDTGZ5fWrzxL2sV1FKtKEo7jV36
+nndYbWINKo/mdMznCdDVHHBHIFSCvenJ/Z524C7NE+HoLPTB9b2p1YCRWoFnjQ7Z
+KKY4cFu7r41mFFB1WDONQU41uZsqQe64sh3EIwfh3NKdNewbWXK0aol8M08D/NpW
+49ow+5Kn73uVfaiH1btrxR479ri8osJmlqi6FBga8vCA8TSLB934SoSnA4X0d4rM
+KliR0hgFAgMBAAECggEBAIL/fGPhilprOi19pgtq60VKO+Ci3hVRzWBi+KpyqlEl
+ydIWRny/iOLlk7Lh+dC9ebf5+71UVDO/AxCDEYgPe/uRApodSD4xWlurkyvinZHq
+GAGKm4Ge8Z+0O7ORdhGaJRH+d7DEmf17IR8myEQvply2IOqd9RkD/GbOBpnMs5vE
+2l7IAm6XVZu9Ps80xbnaPD/Hdm0tXQbi9Ru6IcQ6VlB3ih3Z3RP69VTOu3WWBA3i
+dtsess1hOIxST4F9LZDd/NyvIQ86hsV0/IRVXB4YwPOWb4w1Ec4bPh74h5DQwLm/
+SiOjrAtm7xSY029tTpICU9iTonj25mhkUT/25CsF1ZUCgYEA5gLh6xUHjT3VxynS
+UDK2bgT0w1Je/Rw3LuwMhNtFve4TJMWNJpZSCG4nFp3Hs4XRb85tTdQziVEn76PM
+uFymrnIPt8VjIaMSXzz3Jn36lAI3kql1YdoTPNWt9DX4NId5RmKbK8T8r1xvVyKW
+Zy/oZCBoVBaZvinKyQyQMA+BgDcCgYEA0oKCFgJWCdcxAWxv5mEOjzMoKV1ZS64B
+XG8f8hxnMP0D2LZAwAAKLqonRaEhL5/vlh1C8iX6DXuGI3qPeV0HgHVW4NxfG93o
+jsyxVvFwKYaEMPwdId8y3i5ejQBek9mR+xl3PA8OvlrKRyeQjlEyhFtI1dfbbuwh
+1lOo3caBs6MCgYEAhMM9K7fjt/7tGhxlrKzY6AAsV2GAOpDCuW6+eyElzE9S9XKh
+2pgBmaQbI87GqjSfgu2f4cCOTsBtUME3NWMlQqPLtsNUSOIbhKVn8uvcavosoZb2
+jhiV6hNcaQ2NyqQ3uAzKjCUTxRSRn7XzInRgqwqZrY+uBjmRNyLtRxBsK1kCgYEA
+n1NLpeDKwdPvMUajQth+rn9njDcs2IywVwd8RECfEYLOIH2AcFEXY85AvwB/H46L
+RTCMdL2tjfrJwZyHbrGsddtZkAL6sRq5YyslpcpCvAsljfWjDvnhhCvQQCPhcUGg
+rU3O4tP6srQAmO4nCLAH7gkxDTi8yi1KRu82xfPyQsUCgYEAnmVMp7e40GrfWr31
+aViMG4Ql1tddBUFrDRedld4TsHlytu5WfH40TTOk0ofLePxtOGRPWXavhjm2PaNt
+z0BuVsYIceIgkTXJx1DZkc94s96Ehp7I9HTuPvUNJpZ9+zTGuvGmidWAHWbhu9Dk
+BDxk302STIbza6VkhR2p4C9ZpG4=
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAPVAdy+Zo+SjMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTcxMTI1MTcyNDQyWhcNMTgxMTI1MTcyNDQyWjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAvSOfqs2y/idEq23Pwy9W5RbdcweW9r6KsnWNarVfKSS/0rNz/XCKtbJ3
+WpSR3NwJe7KqD6sBGabCUFuabpqW6BwCNLWvbPdHOHjG+NpicQhdDlh+5hCQ0xme
+X1q88S9rFdRSrShKO41d+p53WG1iDSqP5nTM5wnQ1RxwRyBUgr3pyf2eduAuzRPh
+6Cz0wfW9qdWAkVqBZ40O2SimOHBbu6+NZhRQdVgzjUFONbmbKkHuuLIdxCMH4dzS
+nTXsG1lytGqJfDNPA/zaVuPaMPuSp+97lX2oh9W7a8UeO/a4vKLCZpaouhQYGvLw
+gPE0iwfd+EqEpwOF9HeKzCpYkdIYBQIDAQABo1AwTjAdBgNVHQ4EFgQUWnX5qJkR
+FazWToDo5ThZdnt0fBwwHwYDVR0jBBgwFoAUWnX5qJkRFazWToDo5ThZdnt0fBww
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFxYgBtu5YtMUxyk6Z8AE
+uoyP0XJ7Q7NJRSaUgS8bs9fBp7DwCb0itDwDQTFVzP4IQ8FkU2Ru8ovLNdQU0lqk
+ekp4Htfs5iJ1acml9J0YrVyjHXln2FWsPOFlgdk8g5n43VYHKDVjKrDs8ZyAi0Xe
+S0pH+GYeo9FhDnFt+zmLbtd1TucQFoalgad+BQBLThtR4dwWGEWqTaw0aYO0Sd/6
+m+sjQsQltz28Ks6GY/ACm2i5ah2wKv/+ve/QYJDogfGO31gYaAzrgVmvSvpoVLBq
+Bre0gwfmZlNOajK6dyThZNi9Eu569junmHT4KDCWtthrWnzplugHEY/96HH3upQh
+Qw==
+-----END CERTIFICATE-----
diff --git a/pwa/sw.js b/pwa/sw.js
new file mode 100644
index 0000000..bad94a5
--- /dev/null
+++ b/pwa/sw.js
@@ -0,0 +1,34 @@
+
+self.addEventListener('install', function (e) {
+ console.log(caches);
+ e.waitUntil(
+ caches.open('todos').then(function (cache) {
+ return cache.addAll([
+ '/',
+ '/index.html',
+ '/node_modules/todomvc-common/base.css',
+ '/node_modules/todomvc-app-css/index.css',
+ '/node_modules/todomvc-common/base.js',
+ '/node_modules/director/build/director.js',
+ '/node_modules/vue/dist/vue.js',
+ '/node_modules/axios/dist/axios.min.js',
+ '/js/store.js',
+ '/js/app.js',
+ '/js/routes.js',
+ '/learn.json'
+ ]).then((x) => {
+ console.log('O cache acabou!!!!')
+ return x
+ });
+ })
+ );
+});
+
+self.addEventListener('fetch', function (event) {
+ console.log(event.request.url);
+ event.respondWith(
+ caches.match(event.request).then(function (response) {
+ return response || fetch(event.request);
+ })
+ );
+});