Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement minimal routes #11

Merged
merged 26 commits into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
591e98b
README fix meteor badge link
jankapunkt Mar 21, 2020
48e4070
tests/api/Routes added
jankapunkt Mar 21, 2020
49d8b45
api routes minimal Routes definitions added
jankapunkt Mar 21, 2020
f914c1e
ui login page added
jankapunkt Mar 21, 2020
603f2d0
ui logout page added
jankapunkt Mar 21, 2020
2c65e2d
ui myClasses page added
jankapunkt Mar 21, 2020
1844801
ui class page added
jankapunkt Mar 21, 2020
41fda7f
ui user page added
jankapunkt Mar 21, 2020
ac6e20d
i18n routes translation added
jankapunkt Mar 21, 2020
f50bda5
packages leaonline:factories added
jankapunkt Mar 23, 2020
e97e0de
settings oauth config added
jankapunkt Mar 23, 2020
9b9b70b
server startup oauth ServiceConfiguration implemented
jankapunkt Mar 23, 2020
acb98d4
server main include startup/server/accounts
jankapunkt Mar 23, 2020
3e4d54d
ui login page implemented oauth workflow
jankapunkt Mar 23, 2020
4b46bba
api routing implement createRedirect to create redirects and separate…
jankapunkt Mar 23, 2020
e8411cd
api Routes login onSuccess callback added
jankapunkt Mar 23, 2020
a667680
ui login page onSuccess callback added
jankapunkt Mar 23, 2020
350f5db
standard lint fix
jankapunkt Mar 23, 2020
ef73289
README added accounts server to install guide
jankapunkt Mar 23, 2020
31be027
ui components proxy Template added
jankapunkt Mar 23, 2020
2e8d9e6
ui components loading Template title added
jankapunkt Mar 23, 2020
0d0e8b0
api Routes.logout onSuccess handler added
jankapunkt Mar 23, 2020
3d34bc5
ui logout page added
jankapunkt Mar 23, 2020
be77e92
ui basic layout implemented
jankapunkt Mar 23, 2020
d9e6dbc
standard lint fix
jankapunkt Mar 23, 2020
acad14c
tests api Routes updated
jankapunkt Mar 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ shell-server@0.5.0 # Server-side component of the `meteor shell` comm
# leaonline:errors
# leaonline:interfaces
# leaonline:utils
# leaonline:factories
leaonline:factories

#========================================
# Collections
Expand All @@ -45,7 +45,6 @@ accounts-base@1.6.0
accounts-password@1.6.0
alanning:roles
leaonline:accounts-lea
leaonline:ddp-login-handler
service-configuration@1.0.11

#========================================
Expand Down
2 changes: 1 addition & 1 deletion .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jquery@3.0.0
lai:collection-extensions@0.2.1_1
launch-screen@1.2.0
leaonline:accounts-lea@1.0.1
leaonline:ddp-login-handler@1.0.3
leaonline:factories@1.1.0
leaonline:oauth-lea@1.0.2
livedata@1.0.18
lmieulet:meteor-coverage@3.2.0
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This is the code repository for the lea.online teacher dashboard application.

![built with Meteor](https://img.shields.io/badge/Meteor-1.9.2-%23595dff?logo=meteor&logoColor=white&link=https://meteor.com)
[![built with Meteor](https://img.shields.io/badge/Meteor-1.10.1-green?logo=meteor&logoColor=white)](https://meteor.com)
[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
![GitHub](https://img.shields.io/github/license/leaonline/leaonline-teacher)
Expand All @@ -28,15 +28,31 @@ Please note, that Windows is supported by Meteor but not officially supported by
If you get everything running on Windows (dev, tests, build / deployment) feel free to update
the documentation accordingly for Windows users.

In order to get the code and run the app you simply need to clone this repo and run the `./run.sh` script:
### Install accounts server

The lea.online system (which this app is part of) uses a [dedicated OAuth2 server](https://github.com/leaonline/leaonline-accounts)
for authenticating users and authorizing access across applications. If you have not installed this repo from the main
[development repository](https://github.com/leaonline/dev) (recommended), you need to install the accounts server
manually:

```bash
https://github.com/leaonline/leaonline-accounts
cd ./leaonline-accounts
./run.sh
```

### Install teacher app

For the teacher app you need to clone this repo and run the `./run.sh` script:

```bash
git clone git@github.com:leaonline/leaonline-teacher.git
cd ./leaonline-teacher
./run.sh
```

and then open your browser on `localhost:5555` to load the client app.
Finally, open your browser and go to `localhost:5555` to load the client app.
You can login with `admin` / `password` to continue to the teacher dashboard.

## Development and contribution to this project

Expand Down
19 changes: 18 additions & 1 deletion client/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,27 @@
</style>
</body>

<template name="main-render-target">
<!-- the login render target is used only for routes on logged-out users -->

<template name="logged-out-render-target">
<wrapper class="container-fluid d-flex flex-column">
<main class="w-100 main-container flex-fill">
{{> yield}}
</main>
{{> navBottom}}
</wrapper>
</template>

<!-- the login render target is used for logged-in users -->

<template name="main-render-target">
{{> navTop}}
<wrapper class="container-fluid d-flex flex-row p-0">
{{> navSide}}
<main role="main" class="col-md-9 ml-sm-auto col-lg-9 pt-3 px-4 main-container">
<h1>{{routeLabel}}</h1>
{{> yield}}
</main>
</wrapper>
{{> navBottom}}
</template>
2 changes: 2 additions & 0 deletions client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ import '../imports/startup/client/routes'
import '../imports/startup/client/bootstrap'
import '../imports/startup/client/fontawesome'
import '../imports/startup/client/minimalui'

import './main.scss'
import './main.html'
3 changes: 3 additions & 0 deletions client/main.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
wrapper {
min-height: 100vh;
}
5 changes: 5 additions & 0 deletions imports/api/routing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@
<!-- optional markdown-notes-tree directory description starts here -->

<!-- optional markdown-notes-tree directory description ends here -->

The routing API consists of two main components:

* [Router](./Router.js) - controller for navigating and executing Template rendering based on the current route
* [Routes](./Routes.js) - model with static definitions for valid routes and their respective Templates
12 changes: 11 additions & 1 deletion imports/api/routing/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ Router.go = function (value, ...optionalArgs) {
}
}

const routeCache = new ReactiveVar()

Router.cache = function (value) {
if (value) {
routeCache.set(value)
}
return routeCache.get()
}

Router.has = function (path) {
return paths[path]
}
Expand Down Expand Up @@ -142,8 +151,9 @@ function createRoute (routeDef, onError) {
data.params = params
data.queryParams = queryParams

document.title = `${_defaultLabel}${label}`
document.title = `${_defaultLabel} ${label}`
_currentLabel.set(label)
routeCache.set(routeDef)
try {
this.render(routeDef.target || _defaultTarget, routeDef.template, data)
} catch (e) {
Expand Down
221 changes: 211 additions & 10 deletions imports/api/routing/Routes.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,107 @@
import { i18n } from '../i18n/I18n'
import { createNotFoundTrigger } from './triggers'
import { createLoggedinTrigger, createLoginTrigger, createNotFoundTrigger } from './triggers'
import { translateRoute } from './translateRoute'
import { createRedirect } from './createRedirect'

/**
* Routes are static definitions of pages that the {Router} uses to navigate.
*
* A route defines the following properties:
*
* {path}
* Unique (relative) url that separates this route from other route.
* Must be a function, that resolves to a valid (relative) url string.
*
*
* {label}
* The translation id for the human readable name of this route.
* Must be a string.
*
* {triggersEnter}
* A list of functions that run on the router's on-enter trigger.
* Can be undefined or a function that resolves to an array of functions.
*
* {load}
* An async function that loads the corresponding template of the given route.
* Must be an async function.
*
* {template}
* The name of the template as defined in the Template file.
* Must be a string and exactly match the Template's name.
*
* {target}
* Optional. Defines a render target in case a route is defined for a specific area to be drawn
* by the rendering engine.
*
* {data}
* Optional. An object with arbitrary properties that can be passed to the Template and
* will occur in the Template's {instance.data}. Use this to define routing callbacks
* to keep the {Routes} definitions out of the Template files.
*
*
* @class Singleton class-like structure
*/

export const Routes = {}

// triggers for triggersEnter
// use lazy initialization within
// the triggersEnter methods

let rootLoginTrigger
let myClassesTrigger
let notFoundTrigger

// redirects for data callbacks
// use lazy initialization within
// the data callback methods

let toMyClasses

/**
* This route is triggered when the user enters the url without any further suffix.
* Based on the current logged-in state (logged-in, logged-out) the respective trigger
* will invoke a redirect to either the login page or the dashboard page.
*
* Example: http://localhost:3000
* Example: https://mysite.com
*/

Routes.root = {
path: () => '/',
label: 'routes.redirecting',
triggersEnter: () => {
if (!rootLoginTrigger) {
rootLoginTrigger = createLoginTrigger(Routes.login)
}
if (!myClassesTrigger) {
myClassesTrigger = createLoggedinTrigger(Routes.myClasses)
}
return [rootLoginTrigger, myClassesTrigger]
},
async load () {
return import('../../ui/components/loading/loading')
},
target: null,
template: 'loading',
data: null
}

/**
* Renders a default template for all pages that have not been found.
*/

Routes.notFound = {
path: () => {
const notFound = i18n.get('routes.notFound')
const notFound = translateRoute(Routes.notFound)
return `/${notFound}`
},
label: 'pages.notFound.title',
triggersEnter: () => [],
async load () {
return import('../../ui/pages/notfound/notFound')
},
target: null,
target: 'logged-out-render-target',
template: 'notFound',
roles: null,
data: {
next () {
return Routes.overview
Expand All @@ -33,15 +115,134 @@ Routes.notFound = {

Routes.fallback = {
path: () => '*',
label: 'pages.redirecting.title',
triggersEnter: () => [
createNotFoundTrigger(Routes.notFound)
],
label: 'routes.redirecting',
triggersEnter: () => {
if (!notFoundTrigger) notFoundTrigger = createNotFoundTrigger(Routes.notFound)
return [notFoundTrigger]
},
async load () {
return import('../../ui/components/loading/loading')
},
target: null,
template: 'loading',
roles: null,
data: null
}

/**
* The login page for authentication.
*/

Routes.login = {
path: () => {
const path = translateRoute(Routes.login)
return `/${path}`
},
label: 'pages.login.title ',
triggersEnter: () => {
if (!myClassesTrigger) myClassesTrigger = createLoggedinTrigger(Routes.myClasses)
return [myClassesTrigger]
},
async load () {
return import('../../ui/pages/login/login')
},
target: 'logged-out-render-target',
template: 'login',
data: {
onSuccess: () => {
if (!toMyClasses) {
toMyClasses = createRedirect(Routes.myClasses)
}
toMyClasses()
}
}
}

/**
* The logout page for explicit unloading of data und unregistering publications.
*/

Routes.logout = {
path: () => {
const path = translateRoute(Routes.logout)
return `/${path}`
},
label: 'pages.logout.title',
triggersEnter: () => [],
async load () {
return import('../../ui/pages/logout/logout')
},
target: 'logged-out-render-target',
template: 'logout',
data: {
onSuccess: () => createRedirect(Routes.login).call()
}
}

/**
* The main overview page where all classes are listed.
*/

Routes.myClasses = {
path: () => {
const path = translateRoute(Routes.myClasses)
return `/${path}`
},
label: 'pages.myClasses.title',
triggersEnter: () => {
if (!rootLoginTrigger) rootLoginTrigger = createLoginTrigger(Routes.login)
return [rootLoginTrigger]
},
async load () {
return import('../../ui/pages/myclasses/myClasses')
},
target: null,
template: 'myClasses',
data: null
}

/**
* Summary page for a single class.
*/

Routes.class = {
path: (classId = ':classId') => {
const path = translateRoute(Routes.class)
return `/${path}/${classId}`
},
label: 'pages.class.title',
triggersEnter: () => {
if (!rootLoginTrigger) rootLoginTrigger = createLoginTrigger(Routes.login)
return [rootLoginTrigger]
},
async load () {
return import('../../ui/pages/class/class')
},
parent: Routes.myClasses,
target: null,
template: 'class',
data: null
}

/**
* Summary page for a single user
*/

Routes.user = {
path: (classId = ':classId', userId = ':userId') => {
const classPath = translateRoute(Routes.myClasses)
const userPath = translateRoute(Routes.user)
return `/${classPath}/${classId}/${userPath}/${userId}`
},
label: 'pages.user.title',
triggersEnter: () => {
if (!rootLoginTrigger) rootLoginTrigger = createLoginTrigger(Routes.login)
return [rootLoginTrigger]
},
async load () {
return import('../../ui/pages/user/user')
},
parent: Routes.class,
target: null,
template: 'user',
data: null
}
Loading