aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2025-11-24 21:13:47 +0100
committerNathan Reiner <nathan@nathanreiner.xyz>2025-11-24 21:17:05 +0100
commitf3756dac8a49f3b5599fd50f4c631da4168e9eb0 (patch)
treea84d95e30269d4b368b741ffd1a781b7cfbb1988
parent97b35ce73fab8a84d4d3e6807618a252efcf4cd9 (diff)
add jump-to month
-rw-r--r--src/routes/api/auth/root.zig1
-rw-r--r--src/routes/api/profile/create.zig (renamed from src/routes/api/auth/create-user.zig)6
-rw-r--r--src/routes/api/profile/list.zig17
-rw-r--r--src/routes/api/profile/root.zig2
-rw-r--r--src/storage/user.zig18
-rw-r--r--static/index.js11
-rw-r--r--static/month.js10
-rw-r--r--static/pages/image-viewer/index.js17
-rw-r--r--static/widgets/image/index.js11
-rw-r--r--static/widgets/month-select/index.css9
-rw-r--r--static/widgets/month-select/index.js11
11 files changed, 94 insertions, 19 deletions
diff --git a/src/routes/api/auth/root.zig b/src/routes/api/auth/root.zig
index fa93c92..5f45891 100644
--- a/src/routes/api/auth/root.zig
+++ b/src/routes/api/auth/root.zig
@@ -2,4 +2,3 @@ const HandlerInfo = @import("../../handler-info.zig");
pub const login: HandlerInfo = .from_type(@import("login.zig"));
pub const @"first-login": HandlerInfo = .from_type(@import("first-login.zig"));
-pub const @"create-user": HandlerInfo = .from_type(@import("create-user.zig"));
diff --git a/src/routes/api/auth/create-user.zig b/src/routes/api/profile/create.zig
index 6bd9a82..68f32ec 100644
--- a/src/routes/api/auth/create-user.zig
+++ b/src/routes/api/profile/create.zig
@@ -6,21 +6,21 @@ const Storage = memora.Storage;
pub const access = .admins;
-pub const Body = struct {
+const Body = struct {
name: []const u8,
full_name: []const u8,
birthday: []const u8,
password: []const u8,
};
-pub fn post(ctx: *Context, body: Body) anyerror!void {
+pub fn post(ctx: *Context, body: Body) !void {
var user = try Storage.User.new(
ctx.storage,
body.name,
body.full_name,
body.birthday,
body.password,
- false,
+ true,
ctx.storage.allocator,
);
defer user.deinit();
diff --git a/src/routes/api/profile/list.zig b/src/routes/api/profile/list.zig
new file mode 100644
index 0000000..59d4684
--- /dev/null
+++ b/src/routes/api/profile/list.zig
@@ -0,0 +1,17 @@
+const std = @import("std");
+
+const memora = @import("memora");
+const Context = memora.Context;
+const Storage = memora.Storage;
+
+pub const access = .admins;
+
+const Response = struct {
+ users: [][]const u8,
+};
+
+pub fn get(ctx: *Context) !Response {
+ return .{
+ .users = try Storage.User.list(ctx.storage, ctx.allocator)
+ };
+}
diff --git a/src/routes/api/profile/root.zig b/src/routes/api/profile/root.zig
index 0bdd064..45dec8e 100644
--- a/src/routes/api/profile/root.zig
+++ b/src/routes/api/profile/root.zig
@@ -4,3 +4,5 @@ const HandlerInfo = memora.routes.HandlerInfo;
pub const image = @import("image/root.zig");
pub const set: HandlerInfo = .from_type(@import("set.zig"));
pub const @"update-password": HandlerInfo = .from_type(@import("update-password.zig"));
+pub const create: HandlerInfo = .from_type(@import("create.zig"));
+pub const list: HandlerInfo = .from_type(@import("list.zig"));
diff --git a/src/storage/user.zig b/src/storage/user.zig
index b817f84..c2cc82a 100644
--- a/src/storage/user.zig
+++ b/src/storage/user.zig
@@ -214,3 +214,21 @@ pub fn set_image(
try file_writer.interface.flush();
}
+
+pub fn list(
+ storage: *Storage,
+ allocator: std.mem.Allocator,
+) ![][]const u8 {
+ var user = try storage.dir.openDir("user", .{ .iterate = true });
+ defer user.close();
+
+ var ids: std.ArrayList([]const u8) = .empty;
+
+ var iterator = user.iterate();
+
+ while (try iterator.next()) |entry| {
+ try ids.append(allocator, try allocator.dupe(u8, entry.name));
+ }
+
+ return ids.toOwnedSlice(allocator);
+}
diff --git a/static/index.js b/static/index.js
index c5bfccc..e67d234 100644
--- a/static/index.js
+++ b/static/index.js
@@ -1,6 +1,6 @@
import * as sfw from 'sfw';
import * as api from 'api';
-import { literal as m } from './month.js';
+import Month from './month.js';
import * as service_worker from './service-worker/index.js';
import LoginView from './pages/login/index.js';
@@ -19,8 +19,12 @@ const image_viewer = ImageViewer.new();
const reload = () => {
image_viewer.clear();
api.images.list().then(images => {
+ const last = Month.from_unix(images[0].timestamp);
+ let first = Month.from_unix(images.findLast(i => i.timestamp).timestamp);
+ month_select.months = first.to(last);
+
for (const image of images) {
- image_viewer.add(image.id);
+ image_viewer.add(image);
}
});
}
@@ -50,9 +54,8 @@ const search = Search.new({
});
const month_select = MonthSelect.new({
- months: m`2019-08`.to(m`2025-11`),
+ onmonth: (month) => image_viewer.jump_to(month),
});
-
const upload_bar = UploadBar.new();
const settings = SettingsView.new({
diff --git a/static/month.js b/static/month.js
index 7875f36..05037d4 100644
--- a/static/month.js
+++ b/static/month.js
@@ -55,7 +55,7 @@ export default class Month {
}
is_same(other) {
- return this.year == other.year || this.month == other.month;
+ return this.year == other?.year && this.month == other?.month;
}
is_same_year(other) {
@@ -68,8 +68,14 @@ export default class Month {
return date.toLocaleDateString('default', { month: 'long' })
}
+ static from_unix(date) {
+ if (date == null) return null;
+
+ return Month.from_date(new Date(date * 1000));
+ }
+
static from_date(date) {
- return new Month(date.getMonth() - 1, date.getFullYear());
+ return new Month(date.getMonth() + 1, date.getFullYear());
}
static from_string(string) {
diff --git a/static/pages/image-viewer/index.js b/static/pages/image-viewer/index.js
index 72af671..73b824e 100644
--- a/static/pages/image-viewer/index.js
+++ b/static/pages/image-viewer/index.js
@@ -15,9 +15,6 @@ export default class ImageViewer extends sfw.element.Container {
this.#images = [];
- this.onnewer = () => {}
- this.onolder = () => {}
-
this.body.append(
this.#container = Div.new({
id: 'container',
@@ -32,8 +29,8 @@ export default class ImageViewer extends sfw.element.Container {
);
}
- add(id) {
- const image = Image.new({ id });
+ add(metadata) {
+ const image = Image.new({ metadata });
this.#images.push(image);
this.#container.insertBefore(image, this.#subtitle);
@@ -45,10 +42,16 @@ export default class ImageViewer extends sfw.element.Container {
this.#container.append(this.#subtitle);
}
+ jump_to(month) {
+ const image = this.#images.find(i => i.month == month || i.month.is_same(month));
+ image.scrollIntoView({ behavior: 'instant' })
+ }
+
#preload(image) {
- const loading_zone = (this.#container.scrollTop + this.#container.offsetHeight) * 2;
+ const loading_zone_after = (this.#container.scrollTop + this.#container.offsetHeight) * 2;
+ const loading_zone_before = this.#container.scrollTop - (this.#container.offsetHeight - image.offsetHeight) * 2;
- if (image.offsetTop < loading_zone) {
+ if (loading_zone_before < image.offsetTop && image.offsetTop < loading_zone_after) {
image.load();
}
}
diff --git a/static/widgets/image/index.js b/static/widgets/image/index.js
index 4c25988..291cf23 100644
--- a/static/widgets/image/index.js
+++ b/static/widgets/image/index.js
@@ -1,5 +1,6 @@
import * as api from 'api';
import * as sfw from 'sfw';
+import Month from '../../month.js';
const { Div, Img } = sfw.element.native;
const css = await sfw.css(import.meta.url, './index.css')
@@ -9,6 +10,7 @@ export default class Image extends sfw.element.Container {
#image
#menu
#id
+ #month
constructor() {
super ({ css })
@@ -43,8 +45,9 @@ export default class Image extends sfw.element.Container {
);
}
- set id(id) {
- this.#id = id;
+ set metadata(image) {
+ this.#id = image.id;
+ this.#month = Month.from_unix(image.timestamp);
this.#container.classList.remove('loaded');
}
@@ -53,4 +56,8 @@ export default class Image extends sfw.element.Container {
this.#image.src = `/api/image/load/${this.#id}`;
}
}
+
+ get month() {
+ return this.#month;
+ }
}
diff --git a/static/widgets/month-select/index.css b/static/widgets/month-select/index.css
index 95a87b0..25ed5d2 100644
--- a/static/widgets/month-select/index.css
+++ b/static/widgets/month-select/index.css
@@ -56,6 +56,10 @@
position: relative;
}
+.month-item.none {
+ font-style: italic;
+}
+
.month-item:before {
content: '';
display: block;
@@ -84,6 +88,11 @@
height: 27px;
}
+.month-item.none:before {
+ top: 0;
+ height: 20px;
+}
+
.month-item:hover {
background: var(--primary);
color: var(--fg-primary);
diff --git a/static/widgets/month-select/index.js b/static/widgets/month-select/index.js
index 6ffbac2..63caf70 100644
--- a/static/widgets/month-select/index.js
+++ b/static/widgets/month-select/index.js
@@ -70,5 +70,16 @@ export default class MonthSelect extends sfw.element.Container {
last = month;
}
+
+ this.#month_container.append(
+ Div.new({
+ className: 'month-item none',
+ innerText: 'No Date',
+ onclick: () => {
+ this.hide();
+ this.onmonth(null);
+ }
+ })
+ );
}
}