This commit is contained in:
parent
531c7acefe
commit
638e6cc435
14 changed files with 218 additions and 170 deletions
1
filadelfia_web/.npmrc
Normal file
1
filadelfia_web/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
|
@ -1,53 +1,73 @@
|
||||||
const Authentication = require('./authentication')
|
const Authentication = require('./authentication')
|
||||||
|
|
||||||
exports.sendRequest = function(options, isPagination) {
|
const api = {
|
||||||
let token = Authentication.getToken()
|
loading: false,
|
||||||
let pagination = isPagination
|
sendRequest: function(options, isPagination) {
|
||||||
|
api.loading = true
|
||||||
|
let token = Authentication.getToken()
|
||||||
|
let pagination = isPagination
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
options.headers = options.headers || {}
|
options.headers = options.headers || {}
|
||||||
options.headers['Authorization'] = 'Bearer ' + token
|
options.headers['Authorization'] = 'Bearer ' + token
|
||||||
}
|
|
||||||
|
|
||||||
options.extract = function(xhr) {
|
|
||||||
if (xhr.responseText && xhr.responseText.slice(0, 9) === '<!doctype') {
|
|
||||||
throw new Error('Expected JSON but got HTML (' + xhr.status + ': ' + this.url.split('?')[0] + ')')
|
|
||||||
}
|
}
|
||||||
let out = null
|
|
||||||
if (pagination && xhr.status < 300) {
|
|
||||||
let headers = {}
|
|
||||||
|
|
||||||
xhr.getAllResponseHeaders().split('\r\n').forEach(function(item) {
|
options.extract = function(xhr) {
|
||||||
var splitted = item.split(': ')
|
if (xhr.responseText && xhr.responseText.slice(0, 9) === '<!doctype') {
|
||||||
headers[splitted[0]] = splitted[1]
|
if (xhr.status === 500) {
|
||||||
})
|
throw new Error('Server is temporarily down, try again later.')
|
||||||
|
}
|
||||||
out = {
|
throw new Error('Expected JSON but got HTML (' + xhr.status + ': ' + this.url.split('?')[0] + ')')
|
||||||
headers: headers || {},
|
|
||||||
data: JSON.parse(xhr.responseText),
|
|
||||||
}
|
}
|
||||||
} else {
|
let out = null
|
||||||
if (xhr.responseText) {
|
if (pagination && xhr.status < 300) {
|
||||||
out = JSON.parse(xhr.responseText)
|
let headers = {}
|
||||||
|
|
||||||
|
xhr.getAllResponseHeaders().split('\r\n').forEach(function(item) {
|
||||||
|
var splitted = item.split(': ')
|
||||||
|
headers[splitted[0]] = splitted[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
out = {
|
||||||
|
headers: headers || {},
|
||||||
|
data: JSON.parse(xhr.responseText),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
out = {}
|
if (xhr.responseText) {
|
||||||
|
out = JSON.parse(xhr.responseText)
|
||||||
|
} else {
|
||||||
|
out = {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (xhr.status >= 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)
|
return m.request(options)
|
||||||
.catch(function (error) {
|
.then(function(res) {
|
||||||
if (error.status === 403) {
|
api.loading = false
|
||||||
Authentication.clearToken()
|
window.requestAnimationFrame(m.redraw.bind(m))
|
||||||
m.route.set('/', { redirect: m.route.get() })
|
return res
|
||||||
}
|
})
|
||||||
if (error.response && error.response.status) {
|
.catch(function (error) {
|
||||||
return Promise.reject(error.response)
|
api.loading = false
|
||||||
}
|
window.requestAnimationFrame(m.redraw.bind(m))
|
||||||
return Promise.reject(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
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const m = require('mithril')
|
||||||
const storageName = 'nfp_sites_filadelfia_web_logintoken'
|
const storageName = 'nfp_sites_filadelfia_web_logintoken'
|
||||||
|
|
||||||
const Authentication = {
|
const Authentication = {
|
||||||
|
@ -33,6 +34,16 @@ const Authentication = {
|
||||||
getToken: function() {
|
getToken: function() {
|
||||||
return localStorage.getItem(storageName)
|
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))
|
Authentication.updateToken(localStorage.getItem(storageName))
|
||||||
|
|
|
@ -23,7 +23,6 @@ const Menu = {
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function() {
|
view: function() {
|
||||||
console.log(Authentication.currentUser)
|
|
||||||
return Authentication.currentUser
|
return Authentication.currentUser
|
||||||
? [
|
? [
|
||||||
m('nav', [
|
m('nav', [
|
||||||
|
|
|
@ -3,6 +3,7 @@ const Authentication = require('./authentication')
|
||||||
const Header = require('./header')
|
const Header = require('./header')
|
||||||
const Login = require('./page_login')
|
const Login = require('./page_login')
|
||||||
const Browse = require('./page_browse')
|
const Browse = require('./page_browse')
|
||||||
|
const Upload = require('./page_upload')
|
||||||
window.m = m
|
window.m = m
|
||||||
|
|
||||||
var fileref = document.createElement("link");
|
var fileref = document.createElement("link");
|
||||||
|
@ -28,6 +29,7 @@ m.route.prefix = ''
|
||||||
const allRoutes = {
|
const allRoutes = {
|
||||||
'/': Login,
|
'/': Login,
|
||||||
'/browse': Browse,
|
'/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.
|
// Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called.
|
||||||
|
|
19
filadelfia_web/app/input.js
Normal file
19
filadelfia_web/app/input.js
Normal file
|
@ -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
|
|
@ -4,15 +4,12 @@ const videos = require('./videos')
|
||||||
|
|
||||||
const Browse = {
|
const Browse = {
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
|
Authentication.requiresLogin()
|
||||||
if (!videos.Tree.length) {
|
if (!videos.Tree.length) {
|
||||||
this.refreshTree()
|
this.refreshTree()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
oncreate: function() {
|
|
||||||
if (Authentication.currentUser) return
|
|
||||||
},
|
|
||||||
|
|
||||||
refreshTree: function(vnode) {
|
refreshTree: function(vnode) {
|
||||||
videos.refreshTree()
|
videos.refreshTree()
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,55 +1,42 @@
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
const Authentication = require('./authentication')
|
const Authentication = require('./authentication')
|
||||||
const api = require('./api')
|
const api = require('./api')
|
||||||
|
const Input = require('./input')
|
||||||
|
|
||||||
const Login = {
|
const Login = {
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
this.redirect = vnode.attrs.redirect || ''
|
this.redirect = vnode.attrs.redirect || ''
|
||||||
if (Authentication.currentUser) return m.route.set('/browse')
|
Authentication.requiresNotLogin()
|
||||||
|
|
||||||
this.error = ''
|
this.error = ''
|
||||||
this.loading = false
|
this.form = {
|
||||||
this.username = ''
|
email: '',
|
||||||
this.password = ''
|
password: '',
|
||||||
},
|
}
|
||||||
|
|
||||||
oncreate: function() {
|
|
||||||
if (Authentication.currentUser) return
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loginuser: function(vnode, e) {
|
loginuser: function(vnode, e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.error = ''
|
this.error = ''
|
||||||
|
|
||||||
if (!this.password) this.error = 'Password is missing'
|
if (!this.form.password) this.error = 'Password is missing'
|
||||||
if (!this.username) this.error = 'Email is missing'
|
if (!this.form.email) this.error = 'Email is missing'
|
||||||
|
|
||||||
if (this.error) return false
|
if (this.error) return false
|
||||||
|
|
||||||
this.loading = true
|
|
||||||
|
|
||||||
api.sendRequest({
|
api.sendRequest({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/authentication/login',
|
url: '/api/authentication/login',
|
||||||
body: {
|
body: this.form,
|
||||||
email: this.username,
|
|
||||||
password: this.password,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (!result.token) {
|
if (!result.token) return Promise.reject(new Error('Server authentication down.'))
|
||||||
return Promise.reject(new Error('Server authentication down.'))
|
|
||||||
}
|
|
||||||
Authentication.updateToken(result.token)
|
Authentication.updateToken(result.token)
|
||||||
m.route.set(this.redirect || '/browse')
|
m.route.set(this.redirect || '/browse')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.error = 'Error while logging in! ' + error.message
|
this.error = 'Error while logging in: ' + error.message
|
||||||
vnode.state.password = ''
|
this.form.password = ''
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.loading = false
|
|
||||||
m.redraw()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -57,32 +44,39 @@ const Login = {
|
||||||
|
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return [
|
return [
|
||||||
m('div.modal', [
|
m('div.page.page-login', [
|
||||||
m('form', {
|
m('div.modal', [
|
||||||
onsubmit: this.loginuser.bind(this, vnode),
|
m('form', {
|
||||||
}, [
|
onsubmit: this.loginuser.bind(this, vnode),
|
||||||
m('h3', 'Filadelfia archival center'),
|
}, [
|
||||||
this.error ? m('p.error', this.error) : null,
|
m('h3', 'Filadelfia archival center'),
|
||||||
m('label', 'Email or name'),
|
this.error ? m('p.error', this.error) : null,
|
||||||
m('input', {
|
m(Input, {
|
||||||
type: 'text',
|
label: 'Email or name',
|
||||||
value: this.username,
|
form: this.form,
|
||||||
oninput: (e) => { this.username = e.currentTarget.value },
|
formKey: 'email',
|
||||||
}),
|
}),
|
||||||
m('label', 'Password'),
|
m(Input, {
|
||||||
m('input', {
|
label: 'Password',
|
||||||
type: 'password',
|
type: 'password',
|
||||||
value: this.password,
|
form: this.form,
|
||||||
oninput: (e) => { this.password = e.currentTarget.value },
|
formKey: 'password',
|
||||||
}),
|
}),
|
||||||
m('input.spinner', {
|
m('input.spinner', {
|
||||||
hidden: this.loading,
|
hidden: api.loading,
|
||||||
type: 'submit',
|
type: 'submit',
|
||||||
value: 'Log in',
|
value: 'Log in',
|
||||||
}),
|
}),
|
||||||
this.loading ? m('div.loading-spinner') : null,
|
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'),
|
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,91 +1,54 @@
|
||||||
const m = require('mithril')
|
const m = require('mithril')
|
||||||
const Authentication = require('./authentication')
|
const Authentication = require('./authentication')
|
||||||
const api = require('./api')
|
const api = require('./api')
|
||||||
|
const Input = require('./input')
|
||||||
|
|
||||||
const Login = {
|
const Upload = {
|
||||||
oninit: function(vnode) {
|
oninit: function(vnode) {
|
||||||
this.redirect = vnode.attrs.redirect || ''
|
Authentication.requiresLogin()
|
||||||
if (!Authentication.currentUser) return m.route.set('/')
|
|
||||||
|
|
||||||
this.error = ''
|
this.error = ''
|
||||||
this.loading = false
|
this.form = {
|
||||||
this.body = {
|
|
||||||
year: new Date().getFullYear(),
|
|
||||||
month: new Date().getMonth() + 1,
|
|
||||||
title: '',
|
title: '',
|
||||||
|
date: new Date(),
|
||||||
file: null,
|
file: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadfile: function(vnode, e) {
|
uploadvideo: 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
|
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return [
|
return [
|
||||||
m('div.modal', [
|
m('div.page.page-upload', [
|
||||||
m('form', {
|
m('div.modal', [
|
||||||
onsubmit: this.uploadfile.bind(this, vnode),
|
m('form', {
|
||||||
}, [
|
onsubmit: this.uploadvideo.bind(this, vnode),
|
||||||
m('h3', 'Filadelfia archival center'),
|
}, [
|
||||||
this.error ? m('p.error', this.error) : null,
|
m('h3', 'Upload new video'),
|
||||||
m('label', 'Email or name'),
|
this.error ? m('p.error', this.error) : null,
|
||||||
m('input', {
|
m(Input, {
|
||||||
type: 'text',
|
label: 'Title',
|
||||||
value: this.username,
|
form: this.form,
|
||||||
oninput: (e) => { this.username = e.currentTarget.value },
|
formKey: 'title',
|
||||||
}),
|
}),
|
||||||
m('label', 'Password'),
|
m(Input, {
|
||||||
m('input', {
|
label: 'Date',
|
||||||
type: 'password',
|
type: 'datetime',
|
||||||
value: this.password,
|
form: this.form,
|
||||||
oninput: (e) => { this.password = e.currentTarget.value },
|
formKey: 'date',
|
||||||
}),
|
}),
|
||||||
m('input.spinner', {
|
m('input.spinner', {
|
||||||
hidden: this.loading,
|
hidden: api.loading,
|
||||||
type: 'submit',
|
type: 'submit',
|
||||||
value: 'Log in',
|
value: 'Log in',
|
||||||
}),
|
}),
|
||||||
this.loading ? m('div.loading-spinner') : null,
|
api.loading ? m('div.loading-spinner') : null,
|
||||||
|
]),
|
||||||
|
m('div.login--asuna.spritesheet'),
|
||||||
]),
|
]),
|
||||||
m('div.login--asuna.spritesheet'),
|
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Login
|
module.exports = Upload
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
"build": "asbundle app/index.js public/assets/app.js",
|
"build": "asbundle app/index.js public/assets/app.js",
|
||||||
"dev:build": "eltro --watch build --npm build",
|
"dev:build": "eltro --watch build --npm build",
|
||||||
"dev:server": "eltro --watch server --npm server",
|
"dev:server": "eltro --watch server --npm server",
|
||||||
|
"dev:build:old": "npm-watch build",
|
||||||
|
"dev:server:old": "npm-watch server",
|
||||||
"server": "node dev.mjs | bunyan"
|
"server": "node dev.mjs | bunyan"
|
||||||
},
|
},
|
||||||
"watch": {
|
"watch": {
|
||||||
|
@ -52,6 +54,8 @@
|
||||||
"nconf-lite": "^2.0.0"
|
"nconf-lite": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eonasdan/tempus-dominus": "^6.7.19",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
"asbundle": "^2.6.1",
|
"asbundle": "^2.6.1",
|
||||||
"eltro": "^1.4.4",
|
"eltro": "^1.4.4",
|
||||||
"flaska": "^1.3.2",
|
"flaska": "^1.3.2",
|
||||||
|
|
Binary file not shown.
BIN
filadelfia_web/public/assets/bg.jpg
Normal file
BIN
filadelfia_web/public/assets/bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
|
@ -13,6 +13,7 @@
|
||||||
:root {
|
:root {
|
||||||
--bg: #fff;
|
--bg: #fff;
|
||||||
--bg-component: #f3f7ff;
|
--bg-component: #f3f7ff;
|
||||||
|
--bg-component-half: #f3f7ff77;
|
||||||
--bg-component-alt: #ffd99c;
|
--bg-component-alt: #ffd99c;
|
||||||
--color: #031131;
|
--color: #031131;
|
||||||
--main: #1066ff;
|
--main: #1066ff;
|
||||||
|
@ -121,6 +122,12 @@ h1 {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
flex: 2 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
flex: 2 1 auto;
|
flex: 2 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -236,6 +243,37 @@ form p, label {
|
||||||
align-items: center;
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
BIN
filadelfia_web/rawassets/bg.png
Normal file
BIN
filadelfia_web/rawassets/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
Loading…
Reference in a new issue