Commit acf359e5 authored by rdoerge's avatar rdoerge
Browse files

Merge branch 'master' into feature/imagelist

parents bf9484a4 d29d44e1
stages:
- test
- build
- deploy
eslint:
stage: test
image: node:latest
before_script:
- npm install
script:
- npm run lint
build_master_dev:
stage: build
image: docker:latest
......
......@@ -7,7 +7,7 @@
"start": "webpack-dev-server --hot --inline",
"build": "webpack -p --config webpack.config.prod.js",
"build-dev": "webpack -p --config webpack.config.dev.js",
"lint": "eslint src/**"
"lint": "eslint src/**.js"
},
"repository": {
"type": "git",
......@@ -18,7 +18,7 @@
"@material/drawer": "^0.30.0",
"@material/select": "^0.35.1",
"ajv": "^5.5.0",
"amiv-web-ui-components": "git+https://git@gitlab.ethz.ch/amiv/web-ui-components.git",
"amiv-web-ui-components": "git+https://git@gitlab.ethz.ch/amiv/web-ui-components.git#61e129378f57e98d3f60106298251c3b0f4b2286",
"axios": "^0.17.1",
"client-oauth2": "^4.2.0",
"mithril": "^1.1.6",
......
import tool from 'announcetool';
const m = require('mithril');
export default class AnnounceTool {
oncreate() {
if (tool.wasRenderedOnce()) {
// jQuery catches the first document.ready, but afterwards we have to
// trigger a render
tool.render();
}
}
view() {
return m('div', [
m('div#tableset', [
m('p#events'),
m('div#buttonrow', [
m('button#preview.btn.btn-default', 'Preview'),
m('button#reset.btn.btn-default', 'Reset'),
m('button#send.btn.btn-default', 'Send'),
]),
]),
m('br'),
m('hr'),
m('textarea#target'),
]);
}
}
......@@ -2,6 +2,7 @@ import m from 'mithril';
import axios from 'axios';
import ClientOAuth2 from 'client-oauth2';
import { Snackbar } from 'polythene-mithril';
// eslint-disable-next-line import/extensions
import { apiUrl, ownUrl, oAuthID } from 'networkConfig';
import * as localStorage from './localStorage';
import config from './resourceConfig.json';
......@@ -119,6 +120,7 @@ export class ResourceHandler {
*/
constructor(resource, searchKeys = false) {
this.resource = resource;
this.rights = [];
// special case for users
if (resource === 'users') this.searchKeys = ['firstname', 'lastname', 'nethz'];
else this.searchKeys = searchKeys || config[resource].searchKeys;
......@@ -184,6 +186,9 @@ export class ResourceHandler {
Snackbar.show({ title: 'Network error, try again.', style: { color: 'red' } });
}
// in future, we may communicate based on the data available
// therefore, require data already here
// eslint-disable-next-line no-unused-vars
error422(data) {
Snackbar.show({ title: 'Errors in object, please fix.' });
}
......@@ -203,6 +208,7 @@ export class ResourceHandler {
Snackbar.show({ title: response.data, style: { color: 'red' } });
reject();
} else {
this.rights = response.data._links.self.methods;
resolve(response.data);
}
}).catch((e) => {
......@@ -329,11 +335,11 @@ export class ResourceHandler {
export class OauthRedirect {
view() {
oauth.token.getToken(m.route.get()).then((response) => {
oauth.token.getToken(m.route.get()).then((auth) => {
APISession.authenticated = true;
APISession.token = response.accessToken;
localStorage.set('token', response.accessToken);
amivapi.get(`sessions/${response.accessToken}`, {
APISession.token = auth.accessToken;
localStorage.set('token', auth.accessToken);
amivapi.get(`sessions/${auth.accessToken}`, {
headers: { 'Content-Type': 'application/json', Authorization: APISession.token },
}).then((response) => {
console.log(response);
......
import ItemView from './views/itemView';
import EditView from './views/editView';
import { inputGroup, selectGroup, submitButton } from './views/elements';
import TableView from './views/tableView';
import { events as config } from './config.json';
const m = require('mithril');
export class EventView extends ItemView {
constructor() {
super('events');
this.memberships = [];
}
view() {
// do not render anything if there is no data yet
if (!this.data) return m.trust('');
let comissionBadge = m('span.label.label-important', 'Who is resp of this event?');
if (this.data.membership === 'kultur') {
comissionBadge = m('span.label.label-success', 'Kulturi event');
} else if (this.data.membership === 'eestec') {
comissionBadge = m('span.label.label-important', 'EESTEC event');
} else if (this.data.membership === 'limes') {
comissionBadge = m('span.label.label-warning', 'LIMES event');
}
// TODO Question Lio171201:are we missing a "responsible" key?
const detailKeys = [
'title_de',
'rfid',
'location', 'time_start', 'time_end',
'show_website', 'catchphrase',
'time_register_start', 'price', 'allow_email_signup'];
return m('div', [
m('h1', `${this.data.title_de}`),
comissionBadge,
m('table', detailKeys.map(key => m('tr', [
m('td.detail-descriptor', config.keyDescriptors[key]),
m('td', this.data[key] ? this.data[key] : ''),
]))),
m('h2', 'Location'), m('br'),
m(TableView, {
resource: 'events',
keys: ['event.location'],
query: {
where: { user: this.id },
embedded: { group: 1 },
},
}),
m('h2', 'Signups'), m('br'),
m(TableView, {
resource: 'events',
keys: ['event.title_de'],
query: {
where: { user: this.id },
embedded: { event: 1 },
},
}),
]);
}
}
class EventEdit extends EditView {
constructor(vnode) {
super(vnode, 'events');
}
getForm() {
return m('form', [
m('div.row', [
m(inputGroup, this.bind({ title: 'Deutscher Titel', name: 'title_de' })),
m(inputGroup, this.bind({ title: 'English Title', name: 'title_en' })),
m(inputGroup, this.bind({ title: 'Location', name: 'location' })),
// m(inputGroup, this.bind({ title: 'Date-start', name: 'datetimepicker1' })),
// $('#datetimepicker1').datetimepicker();
m(selectGroup, this.bind({
classes: 'col-xs-6',
title: 'May non-AMIV members register?',
name: 'allow_email_signup',
options: [true, false],
})),
m(selectGroup, this.bind({
classes: 'col-xs-6',
title: 'Show on the website?',
name: 'show_website',
options: [true, false],
})),
m(selectGroup, this.bind({
classes: 'col-xs-6',
title: 'Piority from 1 to 10?',
name: 'priority',
// could be done with array.apply:
options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
})),
]),
m('span', JSON.stringify(this.data)),
m('span', JSON.stringify(this.errors)),
]);
}
view() {
// do not render anything if there is no data yet
if (!this.data) return m.trust('');
return m('form', [
this.getForm(),
m(submitButton, {
active: this.valid,
args: {
onclick: this.submit('PATCH', config.patchableKeys),
class: 'btn-warning',
},
text: 'Update',
}),
]);
}
}
export class NewEvent extends EventEdit {
constructor(vnode) {
super(vnode);
this.data = {
title_de: 'Unvollstaendiges Event',
priority: 7,
show_website: false,
};
this.valid = false;
// if the creation is finished, UI should switch to new Event
this.callback = (response) => { m.route.set(`/events/${response.data._id}`); };
}
view() {
return m('form', [
this.getForm(),
m(submitButton, {
active: this.valid,
args: {
onclick: this.submit('POST', config.patchableKeys),
class: 'btn-warning',
},
text: 'Create',
}),
]);
}
}
export class EventModal {
constructor() {
this.edit = false;
}
view() {
if (this.edit) {
return m(EventEdit, { onfinish: () => { this.edit = false; m.redraw(); } });
}
// else
return m('div', [
m('div.btn.btn-default', { onclick: () => { this.edit = true; } }, 'Edit'),
m('br'),
m(EventView),
]);
}
}
import m from 'mithril';
import { RaisedButton, RadioGroup, Switch } from 'polythene-mithril';
import { RaisedButton, RadioGroup, Switch, Dialog, Button } from 'polythene-mithril';
import { fileInput } from 'amiv-web-ui-components';
import { styler } from 'polythene-core-css';
// eslint-disable-next-line import/extensions
import { apiUrl } from 'networkConfig';
import { apiUrl, ownUrl } from 'networkConfig';
import EditView from '../views/editView';
const style = [
{
'.mywrapper': {
......@@ -20,6 +19,18 @@ export default class newEvent extends EditView {
constructor(vnode) {
super(vnode);
this.currentpage = 1;
// check whether the user has the right to create events or can only propose
this.rightSubmit = !m.route.get().startsWith('/proposeevent');
// proposition URL-link decoder
if (this.rightSubmit && m.route.param('proposition')) {
const data = JSON.parse(window.atob(m.route.param('proposition')));
console.log(data);
this.form.data = data;
}
if (!this.form.data.priority) this.form.data.priority = 1;
// read additional_fields to make it editable
......@@ -34,7 +45,7 @@ export default class newEvent extends EditView {
// if it is 0 however, that would mean that there actually is a price that
// you can edit
this.hasprice = 'price' in this.form.data && this.form.data.price !== null;
this.hasregistration = 'time_advertising_start' in this.form.data;
this.hasregistration = 'spots' in this.form.data || 'time_registration_start' in this.form.data;
}
beforeSubmit() {
......@@ -95,7 +106,9 @@ export default class newEvent extends EditView {
delete this.form.data.allow_email_signup;
}
console.log(this.form.data);
// Propose <=> Submit desicion due to rights
if (this.rightSubmit) {
// Submition tool
if (Object.keys(images).length > 0) {
images._id = this.form.data._id;
images._etag = this.form.data._etag;
......@@ -107,12 +120,50 @@ export default class newEvent extends EditView {
} else {
this.submit();
}
} else {
// Propose tool
Dialog.show({
title: 'Congratulations!',
body: [
m(
'div',
'You sucessfuly setup an event.',
'Please send this link to the respectiv board member for validation.',
),
m('input', {
type: 'text',
style: { width: '335px' },
value: `${ownUrl}/newevent?${m.buildQueryString({
proposition: window.btoa(JSON.stringify(this.form.data)),
})}`,
id: 'textId',
}),
],
backdrop: true,
footerButtons: [
m(Button, {
label: 'Copy',
events: {
onclick: () => {
const copyText = document.getElementById('textId');
copyText.select();
document.execCommand('copy');
},
},
}),
],
});
}
}
view() {
const titles = ['Event Description', 'When and Where?', 'Signups', 'Advertisement'];
if (this.rightSubmit) titles.push('Images');
const buttonRight = m(RaisedButton, {
label: 'next',
disabled: this.currentpage === 5,
disabled: this.currentpage === titles.length,
ink: false,
events: {
onclick: () => {
this.currentpage = Math.min(this.currentpage + 1, 5);
......@@ -123,6 +174,7 @@ export default class newEvent extends EditView {
const buttonLeft = m(RaisedButton, {
label: 'previous',
disabled: this.currentpage === 1,
ink: false,
events: {
onclick: () => {
this.currentpage = Math.max(1, this.currentpage - 1);
......@@ -145,22 +197,52 @@ export default class newEvent extends EditView {
onChange: (state) => {
this.selection_strategy = state.value;
this.form.data.selection_strategy = state.value;
console.log(this.form.data); // Temp proof of concept.
},
value: this.selection_strategy,
});
const title = [
'Event Description', 'When and Where?', 'Signups', 'Advertisement', 'Images',
][this.currentpage - 1];
const keysPages = [[
'title_en',
'catchphrase_en',
'description_en',
'title_de',
'catchphrase_de',
'description_de',
],
['time_start', 'time_end', 'location'],
['price', 'spots', 'time_register_start', 'time_register_end'],
['time_advertising_start', 'time_advertising_end'],
[],
];
const errorPages = keysPages.map(keysOfOnePage => keysOfOnePage.map((key) => {
if (this.form.errors && key in this.form.errors) return this.form.errors[key].length > 0;
return false;
}).includes(true));
// checks currentPage and selects the fitting page
return this.layout([
m('h3', title),
buttonLeft,
m.trust('&nbsp;'),
buttonRight,
m('br'),
// navigation bar
// all pages are displayed, current is highlighted,
// validation errors are shown per page by red icon-background
m('div', {
style: { display: 'flex', 'justify-content': 'space-around', 'flex-wrap': 'wrap' },
}, [...titles.entries()].map(numAndTitle => m('div', m('div', {
style: {
border: (this.currentpage === numAndTitle[0] + 1) ?
'2px solid black' :
'2px solid #888888',
color: (this.currentpage === numAndTitle[0] + 1) ? 'black' : '#888888',
'background-color': errorPages[numAndTitle[0]] ? '#ff7a56' : 'white',
'border-radius': '20px',
height: '40px',
'margin-bottom': '7px',
padding: '12px',
'font-size': '20px',
'line-height': '11px',
},
onclick: () => { this.currentpage = numAndTitle[0] + 1; },
}, numAndTitle[1])))),
// page 1: title & description
m('div', {
style: { display: (this.currentpage === 1) ? 'block' : 'none' },
}, this.form.renderPage({
......@@ -181,6 +263,7 @@ export default class newEvent extends EditView {
rows: 5,
},
})),
// page 2: when & where
m('div', {
style: { display: (this.currentpage === 2) ? 'block' : 'none' },
}, this.form.renderPage({
......@@ -188,6 +271,7 @@ export default class newEvent extends EditView {
time_end: { type: 'datetime', label: 'Event End Time' },
location: { type: 'text', label: 'Location' },
})),
// page 3: registration
m('div', {
style: { display: (this.currentpage === 3) ? 'block' : 'none' },
}, [
......@@ -243,6 +327,7 @@ export default class newEvent extends EditView {
}),
this.hasregistration && radioButtonSelectionMode,
]),
// page 4: advertisement
m('div', {
style: { display: (this.currentpage === 4) ? 'block' : 'none' },
}, [
......@@ -259,7 +344,8 @@ export default class newEvent extends EditView {
},
}),
// TODO is deactivated now
/*m.trust('Priority<br>'),
/*
m.trust('Priority<br>'),
m(Slider, {
min: 1,
max: 10,
......@@ -267,7 +353,8 @@ export default class newEvent extends EditView {
// value: this.data.priority || 1,
// onChange: ({ value }) => { this.data.priority = value; },
}),*/
}),
*/
...this.form.renderPage({
show_website: { type: 'checkbox', label: 'Advertise on Website' },
show_announce: { type: 'checkbox', label: 'Advertise in Announce' },
......@@ -277,6 +364,7 @@ export default class newEvent extends EditView {
},
}),
]),
// page 5: images
m('div', {
style: { display: (this.currentpage === 5) ? 'block' : 'none' },
}, [
......@@ -292,6 +380,15 @@ export default class newEvent extends EditView {
})),
]),
]),
]);
// bottom back & forth
m('div', {
style: {
display: 'flex',
'justify-content': 'space-between',
padding: '35px',
'padding-top': '20px',
},
}, [buttonLeft, buttonRight]),
], this.rightSubmit ? 'submit' : 'propose');
}
}
import { styler } from 'polythene-core-css';
import m from 'mithril';
import { Button, RadioGroup, TextField } from 'polythene-mithril';
import EditView from '../views/editView';
const draftStyle = [
{
'.footer': {
position: 'fixed',
left: 0,
bottom: 0,
width: '100%',
'background-color': '#E8462B',
color: '#FFFFFF',
'text-align': 'right',
}
}
]
styler.add('eventDraft', draftStyle);
export default class eventDraft extends EditView {
constructor(vnode) {
super(vnode, 'events');
this.data = {};
}
view(){
const radioButtonSelectionMode = m(RadioGroup, {
name: 'Selection Mode',
buttons: [
{
value: true,
label: 'Yes, create the project.',
},
{
value: false,
label: 'No, deny the request.',
},
],
onChange: ({ value }) => { this.data.create = value; console.log(this.data); }
});
const Comment = m(TextField, {
label: 'Comment',
required: true,
onChange: ({ value }) => this.data.comment = value,
});
// Submit Button
const buttonMaker = m(Button, {
label: 'Submit Request',
color: 'white',
// Error pop-up in case not all mandatory fields were completed
// CURRENTLY: Error triggered onclick
//if(one of the fields isn't )
events: {
onclick: () => alert('You did not complete all prioritary fields!'),
}
});
return m('div', [
m("main", [
m('h2', { class: 'title' }, 'Creating a new event:'),
m('h3', 'Do you wish to create this event?(Admin only): '),
radioButtonSelectionMode,
Comment,
m('div.footer', buttonMaker),
]
)
]
)
}
}
import m from 'mithril';
import {
TextField,
Button,
Card