diff options
Diffstat (limited to 'static')
| -rw-r--r-- | static/api/auth.js | 12 | ||||
| -rw-r--r-- | static/api/images.js | 20 | ||||
| -rw-r--r-- | static/api/index.js | 13 | ||||
| -rw-r--r-- | static/api/rest.js | 22 | ||||
| -rw-r--r-- | static/icons/index.js | 1 | ||||
| -rw-r--r-- | static/icons/next.js | 3 | ||||
| -rw-r--r-- | static/index.js | 31 | ||||
| -rw-r--r-- | static/pages/login/index.css | 69 | ||||
| -rw-r--r-- | static/pages/login/index.js | 66 | ||||
| -rw-r--r-- | static/pages/settings/index.css | 2 | ||||
| -rw-r--r-- | static/pages/settings/index.js | 2 |
11 files changed, 180 insertions, 61 deletions
diff --git a/static/api/auth.js b/static/api/auth.js new file mode 100644 index 0000000..e663d88 --- /dev/null +++ b/static/api/auth.js @@ -0,0 +1,12 @@ +import * as rest from './rest.js'; + + +export async function login(user, password) { + const response = await rest.post('/api/auth/login', { user, password }); + return response?.success ?? false; +} + +export async function is_first_login(user) { + const response = await rest.post('/api/auth/first-login', { user }); + return response?.is_first ?? false; +} diff --git a/static/api/images.js b/static/api/images.js new file mode 100644 index 0000000..915aae6 --- /dev/null +++ b/static/api/images.js @@ -0,0 +1,20 @@ +import * as sfw from 'sfw'; +const { Input } = sfw.element.native; + +export async function upload_to_timeline() { + const input = Input.new({ + type: 'file', + multiple: true, + accept: 'image/jpeg', + }) + input.click(); +} + +export async function upload_to_profile() { + const input = Input.new({ + type: 'file', + multiple: false, + accept: 'image/jpeg', + }) + input.click(); +} diff --git a/static/api/index.js b/static/api/index.js index 50b9b26..07b484d 100644 --- a/static/api/index.js +++ b/static/api/index.js @@ -1,11 +1,2 @@ -import * as sfw from 'sfw'; -const { Input } = sfw.element.native; - -export async function upload_images() { - const input = Input.new({ - type: 'file', - multiple: true, - accept: 'image/jpeg', - }) - input.click(); -} +export * as images from './images.js'; +export * as auth from './auth.js'; diff --git a/static/api/rest.js b/static/api/rest.js new file mode 100644 index 0000000..b314615 --- /dev/null +++ b/static/api/rest.js @@ -0,0 +1,22 @@ + +async function handle_fetch(promise) { + const result = await promise; + const json = await result.json(); + + if (!result.ok) { + throw json; + } + + return json; +} + +export function get(url) { + return handle_fetch(fetch(url)); +} + +export async function post(url, body) { + return handle_fetch(fetch(url, { + method: 'POST', + body: JSON.stringify(body), + })); +} diff --git a/static/icons/index.js b/static/icons/index.js index 08ab7ea..3562754 100644 --- a/static/icons/index.js +++ b/static/icons/index.js @@ -11,6 +11,7 @@ const icons = [ 'add', 'edit', 'check', + 'next', ]; const target = { diff --git a/static/icons/next.js b/static/icons/next.js new file mode 100644 index 0000000..9728ee7 --- /dev/null +++ b/static/icons/next.js @@ -0,0 +1,3 @@ +export default `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8"/> +</svg>`; diff --git a/static/index.js b/static/index.js index 870e3bb..c6388eb 100644 --- a/static/index.js +++ b/static/index.js @@ -29,15 +29,28 @@ const image_viewer = ImageViewer.new(); '/images/0010.jpg', ].forEach(url => image_viewer.add(url)) + const login = LoginView.new({ - onlogin: () => { - document.body.innerHTML = ''; - document.body.append( - main, - search, - month_select, - ) - } + onlogin: async (user, password) => { + if (await api.auth.login(user, password)) { + document.body.innerHTML = ''; + document.body.append( + main, + search, + month_select, + ) + } else { + login.comment = 'Incorrect username or password.'; + } + }, + + onpassword: async (user) => { + if (await api.auth.is_first_login(user)) { + login.comment = 'Please enter a new password.'; + } else { + login.comment = ''; + } + }, }); const search = Search.new({ @@ -73,7 +86,7 @@ const main = MainView.new({ main.active_view = image_viewer; month_select.show(); }, - onupload: () => api.upload_images(), + onupload: () => api.images.upload_to_timeline(), onshuffle: () => { main.active_kind = MainView.Kind.home; main.active_view = shuffle; diff --git a/static/pages/login/index.css b/static/pages/login/index.css index 8a59d83..aa6726e 100644 --- a/static/pages/login/index.css +++ b/static/pages/login/index.css @@ -12,12 +12,52 @@ transform: translate(-50%, -50%); max-width: 300px; width: 100%; - padding: 20px; + padding: 0px; box-shadow: #223223aa 1px 1px 4px; border-radius: var(--border-radius); + z-index: 2000; + display: grid; + grid-template-columns: auto 30px; background: #ffffff99; backdrop-filter: blur(10px); - z-index: 2000; +} + +#box input { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + background: #0000; +} + +#box button { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + background: var(--page-background); + color: var(--fg); + padding: 5px; + background: #0000; +} + +#username { + position: absolute; + top: calc(50% - 40px); + left: 50%; + transform: translate(-50%, -50%); + z-index: 10000; + user-select: none; + color: var(--fg-primary); + font-size: 0.8em; + cursor: pointer; +} +#comment { + position: absolute; + top: calc(50% + 40px); + left: 50%; + transform: translate(-50%, -50%); + z-index: 10000; + user-select: none; + color: var(--primary); + font-size: 0.8em; + cursor: pointer; } #title { @@ -88,31 +128,6 @@ z-index: -2; animation: right-bubble 1s normal forwards ease; } - -#form { - display: grid; - grid-template-columns: 100px auto; - max-width: 300px; - width: 100%; - margin: 0; - margin-bottom: 20px; - border-radius: 3px; - gap: 10px; -} - -#form input { - width: 100%; -} - -#form label { - margin: auto 0px; - font-weight: bold; -} - -button { - width: 100%; -} - #subtitle { position: absolute; bottom: 50px; diff --git a/static/pages/login/index.js b/static/pages/login/index.js index fc14dbf..07c0aa2 100644 --- a/static/pages/login/index.js +++ b/static/pages/login/index.js @@ -1,42 +1,53 @@ import * as sfw from 'sfw'; const { Div, Label, H1: Title, Input, Button } = sfw.element.native; +import icons from '../../icons/index.js'; + const css = await sfw.css(import.meta.url, './index.css'); export default class LoginView extends sfw.element.Container { + #input #user - #password + #comment constructor() { super({ css }); this.onlogin = () => {}; + this.onpassword = () => {}; + + this.#user = null; this.body.append( Div.new({ id: 'container', children: [ Div.new({ id: 'title', innerText: 'Memora' }), + this.#user = Div.new({ + id: 'username', + onclick: () => this.#reset(), + }), Div.new({ id: 'box', children: [ - - Div.new({ - id: 'form', - children: [ - Label.new({ innerText: 'User' }), - this.#user = Input.new({ }), - - Label.new({ innerText: 'Password' }), - this.#password = Input.new({ type: 'password' }), - ] + this.#input = Input.new({ + placeholder: 'Username', + onkeydown: (e) => { + if (e.key === 'Enter') { + return this.#next(); + } + }, }), Button.new({ - innerText: 'Login', - onclick: () => this.onlogin(this.#user.value, this.#password.value), + children: [ icons.next ], + onclick: () => this.#next(), }), ] }), + this.#comment = Div.new({ + id: 'comment', + onclick: () => this.#reset(), + }), Div.new({ id: 'subtitle', innerText: 'Where nostalgia is home.', @@ -45,4 +56,33 @@ export default class LoginView extends sfw.element.Container { }) ); } + + async #next() { + if (this.#input.value === '') return; + + if (this.#user.innerText) { + await this.onlogin(this.#user.innerText, this.#input.value); + this.#user.innerText = ''; + this.#input.value = ''; + this.#input.placeholder = 'Username'; + this.#input.type = 'text'; + } else { + await this.onpassword(this.#input.value); + this.#user.innerText = this.#input.value; + this.#input.value = ''; + this.#input.placeholder = 'Password'; + this.#input.type = 'password'; + } + } + + #reset() { + this.#user.innerText = ''; + this.#input.value = ''; + this.#input.placeholder = 'Username'; + this.#input.type = 'text'; + } + + set comment(value) { + this.#comment.innerText = value; + } } diff --git a/static/pages/settings/index.css b/static/pages/settings/index.css index 5128c18..eb718ab 100644 --- a/static/pages/settings/index.css +++ b/static/pages/settings/index.css @@ -7,6 +7,8 @@ display: flex; flex-flow: column; gap: 20px; + max-width: 700px; + margin: auto; } #profile-image { diff --git a/static/pages/settings/index.js b/static/pages/settings/index.js index da30ba7..6a0e231 100644 --- a/static/pages/settings/index.js +++ b/static/pages/settings/index.js @@ -45,7 +45,7 @@ export default class SettingsView extends sfw.element.Container { }), Div.new({ id: 'logout', - innerText: 'Log-out', + innerText: 'Logout', onclick: () => this.onlogout(), }), Div.new({ |