diff --git a/filadelfia_web/.npmrc b/filadelfia_web/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/filadelfia_web/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/filadelfia_web/app/api.js b/filadelfia_web/app/api.js index b5cb4af..703a6d6 100644 --- a/filadelfia_web/app/api.js +++ b/filadelfia_web/app/api.js @@ -1,53 +1,73 @@ const Authentication = require('./authentication') -exports.sendRequest = function(options, isPagination) { - let token = Authentication.getToken() - let pagination = isPagination - - if (token) { - options.headers = options.headers || {} - options.headers['Authorization'] = 'Bearer ' + token - } - - options.extract = function(xhr) { - if (xhr.responseText && xhr.responseText.slice(0, 9) === '= 300) { + if (out.message) { + throw out + } + console.error('Got error ' + xhr.status + ' but no error message:', out) + throw new Error('Unknown or empty response from server.') + } + return out } - if (xhr.status >= 300) { - throw out - } - return out + + return m.request(options) + .then(function(res) { + api.loading = false + window.requestAnimationFrame(m.redraw.bind(m)) + return res + }) + .catch(function (error) { + api.loading = false + window.requestAnimationFrame(m.redraw.bind(m)) + if (error.status === 403) { + Authentication.clearToken() + m.route.set('/', { redirect: m.route.get() }) + } + if (error.response && error.response.status) { + return Promise.reject(error.response) + } + return Promise.reject(error) + }) } - - return m.request(options) - .catch(function (error) { - if (error.status === 403) { - Authentication.clearToken() - m.route.set('/', { redirect: m.route.get() }) - } - if (error.response && error.response.status) { - return Promise.reject(error.response) - } - return Promise.reject(error) - }) } + +module.exports = api diff --git a/filadelfia_web/app/authentication.js b/filadelfia_web/app/authentication.js index fe501ab..8759dd3 100644 --- a/filadelfia_web/app/authentication.js +++ b/filadelfia_web/app/authentication.js @@ -1,3 +1,4 @@ +const m = require('mithril') const storageName = 'nfp_sites_filadelfia_web_logintoken' const Authentication = { @@ -33,6 +34,16 @@ const Authentication = { getToken: function() { return localStorage.getItem(storageName) }, + + requiresLogin: function() { + if (Authentication.currentUser) return + m.route.set('/') + }, + + requiresNotLogin: function() { + if (!Authentication.currentUser) return + m.route.set('/browse') + }, } Authentication.updateToken(localStorage.getItem(storageName)) diff --git a/filadelfia_web/app/header.js b/filadelfia_web/app/header.js index 088b795..24032a8 100644 --- a/filadelfia_web/app/header.js +++ b/filadelfia_web/app/header.js @@ -23,7 +23,6 @@ const Menu = { }, view: function() { - console.log(Authentication.currentUser) return Authentication.currentUser ? [ m('nav', [ diff --git a/filadelfia_web/app/index.js b/filadelfia_web/app/index.js index 7c82a86..09fc813 100644 --- a/filadelfia_web/app/index.js +++ b/filadelfia_web/app/index.js @@ -3,6 +3,7 @@ const Authentication = require('./authentication') const Header = require('./header') const Login = require('./page_login') const Browse = require('./page_browse') +const Upload = require('./page_upload') window.m = m var fileref = document.createElement("link"); @@ -28,6 +29,7 @@ m.route.prefix = '' const allRoutes = { '/': Login, '/browse': Browse, + '/upload': Upload, } // Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called. diff --git a/filadelfia_web/app/input.js b/filadelfia_web/app/input.js new file mode 100644 index 0000000..de1c01b --- /dev/null +++ b/filadelfia_web/app/input.js @@ -0,0 +1,19 @@ +const m = require('mithril') + +const Input = { + oninit: function(vnode) { + }, + + view: function(vnode) { + return [ + m('label', vnode.attrs.label), + m('input', { + type: vnode.attrs.type || 'text', + value: vnode.attrs.form[vnode.attrs.formKey], + oninput: (e) => { vnode.attrs.form[vnode.attrs.formKey] = e.currentTarget.value }, + }), + ] + }, +} + +module.exports = Input diff --git a/filadelfia_web/app/page_browse.js b/filadelfia_web/app/page_browse.js index 86cbf84..d676d4f 100644 --- a/filadelfia_web/app/page_browse.js +++ b/filadelfia_web/app/page_browse.js @@ -4,15 +4,12 @@ const videos = require('./videos') const Browse = { oninit: function(vnode) { + Authentication.requiresLogin() if (!videos.Tree.length) { this.refreshTree() } }, - oncreate: function() { - if (Authentication.currentUser) return - }, - refreshTree: function(vnode) { videos.refreshTree() }, diff --git a/filadelfia_web/app/page_login.js b/filadelfia_web/app/page_login.js index 67de9e6..62d226f 100644 --- a/filadelfia_web/app/page_login.js +++ b/filadelfia_web/app/page_login.js @@ -1,55 +1,42 @@ const m = require('mithril') const Authentication = require('./authentication') const api = require('./api') +const Input = require('./input') const Login = { oninit: function(vnode) { this.redirect = vnode.attrs.redirect || '' - if (Authentication.currentUser) return m.route.set('/browse') + Authentication.requiresNotLogin() this.error = '' - this.loading = false - this.username = '' - this.password = '' - }, - - oncreate: function() { - if (Authentication.currentUser) return + this.form = { + email: '', + password: '', + } }, loginuser: function(vnode, e) { e.preventDefault() this.error = '' - if (!this.password) this.error = 'Password is missing' - if (!this.username) this.error = 'Email is missing' + if (!this.form.password) this.error = 'Password is missing' + if (!this.form.email) this.error = 'Email is missing' if (this.error) return false - this.loading = true - api.sendRequest({ method: 'POST', url: '/api/authentication/login', - body: { - email: this.username, - password: this.password, - }, + body: this.form, }) .then((result) => { - if (!result.token) { - return Promise.reject(new Error('Server authentication down.')) - } + if (!result.token) return Promise.reject(new Error('Server authentication down.')) Authentication.updateToken(result.token) m.route.set(this.redirect || '/browse') }) .catch((error) => { - this.error = 'Error while logging in! ' + error.message - vnode.state.password = '' - }) - .then(() => { - this.loading = false - m.redraw() + this.error = 'Error while logging in: ' + error.message + this.form.password = '' }) return false @@ -57,32 +44,39 @@ const Login = { view: function(vnode) { return [ - m('div.modal', [ - m('form', { - onsubmit: this.loginuser.bind(this, vnode), - }, [ - m('h3', 'Filadelfia archival center'), - this.error ? m('p.error', this.error) : null, - m('label', 'Email or name'), - m('input', { - type: 'text', - value: this.username, - oninput: (e) => { this.username = e.currentTarget.value }, - }), - m('label', 'Password'), - m('input', { - type: 'password', - value: this.password, - oninput: (e) => { this.password = e.currentTarget.value }, - }), - m('input.spinner', { - hidden: this.loading, - type: 'submit', - value: 'Log in', - }), - this.loading ? m('div.loading-spinner') : null, + m('div.page.page-login', [ + m('div.modal', [ + m('form', { + onsubmit: this.loginuser.bind(this, vnode), + }, [ + m('h3', 'Filadelfia archival center'), + this.error ? m('p.error', this.error) : null, + m(Input, { + label: 'Email or name', + form: this.form, + formKey: 'email', + }), + m(Input, { + label: 'Password', + type: 'password', + form: this.form, + formKey: 'password', + }), + m('input.spinner', { + hidden: api.loading, + type: 'submit', + value: 'Log in', + }), + api.loading ? m('div.loading-spinner') : null, + ]), + m('div.login--asuna.spritesheet'), + ]), + m('footer', [ + 'Photo by ', + m('a', { href: 'https://unsplash.com/@franhotchin?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Francesca Hotchin'), + ' on ', + m('a', { href: 'https://unsplash.com/photos/landscape-photo-of-mountain-covered-with-snow-FN-cedy6NHA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Unsplash'), ]), - m('div.login--asuna.spritesheet'), ]), ] }, diff --git a/filadelfia_web/app/page_upload.js b/filadelfia_web/app/page_upload.js index c697cfe..1eba146 100644 --- a/filadelfia_web/app/page_upload.js +++ b/filadelfia_web/app/page_upload.js @@ -1,91 +1,54 @@ const m = require('mithril') const Authentication = require('./authentication') const api = require('./api') +const Input = require('./input') -const Login = { +const Upload = { oninit: function(vnode) { - this.redirect = vnode.attrs.redirect || '' - if (!Authentication.currentUser) return m.route.set('/') - + Authentication.requiresLogin() this.error = '' - this.loading = false - this.body = { - year: new Date().getFullYear(), - month: new Date().getMonth() + 1, + this.form = { title: '', + date: new Date(), file: null, } }, - - uploadfile: function(vnode, e) { - e.preventDefault() - this.error = '' - - if (!this.password) this.error = 'Password is missing' - if (!this.username) this.error = 'Email is missing' - - if (this.error) return false - - this.loading = true - - api.sendRequest({ - method: 'POST', - url: '/api/authentication/login', - body: { - email: this.username, - password: this.password, - }, - }) - .then((result) => { - if (!result.token) { - return Promise.reject(new Error('Server authentication down.')) - } - Authentication.updateToken(result.token) - m.route.set(this.redirect || '/browse') - }) - .catch((error) => { - this.error = 'Error while logging in! ' + error.message - vnode.state.password = '' - }) - .then(() => { - this.loading = false - m.redraw() - }) - - return false + + uploadvideo: function(vnode, e) { }, view: function(vnode) { return [ - m('div.modal', [ - m('form', { - onsubmit: this.uploadfile.bind(this, vnode), - }, [ - m('h3', 'Filadelfia archival center'), - this.error ? m('p.error', this.error) : null, - m('label', 'Email or name'), - m('input', { - type: 'text', - value: this.username, - oninput: (e) => { this.username = e.currentTarget.value }, - }), - m('label', 'Password'), - m('input', { - type: 'password', - value: this.password, - oninput: (e) => { this.password = e.currentTarget.value }, - }), - m('input.spinner', { - hidden: this.loading, - type: 'submit', - value: 'Log in', - }), - this.loading ? m('div.loading-spinner') : null, + m('div.page.page-upload', [ + m('div.modal', [ + m('form', { + onsubmit: this.uploadvideo.bind(this, vnode), + }, [ + m('h3', 'Upload new video'), + this.error ? m('p.error', this.error) : null, + m(Input, { + label: 'Title', + form: this.form, + formKey: 'title', + }), + m(Input, { + label: 'Date', + type: 'datetime', + form: this.form, + formKey: 'date', + }), + m('input.spinner', { + hidden: api.loading, + type: 'submit', + value: 'Log in', + }), + api.loading ? m('div.loading-spinner') : null, + ]), + m('div.login--asuna.spritesheet'), ]), - m('div.login--asuna.spritesheet'), ]), ] }, } -module.exports = Login +module.exports = Upload diff --git a/filadelfia_web/package.json b/filadelfia_web/package.json index 64158b2..9e1c476 100644 --- a/filadelfia_web/package.json +++ b/filadelfia_web/package.json @@ -14,6 +14,8 @@ "build": "asbundle app/index.js public/assets/app.js", "dev:build": "eltro --watch build --npm build", "dev:server": "eltro --watch server --npm server", + "dev:build:old": "npm-watch build", + "dev:server:old": "npm-watch server", "server": "node dev.mjs | bunyan" }, "watch": { @@ -52,6 +54,8 @@ "nconf-lite": "^2.0.0" }, "devDependencies": { + "@eonasdan/tempus-dominus": "^6.7.19", + "@popperjs/core": "^2.11.8", "asbundle": "^2.6.1", "eltro": "^1.4.4", "flaska": "^1.3.2", diff --git a/filadelfia_web/public/assets/bg.avif b/filadelfia_web/public/assets/bg.avif index ad40c10..7b20812 100644 Binary files a/filadelfia_web/public/assets/bg.avif and b/filadelfia_web/public/assets/bg.avif differ diff --git a/filadelfia_web/public/assets/bg.jpg b/filadelfia_web/public/assets/bg.jpg new file mode 100644 index 0000000..c102c87 Binary files /dev/null and b/filadelfia_web/public/assets/bg.jpg differ diff --git a/filadelfia_web/public/index.html b/filadelfia_web/public/index.html index d7cdeac..ab923f5 100644 --- a/filadelfia_web/public/index.html +++ b/filadelfia_web/public/index.html @@ -13,6 +13,7 @@ :root { --bg: #fff; --bg-component: #f3f7ff; + --bg-component-half: #f3f7ff77; --bg-component-alt: #ffd99c; --color: #031131; --main: #1066ff; @@ -121,6 +122,12 @@ h1 { flex-direction: column; } +.page { + flex: 2 1 auto; + display: flex; + flex-direction: column; +} + .modal { flex: 2 1 auto; display: flex; @@ -236,6 +243,37 @@ form p, label { align-items: center; } +footer { + text-align: center; + padding: 1rem; +} + +footer a { + font-size: 0.8rem; +} + +/* login */ + +.page-login { + background-repeat: no-repeat; + background-position: center; + background-size: cover; +} + +.page-login .modal form { + backdrop-filter: blur(10px); + background: var(--bg-component-half); +} + +.avifsupport .page-login { + background-image: url('./assets/bg.avif'); +} + +.jpegonly .page-login { + background-image: url('./assets/bg.jpg'); +} + + diff --git a/filadelfia_web/rawassets/bg.png b/filadelfia_web/rawassets/bg.png new file mode 100644 index 0000000..ced7e4b Binary files /dev/null and b/filadelfia_web/rawassets/bg.png differ