diff --git a/CHANGELOG.md b/CHANGELOG.md index b6bc44be..593697aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Fix a bug where [cookie](https://sql.ophir.dev/documentation.sql?component=cookie#component) removal set the cookie value to the empty string instead of removing the cookie completely. - Support form submission using the [button](https://sql.ophir.dev/documentation.sql?component=button#component) component using its new `form` property. This allows you to create a form with multiple submit buttons that submit the form to different targets. - Custom icons and colors for markers in the [map](https://sql.ophir.dev/documentation.sql?component=map#component) component. + - Add support for GeoJSON in the [map](https://sql.ophir.dev/documentation.sql?component=map#component) component. This makes it much more generic and allows you to display any kind of geographic data, including areas, on a map very easily. This plays nicely with PostGIS and Spatialite which can return GeoJSON directly from SQL queries. ## 0.15.0 (2023-10-29) - New function: [`sqlpage.path`](https://sql.ophir.dev/functions.sql?function=path#function) to get the path of the current page. diff --git a/README.md b/README.md index d2e77910..2e0bcf39 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,11 @@ To run on a server, you can use [the docker image](https://hub.docker.com/r/lova - Optionally, you can also mount a directory containing sqlpage's configuration file, custom components, and migrations (see [configuration.md](./configuration.md)) to `/etc/sqlpage` in the container. +- If you want to build your own docker image, taking the raw sqlpage image as a base is not recommended, since it is extremely stripped down and probably won't contain the dependencies you need. Instead, you can take debian as a base and simply copy the sqlpage binary from the official image to your own image: + - ```Dockerfile + FROM debian:stable-slim + COPY --from=lovasoa/sqlpage:main /usr/local/bin/sqlpage /usr/local/bin/sqlpage + ``` ### On Mac OS, with homebrew diff --git a/examples/PostGIS - using sqlpage with geographic data/Dockerfile b/examples/PostGIS - using sqlpage with geographic data/Dockerfile deleted file mode 100644 index 1a6142cc..00000000 --- a/examples/PostGIS - using sqlpage with geographic data/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM debian:12-slim - -WORKDIR /var/www - -ADD https://github.com/lovasoa/SQLpage/releases/download/v0.7.2/sqlpage-linux.tgz . - -RUN apt-get update && \ - apt-get -y install sqlite3 libsqlite3-mod-spatialite && \ - tar xvzf sqlpage-linux.tgz && \ - rm sqlpage-linux.tgz - -COPY . . - -CMD ["sqlite3"] diff --git a/examples/make a geographic data application using sqlite extensions/Dockerfile b/examples/make a geographic data application using sqlite extensions/Dockerfile index 9b9ab9c2..5a3d2260 100644 --- a/examples/make a geographic data application using sqlite extensions/Dockerfile +++ b/examples/make a geographic data application using sqlite extensions/Dockerfile @@ -1,14 +1,10 @@ -FROM debian:12-slim +FROM debian:stable-slim -WORKDIR /var/www - -ADD https://github.com/lovasoa/SQLpage/releases/download/v0.10.1/sqlpage-linux.tgz . +COPY --from=lovasoa/sqlpage:main /usr/local/bin/sqlpage /usr/local/bin/sqlpage RUN apt-get update && \ - apt-get -y install sqlite3 libsqlite3-mod-spatialite && \ - tar xvzf sqlpage-linux.tgz && \ - rm sqlpage-linux.tgz + apt-get -y install libsqlite3-mod-spatialite COPY . . -CMD ["sqlite3"] +CMD ["sqlpage"] diff --git a/examples/make a geographic data application using sqlite extensions/index.sql b/examples/make a geographic data application using sqlite extensions/index.sql index 52d08da4..7a1317c2 100644 --- a/examples/make a geographic data application using sqlite extensions/index.sql +++ b/examples/make a geographic data application using sqlite extensions/index.sql @@ -16,7 +16,9 @@ SELECT 'map' as component, SELECT title, ST_Y(geom) AS latitude, ST_X(geom) AS longitude, - 'point.sql?id=' || id as link + 'point.sql?id=' || id as link, + 'red' as color, + 'world-pin' as icon FROM spatial_data; diff --git a/examples/official-site/custom_components.sql b/examples/official-site/custom_components.sql index 5862dc69..d333b61a 100644 --- a/examples/official-site/custom_components.sql +++ b/examples/official-site/custom_components.sql @@ -109,6 +109,7 @@ and SQLPage adds a few more: - `icon_img`: generate an svg icon from a *tabler* icon name - `markdown`: renders markdown text - `each_row`: iterates over the rows of a query result +- `typeof`: returns the type of a value (`string`, `number`, `boolean`, `object`, `array`, `null`) ## Overwriting the default components diff --git a/examples/official-site/documentation.sql b/examples/official-site/documentation.sql index 603db664..b55fdacc 100644 --- a/examples/official-site/documentation.sql +++ b/examples/official-site/documentation.sql @@ -114,6 +114,8 @@ select WHEN 'true' THEN 'TRUE' WHEN 'false' THEN 'FALSE' WHEN 'null' THEN 'NULL' + WHEN 'object' THEN 'JSON(' || quote(t.value) || ')' + WHEN 'array' THEN 'JSON(' || quote(t.value) || ')' ELSE quote(t.value) END as val, CASE parent.fullkey @@ -121,7 +123,8 @@ select ELSE parent.key END as key from t inner join t parent on parent.id = t.parent - where t.atom is not null + where ((parent.fullkey = '$' and t.type != 'array') + or (parent.type = 'array' and parent.path = '$')) ), key_val_padding as (select CASE WHEN key LIKE '% %' THEN format('"%s"', replace(key, '"', '""')) ELSE key END as key, diff --git a/examples/official-site/sqlpage/migrations/10_map.sql b/examples/official-site/sqlpage/migrations/10_map.sql index 083562d1..51b1e940 100644 --- a/examples/official-site/sqlpage/migrations/10_map.sql +++ b/examples/official-site/sqlpage/migrations/10_map.sql @@ -41,7 +41,7 @@ VALUES ( ( 'map', 'latitude', - 'Latitude of the marker', + 'Latitude of the marker. Required if geojson is not set.', 'REAL', FALSE, FALSE @@ -49,7 +49,7 @@ VALUES ( ( 'map', 'longitude', - 'Longitude of the marker', + 'Longitude of the marker. Required if geojson is not set.', 'REAL', FALSE, FALSE @@ -57,7 +57,7 @@ VALUES ( ( 'map', 'title', - 'Title of the marker', + 'Title of the marker, displayed on hover and in the tooltip when the marker is clicked.', 'TEXT', FALSE, TRUE @@ -65,7 +65,7 @@ VALUES ( ( 'map', 'link', - 'A link to associate to the marker''s title', + 'A link to associate to the marker''s title. If set, the marker tooltip''s title will be clickable and will open the link.', 'TEXT', FALSE, TRUE @@ -73,7 +73,7 @@ VALUES ( ( 'map', 'description', - 'Plain text description of the marker, as plain text', + 'Plain text description of the marker, to be displayed in a tooltip when the marker is clicked.', 'TEXT', FALSE, TRUE @@ -81,7 +81,7 @@ VALUES ( ( 'map', 'description_md', - 'Description of the marker, in markdown', + 'Description of the marker, in markdown, rendered in a tooltip when the marker is clicked.', 'TEXT', FALSE, TRUE @@ -101,6 +101,14 @@ VALUES ( 'COLOR', FALSE, TRUE + ), + ( + 'map', + 'geojson', + 'A GeoJSON geometry (line, a polygon, ...) to display on the map. Can be styled using geojson properties using the name of leaflet path options.', + 'JSON', + FALSE, + TRUE ) ; -- Insert an example usage of the map component into the example table @@ -114,12 +122,17 @@ VALUES ( ), ( 'map', - 'Map of Paris', + 'Map of Paris. +Illustrates the use custom styling, and [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON) to display a line between two points. +In a real-world scenario, the GeoJSON could be generated by calling PostGIS''s +[`ST_AsGeoJSON`](https://postgis.net/docs/ST_AsGeoJSON.html) or +Spatialite''s [`AsGeoJSON`](https://www.gaia-gis.it/gaia-sins/spatialite-sql-5.1.0.html#p3misc) functions on a geometry column.', JSON( '[ { "component": "map", "title": "Paris", "zoom": 11, "latitude": 48.85, "longitude": 2.34, "height": 400 }, { "title": "Notre Dame", "icon": "building-castle", "color": "indigo", "latitude": 48.8530, "longitude": 2.3498, "description_md": "A beautiful cathedral.", "link": "https://en.wikipedia.org/wiki/Notre-Dame_de_Paris" }, - { "title": "Eiffel Tower", "icon": "tower", "color": "yellow", "latitude": 48.8584, "longitude": 2.2945, "description_md": "A tall tower. [Wikipedia](https://en.wikipedia.org/wiki/Eiffel_Tower)" } - ]' + { "title": "Eiffel Tower", "icon": "tower", "color": "yellow", "latitude": 48.8584, "longitude": 2.2945, "description_md": "A tall tower. [Wikipedia](https://en.wikipedia.org/wiki/Eiffel_Tower)" }, + { "title": "Tower to Cathedral", "geojson": {"type": "LineString", "coordinates": [[2.2945, 48.8584], [2.3498, 48.8530]]}, "color": "teal", "description": "A nice 45 minutes walk." } + ]' ) ); \ No newline at end of file diff --git a/sqlpage/sqlpage.js b/sqlpage/sqlpage.js index bbc67c7f..311960cd 100644 --- a/sqlpage/sqlpage.js +++ b/sqlpage/sqlpage.js @@ -54,23 +54,44 @@ function sqlpage_map() { } } function addMarker(marker_elem, map) { - const coords = marker_elem.dataset.coords.split(",").map(c => parseFloat(c)); + const { dataset } = marker_elem; const options = { + color: marker_elem.dataset.color, title: marker_elem.getElementsByTagName("h3")[0].textContent.trim(), }; - const color = marker_elem.dataset.color; + const marker = + dataset.coords ? createMarker(marker_elem, options) + : createGeoJSONMarker(marker_elem, options); + marker.addTo(map).bindPopup(marker_elem); + } + function createMarker(marker_elem, options) { + const coords = marker_elem.dataset.coords.split(",").map(c => parseFloat(c)); const icon_obj = marker_elem.getElementsByClassName("mapicon")[0]; if (icon_obj) { options.icon = L.divIcon({ html: icon_obj, - className: `border-0 bg-${color || 'primary'} bg-gradient text-white rounded-circle p-2 shadow`, + className: `border-0 bg-${options.color || 'primary'} bg-gradient text-white rounded-circle p-2 shadow`, iconSize: [42, 42], - iconAnchor: [21, 5], + iconAnchor: [21, 21], }); } - const marker = L.marker(coords, options).addTo(map); - marker.bindPopup(marker_elem); + return L.marker(coords, options); } + function createGeoJSONMarker(marker_elem, options) { + let geojson = JSON.parse(marker_elem.dataset.geojson); + if (options.color) { + options.color = get_tabler_color(options.color) || options.color; + } + function style({ properties }) { + if (typeof properties !== "object") return options; + return {...options, ...properties}; + } + return L.geoJSON(geojson, { style }); + } +} + +function get_tabler_color(name) { + return getComputedStyle(document.documentElement).getPropertyValue('--tblr-' + name); } document.addEventListener('DOMContentLoaded', function () { diff --git a/sqlpage/templates/map.handlebars b/sqlpage/templates/map.handlebars index 53bf2c11..5f3dde96 100644 --- a/sqlpage/templates/map.handlebars +++ b/sqlpage/templates/map.handlebars @@ -17,7 +17,15 @@