diff options
Diffstat (limited to 'app.js')
-rw-r--r-- | app.js | 174 |
1 files changed, 0 insertions, 174 deletions
@@ -1,174 +0,0 @@ | |||
1 | const app = new Vue({ | ||
2 | el: '#app', | ||
3 | data: { | ||
4 | config: null, | ||
5 | offline: false, | ||
6 | filter: '', | ||
7 | vlayout: true, | ||
8 | isDark: null, | ||
9 | showMenu: false | ||
10 | }, | ||
11 | created: async function () { | ||
12 | let that = this; | ||
13 | |||
14 | this.isDark = 'overrideDark' in localStorage ? | ||
15 | JSON.parse(localStorage.overrideDark) : matchMedia("(prefers-color-scheme: dark)").matches; | ||
16 | |||
17 | if ('vlayout' in localStorage) { | ||
18 | this.vlayout = JSON.parse(localStorage.vlayout) | ||
19 | } | ||
20 | |||
21 | this.checkOffline(); | ||
22 | try { | ||
23 | this.config = await this.getConfig(); | ||
24 | document.title = this.config.title + ' | ' + this.config.subtitle; | ||
25 | } catch (error) { | ||
26 | this.offline = true; | ||
27 | } | ||
28 | |||
29 | // Look for a new message if an endpoint is provided. | ||
30 | if (this.config.message && this.config.message.url) { | ||
31 | this.getMessage(this.config.message.url).then(function(message){ | ||
32 | // keep the original config value if no value is provided by the endpoint | ||
33 | for (const prop of ['title','style','content']) { | ||
34 | if (prop in message && message[prop] !== null) { | ||
35 | that.config.message[prop] = message[prop]; | ||
36 | } | ||
37 | } | ||
38 | }); | ||
39 | } | ||
40 | |||
41 | document.addEventListener('visibilitychange', function () { | ||
42 | if (document.visibilityState == "visible") { | ||
43 | that.checkOffline(); | ||
44 | } | ||
45 | }, false); | ||
46 | }, | ||
47 | methods: { | ||
48 | checkOffline: function () { | ||
49 | let that = this; | ||
50 | return fetch(window.location.href + "?alive", { | ||
51 | method: 'HEAD', | ||
52 | cache: 'no-store' | ||
53 | }).then(function () { | ||
54 | that.offline = false; | ||
55 | }).catch(function () { | ||
56 | that.offline = true; | ||
57 | }); | ||
58 | }, | ||
59 | getConfig: function (event) { | ||
60 | return fetch('config.yml').then(function (response) { | ||
61 | if (response.status != 200) { | ||
62 | return | ||
63 | } | ||
64 | return response.text().then(function (body) { | ||
65 | return jsyaml.load(body); | ||
66 | }); | ||
67 | }); | ||
68 | }, | ||
69 | getMessage: function (url) { | ||
70 | return fetch(url).then(function (response) { | ||
71 | if (response.status != 200) { | ||
72 | return; | ||
73 | } | ||
74 | return response.json(); | ||
75 | }); | ||
76 | }, | ||
77 | toggleTheme: function() { | ||
78 | this.isDark = !this.isDark; | ||
79 | localStorage.overrideDark = this.isDark; | ||
80 | }, | ||
81 | toggleLayout: function() { | ||
82 | this.vlayout = !this.vlayout; | ||
83 | localStorage.vlayout = this.vlayout; | ||
84 | }, | ||
85 | toggleMenu: function() { | ||
86 | this.showMenu = !this.showMenu; | ||
87 | }, | ||
88 | matchesFilter: function(item) { | ||
89 | return (item.name.toLowerCase().includes(this.filter.toLowerCase()) | ||
90 | || (item.tag && item.tag.toLowerCase().includes(this.filter.toLowerCase()))) | ||
91 | }, | ||
92 | firstMatchingService: function() { | ||
93 | for (group of this.config.services) { | ||
94 | for (item of group.items) { | ||
95 | if (this.matchesFilter(item)) { | ||
96 | return item; | ||
97 | } | ||
98 | } | ||
99 | } | ||
100 | return null; | ||
101 | }, | ||
102 | navigateToFirstService: function(target) { | ||
103 | service = this.firstMatchingService(); | ||
104 | if (service) { | ||
105 | window.open(service.url, target || service.target || '_self'); | ||
106 | } | ||
107 | } | ||
108 | }, | ||
109 | mounted() { | ||
110 | function isSmallScreen() { | ||
111 | return window.matchMedia('screen and (max-width: 1023px)').matches; | ||
112 | } | ||
113 | this._keyListener = function(e) { | ||
114 | if (e.key === '/') { | ||
115 | if (isSmallScreen()) { | ||
116 | this.showMenu = true; | ||
117 | } | ||
118 | Vue.nextTick(() => { | ||
119 | this.$refs.search.focus(); | ||
120 | }); | ||
121 | |||
122 | e.preventDefault(); | ||
123 | } | ||
124 | if (e.key === 'Escape') { | ||
125 | this.filter = ''; | ||
126 | this.$refs.search.blur(); | ||
127 | if (isSmallScreen()) { | ||
128 | this.showMenu = false; | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | document.addEventListener('keydown', this._keyListener.bind(this)); | ||
134 | }, | ||
135 | beforeDestroy() { | ||
136 | document.removeEventListener('keydown', this._keyListener); | ||
137 | } | ||
138 | }); | ||
139 | |||
140 | Vue.component('service', { | ||
141 | props: ['item'], | ||
142 | template: `<div> | ||
143 | <div class="card"> | ||
144 | <a :href="item.url" :target="item.target"> | ||
145 | <div class="card-content"> | ||
146 | <div class="media"> | ||
147 | <div v-if="item.logo" class="media-left"> | ||
148 | <figure class="image is-48x48"> | ||
149 | <img :src="item.logo" /> | ||
150 | </figure> | ||
151 | </div> | ||
152 | <div v-if="item.icon" class="media-left"> | ||
153 | <figure class="image is-48x48"> | ||
154 | <i style="font-size: 35px" :class="item.icon"></i> | ||
155 | </figure> | ||
156 | </div> | ||
157 | <div class="media-content"> | ||
158 | <p class="title is-4">{{ item.name }}</p> | ||
159 | <p class="subtitle is-6">{{ item.subtitle }}</p> | ||
160 | </div> | ||
161 | </div> | ||
162 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
163 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
164 | </div> | ||
165 | </div> | ||
166 | </a> | ||
167 | </div></div>` | ||
168 | }); | ||
169 | |||
170 | if ('serviceWorker' in navigator) { | ||
171 | window.addEventListener('load', function () { | ||
172 | navigator.serviceWorker.register('worker.js'); | ||
173 | }); | ||
174 | } | ||