diff --git a/CHANGELOG.md b/CHANGELOG.md index 0791ca6..4591cf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## v0.3.4 +- Added: support for custom marker hex colors. A color picker is now available for the datalayer default marker color and for each per-value marker, so you can override the predefined marker colors with any custom hex value. Custom colors are applied inline on the map, while preset colors keep using their `marker-` CSS classes. + ## v0.3.3 - Fixed: maps on the location and datalayer edit screens could stay grey / only load the top-left tile until the browser window was resized. Maps now recalculate their size via a ResizeObserver (and map.whenReady), so they render correctly on initial load and when becoming visible. - Fixed: addMarker() referenced an undefined `location` variable (the global window.location) for the marker colour/icon; colour and icon are now optional parameters with a sensible CSS default. diff --git a/openkaarten-base.php b/openkaarten-base.php index caac53c..fd340f4 100644 --- a/openkaarten-base.php +++ b/openkaarten-base.php @@ -13,7 +13,7 @@ * Plugin Name: OpenKaarten Base * Plugin URI: https://www.openwebconcept.nl * Description: The OpenKaarten Base plugin. - * Version: 0.3.3 + * Version: 0.3.4 * Author: Acato * Author URI: https://www.acato.nl * License: EUPL-1.2 @@ -27,7 +27,7 @@ die; } -define( 'OWC_OPENKAARTEN_BASE_VERSION', '0.3.3' ); +define( 'OWC_OPENKAARTEN_BASE_VERSION', '0.3.4' ); if ( ! defined( 'OWC_OPENKAARTEN_BASE_ABSPATH' ) ) { define( 'OWC_OPENKAARTEN_BASE_ABSPATH', plugin_dir_path( __FILE__ ) ); diff --git a/package-lock.json b/package-lock.json index 5b347b6..ccd01d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "version": "7.24.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.2", @@ -2378,6 +2379,7 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2410,6 +2412,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2883,6 +2886,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -4894,6 +4898,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz", "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==", + "peer": true, "dependencies": { "file-type": "^12.0.0", "globby": "^10.0.0", @@ -5342,7 +5347,8 @@ "node_modules/leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true }, "node_modules/leaflet.markercluster": { "version": "1.5.3", @@ -6191,6 +6197,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -7163,6 +7170,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -8168,6 +8176,7 @@ "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -8213,6 +8222,7 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", @@ -8281,6 +8291,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -8388,6 +8399,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -8678,6 +8690,7 @@ "version": "7.24.4", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.2", @@ -10407,7 +10420,8 @@ "acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true }, "acorn-import-attributes": { "version": "1.9.5", @@ -10429,6 +10443,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -10781,6 +10796,7 @@ "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "peer": true, "requires": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -12266,6 +12282,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz", "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==", + "peer": true, "requires": { "file-type": "^12.0.0", "globby": "^10.0.0", @@ -12585,7 +12602,8 @@ "leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true }, "leaflet.markercluster": { "version": "1.5.3", @@ -13217,6 +13235,7 @@ "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "peer": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -13857,6 +13876,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", "dev": true, + "peer": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -14608,6 +14628,7 @@ "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "peer": true, "requires": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -14655,6 +14676,7 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "peer": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", @@ -14686,6 +14708,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -14760,6 +14783,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", diff --git a/src/admin/class-cmb2.php b/src/admin/class-cmb2.php index 2b5336c..2326176 100644 --- a/src/admin/class-cmb2.php +++ b/src/admin/class-cmb2.php @@ -91,10 +91,11 @@ public static function cmb2_render_markerpreview_field_type( $field, $escaped_va return; } - $marker = $markers[ $group_index ]; - $color = $marker['marker_color']; - $icon = $marker['marker_icon']; - $icon_url = Locations::get_location_marker_url( $icon ) ? : ''; + $marker = $markers[ $group_index ]; + $color_value = ! empty( $marker['marker_color_custom'] ) ? $marker['marker_color_custom'] : ( isset( $marker['marker_color'] ) ? $marker['marker_color'] : '' ); + $color = Datalayers::resolve_marker_color_hex( $color_value ); + $icon = $marker['marker_icon']; + $icon_url = Locations::get_location_marker_url( $icon ) ? : ''; echo "
"; } diff --git a/src/admin/class-datalayers.php b/src/admin/class-datalayers.php index bafd00f..f47a8b1 100644 --- a/src/admin/class-datalayers.php +++ b/src/admin/class-datalayers.php @@ -536,6 +536,72 @@ public static function get_marker_color_options() { return apply_filters( 'openkaarten_marker_color_options', $default_colors ); } + /** + * Returns an associative array mapping the predefined marker-color class + * names to their hex color values. + * + * This mirrors get_marker_color_options() but provides the actual hex values, + * so the color-picker swatches and any place that needs to render a preset + * class as a real color can share a single source of truth. + * + * @return array Associative array of marker-color class names and their hex values. + */ + public static function get_marker_color_palette() { + $default_palette = [ + 'marker-black' => '#000000', + 'marker-blue' => '#0072B2', + 'marker-brown' => '#A0522D', + 'marker-darkgray' => '#555555', + 'marker-deep-purple' => '#4B0082', + 'marker-gray' => '#757575', + 'marker-green' => '#328725', + 'marker-navy-blue' => '#001D5F', + 'marker-orange' => '#F4801B', + 'marker-purple' => '#792487', + 'marker-red' => '#9F0000', + 'marker-turquoise' => '#3B7BA0', + 'marker-yellow' => '#7E7722', + ]; + + return apply_filters( 'openkaarten_marker_color_palette', $default_palette ); + } + + /** + * Check whether a value is a valid hex color (#rgb or #rrggbb). + * + * @param mixed $value The value to check. + * + * @return bool Whether the value is a hex color. + */ + public static function is_hex_color( $value ) { + return is_string( $value ) && 1 === preg_match( '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $value ); + } + + /** + * Resolve a stored marker-color value to a hex color. + * + * Accepts either a custom hex value (e.g. '#ff5733'), which is returned as-is, + * or a predefined marker-color class name (e.g. 'marker-blue'), which is looked + * up in the color palette. Returns an empty string when the value can't be resolved. + * + * @param string $value The stored marker-color value (hex or class name). + * + * @return string The resolved hex color, or an empty string. + */ + public static function resolve_marker_color_hex( $value ) { + if ( empty( $value ) ) { + return ''; + } + + if ( self::is_hex_color( $value ) ) { + return $value; + } + + $palette = self::get_marker_color_palette(); + + return isset( $palette[ $value ] ) ? $palette[ $value ] : ''; + } + /** * Customize the available marker color options for map markers. * @@ -600,6 +666,22 @@ public static function add_markers_metaboxes() { ] ); + $cmb->add_field( + [ + 'name' => __( 'Default custom marker color', 'openkaarten-base' ), + 'desc' => __( 'Optional. Pick a custom color to override the selected default marker color above.', 'openkaarten-base' ), + 'id' => 'default_marker_color_custom', + 'type' => 'colorpicker', + 'attributes' => [ + 'data-colorpicker' => wp_json_encode( + [ + 'palettes' => array_values( self::get_marker_color_palette() ), + ] + ), + ], + ] + ); + $cmb->add_field( [ 'name' => __( 'Field to customize marker on', 'openkaarten-base' ), @@ -659,6 +741,23 @@ public static function add_markers_metaboxes() { ] ); + $cmb->add_group_field( + $group_field_id, + [ + 'name' => __( 'Custom marker color', 'openkaarten-base' ), + 'desc' => __( 'Optional. Pick a custom color to override the selected marker color above.', 'openkaarten-base' ), + 'id' => 'marker_color_custom', + 'type' => 'colorpicker', + 'attributes' => [ + 'data-colorpicker' => wp_json_encode( + [ + 'palettes' => array_values( self::get_marker_color_palette() ), + ] + ), + ], + ] + ); + $cmb->add_group_field( $group_field_id, [ diff --git a/src/admin/class-locations.php b/src/admin/class-locations.php index 51b48b2..7983991 100644 --- a/src/admin/class-locations.php +++ b/src/admin/class-locations.php @@ -272,8 +272,9 @@ public static function get_location_marker( $datalayer_id, $location_id = false, $marker_field = get_post_meta( $datalayer_id, 'marker_field', true ); $markers = get_post_meta( $datalayer_id, 'markers', true ); - $color = get_post_meta( $datalayer_id, 'default_marker_color', true ); - $icon = get_post_meta( $datalayer_id, 'default_marker_icon', true ); + $default_custom_color = get_post_meta( $datalayer_id, 'default_marker_color_custom', true ); + $color = ! empty( $default_custom_color ) ? $default_custom_color : get_post_meta( $datalayer_id, 'default_marker_color', true ); + $icon = get_post_meta( $datalayer_id, 'default_marker_icon', true ); $datalayer_url_type = get_post_meta( $datalayer_id, 'datalayer_url_type', true ) ? : 'import'; @@ -302,7 +303,9 @@ public static function get_location_marker( $datalayer_id, $location_id = false, } if ( isset( $marker_data['field_value'] ) && $location_marker_field === $marker_data['field_value'] ) { - if ( ! empty( $marker_data['marker_color'] ) ) { + if ( ! empty( $marker_data['marker_color_custom'] ) ) { + $color = $marker_data['marker_color_custom']; + } elseif ( ! empty( $marker_data['marker_color'] ) ) { $color = $marker_data['marker_color']; } if ( ! empty( $marker_data['marker_icon'] ) ) { diff --git a/src/scripts/openstreetmap-base.js b/src/scripts/openstreetmap-base.js index 943f8e6..fa56699 100644 --- a/src/scripts/openstreetmap-base.js +++ b/src/scripts/openstreetmap-base.js @@ -63,8 +63,13 @@ function initializeMap() { const geojsonData = location.feature; const content = location.content; - // Create a custom marker icon with the location color and icon. - let customIconHtml = "
"; + // Create a custom marker icon with the location color and icon. A custom + // color is stored as a hex value and applied inline; a preset color is a + // "marker-" class that the stylesheet maps to a hex. + const isHexColor = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test( location.color || "" ); + let customIconHtml = isHexColor + ? "
" + : "
"; if (location.icon) { customIconHtml += "marker icon"; } diff --git a/src/scripts/openstreetmap-geodata.js b/src/scripts/openstreetmap-geodata.js index ffe73b9..408bbf0 100644 --- a/src/scripts/openstreetmap-geodata.js +++ b/src/scripts/openstreetmap-geodata.js @@ -136,9 +136,13 @@ function ensureMapSize( map, element ) { * @param {string} icon Optional marker icon URL. */ function addMarker( map, lat, lng, color, icon ) { - // Create a custom marker icon. Only add a colour class when one is explicitly - // provided; otherwise let the .marker-pin CSS default apply. - let customIconHtml = "
"; + // Create a custom marker icon. A custom colour is stored as a hex value and + // applied inline; a preset colour is a "marker-" class the stylesheet + // maps to a hex. When no colour is provided, let the .marker-pin CSS default apply. + const isHexColor = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test( color || "" ); + let customIconHtml = isHexColor + ? "
" + : "
"; if ( icon ) { customIconHtml += "marker icon"; }