aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.tmuxinator.yml4
-rwxr-xr-xbackend/bin/dev-server2
-rw-r--r--backend/build.zig26
-rw-r--r--backend/build.zig.zon14
-rw-r--r--backend/src/repos/maps_repo.zig4
-rw-r--r--backend/src/repos/markers_repo.zig4
-rw-r--r--backend/src/repos/users_repo.zig4
-rw-r--r--flake.lock12
-rw-r--r--flake.nix2
-rw-r--r--frontend/styles/pages/map.sass7
-rw-r--r--frontend/ts/src/lib/leaflet.d.ts6
-rw-r--r--frontend/ts/src/lib/rx.ts18
-rw-r--r--frontend/ts/src/pages/map.ts74
-rw-r--r--frontend/ts/src/pages/map/footer.ts25
-rw-r--r--frontend/ts/src/pages/map/marker.ts20
15 files changed, 151 insertions, 71 deletions
diff --git a/.tmuxinator.yml b/.tmuxinator.yml
index 83fe649..7ef9740 100644
--- a/.tmuxinator.yml
+++ b/.tmuxinator.yml
@@ -11,9 +11,9 @@ windows:
- sleep 1
- nix develop
- cd backend
- - bin/dev-server maps-backend
+ - bin/dev-server
- frontend:
- sleep 2
- nix develop
- cd frontend
- - bin/dev-server maps-frontend
+ - bin/dev-server
diff --git a/backend/bin/dev-server b/backend/bin/dev-server
index 680e736..0952209 100755
--- a/backend/bin/dev-server
+++ b/backend/bin/dev-server
@@ -14,7 +14,7 @@ watchexec \
--watch src \
--clear clear \
--restart \
- zig build run &
+ zig build -Dtarget=x86_64-linux run &
LIVE_SERVER_PID="$!"
while true; do sleep 1; done
diff --git a/backend/build.zig b/backend/build.zig
index 3fd7c48..fc26d37 100644
--- a/backend/build.zig
+++ b/backend/build.zig
@@ -1,17 +1,21 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
+ const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "backend",
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
});
+ b.installArtifact(exe);
+
const httpz = b.dependency("httpz", .{
.target = target,
.optimize = optimize,
@@ -22,12 +26,10 @@ pub fn build(b: *std.Build) void {
.target = target,
.optimize = optimize,
});
- exe.linkLibC();
- exe.linkSystemLibrary("sqlite3");
+ // exe.linkLibC();
+ // exe.linkSystemLibrary("sqlite3");
exe.root_module.addImport("zqlite", zqlite.module("zqlite"));
- b.installArtifact(exe);
-
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
@@ -38,9 +40,11 @@ pub fn build(b: *std.Build) void {
run_step.dependOn(&run_cmd.step);
const exe_unit_tests = b.addTest(.{
- .root_source_file = b.path("src/main.zig"),
- .target = target,
- .optimize = optimize,
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
diff --git a/backend/build.zig.zon b/backend/build.zig.zon
index 11fd851..cd8301e 100644
--- a/backend/build.zig.zon
+++ b/backend/build.zig.zon
@@ -1,11 +1,11 @@
-.{ .name = .backend, .version = "0.0.0", .fingerprint = 0x8fe5c971579344fd, .minimum_zig_version = "0.14.0", .dependencies = .{
- .httpz = .{
- .url = "git+https://github.com/karlseguin/http.zig?ref=master#8ecf3a330ab1bed8495604d444e549b94f08bc0f",
- .hash = "httpz-0.0.0-PNVzrOy2BgBA2lU1zixuVrv0UUkSnVrBUIlIHl1XV0XV",
- },
+.{ .name = .backend, .version = "0.0.0", .fingerprint = 0x8fe5c971579344fd, .minimum_zig_version = "0.15.1", .dependencies = .{
.zqlite = .{
- .url = "git+https://github.com/karlseguin/zqlite.zig?ref=master#3cd6edca7d735fe9a8545c699091cfcebe1feb7f",
- .hash = "zqlite-0.0.0-RWLaY0Z7lQApOOwYrJMyZomy1GlA3vRnmtrTaWQ2BSQK",
+ .url = "git+https://github.com/karlseguin/zqlite.zig?ref=master#74e661633ffe04fadc214ad07857633fa339f79c",
+ .hash = "zqlite-0.0.0-RWLaY81-lQAbR_uUQJeKmTzYi16C9Zvl-BjrBOavQfeK",
+ },
+ .httpz = .{
+ .url = "git+https://github.com/karlseguin/http.zig?ref=master#90e7fc07ef7157a4da6922571fb4daef38b48998",
+ .hash = "httpz-0.0.0-PNVzrHLABgDoVnzqKpEbx2rZIzmMclEdoEmdJDfZmxsv",
},
}, .paths = .{
"build.zig",
diff --git a/backend/src/repos/maps_repo.zig b/backend/src/repos/maps_repo.zig
index 8031827..8e38877 100644
--- a/backend/src/repos/maps_repo.zig
+++ b/backend/src/repos/maps_repo.zig
@@ -16,9 +16,9 @@ pub fn get_maps(allocator: std.mem.Allocator, conn: zqlite.Conn) !std.ArrayList(
var rows = try conn.rows(query, .{});
defer rows.deinit();
- var list = std.ArrayList(Map).init(allocator);
+ var list: std.ArrayList(Map) = .{};
while (rows.next()) |row| {
- try list.append(Map{
+ try list.append(allocator, Map{
.id = try allocator.dupe(u8, row.text(0)),
.name = try allocator.dupe(u8, row.text(1)),
});
diff --git a/backend/src/repos/markers_repo.zig b/backend/src/repos/markers_repo.zig
index 232b8b6..9fc2b15 100644
--- a/backend/src/repos/markers_repo.zig
+++ b/backend/src/repos/markers_repo.zig
@@ -24,9 +24,9 @@ pub fn get_markers(allocator: std.mem.Allocator, conn: zqlite.Conn, map_id: []co
defer rows.deinit();
- var list = std.ArrayList(Marker).init(allocator);
+ var list: std.ArrayList(Marker) = .{};
while (rows.next()) |row| {
- try list.append(Marker{
+ try list.append(allocator, Marker{
.id = try allocator.dupe(u8, row.text(0)),
.lat = row.float(1),
.lng = row.float(2),
diff --git a/backend/src/repos/users_repo.zig b/backend/src/repos/users_repo.zig
index 0f4ea82..9bde514 100644
--- a/backend/src/repos/users_repo.zig
+++ b/backend/src/repos/users_repo.zig
@@ -39,7 +39,7 @@ pub fn check_password(allocator: std.mem.Allocator, conn: zqlite.Conn, email: []
defer row.deinit();
const hash = row.text(0);
const verify_options = bcrypt.VerifyOptions{ .silently_truncate_password = false };
- std.time.sleep(100000000 + rand.intRangeAtMost(u32, 0, 100000000));
+ std.Thread.sleep(100000000 + rand.intRangeAtMost(u32, 0, 100000000));
bcrypt.strVerify(hash, password, verify_options) catch {
return null;
};
@@ -48,7 +48,7 @@ pub fn check_password(allocator: std.mem.Allocator, conn: zqlite.Conn, email: []
.name = try allocator.dupe(u8, row.text(2)),
};
} else {
- std.time.sleep(500000000 + rand.intRangeAtMost(u32, 0, 500000000));
+ std.Thread.sleep(500000000 + rand.intRangeAtMost(u32, 0, 500000000));
return null;
}
}
diff --git a/flake.lock b/flake.lock
index c3eddc0..9d5ce22 100644
--- a/flake.lock
+++ b/flake.lock
@@ -36,11 +36,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1746276987,
- "narHash": "sha256-dMBHCH4wH2VzoPowkiRTljpiC/ihZ6Q0fdDH+K0Y9fQ=",
+ "lastModified": 1756565854,
+ "narHash": "sha256-65gr4nJOsp5W4Q27XS8SgaY90TtCe8GuNbXLVtID33s=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "2490f37321fed41b50849efe74013903a6c320b5",
+ "rev": "8bb4e10098f4f5e47730856e8de5f8836ff7d49a",
"type": "github"
},
"original": {
@@ -82,11 +82,11 @@
]
},
"locked": {
- "lastModified": 1746232344,
- "narHash": "sha256-t4dwFdoEksxY8vAX/95ybPSJV7lxZQr7kpmRNv6c69I=",
+ "lastModified": 1756555914,
+ "narHash": "sha256-7yoSPIVEuL+3Wzf6e7NHuW3zmruHizRrYhGerjRHTLI=",
"owner": "mitchellh",
"repo": "zig-overlay",
- "rev": "08e5c921bdf1aff9772874425e09b2a69a972584",
+ "rev": "d0df3a2fd0f11134409d6d5ea0e510e5e477f7d6",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 126e1f8..3058b78 100644
--- a/flake.nix
+++ b/flake.nix
@@ -36,7 +36,7 @@
# Backend
psmisc # fuser
sqlite
- zigpkgs."0.14.0"
+ zigpkgs."0.15.1"
# Tooling
tmux
diff --git a/frontend/styles/pages/map.sass b/frontend/styles/pages/map.sass
index 53c5dc4..1ac6c27 100644
--- a/frontend/styles/pages/map.sass
+++ b/frontend/styles/pages/map.sass
@@ -26,8 +26,15 @@
&__FooterButtons
display: flex
+ align-items: center
gap: 1rem
+ .g-ReadOnly
+ &--Active
+ background-color: colors.$green
+ &:not(.g-ReadOnly--Active)
+ opacity: 0.2
+
.g-ContextMenu
z-index: 1000
position: absolute
diff --git a/frontend/ts/src/lib/leaflet.d.ts b/frontend/ts/src/lib/leaflet.d.ts
index 76b88fd..ab8b038 100644
--- a/frontend/ts/src/lib/leaflet.d.ts
+++ b/frontend/ts/src/lib/leaflet.d.ts
@@ -40,6 +40,12 @@ export interface Layer {
addEventListener: (name: string, fn: (e: MapEvent) => void) => void
getLatLng: () => Pos
setLatLng: (pos: Pos) => void
+ dragging: Dragging
+}
+
+export interface Dragging {
+ enable(): () => void
+ disable(): () => void
}
export function tileLayer(url: string): Layer
diff --git a/frontend/ts/src/lib/rx.ts b/frontend/ts/src/lib/rx.ts
index 5edd3c1..b532842 100644
--- a/frontend/ts/src/lib/rx.ts
+++ b/frontend/ts/src/lib/rx.ts
@@ -1,4 +1,4 @@
-// Rx 3.0.0
+// Rx 3.1.0
// Html
@@ -266,16 +266,22 @@ export function pure<A>(value: A): Rx<A> {
return new Pure(value)
}
-class Var<A> extends Rx<A> {
+export class Var<A> extends Rx<A> {
readonly type: 'Var'
readonly id: string
readonly update: (f: (value: A) => A) => void
+ readonly now: () => A
- constructor(id: string, update: (v: Var<A>) => ((f: ((value: A) => A)) => void)) {
+ constructor(
+ id: string,
+ update: (v: Var<A>) => ((f: ((value: A) => A)) => void),
+ now: (v: Var<A>) => (() => A)
+ ) {
super()
this.id = id
this.type = 'Var'
this.update = update(this)
+ this.now = now(this)
}
}
@@ -350,7 +356,11 @@ class State {
}
register<A>(initValue: A) {
- const v = new Var(this.varCounter.toString(), v => (f => this.update(v, f)))
+ const v = new Var(
+ this.varCounter.toString(),
+ v => (f => this.update(v, f)),
+ v => () => this.get(v)
+ )
this.varCounter += BigInt(1)
this.state[v.id] = {
value: initValue,
diff --git a/frontend/ts/src/pages/map.ts b/frontend/ts/src/pages/map.ts
index b445f63..42da69c 100644
--- a/frontend/ts/src/pages/map.ts
+++ b/frontend/ts/src/pages/map.ts
@@ -1,4 +1,4 @@
-import { h, Html } from 'lib/rx'
+import { h, Html, Var } from 'lib/rx'
import * as rx from 'lib/rx'
import * as request from 'request'
import * as modal from 'ui/modal'
@@ -13,9 +13,9 @@ import * as L from 'lib/loadable'
import * as layout from 'ui/layout'
export function view(id: string): Html {
- return rx.withState2<L.Loadable<Map>, L.Loadable<markerModel.Map>>(
- [L.loading, L.loading],
- (mapVar, markersVar) => {
+ return rx.withState3<L.Loadable<Map>, L.Loadable<markerModel.Map>, boolean>(
+ [L.loading, L.loading, false],
+ (mapVar, markersVar, readOnlyVar) => {
request
.get<Map>(`/api/maps/${id}`)
.then(res => mapVar.update(_ => L.loaded(res)))
@@ -23,7 +23,11 @@ export function view(id: string): Html {
request
.get<Array<markerModel.Marker>>(`/api/markers?map=${id}`)
- .then(res => markersVar.update(_ => L.loaded(markerModel.toMap(res))))
+ .then(res => {
+ markersVar.update(_ => L.loaded(markerModel.toMap(res)))
+ // Set to readonly if there is one marker
+ if (res.length > 0) readOnlyVar.update(_ => true)
+ })
.catch(({ message }) => markersVar.update(_ => L.failure(message)))
return rx.map2(
@@ -33,8 +37,14 @@ export function view(id: string): Html {
L.map2([map, markers], (map, markers) => ({ map, markers })),
({map, markers}) => h('div',
{ className: 'g-Map' },
- withMap(map => mapView(id, map, markers)),
- footer.view(map)
+ withMap(leafletMap =>
+ [ mapView(id, leafletMap, markers, readOnlyVar),
+ footer.view({
+ mapInit: map,
+ readOnly: readOnlyVar,
+ onUpdateReadOnly: (b: boolean) => readOnlyVar.update(_ => b)
+ }) ]
+ )
)
)
}
@@ -74,7 +84,7 @@ interface ErrorModal {
message: string
}
-function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html {
+function mapView(map_id: string, map: M.Map, markers: markerModel.Map, readOnlyVar: Var<boolean>): Html {
let lastUserAdded: markerModel.Marker | undefined
return rx.withState3<
@@ -84,9 +94,17 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html {
>(
[undefined, undefined, undefined],
(addModalVar, updateModalVar, errorModalVar) => {
- map.addEventListener('click', (event: M.MapEvent) => addModalVar.update(_ => event.latlng))
+ map.addEventListener('click', (event: M.MapEvent) => {
+ if (!readOnlyVar.now()) {
+ addModalVar.update(_ => event.latlng)
+ }
+ })
- const onClick = (m: MarkerContext) => updateModalVar.update(_ => m)
+ const onClick = (m: MarkerContext) => {
+ if (!readOnlyVar.now()) {
+ updateModalVar.update(_ => m)
+ }
+ }
const onMoveError = (message: string) => {
errorModalVar.update(_ => ({
@@ -97,7 +115,7 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html {
const elements = M.featureGroup()
Object.values(markers).forEach(marker => {
- const elem = addMarker({ marker, markers, map, onClick, onMoveError })
+ const elem = addMarker({ marker, markers, map, onClick, onMoveError, readOnlyVar })
elements.addLayer(elem)
})
@@ -120,7 +138,7 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html {
body
),
onSuccess: marker => {
- addMarker({ marker, markers, map, onClick, onMoveError })
+ addMarker({ marker, markers, map, onClick, onMoveError, readOnlyVar })
addModalVar.update(_ => undefined)
markers[marker.id] = marker
lastUserAdded = marker
@@ -145,7 +163,7 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html {
),
onSuccess: marker => {
map.removeLayer(markerElem)
- addMarker({ marker, markers, map, onClick, onMoveError })
+ addMarker({ marker, markers, map, onClick, onMoveError, readOnlyVar })
updateModalVar.update(_ => undefined)
markers[marker.id] = marker
lastUserAdded = marker
@@ -175,24 +193,30 @@ interface AddMarkerParams {
map: M.Map
onClick: (m: MarkerContext) => void
onMoveError: (message: string) => void
+ readOnlyVar: Var<boolean>
}
-function addMarker({ markers, marker, map, onClick, onMoveError }: AddMarkerParams): M.FeatureGroup {
+function addMarker({ markers, marker, map, onClick, onMoveError, readOnlyVar }: AddMarkerParams): M.FeatureGroup {
const elem = markerView.create({
marker,
onMove: markerElem => {
- const pos = markerElem.getLatLng()
- const newMarker = structuredClone(marker)
- newMarker.lat = pos.lat
- newMarker.lng = pos.lng
- updateMarker(newMarker)
- .then(m => markers[marker.id] = m)
- .catch(({ message }) => {
- markerElem.setLatLng({ lat: marker.lat, lng: marker.lng })
- onMoveError(message)
- })
+ if (!readOnlyVar.now()) {
+ const pos = markerElem.getLatLng()
+ const newMarker = structuredClone(marker)
+ newMarker.lat = pos.lat
+ newMarker.lng = pos.lng
+ updateMarker(newMarker)
+ .then(m => markers[marker.id] = m)
+ .catch(({ message }) => {
+ markerElem.setLatLng({ lat: marker.lat, lng: marker.lng })
+ onMoveError(message)
+ })
+ } else {
+ markerElem.setLatLng({ lat: marker.lat, lng: marker.lng })
+ }
},
- onClick: (markerElem: M.FeatureGroup) => onClick({ markerId: marker.id, markerElem })
+ onClick: (markerElem: M.FeatureGroup) => onClick({ markerId: marker.id, markerElem }),
+ readOnlyVar
})
map.addLayer(elem)
diff --git a/frontend/ts/src/pages/map/footer.ts b/frontend/ts/src/pages/map/footer.ts
index 945bfd4..f514c66 100644
--- a/frontend/ts/src/pages/map/footer.ts
+++ b/frontend/ts/src/pages/map/footer.ts
@@ -1,4 +1,4 @@
-import { h, Html } from 'lib/rx'
+import { h, Html, Rx } from 'lib/rx'
import * as rx from 'lib/rx'
import * as request from 'request'
import * as modal from 'ui/modal'
@@ -7,12 +7,22 @@ import { Map } from 'models/map'
import * as form from 'lib/form'
import * as L from 'lib/loadable'
-export function view(mapInit: Map): Html {
+interface ViewParams {
+ mapInit: Map,
+ readOnly: Rx<boolean>,
+ onUpdateReadOnly: (b: boolean) => void,
+}
+
+export function view({ mapInit, readOnly, onUpdateReadOnly }: ViewParams): Html {
return h('footer',
{ className: 'g-Map__Footer' },
rx.withState<Map>(mapInit, mapVar =>
mapVar.map(map => [
- map.name,
+ h('div',
+ { className: 'g-Map__FooterButtons' },
+ map.name,
+ readOnlyButton(readOnly, onUpdateReadOnly)
+ ),
h('div',
{ className: 'g-Map__FooterButtons' },
viewRenameButton(map, (map: Map) => mapVar.update(_ => map)),
@@ -23,6 +33,15 @@ export function view(mapInit: Map): Html {
)
}
+function readOnlyButton(readOnly: Rx<boolean>, onUpdateReadOnly: (b: boolean) => void): Html {
+ return h('button',
+ { className: readOnly.map(b => `g-Button g-ReadOnly ${b ? 'g-ReadOnly--Active' : ''}`),
+ onclick: readOnly.map(b => (event: Event) => onUpdateReadOnly(!b))
+ },
+ 'Lecture seule'
+ )
+}
+
// Rename modal
function viewRenameButton(map: Map, onUpdate: (m: Map) => void): Html {
diff --git a/frontend/ts/src/pages/map/marker.ts b/frontend/ts/src/pages/map/marker.ts
index b690741..435ed5b 100644
--- a/frontend/ts/src/pages/map/marker.ts
+++ b/frontend/ts/src/pages/map/marker.ts
@@ -1,16 +1,17 @@
-import { mount, h, s } from 'lib/rx'
+import { Var, mount, h, s } from 'lib/rx'
import * as Color from 'lib/color'
import * as M from 'lib/leaflet'
import * as icons from 'lib/icons'
import * as markerModel from 'models/marker'
interface CreateParams {
- marker: markerModel.Marker,
- onMove: (marker: M.Layer) => void,
- onClick: (markerElem: M.FeatureGroup) => void,
+ marker: markerModel.Marker
+ onMove: (marker: M.Layer) => void
+ onClick: (markerElem: M.FeatureGroup) => void
+ readOnlyVar: Var<boolean>
}
-export function create({ marker, onMove, onClick }: CreateParams): M.FeatureGroup {
+export function create({ marker, onMove, onClick, readOnlyVar }: CreateParams): M.FeatureGroup {
const { lat, lng, color, icon, name, description, radius } = marker
const pos = { lat, lng }
@@ -30,6 +31,15 @@ export function create({ marker, onMove, onClick }: CreateParams): M.FeatureGrou
? M.featureGroup([ markerElem, circle ])
: M.featureGroup([ markerElem ])
+ // Fired before dragging, permits to disable dragging just at the right
+ // moment if in readonly mode.
+ markerElem.addEventListener('mousedown', () => {
+ if (readOnlyVar.now()) {
+ markerElem.dragging.disable()
+ window.setTimeout(() => markerElem.dragging.enable())
+ }
+ })
+
markerElem.addEventListener('drag', e => {
circle && circle.setLatLng(markerElem.getLatLng())
})