]>
Commit | Line | Data |
---|---|---|
9baec9ae | 1 | const app = new Vue({ |
09763dbf BW |
2 | el: '#app', |
3 | data: { | |
4 | config: null, | |
9baec9ae BW |
5 | offline: false, |
6 | filter: '', | |
5323df4a | 7 | vlayout: true, |
7cc525b2 T |
8 | isDark: null, |
9 | showMenu: false | |
09763dbf | 10 | }, |
7fd9dc6f | 11 | created: async function () { |
9ca12a40 | 12 | let that = this; |
7fd9dc6f | 13 | |
a489a0a3 | 14 | this.isDark = 'overrideDark' in localStorage ? |
d6adb2db | 15 | JSON.parse(localStorage.overrideDark) : matchMedia("(prefers-color-scheme: dark)").matches; |
9ca12a40 | 16 | |
d6adb2db BW |
17 | if ('vlayout' in localStorage) { |
18 | this.vlayout = JSON.parse(localStorage.vlayout) | |
19 | } | |
a489a0a3 | 20 | |
9baec9ae | 21 | this.checkOffline(); |
7fd9dc6f BW |
22 | try { |
23 | this.config = await this.getConfig(); | |
26221ad3 | 24 | document.title = this.config.title + ' | Homer'; |
7fd9dc6f BW |
25 | } catch (error) { |
26 | this.offline = true; | |
27 | } | |
28 | ||
29 | // Look for a new message if an endpoint is provided. | |
d5ef84f5 | 30 | if (this.config.message && this.config.message.url) { |
7fd9dc6f BW |
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 | } | |
9baec9ae BW |
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 | }, | |
7fd9dc6f BW |
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 | }, | |
5323df4a | 77 | toggleTheme: function() { |
d6adb2db BW |
78 | this.isDark = !this.isDark; |
79 | localStorage.overrideDark = this.isDark; | |
80 | }, | |
81 | toggleLayout: function() { | |
82 | this.vlayout = !this.vlayout; | |
a489a0a3 FB |
83 | localStorage.vlayout = this.vlayout; |
84 | }, | |
7cc525b2 T |
85 | toggleMenu: function() { |
86 | this.showMenu = !this.showMenu; | |
a4de4a3a JS |
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 | } | |
7cc525b2 | 107 | } |
6eba37a3 JS |
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); | |
09763dbf BW |
137 | } |
138 | }); | |
139 | ||
4877ec98 BW |
140 | Vue.component('service', { |
141 | props: ['item'], | |
142 | template: `<div> | |
143 | <div class="card"> | |
56c69e0d | 144 | <a :href="item.url" :target="item.target"> |
4877ec98 BW |
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> | |
a2fdb8a9 | 162 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> |
163 | <strong class="tag-text">#{{ item.tag }}</strong> | |
164 | </div> | |
4877ec98 BW |
165 | </div> |
166 | </a> | |
167 | </div></div>` | |
168 | }); | |
169 | ||
9baec9ae BW |
170 | if ('serviceWorker' in navigator) { |
171 | window.addEventListener('load', function () { | |
a489a0a3 | 172 | navigator.serviceWorker.register('worker.js'); |
09763dbf | 173 | }); |
e41196e7 | 174 | } |