aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
Diffstat (limited to 'static')
-rw-r--r--static/api/images.js16
-rw-r--r--static/api/index.js1
-rw-r--r--static/api/profile.js1
-rw-r--r--static/icon.pngbin0 -> 15387 bytes
-rw-r--r--static/icon.svg72
-rw-r--r--static/index.html1
-rw-r--r--static/index.js6
-rw-r--r--static/month.js4
-rw-r--r--static/pages/image-viewer/index.css4
-rw-r--r--static/pages/image-viewer/index.js8
-rw-r--r--static/pages/settings/index.css6
-rw-r--r--static/pages/settings/index.js46
12 files changed, 155 insertions, 10 deletions
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
--- /dev/null
+++ b/static/icon.png
Binary files 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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="512"
+ height="512"
+ viewBox="0 0 512 512"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
+ sodipodi:docname="icon.svg"
+ inkscape:export-filename="icon.png"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="px"
+ inkscape:zoom="0.8457031"
+ inkscape:cx="256.59123"
+ inkscape:cy="141.89377"
+ inkscape:window-width="1914"
+ inkscape:window-height="1054"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1" />
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ id="rect1-3"
+ style="fill:#999999;stroke-width:4.62341;stroke-linecap:round;stroke-linejoin:round"
+ d="M 68.51711,75.315691 257.62072,4.3656212 c 14.8546,-5.5733237 28.63849,12.7727618 28.63849,28.6384698 V 310.18366 c 0,15.86572 -13.78389,23.06516 -28.63849,28.63848 L 68.51711,409.77221 C 53.662516,415.34554 39.87863,396.99945 39.87863,381.13373 V 103.95416 c 0,-15.865708 13.783886,-23.065146 28.63848,-28.638469 z"
+ sodipodi:nodetypes="sssssssss" />
+ <path
+ id="rect1"
+ style="fill:#b3b3b3;stroke-width:4.62341;stroke-linecap:round;stroke-linejoin:round"
+ d="M 161.44819,124.24677 350.55181,53.296701 c 14.85459,-5.573324 28.63848,12.772764 28.63848,28.638472 V 359.11475 c 0,15.86572 -13.78389,23.06515 -28.63848,28.63848 L 161.44819,458.7033 c -14.85459,5.57332 -28.63848,-12.77277 -28.63848,-28.63849 V 152.88524 c 0,-15.86571 13.78389,-23.06514 28.63848,-28.63847 z"
+ sodipodi:nodetypes="sssssssss" />
+ <path
+ id="rect1-1"
+ style="fill:#cccccc;stroke-width:4.62341;stroke-linecap:round;stroke-linejoin:round"
+ d="m 254.37928,173.17786 189.10361,-70.95007 c 14.85459,-5.573328 28.63848,12.77276 28.63848,28.63847 v 277.17957 c 0,15.86572 -13.78389,23.06516 -28.63848,28.63848 l -189.10361,70.95007 c -14.8546,5.57332 -28.63849,-12.77276 -28.63849,-28.63848 V 201.81632 c 0,-15.8657 13.78389,-23.06514 28.63849,-28.63846 z"
+ sodipodi:nodetypes="sssssssss" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-family:'Sans Serif';-inkscape-font-specification:'Sans Serif';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;white-space:pre;inline-size:34.4024;display:inline;fill:#4d4d4d;stroke-width:3.77953;stroke-linecap:round;stroke-linejoin:round"
+ x="254.17958"
+ y="345.31635"
+ id="text1"
+ transform="matrix(5.3706663,0,0,5.3706663,-1094.1078,-1507.2055)"><tspan
+ x="254.17958"
+ y="345.31635"
+ id="tspan3"><tspan
+ style="font-family:Pacifico;-inkscape-font-specification:Pacifico"
+ id="tspan2">M</tspan></tspan></text>
+ </g>
+</svg>
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 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Pacifico&display=swap" rel="stylesheet">
+ <link rel="icon" href="icon.png" type="image/png">
</head>
<body>
</body>
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;
+ }
}