Edit the design of a map¶
The code came from the "Edit design" panel of the duckOSM page (duckosm-rust, crates/duckosm-wasm/www/app/) on 2026-11-05.
The design panel lets a person change the style of a map in the page: a ready theme, the colours and widths of the road classes, the street names, the one-way arrows, the layers, the ground and the colours of the base map. Each change shows on the map after 0.5 s. The person can save a design as a theme of their own, download it as a file and load it again.
The panel is a part of the dashboard, the same way as the level editor: it draws into the
host's standard panel (#sb), it hides the host's body while it is open, and close() shows the host's body again. It has no frame of
its own.
Add the panel to a page¶
import {designPanel} from "./dashboard/design/design_panel.js";
const design = await designPanel({
panel: document.getElementById("sb"), // the host's dashboard panel: .sb-card with .sb-top and .sb-body
dashboard: rs, // the page's dashboard (rsDashboard)
look: JSON.parse(lookText), // the look that the map has now (a version 2 look)
setLook: async (text) => live.setLook(Look.fromJson(text), "mlt"), // the same tiles: a style, or undefined
redraw: async (text) => { await drawAgain(text); return "A new map."; }, // new tiles: the host draws the map again
storageKey: "mypage", // localStorage: mypage.design and mypage.themes
});
document.getElementById("open-design").onclick = () => design.open();
design.addEventListener("change", (e) => saveSomewhere(e.detail.text));
The panel has four tabs: Theme, Roads, Text and Layers. A host can add its own tabs (see Host tabs).
The options¶
| Option | Default | What it is |
|---|---|---|
panel |
(necessary) | The host's dashboard panel. The panel uses its .sb-top and .sb-body, as the level editor does. |
dashboard |
window.rs |
The page's dashboard. The panel uses it to show a new style (rs.restyle) and to find the road layers (rs.prefix). |
map |
window.map |
The page's MapLibre map. The panel paints the ground and the road layers on it. |
look |
(necessary) | The look of the map now, as an object. The defaults of every value come from it. |
setLook(text) |
none | Gives the new look to the host's LiveMap.setLook (or to the worker that holds it). It gives a style when the tiles stay right, else undefined. |
restyle |
{} |
The options of rs.restyle (also, imagePrefix). The duckOSM page gives {also: ["rs-low-"], imagePrefix: "rs-"}. |
redraw(text, draw) |
(necessary) | The host draws the map again with the look text (null: the host's own look). With draw: false, the host only keeps the look for its next draw. It gives a note for the panel's bar, or throws an error. |
backLabel |
"Steps" |
The text of the back button. null: no back button. |
storageKey |
"roadstyle" |
The start of the localStorage keys: <key>.design (the design now) and <key>.themes (the person's themes). |
boundary |
none | A function that paints the host's area boundary: boundary(b) with b = {show, color, width, dash, fill, fillOpacity, fillOnRoads}. Without it, the Layers tab has no boundary section. |
link |
none | One link in the bar: {label, onClick}. The duckOSM page has "Export →". |
tabs |
[] |
The host's own tabs (see Host tabs). |
A change of the style¶
After 0.5 s with no new change, the panel writes the design into a copy of look (applyToLook) and gives it to the host:
- If the host gave
setLook, the panel calls it. A style means that the tiles stay right. The panel then shows the style on the same tiles withrs.restyle(style). The live colours of the roads (rs.setData,rsColor) stay. - If
setLookgivesundefinedor throws an error (the panel writes it to the console), or the host gave nosetLook, the panel callsredraw(text, true).
rs.restyle(style, {also}) is new in the dashboard. It is the duckOSM page's restyleRoads, moved into roadstyle. It gives each road
layer the new paint, layout and filter, adds the style's images again and reads the new style (rs.setStyle). also is a list of
more id prefixes for copies of the road layers (the duckOSM page has ["rs-low-"], its overview layers). It gives false when the
new style has other layers: then the host must draw again.
Some values are not in the look, so a change of them draws no new map. The panel paints them on the map at once, with roadstyle's own calls:
| Value | How the panel paints it |
|---|---|
The layers (visible) |
rsSetClasses, rsSetBridges, rsSetTunnels, rsSetArrows, rsSetOverlay, rsSetBuildings3D and the names layer. The panel listens to the events of roadstyle's Layers box, so the two show one state. |
The ground (ground) |
The opacity of the layer bg. |
| The road opacity, the casing as an outline, the halo and arrow opacity | The paint of the road layers (roadOpacity, roadOutline). |
| The base map under the map ("Preview on") | rsSetBasemap. The checkerboard is the class rs-checker on the map's container. |
| The area boundary | The host's boundary(b). |
The panel paints them again after each new style. For this, rs.setStyle sends the new event rs:stylechange.
The methods and the events¶
designPanel gives an EventTarget with these methods:
| Method | What it does |
|---|---|
open(tab?), close(), isOpen() |
Show the panel in the host's panel, or show the host's body again. |
get(), text() |
The design now, as an object or as the text of a design file. |
set(design) |
Use this design (it is completed from the look) and draw it. |
load(text, {draw}) |
Use the design of a file text. It gives false for a bad text, and the design stays. draw: false only gives the look to the host (redraw(text, false)). |
setHost(key, value) |
A change of a host value from outside the panel (the duckOSM Export card changes the tile profile). |
refresh() |
Read the base map again (the "Preview on" control). |
flush() |
Draw the change that waits now, and wait for the end of the draw. For tests. |
base(), state() |
The design of the look, and the text of the bar. |
restored |
true when the panel started from the design that this browser kept (<storageKey>.design). The host then has the look of that design already (redraw(text, false)). |
| Event | detail |
When |
|---|---|---|
change |
{design, text} |
After each change. The host keeps the text (the duckOSM page keeps it with the saved area). |
back |
none | The person clicks the back button. The host calls close() and shows its steps. |
Host tabs¶
A host tab holds a value that only the host knows, for example the tile profile of the duckOSM page. The panel keeps the value in the
design (design[key]), in the design file and in the person's themes. A ready theme and Reset do not change it.
tabs: [{id: "tiles", name: "Tiles", key: "profile",
make: () => defaultProfile(), // the value at the start
normalize: (v) => normalizeProfile(v), // a value of a file, completed and in range
draw: (el, value, put, controls) => { ... }, // the host writes the tab's HTML into el; put(newValue) tells the panel of a change
keep: "Keep the tile profile in the theme"}] // the check box of "Your themes"; without it, a theme never keeps the value
controls has the panel's own HTML parts, so a host tab looks like the other tabs: sec(title, body, note) (a section), color, range,
number and check (a label and a control), and esc (HTML text). The panel does not read the events in a host tab: the host listens
to its own controls.
The design file¶
A design file is JSON with "format": "roadstyle-design" and "version": 1. It holds every value, not only the changes, so it does
not depend on the look that it was made from. The panel reads the old format "duckosm-design" too. The themes file has the format
"roadstyle-themes", and the panel reads "duckosm-themes" too.
Where the code is¶
| File | What it holds |
|---|---|
dashboard/design/design.js |
The design model: defaultsOf(look), normalize, applyToLook, makeTheme, the road opacity expressions, the design file. No page code. |
dashboard/design/user_themes.js |
The person's themes: save, rename, delete, the themes file. No page code. |
dashboard/design/design_panel.js |
The panel: the content of its tabs and its bar. |
dashboard/frame.js |
The editor frame (editorFrame, also rs.frame): the title line, the head row with the tabs, the body that scrolls and the bar. The panel draws into it (An editor in the panel). |
dashboard/design/themes.json |
The ready themes. A colour is a style value, so it is in a data file, not in the code. It is in the dashboard folder, because a host copies that folder (duckosm-rust). |
dashboard/dashboard.css |
The panel's CSS, with the theme tokens, in the light and the dark theme. |
The panel is a module that a page imports, as the level editor is. It is not in every page (files.txt): the panel and its themes
are about 60 KB. A page that render_edges writes does not have it.
The ready themes that copy a roadstyle palette or base-map theme (Google like, Grey, roadstyle, OSM Carto, Amber roads, Carto roads,
High saturation, Mono roads) keep their colours in themes.json. tests/test_design_themes.py compares them with the files in data/palettes/ and
data/basemap/, so a change of a palette shows as a failed test.
The tests: tests/test_design_panel_browser.py (the checks of the design model in tests/design_units.js, the panel on a dashboard page,
and a phone) and tests/test_dashboard_themes_browser.py (the panel in the light and the dark theme).
What stays in the host¶
| Host part | Why |
|---|---|
| The tile profile and the Tiles tab | Only the duckOSM page makes tile files (the Export card). It is a host tab. |
| The area boundary layers | The duckOSM page draws its own area layers. The panel calls boundary(b). |
| Where the design is kept with an area | The host listens to change. |
| How the map is drawn again | setLook and redraw. |