diff options
-rw-r--r-- | app.css | 17 | ||||
-rw-r--r-- | app.js | 67 | ||||
-rw-r--r-- | app.scss | 23 | ||||
-rw-r--r-- | assets/logo.png (renamed from assets/homer.png) | bin | 49200 -> 49200 bytes | |||
-rw-r--r-- | config.yml | 2 | ||||
-rw-r--r-- | index.html | 103 | ||||
-rw-r--r-- | worker.js | 32 |
7 files changed, 160 insertions, 84 deletions
@@ -4,7 +4,7 @@ html { | |||
4 | body { | 4 | body { |
5 | font-family: 'Raleway', sans-serif; | 5 | font-family: 'Raleway', sans-serif; |
6 | background-color: #F5F5F5; | 6 | background-color: #F5F5F5; |
7 | height: 100%; } | 7 | min-height: 100%; } |
8 | body h1, body h2, body h3, body h4, body h5, body h6 { | 8 | body h1, body h2, body h3, body h4, body h5, body h6 { |
9 | font-family: 'Lato', sans-serif; } | 9 | font-family: 'Lato', sans-serif; } |
10 | body h1 { | 10 | body h1 { |
@@ -66,7 +66,7 @@ body { | |||
66 | white-space: nowrap; | 66 | white-space: nowrap; |
67 | overflow: hidden; | 67 | overflow: hidden; |
68 | text-overflow: ellipsis; } | 68 | text-overflow: ellipsis; } |
69 | body #main-section .column { | 69 | body #main-section .container { |
70 | padding: 1.2rem .75rem; } | 70 | padding: 1.2rem .75rem; } |
71 | body #main-section .message { | 71 | body #main-section .message { |
72 | margin-top: 45px; | 72 | margin-top: 45px; |
@@ -82,7 +82,7 @@ body { | |||
82 | background-color: #4285f4; | 82 | background-color: #4285f4; |
83 | position: absolute; | 83 | position: absolute; |
84 | top: 1rem; | 84 | top: 1rem; |
85 | right: -0.3rem; | 85 | right: -0.2rem; |
86 | width: 3px; | 86 | width: 3px; |
87 | overflow: hidden; | 87 | overflow: hidden; |
88 | transition: all 0.2s ease-out; | 88 | transition: all 0.2s ease-out; |
@@ -137,3 +137,14 @@ body { | |||
137 | height: 20px; } | 137 | height: 20px; } |
138 | body .search-bar:focus-within .search-label::before { | 138 | body .search-bar:focus-within .search-label::before { |
139 | color: #4a4a4a; } | 139 | color: #4a4a4a; } |
140 | body .offline-message { | ||
141 | text-align: center; | ||
142 | margin: 35px 0; } | ||
143 | body .offline-message i { | ||
144 | font-size: 2rem; } | ||
145 | body .offline-message i.fa-redo-alt { | ||
146 | font-size: 1.3rem; | ||
147 | line-height: 1rem; | ||
148 | vertical-align: middle; | ||
149 | cursor: pointer; | ||
150 | color: #3273dc; } | ||
@@ -1,42 +1,53 @@ | |||
1 | var app = new Vue({ | 1 | const app = new Vue({ |
2 | el: '#app', | 2 | el: '#app', |
3 | data: { | 3 | data: { |
4 | config: null, | 4 | config: null, |
5 | filter: '' | 5 | offline: false, |
6 | filter: '', | ||
6 | }, | 7 | }, |
7 | beforeCreate() { | 8 | created: function () { |
8 | let that = this; | 9 | let that = this; |
9 | 10 | ||
10 | return getConfig().then(function (config) { | 11 | this.checkOffline(); |
11 | const size = 3; | 12 | that.getConfig().then(function (config) { |
12 | config.services.forEach(function (service) { | ||
13 | service.rows = []; | ||
14 | items = service.items; | ||
15 | while (items.length) { | ||
16 | service.rows.push(items.splice(0, size)); | ||
17 | } | ||
18 | |||
19 | if (service.rows.length) { | ||
20 | let last = service.rows.length - 1; | ||
21 | service.rows[last] = service.rows[last].concat(Array(size - service.rows[last].length)); | ||
22 | } | ||
23 | }); | ||
24 | that.config = config; | 13 | that.config = config; |
25 | }).catch(function () { | 14 | }).catch(function () { |
26 | console.error('Fail to get config'); | 15 | that.offline = true; |
27 | }); | 16 | }); |
17 | |||
18 | document.addEventListener('visibilitychange', function () { | ||
19 | if (document.visibilityState == "visible") { | ||
20 | that.checkOffline(); | ||
21 | } | ||
22 | }, false); | ||
23 | }, | ||
24 | methods: { | ||
25 | checkOffline: function () { | ||
26 | let that = this; | ||
27 | return fetch(window.location.href + "?alive", { | ||
28 | method: 'HEAD', | ||
29 | cache: 'no-store' | ||
30 | }).then(function () { | ||
31 | that.offline = false; | ||
32 | }).catch(function () { | ||
33 | that.offline = true; | ||
34 | }); | ||
35 | }, | ||
36 | getConfig: function (event) { | ||
37 | return fetch('config.yml').then(function (response) { | ||
38 | if (response.status != 200) { | ||
39 | return | ||
40 | } | ||
41 | return response.text().then(function (body) { | ||
42 | return jsyaml.load(body); | ||
43 | }); | ||
44 | }); | ||
45 | }, | ||
28 | } | 46 | } |
29 | }); | 47 | }); |
30 | 48 | ||
31 | 49 | if ('serviceWorker' in navigator) { | |
32 | function getConfig() { | 50 | window.addEventListener('load', function () { |
33 | return fetch('config.yml').then(function (response) { | 51 | navigator.serviceWorker.register('/worker.js'); |
34 | if (response.status !== 200) { | ||
35 | return; | ||
36 | } | ||
37 | |||
38 | return response.text().then(function (body) { | ||
39 | return jsyaml.load(body); | ||
40 | }); | ||
41 | }); | 52 | }); |
42 | } | 53 | } |
@@ -6,7 +6,7 @@ html { height: 100%; } | |||
6 | body { | 6 | body { |
7 | font-family: 'Raleway', sans-serif; | 7 | font-family: 'Raleway', sans-serif; |
8 | background-color: #F5F5F5; | 8 | background-color: #F5F5F5; |
9 | height: 100%; | 9 | min-height: 100%; |
10 | 10 | ||
11 | h1, h2, h3, h4, h5, h6 { | 11 | h1, h2, h3, h4, h5, h6 { |
12 | font-family: 'Lato', sans-serif; | 12 | font-family: 'Lato', sans-serif; |
@@ -109,7 +109,7 @@ body { | |||
109 | text-overflow: ellipsis; | 109 | text-overflow: ellipsis; |
110 | } | 110 | } |
111 | 111 | ||
112 | .column { | 112 | .container { |
113 | padding: 1.2rem .75rem; | 113 | padding: 1.2rem .75rem; |
114 | } | 114 | } |
115 | 115 | ||
@@ -136,7 +136,7 @@ body { | |||
136 | background-color: $secondary-color; | 136 | background-color: $secondary-color; |
137 | position: absolute; | 137 | position: absolute; |
138 | top: 1rem; | 138 | top: 1rem; |
139 | right: -0.3rem; | 139 | right: -0.2rem; |
140 | width: 3px; | 140 | width: 3px; |
141 | overflow: hidden; | 141 | overflow: hidden; |
142 | transition: all 0.2s ease-out; | 142 | transition: all 0.2s ease-out; |
@@ -213,4 +213,21 @@ body { | |||
213 | } | 213 | } |
214 | } | 214 | } |
215 | 215 | ||
216 | .offline-message { | ||
217 | text-align: center; | ||
218 | margin: 35px 0; | ||
219 | |||
220 | i { | ||
221 | font-size: 2rem; | ||
222 | } | ||
223 | |||
224 | i.fa-redo-alt { | ||
225 | font-size: 1.3rem; | ||
226 | line-height: 1rem; | ||
227 | vertical-align: middle; | ||
228 | cursor: pointer; | ||
229 | color: #3273dc; | ||
230 | } | ||
231 | } | ||
232 | |||
216 | } | 233 | } |
diff --git a/assets/homer.png b/assets/logo.png index 6fa8ee8..6fa8ee8 100644 --- a/assets/homer.png +++ b/assets/logo.png | |||
Binary files differ | |||
@@ -4,7 +4,7 @@ | |||
4 | 4 | ||
5 | title: "Simple homepage" | 5 | title: "Simple homepage" |
6 | subtitle: "Homer" | 6 | subtitle: "Homer" |
7 | logo: "assets/homer.png" | 7 | logo: "assets/logo.png" |
8 | icon: "fas fa-skull-crossbones" | 8 | icon: "fas fa-skull-crossbones" |
9 | 9 | ||
10 | # Optional message | 10 | # Optional message |
@@ -7,8 +7,8 @@ | |||
7 | <meta name="robots" content="noindex"> | 7 | <meta name="robots" content="noindex"> |
8 | <link rel="icon" type="image/png" href="assets/favicon.png"> | 8 | <link rel="icon" type="image/png" href="assets/favicon.png"> |
9 | <title>Homer</title> | 9 | <title>Homer</title> |
10 | <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" | 10 | <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" |
11 | crossorigin="anonymous"> | 11 | integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous"> |
12 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css"> | 12 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css"> |
13 | <link href="https://fonts.googleapis.com/css?family=Lato|Raleway" rel="stylesheet"> | 13 | <link href="https://fonts.googleapis.com/css?family=Lato|Raleway" rel="stylesheet"> |
14 | <link rel="stylesheet" href="app.css"> | 14 | <link rel="stylesheet" href="app.css"> |
@@ -18,18 +18,18 @@ | |||
18 | <div id="app" v-if="config"> | 18 | <div id="app" v-if="config"> |
19 | <div id="bighead"> | 19 | <div id="bighead"> |
20 | <section class="first-line"> | 20 | <section class="first-line"> |
21 | <div class="container"> | 21 | <div v-cloak class="container"> |
22 | <div class="logo"> | 22 | <div class="logo"> |
23 | <img v-if="config.logo" :src="config.logo" /> | 23 | <img v-if="config.logo" :src="config.logo" /> |
24 | <i v-if="config.icon" :class="config.icon"></i> | 24 | <i v-if="config.icon" :class="config.icon"></i> |
25 | </div> | 25 | </div> |
26 | <div class="dashboard-title"> | 26 | <div class="dashboard-title"> |
27 | <span v-cloak class="headline">{{ config.subtitle }}</span> | 27 | <span class="headline">{{ config.subtitle }}</span> |
28 | <h1 v-cloak>{{ config.title }}</h1> | 28 | <h1>{{ config.title }}</h1> |
29 | </div> | 29 | </div> |
30 | </div> | 30 | </div> |
31 | </section> | 31 | </section> |
32 | <div v-if="config.links" class="container-fluid"> | 32 | <div v-cloak v-if="config.links" class="container-fluid"> |
33 | <nav class="navbar" role="navigation" aria-label="main navigation"> | 33 | <nav class="navbar" role="navigation" aria-label="main navigation"> |
34 | <div class="container"> | 34 | <div class="container"> |
35 | <div class="navbar-menu"> | 35 | <div class="navbar-menu"> |
@@ -52,50 +52,53 @@ | |||
52 | </div> | 52 | </div> |
53 | 53 | ||
54 | <section id="main-section" class="section"> | 54 | <section id="main-section" class="section"> |
55 | <div class="container"> | 55 | <div v-cloak class="container"> |
56 | <!-- Optional messages --> | 56 | <div v-if="offline" class="offline-message"> |
57 | <article v-cloak v-if="config && config.message" class="message" :class="config.message.style"> | 57 | <i class="far fa-dizzy"></i> |
58 | <div v-if="config.message.title" class="message-header"> | 58 | <h1>You're offline bro. <i class="fas fa-redo-alt" v-on:click="checkOffline()"></i></h1> |
59 | <p>{{ config.message.title }}</p> | 59 | </div> |
60 | </div> | 60 | <div v-else> |
61 | <div v-if="config.message.content" class="message-body"> | 61 | <!-- Optional messages --> |
62 | {{ config.message.content }} | 62 | <article v-if="config && config.message" class="message" :class="config.message.style"> |
63 | </div> | 63 | <div v-if="config.message.title" class="message-header"> |
64 | </article> | 64 | <p>{{ config.message.title }}</p> |
65 | </div> | ||
66 | <div v-if="config.message.content" class="message-body"> | ||
67 | {{ config.message.content }} | ||
68 | </div> | ||
69 | </article> | ||
65 | 70 | ||
66 | <h2 v-cloak v-if="filter"><i class="fas fa-search"></i> Search</h2> | 71 | <h2 v-if="filter"><i class="fas fa-search"></i> Search</h2> |
67 | 72 | ||
68 | <div v-for="(group, index) in config.services"> | 73 | <div v-for="(group, index) in config.services"> |
69 | <h2 v-if="!filter && group.name"><i v-if="group.icon" :class="group.icon"></i><span v-else>#</span> | 74 | <h2 v-if="!filter && group.name"><i v-if="group.icon" :class="group.icon"></i><span v-else>#</span> |
70 | {{ group.name }}</h2> | 75 | {{ group.name }}</h2> |
71 | <div v-for="items in group.rows"> | 76 | <div class="columns is-multiline"> |
72 | <div class="columns"> | 77 | <div v-for="item in group.items" |
73 | <div v-for="item in items" v-if="!filter || (item && (item.name.toLowerCase().includes(filter.toLowerCase()) || (item.tag && item.tag.toLowerCase().includes(filter.toLowerCase()))))" | 78 | v-if="!filter || (item && (item.name.toLowerCase().includes(filter.toLowerCase()) || (item.tag && item.tag.toLowerCase().includes(filter.toLowerCase()))))" |
74 | class="column"> | 79 | class="column is-one-third-widescreen"> |
75 | <div> | 80 | <div v-if='item' class="card"> |
76 | <div v-if='item' class="card"> | 81 | <a :href="item.url"> |
77 | <a :href="item.url"> | 82 | <div class="card-content"> |
78 | <div class="card-content"> | 83 | <div class="media"> |
79 | <div class="media"> | 84 | <div v-if="item.logo" class="media-left"> |
80 | <div v-if="item.logo" class="media-left"> | 85 | <figure class="image is-48x48"> |
81 | <figure class="image is-48x48"> | 86 | <img :src="item.logo" /> |
82 | <img :src="item.logo" /> | 87 | </figure> |
83 | </figure> | 88 | </div> |
84 | </div> | 89 | <div v-if="item.icon" class="media-left"> |
85 | <div v-if="item.icon" class="media-left"> | 90 | <figure class="image is-48x48"> |
86 | <figure class="image is-48x48"> | 91 | <i style="font-size: 35px" :class="item.icon"></i> |
87 | <i style="font-size: 35px" :class="item.icon"></i> | 92 | </figure> |
88 | </figure> | 93 | </div> |
89 | </div> | 94 | <div class="media-content"> |
90 | <div class="media-content"> | 95 | <p class="title is-4">{{ item.name }}</p> |
91 | <p class="title is-4">{{ item.name }}</p> | 96 | <p class="subtitle is-6">{{ item.subtitle }}</p> |
92 | <p class="subtitle is-6">{{ item.subtitle }}</p> | ||
93 | </div> | ||
94 | </div> | 97 | </div> |
95 | <strong v-if="item.tag" class="tag">#{{ item.tag }}</strong> | ||
96 | </div> | 98 | </div> |
97 | </a> | 99 | <strong class="tag" v-if="item.tag">#{{ item.tag }}</strong> |
98 | </div> | 100 | </div> |
101 | </a> | ||
99 | </div> | 102 | </div> |
100 | </div> | 103 | </div> |
101 | </div> | 104 | </div> |
@@ -108,13 +111,15 @@ | |||
108 | <footer class="footer"> | 111 | <footer class="footer"> |
109 | <div class="container"> | 112 | <div class="container"> |
110 | <div class="content has-text-centered"> | 113 | <div class="content has-text-centered"> |
111 | <p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> | 114 | <p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a |
112 | & <a href="#">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p> | 115 | href="https://vuejs.org/">vuejs</a> |
116 | & <a href="#">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i | ||
117 | class="fab fa-github-alt"></i></a></p> | ||
113 | </div> | 118 | </div> |
114 | </div> | 119 | </div> |
115 | </footer> | 120 | </footer> |
116 | 121 | ||
117 | <script src="https://unpkg.com/vue"></script> | 122 | <script src="https://cdn.jsdelivr.net/npm/vue@2.6.2/dist/vue.min.js"></script> |
118 | <script src="vendors/js-yaml.min.js"></script> | 123 | <script src="vendors/js-yaml.min.js"></script> |
119 | <script src="app.js"></script> | 124 | <script src="app.js"></script> |
120 | </body> | 125 | </body> |
diff --git a/worker.js b/worker.js new file mode 100644 index 0000000..511c35a --- /dev/null +++ b/worker.js | |||
@@ -0,0 +1,32 @@ | |||
1 | self.addEventListener('install', event => { | ||
2 | event.waitUntil( | ||
3 | caches | ||
4 | .open('homer') | ||
5 | .then(cache => | ||
6 | cache.addAll([ | ||
7 | '/', | ||
8 | '/index.html', | ||
9 | '/config.yml', | ||
10 | '/app.css', | ||
11 | '/app.js', | ||
12 | '/vendors/js-yaml.min.js', | ||
13 | '/assets/logo.png', | ||
14 | 'https://use.fontawesome.com/releases/v5.5.0/css/all.css', | ||
15 | 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css', | ||
16 | 'https://fonts.googleapis.com/css?family=Lato|Raleway', | ||
17 | 'https://cdn.jsdelivr.net/npm/vue@2.6.2/dist/vue.min.js', | ||
18 | ]) | ||
19 | ) | ||
20 | ) | ||
21 | }) | ||
22 | |||
23 | self.addEventListener('fetch', event => { | ||
24 | event.respondWith( | ||
25 | caches.match(event.request).then(response => { | ||
26 | if (response) { | ||
27 | return response; | ||
28 | } | ||
29 | return fetch(event.request); | ||
30 | }) | ||
31 | ); | ||
32 | }); | ||