From 4c06eb64cbed3562e428ce59857d1763098638f3 Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Wed, 19 Nov 2025 09:15:49 +0100 Subject: allow images to upload and sort if according to their datetime --- static/api/images.js | 16 +++++++- static/api/index.js | 1 + static/api/profile.js | 1 + static/icon.png | Bin 0 -> 15387 bytes static/icon.svg | 72 ++++++++++++++++++++++++++++++++++++ static/index.html | 1 + static/index.js | 6 ++- static/month.js | 4 ++ static/pages/image-viewer/index.css | 4 ++ static/pages/image-viewer/index.js | 8 +++- static/pages/settings/index.css | 6 +++ static/pages/settings/index.js | 46 ++++++++++++++++++++--- 12 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 static/api/profile.js create mode 100644 static/icon.png create mode 100644 static/icon.svg (limited to 'static') diff --git a/static/api/images.js b/static/api/images.js index 4bdac1f..aa783e9 100644 --- a/static/api/images.js +++ b/static/api/images.js @@ -52,15 +52,27 @@ export async function upload_to_timeline() { }) } -export async function upload_to_profile() { +export async function upload_to_profile(id) { const input = Input.new({ type: 'file', multiple: false, accept: 'image/jpeg', }) input.click(); + + return new Promise((resolve) => { + input.onchange = () => { + resolve(new FileUploader(`/api/profile/image/upload/${id}`, [...input.files])); + } + }) } export function list() { - return rest.get('/api/image/list').then(r => r.images); + return rest.get('/api/image/list') + .then(r => { + r.images.forEach(i => { + i.date = new Date(i.date * 1000) + }); + return r.images; + }); } diff --git a/static/api/index.js b/static/api/index.js index 3f2f483..cddd7bd 100644 --- a/static/api/index.js +++ b/static/api/index.js @@ -1,3 +1,4 @@ export * as images from './images.js'; export * as auth from './auth.js'; export * as session from './session.js'; +export * as profile from './profile.js'; diff --git a/static/api/profile.js b/static/api/profile.js new file mode 100644 index 0000000..a9ee591 --- /dev/null +++ b/static/api/profile.js @@ -0,0 +1 @@ +import * as rest from './rest.js'; diff --git a/static/icon.png b/static/icon.png new file mode 100644 index 0000000..fc34cde Binary files /dev/null and b/static/icon.png differ diff --git a/static/icon.svg b/static/icon.svg new file mode 100644 index 0000000..bce418c --- /dev/null +++ b/static/icon.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + M + + diff --git a/static/index.html b/static/index.html index 7450237..c69eb16 100644 --- a/static/index.html +++ b/static/index.html @@ -14,6 +14,7 @@ + diff --git a/static/index.js b/static/index.js index 2fcca88..c84aab9 100644 --- a/static/index.js +++ b/static/index.js @@ -100,7 +100,11 @@ const main = MainView.new({ main.active_kind = MainView.Kind.home; main.active_view = shuffle; }, - onsettings: () => { + onsettings: async () => { + if (!settings.profile) { + settings.profile = await api.session.current(); + } + main.active_kind = MainView.Kind.home; main.active_view = settings; }, diff --git a/static/month.js b/static/month.js index 9ed0467..7875f36 100644 --- a/static/month.js +++ b/static/month.js @@ -80,6 +80,10 @@ export default class Month { static literal(strings) { return Month.from_string(strings[0]); } + + static get current() { + return Month.from_date(new Date()); + } } export const literal = Month.literal; diff --git a/static/pages/image-viewer/index.css b/static/pages/image-viewer/index.css index 6f53333..a1a9b76 100644 --- a/static/pages/image-viewer/index.css +++ b/static/pages/image-viewer/index.css @@ -15,3 +15,7 @@ border-radius: var(--border-radius); box-shadow: #223223aa 1px 1px 4px; } + +#container img.hidden { + filter: brightness(0) invert(); +} diff --git a/static/pages/image-viewer/index.js b/static/pages/image-viewer/index.js index f65c12b..d28b3e1 100644 --- a/static/pages/image-viewer/index.js +++ b/static/pages/image-viewer/index.js @@ -15,7 +15,13 @@ export default class ImageViewer extends sfw.element.Container { } add(url) { - this.#container.append(Img.new({ src: url })); + let image; + this.#container.append( + image = Img.new({ + className: 'hidden', + src: url, + onload: () => image.classList.remove('hidden') + })); } clear() { diff --git a/static/pages/settings/index.css b/static/pages/settings/index.css index eb718ab..0d6b234 100644 --- a/static/pages/settings/index.css +++ b/static/pages/settings/index.css @@ -21,6 +21,7 @@ } #image-container { + background: #fff; position: relative; border-radius: 100%; width: 100%; @@ -31,6 +32,7 @@ } #image-container img { + visibility: hidden; position: absolute; top: 50%; left: 50%; @@ -43,6 +45,10 @@ user-select: none; } +#image-container img[src] { + visibility: visible; +} + #profile-image #edit-container { overflow: unset; } diff --git a/static/pages/settings/index.js b/static/pages/settings/index.js index c11a3e9..f316e8e 100644 --- a/static/pages/settings/index.js +++ b/static/pages/settings/index.js @@ -1,6 +1,8 @@ import * as sfw from 'sfw'; const { Div, Img } = sfw.element.native; +import * as api from '../../api/index.js'; + import Editable from '../../widgets/editable/index.js'; import icons from '../../icons/index.js'; @@ -8,6 +10,12 @@ import icons from '../../icons/index.js'; const css = await sfw.css(import.meta.url, './index.css'); export default class SettingsView extends sfw.element.Container { + #profile_image + #name + #birthday + + #profile + constructor() { super({ css }); @@ -23,23 +31,35 @@ export default class SettingsView extends sfw.element.Container { Div.new({ id: 'image-container', children: [ - Img.new(), + this.#profile_image = Img.new(), ] }), Div.new({ id: 'edit', - children: [ icons.edit ] + children: [ icons.edit ], + onclick: async () => { + const uploader = await api.images.upload_to_profile(this.#profile.name); + uploader.ondone = async () => { + const blob = await fetch( + `/api/profile/image/load/${this.#profile.name}`, + {cache: 'reload', mode: 'no-cors'} + ).then(r => r.blob()); + + this.#profile_image.src = URL.createObjectURL(blob); + }; + uploader.send() + } }), ] }), - Editable.new({ + this.#name = Editable.new({ title: 'Name', - value: 'Nathan Reiner' + value: '' }), - Editable.new({ + this.#birthday = Editable.new({ title: 'Birthday', type: 'date', - value: '2002-08-06', + value: '', }), Div.new({ id: 'logout', @@ -57,4 +77,18 @@ export default class SettingsView extends sfw.element.Container { }) ) } + + set profile(profile) { + this.#profile = profile; + + this.#profile_image.src = `/api/profile/image/load/${profile.name}`; + this.#profile_image.onerror = () => this.#profile_image.removeAttribute('src'); + + this.#name.value = profile.full_name; + this.#birthday.value = profile.birthday; + } + + get profile() { + return this.#profile; + } } -- cgit v1.2.3-70-g09d2