diff options
| -rw-r--r-- | src/main.zig | 3 | ||||
| -rw-r--r-- | src/root.zig | 3 | ||||
| -rw-r--r-- | src/web/component/timeline/index.css | 9 | ||||
| -rw-r--r-- | src/web/component/timeline/index.js | 54 | ||||
| -rw-r--r-- | src/web/index.css | 1 | ||||
| -rw-r--r-- | src/web/pinch.js | 60 |
6 files changed, 116 insertions, 14 deletions
diff --git a/src/main.zig b/src/main.zig index 82f5824..4b2b025 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,6 +17,7 @@ const document: html.Element = .native(.html, .{}, .children(&.{ .name = "viewport", .content = "width=device-width, initial-scale=1.0", }, .void), + z.script.load(@embedFile("web/pinch.js")), z.style.load(@embedFile("web/index.css")), z.env(components), })), @@ -26,7 +27,7 @@ const document: html.Element = .native(.html, .{}, .children(&.{ })); pub fn main(init: std.process.Init) !void { - var server: http.Server = try .init(init.gpa, try .parseLiteral("127.0.0.1:8080")); + var server: http.Server = try .init(init.gpa, try .parseLiteral("0.0.0.0:8080")); try server.serve(comptime document.toDocument()); } diff --git a/src/root.zig b/src/root.zig index 8d85eb4..0cd7c71 100644 --- a/src/root.zig +++ b/src/root.zig @@ -2,5 +2,6 @@ pub const db = @import("db"); pub const api = @import("api"); -pub const frontend = @import("frontend"); +pub const html = @import("html"); +pub const z = @import("z"); pub const http = @import("http"); diff --git a/src/web/component/timeline/index.css b/src/web/component/timeline/index.css index 4060deb..8e61fcf 100644 --- a/src/web/component/timeline/index.css +++ b/src/web/component/timeline/index.css @@ -2,6 +2,15 @@ width: 100%; display: inline-grid; gap: 0px; + transition: transform 0.1s ease; +} + +.image { + text-align: center; + align-content: center; + font-weight: bold; + font-size: 0.5em; + overflow: hidden; } #container.tiling .image { diff --git a/src/web/component/timeline/index.js b/src/web/component/timeline/index.js index 0bbbcc2..0e237a1 100644 --- a/src/web/component/timeline/index.js +++ b/src/web/component/timeline/index.js @@ -9,8 +9,9 @@ function getRandomColor() { return color; } -for (let i = 0; i < 100; ++i) { +for (let i = 0; i < 10000; ++i) { container.appendChild(Z.native('div', { + innerText: i, className: 'image', style: { background: getRandomColor(), @@ -19,24 +20,53 @@ for (let i = 0; i < 100; ++i) { })); } -const set_number_of_rows = (n) => { - container.style.gridTemplateColumns = '1fr '.repeat(n); +const zoom = new (class { + constructor() { + this.committed = 5; + this.count = 5; + container.style.gridTemplateColumns = `repeat(5, 1fr)`; + container.classList.toggle('tiling', this.count != 1); + } - container.classList.toggle('tiling', n != 1); -}; + update(delta, point) { + this.count += delta; + this.count = Math.min(Math.max(this.count, 1), 20); + + const scale = this.committed / this.count; + const rects = Z.document.getClientRects()[0]; + + const x = point.x; + const y = point.y - rects.y; -let zoom_state = 5; -set_number_of_rows(zoom_state); + container.style.transformOrigin = `${x}px ${y}px`; + container.style.transform = `scale(${scale})`; + } + + commit() { + console.log('commit'); + this.count = Math.round(this.count); + container.style.transform = 'scale(1)'; + container.style.gridTemplateColumns = `repeat(${this.count}, 1fr)`; + container.classList.toggle('tiling', this.count != 1); + this.committed = this.count; + } +}); Z.document.onwheel = (event) => { if (!event.ctrlKey) { return; } - event.preventDefault(); + zoom.update(event.wheelDeltaY / 120, { x: 0, y: 0 }); + zoom.commit(); +}; - zoom_state += event.wheelDeltaY / 120; - zoom_state = Math.max(zoom_state, 1); - set_number_of_rows(zoom_state); -} +const pinch = new Pinch(Z.document); +pinch.onpinch = (delta, point) => { + zoom.update(delta / 20, point); +}; + +pinch.onfinish = (delta) => { + zoom.commit(); +}; diff --git a/src/web/index.css b/src/web/index.css index b5647b1..3c6b0d6 100644 --- a/src/web/index.css +++ b/src/web/index.css @@ -3,6 +3,7 @@ body { margin: 0; width: 100vw; height: 100vh; + touch-action: pan-y; /* Prevent default touch behavior */ } #timeline { diff --git a/src/web/pinch.js b/src/web/pinch.js new file mode 100644 index 0000000..14f843a --- /dev/null +++ b/src/web/pinch.js @@ -0,0 +1,60 @@ +class Pinch { + constructor(element) { + this.event_cache = []; + this.previous_diff = null; + this.onpinch = () => {}; + this.onfinish = () => {}; + + element.onpointerdown = (e) => this.pointer_down_handler(e); + element.onpointermove = (e) => this.pointer_move_handler(e); + element.onpointerup = (e) => this.pointer_up_handler(e); + element.onpointercancel = (e) => this.pointer_up_handler(e); + element.onpointerout = (e) => this.pointer_up_handler(e); + element.onpointerleave = (e) => this.pointer_up_handler(e); + } + + pointer_down_handler(event) { + this.event_cache.push(event); + } + + pointer_move_handler(event) { + const index = this.event_cache.findIndex( + (cached) => cached.pointerId == event.pointerId, + ); + this.event_cache[index] = event; + + if (this.event_cache.length === 2) { + event.preventDefault(); + + const diff = Math.hypot( + this.event_cache[0].clientX - this.event_cache[1].clientX, + this.event_cache[0].clientY - this.event_cache[1].clientY, + ); + + if (this.previous_diff != null) { + const point = { + x: (this.event_cache[0].clientX + this.event_cache[1].clientX) / 2, + y: (this.event_cache[0].clientY + this.event_cache[1].clientY) / 2, + }; + + const delta = this.previous_diff - diff; + this.onpinch(delta, point); + } + + this.previous_diff = diff; + } + } + + pointer_up_handler(event) { + const index = this.event_cache.findIndex( + (cached) => cached.pointerId === event.pointerId, + ); + this.event_cache.splice(index, 1); + + if (this.event_cache.length < 2) { + this.previous_diff = null; + } + + this.onfinish(); + } +} |