From 3fb86482404e11942cd83c3500a297a3991db0e4 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Wed, 13 Sep 2017 16:28:53 +0200 Subject: Restructure project --- src/client/javascript/analytics.js | 35 ++ src/client/javascript/autocomplete.js | 87 +++++ src/client/javascript/browserFixToolkit.js | 12 + src/client/javascript/favorite.js | 79 +++++ src/client/javascript/featureDetect.js | 29 ++ src/client/javascript/frontpage.js | 23 ++ src/client/javascript/main.js | 71 ++++ src/client/javascript/schedule.js | 78 ++++ src/client/javascript/scrollSnap.js | 59 ++++ src/client/javascript/search.js | 95 +++++ src/client/javascript/url.js | 67 ++++ src/client/javascript/weekSelector.js | 99 ++++++ src/client/javascript/zoom.js | 30 ++ src/client/static/.well-known/keybase.txt | 54 +++ src/client/static/apple-touch-icon.png | Bin 0 -> 6447 bytes src/client/static/browserconfig.xml | 9 + src/client/static/favicon-16x16.png | Bin 0 -> 1293 bytes src/client/static/favicon-32x32.png | Bin 0 -> 2103 bytes src/client/static/favicon.ico | Bin 0 -> 15086 bytes src/client/static/icons/mml-logo.png | Bin 0 -> 12508 bytes .../static/icons/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 6503 bytes .../static/icons/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3854 bytes .../static/icons/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 9631 bytes .../static/icons/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 15315 bytes .../icons/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 23445 bytes src/client/static/manifest.beta.webmanifest | 29 ++ src/client/static/manifest.webmanifest | 29 ++ src/client/static/mstile-150x150.png | Bin 0 -> 3995 bytes src/client/static/safari-pinned-tab.svg | 34 ++ src/client/static/stylesheets/hello.css | 23 ++ src/client/static/stylesheets/print.css | 63 ++++ src/client/static/stylesheets/style.css | 392 +++++++++++++++++++++ src/client/static/sw.js | 29 ++ src/client/static/untisinfo.css | 11 + src/client/views/error.jade | 6 + src/client/views/index.jade | 51 +++ src/client/views/layout.jade | 25 ++ src/client/views/redirect.jade | 47 +++ 38 files changed, 1566 insertions(+) create mode 100644 src/client/javascript/analytics.js create mode 100644 src/client/javascript/autocomplete.js create mode 100644 src/client/javascript/browserFixToolkit.js create mode 100644 src/client/javascript/favorite.js create mode 100644 src/client/javascript/featureDetect.js create mode 100644 src/client/javascript/frontpage.js create mode 100644 src/client/javascript/main.js create mode 100644 src/client/javascript/schedule.js create mode 100644 src/client/javascript/scrollSnap.js create mode 100644 src/client/javascript/search.js create mode 100644 src/client/javascript/url.js create mode 100644 src/client/javascript/weekSelector.js create mode 100644 src/client/javascript/zoom.js create mode 100644 src/client/static/.well-known/keybase.txt create mode 100644 src/client/static/apple-touch-icon.png create mode 100644 src/client/static/browserconfig.xml create mode 100644 src/client/static/favicon-16x16.png create mode 100644 src/client/static/favicon-32x32.png create mode 100644 src/client/static/favicon.ico create mode 100644 src/client/static/icons/mml-logo.png create mode 100644 src/client/static/icons/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/client/static/icons/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/client/static/icons/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/client/static/icons/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/client/static/icons/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/client/static/manifest.beta.webmanifest create mode 100644 src/client/static/manifest.webmanifest create mode 100644 src/client/static/mstile-150x150.png create mode 100644 src/client/static/safari-pinned-tab.svg create mode 100644 src/client/static/stylesheets/hello.css create mode 100644 src/client/static/stylesheets/print.css create mode 100644 src/client/static/stylesheets/style.css create mode 100644 src/client/static/sw.js create mode 100644 src/client/static/untisinfo.css create mode 100644 src/client/views/error.jade create mode 100644 src/client/views/index.jade create mode 100644 src/client/views/layout.jade create mode 100644 src/client/views/redirect.jade (limited to 'src/client') diff --git a/src/client/javascript/analytics.js b/src/client/javascript/analytics.js new file mode 100644 index 0000000..a93c8a4 --- /dev/null +++ b/src/client/javascript/analytics.js @@ -0,0 +1,35 @@ +/* global ga */ + +const self = {} + +self.send = {} + +self.send.search = function (selectedUser, favorite) { + const hitType = 'event' + + const eventCategory = favorite ? 'search fav' : 'search' + + let eventAction + switch (selectedUser.type) { + case 'c': + eventAction = 'Class' + break + case 't': + eventAction = 'Teacher' + break + case 'r': + eventAction = 'Room' + break + case 's': + eventAction = 'Student' + break + } + + const eventLabel = selectedUser.value + + ga(function () { + ga('send', { hitType, eventCategory, eventAction, eventLabel }) + }) +} + +module.exports = self diff --git a/src/client/javascript/autocomplete.js b/src/client/javascript/autocomplete.js new file mode 100644 index 0000000..61f400a --- /dev/null +++ b/src/client/javascript/autocomplete.js @@ -0,0 +1,87 @@ +const EventEmitter = require('events') + +const self = new EventEmitter() + +self._users = [] +self._selectedUserIndex = -1 + +self._nodes = { + search: document.querySelector('#search'), + input: document.querySelector('input[type="search"]'), + autocomplete: document.querySelector('.autocomplete') +} + +self.getSelectedUser = function () { + if (self.getItems() === []) return + + if (self.getSelectedUserIndex() === -1) { + return self.getItems()[0] + } else { + return self.getItems()[self.getSelectedUserIndex()] + } +} + +self.getSelectedUserIndex = function () { + return self._selectedUserIndex +} + +self.getItems = function () { + return self._users +} + +self.removeAllItems = function () { + while (self._nodes.autocomplete.firstChild) { + self._nodes.autocomplete.removeChild(self._nodes.autocomplete.firstChild) + } + self._users = [] + self._selectedUserIndex = -1 +} + +self.addItem = function (user) { + const listItem = document.createElement('li') + listItem.textContent = user.value + self._nodes.autocomplete.appendChild(listItem) + self._users.push(user) +} + +self._moveSelected = function (shift) { + if (self._selectedUserIndex + shift >= self.getItems().length) { + self._selectedUserIndex = -1 + } else if (self._selectedUserIndex + shift < -1) { + self._selectedUserIndex = self.getItems().length - 1 + } else { + self._selectedUserIndex += shift + } + + for (let i = 0; i < self.getItems().length; i++) { + self._nodes.autocomplete.children[i].classList.remove('selected') + } + if (self._selectedUserIndex >= 0) { + self._nodes.autocomplete + .children[self._selectedUserIndex].classList.add('selected') + } +} + +self._handleItemClick = function (event) { + if (!self._nodes.autocomplete.contains(event.target)) return + const userIndex = Array.prototype.indexOf + .call(self._nodes.autocomplete.children, event.target) + self._selectedUserIndex = userIndex + self.emit('select', self.getSelectedUser()) +} + +self._handleKeydown = function (event) { + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + event.preventDefault() + if (event.key === 'ArrowDown') { + self._moveSelected(1) + } else if (event.key === 'ArrowUp') { + self._moveSelected(-1) + } + } +} + +self._nodes.autocomplete.addEventListener('click', self._handleItemClick) +self._nodes.input.addEventListener('keydown', self._handleKeydown) + +module.exports = self diff --git a/src/client/javascript/browserFixToolkit.js b/src/client/javascript/browserFixToolkit.js new file mode 100644 index 0000000..fbeab74 --- /dev/null +++ b/src/client/javascript/browserFixToolkit.js @@ -0,0 +1,12 @@ +const self = {} + +self.isIE = navigator.userAgent.indexOf('MSIE') !== -1 || + navigator.appVersion.indexOf('Trident/') > 0 + +if (self.isIE) { + self.inputEvent = 'textinput' +} else { + self.inputEvent = 'input' +} + +module.exports = self diff --git a/src/client/javascript/favorite.js b/src/client/javascript/favorite.js new file mode 100644 index 0000000..92c87f7 --- /dev/null +++ b/src/client/javascript/favorite.js @@ -0,0 +1,79 @@ +/* global USERS */ + +const EventEmitter = require('events') + +const self = new EventEmitter() + +self._nodes = { + toggle: document.querySelector('.fav') +} + +self.get = function () { + try { + const localStorageUser = JSON.parse(window.localStorage.getItem('fav')) + if (localStorageUser == null) return + + const correctedUser = USERS.filter(function (user) { + return user.type === localStorageUser.type && + user.value === localStorageUser.value + })[0] + return correctedUser + } catch (e) { + self.delete() + return + } +} + +self.set = function (user) { + window.localStorage.setItem('fav', JSON.stringify(user)) + self._nodes.innerHTML = '' +} + +self.delete = function () { + window.localStorage.removeItem('fav') +} + +self.updateDom = function (isFavorite) { + if (isFavorite) { + self._nodes.toggle.innerHTML = '' + } else { + self._nodes.toggle.innerHTML = '' + } +} + +self.update = function (selectedUser) { + const currentUser = self.get() + + if (currentUser == null || selectedUser == null) { + self.updateDom(false) + return + } + + const isEqual = currentUser.type === selectedUser.type && + currentUser.index === selectedUser.index + + self.updateDom(isEqual) +} + +self.toggle = function (selectedUser) { + const currentUser = self.get() + const isEqual = currentUser != null && + currentUser.type === selectedUser.type && + currentUser.index === selectedUser.index + + if (isEqual) { + self.delete() + self.updateDom(false) + } else { + self.set(selectedUser) + self.updateDom(true) + } +} + +self._handleClick = function () { + self.emit('click') +} + +self._nodes.toggle.addEventListener('click', self._handleClick) + +module.exports = self diff --git a/src/client/javascript/featureDetect.js b/src/client/javascript/featureDetect.js new file mode 100644 index 0000000..3a072a1 --- /dev/null +++ b/src/client/javascript/featureDetect.js @@ -0,0 +1,29 @@ +/* global FLAGS */ + +const self = {} + +self._nodes = { + input: document.querySelector('input[type="search"]'), + overflowButton: document.querySelector('#overflow-button') +} + +self._shouldCheck = function () { + return FLAGS.indexOf('NO_FEATURE_DETECT') === -1 +} + +self._redirect = function () { + window.location.href = 'http://www.meetingpointmco.nl/Roosters-AL/doc/' +} + +self.check = function () { + if (!self._shouldCheck()) return + + window.onerror = self._redirect + + if (self._nodes.input.getClientRects()[0].top !== + self._nodes.overflowButton.getClientRects()[0].top) { + self._redirect() + } +} + +module.exports = self diff --git a/src/client/javascript/frontpage.js b/src/client/javascript/frontpage.js new file mode 100644 index 0000000..17cb539 --- /dev/null +++ b/src/client/javascript/frontpage.js @@ -0,0 +1,23 @@ +const browserFixToolkit = require('./browserFixToolkit') + +const self = {} + +self._nodes = { + input: document.querySelector('input[type="search"]') +} + +self.isShown = false + +self.show = function () { + document.body.classList.add('no-input') + self.isShown = true +} + +self.hide = function () { + document.body.classList.remove('no-input') + self.isShown = false +} + +self._nodes.input.addEventListener(browserFixToolkit.inputEvent, self.hide) + +module.exports = self diff --git a/src/client/javascript/main.js b/src/client/javascript/main.js new file mode 100644 index 0000000..0d125cb --- /dev/null +++ b/src/client/javascript/main.js @@ -0,0 +1,71 @@ +require('./featureDetect').check() +require('./zoom') + +const frontpage = require('./frontpage') +const search = require('./search') +const schedule = require('./schedule') +const weekSelector = require('./weekSelector') +const favorite = require('./favorite') +const scrollSnap = require('./scrollSnap') +const analytics = require('./analytics') +const url = require('./url') + +const state = {} + +window.state = state + +frontpage.show() +weekSelector.updateCurrentWeek() +scrollSnap.startListening() + +if (url.hasSelectedUser()) { + state.selectedUser = url.getSelectedUser() + + favorite.update(state.selectedUser) + url.update(state.selectedUser) + analytics.send.search(state.selectedUser) + + schedule.viewItem(weekSelector.getSelectedWeek(), state.selectedUser) +} else if (favorite.get() != null) { + state.selectedUser = favorite.get() + + favorite.update(state.selectedUser) + url.push(state.selectedUser, false) + url.update(state.selectedUser) + analytics.send.search(state.selectedUser, true) + + schedule.viewItem(weekSelector.getSelectedWeek(), state.selectedUser) +} else { + search.focus() +} + +search.on('search', function (selectedUser) { + state.selectedUser = selectedUser + + favorite.update(state.selectedUser) + url.push(state.selectedUser) + url.update(state.selectedUser) + analytics.send.search(state.selectedUser) + + schedule.viewItem(weekSelector.getSelectedWeek(), state.selectedUser) +}) + +url.on('update', function (selectedUser) { + state.selectedUser = selectedUser + + favorite.update(state.selectedUser) + url.update(state.selectedUser) + + schedule.viewItem(weekSelector.getSelectedWeek(), state.selectedUser) +}) + +weekSelector.on('weekChanged', function (newWeek) { + analytics.send.search(state.selectedUser) + schedule.viewItem(newWeek, state.selectedUser) +}) + +favorite.on('click', function () { + favorite.toggle(state.selectedUser) +}) + +document.body.style.opacity = 1 diff --git a/src/client/javascript/schedule.js b/src/client/javascript/schedule.js new file mode 100644 index 0000000..38ad66d --- /dev/null +++ b/src/client/javascript/schedule.js @@ -0,0 +1,78 @@ +/* global VALID_WEEK_NUMBERS */ + +const EventEmitter = require('events') +const search = require('./search') + +const self = new EventEmitter() + +self._nodes = { + schedule: document.querySelector('#schedule') +} + +self._parseMeetingpointHTML = function (htmlStr) { + const html = document.createElement('html') + html.innerHTML = htmlStr + const centerNode = html.querySelector('center') + return centerNode +} + +self._handleLoad = function (event) { + const request = event.target + if (request.status < 200 || request.status >= 400) { + self._handleError(event) + return + } + const document = self._parseMeetingpointHTML(request.response) + self._removeChilds() + self._nodes.schedule.appendChild(document) + self._nodes.schedule.classList.remove('error') + self.emit('load') +} + +self._handleError = function (event) { + const request = event.target + let error + if (request.status === 404) { + error = 'Sorry, er is (nog) geen rooster voor deze week.' + } else { + error = 'Sorry, er is iets mis gegaan tijdens het laden van deze week.' + } + self._removeChilds() + self._nodes.schedule.textContent = error + self._nodes.schedule.classList.add('error') + self.emit('load') +} + +self._getURLOfUser = function (week, user) { + return `/get/${user.type}/${user.value}?week=${week}` +} + +self._removeChilds = function () { + while (self._nodes.schedule.firstChild) { + self._nodes.schedule.removeChild(self._nodes.schedule.firstChild) + } +} + +self.viewItem = function (week, selectedUser) { + if (selectedUser == null) { + self._removeChilds() + search.updateDom(selectedUser) + } else if (VALID_WEEK_NUMBERS.indexOf(week) === -1) { + self._handleError({ target: { status: 404 } }); + return + } else { + const url = self._getURLOfUser(week, selectedUser) + + self._removeChilds() + + const request = new window.XMLHttpRequest() + request.addEventListener('load', self._handleLoad) + request.addEventListener('error', self._handleError) + request.open('GET', url, true) + request.send() + + search.updateDom(selectedUser) + } +} + +module.exports = self diff --git a/src/client/javascript/scrollSnap.js b/src/client/javascript/scrollSnap.js new file mode 100644 index 0000000..afee979 --- /dev/null +++ b/src/client/javascript/scrollSnap.js @@ -0,0 +1,59 @@ +require('smoothscroll-polyfill').polyfill() + +const self = {} +const schedule = require('./schedule') + +self._nodes = { + search: document.querySelector('#search'), + weekSelector: document.querySelector('#week-selector') +} + +self._timeoutID = null + +self._getScrollPosition = function () { + return (document.documentElement && document.documentElement.scrollTop) || + document.body.scrollTop +} + +self._handleDoneScrolling = function () { + const scrollPosition = self._getScrollPosition() + const weekSelectorHeight = + self._nodes.weekSelector.clientHeight - self._nodes.search.clientHeight + if (scrollPosition < weekSelectorHeight && scrollPosition > 0) { + window.scroll({ top: weekSelectorHeight, left: 0, behavior: 'smooth' }) + } +} + +self._handleScroll = function () { + if (self._timeoutID != null) window.clearTimeout(self._timeoutID) + self._timeoutID = window.setTimeout(self._handleDoneScrolling, 500) + + const scrollPosition = self._getScrollPosition() + const weekSelectorHeight = + self._nodes.weekSelector.clientHeight - self._nodes.search.clientHeight + if (scrollPosition >= weekSelectorHeight) { + document.body.classList.add('week-selector-not-visible') + } else { + document.body.classList.remove('week-selector-not-visible') + } +} + +self._handleWindowResize = function () { + const weekSelectorHeight = + self._nodes.weekSelector.clientHeight - self._nodes.search.clientHeight + const extraPixelsNeeded = + weekSelectorHeight - (document.body.clientHeight - window.innerHeight) + if (extraPixelsNeeded > 0) { + document.body.style.marginBottom = extraPixelsNeeded + 'px' + } else { + document.body.style.marginBottom = null + } +} + +self.startListening = function () { + window.addEventListener('scroll', self._handleScroll) +} + +schedule.on('load', self._handleWindowResize) +window.addEventListener('resize', self._handleWindowResize) +module.exports = self diff --git a/src/client/javascript/search.js b/src/client/javascript/search.js new file mode 100644 index 0000000..96413b0 --- /dev/null +++ b/src/client/javascript/search.js @@ -0,0 +1,95 @@ +/* global USERS */ + +const EventEmitter = require('events') +const fuzzy = require('fuzzy') +const autocomplete = require('./autocomplete') +const browserFixToolkit = require('./browserFixToolkit') + +const self = new EventEmitter() + +self._nodes = { + search: document.querySelector('#search'), + input: document.querySelector('input[type="search"]') +} + +self.submit = function () { + const selectedUser = autocomplete.getSelectedUser() + if (selectedUser == null) return + + console.log(selectedUser) + + self._nodes.input.blur() + document.body.classList.remove('week-selector-not-visible') // Safari bug + + self.emit('search', selectedUser) +} + +self.updateDom = function (selectedUser) { + if (selectedUser == null) { + self._nodes.input.value = '' + autocomplete.removeAllItems() + document.body.classList.add('no-input') + document.body.classList.remove('searched') + } else { + self._nodes.input.value = selectedUser.value + autocomplete.removeAllItems() + document.body.classList.remove('no-input') + document.body.classList.add('searched') + } +} + +self.focus = function () { + self._nodes.input.focus() +} + +self._handleSubmit = function (event) { + event.preventDefault() + self.submit() +} + +self._calculate = function (searchTerm) { + const allResults = fuzzy.filter(searchTerm, USERS, { + extract: function (user) { return user.value } + }) + const firstResults = allResults.slice(0, 7) + + const originalResults = firstResults.map(function (result) { + return result.original + }) + + return originalResults +} + +self._handleTextUpdate = function () { + const results = self._calculate(self._nodes.input.value) + + autocomplete.removeAllItems() + for (let i = 0; i < results.length; i++) { + autocomplete.addItem(results[i]) + } +} + +self._handleFocus = function () { + self._nodes.input.select() +} + +self._handleBlur = function () { + // this will removed the selection without drawing focus on it (safari) + // this will removed selection even when focusing an iframe (chrome) + const oldValue = self._nodes.value + self._nodes.value = '' + self._nodes.value = oldValue + + // this will hide the keyboard (iOS safari) + document.activeElement.blur() +} + +autocomplete.on('select', self.submit) + +self._nodes.search.addEventListener('submit', self._handleSubmit) +self._nodes.input.addEventListener('focus', self._handleFocus) +self._nodes.input.addEventListener('blur', self._handleBlur) +self._nodes.input.addEventListener(browserFixToolkit.inputEvent, + self._handleTextUpdate) + +module.exports = self diff --git a/src/client/javascript/url.js b/src/client/javascript/url.js new file mode 100644 index 0000000..17ab7c8 --- /dev/null +++ b/src/client/javascript/url.js @@ -0,0 +1,67 @@ +/* global USERS FLAGS */ + +const EventEmitter = require('events') + +const self = new EventEmitter() + +self._getPageTitle = function (selectedUser) { + let ret + + if (selectedUser == null) { + ret = `Metis Rooster` + } else { + ret = `Metis Rooster - ${selectedUser.value}` + } + + if (FLAGS.indexOf('BETA') !== -1) { + ret = `BETA ${ret}` + } + + return ret +} + +self._getPageURL = function (selectedUser) { + return `/${selectedUser.type}/${selectedUser.value}` +} + +self.push = function (selectedUser, push) { + if (push == null) push = true + const pageTitle = self._getPageTitle(selectedUser) + const pageURL = self._getPageURL(selectedUser) + if (push) { + window.history.pushState(selectedUser, pageTitle, pageURL) + } else { + window.history.replaceState(selectedUser, pageTitle, pageURL) + } +} + +self.update = function (selectedUser) { + document.title = self._getPageTitle(selectedUser) +} + +self.hasSelectedUser = function () { + const pageUrl = window.location.pathname + return /^\/s\/|^\/t\/|^\/r\/|^\/c\//.test(pageUrl) +} + +self.getSelectedUser = function () { + const pageUrl = window.location.pathname + const pageUrlData = pageUrl.split('/') + const type = pageUrlData[1] + const value = pageUrlData[2] + + const user = USERS.filter(function (user) { + return user.type === type && + user.value === value + })[0] + + return user +} + +self._handleUpdate = function (event) { + self.emit('update', event.state) +} + +window.addEventListener('popstate', self._handleUpdate) + +module.exports = self diff --git a/src/client/javascript/weekSelector.js b/src/client/javascript/weekSelector.js new file mode 100644 index 0000000..d4e7f2a --- /dev/null +++ b/src/client/javascript/weekSelector.js @@ -0,0 +1,99 @@ +const EventEmitter = require('events') + +const self = new EventEmitter() + +self._nodes = { + prevButton: document.querySelectorAll('#week-selector button')[0], + nextButton: document.querySelectorAll('#week-selector button')[1], + currentWeekNode: document.querySelector('#week-selector .current'), + currentWeekNormalText: document.querySelector('#week-selector .current .no-print'), + currentWeekPrintText: document.querySelector('#week-selector .current .print') +} + +self._weekOffset = 0 + +// copied from http://www.meetingpointmco.nl/Roosters-AL/doc/dagroosters/untisscripts.js, +// were using the same code as they do to be sure that we always get the same +// week number. +self.getCurrentWeek = function (target) { + const dayNr = (target.getDay() + 6) % 7 + target.setDate(target.getDate() - dayNr + 3) + const firstThursday = target.valueOf() + target.setMonth(0, 1) + if (target.getDay() !== 4) { + target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7) + } + + return 1 + Math.ceil((firstThursday - target) / 604800000) +} + +self.getSelectedWeek = function () { + const now = new Date() + const targetDate = new Date(now.getTime() + + self._weekOffset * 604800 * 1000 + 86400 * 1000) + return self.getCurrentWeek(targetDate) +} + +self.updateCurrentWeek = function () { + const selectedWeekNumber = self.getSelectedWeek() + if (self.getCurrentWeek(new Date()) !== selectedWeekNumber) { + self._nodes.currentWeekNode.classList.add('changed') + } else { + self._nodes.currentWeekNode.classList.remove('changed') + } + self.updateDom() + self.emit('weekChanged', selectedWeekNumber) +} + +self.updateDom = function () { + const selectedWeekNumber = self.getSelectedWeek() + const isSunday = new Date().getDay() === 0 + let humanReadableWeek = null + if (isSunday) { + switch (self._weekOffset) { + case 0: + humanReadableWeek = 'Aanstaande week' + break + case 1: + humanReadableWeek = 'Volgende week' + break + case -1: + humanReadableWeek = 'Afgelopen week' + break + } + } else { + switch (self._weekOffset) { + case 0: + humanReadableWeek = 'Huidige week' + break + case 1: + humanReadableWeek = 'Volgende week' + break + case -1: + humanReadableWeek = 'Vorige week' + break + } + } + if (humanReadableWeek != null) { + self._nodes.currentWeekNormalText.textContent = humanReadableWeek + ' • ' + selectedWeekNumber + self._nodes.currentWeekPrintText.textContent = 'Week ' + selectedWeekNumber + } else { + self._nodes.currentWeekNormalText.textContent = 'Week ' + selectedWeekNumber + self._nodes.currentWeekPrintText.textContent = 'Week ' + selectedWeekNumber + } +} + +self._handlePrevButtonClick = function () { + self._weekOffset -= 1 + self.updateCurrentWeek() +} + +self._handleNextButtonClick = function () { + self._weekOffset += 1 + self.updateCurrentWeek() +} + +self._nodes.prevButton.addEventListener('click', self._handlePrevButtonClick) +self._nodes.nextButton.addEventListener('click', self._handleNextButtonClick) + +module.exports = self diff --git a/src/client/javascript/zoom.js b/src/client/javascript/zoom.js new file mode 100644 index 0000000..59b80db --- /dev/null +++ b/src/client/javascript/zoom.js @@ -0,0 +1,30 @@ +const schedule = require('./schedule') + +const self = {} + +self._nodes = { + body: document.body +} + +self._handleResize = function () { + // the table node may not exist before this function is called + const tableNode = document.querySelector('center > table') + + // infact, it may not even exist when this function is called. + if (!tableNode) return + + const tableWidth = tableNode.getBoundingClientRect().width + const tableGoalWidth = self._nodes.body.getBoundingClientRect().width * 0.9 + const zoomFactor = tableGoalWidth / tableWidth + + if (zoomFactor < 1) { + tableNode.style.zoom = `${zoomFactor}` + } else { + tableNode.style.zoom = `1` + } +} + +schedule.on('load', self._handleResize) +window.addEventListener('resize', self._handleResize) + +module.exports = self diff --git a/src/client/static/.well-known/keybase.txt b/src/client/static/.well-known/keybase.txt new file mode 100644 index 0000000..7e11526 --- /dev/null +++ b/src/client/static/.well-known/keybase.txt @@ -0,0 +1,54 @@ +================================================================== +https://keybase.io/nloomans +-------------------------------------------------------------------- + +I hereby claim: + + * I am an admin of https://rooster.hetmml.nl + * I am nloomans (https://keybase.io/nloomans) on keybase. + * I have a public key ASCCV4aRFiMkEv7inJTf34RgxZ6IK0wQ-wTH2ZfSIu3OzAo + +To do so, I am signing this object: + +{ + "body": { + "key": { + "eldest_kid": "0101bbdb28841b169de6538a51d17ca94b30088ba2914e56fd19121eec05f7a389cc0a", + "host": "keybase.io", + "kid": "01208257869116232412fee29c94dfdf8460c59e882b4c10fb04c7d997d222edcecc0a", + "uid": "7a52ddabf92293dd59f8fbf3774ea319", + "username": "nloomans" + }, + "service": { + "hostname": "rooster.hetmml.nl", + "protocol": "https:" + }, + "type": "web_service_binding", + "version": 1 + }, + "client": { + "name": "keybase.io go client", + "version": "1.0.20" + }, + "ctime": 1492017398, + "expire_in": 504576000, + "merkle_root": { + "ctime": 1492017367, + "hash": "463e597079ce3829ccc1f1aa7b15533c0848f9e13cdb55407af490a87bf4ac1b2d64e8235518ada07d93003b889157b576aad02eda294ccd594dc0dcbf8862ef", + "seqno": 1015311 + }, + "prev": "36959cd282a98f651138068f8695b07480a016f02ba99a0acbde277e0cf4ca30", + "seqno": 19, + "tag": "signature" +} + +which yields the signature: + +hKRib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEggleGkRYjJBL+4pyU39+EYMWeiCtMEPsEx9mX0iLtzswKp3BheWxvYWTFAvt7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTAxYmJkYjI4ODQxYjE2OWRlNjUzOGE1MWQxN2NhOTRiMzAwODhiYTI5MTRlNTZmZDE5MTIxZWVjMDVmN2EzODljYzBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwODI1Nzg2OTExNjIzMjQxMmZlZTI5Yzk0ZGZkZjg0NjBjNTllODgyYjRjMTBmYjA0YzdkOTk3ZDIyMmVkY2VjYzBhIiwidWlkIjoiN2E1MmRkYWJmOTIyOTNkZDU5ZjhmYmYzNzc0ZWEzMTkiLCJ1c2VybmFtZSI6Im5sb29tYW5zIn0sInNlcnZpY2UiOnsiaG9zdG5hbWUiOiJyb29zdGVyLmhldG1tbC5ubCIsInByb3RvY29sIjoiaHR0cHM6In0sInR5cGUiOiJ3ZWJfc2VydmljZV9iaW5kaW5nIiwidmVyc2lvbiI6MX0sImNsaWVudCI6eyJuYW1lIjoia2V5YmFzZS5pbyBnbyBjbGllbnQiLCJ2ZXJzaW9uIjoiMS4wLjIwIn0sImN0aW1lIjoxNDkyMDE3Mzk4LCJleHBpcmVfaW4iOjUwNDU3NjAwMCwibWVya2xlX3Jvb3QiOnsiY3RpbWUiOjE0OTIwMTczNjcsImhhc2giOiI0NjNlNTk3MDc5Y2UzODI5Y2NjMWYxYWE3YjE1NTMzYzA4NDhmOWUxM2NkYjU1NDA3YWY0OTBhODdiZjRhYzFiMmQ2NGU4MjM1NTE4YWRhMDdkOTMwMDNiODg5MTU3YjU3NmFhZDAyZWRhMjk0Y2NkNTk0ZGMwZGNiZjg4NjJlZiIsInNlcW5vIjoxMDE1MzExfSwicHJldiI6IjM2OTU5Y2QyODJhOThmNjUxMTM4MDY4Zjg2OTViMDc0ODBhMDE2ZjAyYmE5OWEwYWNiZGUyNzdlMGNmNGNhMzAiLCJzZXFubyI6MTksInRhZyI6InNpZ25hdHVyZSJ9o3NpZ8RAcP5FuvbGM9nXBzWqChr9zdj452IpBzrVbd6YvcktLyKjaUaRg51BOWsyHmYQ+uxmZ2ZCUI6xZbbJ1SIAnWqvC6hzaWdfdHlwZSCkaGFzaIKkdHlwZQildmFsdWXEING7Z+BlY2sOTnQqQJo/PUBashy75VL9UU4tGIEvMXbco3RhZ80CAqd2ZXJzaW9uAQ== + +And finally, I am proving ownership of this host by posting or +appending to this document. + +View my publicly-auditable identity here: https://keybase.io/nloomans + +================================================================== diff --git a/src/client/static/apple-touch-icon.png b/src/client/static/apple-touch-icon.png new file mode 100644 index 0000000..5adfc69 Binary files /dev/null and b/src/client/static/apple-touch-icon.png differ diff --git a/src/client/static/browserconfig.xml b/src/client/static/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/src/client/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/src/client/static/favicon-16x16.png b/src/client/static/favicon-16x16.png new file mode 100644 index 0000000..1df47d3 Binary files /dev/null and b/src/client/static/favicon-16x16.png differ diff --git a/src/client/static/favicon-32x32.png b/src/client/static/favicon-32x32.png new file mode 100644 index 0000000..36cd5da Binary files /dev/null and b/src/client/static/favicon-32x32.png differ diff --git a/src/client/static/favicon.ico b/src/client/static/favicon.ico new file mode 100644 index 0000000..c201043 Binary files /dev/null and b/src/client/static/favicon.ico differ diff --git a/src/client/static/icons/mml-logo.png b/src/client/static/icons/mml-logo.png new file mode 100644 index 0000000..fa5ae11 Binary files /dev/null and b/src/client/static/icons/mml-logo.png differ diff --git a/src/client/static/icons/res/mipmap-hdpi/ic_launcher.png b/src/client/static/icons/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..514ad14 Binary files /dev/null and b/src/client/static/icons/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/client/static/icons/res/mipmap-mdpi/ic_launcher.png b/src/client/static/icons/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..59bab1d Binary files /dev/null and b/src/client/static/icons/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/client/static/icons/res/mipmap-xhdpi/ic_launcher.png b/src/client/static/icons/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..908a6e8 Binary files /dev/null and b/src/client/static/icons/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/client/static/icons/res/mipmap-xxhdpi/ic_launcher.png b/src/client/static/icons/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..f12048f Binary files /dev/null and b/src/client/static/icons/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/client/static/icons/res/mipmap-xxxhdpi/ic_launcher.png b/src/client/static/icons/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..7a0462e Binary files /dev/null and b/src/client/static/icons/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/client/static/manifest.beta.webmanifest b/src/client/static/manifest.beta.webmanifest new file mode 100644 index 0000000..a1fdd92 --- /dev/null +++ b/src/client/static/manifest.beta.webmanifest @@ -0,0 +1,29 @@ +{ + "name": "BETA Metis Rooster", + "short_name": "BETA Rooster", + "start_url": "/", + "display": "standalone", + "background_color": "#ececec", + "description": "Een verbeterde rooster pagina voor het metis", + "icons": [{ + "src": "/icons/res/mipmap-mdpi/ic_launcher.png", + "sizes": "48x48", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-hdpi/ic_launcher.png", + "sizes": "72x72", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-xhdpi/ic_launcher.png", + "sizes": "96x96", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-xxhdpi/ic_launcher.png", + "sizes": "144x144", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-xxxhdpi/ic_launcher.png", + "sizes": "192x192", + "type": "image/png" + }] +} diff --git a/src/client/static/manifest.webmanifest b/src/client/static/manifest.webmanifest new file mode 100644 index 0000000..d33ee8e --- /dev/null +++ b/src/client/static/manifest.webmanifest @@ -0,0 +1,29 @@ +{ + "name": "Metis Rooster", + "short_name": "Rooster", + "start_url": "/", + "display": "standalone", + "background_color": "#ececec", + "description": "Een verbeterde rooster pagina voor het metis", + "icons": [{ + "src": "/icons/res/mipmap-mdpi/ic_launcher.png", + "sizes": "48x48", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-hdpi/ic_launcher.png", + "sizes": "72x72", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-xhdpi/ic_launcher.png", + "sizes": "96x96", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-xxhdpi/ic_launcher.png", + "sizes": "144x144", + "type": "image/png" + }, { + "src": "/icons/res/mipmap-xxxhdpi/ic_launcher.png", + "sizes": "192x192", + "type": "image/png" + }] +} diff --git a/src/client/static/mstile-150x150.png b/src/client/static/mstile-150x150.png new file mode 100644 index 0000000..5e381e6 Binary files /dev/null and b/src/client/static/mstile-150x150.png differ diff --git a/src/client/static/safari-pinned-tab.svg b/src/client/static/safari-pinned-tab.svg new file mode 100644 index 0000000..97ce8bf --- /dev/null +++ b/src/client/static/safari-pinned-tab.svg @@ -0,0 +1,34 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + diff --git a/src/client/static/stylesheets/hello.css b/src/client/static/stylesheets/hello.css new file mode 100644 index 0000000..edcbc92 --- /dev/null +++ b/src/client/static/stylesheets/hello.css @@ -0,0 +1,23 @@ +* { + box-sizing: border-box; +} + +html, body { + margin: 0; + font-family: 'Roboto', sans-serif; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + width: 100vw; + height: 100vh; + color: gray; +} + +.ideas { + font-size: 0.8em; +} + +a { + color: #3f51b5; +} diff --git a/src/client/static/stylesheets/print.css b/src/client/static/stylesheets/print.css new file mode 100644 index 0000000..0e09533 --- /dev/null +++ b/src/client/static/stylesheets/print.css @@ -0,0 +1,63 @@ +#search, #week-selector { + background-color: inherit; + box-shadow: inherit; +} + +#search { + border-bottom: 1px solid black; + position: absolute; +} + +#search .top-bar, #week-selector .week-wrapper { + max-width: inherit; +} + +#search input[type="search"] { + background-color: inherit; + color: black; + font-weight: bold; +} + +#search .fav { + display: none !important; +} + +#search #overflow-button { + display: none; +} + +#week-selector .week-wrapper { + display: block; +} + +#week-selector button { + display: none; +} + +#week-selector .current { + color: black; + padding: 16px; + font-size: 1.1em; + float: right; +} + +#search-space-filler { + display: none; +} + +.mdl-menu__container { + display: none !important; +} + +#schedule { + padding-top: 16px; + width: 100%; +} + +.no-print { + display: none; +} + +.print { + display: initial; +} diff --git a/src/client/static/stylesheets/style.css b/src/client/static/stylesheets/style.css new file mode 100644 index 0000000..830b007 --- /dev/null +++ b/src/client/static/stylesheets/style.css @@ -0,0 +1,392 @@ +* { + box-sizing: border-box; +} + +html, body { + margin: 0; + font-family: 'Roboto', sans-serif; +} + +.other { + color: gray; + font-style: italic; + margin-left: 5px; +} + +#search { + z-index: 2; + background-color: #F44336; + margin: 0 auto; + width: 100%; + position: fixed; + box-shadow: 0 0.5px 1.5px rgba(0,0,0,0.06), 0 0.5px 1px rgba(0,0,0,0.12); +} + +#search .top-bar { + position: relative; + margin: 0 auto; + max-width: 600px; + padding: 10px; + display: flex; +} + +#search .input-wrapper { + position: relative; + flex-grow: 1; + color: #FFFFFF; +} + +#search input[type='search'] { + display: block; + background-color: #f6695e; + color: inherit; + border-radius: 2px; + width: 100%; + display: block; + outline: none; + border: 0; + padding: 16px; + font-size: 16px; + transition: box-shadow 200ms ease-in-out; +} + +#search input[type='search']:focus { + background-color: #FFFFFF; + color: #212121; + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); +} + +#search input[type='search']:focus + button { + color: #212121; +} + +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-results-button, +input[type="search"]::-webkit-search-results-decoration { + display: none; +} + +input[type="search"]::-ms-clear { + width: 0; + height: 0; +} + +button::-moz-focus-inner { + border: 0; +} + + +/* WebKit, Blink, Edge */ +input::-webkit-input-placeholder { + color: #FFCDD2; +} +input:focus::-webkit-input-placeholder { + color: #757575; +} + +/* Mozilla Firefox 4 to 18 */ +input:-moz-placeholder { + color: #FFCDD2; + opacity: 1; +} +input:focus:-moz-placeholder { + color: #757575; +} + +/* Mozilla Firefox 19+ */ +input::-moz-placeholder { + color: #FFCDD2; + opacity: 1; +} +input:focus::-moz-placeholder { + color: #757575; +} + +/* Internet Explorer 10-11 */ +input:-ms-input-placeholder { + color: #FFCDD2; +} +input:focus:-ms-input-placeholder { + color: #757575; +} + +li:hover { + background-color: lightgray; + cursor: pointer; +} + +.selected { + background-color: lightgray; +} + +#schedule { + overflow: auto; +} + +body.searched #search-space-filler { + height: 70px; +} + +.autocomplete-wrapper { + background-color: white; +} + +.autocomplete { + max-width: 600px; + margin: 0 auto; + padding: 0; +} + +.autocomplete li { + list-style: none; + padding: 10px; +} + +#week-selector { + z-index: 1; + background-color: #F44336; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + color: white; +} + +#week-selector .week-wrapper { + max-width: 600px; + padding: 10px !important; + margin: 0 auto; + display: flex; + -js-display: flex; + padding: 10px 0; +} + +#week-selector .current { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: center; +} + +#week-selector .current.changed { + font-weight: bold; +} + +#week-selector button { + background: transparent; + color: white; + border: 0px; + padding: 5px 10px; + border-radius: 2px; +} + +input { + -webkit-appearance: none; +} + +#search .fav { + position: absolute; + font-size: 1.8em; + color: inherit; + right: 8.5px; + top: 8.5px; + border: 0; + padding: 4px; + border-radius: 2px; + background: none; + + display: none; +} + +body.searched #search .fav { + display: block; +} + +#week-selector button:focus, #search #overflow-button:focus, #search .fav:focus { + outline: none; + background-color: #D32F2F; +} + +#search #overflow-button { + background: none; + border: none; + padding: 3px 9px; + color: white; + border-radius: 2px; +} + +.hidden { + display: none !important; +} + +ul a { + color: inherit; + text-decoration: none; +} + +#search .title { + display: none; +} + +body:not(.no-input) { + overflow-y: scroll; +} + +body.no-input #week-selector { + display: none; +} + +@media screen and (min-height: 400px) { + body.no-input { + background-color: #ececec; + } + + body.no-input #search { + height: 100%; + background-color: #ececec; + box-shadow: none; + } + + body.no-input #search button { + display: none; + } + + body.no-input #search #overflow-button { + position: absolute; + display: block; + top: 0; + right: 0; + color: #757575; + } + + body.no-input #search .print-page { + display: none; + } + + body.no-input #search #overflow-button:focus { + background-color: inherit; + color: #212121; + } + + body.no-input #search .logo { + background-image: url(/icons/mml-logo.png); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + height: 100px; + width: 100px; + + /* virtual center: http://javier.xyz/visual-center/ */ + transform: translate(-8%,-3%); + margin: 0 auto; + } + + body.no-input #search .title { + display: block; + font-size: 55px; + padding-bottom: 32px; + } + + body.no-input #search .title .text { + text-align: center; + line-height: 55px; + } + + body.no-input #search .top-bar { + position: static; + display: block; + margin-top: 50vh; + transform: translateY(-75%); + } + + body.no-input #search input[type='search'] { + background-color: #FFF; + } + + /* WebKit, Blink, Edge */ + body.no-input #search input::-webkit-input-placeholder { + color: #757575; + } + + /* Mozilla Firefox 4 to 18 */ + body.no-input #search input:-moz-placeholder { + color: #757575; + opacity: 1; + } + + /* Mozilla Firefox 19+ */ + body.no-input #search input::-moz-placeholder { + color: #757575; + opacity: 1; + } + + /* Internet Explorer 10-11 */ + body.no-input #search input:-ms-input-placeholder { + color: #757575; + } + + body.no-input .tooltip { + display: block; + position: absolute; + background-color: white; + padding: 15px; + margin: 32px 8px; + border-radius: 2px; + + left: 16px; + right: 16px; + } + + body.no-input .tooltip::before { + content: ''; + width: 24px; + height: 24px; + background-color: white; + top: -12px; + position: absolute; + transform: rotate(45deg); + z-index: -1; + } +} + +.tooltip { + display: none; +} + +.error { + text-align: center; + margin-top: 100px; + padding: 16px; +} + +body.week-selector-not-visible #search { + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); +} + +body.week-selector-not-visible #week-selector { + box-shadow: inherit; +} + +.print { + display: none; +} + +#notification { + max-width: 600px; + padding: 10px; + margin: 0 auto; +} + +#notification .box { + display: flex; + background-color: #e0e0e0; + padding: 8px; + border-radius: 2px; + align-items: center; +} + +#notification .text { + padding-left: 8px; +} + +.grow { + flex-grow: 1; +} diff --git a/src/client/static/sw.js b/src/client/static/sw.js new file mode 100644 index 0000000..bd43805 --- /dev/null +++ b/src/client/static/sw.js @@ -0,0 +1,29 @@ +/* global importScripts toolbox self */ + +(global => { + 'use strict' + + // Load the sw-toolbox library. + importScripts('/components/sw-toolbox/sw-toolbox.js') + + // Ensure that our service worker takes control of the page as soon as possible. + global.addEventListener('install', event => event.waitUntil(global.skipWaiting())) + global.addEventListener('activate', event => event.waitUntil(global.clients.claim())) + + toolbox.precache([ + '/', + '/hello', + '/untisinfo.css', + '/javascripts/bundle.js', + '/stylesheets/style.css', + '/stylesheets/hello.css' + ]) + + toolbox.router.get('/', toolbox.fastest) + toolbox.router.get('/hello', toolbox.fastest) + + toolbox.router.get('/javascripts/bundle.js', toolbox.fastest) + toolbox.router.get('/stylesheets/*', toolbox.fastest) + toolbox.router.get('/untisinfo.css', toolbox.fastest) + toolbox.router.get('/meetingpointProxy/*', toolbox.networkFirst) +})(self) diff --git a/src/client/static/untisinfo.css b/src/client/static/untisinfo.css new file mode 100644 index 0000000..d74a7aa --- /dev/null +++ b/src/client/static/untisinfo.css @@ -0,0 +1,11 @@ +html, body { + overflow: auto; + width: 100vw; + height: 100vh; + margin: 0; + -webkit-overflow-scrolling: touch; +} + +center { + margin: 5px; +} diff --git a/src/client/views/error.jade b/src/client/views/error.jade new file mode 100644 index 0000000..51ec12c --- /dev/null +++ b/src/client/views/error.jade @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/src/client/views/index.jade b/src/client/views/index.jade new file mode 100644 index 0000000..540fd42 --- /dev/null +++ b/src/client/views/index.jade @@ -0,0 +1,51 @@ +extends layout + +block variables + - var bodyStyle = 'opacity: 0;'; + +block head + link(rel='stylesheet', href='/stylesheets/style.css') + link(rel='stylesheet', href='/stylesheets/print.css', media='print') + link(rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Material+Icons') + link(rel='stylesheet', href='/components/material-design-lite/material.min.css') + script(defer='', src='/components/material-design-lite/material.min.js') + +block content + form#search + .top-bar + .title + .logo + .text Rooster + .input-wrapper + input(type='search', placeholder='Zoeken', autocomplete='off') + button.material-icons.fav(tabindex='0', type='button')  + .tooltip + span Voer hier een docentafkorting, klas, leerlingnummer of lokaalnummer in. + button#overflow-button(type='button') + i.material-icons  + + ul.mdl-menu.mdl-menu--bottom-right.mdl-js-menu.mdl-js-ripple-effect(for='overflow-button') + a(href='http://www.meetingpointmco.nl/Roosters-AL/doc/basisroosters/default.htm') + li.mdl-menu__item Basis rooster gebruiken + a(href='http://www.meetingpointmco.nl/Roosters-AL/doc/') + li.mdl-menu__item Oud rooster gebruiken + a(href='javascript:window.print()').print-page + li.mdl-menu__item.mdl-menu__item--full-bleed-divider#print-page Pagina printen + li.mdl-menu__item(disabled) Gemaakt door Noah Loomans + .autocomplete-wrapper + ul.autocomplete + #week-selector + #search-space-filler + .week-wrapper + button(type='button').material-icons  + span.current + span.no-print Loading... + span.print + button(type='button').material-icons  + + #schedule + +block scripts + script. + !{flagsStr}!{usersStr}!{validWeekNumbersStr} + script(src='/bundle.js') diff --git a/src/client/views/layout.jade b/src/client/views/layout.jade new file mode 100644 index 0000000..f7f9e1f --- /dev/null +++ b/src/client/views/layout.jade @@ -0,0 +1,25 @@ +block variables + - var bodyStyle = ''; + +doctype html +html(lang='nl') + head + block head_top + if isBeta + title BETA Metis Rooster + else + title Metis Rooster + meta(name='theme-color',content='#F44336') + meta(name='viewport', content='width=device-width, initial-scale=1') + link(href='https://fonts.googleapis.com/css?family=Roboto', rel='stylesheet') + link(rel='manifest', href='/manifest.webmanifest') + link(rel="apple-touch-icon", sizes="120x120", href="/apple-touch-icon.png") + link(rel="icon", type="image/png", href="/favicon-32x32.png", sizes="32x32") + link(rel="icon", type="image/png", href="/favicon-16x16.png", sizes="16x16") + link(rel="mask-icon", href="/safari-pinned-tab.svg", color="#f44336") + block head + body(style=bodyStyle) + block content + script. + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');ga('create', 'UA-83684754-1', 'auto');ga('send', 'pageview'); + block scripts diff --git a/src/client/views/redirect.jade b/src/client/views/redirect.jade new file mode 100644 index 0000000..e895456 --- /dev/null +++ b/src/client/views/redirect.jade @@ -0,0 +1,47 @@ +extends layout + +block head + style. + body { + font-family: 'Roboto', sans-serif; + text-align: center; + margin: 32px; + } + + .content { + max-width: 600px; + margin: 0 auto; + } + + h1 { + color: #212121; + margin-bottom: 32px; + } + + h2 { + color: #727272; + margin-bottom: 64px; + } + + a, a:visited { + margin: 8px; + padding: 8px 16px; + background-color: #c84127; + color: white; + text-decoration: none; + font-weight: bold; + border-radius: 3px; + } + + a:hover, a:focus, a:active { + background-color: #e45a3f; + } + +block content + .content + script document.body.style.opacity = 1 + img(src='/icons/mml-logo.png') + h1 Er is iets mis gegaan tijdens het ophalen van de benodigde informatie + h2 Je kunt proberen door te gaan naar het oude rooster of Magister + a(href='http://www.meetingpointmco.nl/Roosters-AL/doc/') Oud rooster + a(href='http://msa.magister.net/') Magister -- cgit v1.1