How it works
One model, three views
The project started as the second half of a Model-View-Controller assignment in Java: a model of shapes and snapshots, a command-file reader, and two views. The graphical view is a Swing window with Prev, Select and Next buttons. The web view writes one HTML file with an SVG per snapshot. Both still live in the repository, unchanged.
This site is the third view. The model is the same idea, rebuilt with current Java:
records for positions, colours and shapes, a sealed Shape interface with
exactly two kinds, and an Album that keeps drawing order and freezes immutable
snapshots. Nothing in the model, parser or renderer imports Spring, and a test checks that.
What happens on a request
- Caddy terminates HTTPS at
shapesalbum.7107labs.comand proxies to this process on a localhost port. - Spring Boot (embedded Tomcat on virtual threads) routes the URL to a
controller method.
/albums/buildings/2means the second snapshot of the bundled album called buildings. - CommandParser turns the command text into a list of commands. A bad line stops it with the line number.
- AlbumBuilder plays the commands into an
Album, which rejects unknown or duplicate shape names and enforces size limits. - SvgRenderer draws the requested snapshot as an inline SVG, switching over the sealed shape kinds.
- A Thymeleaf template wraps it in HTML. Navigation is links and one form, so the page works without JavaScript.
The renderer, briefly
This is the heart of the view. The switch is exhaustive: add a third shape kind to the sealed interface and this code stops compiling until it draws it.
public String element(Shape shape) {
String fill = fill(shape.color());
String title = "<title>" + Xml.escape(shape.id()) + "</title>";
return switch (shape) {
case Rectangle r -> "<rect x=\"%s\" y=\"%s\" width=\"%s\" height=\"%s\" fill=\"%s\">%s</rect>"
.formatted(num(r.position().x()), num(r.position().y()),
num(r.width()), num(r.height()), fill, title);
case Oval o -> "<ellipse cx=\"%s\" cy=\"%s\" rx=\"%s\" ry=\"%s\" fill=\"%s\">%s</ellipse>"
.formatted(num(o.position().x()), num(o.position().y()),
num(o.xRadius()), num(o.yRadius()), fill, title);
};
}
The command language
One command per line. Coordinates start at the top-left corner, as in SVG and Swing.
Lines starting with # are comments. The command word is case-insensitive.
| Command | Arguments | Meaning |
|---|---|---|
shape | id rectangle|oval x y w h r g b |
Create a shape on top of the others. For ovals, x y is the centre and
w h are the radii. Colours are 0 to 255. |
move | id x y | Move to an absolute position. |
color | id r g b | Change the colour. |
resize | id w h | New width and height, or radii. |
remove | id | Take the shape off the canvas. |
snapshot | [description] | Freeze the canvas as the next snapshot. |
Limits
| Pasted commands | 64 KB |
| Shapes on the canvas | 5,000 |
| Snapshots per album | 200 |
| Canvas side | 100 to 2,000, default 1,000 |
| Pasted albums kept | 90 days after they were last opened, at most 5,000 |
Running it
Java 21 from Ubuntu's packages, one jar built with Maven, a systemd unit that restarts it
if it dies, and Caddy in front for TLS and headers. The service listens only on localhost.
Pasted albums go to a SQLite file through Spring's JdbcClient, with write-ahead
logging, so your link outlives restarts and deploys. /healthz reports the
service's health, database included. The same code builds and tests on every push.