diff --git a/Gemfile b/Gemfile index 74167afe..7ca8cb4d 100644 --- a/Gemfile +++ b/Gemfile @@ -37,7 +37,7 @@ gem 'resource_api', git: 'https://github.com/performant-software/resource-api.gi gem 'jwt_auth', git: 'https://github.com/performant-software/jwt-auth.git', tag: 'v0.1.3' # Core data -gem 'core_data_connector', git: 'https://github.com/performant-software/core-data-connector.git', tag: 'v0.1.121' +gem 'core_data_connector', git: 'https://github.com/performant-software/core-data-connector.git', tag: 'v0.1.122' # IIIF gem 'triple_eye_effable', git: 'https://github.com/performant-software/triple-eye-effable.git', tag: 'v0.2.7' diff --git a/Gemfile.lock b/Gemfile.lock index 18ec28e8..374ae736 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/performant-software/core-data-connector.git - revision: c29a4f5d0ac39ecac405b08481dd21db171be081 - tag: v0.1.121 + revision: f10cb17b4bf53c0389c6d3d23e25eb6173c00759 + tag: v0.1.122 specs: core_data_connector (0.1.0) activerecord-postgis-adapter (~> 11.0) diff --git a/client/package.json b/client/package.json index 3320fba0..8ccc35a6 100644 --- a/client/package.json +++ b/client/package.json @@ -11,10 +11,10 @@ "dependencies": { "@bunchtogether/vite-plugin-flow": "^1.0.2", "@clerk/react": "^6.0.1", - "@performant-software/geospatial": "^3.1.7", - "@performant-software/semantic-components": "^3.1.7", - "@performant-software/shared-components": "^3.1.7", - "@performant-software/user-defined-fields": "^3.1.7", + "@performant-software/geospatial": "^3.1.17", + "@performant-software/semantic-components": "^3.1.17", + "@performant-software/shared-components": "^3.1.17", + "@performant-software/user-defined-fields": "^3.1.17", "@peripleo/maplibre": "^0.8.7", "@peripleo/peripleo": "^0.8.7", "@samvera/clover-iiif": "^2.18.3", diff --git a/client/src/components/MapCertaintyControl.js b/client/src/components/MapCertaintyControl.js new file mode 100644 index 00000000..a24570d4 --- /dev/null +++ b/client/src/components/MapCertaintyControl.js @@ -0,0 +1,70 @@ +// @flow + +import cx from 'classnames'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { TbCircleDashed } from 'react-icons/tb'; +import { MapControl } from '@performant-software/geospatial'; +import styles from './MapCertaintyControl.module.css'; + +type Props = { + data: any, + onChange: (data: any) => void +}; + +const MapCertaintyControl = (props: Props) => { + const { t } = useTranslation(); + const [showCertaintyRadiusInput, setShowCertaintyRadiusInput] = useState(false); + + const certaintyRadius = useMemo(() => ( + props.data?.certainty_radius || 0 + ), [props.data]); + + const onCertaintyRadiusChange = useCallback((e) => { + const radius = parseFloat(e.target.value) || 0; + + props.onChange({ + ...props.data, + certainty_radius: radius + }); + }, [props.data, props.onChange]); + + return ( + +
+ + { showCertaintyRadiusInput && ( +
+ +
+ )} +
+
+ ); +}; + +export default MapCertaintyControl; diff --git a/client/src/components/MapCertaintyControl.module.css b/client/src/components/MapCertaintyControl.module.css new file mode 100644 index 00000000..3dece1cb --- /dev/null +++ b/client/src/components/MapCertaintyControl.module.css @@ -0,0 +1,21 @@ +.certaintyRadiusControl { + display: inline-flex; + justify-content: center; + align-items: center; + margin: 0; + padding: 0; +} + +.certaintyRadiusControl .certaintyRadiusInputContainer { + margin: 0; + height: 20px; + position: absolute; + left: 40px; + top: -2px; + width: 100px; + background-color: white; +} + +.certaintyRadiusControl > .ui.input { + margin: 0; +} diff --git a/client/src/components/PlaceForm.js b/client/src/components/PlaceForm.js index 6bd4a96e..8098b571 100644 --- a/client/src/components/PlaceForm.js +++ b/client/src/components/PlaceForm.js @@ -1,6 +1,7 @@ // @flow import { + CertaintyLayer, GeoJsonLayer, LayerMenu, MapControl, @@ -29,6 +30,7 @@ import PlaceLayerModal from './PlaceLayerModal'; import PlaceLayerUtils from '../utils/PlaceLayers'; import PlaceNameModal from './PlaceNameModal'; import styles from './PlaceForm.module.css'; +import MapCertaintyControl from './MapCertaintyControl'; type Props = EditContainerProps & { item: PlaceType @@ -62,7 +64,7 @@ const PlaceForm = (props: Props) => { }, [geocoding]); /** - * Memo-izes the names of the passed place layers. + * Memoizes the names of the passed place layers. */ const layerNames = useMemo(() => _.pluck(props.item.place_layers, 'name'), [props.item.place_layers]); @@ -115,7 +117,7 @@ const PlaceForm = (props: Props) => { /** * Sets map geometry data on the item, destroying any existing geometry record - * if all geometry is deleted. + * if all geometry is deleted. */ useEffect(() => { if (mapData !== null) { @@ -124,7 +126,12 @@ const PlaceForm = (props: Props) => { place_geometry: { id: props.item.place_geometry.id, _destroy: true } }); } else { - props.onSetState({ place_geometry: mapData }) + props.onSetState({ + place_geometry: { + geometry_json: mapData.geometry_json || props.item.place_geometry?.geometry_json, + properties: mapData.properties || props.item.place_geometry?.properties + } + }); } } }, [mapData, props.item.place_geometry?.id]); @@ -134,7 +141,20 @@ const PlaceForm = (props: Props) => { * * @type {function(*): *} */ - const onMapChange = useCallback((data) => setMapData({ geometry_json: data }), []); + const onMapChange = (data) => setMapData({ + properties: props.item.place_geometry?.properties, + geometry_json: data + }); + + /** + * Sets the new map properties on the state. + * + * @type {function(*): *} + */ + const onPropertiesChange = (data) => setMapData({ + geometry_json: props.item.place_geometry?.geometry_json, + properties: data + }); /** * Sets the uploaded file as the GeoJSON object. @@ -246,6 +266,10 @@ const PlaceForm = (props: Props) => { WebkitBoxShadow: 'rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px' }} > + @@ -282,6 +306,10 @@ const PlaceForm = (props: Props) => { onSelection={onUpload} /> + diff --git a/client/src/i18n/en.json b/client/src/i18n/en.json index 2a58f8ea..90cab96c 100644 --- a/client/src/i18n/en.json +++ b/client/src/i18n/en.json @@ -504,7 +504,8 @@ "location": "Location", "names": "Names", "polygonMode": "Polygon mode", - "pointMode": "Point mode" + "pointMode": "Point mode", + "certaintyRadius": "Certainty radius (in KM)" }, "placeLayers": { "columns": { diff --git a/client/src/transforms/Place.js b/client/src/transforms/Place.js index 29aca8ec..3ec39f0d 100644 --- a/client/src/transforms/Place.js +++ b/client/src/transforms/Place.js @@ -69,7 +69,7 @@ class Place extends BaseTransform { } /** - * Returns the place for POST/PUT requests as a plain Javascript object. + * Returns the place for POST/PUT requests as a plain JavaScript object. * * @param place * diff --git a/client/src/transforms/PlaceGeometry.js b/client/src/transforms/PlaceGeometry.js index 6c554428..0e1ebe61 100644 --- a/client/src/transforms/PlaceGeometry.js +++ b/client/src/transforms/PlaceGeometry.js @@ -34,7 +34,7 @@ class PlaceGeometry extends BaseTransform { * * @param place * - * @returns {{place_geometry: (*&{geometry_json: string})}} + * @returns {{place_geometry: (*&{geometry_json: string, properties: string})}} */ toPayload(place: PlaceType) { const { place_geometry: placeGeometry } = place; @@ -42,7 +42,8 @@ class PlaceGeometry extends BaseTransform { return { [this.getParameterName()]: { ..._.pick(placeGeometry, this.getPayloadKeys()), - geometry_json: JSON.stringify(placeGeometry?.geometry_json) + geometry_json: JSON.stringify(placeGeometry?.geometry_json), + properties: placeGeometry?.properties } }; } diff --git a/client/src/types/Place.js b/client/src/types/Place.js index 474cc99d..72ece979 100644 --- a/client/src/types/Place.js +++ b/client/src/types/Place.js @@ -16,7 +16,8 @@ export type PlaceName = { export type PlaceGeometry = { id: number, - geometry_json: string + geometry_json: string, + properties: string }; export type Place = { diff --git a/client/yarn.lock b/client/yarn.lock index 65bef1f2..e574ea0e 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1846,10 +1846,10 @@ remark-rehype "^11.1.0" unified "^11.0.4" -"@performant-software/geospatial@^3.1.7": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@performant-software/geospatial/-/geospatial-3.1.7.tgz#59a3db0f97888ecdba3e3fac4306712412f6c2e1" - integrity sha512-TZC7BJKetg8YFWdcCqCWe/ThCmSHEpISiT6b/QFCwjp/Lat2UBee/qpsfBybDesL24ainMfEit5ZsHS0fNh/4w== +"@performant-software/geospatial@^3.1.17": + version "3.1.17" + resolved "https://registry.yarnpkg.com/@performant-software/geospatial/-/geospatial-3.1.17.tgz#7ef6dc4663a7e6b4333a5fa3d9a836ef00b360a6" + integrity sha512-kfb3DC5KV6AaE85whW1c15UXdRlacbLeyhhzxBsh64YHORORuIvKF4MKOTO9nQzcjwuuDDkWhuIg9oVWTsefEg== dependencies: "@allmaps/maplibre" "^1.0.0-beta.25" "@mapbox/mapbox-gl-draw" "1.4.3" @@ -1861,10 +1861,10 @@ react-map-gl "^8.0.4" underscore "^1.13.7" -"@performant-software/semantic-components@^3.1.7": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@performant-software/semantic-components/-/semantic-components-3.1.7.tgz#bc4a130ca9a95fe7f9abb2ec39b88d34d8e739fc" - integrity sha512-XQ0UyIoO77sZFjhVWp4V3V3VdtqTpZdN8mbL71HlsYA6ZXJfgBeFs58VLX9mEMGpKMnCATpw5FLqikWGrOkhQg== +"@performant-software/semantic-components@^3.1.17": + version "3.1.17" + resolved "https://registry.yarnpkg.com/@performant-software/semantic-components/-/semantic-components-3.1.17.tgz#9aeede214994dc5dd802743671623251e2d92235" + integrity sha512-D8nCVALu91lM+YAu6zUpDHX9shhxJjUhkD/qmKPhaOgYhzsOGUzy72wuUYRcNu1Y1MTx14d1f09K/qeBAEcmaA== dependencies: "@react-google-maps/api" "^2.20.7" axios "^1.10.0" @@ -1882,10 +1882,10 @@ zotero-api-client "^0.40.0" zotero-translation-client "^5.0.1" -"@performant-software/shared-components@^3.1.7": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@performant-software/shared-components/-/shared-components-3.1.7.tgz#f4618db460003fa92349cc2d5aa9d93085f20be9" - integrity sha512-uLfIdG1kYGUwfa8B28EaI92bnBJz6ya2Idol+b1h/OY9Yu2z643aAMSEiERtAm4ufRnxzMkSeBOzJUWcUd3jnw== +"@performant-software/shared-components@^3.1.17": + version "3.1.17" + resolved "https://registry.yarnpkg.com/@performant-software/shared-components/-/shared-components-3.1.17.tgz#f57880443a40b72f2ce415ac6a641b2bcfd8e108" + integrity sha512-LMXFiUHKmjX223aWqiui+wRzGuLPhPw6hS2YbPudfE+2Fx0jhoMfq0Ks4tMXmhgfdms7bNw8XAfyD+OG6CEGDg== dependencies: "@rails/activestorage" "^8.0.201" "@react-google-maps/api" "^2.20.7" @@ -1901,10 +1901,10 @@ underscore "^1.13.7" zotero-translation-client "^5.0.1" -"@performant-software/user-defined-fields@^3.1.7": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@performant-software/user-defined-fields/-/user-defined-fields-3.1.7.tgz#d6d5b2177f6ec3215db0668abcaa9e6801252a06" - integrity sha512-Wx6dIn0avVELoOhnsz6VpaE9w2p6AaeKi8mDxfM01AMoXXYrDMrwea+ch2LAUZ08t5UHfarvHDcyjMqvTW0T7g== +"@performant-software/user-defined-fields@^3.1.17": + version "3.1.17" + resolved "https://registry.yarnpkg.com/@performant-software/user-defined-fields/-/user-defined-fields-3.1.17.tgz#95b5a58151f3f61a1f4ab5f018fd8b06f4b87e3c" + integrity sha512-3U4bWTIvvfAZvVce4ue3xIQrJOJ/nU4mFsAYxKltBll65ZTL6I1LZ3yadVE2UTGirUKPwjzIh7UbwM9u4nZdfw== dependencies: i18next "^25.3.2" semantic-ui-react "^2.1.5" diff --git a/db/migrate/20260402204332_add_properties_to_place_geometries.core_data_connector.rb b/db/migrate/20260402204332_add_properties_to_place_geometries.core_data_connector.rb new file mode 100644 index 00000000..8c2683a3 --- /dev/null +++ b/db/migrate/20260402204332_add_properties_to_place_geometries.core_data_connector.rb @@ -0,0 +1,6 @@ +# This migration comes from core_data_connector (originally 20260402204230) +class AddPropertiesToPlaceGeometries < ActiveRecord::Migration[8.0] + def change + add_column :core_data_connector_place_geometries, :properties, :jsonb, default: {} + end +end diff --git a/db/schema.rb b/db/schema.rb index f6e78357..08cff001 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_03_10_203200) do +ActiveRecord::Schema[8.0].define(version: 2026_04_02_204332) do create_schema "heroku_ext" # These are extensions that must be enabled in order to support this database @@ -180,6 +180,7 @@ t.geometry "geometry", limit: {srid: 0, type: "geometry"} t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.jsonb "properties", default: {} t.index ["place_id"], name: "index_core_data_connector_place_geometries_on_place_id" end