aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/react/components/container/HelpBox.js4
-rw-r--r--src/client/react/components/container/HelpBox.scss23
-rw-r--r--src/client/react/components/container/Menu.js30
-rw-r--r--src/client/react/components/container/Menu.scss25
-rw-r--r--src/client/react/components/container/Results.js6
-rw-r--r--src/client/react/components/container/Results.scss5
-rw-r--r--src/client/react/components/container/RoomFinder.js12
-rw-r--r--src/client/react/components/container/RoomFinder.scss23
-rw-r--r--src/client/react/components/container/Search.js12
-rw-r--r--src/client/react/components/container/Search.scss42
-rw-r--r--src/client/react/components/container/WeekSelector.js4
-rw-r--r--src/client/react/components/container/WeekSelector.scss40
-rw-r--r--src/client/react/components/page/Index.js4
-rw-r--r--src/client/react/components/page/Index.scss33
-rw-r--r--src/client/react/components/page/User.js12
-rw-r--r--src/client/react/components/page/User.scss24
-rw-r--r--src/client/react/components/presentational/Loading.js2
-rw-r--r--src/client/react/components/presentational/Loading.scss5
-rw-r--r--src/client/react/components/presentational/Result.js15
-rw-r--r--src/client/react/components/presentational/Result.scss30
-rw-r--r--src/client/react/index.js21
-rw-r--r--src/client/react/index.scss12
-rw-r--r--src/client/static/browserconfig.xml2
-rw-r--r--src/client/static/icons/apple-touch-icon.png (renamed from src/client/static/apple-touch-icon.png)bin6447 -> 6447 bytes
-rw-r--r--src/client/static/icons/favicon-16x16.png (renamed from src/client/static/favicon-16x16.png)bin1293 -> 1293 bytes
-rw-r--r--src/client/static/icons/favicon-32x32.png (renamed from src/client/static/favicon-32x32.png)bin2103 -> 2103 bytes
-rw-r--r--src/client/static/icons/mstile-150x150.png (renamed from src/client/static/mstile-150x150.png)bin3995 -> 3995 bytes
-rw-r--r--src/client/static/icons/safari-pinned-tab.svg (renamed from src/client/static/safari-pinned-tab.svg)0
-rw-r--r--src/client/static/manifest.beta.webmanifest29
-rw-r--r--src/client/static/manifest.json (renamed from src/client/static/manifest.webmanifest)0
-rw-r--r--src/client/static/sw.js29
-rw-r--r--src/client/static/untisinfo.css11
-rw-r--r--src/client/views/index.handlebars1
-rw-r--r--src/client/views/partials/head.handlebars10
-rw-r--r--src/server/lib/schools/hetmml/parseSchedule.js100
-rw-r--r--src/server/routes/getSchedule.js88
36 files changed, 495 insertions, 159 deletions
diff --git a/src/client/react/components/container/HelpBox.js b/src/client/react/components/container/HelpBox.js
index 31624db..8b53382 100644
--- a/src/client/react/components/container/HelpBox.js
+++ b/src/client/react/components/container/HelpBox.js
@@ -22,6 +22,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
+import './HelpBox.scss';
+
class HelpBox extends React.Component {
static propTypes = {
// redux
@@ -35,7 +37,7 @@ class HelpBox extends React.Component {
}
return (
- <div className="help-box">
+ <div className="HelpBox">
<div className="arrow" />
<div className="bubble">
Voer hier een docentafkorting, klas, leerlingnummer of lokaalnummer in.
diff --git a/src/client/react/components/container/HelpBox.scss b/src/client/react/components/container/HelpBox.scss
new file mode 100644
index 0000000..f91eb77
--- /dev/null
+++ b/src/client/react/components/container/HelpBox.scss
@@ -0,0 +1,23 @@
+.HelpBox {
+ position: relative;
+ margin-top: 32px;
+
+ .arrow {
+ position: absolute;
+ background-color: white;
+ width: 32px;
+ height: 32px;
+ top: -16px;
+ left: 48px;
+ transform: rotate(45deg);
+ }
+
+ .bubble {
+ position: relative;
+ background-color: white;
+ font-weight: bold;
+ margin: 16px;
+ padding: 16px;
+ border-radius: 4px;
+ }
+}
diff --git a/src/client/react/components/container/Menu.js b/src/client/react/components/container/Menu.js
index 920973b..0b5c4dc 100644
--- a/src/client/react/components/container/Menu.js
+++ b/src/client/react/components/container/Menu.js
@@ -28,6 +28,8 @@ import { Icon } from 'rmwc/Icon';
import users from '../../users';
import { setUser, userFromMatch } from '../../lib/url';
+import './Menu.scss';
+
class Menu extends React.Component {
static propTypes = {
// redux
@@ -59,19 +61,21 @@ class Menu extends React.Component {
render() {
return (
- <SimpleMenu
- handle={<Button><ButtonIcon use="more_vert" /></Button>}
- onSelected={(event) => {
- // Send the `data-type` of the selected <MenuItem />
- this.onItemSelected(event.detail.item.dataset.type);
- }}
- >
- <MenuItem data-type="add_label"><Icon use="bookmark_border" />Voeg label toe</MenuItem>
- <MenuItem data-type="make_favorite"><Icon use="star_border" />Maak favoriet</MenuItem>
- <div className="mdc-list-divider" role="separator" />
- <MenuItem data-type="room_finder"><Icon use="location_searching" />Lokaal zoeken</MenuItem>
- <MenuItem data-type="use_legacy_schedule"><Icon use="launch" />Oud rooster gebruiken</MenuItem>
- </SimpleMenu>
+ <div className="Menu">
+ <SimpleMenu
+ handle={<Button><ButtonIcon use="more_vert" /></Button>}
+ onSelected={(event) => {
+ // Send the `data-type` of the selected <MenuItem />
+ this.onItemSelected(event.detail.item.dataset.type);
+ }}
+ >
+ <MenuItem data-type="add_label"><Icon use="bookmark_border" />Voeg label toe</MenuItem>
+ <MenuItem data-type="make_favorite"><Icon use="star_border" />Maak favoriet</MenuItem>
+ <div className="mdc-list-divider" role="separator" />
+ <MenuItem data-type="room_finder"><Icon use="location_searching" />Lokaal zoeken</MenuItem>
+ <MenuItem data-type="use_legacy_schedule"><Icon use="launch" />Oud rooster gebruiken</MenuItem>
+ </SimpleMenu>
+ </div>
);
}
}
diff --git a/src/client/react/components/container/Menu.scss b/src/client/react/components/container/Menu.scss
new file mode 100644
index 0000000..3d84507
--- /dev/null
+++ b/src/client/react/components/container/Menu.scss
@@ -0,0 +1,25 @@
+.Menu {
+ .mdc-menu-anchor {
+ height: 100%;
+ }
+
+ .mdc-button {
+ height: 100%;
+ min-width: unset;
+ color: black;
+
+ &::before, &::after {
+ background-color: black;
+ }
+
+ i {
+ font-size: 24px;
+ }
+ }
+
+ .mdc-list-item {
+ i {
+ padding-right: 8px;
+ }
+ }
+}
diff --git a/src/client/react/components/container/Results.js b/src/client/react/components/container/Results.js
index 4fc9987..82e37cb 100644
--- a/src/client/react/components/container/Results.js
+++ b/src/client/react/components/container/Results.js
@@ -28,6 +28,8 @@ import users from '../../users';
import { setUser, userFromMatch } from '../../lib/url';
import Result from '../presentational/Result';
+import './Results.scss';
+
class Results extends React.Component {
static propTypes = {
results: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -56,8 +58,8 @@ class Results extends React.Component {
return (
<div
- className={classnames('search__results', {
- 'search__results--has-results': !isExactMatch && this.props.results.length > 0,
+ className={classnames('Results', {
+ hasResults: !isExactMatch && this.props.results.length > 0,
})}
style={{
minHeight: isExactMatch ? 0 : this.props.results.length * 54,
diff --git a/src/client/react/components/container/Results.scss b/src/client/react/components/container/Results.scss
new file mode 100644
index 0000000..60379cf
--- /dev/null
+++ b/src/client/react/components/container/Results.scss
@@ -0,0 +1,5 @@
+.Results {
+ &.hasResults {
+ border-top: 1px #BDBDBD solid;
+ }
+}
diff --git a/src/client/react/components/container/RoomFinder.js b/src/client/react/components/container/RoomFinder.js
index 97ed175..b729ee3 100644
--- a/src/client/react/components/container/RoomFinder.js
+++ b/src/client/react/components/container/RoomFinder.js
@@ -26,6 +26,8 @@ import { Button, ButtonIcon } from 'rmwc/Button';
import users from '../../users';
import { setUser, userFromMatch } from '../../lib/url';
+import './RoomFinder.scss';
+
class RoomFinder extends React.Component {
static propTypes = {
// redux
@@ -38,12 +40,6 @@ class RoomFinder extends React.Component {
history: PropTypes.object.isRequired,
}
- constructor(props) {
- super(props);
-
- this.changeRoom = this.changeRoom.bind(this);
- }
-
componentWillMount() {
const user = userFromMatch(this.props.match);
if (this.props.isVisible && users.byId[user].type !== 'r') {
@@ -81,12 +77,12 @@ class RoomFinder extends React.Component {
}
return (
- <div className="room-finder">
+ <div className="RoomFinder">
<Button onClick={() => this.changeRoom(-1)}>Vorige</Button>
<Button onClick={() => this.changeRoom(+1)}>Volgende</Button>
<div className="grow" />
<Button
- className="close-button"
+ className="closeButton"
onClick={() => this.props.dispatch({ type: 'ROOM_FINDER/HIDE' })}
>
<ButtonIcon use="close" />
diff --git a/src/client/react/components/container/RoomFinder.scss b/src/client/react/components/container/RoomFinder.scss
new file mode 100644
index 0000000..d4b16d9
--- /dev/null
+++ b/src/client/react/components/container/RoomFinder.scss
@@ -0,0 +1,23 @@
+.RoomFinder {
+ display: flex;
+ margin: 8px;
+ padding: 8px;
+ border-radius: 2px;
+ background-color: #D32F2F;
+
+ .mdc-button {
+ color: white;
+
+ &::before, &::after {
+ background-color: white;
+ }
+ }
+
+ .closeButton {
+ min-width: 48px;
+
+ .mdc-button__icon {
+ margin-right: 0;
+ }
+ }
+}
diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js
index c968957..a124b21 100644
--- a/src/client/react/components/container/Search.js
+++ b/src/client/react/components/container/Search.js
@@ -33,6 +33,8 @@ import Menu from './Menu';
import Results from './Results';
import IconFromUserType from '../presentational/IconFromUserType';
+import './Search.scss';
+
class Search extends React.Component {
static propTypes = {
results: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -143,17 +145,17 @@ class Search extends React.Component {
searchText === users.byId[urlUser].value;
return (
- <div className="search">
- <div className={classnames('search-overflow', { 'search--has-focus': hasFocus })}>
- <div className="search__input-wrapper">
- <div className="search__icon-wrapper">
+ <div className="Search">
+ <div className={classnames('overflow', { hasFocus })}>
+ <div className="inputWrapper">
+ <div className="iconWrapper">
<IconFromUserType
userType={isExactMatch ? users.byId[urlUser].type : null}
defaultIcon={<SearchIcon />}
/>
</div>
<input
- id="search__input"
+ id="searchInput"
onChange={event => dispatch({ type: 'SEARCH/INPUT_CHANGE', searchText: event.target.value })}
onKeyDown={this.onKeyDown}
value={searchText}
diff --git a/src/client/react/components/container/Search.scss b/src/client/react/components/container/Search.scss
new file mode 100644
index 0000000..ef629c2
--- /dev/null
+++ b/src/client/react/components/container/Search.scss
@@ -0,0 +1,42 @@
+.Search {
+ height: 54px;
+ position: relative;
+
+ .overflow {
+ border-radius: 2px;
+ background-color: white;
+ position: absolute;
+ width: 100%;
+ box-shadow: 0 2px 2px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08);
+
+ &.hasFocus {
+ box-shadow: 0 3px 8px 0 rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.08);
+ }
+
+ .inputWrapper {
+ display: flex;
+ height: 54px;
+
+ .iconWrapper {
+ height: 54px;
+ padding: 15px;
+
+ svg {
+ height: 24px;
+ width: 24px;
+ }
+ }
+
+ input {
+ border: 0;
+ background-color: transparent;
+ flex-grow: 1;
+ height: inherit;
+ padding: 16px;
+ padding-left: 0px;
+ font-size: 16px;
+ outline: none;
+ }
+ }
+ }
+}
diff --git a/src/client/react/components/container/WeekSelector.js b/src/client/react/components/container/WeekSelector.js
index 9a88af6..a8f5936 100644
--- a/src/client/react/components/container/WeekSelector.js
+++ b/src/client/react/components/container/WeekSelector.js
@@ -29,6 +29,8 @@ import ArrowForwardIcon from 'react-icons/lib/md/arrow-forward';
import purifyWeek from '../../lib/purifyWeek';
import { setWeek, weekFromLocation } from '../../lib/url';
+import './WeekSelector.scss';
+
class WeekSelector extends React.Component {
static propTypes = {
// react-router
@@ -67,7 +69,7 @@ class WeekSelector extends React.Component {
render() {
return (
- <div className="week-selector">
+ <div className="WeekSelector">
<button onClick={() => this.updateWeek(-1)}><ArrowBackIcon /></button>
<div className="text">{this.getWeekText()}</div>
<button onClick={() => this.updateWeek(+1)}><ArrowForwardIcon /></button>
diff --git a/src/client/react/components/container/WeekSelector.scss b/src/client/react/components/container/WeekSelector.scss
new file mode 100644
index 0000000..dd71fea
--- /dev/null
+++ b/src/client/react/components/container/WeekSelector.scss
@@ -0,0 +1,40 @@
+.WeekSelector {
+ display: flex;
+ padding: 8px;
+ padding-bottom: 0;
+ background-color: #F44336;
+ color: white;
+ align-items: center;
+
+ .text {
+ flex-grow: 1;
+ text-align: center;
+ }
+
+ button {
+ background-color: initial;
+ border: initial;
+ color: inherit;
+ padding: 8px;
+ border-radius: 4px;
+
+ svg {
+ font-size: 2em;
+ }
+
+ &:focus {
+ background-color: #D32F2F;
+ outline: none;
+ }
+
+ &:active {
+ background-color: #B81111;
+ outline: none;
+ }
+
+ &::-moz-focus-inner {
+ /* Remove the dotted line outline from Firefox */
+ border: 0;
+ }
+ }
+}
diff --git a/src/client/react/components/page/Index.js b/src/client/react/components/page/Index.js
index ac6c4f6..b2ebbcb 100644
--- a/src/client/react/components/page/Index.js
+++ b/src/client/react/components/page/Index.js
@@ -22,10 +22,12 @@ import React from 'react';
import Search from '../container/Search';
import HelpBox from '../container/HelpBox';
+import './Index.scss';
+
class IndexPage extends React.Component {
render() {
return (
- <div className="page-index">
+ <div className="IndexPage">
<div className="container">
<img src="/icons/mml-logo.png" alt="Metis" />
<Search />
diff --git a/src/client/react/components/page/Index.scss b/src/client/react/components/page/Index.scss
new file mode 100644
index 0000000..f47b36d
--- /dev/null
+++ b/src/client/react/components/page/Index.scss
@@ -0,0 +1,33 @@
+.IndexPage {
+ background-color: #ececec;
+ padding-top: calc(50vh - 310px);
+ height: 100vh;
+
+ .container {
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 8px;
+
+ img {
+ display: block;
+ margin: 64px auto;
+ }
+
+ .Search {
+ z-index: 1; // Position search above help-box
+ }
+ }
+}
+
+
+@media (max-height: 510px) {
+ .IndexPage {
+ padding-top: 0px;
+
+ .container {
+ img {
+ display: none;
+ }
+ }
+ }
+}
diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js
index 00fde7e..6f9373f 100644
--- a/src/client/react/components/page/User.js
+++ b/src/client/react/components/page/User.js
@@ -28,6 +28,8 @@ import { userFromMatch } from '../../lib/url';
import WeekSelector from '../container/WeekSelector';
import RoomFinder from '../container/RoomFinder';
+import './User.scss';
+
class UserPage extends React.Component {
static propTypes = {
// react-router
@@ -43,15 +45,15 @@ class UserPage extends React.Component {
}
return (
- <div className="page-user">
- <div className="search-wrapper">
- <div className="search-container">
+ <div className="UserPage">
+ <div className="searchWrapper">
+ <div className="searchContainer">
<Search />
</div>
</div>
<Elevation z={2}>
- <div className="menu">
- <div className="menu-container">
+ <div className="headerWrapper">
+ <div className="header">
<RoomFinder />
<WeekSelector />
</div>
diff --git a/src/client/react/components/page/User.scss b/src/client/react/components/page/User.scss
new file mode 100644
index 0000000..f56a593
--- /dev/null
+++ b/src/client/react/components/page/User.scss
@@ -0,0 +1,24 @@
+.UserPage {
+ .searchWrapper {
+ position: fixed;
+ z-index: 1; // Position the search bar abore the schedule
+ width: 100%;
+
+ .searchContainer {
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 8px;
+ }
+ }
+
+ .headerWrapper {
+ background-color: #F44336;
+ padding-top: 54px;
+
+ .header {
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 8px;
+ }
+ }
+}
diff --git a/src/client/react/components/presentational/Loading.js b/src/client/react/components/presentational/Loading.js
index e33720b..ce8f49a 100644
--- a/src/client/react/components/presentational/Loading.js
+++ b/src/client/react/components/presentational/Loading.js
@@ -24,7 +24,7 @@ import { LinearProgress } from 'rmwc/LinearProgress';
class Loading extends React.Component {
render() {
return (
- <div className="loading">
+ <div className="Loading">
<LinearProgress determinate={false} />
</div>
);
diff --git a/src/client/react/components/presentational/Loading.scss b/src/client/react/components/presentational/Loading.scss
new file mode 100644
index 0000000..5748899
--- /dev/null
+++ b/src/client/react/components/presentational/Loading.scss
@@ -0,0 +1,5 @@
+.Loading {
+ .mdc-linear-progress .mdc-linear-progress__bar-inner {
+ background-color: #9C27B0;
+ }
+}
diff --git a/src/client/react/components/presentational/Result.js b/src/client/react/components/presentational/Result.js
index 18e4eb9..900d3ac 100644
--- a/src/client/react/components/presentational/Result.js
+++ b/src/client/react/components/presentational/Result.js
@@ -25,6 +25,8 @@ import users from '../../users';
import IconFromUserType from './IconFromUserType';
+import './Result.scss';
+
class Result extends React.Component {
static propTypes = {
userId: PropTypes.string.isRequired,
@@ -34,20 +36,21 @@ class Result extends React.Component {
render() {
return (
- // eslint-disable-next-line
+ /* eslint-disable jsx-a11y/click-events-have-key-events */
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
<div
- className={classnames('search__result', {
- 'search__result--selected': this.props.isSelected,
+ className={classnames('Result', {
+ isSelected: this.props.isSelected,
})}
onClick={this.props.onClick}
>
- <div className="search__icon-wrapper">
+ <div className="iconWrapper">
<IconFromUserType userType={users.byId[this.props.userId].type} />
</div>
- <div className="search__result__text">
+ <div className="text">
{users.byId[this.props.userId].value}
{users.byId[this.props.userId].alt &&
- <span className="search__result__text__alt">
+ <span className="alt">
{` ${users.byId[this.props.userId].alt}`}
</span>
}
diff --git a/src/client/react/components/presentational/Result.scss b/src/client/react/components/presentational/Result.scss
new file mode 100644
index 0000000..1ca0dd8
--- /dev/null
+++ b/src/client/react/components/presentational/Result.scss
@@ -0,0 +1,30 @@
+.Result {
+ display: flex;
+ cursor: pointer;
+
+ &:hover, &.isSelected {
+ background-color: lightgray;
+ }
+
+ .iconWrapper {
+ height: 54px;
+ padding: 15px;
+
+ svg {
+ height: 24px;
+ width: 24px;
+ }
+ }
+
+ .text {
+ padding: 15px;
+ padding-left: 0px;
+ font-size: 16px;
+ transform: translate(0, 3px);
+
+ .alt {
+ font-style: italic;
+ color: gray;
+ }
+ }
+}
diff --git a/src/client/react/index.js b/src/client/react/index.js
index 566c847..2a66f82 100644
--- a/src/client/react/index.js
+++ b/src/client/react/index.js
@@ -18,13 +18,14 @@
*
*/
+import 'babel-polyfill';
+import 'whatwg-fetch';
import React from 'react';
import ReactDOM from 'react-dom';
import moment from 'moment';
-import { createStore, applyMiddleware, compose } from 'redux';
+import { createStore } from 'redux';
import { Provider } from 'react-redux';
-import logger from 'redux-logger';
import {
BrowserRouter as Router,
@@ -37,14 +38,20 @@ import reducer from './reducers';
import Index from './components/page/Index';
import User from './components/page/User';
+import './index.scss';
+
+// Set the locale for moment.js to dutch. This ensures that the correct week
+// number logic is used.
moment.locale('nl');
-// eslint-disable-next-line no-underscore-dangle
-const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+/* eslint-disable no-underscore-dangle */
const store = createStore(
reducer,
- composeEnhancers(applyMiddleware(logger)),
+ // Redux devtools extension
+ // https://github.com/zalmoxisus/redux-devtools-extension
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
);
+/* eslint-enable no-underscore-dangle */
ReactDOM.render(
<Provider store={store}>
@@ -56,9 +63,9 @@ ReactDOM.render(
</Switch>
</Router>
</Provider>,
- document.getElementById('root'),
+ document.querySelector('#root'),
);
// We only want to focus the input on page load. NOT on a in-javascript
// redirect. This is because that is when people usually want to start typing.
-document.querySelector('.search input').focus();
+document.querySelector('#searchInput').focus();
diff --git a/src/client/react/index.scss b/src/client/react/index.scss
new file mode 100644
index 0000000..4042d06
--- /dev/null
+++ b/src/client/react/index.scss
@@ -0,0 +1,12 @@
+* {
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Roboto';
+ margin: 0;
+}
+
+:global(.grow) {
+ flex-grow: 1;
+}
diff --git a/src/client/static/browserconfig.xml b/src/client/static/browserconfig.xml
index b3930d0..6a1d00e 100644
--- a/src/client/static/browserconfig.xml
+++ b/src/client/static/browserconfig.xml
@@ -2,7 +2,7 @@
<browserconfig>
<msapplication>
<tile>
- <square150x150logo src="/mstile-150x150.png"/>
+ <square150x150logo src="/icons/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
diff --git a/src/client/static/apple-touch-icon.png b/src/client/static/icons/apple-touch-icon.png
index 5adfc69..5adfc69 100644
--- a/src/client/static/apple-touch-icon.png
+++ b/src/client/static/icons/apple-touch-icon.png
Binary files differ
diff --git a/src/client/static/favicon-16x16.png b/src/client/static/icons/favicon-16x16.png
index 1df47d3..1df47d3 100644
--- a/src/client/static/favicon-16x16.png
+++ b/src/client/static/icons/favicon-16x16.png
Binary files differ
diff --git a/src/client/static/favicon-32x32.png b/src/client/static/icons/favicon-32x32.png
index 36cd5da..36cd5da 100644
--- a/src/client/static/favicon-32x32.png
+++ b/src/client/static/icons/favicon-32x32.png
Binary files differ
diff --git a/src/client/static/mstile-150x150.png b/src/client/static/icons/mstile-150x150.png
index 5e381e6..5e381e6 100644
--- a/src/client/static/mstile-150x150.png
+++ b/src/client/static/icons/mstile-150x150.png
Binary files differ
diff --git a/src/client/static/safari-pinned-tab.svg b/src/client/static/icons/safari-pinned-tab.svg
index 97ce8bf..97ce8bf 100644
--- a/src/client/static/safari-pinned-tab.svg
+++ b/src/client/static/icons/safari-pinned-tab.svg
diff --git a/src/client/static/manifest.beta.webmanifest b/src/client/static/manifest.beta.webmanifest
deleted file mode 100644
index a1fdd92..0000000
--- a/src/client/static/manifest.beta.webmanifest
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "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.json
index d33ee8e..d33ee8e 100644
--- a/src/client/static/manifest.webmanifest
+++ b/src/client/static/manifest.json
diff --git a/src/client/static/sw.js b/src/client/static/sw.js
deleted file mode 100644
index bd43805..0000000
--- a/src/client/static/sw.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/* 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
deleted file mode 100644
index d74a7aa..0000000
--- a/src/client/static/untisinfo.css
+++ /dev/null
@@ -1,11 +0,0 @@
-html, body {
- overflow: auto;
- width: 100vw;
- height: 100vh;
- margin: 0;
- -webkit-overflow-scrolling: touch;
-}
-
-center {
- margin: 5px;
-}
diff --git a/src/client/views/index.handlebars b/src/client/views/index.handlebars
index be6a075..5105439 100644
--- a/src/client/views/index.handlebars
+++ b/src/client/views/index.handlebars
@@ -24,7 +24,6 @@
<title>Metis Rooster</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
- <link rel="stylesheet" href="/bundle.css">
</head>
<body>
<div id="root"></div>
diff --git a/src/client/views/partials/head.handlebars b/src/client/views/partials/head.handlebars
index 9801a95..9cf4023 100644
--- a/src/client/views/partials/head.handlebars
+++ b/src/client/views/partials/head.handlebars
@@ -23,8 +23,8 @@
<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">
+<link rel="manifest" href="/manifest.json">
+<link rel="apple-touch-icon" sizes="120x120" href="/icons/apple-touch-icon.png">
+<link rel="icon" type="image/png" href="/icons/favicon-32x32.png" sizes="32x32">
+<link rel="icon" type="image/png" href="/icons/favicon-16x16.png" sizes="16x16">
+<link rel="mask-icon" href="/icons/safari-pinned-tab.svg" color="#f44336">
diff --git a/src/server/lib/schools/hetmml/parseSchedule.js b/src/server/lib/schools/hetmml/parseSchedule.js
new file mode 100644
index 0000000..14e861e
--- /dev/null
+++ b/src/server/lib/schools/hetmml/parseSchedule.js
@@ -0,0 +1,100 @@
+const { JSDOM } = require('jsdom');
+
+function fixFirstLargeScheduleItem(trNodeList) {
+ return Array.from(trNodeList).some((trNode, timeOfDay) => {
+ const tdNodeList = trNode.children;
+
+ return Array.from(tdNodeList).some((tdNode, dayOfWeek) => {
+ const height = tdNode.rowSpan / 2;
+ if (height === 1) {
+ return false;
+ }
+
+ tdNode.rowSpan = 2; // eslint-disable-line no-param-reassign
+
+ for (let i = 1; i < height; i += 1) {
+ // Are we at the end of the table?
+ if (dayOfWeek === 4) {
+ // If so, we cannot use insertBefore, because the is no node to insert
+ // it before. Use appendChild instead.
+ trNodeList[timeOfDay + i].appendChild(tdNode.cloneNode(true));
+ } else {
+ trNodeList[timeOfDay + i]
+ .insertBefore(
+ tdNode.cloneNode(true),
+ trNodeList[timeOfDay + i].children[dayOfWeek],
+ );
+ }
+ }
+
+ return true;
+ });
+ });
+}
+
+function parseSchedule(axiosResponse) {
+ const dom = new JSDOM(axiosResponse.data);
+ const { window } = dom;
+ const { document } = window;
+
+ const tableNode = document.querySelector('center > table');
+ const tbodyNode = tableNode.querySelector('tbody');
+ const trNodeList = tbodyNode.children;
+
+ Array.from(trNodeList).forEach((trNode, timeOfDay) => {
+ const tdNodeList = trNode.children;
+
+ if (timeOfDay === 0 || trNode.children.length === 0) {
+ tbodyNode.removeChild(trNode);
+ return;
+ }
+
+ Array.from(tdNodeList).forEach((tdNode, dayOfWeek) => {
+ if (dayOfWeek === 0) {
+ trNode.removeChild(tdNode);
+ }
+ });
+ });
+
+ let shouldContinue = true;
+ while (shouldContinue) {
+ shouldContinue = fixFirstLargeScheduleItem(trNodeList);
+ }
+
+ const scheduleItems = [];
+
+ Array.from(trNodeList).forEach((trNode, timeOfDay) => {
+ const tdNodeList = trNode.children;
+ Array.from(tdNodeList).forEach((tdNode, dayOfWeek) => {
+ if (tdNode.textContent.trim() === '') {
+ return;
+ }
+
+ const childTableNode = tdNode.querySelector('table');
+ const childTrNodeList = childTableNode.querySelectorAll('tr');
+
+ Array.from(childTrNodeList).forEach((childTrNode) => {
+ const subject = childTrNode.children[0].textContent.trim();
+ const attendees = childTrNode.children[1]
+ ? childTrNode.children[1].textContent.trim()
+ : undefined;
+ const location = childTrNode.children[2]
+ ? childTrNode.children[2].textContent.trim()
+ : undefined;
+
+ scheduleItems.push({
+ startTime: timeOfDay,
+ endTime: timeOfDay + 1,
+ dayOfWeek,
+ subject,
+ attendees,
+ location,
+ });
+ });
+ });
+ });
+
+ return scheduleItems;
+}
+
+module.exports = parseSchedule;
diff --git a/src/server/routes/getSchedule.js b/src/server/routes/getSchedule.js
index 04df28a..7a84dd2 100644
--- a/src/server/routes/getSchedule.js
+++ b/src/server/routes/getSchedule.js
@@ -25,11 +25,14 @@ const router = express.Router();
const getScheduleData = require('../lib/schools/hetmml/getScheduleData');
const getURLOfUser = require('../lib/schools/hetmml/getURLOfUser');
const axios = require('../lib/schools/hetmml/axios');
+const parseSchedule = require('../lib/schools/hetmml/parseSchedule');
// 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.
-function getWeekNumber(target) {
+function currentWeekNumber() {
+ const target = new Date();
+
const dayNr = (target.getDay() + 6) % 7;
// eslint-disable-next-line
target.setDate(target.getDate() - dayNr + 3);
@@ -43,39 +46,58 @@ function getWeekNumber(target) {
return 1 + Math.ceil((firstThursday - target) / 604800000);
}
+async function getSchedule(userType, userValue, week, scheduleType = 'dag') {
+ const { users } = await getScheduleData();
+ const user =
+ users.filter(user_ => user_.type === userType && user_.value === userValue)[0];
+
+ if (!user) {
+ throw new Error(`${userType}/${userValue} is not in the user index.`);
+ }
+
+ if (!week) {
+ week = currentWeekNumber(); // eslint-disable-line no-param-reassign
+ }
+
+ const { index } = user;
+ const url = getURLOfUser(scheduleType, userType, index, week);
+
+ return axios.get(url);
+}
+
+router.get('/:type/:value.json', (req, res, next) => {
+ const { type, value } = req.params;
+ const { week, type: scheduleType } = req.query;
+
+ getSchedule(type, value, week, scheduleType)
+ .then((response) => {
+ const schedule = parseSchedule(response);
+ res.json(schedule);
+ })
+ .catch((err) => {
+ if (err.response) {
+ // eslint-disable-next-line no-param-reassign
+ err.status = err.response.status;
+ }
+ next(err);
+ });
+});
+
router.get('/:type/:value', (req, res, next) => {
- getScheduleData().then(({ users }) => {
- const { type, value } = req.params;
- let { week } = req.query;
- const user =
- users.filter(user_ => user_.type === type && user_.value === value)[0];
-
- if (!user) {
- next(new Error(`${type}/${value} is not in the user index.`));
- }
-
- if (!week) {
- week = getWeekNumber(new Date());
- }
-
- const { index } = user;
-
- const scheduleType = req.query.type || 'dag';
-
- const url = getURLOfUser(scheduleType, type, index, week);
-
- axios.get(url)
- .then((response) => {
- res.status(response.status).end(response.data);
- })
- .catch((err) => {
- if (err.response) {
- // eslint-disable-next-line no-param-reassign
- err.status = err.response.status;
- }
- next(err);
- });
- });
+ const { type, value } = req.params;
+ const { week, type: scheduleType } = req.query;
+
+ getSchedule(type, value, week, scheduleType)
+ .then((response) => {
+ res.status(response.status).end(response.data);
+ })
+ .catch((err) => {
+ if (err.response) {
+ // eslint-disable-next-line no-param-reassign
+ err.status = err.response.status;
+ }
+ next(err);
+ });
});
module.exports = router;