import m from 'mithril'; import { RadioGroup, Switch, Dialog, Button, Tabs, Icon } from 'polythene-mithril'; import { FileInput } from 'amiv-web-ui-components'; import { TabsCSS, ButtonCSS } from 'polythene-css'; // eslint-disable-next-line import/extensions import { apiUrl, ownUrl } from 'networkConfig'; import { colors } from '../style'; import { loadingScreen } from '../layout'; import { icons } from '../views/elements'; import EditView from '../views/editView'; ButtonCSS.addStyle('.nav-button', { color_light_border: 'rgba(0, 0, 0, 0.09)', color_light_disabled_background: 'rgba(0, 0, 0, 0.09)', color_light_disabled_border: 'transparent', }); TabsCSS.addStyle('.edit-tabs', { color_light: '#555555', // no hover effect color_light_hover: '#555555', color_light_selected: colors.amiv_blue, color_light_tab_indicator: colors.amiv_blue, }); 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 if (this.form.data.additional_fields) { const copy = JSON.parse(this.form.data.additional_fields); this.form.data.add_fields_sbb = 'sbb_abo' in copy.properties; this.form.data.add_fields_food = 'food' in copy.properties; this.form.data.additional_fields = null; let i = 0; while (`text${i}` in copy.properties) { this.form.data[`add_fields_text${i}`] = copy.properties[`text${i}`].title; i += 1; } // TODO: find a better solution to keep track of the additional textfields this.add_fields_text_index = i; } else { this.add_fields_text_index = 0; } // price can either not be set or set to null // 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 = 'spots' in this.form.data || 'time_registration_start' in this.form.data; } beforeSubmit() { // Collect images seperate from everything else const images = {}; ['thumbnail', 'banner', 'infoscreen', 'poster'].forEach((key) => { if (this.form.data[`new_${key}`]) { images[`img_${key}`] = this.form.data[`new_${key}`]; delete this.form.data[`new_${key}`]; } if (this.form.data[`img_${key}`] !== undefined) delete this.form.data[`img_${key}`]; }); // Merge Options for additional fields const additionalFields = { $schema: 'http://json-schema.org/draft-04/schema#', additionalProperties: false, title: 'Additional Fields', type: 'object', properties: {}, required: [], }; if (this.form.data.add_fields_sbb) { additionalFields.properties.sbb_abo = { type: 'string', title: 'SBB Abonnement', enum: ['None', 'GA', 'Halbtax', 'Gleis 7'], }; additionalFields.required.push('sbb_abo'); } if (this.form.data.add_fields_food) { additionalFields.properties.food = { type: 'string', title: 'Food', enum: ['Omnivor', 'Vegi', 'Vegan', 'Other'], }; additionalFields.properties.food_special = { type: 'string', title: 'Special Food Requirements', }; additionalFields.required.push('food'); } let i = 0; while (`add_fields_text${i}` in this.form.data) { const fieldName = `text${i}`; additionalFields.properties[fieldName] = { type: 'string', minLength: 1, title: this.form.data[`add_fields_text${i}`], }; additionalFields.required.push(fieldName); delete this.form.data[`add_fields_text${i}`]; i += 1; } if ('add_fields_sbb' in this.form.data) delete this.form.data.add_fields_sbb; if ('add_fields_food' in this.form.data) delete this.form.data.add_fields_food; // if the properties are empty, we null the whole field, otherwise we send a json string // of the additional fields object if (Object.keys(additionalFields.properties).length > 0) { this.form.data.additional_fields = JSON.stringify(additionalFields); } else { this.form.data.additional_fields = null; } // if spots is not set, also remove 'allow_email_signup' if (!('spots' in this.form.data) && 'allow_email_signup' in this.form.data && !this.form.data.allow_email_signup) { delete this.form.data.allow_email_signup; } // Propose <=> Submit desicion due to rights if (this.rightSubmit) { // Submition tool if (Object.keys(images).length > 0) { const imageForm = new FormData(); Object.keys(images).forEach(key => imageForm.append(key, images[key])); imageForm.append('_id', this.form.data._id); imageForm.append('_etag', this.form.data._etag); // first upload the images as formData, then the rest as JSON this.controller.handler.patch(imageForm).then(({ _etag }) => { this.submit({ ...this.form.data, _etag }); }); } else this.submit(this.form.data); } 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() { if (!this.form.schema) return m(loadingScreen); const titles = ['Event Description', 'When and Where?', 'Signups', 'Advertisement']; if (this.rightSubmit) titles.push('Images'); const buttonRight = m(Button, { label: m('div.pe-button__label', m(Icon, { svg: { content: m.trust(icons.ArrowRight) }, style: { top: '-5px', float: 'right' }, }), 'next'), disabled: this.currentpage === titles.length, ink: false, border: true, className: 'nav-button', events: { onclick: () => { this.currentpage = Math.min(this.currentpage + 1, 5); } }, }); const buttonLeft = m(Button, { label: m('div.pe-button__label', m(Icon, { svg: { content: m.trust(icons.ArrowLeft) }, style: { top: '-5px', float: 'left' }, }), 'previous'), disabled: this.currentpage === 1, ink: false, border: true, className: 'nav-button', events: { onclick: () => { this.currentpage = Math.max(1, this.currentpage - 1); } }, }); const radioButtonSelectionMode = m(RadioGroup, { name: 'Selection Mode', buttons: [ { value: 'fcfs', label: 'First come, first serve', }, { value: 'manual', label: 'Selection made by organizer', }, ], onChange: (state) => { this.selection_strategy = state.value; this.form.data.selection_strategy = state.value; }, value: this.selection_strategy, }); 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)); const addFieldsText = []; let i = 0; while (`add_fields_text${i}` in this.form.data) { addFieldsText.push(this.form._renderField(`add_fields_text${i}`, { type: 'string', label: `Label for Textfield ${i}`, })); const fieldIndex = i; addFieldsText.push(m(Button, { label: `Remove Textfield ${i}`, className: 'red-row-button', events: { onclick: () => { let index = fieldIndex; while (`add_fields_text${index + 1}` in this.form.data) { this.form.data[`add_fields_text${index}`] = this.form.data[`add_fields_text${index + 1}`]; index += 1; } delete this.form.data[`add_fields_text${index}`]; this.add_fields_text_index = index; }, }, })); i += 1; } // checks currentPage and selects the fitting page return this.layout([ // navigation bar // all pages are displayed, current is highlighted, // validation errors are shown per page by red icon-background m(Tabs, { className: 'edit-tabs', // it would be too easy if we could set the background color in the theme class style: { backgroundColor: colors.orange }, onChange: ({ index }) => { this.currentpage = index + 1; }, centered: true, selectedTabIndex: this.currentpage - 1, }, [...titles.entries()].map((numAndTitle) => { const buttonAttrs = { label: numAndTitle[1] }; if (errorPages[numAndTitle[0]]) { // in case of an error, put an error icon before the tab label buttonAttrs.label = m('div', m(Icon, { svg: { content: m.trust(icons.error) }, style: { top: '-2px', 'margin-right': '4px' }, }), numAndTitle[1]); } return buttonAttrs; })), m('div.maincontainer', { style: { height: 'calc(100vh - 180px)', 'overflow-y': 'auto' } }, [ // page 1: title & description m('div', { style: { display: (this.currentpage === 1) ? 'block' : 'none' }, }, [ ...this.form.renderSchema(['title_en', 'catchphrase_en']), this.form._renderField('description_en', { type: 'string', label: 'English Description', multiLine: true, rows: 5, }), ...this.form.renderSchema(['title_de', 'catchphrase_de']), this.form._renderField('description_de', { type: 'string', label: 'German Description', multiLine: true, rows: 5, }), ]), // page 2: when & where m('div', { style: { display: (this.currentpage === 2) ? 'block' : 'none' }, }, this.form.renderSchema(['time_start', 'time_end', 'location'])), // page 3: registration m('div', { style: { display: (this.currentpage === 3) ? 'block' : 'none' }, }, [ m(Switch, { label: 'people have to pay something to attend this event', style: { 'margin-bottom': '5px' }, checked: this.hasprice, onChange: ({ checked }) => { this.hasprice = checked; if (!checked) { // if it originally had a price, set to null, otherwise delete if (this.controller.data.price) this.form.data.price = null; else delete this.form.data.price; } }, }), this.hasprice && this.form._renderField('price', { label: 'Price', type: 'number' }), m('br'), m(Switch, { label: 'people have to register to attend this event', checked: this.hasregistration, onChange: ({ checked }) => { this.hasregistration = checked; if (!checked) { delete this.form.data.spots; delete this.form.data.time_register_start; delete this.form.data.time_register_end; delete this.form.data.add_fields_sbb; delete this.form.data.add_fields_food; delete this.form.data.allow_email_signup; delete this.form.data.selection_strategy; } }, }), ...this.hasregistration && this.form.renderSchema([ 'spots', 'time_register_start', 'time_register_end']), this.hasregistration && this.form._renderField('add_fields_food', { type: 'boolean', label: 'Food Limitations', }), this.hasregistration && this.form._renderField('add_fields_sbb', { type: 'boolean', label: 'SBB Abonnement', }), m('br'), ...this.hasregistration && addFieldsText, m('br'), this.hasregistration && m(Button, { label: 'Additional Textfield', className: 'blue-button', border: true, events: { onclick: () => { this.form.data[`add_fields_text${this.add_fields_text_index}`] = ''; this.add_fields_text_index += 1; }, }, }), m('br'), ...this.hasregistration && this.form.renderSchema(['allow_email_signup']), this.hasregistration && radioButtonSelectionMode, ]), // page 4: advertisement m('div', { style: { display: (this.currentpage === 4) ? 'block' : 'none' }, }, [ ...this.form.renderSchema(['time_advertising_start', 'time_advertising_end']), // TODO is deactivated now /* m.trust('Priority<br>'), m(Slider, { min: 1, max: 10, stepSize: 1, // value: this.data.priority || 1, // onChange: ({ value }) => { this.data.priority = value; }, }), */ ...this.form.renderSchema(['show_website', 'show_announce', 'show_infoscreen']), ]), // page 5: images m('div', { style: { display: (this.currentpage === 5) ? 'block' : 'none' }, }, [ ['thumbnail', 'banner', 'poster', 'infoscreen'].map(key => [ this.form.data[`img_${key}`] ? m('img', { src: `${apiUrl}${this.form.data[`img_${key}`].file}`, style: { 'max-height': '50px', 'max-width': '100px' }, }) : m('div', `currently no ${key} image set`), m(FileInput, this.form.bind({ name: `new_${key}`, label: `New ${key} Image`, accept: 'image/png, image/jpeg', })), ]), ]), // bottom back & forth m('div', { style: { display: 'flex', 'justify-content': 'space-between', padding: '35px', 'padding-top': '20px', }, }, [buttonLeft, buttonRight]), ]), ], this.rightSubmit ? 'submit' : 'propose', false); } }