Pointer Events rather than mouse events, so one code path covers a mouse, a
trackpad, a pen and a finger, and `setPointerCapture` so a drag carried into
Wednesday keeps being delivered to the column it began in. v1 used mouse
events with `e.layer_y()` and an `e.offset_y()` fallback repeated at seven
call sites, and `pixels_per_hour = if time_increment == 15 { 120.0 } else
{ 60.0 }`.
The pointer's pixels become grid units once, against geometry read from the
page when the drag begins, and everything after that is arithmetic on times.
The same call decides what the preview shows and what is saved, so they cannot
disagree -- there is only one of them. Nothing recomputes the grid: one
absolutely positioned element is repositioned per pointer move, using the same
percentages every chip is placed with.
Dragging an occurrence of a repeating event asks which occurrences it applies
to, because it is the same edit the form makes, arriving a different way. The
sending moved to `crate::write` so both use it rather than one reaching into
the other. A new event drawn out on empty grid opens the form over that span:
it has no title yet, and an untitled event on somebody's calendar is worse
than one more step.
Three things the browser found that reading could not:
the now-line's wrapper spans its whole column, so it was swallowing every
pointer that landed on today;
`offsetY` is relative to whatever was pressed, and a press on a resize edge
lands on the edge, where every offset is nearly zero -- so every grab read
as "the top";
the chip's pointerup bubbled to the column, which ended the same drag a
second time and cleared the record that it had happened, so the click that
follows a drag opened the event that had just been moved.
The seed now empties each calendar before filling it, including the one Baikal
makes for a new account, and `dev.sh shoot` reseeds. The checks create, move
and delete real events; a run starting from whatever the last one left behind
was measuring drift as often as behaviour.
120 lines
4.0 KiB
Bash
Executable File
120 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Bring the whole stack up for looking at, or take it down again.
|
|
#
|
|
# e2e/dev.sh up # Baikal, backend, frontend, seeded with a week of events
|
|
# e2e/dev.sh down
|
|
# e2e/dev.sh shoot # screenshots of whatever is running
|
|
#
|
|
# `trunk serve` owns crates/runway-web/dist. Running `trunk build` against the
|
|
# same directory while it is serving rewrites the files without rewriting the
|
|
# integrity hashes in the served index.html, and the browser then blocks its own
|
|
# scripts -- which looks like a blank page and is nothing of the sort.
|
|
set -euo pipefail
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT="$(cd "$HERE/.." && pwd)"
|
|
RUN="${RUNWAY_DEV_DIR:-${TMPDIR:-/tmp}/runway-dev}"
|
|
CALDAV_PORT=8800
|
|
API_PORT=3000
|
|
WEB_PORT=8080
|
|
|
|
# Where the frontend listens. 127.0.0.1 by default so an ordinary run is not
|
|
# reachable from the network; HOST=0.0.0.0 opens it up for looking at from
|
|
# another machine. The backend stays on the loopback either way -- Trunk proxies
|
|
# /api to it from the server side, so it never needs to be exposed.
|
|
HOST="${HOST:-127.0.0.1}"
|
|
|
|
runtime() {
|
|
if command -v podman >/dev/null 2>&1; then echo podman
|
|
elif command -v docker >/dev/null 2>&1; then echo docker
|
|
else echo "need podman or docker" >&2; exit 1
|
|
fi
|
|
}
|
|
|
|
wait_for() {
|
|
local url="$1" name="$2"
|
|
for _ in $(seq 1 90); do
|
|
if [ "$(curl -sS -o /dev/null -w '%{http_code}' -L "$url" 2>/dev/null)" = "200" ]; then
|
|
return 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "$name never came up at $url" >&2
|
|
return 1
|
|
}
|
|
|
|
up() {
|
|
mkdir -p "$RUN"
|
|
local rt; rt="$(runtime)"
|
|
|
|
"$rt" rm -f runway-dev-caldav >/dev/null 2>&1 || true
|
|
"$rt" run -d --rm --name runway-dev-caldav -p "$CALDAV_PORT:80" \
|
|
docker.io/ckulka/baikal:nginx >/dev/null
|
|
wait_for "http://localhost:$CALDAV_PORT/" "Baikal"
|
|
python3 "$ROOT/crates/runway-caldav/tests/baikal/setup.py" \
|
|
"http://localhost:$CALDAV_PORT" testuser testpassword
|
|
python3 "$HERE/seed.py"
|
|
|
|
if [ ! -f "$RUN/secret.key" ]; then
|
|
(cd "$ROOT" && cargo run -q -p runway-server -- genkey) > "$RUN/secret.key"
|
|
fi
|
|
|
|
pkill -f "target/.*/runway-server" >/dev/null 2>&1 || true
|
|
rm -f "$RUN"/runway.db*
|
|
(
|
|
cd "$ROOT"
|
|
RUNWAY_SECRET_KEY="$(cat "$RUN/secret.key")" \
|
|
RUNWAY_DATABASE_URL="sqlite:$RUN/runway.db" \
|
|
RUNWAY_INSECURE_COOKIES=1 \
|
|
RUNWAY_BIND="127.0.0.1:$API_PORT" \
|
|
setsid cargo run -q -p runway-server > "$RUN/server.log" 2>&1 < /dev/null &
|
|
disown
|
|
)
|
|
wait_for "http://127.0.0.1:$API_PORT/api/health" "the backend"
|
|
|
|
pkill -f "trunk serve" >/dev/null 2>&1 || true
|
|
rm -rf "$ROOT/crates/runway-web/dist"
|
|
(
|
|
cd "$ROOT/crates/runway-web"
|
|
setsid trunk serve --address "$HOST" "${TRUNK_ARGS:---release}" \
|
|
> "$RUN/trunk.log" 2>&1 < /dev/null &
|
|
disown
|
|
)
|
|
wait_for "http://127.0.0.1:$WEB_PORT/" "the frontend"
|
|
|
|
echo
|
|
echo " calendar server http://localhost:$CALDAV_PORT testuser / testpassword"
|
|
echo " backend http://127.0.0.1:$API_PORT (loopback only)"
|
|
echo " app http://127.0.0.1:$WEB_PORT"
|
|
if [ "$HOST" = "0.0.0.0" ]; then
|
|
# `hostname -I` is empty on some systems; ask the routing table instead.
|
|
ip -4 -o addr show scope global 2>/dev/null \
|
|
| awk '{split($4, a, "/"); print a[1], $2}' \
|
|
| grep -v ' podman' \
|
|
| while read -r address _; do
|
|
echo " http://$address:$WEB_PORT"
|
|
done
|
|
fi
|
|
echo " logs $RUN"
|
|
}
|
|
|
|
down() {
|
|
pkill -f "trunk serve" >/dev/null 2>&1 || true
|
|
pkill -f "target/.*/runway-server" >/dev/null 2>&1 || true
|
|
"$(runtime)" rm -f runway-dev-caldav >/dev/null 2>&1 || true
|
|
echo "stopped"
|
|
}
|
|
|
|
case "${1:-up}" in
|
|
up) up ;;
|
|
down) down ;;
|
|
# Reseeded first. The checks create, move and delete real events, so a run
|
|
# that started from whatever the last one left behind would be measuring
|
|
# drift as often as behaviour.
|
|
shoot)
|
|
python3 "$HERE/seed.py" >/dev/null
|
|
node "$HERE/shoot.mjs" "http://127.0.0.1:$WEB_PORT" "$ROOT/e2e/screenshots"
|
|
;;
|
|
*) echo "usage: dev.sh [up|down|shoot]" >&2; exit 1 ;;
|
|
esac
|