]> git.immae.eu Git - github/bastienwirtz/homer.git/commitdiff
Add offline cache + improve layout
authorBastien Wirtz <bastien.wirtz@gmail.com>
Mon, 18 Feb 2019 08:23:20 +0000 (00:23 -0800)
committerBastien Wirtz <bastien.wirtz@gmail.com>
Mon, 18 Feb 2019 08:23:20 +0000 (00:23 -0800)
app.css
app.js
app.scss
assets/logo.png [moved from assets/homer.png with 100% similarity]
config.yml
index.html
worker.js [new file with mode: 0644]

diff --git a/app.css b/app.css
index 98574af7f2e1957a7a4d665cf22d13bb0cfeb4ba..60d276213cdb28d720adf4d289a70196fbfd5501 100644 (file)
--- a/app.css
+++ b/app.css
@@ -4,7 +4,7 @@ html {
 body {
   font-family: 'Raleway', sans-serif;
   background-color: #F5F5F5;
-  height: 100%; }
+  min-height: 100%; }
   body h1, body h2, body h3, body h4, body h5, body h6 {
     font-family: 'Lato', sans-serif; }
   body h1 {
@@ -66,7 +66,7 @@ body {
       white-space: nowrap;
       overflow: hidden;
       text-overflow: ellipsis; }
-    body #main-section .column {
+    body #main-section .container {
       padding: 1.2rem .75rem; }
     body #main-section .message {
       margin-top: 45px;
@@ -82,7 +82,7 @@ body {
     background-color: #4285f4;
     position: absolute;
     top: 1rem;
-    right: -0.3rem;
+    right: -0.2rem;
     width: 3px;
     overflow: hidden;
     transition: all 0.2s ease-out;
@@ -137,3 +137,14 @@ body {
       height: 20px; }
     body .search-bar:focus-within .search-label::before {
       color: #4a4a4a; }
+  body .offline-message {
+    text-align: center;
+    margin: 35px 0; }
+    body .offline-message i {
+      font-size: 2rem; }
+    body .offline-message i.fa-redo-alt {
+      font-size: 1.3rem;
+      line-height: 1rem;
+      vertical-align: middle;
+      cursor: pointer;
+      color: #3273dc; }
diff --git a/app.js b/app.js
index fda16b2f21b3842808fec7fd0d9ad08cad4f5c26..6e20ea3e08a4aeccd6d7f0030f60defe726b33d0 100644 (file)
--- a/app.js
+++ b/app.js
@@ -1,42 +1,53 @@
-var app = new Vue({
+const app = new Vue({
     el: '#app',
     data: {
         config: null,
-        filter: ''
+        offline: false,
+        filter: '',
     },
-    beforeCreate() {
+    created: function () {
         let that = this;
 
-        return getConfig().then(function (config) {
-            const size = 3;
-            config.services.forEach(function (service) {
-                service.rows = [];
-                items = service.items;
-                while (items.length) {
-                    service.rows.push(items.splice(0, size));
-                }
-
-                if (service.rows.length) {
-                    let last = service.rows.length - 1;
-                    service.rows[last] = service.rows[last].concat(Array(size - service.rows[last].length));
-                }
-            });
+        this.checkOffline();
+        that.getConfig().then(function (config) {
             that.config = config;
         }).catch(function () {
-            console.error('Fail to get config');
+            that.offline = true;
         });
+
+        document.addEventListener('visibilitychange', function () {
+            if (document.visibilityState == "visible") {
+                that.checkOffline();
+            }
+        }, false);
+    },
+    methods: {
+        checkOffline: function () {
+            let that = this;
+            return fetch(window.location.href + "?alive", {
+                method: 'HEAD',
+                cache: 'no-store'
+            }).then(function () {
+                that.offline = false;
+            }).catch(function () {
+                that.offline = true;
+            });
+        },
+        getConfig: function (event) {
+            return fetch('config.yml').then(function (response) {
+                if (response.status != 200) {
+                    return
+                }
+                return response.text().then(function (body) {
+                    return jsyaml.load(body);
+                });
+            });
+        },
     }
 });
 
-
-function getConfig() {
-    return fetch('config.yml').then(function (response) {
-        if (response.status !== 200) {
-            return;
-        }
-
-        return response.text().then(function (body) {
-            return jsyaml.load(body);
-        });
+if ('serviceWorker' in navigator) {
+    window.addEventListener('load', function () {
+        navigator.serviceWorker.register('/worker.js');
     });
 }
index e420c0eae852ab74360ffc1b44b2beb7ebb0f6b2..da6271d63822d960574b0ce5f6061335f1e2ab88 100644 (file)
--- a/app.scss
+++ b/app.scss
@@ -6,7 +6,7 @@ html { height: 100%; }
 body {
   font-family: 'Raleway', sans-serif;
   background-color: #F5F5F5;
-  height: 100%;
+  min-height: 100%;
 
   h1, h2, h3, h4, h5, h6 {
     font-family: 'Lato', sans-serif;
@@ -109,7 +109,7 @@ body {
       text-overflow: ellipsis;
     }
 
-    .column {
+    .container {
       padding: 1.2rem .75rem;
     }
 
@@ -136,7 +136,7 @@ body {
     background-color: $secondary-color;
     position: absolute;
     top: 1rem;
-    right: -0.3rem;
+    right: -0.2rem;
     width: 3px;
     overflow: hidden;
     transition: all 0.2s ease-out;
@@ -213,4 +213,21 @@ body {
       }
   }
 
+  .offline-message {
+      text-align: center;
+      margin: 35px 0;
+
+      i {
+        font-size: 2rem;
+      }
+
+      i.fa-redo-alt {
+        font-size: 1.3rem;
+        line-height: 1rem;
+        vertical-align: middle;
+        cursor: pointer;
+        color: #3273dc;
+      }
+  }
+
 }
similarity index 100%
rename from assets/homer.png
rename to assets/logo.png
index 3b0dec5aa5c7d02b7df87b82e15f876566760d16..69c87ea702cee1a69cabc84c09118a1c7ecdf9a4 100644 (file)
@@ -4,7 +4,7 @@
 
 title: "Simple homepage"
 subtitle: "Homer"
-logo: "assets/homer.png"
+logo: "assets/logo.png"
 icon: "fas fa-skull-crossbones"
 
 # Optional message
index 24adcd4720a7c542be9c3ae9fd80193995ef4574..3b83c8e5c229a0976dfab0005b333e1132013105 100644 (file)
@@ -7,8 +7,8 @@
   <meta name="robots" content="noindex">
   <link rel="icon" type="image/png" href="assets/favicon.png">
   <title>Homer</title>
-  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU"
-    crossorigin="anonymous">
+  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css"
+    integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css">
   <link href="https://fonts.googleapis.com/css?family=Lato|Raleway" rel="stylesheet">
   <link rel="stylesheet" href="app.css">
   <div id="app" v-if="config">
     <div id="bighead">
       <section class="first-line">
-        <div class="container">
+        <div v-cloak class="container">
           <div class="logo">
             <img v-if="config.logo" :src="config.logo" />
             <i v-if="config.icon" :class="config.icon"></i>
           </div>
           <div class="dashboard-title">
-            <span v-cloak class="headline">{{ config.subtitle }}</span>
-            <h1 v-cloak>{{ config.title }}</h1>
+            <span class="headline">{{ config.subtitle }}</span>
+            <h1>{{ config.title }}</h1>
           </div>
         </div>
       </section>
-      <div v-if="config.links" class="container-fluid">
+      <div v-cloak v-if="config.links" class="container-fluid">
         <nav class="navbar" role="navigation" aria-label="main navigation">
           <div class="container">
             <div class="navbar-menu">
     </div>
 
     <section id="main-section" class="section">
-      <div class="container">
-        <!-- Optional messages -->
-        <article v-cloak v-if="config && config.message" class="message" :class="config.message.style">
-          <div v-if="config.message.title" class="message-header">
-            <p>{{ config.message.title }}</p>
-          </div>
-          <div v-if="config.message.content" class="message-body">
-            {{ config.message.content }}
-          </div>
-        </article>
+      <div v-cloak class="container">
+        <div v-if="offline" class="offline-message">
+          <i class="far fa-dizzy"></i>
+          <h1>You're offline bro. <i class="fas fa-redo-alt" v-on:click="checkOffline()"></i></h1>
+        </div>
+        <div v-else>
+          <!-- Optional messages -->
+          <article v-if="config && config.message" class="message" :class="config.message.style">
+            <div v-if="config.message.title" class="message-header">
+              <p>{{ config.message.title }}</p>
+            </div>
+            <div v-if="config.message.content" class="message-body">
+              {{ config.message.content }}
+            </div>
+          </article>
 
-        <h2 v-cloak v-if="filter"><i class="fas fa-search"></i> Search</h2>
+          <h2 v-if="filter"><i class="fas fa-search"></i> Search</h2>
 
-        <div v-for="(group, index) in config.services">
-          <h2 v-if="!filter && group.name"><i v-if="group.icon" :class="group.icon"></i><span v-else>#</span>
-            {{ group.name }}</h2>
-          <div v-for="items in group.rows">
-            <div class="columns">
-              <div v-for="item in items" v-if="!filter || (item && (item.name.toLowerCase().includes(filter.toLowerCase()) || (item.tag && item.tag.toLowerCase().includes(filter.toLowerCase()))))"
-                class="column">
-                <div>
-                  <div v-if='item' class="card">
-                    <a :href="item.url">
-                      <div class="card-content">
-                        <div class="media">
-                          <div v-if="item.logo" class="media-left">
-                            <figure class="image is-48x48">
-                              <img :src="item.logo" />
-                            </figure>
-                          </div>
-                          <div v-if="item.icon" class="media-left">
-                            <figure class="image is-48x48">
-                              <i style="font-size: 35px" :class="item.icon"></i>
-                            </figure>
-                          </div>
-                          <div class="media-content">
-                            <p class="title is-4">{{ item.name }}</p>
-                            <p class="subtitle is-6">{{ item.subtitle }}</p>
-                          </div>
+          <div v-for="(group, index) in config.services">
+            <h2 v-if="!filter && group.name"><i v-if="group.icon" :class="group.icon"></i><span v-else>#</span>
+              {{ group.name }}</h2>
+            <div class="columns is-multiline">
+              <div v-for="item in group.items"
+                v-if="!filter || (item && (item.name.toLowerCase().includes(filter.toLowerCase()) || (item.tag && item.tag.toLowerCase().includes(filter.toLowerCase()))))"
+                class="column is-one-third-widescreen">
+                <div v-if='item' class="card">
+                  <a :href="item.url">
+                    <div class="card-content">
+                      <div class="media">
+                        <div v-if="item.logo" class="media-left">
+                          <figure class="image is-48x48">
+                            <img :src="item.logo" />
+                          </figure>
+                        </div>
+                        <div v-if="item.icon" class="media-left">
+                          <figure class="image is-48x48">
+                            <i style="font-size: 35px" :class="item.icon"></i>
+                          </figure>
+                        </div>
+                        <div class="media-content">
+                          <p class="title is-4">{{ item.name }}</p>
+                          <p class="subtitle is-6">{{ item.subtitle }}</p>
                         </div>
-                        <strong v-if="item.tag" class="tag">#{{ item.tag }}</strong>
                       </div>
-                    </a>
-                  </div>
+                      <strong class="tag" v-if="item.tag">#{{ item.tag }}</strong>
+                    </div>
+                  </a>
                 </div>
               </div>
             </div>
   <footer class="footer">
     <div class="container">
       <div class="content has-text-centered">
-        <p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a>
-          & <a href="#">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>
+        <p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a
+            href="https://vuejs.org/">vuejs</a>
+          & <a href="#">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i
+              class="fab fa-github-alt"></i></a></p>
       </div>
     </div>
   </footer>
 
-  <script src="https://unpkg.com/vue"></script>
+  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.2/dist/vue.min.js"></script>
   <script src="vendors/js-yaml.min.js"></script>
   <script src="app.js"></script>
 </body>
diff --git a/worker.js b/worker.js
new file mode 100644 (file)
index 0000000..511c35a
--- /dev/null
+++ b/worker.js
@@ -0,0 +1,32 @@
+self.addEventListener('install', event => {
+    event.waitUntil(
+        caches
+            .open('homer')
+            .then(cache =>
+                cache.addAll([
+                    '/',
+                    '/index.html',
+                    '/config.yml',
+                    '/app.css',
+                    '/app.js',
+                    '/vendors/js-yaml.min.js',
+                    '/assets/logo.png',
+                    'https://use.fontawesome.com/releases/v5.5.0/css/all.css',
+                    'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css',
+                    'https://fonts.googleapis.com/css?family=Lato|Raleway',
+                    'https://cdn.jsdelivr.net/npm/vue@2.6.2/dist/vue.min.js',
+                ])
+            )
+    )
+})
+
+self.addEventListener('fetch', event => {
+    event.respondWith(
+        caches.match(event.request).then(response => {
+            if (response) {
+                return response;
+            }
+            return fetch(event.request);
+        })
+    );
+});