diff --git a/backend_py/primary/primary/services/vds_access/vds_access.py b/backend_py/primary/primary/services/vds_access/vds_access.py index 0073c350e..449ce65de 100644 --- a/backend_py/primary/primary/services/vds_access/vds_access.py +++ b/backend_py/primary/primary/services/vds_access/vds_access.py @@ -94,8 +94,14 @@ async def get_inline_slice_async(self, line_no: int) -> Tuple[NDArray[np.float32 response = await self._query_async(endpoint, slice_request) parts = self._extract_and_validate_body_parts_from_response(response) - - metadata = VdsSliceMetadata(**json.loads(parts[0].content)) + response_metadata = json.loads(parts[0].content) + metadata = VdsSliceMetadata( + format=response_metadata["format"], + shape=response_metadata["shape"], + x_axis=VdsAxis(**response_metadata["x"]), + y_axis=VdsAxis(**response_metadata["y"]), + geospatial=response_metadata["geospatial"], + ) self._assert_valid_metadata_format_and_shape(metadata) byte_array = parts[1].content @@ -254,7 +260,7 @@ async def get_flattened_fence_traces_array_and_metadata_async( # Convert every value of `hard_coded_fill_value` to np.nan flattened_fence_traces_float32_array[flattened_fence_traces_float32_array == hard_coded_fill_value] = np.nan - + print("flattened fence data", flattened_fence_traces_float32_array, len(flattened_fence_traces_float32_array)) return (flattened_fence_traces_float32_array, num_traces, num_samples_per_trace) def _extract_and_validate_body_parts_from_response(self, response: httpx.Response) -> Tuple[BodyPart, BodyPart]: diff --git a/frontend/index.html b/frontend/index.html index 80a372409..7a18853bd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - + Webviz | FMU results visualization diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 98f159ec8..9f9c081d4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,14 @@ "name": "webviz", "version": "0.0.0", "dependencies": { + "@deck.gl/aggregation-layers": "^9.0.33", + "@deck.gl/core": "^9.0.33", + "@deck.gl/extensions": "^9.0.33", + "@deck.gl/geo-layers": "^9.0.33", + "@deck.gl/json": "^9.0.33", + "@deck.gl/layers": "^9.0.33", + "@deck.gl/mesh-layers": "^9.0.33", + "@deck.gl/react": "^9.0.33", "@equinor/eds-core-react": "^0.45.1", "@equinor/esv-intersection": "^3.1.2", "@headlessui/react": "^1.7.8", @@ -37,7 +45,8 @@ "simplify-js": "^1.2.4", "uuid": "^9.0.0", "vite-plugin-node-polyfills": "^0.23.0", - "wonka": "^6.3.4" + "wonka": "^6.3.4", + "workerpool": "^9.2.0" }, "devDependencies": { "@hey-api/openapi-ts": "^0.61.1", @@ -52,6 +61,7 @@ "@types/react-dom": "^18.2.7", "@types/react-plotly.js": "^2.6.0", "@types/uuid": "^9.0.0", + "@types/workerpool": "^6.4.7", "@vitejs/plugin-react": "^4.4.1", "@vitest/coverage-istanbul": "^3.0.7", "dependency-cruiser": "^14.0.0", @@ -70,6 +80,7 @@ "typescript-eslint": "^8.25.0", "vite": "^6.3.5", "vite-plugin-checker": "^0.9.0", + "vite-plugin-glsl": "^1.3.1", "vitest": "^3.0.7" } }, @@ -99,6 +110,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -113,6 +125,7 @@ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -122,6 +135,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -147,17 +161,12 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/generator": { + "node_modules/@babel/core/node_modules/@babel/generator": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.27.1", "@babel/types": "^7.27.1", @@ -169,11 +178,61 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/core/node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/core/node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -189,6 +248,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -197,11 +257,67 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-module-imports/node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports/node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-module-imports/node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/helper-module-transforms": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", @@ -214,6 +330,65 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", @@ -228,6 +403,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -236,6 +412,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -245,6 +422,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -254,6 +432,7 @@ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.27.1", "@babel/types": "^7.27.1" @@ -266,6 +445,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" }, @@ -312,6 +492,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -320,6 +501,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -329,35 +511,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -377,70 +535,11 @@ "findup": "bin/findup.js" } }, - "node_modules/@deck.gl-community/editable-layers": { - "version": "9.1.0-beta.5", - "resolved": "https://registry.npmjs.org/@deck.gl-community/editable-layers/-/editable-layers-9.1.0-beta.5.tgz", - "integrity": "sha512-kMlGepC81fRPE4Vuu9bsF4dv0Jz+5ZIfYRmfedWNyC1S9zJAFBBkIwkfFPEMC8IkL4Tjd1CRfCVyOcy3CqBd+w==", - "dependencies": { - "@turf/along": "^6.5.0", - "@turf/area": "^6.5.0", - "@turf/bbox": "^6.5.0", - "@turf/bbox-polygon": "^6.5.0", - "@turf/bearing": "^6.5.0", - "@turf/boolean-point-in-polygon": "^6.5.0", - "@turf/buffer": "^6.5.0", - "@turf/center": "^6.5.0", - "@turf/centroid": "^6.5.0", - "@turf/circle": "^6.5.0", - "@turf/clone": "^6.5.0", - "@turf/destination": "^6.5.0", - "@turf/difference": "^6.5.0", - "@turf/distance": "^6.5.0", - "@turf/ellipse": "^6.5.0", - "@turf/helpers": "^6.5.0", - "@turf/intersect": "^6.5.0", - "@turf/invariant": "^6.5.0", - "@turf/line-intersect": "^6.5.0", - "@turf/meta": "^6.5.0", - "@turf/midpoint": "^6.5.0", - "@turf/nearest-point-on-line": "^6.5.0", - "@turf/point-to-line-distance": "^6.5.0", - "@turf/polygon-to-line": "^6.5.0", - "@turf/rewind": "^6.5.0", - "@turf/transform-rotate": "^6.5.0", - "@turf/transform-scale": "^6.5.0", - "@turf/transform-translate": "^6.5.0", - "@turf/union": "^6.5.0", - "@types/geojson": "^7946.0.14", - "cubic-hermite-spline": "^1.0.1", - "eventemitter3": "^5.0.0", - "lodash.omit": "^4.1.1", - "lodash.throttle": "^4.1.1", - "uuid": "9.0.0", - "viewport-mercator-project": ">=6.2.3" - }, - "peerDependencies": { - "@deck.gl-community/layers": "^9.1.0-beta.2", - "@deck.gl/core": "^9.1.0", - "@deck.gl/extensions": "^9.1.0", - "@deck.gl/geo-layers": "^9.1.0", - "@deck.gl/layers": "^9.1.0", - "@deck.gl/mesh-layers": "^9.1.0", - "@luma.gl/constants": ">=9.1.0", - "@luma.gl/core": ">=9.1.0", - "@luma.gl/engine": ">=9.1.0", - "@math.gl/core": ">=4.0.1" - } - }, - "node_modules/@deck.gl-community/editable-layers/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, "node_modules/@deck.gl-community/layers": { "version": "9.1.0-beta.5", "resolved": "https://registry.npmjs.org/@deck.gl-community/layers/-/layers-9.1.0-beta.5.tgz", "integrity": "sha512-1aYrRBCzwYG4K7thxLvKlBIiDFxJPr8UElzqLVDPSdAsp6TDrlQYp/1NmHfzVu1KQeRg+ovcKalkB9gDXuCqOw==", + "license": "MIT", "peer": true, "dependencies": { "@deck.gl/core": "^9.1.0", @@ -460,28 +559,189 @@ "@math.gl/core": "^4.0.0" } }, - "node_modules/@deck.gl/aggregation-layers": { + "node_modules/@deck.gl-community/layers/node_modules/@deck.gl/extensions": { "version": "9.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-9.1.11.tgz", - "integrity": "sha512-3sVHcNsLj8Lzevf/2uZUn4pv6Z5Bpz7CzwataQ0EWGjjzHJ/4uIi24EIYEJmTzLGueenFS+70s8hWujJaAJMoQ==", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.1.11.tgz", + "integrity": "sha512-wnWUhd7yyQTdHB0x2fE/FPeZUz5ZzKNDx2FwY+bQuQVaiNcc5YpGKQSV0ahIKUg6NF+shxZIJ/WBVi3wc6RFRQ==", + "license": "MIT", + "peer": true, "dependencies": { "@luma.gl/constants": "^9.1.5", "@luma.gl/shadertools": "^9.1.5", + "@math.gl/core": "^4.1.0" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, + "node_modules/@deck.gl-community/layers/node_modules/@deck.gl/geo-layers": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-9.1.11.tgz", + "integrity": "sha512-z2rYT9617JaIP+a1nKhbxu32a5BjiR5L0Tmd4x7UUOlipx6A5850SyPyIUx5ca7lpIlbukGRjg600pRKKk3XEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@loaders.gl/3d-tiles": "^4.2.0", + "@loaders.gl/gis": "^4.2.0", + "@loaders.gl/loader-utils": "^4.2.0", + "@loaders.gl/mvt": "^4.2.0", + "@loaders.gl/schema": "^4.2.0", + "@loaders.gl/terrain": "^4.2.0", + "@loaders.gl/tiles": "^4.2.0", + "@loaders.gl/wms": "^4.2.0", + "@luma.gl/gltf": "^9.1.5", + "@luma.gl/shadertools": "^9.1.5", "@math.gl/core": "^4.1.0", + "@math.gl/culling": "^4.1.0", "@math.gl/web-mercator": "^4.1.0", - "d3-hexbin": "^0.2.1" + "@types/geojson": "^7946.0.8", + "h3-js": "^4.1.0", + "long": "^3.2.0" }, "peerDependencies": { "@deck.gl/core": "^9.1.0", + "@deck.gl/extensions": "^9.1.0", "@deck.gl/layers": "^9.1.0", + "@deck.gl/mesh-layers": "^9.1.0", + "@loaders.gl/core": "^4.2.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, + "node_modules/@deck.gl-community/layers/node_modules/@deck.gl/layers": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.1.11.tgz", + "integrity": "sha512-RDFF+YH4BP3HPUCtblnfTaWXBt1DBk0i7FbQ9jgcjdv4CB7MtEOHme5k2LJ0cBV4ROQtSHg/vjAvMayZewI4vg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@loaders.gl/images": "^4.2.0", + "@loaders.gl/schema": "^4.2.0", + "@luma.gl/shadertools": "^9.1.5", + "@mapbox/tiny-sdf": "^2.0.5", + "@math.gl/core": "^4.1.0", + "@math.gl/polygon": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "earcut": "^2.2.4" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@loaders.gl/core": "^4.2.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, + "node_modules/@deck.gl-community/layers/node_modules/@deck.gl/mesh-layers": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.1.11.tgz", + "integrity": "sha512-9zdtRV8rVyJI4t8bPE3/jLEuiBfCp+53KneFrh/nmANVupZ5SP44TobSWJWl+nZ3Xs4LkJXaQPbI8gZR39lJBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@loaders.gl/gltf": "^4.2.0", + "@luma.gl/gltf": "^9.1.5", + "@luma.gl/shadertools": "^9.1.5" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", "@luma.gl/core": "^9.1.5", "@luma.gl/engine": "^9.1.5" } }, + "node_modules/@deck.gl-community/layers/node_modules/@luma.gl/constants": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.9.tgz", + "integrity": "sha512-yc9fml04OeTTcwK+7gmDMxoLQ67j4ZiAFXjmYvPomYyBVzS0NZxTDuwcCBmnxjLOiroOZW8FRRrVc/yOiFug2w==", + "license": "MIT", + "peer": true + }, + "node_modules/@deck.gl-community/layers/node_modules/@luma.gl/core": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.1.9.tgz", + "integrity": "sha512-1i9N7+I/UbFjx3axSMlc3/NufA+C2iBv/7mw51gRE/ypQPgvFmY/QqXBVZRe+nthF+OhlUMhO19TBndzYFTWhA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@math.gl/types": "^4.1.0", + "@probe.gl/env": "^4.0.8", + "@probe.gl/log": "^4.0.8", + "@probe.gl/stats": "^4.0.8", + "@types/offscreencanvas": "^2019.6.4" + } + }, + "node_modules/@deck.gl-community/layers/node_modules/@luma.gl/engine": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.1.9.tgz", + "integrity": "sha512-n1GLK1sUMFkWxdb+aZYn6ZBFltFEMi7X+6ZPxn2pBsNT6oeF4AyvH5AyqhOpvHvUnCLDt3Zsf1UIfx3MI//YSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@math.gl/core": "^4.1.0", + "@math.gl/types": "^4.1.0", + "@probe.gl/log": "^4.0.8", + "@probe.gl/stats": "^4.0.8" + }, + "peerDependencies": { + "@luma.gl/core": "^9.1.0", + "@luma.gl/shadertools": "^9.1.0" + } + }, + "node_modules/@deck.gl-community/layers/node_modules/@luma.gl/gltf": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/gltf/-/gltf-9.1.9.tgz", + "integrity": "sha512-KgVBIFCtRO1oadgMDycMJA5s+q519l/fQBGAZpUcLfWsaEDQfdHW2NLdrK/00VDv46Ng8tN/O6uyH6E40uLcLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@loaders.gl/core": "^4.2.0", + "@loaders.gl/textures": "^4.2.0", + "@math.gl/core": "^4.1.0" + }, + "peerDependencies": { + "@luma.gl/core": "^9.1.0", + "@luma.gl/engine": "^9.1.0", + "@luma.gl/shadertools": "^9.1.0" + } + }, + "node_modules/@deck.gl-community/layers/node_modules/@luma.gl/shadertools": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.1.9.tgz", + "integrity": "sha512-Uqp2xfgIEunRMLXTeCJ4uEMlWcUGcYMZGJ8GAOrAeDzn4bMKVRKmZDC71vkuTctnaodM3UdrI9W6s1sJlrXsxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@math.gl/core": "^4.1.0", + "@math.gl/types": "^4.1.0", + "wgsl_reflect": "^1.2.0" + }, + "peerDependencies": { + "@luma.gl/core": "^9.1.0" + } + }, + "node_modules/@deck.gl/aggregation-layers": { + "version": "9.0.40", + "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-9.0.40.tgz", + "integrity": "sha512-tZ3NEDVlZnCnwbxdoB+qB184gSricnbcOZwkPHqNWk+2wadyd6Q0j9V9aZ9V6M5BGDn86DrOKPFygwmUl/jkwA==", + "dependencies": { + "@luma.gl/constants": "~9.0.27", + "@luma.gl/shadertools": "~9.0.27", + "@math.gl/web-mercator": "^4.0.0", + "d3-hexbin": "^0.2.1" + }, + "peerDependencies": { + "@deck.gl/core": "^9.0.0", + "@deck.gl/layers": "^9.0.0", + "@luma.gl/core": "~9.0.0", + "@luma.gl/engine": "~9.0.0" + } + }, "node_modules/@deck.gl/core": { "version": "9.1.11", "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-9.1.11.tgz", "integrity": "sha512-ibqeLgx7fRqvJeCWtugbPE/LcOl3uDy8wSSd+l0FBhOqOPQcLdluTVMs0JW2OyqMuR2eOqcfarxzXdDVM7CcJA==", + "license": "MIT", "dependencies": { "@loaders.gl/core": "^4.2.0", "@loaders.gl/images": "^4.2.0", @@ -502,25 +762,88 @@ "mjolnir.js": "^3.0.0" } }, + "node_modules/@deck.gl/core/node_modules/@luma.gl/constants": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.9.tgz", + "integrity": "sha512-yc9fml04OeTTcwK+7gmDMxoLQ67j4ZiAFXjmYvPomYyBVzS0NZxTDuwcCBmnxjLOiroOZW8FRRrVc/yOiFug2w==", + "license": "MIT" + }, + "node_modules/@deck.gl/core/node_modules/@luma.gl/core": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.1.9.tgz", + "integrity": "sha512-1i9N7+I/UbFjx3axSMlc3/NufA+C2iBv/7mw51gRE/ypQPgvFmY/QqXBVZRe+nthF+OhlUMhO19TBndzYFTWhA==", + "license": "MIT", + "dependencies": { + "@math.gl/types": "^4.1.0", + "@probe.gl/env": "^4.0.8", + "@probe.gl/log": "^4.0.8", + "@probe.gl/stats": "^4.0.8", + "@types/offscreencanvas": "^2019.6.4" + } + }, + "node_modules/@deck.gl/core/node_modules/@luma.gl/engine": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.1.9.tgz", + "integrity": "sha512-n1GLK1sUMFkWxdb+aZYn6ZBFltFEMi7X+6ZPxn2pBsNT6oeF4AyvH5AyqhOpvHvUnCLDt3Zsf1UIfx3MI//YSw==", + "license": "MIT", + "dependencies": { + "@math.gl/core": "^4.1.0", + "@math.gl/types": "^4.1.0", + "@probe.gl/log": "^4.0.8", + "@probe.gl/stats": "^4.0.8" + }, + "peerDependencies": { + "@luma.gl/core": "^9.1.0", + "@luma.gl/shadertools": "^9.1.0" + } + }, + "node_modules/@deck.gl/core/node_modules/@luma.gl/shadertools": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.1.9.tgz", + "integrity": "sha512-Uqp2xfgIEunRMLXTeCJ4uEMlWcUGcYMZGJ8GAOrAeDzn4bMKVRKmZDC71vkuTctnaodM3UdrI9W6s1sJlrXsxw==", + "license": "MIT", + "dependencies": { + "@math.gl/core": "^4.1.0", + "@math.gl/types": "^4.1.0", + "wgsl_reflect": "^1.2.0" + }, + "peerDependencies": { + "@luma.gl/core": "^9.1.0" + } + }, + "node_modules/@deck.gl/core/node_modules/@luma.gl/webgl": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-9.1.9.tgz", + "integrity": "sha512-jecHjhNSWkXH0v62rM6G5fIIkOmsrND27099iKgdutFvHIvd4QS4UzGWEEa9AEPlP0rTLqXkA6y6YL7f42ZkVg==", + "license": "MIT", + "dependencies": { + "@luma.gl/constants": "9.1.9", + "@math.gl/types": "^4.1.0", + "@probe.gl/env": "^4.0.8" + }, + "peerDependencies": { + "@luma.gl/core": "^9.1.0" + } + }, "node_modules/@deck.gl/extensions": { - "version": "9.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.1.11.tgz", - "integrity": "sha512-wnWUhd7yyQTdHB0x2fE/FPeZUz5ZzKNDx2FwY+bQuQVaiNcc5YpGKQSV0ahIKUg6NF+shxZIJ/WBVi3wc6RFRQ==", + "version": "9.0.40", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.0.40.tgz", + "integrity": "sha512-1lESbg4NLkXxonO5f6aEX9a1DP4d8Vd+YLz9O4SwBJeZQZSeo4d3iJuJPL3MyA4thqdaA1Q9Vi8gtD9Mru6asg==", "dependencies": { - "@luma.gl/constants": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5", - "@math.gl/core": "^4.1.0" + "@luma.gl/constants": "~9.0.27", + "@luma.gl/shadertools": "~9.0.27", + "@math.gl/core": "^4.0.0" }, "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" + "@deck.gl/core": "^9.0.0", + "@luma.gl/core": "~9.0.0", + "@luma.gl/engine": "~9.0.0" } }, "node_modules/@deck.gl/geo-layers": { - "version": "9.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-9.1.11.tgz", - "integrity": "sha512-z2rYT9617JaIP+a1nKhbxu32a5BjiR5L0Tmd4x7UUOlipx6A5850SyPyIUx5ca7lpIlbukGRjg600pRKKk3XEA==", + "version": "9.0.40", + "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-9.0.40.tgz", + "integrity": "sha512-kIxryoyWzicqLvAImtuUSX7NEntSdMIjCq0DoDdvSfhE37R0FxDRc5Vse5fjg9/1nNshw7mEcD2KxUS8MI8ypA==", "dependencies": { "@loaders.gl/3d-tiles": "^4.2.0", "@loaders.gl/gis": "^4.2.0", @@ -530,29 +853,30 @@ "@loaders.gl/terrain": "^4.2.0", "@loaders.gl/tiles": "^4.2.0", "@loaders.gl/wms": "^4.2.0", - "@luma.gl/gltf": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5", - "@math.gl/core": "^4.1.0", - "@math.gl/culling": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", + "@luma.gl/gltf": "~9.0.27", + "@luma.gl/shadertools": "~9.0.27", + "@math.gl/core": "^4.0.0", + "@math.gl/culling": "^4.0.0", + "@math.gl/web-mercator": "^4.0.0", "@types/geojson": "^7946.0.8", "h3-js": "^4.1.0", "long": "^3.2.0" }, "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@deck.gl/extensions": "^9.1.0", - "@deck.gl/layers": "^9.1.0", - "@deck.gl/mesh-layers": "^9.1.0", + "@deck.gl/core": "^9.0.0", + "@deck.gl/extensions": "^9.0.0", + "@deck.gl/layers": "^9.0.0", + "@deck.gl/mesh-layers": "^9.0.0", "@loaders.gl/core": "^4.2.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" + "@luma.gl/core": "~9.0.0", + "@luma.gl/engine": "~9.0.0" } }, "node_modules/@deck.gl/json": { "version": "9.1.11", "resolved": "https://registry.npmjs.org/@deck.gl/json/-/json-9.1.11.tgz", "integrity": "sha512-rSV5AqWqmt0/C9rgldk21Pbsfqk+OrrttG42VZBjCYW3RkosSOr0aUhSlAx3/tERXUNZoN46WBtzQWV1GQptxg==", + "license": "MIT", "dependencies": { "jsep": "^0.3.0" }, @@ -561,45 +885,45 @@ } }, "node_modules/@deck.gl/layers": { - "version": "9.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.1.11.tgz", - "integrity": "sha512-RDFF+YH4BP3HPUCtblnfTaWXBt1DBk0i7FbQ9jgcjdv4CB7MtEOHme5k2LJ0cBV4ROQtSHg/vjAvMayZewI4vg==", + "version": "9.0.40", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.0.40.tgz", + "integrity": "sha512-7emTkPLVWeVuV5VPBTmHDJegkGH1i76GuvhHNXikper/Zwljf9QVUacPqk3or4lHBCzzlkeccCsRmTd3l+MBYQ==", "dependencies": { "@loaders.gl/images": "^4.2.0", "@loaders.gl/schema": "^4.2.0", - "@luma.gl/shadertools": "^9.1.5", "@mapbox/tiny-sdf": "^2.0.5", - "@math.gl/core": "^4.1.0", - "@math.gl/polygon": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", + "@math.gl/core": "^4.0.0", + "@math.gl/polygon": "^4.0.0", + "@math.gl/web-mercator": "^4.0.0", "earcut": "^2.2.4" }, "peerDependencies": { - "@deck.gl/core": "^9.1.0", + "@deck.gl/core": "^9.0.0", "@loaders.gl/core": "^4.2.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" + "@luma.gl/core": "~9.0.0", + "@luma.gl/engine": "~9.0.0" } }, "node_modules/@deck.gl/mesh-layers": { - "version": "9.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.1.11.tgz", - "integrity": "sha512-9zdtRV8rVyJI4t8bPE3/jLEuiBfCp+53KneFrh/nmANVupZ5SP44TobSWJWl+nZ3Xs4LkJXaQPbI8gZR39lJBQ==", + "version": "9.0.40", + "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.0.40.tgz", + "integrity": "sha512-aCYnxfXmTrod+YMC6cHN4fP/OBihkZi+hgllJkBDiKkFr83cktJJGfjruHYYyQQuBKUPI1J2LP6aUD5/vXO2MA==", "dependencies": { "@loaders.gl/gltf": "^4.2.0", - "@luma.gl/gltf": "^9.1.5", - "@luma.gl/shadertools": "^9.1.5" + "@luma.gl/gltf": "~9.0.27", + "@luma.gl/shadertools": "~9.0.27" }, "peerDependencies": { - "@deck.gl/core": "^9.1.0", - "@luma.gl/core": "^9.1.5", - "@luma.gl/engine": "^9.1.5" + "@deck.gl/core": "^9.0.0", + "@luma.gl/core": "~9.0.0", + "@luma.gl/engine": "~9.0.0" } }, "node_modules/@deck.gl/react": { "version": "9.1.11", "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-9.1.11.tgz", "integrity": "sha512-RYjVYMZteyGwlZtuAiVD54TqvFgtZEJYBju2E/a6Bg3LBZ1eNtGHCpx5dIVKiOOU99gOkw2g2h0yrRgQt1j1cQ==", + "license": "MIT", "peerDependencies": { "@deck.gl/core": "^9.1.0", "@deck.gl/widgets": "^9.1.0", @@ -611,6 +935,7 @@ "version": "9.1.11", "resolved": "https://registry.npmjs.org/@deck.gl/widgets/-/widgets-9.1.11.tgz", "integrity": "sha512-E2t3YijvBEjIvPf2zTqaaPMYGpm3YLSTA3ap2JwEn+F+UQhkrCcahlPu4dXhpd0HHG8QEQCMZed/umad6kWZ2g==", + "license": "MIT", "peer": true, "dependencies": { "preact": "^10.17.0" @@ -623,6 +948,7 @@ "version": "0.4.92", "resolved": "https://registry.npmjs.org/@emerson-eps/color-tables/-/color-tables-0.4.92.tgz", "integrity": "sha512-qh/yKH6ue1qsQWP0gQxjIbNVMaI+eU9eoNxIWW15hX7bcFQgXGgVTk/IOF+9xQKx6UtU3Pd2yDcQon14j3BmoQ==", + "license": "MPL", "dependencies": { "d3": "^7.8.4", "d3-color": "^3.0.1", @@ -801,6 +1127,7 @@ "version": "0.45.1", "resolved": "https://registry.npmjs.org/@equinor/eds-core-react/-/eds-core-react-0.45.1.tgz", "integrity": "sha512-XMNjr7KLdUHULApRM3YoqBHVPoZVn5Z4rtVCGjAz2cwDcqMzv+R2cQ1flfBrb5ZD0/SUNpGMYOjSE1Z3RUyEaA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.27.0", "@equinor/eds-icons": "^0.22.0", @@ -825,12 +1152,14 @@ "node_modules/@equinor/eds-core-react/node_modules/@equinor/eds-icons": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/@equinor/eds-icons/-/eds-icons-0.22.0.tgz", - "integrity": "sha512-4SmPT67rUbl6qFyOCiSsue68EERgEGaXl9Xx6DkScW46AMbM6OdX3New1hctx1MIb6YdNlkHc/WwXTc+MG9j9Q==" + "integrity": "sha512-4SmPT67rUbl6qFyOCiSsue68EERgEGaXl9Xx6DkScW46AMbM6OdX3New1hctx1MIb6YdNlkHc/WwXTc+MG9j9Q==", + "license": "Apache-2.0" }, "node_modules/@equinor/eds-core-react/node_modules/@floating-ui/react": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.8.tgz", "integrity": "sha512-EQJ4Th328y2wyHR3KzOUOoTW2UKjFk53fmyahfwExnFQ8vnsMYqKc+fFPOkeYtj5tcp1DUMiNJ7BFhed7e9ONw==", + "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.9", @@ -843,8 +1172,7 @@ }, "node_modules/@equinor/eds-icons": { "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@equinor/eds-icons/-/eds-icons-0.21.0.tgz", - "integrity": "sha512-k2keACHou9h9D5QLfSBeojTApqbPCkHNBWplUA/B9FQv8FMCMSBbjJAo2L/3yAExMylQN9LdwKo81T2tijRXoA==", + "license": "Apache-2.0", "engines": { "node": ">=10.0.0", "pnpm": ">=4" @@ -862,6 +1190,7 @@ "version": "0.8.7", "resolved": "https://registry.npmjs.org/@equinor/eds-utils/-/eds-utils-0.8.7.tgz", "integrity": "sha512-ra4yKMSr5/yseSYqEm7L9fGqR3b2IMaR9leOxlDs37FleOIPL1tI+PvVK+vYGluRmid/uD8X95N/pRQoWR21ow==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.10", "@equinor/eds-tokens": "0.9.2" @@ -876,6 +1205,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@equinor/esv-intersection/-/esv-intersection-3.1.2.tgz", "integrity": "sha512-rl0BXjLN0VaEzFfOq+bafJMTfP78mIttU2Bk2jBz/bU7tDg87SNK44nsFj5l8RlVv41qFG41/r2RpewcktJ6CA==", + "license": "MIT", "dependencies": { "@equinor/videx-math": "^1.1.0", "@equinor/videx-vector2": "^1.0.44", @@ -901,7 +1231,8 @@ "node_modules/@equinor/videx-math": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@equinor/videx-math/-/videx-math-1.1.1.tgz", - "integrity": "sha512-bEVXEzTkqHlUIgfHIQg6Ozp3eC0JKiqAWBTEbrWGnhEZBqpYpnZx9NYmZozoRFDfthlvHRcqSm77z8CIv3KuJQ==" + "integrity": "sha512-bEVXEzTkqHlUIgfHIQg6Ozp3eC0JKiqAWBTEbrWGnhEZBqpYpnZx9NYmZozoRFDfthlvHRcqSm77z8CIv3KuJQ==", + "license": "MIT" }, "node_modules/@equinor/videx-vector2": { "version": "1.0.44", @@ -914,6 +1245,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/@equinor/videx-wellog/-/videx-wellog-0.11.4.tgz", "integrity": "sha512-hgOrX0EruiW7udrxBSMJiERi5II1tI0GxRxBuyVytVSY0BfmxCVhKL2WVgLJacInGvM8ELt6/BHcGi1Vf+6ivg==", + "license": "MIT", "dependencies": { "@equinor/videx-math": "^1.1.1", "d3-array": "^3.2.0", @@ -1115,9 +1447,8 @@ } }, "node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "version": "0.26.24", + "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", @@ -1142,12 +1473,14 @@ "node_modules/@floating-ui/utils": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", + "license": "MIT", "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.1", @@ -1159,6 +1492,7 @@ "version": "2.2.7", "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", "dependencies": { "tslib": "^2.8.0" } @@ -1167,6 +1501,7 @@ "version": "2.11.2", "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz", "integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==", + "license": "MIT", "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/icu-skeleton-parser": "1.8.14", @@ -1177,6 +1512,7 @@ "version": "1.8.14", "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz", "integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==", + "license": "MIT", "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "tslib": "^2.8.0" @@ -1186,6 +1522,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "license": "MIT", "dependencies": { "tslib": "^2.8.0" } @@ -1363,6 +1700,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "license": "MIT", "peerDependencies": { "react": "*" } @@ -1371,6 +1709,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.0.tgz", "integrity": "sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } @@ -1379,6 +1718,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.7.tgz", "integrity": "sha512-gLQlhEW4iO7DEFPf/U7IrIdA3UyLGS0opeqouaFwlMObLUzwexRjbygONHDVbC9G9oFLXsLyGKYkJwqXw/QADg==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0", "intl-messageformat": "^10.1.0" @@ -1388,6 +1728,7 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.1.tgz", "integrity": "sha512-UVsb4bCwbL944E0SX50CHFtWEeZ2uB5VozZ5yDXJdq6iPZsZO5p+bjVMZh2GxHf4Bs/7xtDCcPwEa2NU9DaG/g==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } @@ -1396,6 +1737,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.6.tgz", "integrity": "sha512-LR2lnM4urJta5/wYJVV7m8qk5DrMZmLRTuFhbQO5b9/sKLHgty6unQy1Li4+Su2DWydmB4aZdS5uxBRXIq2aAw==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } @@ -1518,38 +1860,187 @@ "license": "MIT" }, "node_modules/@loaders.gl/3d-tiles": { + "version": "4.2.5", + "license": "MIT", + "dependencies": { + "@loaders.gl/compression": "4.2.5", + "@loaders.gl/crypto": "4.2.5", + "@loaders.gl/draco": "4.2.5", + "@loaders.gl/gltf": "4.2.5", + "@loaders.gl/images": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/math": "4.2.5", + "@loaders.gl/tiles": "4.2.5", + "@loaders.gl/zip": "4.2.5", + "@math.gl/core": "^4.0.1", + "@math.gl/culling": "^4.0.1", + "@math.gl/geospatial": "^4.0.1", + "@probe.gl/log": "^4.0.4", + "long": "^5.2.1" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.0.0" + } + }, + "node_modules/@loaders.gl/3d-tiles/node_modules/long": { + "version": "5.2.3", + "license": "Apache-2.0" + }, + "node_modules/@loaders.gl/compression": { + "version": "4.2.5", + "license": "MIT", + "dependencies": { + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/worker-utils": "4.2.5", + "@types/brotli": "^1.3.0", + "@types/pako": "^1.0.1", + "fflate": "0.7.4", + "lzo-wasm": "^0.0.4", + "pako": "1.0.11", + "snappyjs": "^0.6.1" + }, + "optionalDependencies": { + "brotli": "^1.3.2", + "lz4js": "^0.2.0", + "zstd-codec": "^0.1" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.0.0" + } + }, + "node_modules/@loaders.gl/core": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.3.3.tgz", + "integrity": "sha512-RaQ3uNg4ZaVqDRgvJ2CjaOjeeHdKvbKuzFFgbGnflVB9is5bu+h3EKc3Jke7NGVvLBsZ6oIXzkwHijVsMfxv8g==", + "license": "MIT", + "dependencies": { + "@loaders.gl/loader-utils": "4.3.3", + "@loaders.gl/schema": "4.3.3", + "@loaders.gl/worker-utils": "4.3.3", + "@probe.gl/log": "^4.0.2" + } + }, + "node_modules/@loaders.gl/core/node_modules/@loaders.gl/loader-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.3.tgz", + "integrity": "sha512-8erUIwWLiIsZX36fFa/seZsfTsWlLk72Sibh/YZJrPAefuVucV4mGGzMBZ96LE2BUfJhadn250eio/59TUFbNw==", + "license": "MIT", + "dependencies": { + "@loaders.gl/schema": "4.3.3", + "@loaders.gl/worker-utils": "4.3.3", + "@probe.gl/log": "^4.0.2", + "@probe.gl/stats": "^4.0.2" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/core/node_modules/@loaders.gl/schema": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.3.tgz", + "integrity": "sha512-zacc9/8je+VbuC6N/QRfiTjRd+BuxsYlddLX1u5/X/cg9s36WZZBlU1oNKUgTYe8eO6+qLyYx77yi+9JbbEehw==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.7" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/core/node_modules/@loaders.gl/worker-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.3.tgz", + "integrity": "sha512-eg45Ux6xqsAfqPUqJkhmbFZh9qfmYuPfA+34VcLtfeXIwAngeP6o4SrTmm9LWLGUKiSh47anCEV1p7borDgvGQ==", + "license": "MIT", + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/crypto": { + "version": "4.2.5", + "license": "MIT", + "dependencies": { + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/worker-utils": "4.2.5", + "@types/crypto-js": "^4.0.2" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.0.0" + } + }, + "node_modules/@loaders.gl/draco": { + "version": "4.2.5", + "license": "MIT", + "dependencies": { + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", + "@loaders.gl/worker-utils": "4.2.5", + "draco3d": "1.5.7" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.0.0" + } + }, + "node_modules/@loaders.gl/gis": { + "version": "4.2.5", + "license": "MIT", + "dependencies": { + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", + "@mapbox/vector-tile": "^1.3.1", + "@math.gl/polygon": "^4.0.0", + "pbf": "^3.2.1" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.0.0" + } + }, + "node_modules/@loaders.gl/gltf": { + "version": "4.2.5", + "license": "MIT", + "dependencies": { + "@loaders.gl/draco": "4.2.5", + "@loaders.gl/images": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", + "@loaders.gl/textures": "4.2.5", + "@math.gl/core": "^4.0.0" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.0.0" + } + }, + "node_modules/@loaders.gl/i3s": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-4.3.3.tgz", - "integrity": "sha512-3uOXE8W0ppbY7tI5ywrU3RwCLMZtd+Jh0KgY9+EbjBVnZDHcnFxytYuG4NzfJEf5zwv0jladeSbJS1oVbLi8Jw==", + "resolved": "https://registry.npmjs.org/@loaders.gl/i3s/-/i3s-4.3.3.tgz", + "integrity": "sha512-un4CoCxKNrTW4nhMTwQxRqPWA9/P2oG6+p5NJjnPPbEcs9QvrHJVXzCnzIpt2nvW7gyZyEHXfwBkVqGrZN48ng==", + "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/compression": "4.3.3", "@loaders.gl/crypto": "4.3.3", "@loaders.gl/draco": "4.3.3", - "@loaders.gl/gltf": "4.3.3", "@loaders.gl/images": "4.3.3", "@loaders.gl/loader-utils": "4.3.3", "@loaders.gl/math": "4.3.3", + "@loaders.gl/schema": "4.3.3", + "@loaders.gl/textures": "4.3.3", "@loaders.gl/tiles": "4.3.3", "@loaders.gl/zip": "4.3.3", "@math.gl/core": "^4.1.0", "@math.gl/culling": "^4.1.0", - "@math.gl/geospatial": "^4.1.0", - "@probe.gl/log": "^4.0.4", - "long": "^5.2.1" + "@math.gl/geospatial": "^4.1.0" }, "peerDependencies": { "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/3d-tiles/node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" - }, - "node_modules/@loaders.gl/compression": { + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/compression": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@loaders.gl/compression/-/compression-4.3.3.tgz", "integrity": "sha512-1IZIFb6MaIiwMwsLEUk5Tyu8qlY7ge2S2Uy2qJxTP23CHakdocue89c54ygo0CgOiUw3Tr1r5JVa3EhB4+lOJQ==", + "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.3", "@loaders.gl/worker-utils": "4.3.3", @@ -1569,21 +2060,12 @@ "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/core": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-4.3.3.tgz", - "integrity": "sha512-RaQ3uNg4ZaVqDRgvJ2CjaOjeeHdKvbKuzFFgbGnflVB9is5bu+h3EKc3Jke7NGVvLBsZ6oIXzkwHijVsMfxv8g==", - "dependencies": { - "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/schema": "4.3.3", - "@loaders.gl/worker-utils": "4.3.3", - "@probe.gl/log": "^4.0.2" - } - }, - "node_modules/@loaders.gl/crypto": { + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/crypto": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@loaders.gl/crypto/-/crypto-4.3.3.tgz", "integrity": "sha512-uwqcSGJ4DdS2g3BYc4Noa4EGfnbK63wCQnke4Xyc7KTNl6P70oblDlRbL3df1WQPMTUoXYOERE+ei7Q0Tee4vQ==", + "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.3", "@loaders.gl/worker-utils": "4.3.3", @@ -1593,10 +2075,12 @@ "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/draco": { + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/draco": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-4.3.3.tgz", "integrity": "sha512-f2isxvOoH4Pm5p4mGvNN9gVigUwX84j9gdKNMV1aSo56GS1KE3GS2rXaIoy1qaIHMzkPySUTEcOTwayf0hWU7A==", + "license": "MIT", + "peer": true, "dependencies": { "@loaders.gl/loader-utils": "4.3.3", "@loaders.gl/schema": "4.3.3", @@ -1607,195 +2091,257 @@ "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/gis": { + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/images": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-4.3.3.tgz", + "integrity": "sha512-s4InjIXqEu0T7anZLj4OBUuDBt2BNnAD0GLzSexSkBfQZfpXY0XJNl4mMf5nUKb5NDfXhIKIqv8y324US+I28A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@loaders.gl/loader-utils": "4.3.3" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/loader-utils": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-4.3.3.tgz", - "integrity": "sha512-OQNrieRMihsy2mVHuhi7d/SThUdNCgFXmUqhCG53qAVIS+7Nm//lO9zty3EzfOGWHjYcx6+nxl4QO3mR5fXMvg==", + "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.3.tgz", + "integrity": "sha512-8erUIwWLiIsZX36fFa/seZsfTsWlLk72Sibh/YZJrPAefuVucV4mGGzMBZ96LE2BUfJhadn250eio/59TUFbNw==", + "license": "MIT", + "peer": true, "dependencies": { - "@loaders.gl/loader-utils": "4.3.3", "@loaders.gl/schema": "4.3.3", - "@mapbox/vector-tile": "^1.3.1", - "@math.gl/polygon": "^4.1.0", - "pbf": "^3.2.1" + "@loaders.gl/worker-utils": "4.3.3", + "@probe.gl/log": "^4.0.2", + "@probe.gl/stats": "^4.0.2" }, "peerDependencies": { "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/gltf": { + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/math": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-4.3.3.tgz", - "integrity": "sha512-M7jQ7KIB5itctDmGYuT9gndmjNwk1lwQ+BV4l5CoFp38e4xJESPglj2Kj8csWdm3WJhrxIYEP4GpjXK02n8DSQ==", + "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-4.3.3.tgz", + "integrity": "sha512-oUfCFYsybm6fKnYHU1BzqXsh0sCJ+M9CXNnD/083ZNW+lWdxD44eeTE3DdFYPEMe+yyMkLSGx/8WTMv7ev2t5Q==", + "license": "MIT", + "peer": true, "dependencies": { - "@loaders.gl/draco": "4.3.3", "@loaders.gl/images": "4.3.3", "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/schema": "4.3.3", - "@loaders.gl/textures": "4.3.3", "@math.gl/core": "^4.1.0" }, "peerDependencies": { "@loaders.gl/core": "^4.3.0" } }, - "node_modules/@loaders.gl/i3s": { + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/schema": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.3.tgz", + "integrity": "sha512-zacc9/8je+VbuC6N/QRfiTjRd+BuxsYlddLX1u5/X/cg9s36WZZBlU1oNKUgTYe8eO6+qLyYx77yi+9JbbEehw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/geojson": "^7946.0.7" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/textures": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/textures/-/textures-4.3.3.tgz", + "integrity": "sha512-qIo4ehzZnXFpPKl1BGQG4G3cAhBSczO9mr+H/bT7qFwtSirWVlqsvMlx1Q4VpmouDu+tudwwOlq7B3yqU5P5yQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@loaders.gl/images": "4.3.3", + "@loaders.gl/loader-utils": "4.3.3", + "@loaders.gl/schema": "4.3.3", + "@loaders.gl/worker-utils": "4.3.3", + "@math.gl/types": "^4.1.0", + "ktx-parse": "^0.7.0", + "texture-compressor": "^1.0.2" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/tiles": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-4.3.3.tgz", + "integrity": "sha512-cmC/spc+DM5aCSHoHrEuTPhDLuZRtkrWnlHkhC2Tur9uiUr41U3vXnC5slJkOeIWkaN4Q7KRFGCQ6SCendYfMg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@loaders.gl/loader-utils": "4.3.3", + "@loaders.gl/math": "4.3.3", + "@math.gl/core": "^4.1.0", + "@math.gl/culling": "^4.1.0", + "@math.gl/geospatial": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "@probe.gl/stats": "^4.0.2" + }, + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/worker-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.3.tgz", + "integrity": "sha512-eg45Ux6xqsAfqPUqJkhmbFZh9qfmYuPfA+34VcLtfeXIwAngeP6o4SrTmm9LWLGUKiSh47anCEV1p7borDgvGQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@loaders.gl/core": "^4.3.0" + } + }, + "node_modules/@loaders.gl/i3s/node_modules/@loaders.gl/zip": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/i3s/-/i3s-4.3.3.tgz", - "integrity": "sha512-un4CoCxKNrTW4nhMTwQxRqPWA9/P2oG6+p5NJjnPPbEcs9QvrHJVXzCnzIpt2nvW7gyZyEHXfwBkVqGrZN48ng==", + "resolved": "https://registry.npmjs.org/@loaders.gl/zip/-/zip-4.3.3.tgz", + "integrity": "sha512-PPNR9xBLfhBd4Fw69Ai5cUzIJZFCYg3DiYGeR8mA8ik9tuseH+hEBUSsmzU4RFP53xkPLLYvzXjVyiBzfbsjZg==", + "license": "MIT", "peer": true, "dependencies": { "@loaders.gl/compression": "4.3.3", "@loaders.gl/crypto": "4.3.3", - "@loaders.gl/draco": "4.3.3", - "@loaders.gl/images": "4.3.3", "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/math": "4.3.3", - "@loaders.gl/schema": "4.3.3", - "@loaders.gl/textures": "4.3.3", - "@loaders.gl/tiles": "4.3.3", - "@loaders.gl/zip": "4.3.3", - "@math.gl/core": "^4.1.0", - "@math.gl/culling": "^4.1.0", - "@math.gl/geospatial": "^4.1.0" + "jszip": "^3.1.5", + "md5": "^2.3.0" }, "peerDependencies": { "@loaders.gl/core": "^4.3.0" } }, + "node_modules/@loaders.gl/i3s/node_modules/ktx-parse": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.7.1.tgz", + "integrity": "sha512-FeA3g56ksdFNwjXJJsc1CCc7co+AJYDp6ipIp878zZ2bU8kWROatLYf39TQEd4/XRSUvBXovQ8gaVKWPXsCLEQ==", + "license": "MIT", + "peer": true + }, "node_modules/@loaders.gl/images": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-4.3.3.tgz", - "integrity": "sha512-s4InjIXqEu0T7anZLj4OBUuDBt2BNnAD0GLzSexSkBfQZfpXY0XJNl4mMf5nUKb5NDfXhIKIqv8y324US+I28A==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.3" + "@loaders.gl/loader-utils": "4.2.5" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/loader-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-4.3.3.tgz", - "integrity": "sha512-8erUIwWLiIsZX36fFa/seZsfTsWlLk72Sibh/YZJrPAefuVucV4mGGzMBZ96LE2BUfJhadn250eio/59TUFbNw==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/schema": "4.3.3", - "@loaders.gl/worker-utils": "4.3.3", - "@probe.gl/log": "^4.0.2", + "@loaders.gl/schema": "4.2.5", + "@loaders.gl/worker-utils": "4.2.5", "@probe.gl/stats": "^4.0.2" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/math": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-4.3.3.tgz", - "integrity": "sha512-oUfCFYsybm6fKnYHU1BzqXsh0sCJ+M9CXNnD/083ZNW+lWdxD44eeTE3DdFYPEMe+yyMkLSGx/8WTMv7ev2t5Q==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.3", - "@loaders.gl/loader-utils": "4.3.3", - "@math.gl/core": "^4.1.0" + "@loaders.gl/images": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", + "@math.gl/core": "^4.0.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/mvt": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-4.3.3.tgz", - "integrity": "sha512-y7YtrpPBOR4ek1Vj8vM2dRFrFfZHz7e5ZuYSgANOAyGzDXbnZ5TKPPIQC8Plm/y3ZQe+063yJ+kuGc91FBRbXg==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/gis": "4.3.3", - "@loaders.gl/images": "4.3.3", - "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/schema": "4.3.3", - "@math.gl/polygon": "^4.1.0", - "@probe.gl/stats": "^4.0.0", + "@loaders.gl/gis": "4.2.5", + "@loaders.gl/images": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", + "@math.gl/polygon": "^4.0.0", "pbf": "^3.2.1" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/schema": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.3.tgz", - "integrity": "sha512-zacc9/8je+VbuC6N/QRfiTjRd+BuxsYlddLX1u5/X/cg9s36WZZBlU1oNKUgTYe8eO6+qLyYx77yi+9JbbEehw==", + "version": "4.2.5", + "license": "MIT", "dependencies": { "@types/geojson": "^7946.0.7" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/terrain": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-4.3.3.tgz", - "integrity": "sha512-qPfpYL0imojyic0dTW71d9M8k2SY+wD60m31658vtsMogdVeBiAX/WYtk8W/NKcqBS8FMv9CC41PlULrvcZ7TQ==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.3", - "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/schema": "4.3.3", + "@loaders.gl/images": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", "@mapbox/martini": "^0.2.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/textures": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/textures/-/textures-4.3.3.tgz", - "integrity": "sha512-qIo4ehzZnXFpPKl1BGQG4G3cAhBSczO9mr+H/bT7qFwtSirWVlqsvMlx1Q4VpmouDu+tudwwOlq7B3yqU5P5yQ==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.3", - "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/schema": "4.3.3", - "@loaders.gl/worker-utils": "4.3.3", - "@math.gl/types": "^4.1.0", - "ktx-parse": "^0.7.0", + "@loaders.gl/images": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", + "@loaders.gl/worker-utils": "4.2.5", + "@math.gl/types": "^4.0.1", + "ktx-parse": "^0.0.4", "texture-compressor": "^1.0.2" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/tiles": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-4.3.3.tgz", - "integrity": "sha512-cmC/spc+DM5aCSHoHrEuTPhDLuZRtkrWnlHkhC2Tur9uiUr41U3vXnC5slJkOeIWkaN4Q7KRFGCQ6SCendYfMg==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/math": "4.3.3", - "@math.gl/core": "^4.1.0", - "@math.gl/culling": "^4.1.0", - "@math.gl/geospatial": "^4.1.0", - "@math.gl/web-mercator": "^4.1.0", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/math": "4.2.5", + "@math.gl/core": "^4.0.0", + "@math.gl/culling": "^4.0.0", + "@math.gl/geospatial": "^4.0.0", + "@math.gl/web-mercator": "^4.0.0", "@probe.gl/stats": "^4.0.2" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/wms": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/wms/-/wms-4.3.3.tgz", - "integrity": "sha512-SmpdFB/Jhtzbc52TlMKRSxQkUDfYP/FN8qdTdL3PtVN74Vuh4eZ8t7nLzplUCx0tbkbT1D7nfreSU4ndWq2zjQ==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/images": "4.3.3", - "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/schema": "4.3.3", - "@loaders.gl/xml": "4.3.3", + "@loaders.gl/images": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", + "@loaders.gl/xml": "4.2.5", "@turf/rewind": "^5.1.5", "deep-strict-equal": "^0.2.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/wms/node_modules/@turf/boolean-clockwise": { "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-5.1.5.tgz", - "integrity": "sha512-FqbmEEOJ4rU4/2t7FKx0HUWmjFEVqR+NJrFP7ymGSjja2SQ7Q91nnBihGuT+yuHHl6ElMjQ3ttsB/eTmyCycxA==", + "license": "MIT", "dependencies": { "@turf/helpers": "^5.1.5", "@turf/invariant": "^5.1.5" @@ -1803,37 +2349,32 @@ }, "node_modules/@loaders.gl/wms/node_modules/@turf/clone": { "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-5.1.5.tgz", - "integrity": "sha512-//pITsQ8xUdcQ9pVb4JqXiSqG4dos5Q9N4sYFoWghX21tfOV2dhc5TGqYOhnHrQS7RiKQL1vQ48kIK34gQ5oRg==", + "license": "MIT", "dependencies": { "@turf/helpers": "^5.1.5" } }, "node_modules/@loaders.gl/wms/node_modules/@turf/helpers": { "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz", - "integrity": "sha512-/lF+JR+qNDHZ8bF9d+Cp58nxtZWJ3sqFe6n3u3Vpj+/0cqkjk4nXKYBSY0azm+GIYB5mWKxUXvuP/m0ZnKj1bw==" + "license": "MIT" }, "node_modules/@loaders.gl/wms/node_modules/@turf/invariant": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz", - "integrity": "sha512-28RCBGvCYsajVkw2EydpzLdcYyhSA77LovuOvgCJplJWaNVyJYH6BOR3HR9w50MEkPqb/Vc/jdo6I6ermlRtQA==", + "license": "MIT", "dependencies": { "@turf/helpers": "^5.1.5" } }, "node_modules/@loaders.gl/wms/node_modules/@turf/meta": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz", - "integrity": "sha512-ZjQ3Ii62X9FjnK4hhdsbT+64AYRpaI8XMBMcyftEOGSmPMUVnkbvuv3C9geuElAXfQU7Zk1oWGOcrGOD9zr78Q==", + "license": "MIT", "dependencies": { "@turf/helpers": "^5.1.5" } }, "node_modules/@loaders.gl/wms/node_modules/@turf/rewind": { "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-5.1.5.tgz", - "integrity": "sha512-Gdem7JXNu+G4hMllQHXRFRihJl3+pNl7qY+l4qhQFxq+hiU1cQoVFnyoleIqWKIrdK/i2YubaSwc3SCM7N5mMw==", + "license": "MIT", "dependencies": { "@turf/boolean-clockwise": "^5.1.5", "@turf/clone": "^5.1.5", @@ -1843,112 +2384,92 @@ } }, "node_modules/@loaders.gl/worker-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-4.3.3.tgz", - "integrity": "sha512-eg45Ux6xqsAfqPUqJkhmbFZh9qfmYuPfA+34VcLtfeXIwAngeP6o4SrTmm9LWLGUKiSh47anCEV1p7borDgvGQ==", + "version": "4.2.5", + "license": "MIT", "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/xml": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/xml/-/xml-4.3.3.tgz", - "integrity": "sha512-p4GjJn7cElnSxZE2DVsTPWnEJWL3iqTVnGbW2ODHFpW2E7ClPmyoDsUxb8zdW8DuQKfLPJkUILtubbaHmwOwZg==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/loader-utils": "4.3.3", - "@loaders.gl/schema": "4.3.3", + "@loaders.gl/loader-utils": "4.2.5", + "@loaders.gl/schema": "4.2.5", "fast-xml-parser": "^4.2.5" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@loaders.gl/zip": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@loaders.gl/zip/-/zip-4.3.3.tgz", - "integrity": "sha512-PPNR9xBLfhBd4Fw69Ai5cUzIJZFCYg3DiYGeR8mA8ik9tuseH+hEBUSsmzU4RFP53xkPLLYvzXjVyiBzfbsjZg==", + "version": "4.2.5", + "license": "MIT", "dependencies": { - "@loaders.gl/compression": "4.3.3", - "@loaders.gl/crypto": "4.3.3", - "@loaders.gl/loader-utils": "4.3.3", + "@loaders.gl/compression": "4.2.5", + "@loaders.gl/crypto": "4.2.5", + "@loaders.gl/loader-utils": "4.2.5", "jszip": "^3.1.5", "md5": "^2.3.0" }, "peerDependencies": { - "@loaders.gl/core": "^4.3.0" + "@loaders.gl/core": "^4.0.0" } }, "node_modules/@luma.gl/constants": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.9.tgz", - "integrity": "sha512-yc9fml04OeTTcwK+7gmDMxoLQ67j4ZiAFXjmYvPomYyBVzS0NZxTDuwcCBmnxjLOiroOZW8FRRrVc/yOiFug2w==" + "version": "9.0.27", + "license": "MIT" }, "node_modules/@luma.gl/core": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.1.9.tgz", - "integrity": "sha512-1i9N7+I/UbFjx3axSMlc3/NufA+C2iBv/7mw51gRE/ypQPgvFmY/QqXBVZRe+nthF+OhlUMhO19TBndzYFTWhA==", + "version": "9.0.27", + "license": "MIT", + "peer": true, "dependencies": { - "@math.gl/types": "^4.1.0", - "@probe.gl/env": "^4.0.8", - "@probe.gl/log": "^4.0.8", - "@probe.gl/stats": "^4.0.8", + "@math.gl/types": "^4.0.0", + "@probe.gl/env": "^4.0.2", + "@probe.gl/log": "^4.0.2", + "@probe.gl/stats": "^4.0.2", "@types/offscreencanvas": "^2019.6.4" } }, "node_modules/@luma.gl/engine": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.1.9.tgz", - "integrity": "sha512-n1GLK1sUMFkWxdb+aZYn6ZBFltFEMi7X+6ZPxn2pBsNT6oeF4AyvH5AyqhOpvHvUnCLDt3Zsf1UIfx3MI//YSw==", + "version": "9.0.27", + "license": "MIT", + "peer": true, "dependencies": { - "@math.gl/core": "^4.1.0", - "@math.gl/types": "^4.1.0", - "@probe.gl/log": "^4.0.8", - "@probe.gl/stats": "^4.0.8" + "@luma.gl/shadertools": "9.0.27", + "@math.gl/core": "^4.0.0", + "@probe.gl/log": "^4.0.2", + "@probe.gl/stats": "^4.0.2" }, "peerDependencies": { - "@luma.gl/core": "^9.1.0", - "@luma.gl/shadertools": "^9.1.0" + "@luma.gl/core": "^9.0.0" } }, "node_modules/@luma.gl/gltf": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/gltf/-/gltf-9.1.9.tgz", - "integrity": "sha512-KgVBIFCtRO1oadgMDycMJA5s+q519l/fQBGAZpUcLfWsaEDQfdHW2NLdrK/00VDv46Ng8tN/O6uyH6E40uLcLw==", + "version": "9.0.27", + "license": "MIT", "dependencies": { - "@loaders.gl/core": "^4.2.0", "@loaders.gl/textures": "^4.2.0", - "@math.gl/core": "^4.1.0" + "@luma.gl/shadertools": "9.0.27", + "@math.gl/core": "^4.0.0" }, "peerDependencies": { - "@luma.gl/core": "^9.1.0", - "@luma.gl/engine": "^9.1.0", - "@luma.gl/shadertools": "^9.1.0" + "@loaders.gl/core": "^4.2.0", + "@luma.gl/core": "^9.0.0", + "@luma.gl/engine": "^9.0.0" } }, "node_modules/@luma.gl/shadertools": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.1.9.tgz", - "integrity": "sha512-Uqp2xfgIEunRMLXTeCJ4uEMlWcUGcYMZGJ8GAOrAeDzn4bMKVRKmZDC71vkuTctnaodM3UdrI9W6s1sJlrXsxw==", - "dependencies": { - "@math.gl/core": "^4.1.0", - "@math.gl/types": "^4.1.0", - "wgsl_reflect": "^1.2.0" - }, - "peerDependencies": { - "@luma.gl/core": "^9.1.0" - } - }, - "node_modules/@luma.gl/webgl": { - "version": "9.1.9", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-9.1.9.tgz", - "integrity": "sha512-jecHjhNSWkXH0v62rM6G5fIIkOmsrND27099iKgdutFvHIvd4QS4UzGWEEa9AEPlP0rTLqXkA6y6YL7f42ZkVg==", + "version": "9.0.27", + "license": "MIT", "dependencies": { - "@luma.gl/constants": "9.1.9", - "@math.gl/types": "^4.1.0", - "@probe.gl/env": "^4.0.8" + "@math.gl/core": "^4.0.0", + "@math.gl/types": "^4.0.0", + "wgsl_reflect": "^1.0.1" }, "peerDependencies": { - "@luma.gl/core": "^9.1.0" + "@luma.gl/core": "^9.0.0" } }, "node_modules/@mapbox/geojson-rewind": { @@ -1990,8 +2511,7 @@ }, "node_modules/@mapbox/martini": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@mapbox/martini/-/martini-0.2.0.tgz", - "integrity": "sha512-7hFhtkb0KTLEls+TRw/rWayq5EeHtTaErgm/NskVoXmtgAQu/9D299aeyj6mzAR/6XUnYRp2lU+4IcrYRFjVsQ==" + "license": "ISC" }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", @@ -2064,8 +2584,7 @@ }, "node_modules/@math.gl/culling": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@math.gl/culling/-/culling-4.1.0.tgz", - "integrity": "sha512-jFmjFEACnP9kVl8qhZxFNhCyd47qPfSVmSvvjR0/dIL6R9oD5zhR1ub2gN16eKDO/UM7JF9OHKU3EBIfeR7gtg==", + "license": "MIT", "dependencies": { "@math.gl/core": "4.1.0", "@math.gl/types": "4.1.0" @@ -2073,8 +2592,7 @@ }, "node_modules/@math.gl/geospatial": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@math.gl/geospatial/-/geospatial-4.1.0.tgz", - "integrity": "sha512-BzsUhpVvnmleyYF6qdqJIip6FtIzJmnWuPTGhlBuPzh7VBHLonCFSPtQpbkRuoyAlbSyaGXcVt6p6lm9eK2vtg==", + "license": "MIT", "dependencies": { "@math.gl/core": "4.1.0", "@math.gl/types": "4.1.0" @@ -2082,8 +2600,7 @@ }, "node_modules/@math.gl/polygon": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@math.gl/polygon/-/polygon-4.1.0.tgz", - "integrity": "sha512-YA/9PzaCRHbIP5/0E9uTYrqe+jsYTQoqoDWhf6/b0Ixz8bPZBaGDEafLg3z7ffBomZLacUty9U3TlPjqMtzPjA==", + "license": "MIT", "dependencies": { "@math.gl/core": "4.1.0" } @@ -2992,12 +3509,14 @@ "node_modules/@probe.gl/env": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@probe.gl/env/-/env-4.1.0.tgz", - "integrity": "sha512-5ac2Jm2K72VCs4eSMsM7ykVRrV47w32xOGMvcgqn8vQdEMF9PRXyBGYEV9YbqRKWNKpNKmQJVi4AHM/fkCxs9w==" + "integrity": "sha512-5ac2Jm2K72VCs4eSMsM7ykVRrV47w32xOGMvcgqn8vQdEMF9PRXyBGYEV9YbqRKWNKpNKmQJVi4AHM/fkCxs9w==", + "license": "MIT" }, "node_modules/@probe.gl/log": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@probe.gl/log/-/log-4.1.0.tgz", "integrity": "sha512-r4gRReNY6f+OZEMgfWEXrAE2qJEt8rX0HsDJQXUBMoc+5H47bdB7f/5HBHAmapK8UydwPKL9wCDoS22rJ0yq7Q==", + "license": "MIT", "dependencies": { "@probe.gl/env": "4.1.0" } @@ -3005,12 +3524,14 @@ "node_modules/@probe.gl/stats": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-4.1.0.tgz", - "integrity": "sha512-EI413MkWKBDVNIfLdqbeNSJTs7ToBz/KVGkwi3D+dQrSIkRI2IYbWGAU3xX+D6+CI4ls8ehxMhNpUVMaZggDvQ==" + "integrity": "sha512-EI413MkWKBDVNIfLdqbeNSJTs7ToBz/KVGkwi3D+dQrSIkRI2IYbWGAU3xX+D6+CI4ls8ehxMhNpUVMaZggDvQ==", + "license": "MIT" }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.23", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.23.tgz", "integrity": "sha512-4uLxuAgPfXds8sBc/Cg0ml7LKWzK+YTwHL7xclhQUkPO32rzlHDl+BJ5cyWhvZgGUf8JJXbXhD5VlJJzbbl8Xg==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/link": "^3.8.0", @@ -3028,6 +3549,7 @@ "version": "3.13.0", "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.13.0.tgz", "integrity": "sha512-BEcTQb7Q8ZrAtn0scPDv/ErZoGC1FI0sLk0UTPGskuh/RV9ZZGFbuSWTqOwV8w5CS6VMvPjH6vaE8hS7sb5DIw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/toolbar": "3.0.0-beta.15", @@ -3046,6 +3568,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.8.0.tgz", "integrity": "sha512-9vms/fWjJPZkJcMxciwWWOjGy/Q0nqI6FV0pYbMZbqepkzglEaVd98kl506r/4hLhWKwLdTfqCgbntRecj8jBg==", + "license": "Apache-2.0", "dependencies": { "@internationalized/date": "^3.8.0", "@react-aria/i18n": "^3.12.8", @@ -3067,6 +3590,7 @@ "version": "3.15.4", "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.15.4.tgz", "integrity": "sha512-ZkDJFs2EfMBXVIpBSo4ouB+NXyr2LRgZNp2x8/v+7n3aTmMU8j2PzT+Ra2geTQbC0glMP7UrSg4qZblqrxEBcQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/form": "^3.0.15", "@react-aria/interactions": "^3.25.0", @@ -3089,6 +3613,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/@react-aria/color/-/color-3.0.6.tgz", "integrity": "sha512-ik4Db9hrN1yIT0CQMB888ktBmrwA/kNhkfiDACtoUHv8Ev+YEpmagnmih9vMyW2vcnozYJpnn/aCMl59J5uMew==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/interactions": "^3.25.0", @@ -3113,6 +3638,7 @@ "version": "3.12.2", "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.12.2.tgz", "integrity": "sha512-EgddiF8VnAjB4EynJERPn4IoDMUabI8GiKOQZ6Ar3MlRWxQnUfxPpZwXs8qWR3dPCzYUt2PhBinhBMjyR1yRIw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/i18n": "^3.12.8", @@ -3140,6 +3666,7 @@ "version": "3.14.2", "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.14.2.tgz", "integrity": "sha512-O7fdzcqIJ7i/+8SGYvx4tloTZgK4Ws8OChdbFcd2rZoRPqxM50M6J+Ota8hTet2wIhojUXnM3x2na3EvoucBXA==", + "license": "Apache-2.0", "dependencies": { "@internationalized/date": "^3.8.0", "@internationalized/number": "^3.6.1", @@ -3169,6 +3696,7 @@ "version": "3.5.24", "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.24.tgz", "integrity": "sha512-tw0WH89gVpHMI5KUQhuzRE+IYCc9clRfDvCppuXNueKDrZmrQKbeoU6d0b5WYRsBur2+d7ErtvpLzHVqE1HzfA==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/overlays": "^3.27.0", @@ -3186,6 +3714,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@react-aria/disclosure/-/disclosure-3.0.4.tgz", "integrity": "sha512-HXGVLA06BH0b/gN8dCTzWATwMikz8D+ahRxZiI0HDZxLADWGsSPqRXKN0GNAiBKbvPtvAbrwslE3pktk/SlU/w==", + "license": "Apache-2.0", "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-aria/utils": "^3.28.2", @@ -3202,6 +3731,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.9.2.tgz", "integrity": "sha512-pPYygmJTjSPV2K/r48TvF75WuddG8d8nlIxAXSW22++WKqZ0z+eun6gDUXoKeB2rgY7sVfLqpRdnPV52AnBX+Q==", + "license": "Apache-2.0", "dependencies": { "@internationalized/string": "^3.2.6", "@react-aria/i18n": "^3.12.8", @@ -3223,6 +3753,7 @@ "version": "3.20.2", "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.2.tgz", "integrity": "sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/utils": "^3.28.2", @@ -3239,6 +3770,7 @@ "version": "3.0.15", "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.15.tgz", "integrity": "sha512-kk8AnLz+EOgnn3sTaXYmtw+YzVDc1of/+xAkuOupQi6zQFnNRjc99JlDbKHoUZ39urMl+8lsp/1b9VPPhNrBNw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/utils": "^3.28.2", @@ -3255,6 +3787,7 @@ "version": "3.13.0", "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.13.0.tgz", "integrity": "sha512-RcuJYA4fyJ83MH3SunU+P5BGkx3LJdQ6kxwqwWGIuI9eUKc7uVbqvN9WN3fI+L0QfxqBFmh7ffRxIdQn7puuzw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/i18n": "^3.12.8", @@ -3279,6 +3812,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.12.0.tgz", "integrity": "sha512-KSpnSBYQ7ozGQNaRR2NGq7Fl2zIv5w9KNyO9V/IE2mxUNfX6fwqUPoANFcy9ySosksE7pPnFtuYIB+TQtUjYqQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/grid": "^3.13.0", @@ -3301,6 +3835,7 @@ "version": "3.12.8", "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.8.tgz", "integrity": "sha512-V/Nau9WuwTwxfFffQL4URyKyY2HhRlu9zmzkF2Hw/j5KmEQemD+9jfaLueG2CJu85lYL06JrZXUdnhZgKnqMkA==", + "license": "Apache-2.0", "dependencies": { "@internationalized/date": "^3.8.0", "@internationalized/message": "^3.1.7", @@ -3320,6 +3855,7 @@ "version": "3.25.0", "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.0.tgz", "integrity": "sha512-GgIsDLlO8rDU/nFn6DfsbP9rfnzhm8QFjZkB9K9+r+MTSCn7bMntiWQgMM+5O6BiA8d7C7x4zuN4bZtc0RBdXQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-aria/utils": "^3.28.2", @@ -3336,6 +3872,7 @@ "version": "3.7.17", "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.17.tgz", "integrity": "sha512-Fz7IC2LQT2Y/sAoV+gFEXoULtkznzmK2MmeTv5shTNjeTxzB1BhQbD4wyCypi7eGsnD/9Zy+8viULCsIUbvjWw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/utils": "^3.28.2", "@react-types/shared": "^3.29.0", @@ -3350,6 +3887,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.2.tgz", "integrity": "sha512-KVXa9s3fSgo/PiUjdbnPh3a1yS4t2bMZeVBPPzYAgQ4wcU2WjuLkhviw+5GWSWRfT+jpIMV7R/cmyvr0UHvRfg==", + "license": "Apache-2.0", "dependencies": { "@react-aria/utils": "^3.28.2", "@react-types/shared": "^3.29.0", @@ -3365,6 +3903,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.8.0.tgz", "integrity": "sha512-gpDD6t3FqtFR9QjSIKNpmSR3tS4JG2anVKx2wixuRDHO6Ddexxv4SBzsE1+230p+FlFGjftFa2lEgQ7RNjZrmA==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/utils": "^3.28.2", @@ -3381,6 +3920,7 @@ "version": "3.14.3", "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.14.3.tgz", "integrity": "sha512-wzelam1KENUvKjsTq8gfrOW2/iab8SyIaSXfFvGmWW82XlDTlW+oQeA39tvOZktMVGspr+xp8FySY09rtz6UXw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/label": "^3.7.17", @@ -3401,6 +3941,7 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.4.2.tgz", "integrity": "sha512-6+yNF9ZrZ4YJ60Oxy2gKI4/xy6WUv1iePDCFJkgpNVuOEYi8W8czff8ctXu/RPB25OJx5v2sCw9VirRogTo2zA==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } @@ -3409,6 +3950,7 @@ "version": "3.18.2", "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.18.2.tgz", "integrity": "sha512-90k+Ke1bhFWhR2zuRI6OwKWQrCpOD99n+9jhG96JZJZlNo5lB+5kS+ufG1LRv5GBnCug0ciLQmPMAfguVsCjEQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/i18n": "^3.12.8", @@ -3434,6 +3976,7 @@ "version": "3.4.22", "resolved": "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.22.tgz", "integrity": "sha512-A/30vrtJO0xqctS/ngE1Lp/w3Aq3MPcpdRHU5E06EUYotzRzHFE9sNmezWslkZ3NfYwA/mxLvgmrsOJSR0Hx6A==", + "license": "Apache-2.0", "dependencies": { "@react-aria/progress": "^3.4.22", "@react-types/meter": "^3.4.8", @@ -3449,6 +3992,7 @@ "version": "3.11.13", "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.13.tgz", "integrity": "sha512-F73BVdIRV8VvKl0omhGaf0E7mdJ7pdPjDP3wYNf410t55BXPxmndItUKpGfxSbl8k6ZYLvQyOqkD6oWSfZXpZw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/interactions": "^3.25.0", @@ -3471,6 +4015,7 @@ "version": "3.27.0", "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.27.0.tgz", "integrity": "sha512-2vZVgL7FrloN5Rh8sAhadGADJbuWg69DdSJB3fd2/h5VvcEhnIfNPu9Ma5XmdkApDoTboIEsKZ4QLYwRl98w6w==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/i18n": "^3.12.8", @@ -3493,6 +4038,7 @@ "version": "3.4.22", "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.22.tgz", "integrity": "sha512-wK2hath4C9HKgmjCH+iSrAs86sUKqqsYKbEKk9/Rj9rzXqHyaEK9EG0YZDnSjd8kX+N9hYcs5MfJl6AZMH4juQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/label": "^3.7.17", @@ -3510,6 +4056,7 @@ "version": "3.11.2", "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.11.2.tgz", "integrity": "sha512-6AFJHXMewJBgHNhqkN1qjgwwx6kmagwYD+3Z+hNK1UHTsKe1Uud5/IF7gPFCqlZeKxA+Lvn9gWiqJrQbtD2+wg==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/form": "^3.0.15", @@ -3531,6 +4078,7 @@ "version": "3.8.3", "resolved": "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.8.3.tgz", "integrity": "sha512-t1DW3nUkPHyZhFhUbT+TdhvI8yZYvUPCuwl0FyraMRCQ4+ww5Ieu4n8JB9IGYmIUB/GWEbZlDHplu4s3efmliA==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/textfield": "^3.17.2", @@ -3550,6 +4098,7 @@ "version": "3.15.4", "resolved": "https://registry.npmjs.org/@react-aria/select/-/select-3.15.4.tgz", "integrity": "sha512-CipqXgdOfWsiHw/chfqd8t9IQpvehP+3uKLJx3ic4Uyj+FT/SxVmmjX0gyvVbZd00ltFCMJYO2xYKQUlbW2AtQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/form": "^3.0.15", "@react-aria/i18n": "^3.12.8", @@ -3575,6 +4124,7 @@ "version": "3.24.0", "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.24.0.tgz", "integrity": "sha512-RfGXVc04zz41NVIW89/a3quURZ4LT/GJLkiajQK2VjhisidPdrAWkcfjjWJj0n+tm5gPWbi9Rs5R/Rc8mrvq8Q==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/i18n": "^3.12.8", @@ -3593,6 +4143,7 @@ "version": "3.4.8", "resolved": "https://registry.npmjs.org/@react-aria/separator/-/separator-3.4.8.tgz", "integrity": "sha512-ncuOSTBF/qbNumnW/IRz+xyr+Ud85eCF0Expw4XWhKjAZfzJd86MxPY5ZsxE7pYLOcRWdOSIH1/obwwwSz8ALQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/utils": "^3.28.2", "@react-types/shared": "^3.29.0", @@ -3607,6 +4158,7 @@ "version": "3.7.18", "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.18.tgz", "integrity": "sha512-GBVv5Rpvj/6JH2LnF1zVAhBmxGiuq7R8Ekqyr5kBrCc2ToF3PrTjfGc/mlh0eEtbj+NvAcnlgTx1/qosYt1sGw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/interactions": "^3.25.0", @@ -3626,6 +4178,7 @@ "version": "3.6.14", "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.14.tgz", "integrity": "sha512-oSKe9p0Q/7W39eXRnLxlwJG5dQo4ffosRT3u2AtOcFkk2Zzj+tSQFzHQ4202nrWdzRnQ2KLTgUUNnUvXf0BJcg==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/live-announcer": "^3.4.2", @@ -3643,6 +4196,7 @@ "version": "3.9.8", "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz", "integrity": "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" }, @@ -3657,6 +4211,7 @@ "version": "3.7.2", "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.7.2.tgz", "integrity": "sha512-vaREbp1gFjv+jEMXoXpNK7JYFO/jhwnSYAwEINNWnwf54IGeHvTPaB2NwolYSFvP4HAj8TKYbGFUSz7RKLhLgw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/toggle": "^3.11.2", "@react-stately/toggle": "^3.8.3", @@ -3673,6 +4228,7 @@ "version": "3.17.2", "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.17.2.tgz", "integrity": "sha512-wsF3JqiAKcol1sfeNqTxyzH6+nxu0sAfyuh+XQfp1tvSGx15NifYeNKovNX4EPpUVkAI7jL5Le+eYeYYGELfnw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/grid": "^3.13.0", @@ -3699,6 +4255,7 @@ "version": "3.10.2", "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.10.2.tgz", "integrity": "sha512-rpEgh//Gnew3le49tQVFOQ6ZyacJdaNUDXHt0ocguXb+2UrKtH54M8oIAE7E8KaB1puQlFXRs+Rjlr1rOlmjEQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/i18n": "^3.12.8", @@ -3718,6 +4275,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/@react-aria/tag/-/tag-3.5.2.tgz", "integrity": "sha512-xZ5Df0x+xcDg6UTDvnjP4pu+XrmYVaYcqzF7RGoCD1KyRCHU5Czg9p+888NB0K+vnJHfNsQh6rmMhDUydXu9eg==", + "license": "Apache-2.0", "dependencies": { "@react-aria/gridlist": "^3.12.0", "@react-aria/i18n": "^3.12.8", @@ -3739,6 +4297,7 @@ "version": "3.17.2", "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.17.2.tgz", "integrity": "sha512-4KINB0HueYUHUgvi/ThTP27hu4Mv5ujG55pH3dmSRD4Olu/MRy1m/Psq72o8LTf4bTOM9ZP1rKccUg6xfaMidA==", + "license": "Apache-2.0", "dependencies": { "@react-aria/form": "^3.0.15", "@react-aria/interactions": "^3.25.0", @@ -3759,6 +4318,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@react-aria/toast/-/toast-3.0.2.tgz", "integrity": "sha512-iaiHDE1CKYM3BbNEp3A2Ed8YAlpXUGyY6vesKISdHEZ2lJ7r+1hbcFoTNdG8HfbB8Lz5vw8Wd2o+ZmQ2tnDY9Q==", + "license": "Apache-2.0", "dependencies": { "@react-aria/i18n": "^3.12.8", "@react-aria/interactions": "^3.25.0", @@ -3778,6 +4338,7 @@ "version": "3.11.2", "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.11.2.tgz", "integrity": "sha512-JOg8yYYCjLDnEpuggPo9GyXFaT/B238d3R8i/xQ6KLelpi3fXdJuZlFD6n9NQp3DJbE8Wj+wM5/VFFAi3cISpw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/utils": "^3.28.2", @@ -3795,6 +4356,7 @@ "version": "3.0.0-beta.15", "resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.15.tgz", "integrity": "sha512-PNGpNIKIsCW8rxI9XXSADlLrSpikILJKKECyTRw9KwvXDRc44pezvdjGHCNinQcKsQoy5BtkK5cTSAyVqzzTXQ==", + "license": "Apache-2.0", "dependencies": { "@react-aria/focus": "^3.20.2", "@react-aria/i18n": "^3.12.8", @@ -3811,6 +4373,7 @@ "version": "3.8.2", "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.8.2.tgz", "integrity": "sha512-ctVTgh1LXvmr1ve3ehAWfvlJR7nHYZeqhl/g1qnA+983LQtc1IF9MraCs92g0m7KpBwCihuA+aYwTPsUHfKfXg==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/utils": "^3.28.2", @@ -3828,6 +4391,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@react-aria/tree/-/tree-3.0.2.tgz", "integrity": "sha512-gr06Y1760+kdlDeUcGNR+PCuJMtlrdtNMGG1Z0fSygy8y7/zVdTOLQp0c1Q3pjL2nr7Unjz/H1xSgERParHsbg==", + "license": "Apache-2.0", "dependencies": { "@react-aria/gridlist": "^3.12.0", "@react-aria/i18n": "^3.12.8", @@ -3847,6 +4411,7 @@ "version": "3.28.2", "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.2.tgz", "integrity": "sha512-J8CcLbvnQgiBn54eeEvQQbIOfBF3A1QizxMw9P4cl9MkeR03ug7RnjTIdJY/n2p7t59kLeAB3tqiczhcj+Oi5w==", + "license": "Apache-2.0", "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-stately/flags": "^3.1.1", @@ -3864,6 +4429,7 @@ "version": "3.8.22", "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.22.tgz", "integrity": "sha512-EO3R8YTKZ7HkLl9k1Y2uBKYBgpJagth4/4W7mfpJZE24A3fQnCP8zx1sweXiAm0mirR4J6tNaK7Ia8ssP5TpOw==", + "license": "Apache-2.0", "dependencies": { "@react-aria/interactions": "^3.25.0", "@react-aria/utils": "^3.28.2", @@ -3879,6 +4445,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.8.0.tgz", "integrity": "sha512-YAuJiR9EtVThX91gU2ay/6YgPe0LvZWEssu4BS0Atnwk5cAo32gvF5FMta9ztH1LIULdZFaypU/C1mvnayMf+Q==", + "license": "Apache-2.0", "dependencies": { "@internationalized/date": "^3.8.0", "@react-stately/utils": "^3.10.6", @@ -3894,6 +4461,7 @@ "version": "3.6.13", "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.13.tgz", "integrity": "sha512-b8+bkOhobzuJ5bAA16JpYg1tM973eNXD3U4h/8+dckLndKHRjIwPvrL25tzKN7NcQp2LKVCauFesgI+Z+/2FJg==", + "license": "Apache-2.0", "dependencies": { "@react-stately/form": "^3.1.3", "@react-stately/utils": "^3.10.6", @@ -3909,6 +4477,7 @@ "version": "3.12.3", "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.3.tgz", "integrity": "sha512-QfSBME2QWDjUw/RmmUjrYl/j1iCYcYCIDsgZda1OeRtt63R11k0aqmmwrDRwCsA+Sv+D5QgkOp4KK+CokTzoVQ==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0" @@ -3921,6 +4490,7 @@ "version": "3.8.4", "resolved": "https://registry.npmjs.org/@react-stately/color/-/color-3.8.4.tgz", "integrity": "sha512-LXmfnJPWnL5q1/Z8Pn2d+9efrClLWCiK6c3IGXN8ZWcdR/cMJ/w9SY9f7evyXvmeUmdU1FTGgoSVqGfup3tSyA==", + "license": "Apache-2.0", "dependencies": { "@internationalized/number": "^3.6.1", "@internationalized/string": "^3.2.6", @@ -3940,6 +4510,7 @@ "version": "3.10.4", "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.10.4.tgz", "integrity": "sha512-sgujLhukIGKskLDrOL4SAbO7WOgLsD7gSdjRQZ0f/e8bWMmUOWEp22T+X1hMMcuVRkRdXlEF1kH2/E6BVanXYw==", + "license": "Apache-2.0", "dependencies": { "@react-stately/collections": "^3.12.3", "@react-stately/form": "^3.1.3", @@ -3959,6 +4530,7 @@ "version": "3.14.0", "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.14.0.tgz", "integrity": "sha512-JSkQfKW0+WpPQyOOeRPBLwXkVfpTUwgZJDnHBCud5kEuQiFFyeAIbL57RNXc4AX2pzY3piQa6OHnjDGTfqClxQ==", + "license": "Apache-2.0", "dependencies": { "@internationalized/date": "^3.8.0", "@internationalized/string": "^3.2.6", @@ -3977,6 +4549,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@react-stately/disclosure/-/disclosure-3.0.3.tgz", "integrity": "sha512-4kB+WDXVcrxCmJ+X6c23wa5Ax5dPSpm6Ef8DktLrLcUfJyfr+SWs5/IfkrYG0sOl3/u5OwyWe1pq3hDpzyDlLA==", + "license": "Apache-2.0", "dependencies": { "@react-stately/utils": "^3.10.6", "@react-types/shared": "^3.29.0", @@ -3990,6 +4563,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/@react-stately/dnd/-/dnd-3.5.3.tgz", "integrity": "sha512-e4IodPF7fv9hR6jqSjiyrrFQ/6NbHNM5Ft1MJzCu6tJHvT+sl6qxIP5A+XR3wkjMpi4QW2WhVUmoFNbS/6ZAug==", + "license": "Apache-2.0", "dependencies": { "@react-stately/selection": "^3.20.1", "@react-types/shared": "^3.29.0", @@ -4003,6 +4577,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.1.tgz", "integrity": "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } @@ -4011,6 +4586,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.1.3.tgz", "integrity": "sha512-Jisgm0facSS3sAzHfSgshoCo3LxfO0wmQj98MOBCGXyVL+MSwx2ilb38eXIyBCzHJzJnPRTLaK/E4T49aph47A==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0" @@ -4023,6 +4599,7 @@ "version": "3.11.1", "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.11.1.tgz", "integrity": "sha512-xMk2YsaIKkF8dInRLUFpUXBIqnYt88hehhq2nb65RFgsFFhngE/OkaFudSUzaYPc1KvHpW+oHqvseC+G1iDG2w==", + "license": "Apache-2.0", "dependencies": { "@react-stately/collections": "^3.12.3", "@react-stately/selection": "^3.20.1", @@ -4038,6 +4615,7 @@ "version": "3.12.1", "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.12.1.tgz", "integrity": "sha512-N+YCInNZ2OpY0WUNvJWUTyFHtzE5yBtZ9DI4EHJDvm61+jmZ2s3HszOfa7j+7VOKq78VW3m5laqsQNWvMrLFrQ==", + "license": "Apache-2.0", "dependencies": { "@react-stately/collections": "^3.12.3", "@react-stately/selection": "^3.20.1", @@ -4053,6 +4631,7 @@ "version": "3.9.3", "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.9.3.tgz", "integrity": "sha512-9x1sTX3Xq2Q3mJUHV+YN9MR36qNzgn8eBSLa40eaFDaOOtoJ+V10m7OriUfpjey7WzLBpq00Sfda54/PbQHZ0g==", + "license": "Apache-2.0", "dependencies": { "@react-stately/overlays": "^3.6.15", "@react-types/menu": "^3.10.0", @@ -4067,6 +4646,7 @@ "version": "3.9.11", "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.11.tgz", "integrity": "sha512-gAFSZIHnZsgIWVPgGRUUpfW6zM7TCV5oS1SCY90ay5nrS7JCXurQbMrWJLOWHTdM5iSeYMgoyt68OK5KD0KHMw==", + "license": "Apache-2.0", "dependencies": { "@internationalized/number": "^3.6.1", "@react-stately/form": "^3.1.3", @@ -4082,6 +4662,7 @@ "version": "3.6.15", "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.15.tgz", "integrity": "sha512-LBaGpXuI+SSd5HSGzyGJA0Gy09V2tl2G/r0lllTYqwt0RDZR6p7IrhdGVXZm6vI0oWEnih7yLC32krkVQrffgQ==", + "license": "Apache-2.0", "dependencies": { "@react-stately/utils": "^3.10.6", "@react-types/overlays": "^3.8.14", @@ -4095,6 +4676,7 @@ "version": "3.10.12", "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.12.tgz", "integrity": "sha512-hFH45CXVa7uyXeTYQy7LGR0SnmGnNRx7XnEXS25w4Ch6BpH8m8SAbhKXqysgcmsE3xrhRas7P9zWw7wI24G28Q==", + "license": "Apache-2.0", "dependencies": { "@react-stately/form": "^3.1.3", "@react-stately/utils": "^3.10.6", @@ -4110,6 +4692,7 @@ "version": "3.5.11", "resolved": "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.11.tgz", "integrity": "sha512-vOgK3kgkYcyjTLsBABVzoQL9w6qBamnWAQICcw5OkA6octnF7NZ5DqdjkwnMY95KOGchiTlD5tNNHrz0ekeGiw==", + "license": "Apache-2.0", "dependencies": { "@react-stately/utils": "^3.10.6", "@react-types/searchfield": "^3.6.1", @@ -4123,6 +4706,7 @@ "version": "3.6.12", "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.6.12.tgz", "integrity": "sha512-5o/NAaENO/Gxs1yui5BHLItxLnDPSQJ5HDKycuD0/gGC17BboAGEY/F9masiQ5qwRPe3JEc0QfvMRq3yZVNXog==", + "license": "Apache-2.0", "dependencies": { "@react-stately/form": "^3.1.3", "@react-stately/list": "^3.12.1", @@ -4139,6 +4723,7 @@ "version": "3.20.1", "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.20.1.tgz", "integrity": "sha512-K9MP6Rfg2yvFoY2Cr+ykA7bP4EBXlGaq5Dqfa1krvcXlEgMbQka5muLHdNXqjzGgcwPmS1dx1NECD15q63NtOw==", + "license": "Apache-2.0", "dependencies": { "@react-stately/collections": "^3.12.3", "@react-stately/utils": "^3.10.6", @@ -4153,6 +4738,7 @@ "version": "3.6.3", "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.6.3.tgz", "integrity": "sha512-755X1jhpRD1bqf/5Ax1xuSpZbnG/0EEHGOowH28FLYKy5+1l4QVDGPFYxLB9KzXPdRAr9EF0j2kRhH2d8MCksQ==", + "license": "Apache-2.0", "dependencies": { "@react-stately/utils": "^3.10.6", "@react-types/shared": "^3.29.0", @@ -4167,6 +4753,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.14.1.tgz", "integrity": "sha512-7P5h4YBAv3B/7BGq/kln+xSKgJCSq4xjt4HmJA7ZkGnEksUPUokBNQdWwZsy3lX/mwunaaKR9x/YNIu7yXB02g==", + "license": "Apache-2.0", "dependencies": { "@react-stately/collections": "^3.12.3", "@react-stately/flags": "^3.1.1", @@ -4186,6 +4773,7 @@ "version": "3.8.1", "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.8.1.tgz", "integrity": "sha512-1TBbt2BXbemstb/gEYw/NVt3esi5WvgWQW5Z7G8nDzLkpnMHOZXueoUkMxsdm0vhE8p0M9fsJQCMXKvCG3JzJg==", + "license": "Apache-2.0", "dependencies": { "@react-stately/list": "^3.12.1", "@react-types/shared": "^3.29.0", @@ -4200,6 +4788,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@react-stately/toast/-/toast-3.1.0.tgz", "integrity": "sha512-9W2+evz+EARrjkR1QPLlOL5lcNpVo6PjMAIygRSaCPJ6ftQAZ6B+7xTFGPFabWh83gwXQDUgoSwC3/vosvxZaQ==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0", "use-sync-external-store": "^1.4.0" @@ -4212,6 +4801,7 @@ "version": "3.8.3", "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.3.tgz", "integrity": "sha512-4T2V3P1RK4zEFz4vJjUXUXyB0g4Slm6stE6Ry20fzDWjltuW42cD2lmrd7ccTO/CXFmHLECcXQLD4GEbOj0epA==", + "license": "Apache-2.0", "dependencies": { "@react-stately/utils": "^3.10.6", "@react-types/checkbox": "^3.9.3", @@ -4226,6 +4816,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.5.3.tgz", "integrity": "sha512-btfy/gQ3Eccudx//4HkyQ+CRr3vxbLs74HYHthaoJ9GZbRj/3XDzfUM2X16zRoqTZVrIz/AkUj7AfGfsitU5nQ==", + "license": "Apache-2.0", "dependencies": { "@react-stately/overlays": "^3.6.15", "@react-types/tooltip": "^3.4.16", @@ -4239,6 +4830,7 @@ "version": "3.8.9", "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.8.9.tgz", "integrity": "sha512-j/LLI9UvbqcfOdl2v9m3gET3etUxoQzv3XdryNAbSkg0jTx8/13Fgi/Xp98bUcNLfynfeGW5P/fieU71sMkGog==", + "license": "Apache-2.0", "dependencies": { "@react-stately/collections": "^3.12.3", "@react-stately/selection": "^3.20.1", @@ -4254,6 +4846,7 @@ "version": "3.10.6", "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.6.tgz", "integrity": "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA==", + "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" }, @@ -4265,6 +4858,7 @@ "version": "3.7.12", "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.12.tgz", "integrity": "sha512-+LvGEADlv11mLQjxEAZriptSYJJTP+2OIFEKx0z9mmpp+8jTlEHFhAnRVaE6I9QCxcDB5F6q/olfizSwOPOMIg==", + "license": "Apache-2.0", "dependencies": { "@react-types/link": "^3.6.0", "@react-types/shared": "^3.29.0" @@ -4277,6 +4871,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.12.0.tgz", "integrity": "sha512-YrASNa+RqGQpzJcxNAahzNuTYVID1OE6HCorrEOXIyGS3EGogHsQmFs9OyThXnGHq6q4rLlA806/jWbP9uZdxA==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4288,6 +4883,7 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.7.0.tgz", "integrity": "sha512-RiEfX2ZTcvfRktQc5obOJtNTgW+UwjNOUW5yf9CLCNOSM07e0w5jtC1ewsOZZbcctMrMCljjL8niGWiBv1wQ1Q==", + "license": "Apache-2.0", "dependencies": { "@internationalized/date": "^3.8.0", "@react-types/shared": "^3.29.0" @@ -4300,6 +4896,7 @@ "version": "3.9.3", "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.3.tgz", "integrity": "sha512-h6wmK7CraKHKE6L13Ut+CtnjRktbMRhkCSorv7eg82M6p4PDhZ7mfDSh13IlGR4sryT8Ka+aOjOU+EvMrKiduA==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4311,6 +4908,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@react-types/color/-/color-3.0.4.tgz", "integrity": "sha512-D6Uea8kYGaoZRHgemJ0b0+iXbrvABP8RzsctL8Yp5QVyGgYJDMO8/7eZ3tdtGs/V8Iv+yCzG4yBexPA95i6tEg==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0", "@react-types/slider": "^3.7.10" @@ -4323,6 +4921,7 @@ "version": "3.13.4", "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.13.4.tgz", "integrity": "sha512-4mX7eZ/Bv3YWzEzLEZAF/TfKM+I+SCsvnm/cHqOJq3jEE8aVU1ql4Q1+3+SvciX3pfFIfeKlu9S3oYKRT5WIgg==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4334,6 +4933,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.12.0.tgz", "integrity": "sha512-dw/xflOdQPQ3uEABaBrZRTvjsMRu5/VZjRx9ygc64sX2N7HKIt+foMPXKJ+1jhtki2p4gigNVjcnJndJHoj9SA==", + "license": "Apache-2.0", "dependencies": { "@internationalized/date": "^3.8.0", "@react-types/calendar": "^3.7.0", @@ -4348,6 +4948,7 @@ "version": "3.5.17", "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.17.tgz", "integrity": "sha512-rKe2WrT272xuCH13euegBGjJAORYXJpHsX2hlu/f02TmMG4nSLss9vKBnY2N7k7nci65k5wDTW6lcsvQ4Co5zQ==", + "license": "Apache-2.0", "dependencies": { "@react-types/overlays": "^3.8.14", "@react-types/shared": "^3.29.0" @@ -4360,6 +4961,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.1.tgz", "integrity": "sha512-bPDckheJiHSIzSeSkLqrO6rXRLWvciFJr9rpCjq/+wBj6HsLh2iMpkB/SqmRHTGpPlJvlu0b7AlxK1FYE0QSKA==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4371,6 +4973,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.6.0.tgz", "integrity": "sha512-BQ5Tktb+fUxvtqksAJZuP8Z/bpmnQ/Y/zgwxfU0OKmIWkKMUsXY+e0GBVxwFxeh39D77stpVxRsTl7NQrjgtSw==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4382,6 +4985,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.6.0.tgz", "integrity": "sha512-+1ugDKTxson/WNOQZO4BfrnQ6cGDt+72mEytXMsSsd4aEC+x3RyUv6NKwdOl4n602cOreo0MHtap1X2BOACVoQ==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4393,6 +4997,7 @@ "version": "3.10.0", "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.10.0.tgz", "integrity": "sha512-DKMqEmUmarVCK0jblNkSlzSH53AAsxWCX9RaKZeP9EnRs2/l1oZRuiQVHlOQRgYwEigAXa2TrwcX4nnxZ+U36Q==", + "license": "Apache-2.0", "dependencies": { "@react-types/overlays": "^3.8.14", "@react-types/shared": "^3.29.0" @@ -4405,6 +5010,7 @@ "version": "3.4.8", "resolved": "https://registry.npmjs.org/@react-types/meter/-/meter-3.4.8.tgz", "integrity": "sha512-uXmHdUDbAo7L3EkytrUrU6DLOFUt63s9QSTcDp+vwyWoshY4/4Dm4JARdmhJU2ZP1nb2Sy45ASeMvSBw3ia2oA==", + "license": "Apache-2.0", "dependencies": { "@react-types/progress": "^3.5.11" }, @@ -4416,6 +5022,7 @@ "version": "3.8.10", "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.10.tgz", "integrity": "sha512-mdb4lMC4skO8Eqd0GeU4lJgDTEvqIhtINB5WCzLVZFrFVuxgWDoU5otsu0lbWhCnUA7XWQxupGI//TC1LLppjQ==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4427,6 +5034,7 @@ "version": "3.8.14", "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.14.tgz", "integrity": "sha512-XJS67KHYhdMvPNHXNGdmc85gE+29QT5TwC58V4kxxHVtQh9fYzEEPzIV8K84XWSz04rRGe3fjDgRNbcqBektWQ==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4438,6 +5046,7 @@ "version": "3.5.11", "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.11.tgz", "integrity": "sha512-CysuMld/lycOckrnlvrlsVoJysDPeBnUYBChwtqwiv4ZNRXos+wgAL1ows6dl7Nr57/FH5B4v5gf9AHEo7jUvw==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4449,6 +5058,7 @@ "version": "3.8.8", "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.8.tgz", "integrity": "sha512-QfAIp+0CnRSnoRTJVXUEPi+9AvFvRzWLIKEnE9OmgXjuvJCU3QNiwd8NWjNeE+94QBEVvAZQcqGU+44q5poxNg==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4460,6 +5070,7 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.6.1.tgz", "integrity": "sha512-XR4tYktxHxGJufpO0MTAPknIbmN5eZqXCZwTdBS4tecihf9iGDsXmrBOs+M7LEnil67GaZcFrMhKxOMVpLwZAg==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0", "@react-types/textfield": "^3.12.1" @@ -4472,6 +5083,7 @@ "version": "3.9.11", "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.11.tgz", "integrity": "sha512-uEpQCgDlrq/5fW05FgNEsqsqpvZVKfHQO9Mp7OTqGtm4UBNAbcQ6hOV7MJwQCS25Lu2luzOYdgqDUN8eAATJVQ==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4483,6 +5095,7 @@ "version": "3.29.0", "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } @@ -4491,6 +5104,7 @@ "version": "3.7.10", "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.10.tgz", "integrity": "sha512-Yb8wbpu2gS7AwvJUuz0IdZBRi6eIBZq32BSss4UHX0StA8dtR1/K4JeTsArxwiA3P0BA6t0gbR6wzxCvVA9fRw==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4502,6 +5116,7 @@ "version": "3.5.10", "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.10.tgz", "integrity": "sha512-YyNhx4CvuJ0Rvv7yMuQaqQuOIeg+NwLV00NHHJ+K0xEANSLcICLOLPNMOqRIqLSQDz5vDI705UKk8gVcxqPX5g==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4513,6 +5128,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.12.0.tgz", "integrity": "sha512-dmTzjCYwHf2HBOeTa/CEL177Aox0f0mkeLF5nQw/2z6SBolfmYoAwVTPxTaYFVu4MkEJxQTz9AuAsJvCbRJbhg==", + "license": "Apache-2.0", "dependencies": { "@react-types/grid": "^3.3.1", "@react-types/shared": "^3.29.0" @@ -4525,6 +5141,7 @@ "version": "3.3.14", "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.14.tgz", "integrity": "sha512-/uKsA7L2dctKU0JEaBWerlX+3BoXpKUFr3kHpRUoH66DSGvAo34vZ7kv/BHMZifJenIbF04GhDBsGp1zjrQKBg==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4536,6 +5153,7 @@ "version": "3.12.1", "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.12.1.tgz", "integrity": "sha512-6YTAMCKjEGuXg0A4bZA77j5QJ1a6yFviMUWsCIL6Dxq5K3TklzVsbAduSbHomPPuvkNTBSW4+TUJrVSnoTjMNA==", + "license": "Apache-2.0", "dependencies": { "@react-types/shared": "^3.29.0" }, @@ -4547,6 +5165,7 @@ "version": "3.4.16", "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.16.tgz", "integrity": "sha512-XEyKeqR3YxqJcR0cpigLGEBeRTEzrB0cu++IaADdqXJ8dBzS6s8y9EgR5UvKZmX1CQOBvMfXyYkj7nmJ039fOw==", + "license": "Apache-2.0", "dependencies": { "@react-types/overlays": "^3.8.14", "@react-types/shared": "^3.29.0" @@ -4559,6 +5178,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "estree-walker": "^2.0.2", @@ -4579,12 +5199,14 @@ "node_modules/@rollup/plugin-inject/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" }, "node_modules/@rollup/pluginutils": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -4605,12 +5227,14 @@ "node_modules/@rollup/pluginutils/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -4618,6 +5242,201 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz", + "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz", + "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz", + "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz", + "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz", + "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz", + "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz", + "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz", + "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz", + "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz", + "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz", + "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz", + "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz", + "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz", + "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz", + "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.40.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", @@ -4625,6 +5444,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -4637,11 +5457,51 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz", + "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz", + "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz", + "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4652,6 +5512,7 @@ "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" } @@ -4811,6 +5672,7 @@ "version": "3.13.2", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.2.tgz", "integrity": "sha512-LceSUgABBKF6HSsHK2ZqHzQ37IKV/jlaWbHm+NyTa3/WNb/JZVcThDuTainf+PixltOOcFCYXwxbLpOX9sCx+g==", + "license": "MIT", "dependencies": { "@tanstack/virtual-core": "3.13.2" }, @@ -4827,6 +5689,7 @@ "version": "3.13.2", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.2.tgz", "integrity": "sha512-Qzz4EgzMbO5gKrmqUondCjiHcuu4B1ftHb0pjCut661lXZdGoHeze9f/M8iwsK1t5LGR6aNuNGU7mxkowaW6RQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -4836,6 +5699,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz", "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==", + "license": "MIT", "dependencies": { "@turf/bearing": "^6.5.0", "@turf/destination": "^6.5.0", @@ -4851,6 +5715,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz", "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/meta": "^6.5.0" @@ -4863,6 +5728,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/meta": "^6.5.0" @@ -4875,6 +5741,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-6.5.0.tgz", "integrity": "sha512-+/r0NyL1lOG3zKZmmf6L8ommU07HliP4dgYToMoTxqzsWzyLjaj/OzgQ8rBmv703WJX+aS6yCmLuIhYqyufyuw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0" }, @@ -4886,6 +5753,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz", "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -4898,6 +5766,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz", "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -4910,6 +5779,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -4922,6 +5792,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.5.0.tgz", "integrity": "sha512-qeX4N6+PPWbKqp1AVkBVWFerGjMYMUyencwfnkCesoznU6qvfugFHNAngNqIBVnJjZ5n8IFyOf+akcxnrt9sNg==", + "license": "MIT", "dependencies": { "@turf/bbox": "^6.5.0", "@turf/center": "^6.5.0", @@ -4939,6 +5810,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.5.0.tgz", "integrity": "sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==", + "license": "MIT", "dependencies": { "@turf/bbox": "^6.5.0", "@turf/helpers": "^6.5.0" @@ -4951,6 +5823,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz", "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/meta": "^6.5.0" @@ -4963,6 +5836,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz", "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==", + "license": "MIT", "dependencies": { "@turf/destination": "^6.5.0", "@turf/helpers": "^6.5.0" @@ -4972,39 +5846,36 @@ } }, "node_modules/@turf/clean-coords": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-7.2.0.tgz", - "integrity": "sha512-+5+J1+D7wW7O/RDXn46IfCHuX1gIV1pIAQNSA7lcDbr3HQITZj334C4mOGZLEcGbsiXtlHWZiBtm785Vg8i+QQ==", + "version": "7.1.0", + "license": "MIT", "dependencies": { - "@turf/helpers": "^7.2.0", - "@turf/invariant": "^7.2.0", + "@turf/helpers": "^7.1.0", + "@turf/invariant": "^7.1.0", "@types/geojson": "^7946.0.10", - "tslib": "^2.8.1" + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/clean-coords/node_modules/@turf/helpers": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.2.0.tgz", - "integrity": "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==", + "version": "7.1.0", + "license": "MIT", "dependencies": { "@types/geojson": "^7946.0.10", - "tslib": "^2.8.1" + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/clean-coords/node_modules/@turf/invariant": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.2.0.tgz", - "integrity": "sha512-kV4u8e7Gkpq+kPbAKNC21CmyrXzlbBgFjO1PhrHPgEdNqXqDawoZ3i6ivE3ULJj2rSesCjduUaC/wyvH/sNr2Q==", + "version": "7.1.0", + "license": "MIT", "dependencies": { - "@turf/helpers": "^7.2.0", + "@turf/helpers": "^7.1.0", "@types/geojson": "^7946.0.10", - "tslib": "^2.8.1" + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" @@ -5014,6 +5885,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz", "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0" }, @@ -5025,6 +5897,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -5037,6 +5910,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-6.5.0.tgz", "integrity": "sha512-l8iR5uJqvI+5Fs6leNbhPY5t/a3vipUF/3AeVLpwPQcgmedNXyheYuy07PcMGH5Jdpi5gItOiTqwiU/bUH4b3A==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", @@ -5050,6 +5924,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz", "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -5062,6 +5937,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-6.5.0.tgz", "integrity": "sha512-kuXtwFviw/JqnyJXF1mrR/cb496zDTSbGKtSiolWMNImYzGGkbsAsFTjwJYgD7+4FixHjp0uQPzo70KDf3AIBw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", @@ -5076,6 +5952,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "license": "MIT", "funding": { "url": "https://opencollective.com/turf" } @@ -5084,6 +5961,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-6.5.0.tgz", "integrity": "sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", @@ -5097,6 +5975,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0" }, @@ -5108,6 +5987,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz", "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", @@ -5123,6 +6003,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz", "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", @@ -5136,6 +6017,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0" }, @@ -5147,6 +6029,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-6.5.0.tgz", "integrity": "sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==", + "license": "MIT", "dependencies": { "@turf/bearing": "^6.5.0", "@turf/destination": "^6.5.0", @@ -5161,6 +6044,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz", "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==", + "license": "MIT", "dependencies": { "@turf/bearing": "^6.5.0", "@turf/destination": "^6.5.0", @@ -5178,6 +6062,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-6.5.0.tgz", "integrity": "sha512-opHVQ4vjUhNBly1bob6RWy+F+hsZDH9SA0UW36pIRzfpu27qipU18xup0XXEePfY6+wvhF6yL/WgCO2IbrLqEA==", + "license": "MIT", "dependencies": { "@turf/bearing": "^6.5.0", "@turf/distance": "^6.5.0", @@ -5196,6 +6081,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-6.5.0.tgz", "integrity": "sha512-5p4n/ij97EIttAq+ewSnKt0ruvuM+LIDzuczSzuHTpq4oS7Oq8yqg5TQ4nzMVuK41r/tALCk7nAoBuw3Su4Gcw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -5208,6 +6094,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.5.0.tgz", "integrity": "sha512-/Pgh9mDvQWWu8HRxqpM+tKz8OzgauV+DiOcr3FCjD6ubDnrrmMJlsf6fFJmggw93mtVPrZRL6yyi9aYCQBOIvg==", + "license": "MIT", "dependencies": { "@turf/clone": "^6.5.0", "@turf/helpers": "^6.5.0", @@ -5221,6 +6108,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz", "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==", + "license": "MIT", "dependencies": { "@turf/boolean-clockwise": "^6.5.0", "@turf/clone": "^6.5.0", @@ -5236,6 +6124,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz", "integrity": "sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -5248,6 +6137,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz", "integrity": "sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -5260,6 +6150,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz", "integrity": "sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0" @@ -5269,52 +6160,48 @@ } }, "node_modules/@turf/simplify": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-7.2.0.tgz", - "integrity": "sha512-9YHIfSc8BXQfi5IvEMbCeQYqNch0UawIGwbboJaoV8rodhtk6kKV2wrpXdGqk/6Thg6/RWvChJFKVVTjVrULyQ==", + "version": "7.1.0", + "license": "MIT", "dependencies": { - "@turf/clean-coords": "^7.2.0", - "@turf/clone": "^7.2.0", - "@turf/helpers": "^7.2.0", - "@turf/meta": "^7.2.0", + "@turf/clean-coords": "^7.1.0", + "@turf/clone": "^7.1.0", + "@turf/helpers": "^7.1.0", + "@turf/meta": "^7.1.0", "@types/geojson": "^7946.0.10", - "tslib": "^2.8.1" + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/simplify/node_modules/@turf/clone": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-7.2.0.tgz", - "integrity": "sha512-JlGUT+/5qoU5jqZmf6NMFIoLDY3O7jKd53Up+zbpJ2vzUp6QdwdNzwrsCeONhynWM13F0MVtPXH4AtdkrgFk4g==", + "version": "7.1.0", + "license": "MIT", "dependencies": { - "@turf/helpers": "^7.2.0", + "@turf/helpers": "^7.1.0", "@types/geojson": "^7946.0.10", - "tslib": "^2.8.1" + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/simplify/node_modules/@turf/helpers": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.2.0.tgz", - "integrity": "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==", + "version": "7.1.0", + "license": "MIT", "dependencies": { "@types/geojson": "^7946.0.10", - "tslib": "^2.8.1" + "tslib": "^2.6.2" }, "funding": { "url": "https://opencollective.com/turf" } }, "node_modules/@turf/simplify/node_modules/@turf/meta": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.2.0.tgz", - "integrity": "sha512-igzTdHsQc8TV1RhPuOLVo74Px/hyPrVgVOTgjWQZzt3J9BVseCdpfY/0cJBdlSRI4S/yTmmHl7gAqjhpYH5Yaw==", + "version": "7.1.0", + "license": "MIT", "dependencies": { - "@turf/helpers": "^7.2.0", + "@turf/helpers": "^7.1.0", "@types/geojson": "^7946.0.10" }, "funding": { @@ -5325,6 +6212,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz", "integrity": "sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag==", + "license": "MIT", "dependencies": { "@turf/centroid": "^6.5.0", "@turf/clone": "^6.5.0", @@ -5343,6 +6231,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-6.5.0.tgz", "integrity": "sha512-VsATGXC9rYM8qTjbQJ/P7BswKWXHdnSJ35JlV4OsZyHBMxJQHftvmZJsFbOqVtQnIQIzf2OAly6rfzVV9QLr7g==", + "license": "MIT", "dependencies": { "@turf/bbox": "^6.5.0", "@turf/center": "^6.5.0", @@ -5363,6 +6252,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-6.5.0.tgz", "integrity": "sha512-NABLw5VdtJt/9vSstChp93pc6oel4qXEos56RBMsPlYB8hzNTEKYtC146XJvyF4twJeeYS8RVe1u7KhoFwEM5w==", + "license": "MIT", "dependencies": { "@turf/clone": "^6.5.0", "@turf/helpers": "^6.5.0", @@ -5378,6 +6268,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.5.0.tgz", "integrity": "sha512-igYWCwP/f0RFHIlC2c0SKDuM/ObBaqSljI3IdV/x71805QbIvY/BYGcJdyNcgEA6cylIGl/0VSlIbpJHZ9ldhw==", + "license": "MIT", "dependencies": { "@turf/helpers": "^6.5.0", "@turf/invariant": "^6.5.0", @@ -5434,16 +6325,14 @@ }, "node_modules/@types/brotli": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/brotli/-/brotli-1.3.4.tgz", - "integrity": "sha512-cKYjgaS2DMdCKF7R0F5cgx1nfBYObN2ihIuPGQ4/dlIY6RpV7OWNwe9L8V4tTVKL2eZqOkNM9FM/rgTvLf4oXw==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/crypto-js": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", - "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==" + "license": "MIT" }, "node_modules/@types/css-font-loading-module": { "version": "0.0.7", @@ -5481,12 +6370,14 @@ "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" }, "node_modules/@types/geojson-vt": { "version": "3.2.5", @@ -5553,8 +6444,7 @@ }, "node_modules/@types/pako": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz", - "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==" + "license": "MIT" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -5659,6 +6549,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/workerpool": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@types/workerpool/-/workerpool-6.4.7.tgz", + "integrity": "sha512-DI2U4obcMzFViyNjLw0xXspim++qkAJ4BWRdYPVMMFtOpTvMr6PAk3UTZEoSqnZnvgUkJ3ck97Ybk+iIfuJHMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.25.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", @@ -5893,6 +6793,7 @@ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", @@ -6051,8 +6952,7 @@ }, "node_modules/@vivaxy/png": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@vivaxy/png/-/png-1.3.0.tgz", - "integrity": "sha512-DkJnWnd451Y6VuuNXVnUD8V7Mg3ZeQ7F77YwH2sB8tyi4KrUEfaDvMyqlCSpSLlTmtsXlT8BOaegjMRg/VTRdQ==", + "license": "MIT", "dependencies": { "pako": "^1.0.10" } @@ -6207,6 +7107,7 @@ "version": "1.5.3", "resolved": "https://registry.npmjs.org/@webviz/group-tree-plot/-/group-tree-plot-1.5.3.tgz", "integrity": "sha512-Wgbg73AKFt/xd+VMYvHu2/4qoQtfK52lHCDoT6aNKH3oMhGjtzXIO5VdxqxNaqT5NkqUdqZOcC4vr2EFRaUsTg==", + "license": "MPL-2.0", "dependencies": { "d3": "^7.8.2", "lodash": "^4.17.21", @@ -6218,9 +7119,10 @@ } }, "node_modules/@webviz/subsurface-viewer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webviz/subsurface-viewer/-/subsurface-viewer-1.11.1.tgz", - "integrity": "sha512-xLexNjh0HbYiL5o0vqr2ksqOlCiumpxnXPMTtu7HRuXYqjEkwoMLfvM6wwLD0nQ8+LnbRPU0kXoohIt0vSXmPw==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@webviz/subsurface-viewer/-/subsurface-viewer-1.11.2.tgz", + "integrity": "sha512-urPdKPdgPPHx7WYEZ+20iiyWWvmJfbiUG+Wy92BNTnFbLjR3W+sA0AyzvKj7RKAdL1PGjsK+lbmT3f+/fGbZjA==", + "license": "MPL-2.0", "dependencies": { "@deck.gl-community/editable-layers": "^9.1.0-beta.4", "@deck.gl/aggregation-layers": "^9.1.11", @@ -6267,10 +7169,171 @@ } } }, + "node_modules/@webviz/subsurface-viewer/node_modules/@deck.gl-community/editable-layers": { + "version": "9.1.0-beta.5", + "resolved": "https://registry.npmjs.org/@deck.gl-community/editable-layers/-/editable-layers-9.1.0-beta.5.tgz", + "integrity": "sha512-kMlGepC81fRPE4Vuu9bsF4dv0Jz+5ZIfYRmfedWNyC1S9zJAFBBkIwkfFPEMC8IkL4Tjd1CRfCVyOcy3CqBd+w==", + "license": "MIT", + "dependencies": { + "@turf/along": "^6.5.0", + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/bearing": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/buffer": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/circle": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/difference": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/midpoint": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0", + "@turf/rewind": "^6.5.0", + "@turf/transform-rotate": "^6.5.0", + "@turf/transform-scale": "^6.5.0", + "@turf/transform-translate": "^6.5.0", + "@turf/union": "^6.5.0", + "@types/geojson": "^7946.0.14", + "cubic-hermite-spline": "^1.0.1", + "eventemitter3": "^5.0.0", + "lodash.omit": "^4.1.1", + "lodash.throttle": "^4.1.1", + "uuid": "9.0.0", + "viewport-mercator-project": ">=6.2.3" + }, + "peerDependencies": { + "@deck.gl-community/layers": "^9.1.0-beta.2", + "@deck.gl/core": "^9.1.0", + "@deck.gl/extensions": "^9.1.0", + "@deck.gl/geo-layers": "^9.1.0", + "@deck.gl/layers": "^9.1.0", + "@deck.gl/mesh-layers": "^9.1.0", + "@luma.gl/constants": ">=9.1.0", + "@luma.gl/core": ">=9.1.0", + "@luma.gl/engine": ">=9.1.0", + "@math.gl/core": ">=4.0.1" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@deck.gl/aggregation-layers": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-9.1.11.tgz", + "integrity": "sha512-3sVHcNsLj8Lzevf/2uZUn4pv6Z5Bpz7CzwataQ0EWGjjzHJ/4uIi24EIYEJmTzLGueenFS+70s8hWujJaAJMoQ==", + "license": "MIT", + "dependencies": { + "@luma.gl/constants": "^9.1.5", + "@luma.gl/shadertools": "^9.1.5", + "@math.gl/core": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "d3-hexbin": "^0.2.1" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@deck.gl/layers": "^9.1.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@deck.gl/extensions": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-9.1.11.tgz", + "integrity": "sha512-wnWUhd7yyQTdHB0x2fE/FPeZUz5ZzKNDx2FwY+bQuQVaiNcc5YpGKQSV0ahIKUg6NF+shxZIJ/WBVi3wc6RFRQ==", + "license": "MIT", + "dependencies": { + "@luma.gl/constants": "^9.1.5", + "@luma.gl/shadertools": "^9.1.5", + "@math.gl/core": "^4.1.0" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@deck.gl/geo-layers": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-9.1.11.tgz", + "integrity": "sha512-z2rYT9617JaIP+a1nKhbxu32a5BjiR5L0Tmd4x7UUOlipx6A5850SyPyIUx5ca7lpIlbukGRjg600pRKKk3XEA==", + "license": "MIT", + "dependencies": { + "@loaders.gl/3d-tiles": "^4.2.0", + "@loaders.gl/gis": "^4.2.0", + "@loaders.gl/loader-utils": "^4.2.0", + "@loaders.gl/mvt": "^4.2.0", + "@loaders.gl/schema": "^4.2.0", + "@loaders.gl/terrain": "^4.2.0", + "@loaders.gl/tiles": "^4.2.0", + "@loaders.gl/wms": "^4.2.0", + "@luma.gl/gltf": "^9.1.5", + "@luma.gl/shadertools": "^9.1.5", + "@math.gl/core": "^4.1.0", + "@math.gl/culling": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "@types/geojson": "^7946.0.8", + "h3-js": "^4.1.0", + "long": "^3.2.0" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@deck.gl/extensions": "^9.1.0", + "@deck.gl/layers": "^9.1.0", + "@deck.gl/mesh-layers": "^9.1.0", + "@loaders.gl/core": "^4.2.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@deck.gl/layers": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-9.1.11.tgz", + "integrity": "sha512-RDFF+YH4BP3HPUCtblnfTaWXBt1DBk0i7FbQ9jgcjdv4CB7MtEOHme5k2LJ0cBV4ROQtSHg/vjAvMayZewI4vg==", + "license": "MIT", + "dependencies": { + "@loaders.gl/images": "^4.2.0", + "@loaders.gl/schema": "^4.2.0", + "@luma.gl/shadertools": "^9.1.5", + "@mapbox/tiny-sdf": "^2.0.5", + "@math.gl/core": "^4.1.0", + "@math.gl/polygon": "^4.1.0", + "@math.gl/web-mercator": "^4.1.0", + "earcut": "^2.2.4" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@loaders.gl/core": "^4.2.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@deck.gl/mesh-layers": { + "version": "9.1.11", + "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.1.11.tgz", + "integrity": "sha512-9zdtRV8rVyJI4t8bPE3/jLEuiBfCp+53KneFrh/nmANVupZ5SP44TobSWJWl+nZ3Xs4LkJXaQPbI8gZR39lJBQ==", + "license": "MIT", + "dependencies": { + "@loaders.gl/gltf": "^4.2.0", + "@luma.gl/gltf": "^9.1.5", + "@luma.gl/shadertools": "^9.1.5" + }, + "peerDependencies": { + "@deck.gl/core": "^9.1.0", + "@luma.gl/core": "^9.1.5", + "@luma.gl/engine": "^9.1.5" + } + }, "node_modules/@webviz/subsurface-viewer/node_modules/@equinor/eds-core-react": { "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@equinor/eds-core-react/-/eds-core-react-0.36.1.tgz", - "integrity": "sha512-cFpmsT4+EEFDhGE1DLNDT9Scr6SNBF4xnIfAgkMZcK6wmmZZT30lV2zdGgFC1JN9FfyvlisQukgpurynuBoJTw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.0", "@equinor/eds-icons": "^0.21.0", @@ -6285,33 +7348,98 @@ "pnpm": ">=4" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8", - "styled-components": ">=4.2" + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4.2" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@equinor/eds-utils": { + "version": "0.8.4", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.8", + "@equinor/eds-tokens": "0.9.2" + }, + "engines": { + "node": ">=10.0.0", + "pnpm": ">=4" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4.2" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@luma.gl/constants": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-9.1.9.tgz", + "integrity": "sha512-yc9fml04OeTTcwK+7gmDMxoLQ67j4ZiAFXjmYvPomYyBVzS0NZxTDuwcCBmnxjLOiroOZW8FRRrVc/yOiFug2w==", + "license": "MIT" + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@luma.gl/core": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-9.1.9.tgz", + "integrity": "sha512-1i9N7+I/UbFjx3axSMlc3/NufA+C2iBv/7mw51gRE/ypQPgvFmY/QqXBVZRe+nthF+OhlUMhO19TBndzYFTWhA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@math.gl/types": "^4.1.0", + "@probe.gl/env": "^4.0.8", + "@probe.gl/log": "^4.0.8", + "@probe.gl/stats": "^4.0.8", + "@types/offscreencanvas": "^2019.6.4" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@luma.gl/engine": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-9.1.9.tgz", + "integrity": "sha512-n1GLK1sUMFkWxdb+aZYn6ZBFltFEMi7X+6ZPxn2pBsNT6oeF4AyvH5AyqhOpvHvUnCLDt3Zsf1UIfx3MI//YSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@math.gl/core": "^4.1.0", + "@math.gl/types": "^4.1.0", + "@probe.gl/log": "^4.0.8", + "@probe.gl/stats": "^4.0.8" + }, + "peerDependencies": { + "@luma.gl/core": "^9.1.0", + "@luma.gl/shadertools": "^9.1.0" } }, - "node_modules/@webviz/subsurface-viewer/node_modules/@equinor/eds-utils": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@equinor/eds-utils/-/eds-utils-0.8.4.tgz", - "integrity": "sha512-njvqXd3Hzfy5vkEqnx+uEBAu00vnG/5R+gDgWCReVDjjUoHdQNcrqfjBLsGF2UungtO0LbYV8YuBP+9l4V7ywQ==", + "node_modules/@webviz/subsurface-viewer/node_modules/@luma.gl/gltf": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/gltf/-/gltf-9.1.9.tgz", + "integrity": "sha512-KgVBIFCtRO1oadgMDycMJA5s+q519l/fQBGAZpUcLfWsaEDQfdHW2NLdrK/00VDv46Ng8tN/O6uyH6E40uLcLw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.8", - "@equinor/eds-tokens": "0.9.2" + "@loaders.gl/core": "^4.2.0", + "@loaders.gl/textures": "^4.2.0", + "@math.gl/core": "^4.1.0" }, - "engines": { - "node": ">=10.0.0", - "pnpm": ">=4" + "peerDependencies": { + "@luma.gl/core": "^9.1.0", + "@luma.gl/engine": "^9.1.0", + "@luma.gl/shadertools": "^9.1.0" + } + }, + "node_modules/@webviz/subsurface-viewer/node_modules/@luma.gl/shadertools": { + "version": "9.1.9", + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-9.1.9.tgz", + "integrity": "sha512-Uqp2xfgIEunRMLXTeCJ4uEMlWcUGcYMZGJ8GAOrAeDzn4bMKVRKmZDC71vkuTctnaodM3UdrI9W6s1sJlrXsxw==", + "license": "MIT", + "dependencies": { + "@math.gl/core": "^4.1.0", + "@math.gl/types": "^4.1.0", + "wgsl_reflect": "^1.2.0" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8", - "styled-components": ">=4.2" + "@luma.gl/core": "^9.1.0" } }, "node_modules/@webviz/subsurface-viewer/node_modules/@tanstack/react-virtual": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.1.3.tgz", - "integrity": "sha512-YCzcbF/Ws/uZ0q3Z6fagH+JVhx4JLvbSflgldMgLsuvB8aXjZLLb3HvrEVxY480F9wFlBiXlvQxOyXb5ENPrNA==", + "license": "MIT", "dependencies": { "@tanstack/virtual-core": "3.1.3" }, @@ -6326,8 +7454,7 @@ }, "node_modules/@webviz/subsurface-viewer/node_modules/@tanstack/virtual-core": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.3.tgz", - "integrity": "sha512-Y5B4EYyv1j9V8LzeAoOVeTg0LI7Fo5InYKgAjkY1Pu9GjtUwX/EKxNcU7ng3sKr99WEf+bPTcktAeybyMOYo+g==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -6335,16 +7462,14 @@ }, "node_modules/@webviz/subsurface-viewer/node_modules/d3-format": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/@webviz/subsurface-viewer/node_modules/downshift": { "version": "8.3.3", - "resolved": "https://registry.npmjs.org/downshift/-/downshift-8.3.3.tgz", - "integrity": "sha512-f9znQFYF/3AWBkFiEc4H05Vdh41XFgJ80IatLBKIFoA3p86mAXc/iM9/XJ24loF9djtABD5NBEYL7b1b7xh2pw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.22.15", "compute-scroll-into-view": "^3.0.3", @@ -6356,10 +7481,17 @@ "react": ">=16.12.0" } }, + "node_modules/@webviz/subsurface-viewer/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/@webviz/well-completions-plot": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@webviz/well-completions-plot/-/well-completions-plot-1.7.3.tgz", "integrity": "sha512-3cI2Wkjefq2xJYrLZfOwj6PR1Lqz140y9m3H4YXWa+Twi2nXGGlEHsqpemJr0wD8aCWchQPw9+HjJlYkvqckAQ==", + "license": "MPL-2.0", "dependencies": { "react-resize-detector": "^11.0.1", "react-tooltip": "^5.28.0" @@ -6380,6 +7512,7 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/@webviz/well-log-viewer/-/well-log-viewer-2.4.4.tgz", "integrity": "sha512-quduVDS8ZBMPgdW+5YbbUGycitMY44ZZ7+rr9YfrEvWSWi/wRTgFg1eh0AmzKNiP6NmPXZxmIZ7oMauyGbunhw==", + "license": "MPL-2.0", "dependencies": { "@emerson-eps/color-tables": "^0.4.92", "@equinor/videx-wellog": "^0.11.3", @@ -6737,6 +7870,7 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -6746,12 +7880,14 @@ "node_modules/asn1.js/node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", @@ -6836,8 +7972,6 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -6851,7 +7985,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -6888,7 +8023,8 @@ "node_modules/bn.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", - "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==" + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -6912,12 +8048,12 @@ "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" }, "node_modules/brotli": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", - "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", "optional": true, "dependencies": { "base64-js": "^1.1.2" @@ -6927,6 +8063,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "license": "MIT", "dependencies": { "resolve": "^1.17.0" } @@ -6935,6 +8072,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -6948,6 +8086,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", "dependencies": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -6958,6 +8097,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", "dependencies": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", @@ -6969,6 +8109,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", "dependencies": { "bn.js": "^5.2.1", "randombytes": "^2.1.0", @@ -6982,6 +8123,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "license": "ISC", "dependencies": { "bn.js": "^5.2.1", "browserify-rsa": "^4.1.0", @@ -7002,6 +8144,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", "dependencies": { "pako": "~1.0.5" } @@ -7040,8 +8183,7 @@ }, "node_modules/buf-compare": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", - "integrity": "sha512-Bvx4xH00qweepGc43xFvMs5BKASXTbHaHm6+kDYIK9p/4iFwjATQkmPKHQSgJZzKbAymhztRbXUf1Nqhzl73/Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7064,6 +8206,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -7077,12 +8220,14 @@ "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "license": "MIT" }, "node_modules/c12": { "version": "2.0.1", @@ -7263,8 +8408,7 @@ }, "node_modules/charenc": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", "engines": { "node": "*" } @@ -7334,6 +8478,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "license": "MIT", "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" @@ -7459,9 +8604,8 @@ "license": "MIT" }, "node_modules/complex.js": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz", - "integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==", + "version": "2.3.0", + "license": "MIT", "engines": { "node": "*" }, @@ -7516,7 +8660,8 @@ "node_modules/constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==" + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", @@ -7533,8 +8678,7 @@ }, "node_modules/core-assert": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", - "integrity": "sha512-IG97qShIP+nrJCXMCgkNZgH7jZQ4n8RpPyPeXX++T6avR/KhLhgLiHKoEn5Rc1KjfycSfA9DMa6m+4C4eguHhw==", + "license": "MIT", "dependencies": { "buf-compare": "^1.0.0", "is-error": "^2.2.0" @@ -7581,6 +8725,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" @@ -7589,12 +8734,14 @@ "node_modules/create-ecdh/node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -7607,6 +8754,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -7619,7 +8767,8 @@ "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7637,8 +8786,7 @@ }, "node_modules/crypt": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", "engines": { "node": "*" } @@ -7647,6 +8795,7 @@ "version": "3.12.1", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", "dependencies": { "browserify-cipher": "^1.0.1", "browserify-sign": "^4.2.3", @@ -7811,7 +8960,8 @@ "node_modules/cubic-hermite-spline": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cubic-hermite-spline/-/cubic-hermite-spline-1.0.1.tgz", - "integrity": "sha512-OlfZfJqnCi44aYNg3YMn0IqYcvlUGv3SzRqNbm19cnZNTaMiWjFeA5l6rF/WLnmh1VBZs/kYc2QwAkD1t2Zhdg==" + "integrity": "sha512-OlfZfJqnCi44aYNg3YMn0IqYcvlUGv3SzRqNbm19cnZNTaMiWjFeA5l6rF/WLnmh1VBZs/kYc2QwAkD1t2Zhdg==", + "license": "MIT" }, "node_modules/culori": { "version": "3.2.0", @@ -7828,6 +8978,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz", "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==", + "license": "MIT", "dependencies": { "uniq": "^1.0.0" } @@ -8032,6 +9183,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "license": "BSD-3-Clause", "dependencies": { "d3-array": "1" } @@ -8070,7 +9222,8 @@ "node_modules/d3-geo/node_modules/d3-array": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" }, "node_modules/d3-hexbin": { "version": "0.2.2", @@ -8366,8 +9519,7 @@ }, "node_modules/deep-strict-equal": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/deep-strict-equal/-/deep-strict-equal-0.2.0.tgz", - "integrity": "sha512-3daSWyvZ/zwJvuMGlzG1O+Ow0YSadGfb3jsh9xoCutv2tWyB9dA4YvR9L9/fSdDZa2dByYQe+TqapSGUrjnkoA==", + "license": "MIT", "dependencies": { "core-assert": "^0.2.0" }, @@ -8549,6 +9701,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" @@ -8582,6 +9735,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", @@ -8591,7 +9745,8 @@ "node_modules/diffie-hellman/node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -8606,6 +9761,7 @@ "version": "4.22.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -8642,8 +9798,7 @@ }, "node_modules/draco3d": { "version": "1.5.7", - "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", - "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==" + "license": "Apache-2.0" }, "node_modules/draw-svg-path": { "version": "1.0.0", @@ -8722,6 +9877,7 @@ "version": "6.6.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -8735,7 +9891,8 @@ "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -9053,8 +10210,7 @@ }, "node_modules/escape-latex": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", - "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -9665,6 +10821,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -9826,17 +10983,20 @@ "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "version": "4.5.0", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "dependencies": { - "strnum": "^1.1.1" + "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" @@ -9852,8 +11012,7 @@ }, "node_modules/fflate": { "version": "0.7.4", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", - "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==" + "license": "MIT" }, "node_modules/figures": { "version": "5.0.0", @@ -10032,8 +11191,7 @@ }, "node_modules/fraction.js": { "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", "engines": { "node": "*" }, @@ -10107,6 +11265,20 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", @@ -10173,6 +11345,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz", "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==", + "license": "MIT", "dependencies": { "@turf/bbox": "*", "@turf/helpers": "6.x", @@ -10184,7 +11357,8 @@ "node_modules/geojson-rbush/node_modules/@types/geojson": { "version": "7946.0.8", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", - "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", + "license": "MIT" }, "node_modules/geojson-vt": { "version": "3.2.1", @@ -10698,9 +11872,8 @@ "peer": true }, "node_modules/h3-js": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-4.2.1.tgz", - "integrity": "sha512-HYiUrq5qTRFqMuQu3jEHqxXLk1zsSJiby9Lja/k42wHjabZG7tN9rOuzT/PEFf+Wa7rsnHLMHRWIu0mgcJ0ewQ==", + "version": "4.1.0", + "license": "Apache-2.0", "engines": { "node": ">=4", "npm": ">=3", @@ -10799,6 +11972,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" @@ -10811,6 +11985,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -10830,6 +12005,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -10864,7 +12040,8 @@ "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "license": "MIT" }, "node_modules/human-signals": { "version": "5.0.0", @@ -10924,8 +12101,7 @@ }, "node_modules/image-size": { "version": "0.7.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.7.5.tgz", - "integrity": "sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g==", + "license": "MIT", "bin": { "image-size": "bin/image-size.js" }, @@ -10935,8 +12111,7 @@ }, "node_modules/immediate": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "license": "MIT" }, "node_modules/immutable": { "version": "4.3.1", @@ -10992,6 +12167,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/inorder-tree-layout/-/inorder-tree-layout-1.0.0.tgz", "integrity": "sha512-nRgl0K3Cd8LC5U3qNzhwwDFC9CIGy0O/RN52L9woY0nqS0UkasEP752l2zxTql3P93FgNuewQKjmsNYNYw4xmA==", + "license": "MIT", "dependencies": { "bit-twiddle": "~0.0.1" } @@ -10999,7 +12175,8 @@ "node_modules/inorder-tree-layout/node_modules/bit-twiddle": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-0.0.2.tgz", - "integrity": "sha512-76iFAOrkcuw5UPA30Pt32XaytMHXz/04JembgIwsQAp7ImHYSWNq1shBbrlWf6CUvh1+amQ81LI8hNhqQgsBEw==" + "integrity": "sha512-76iFAOrkcuw5UPA30Pt32XaytMHXz/04JembgIwsQAp7ImHYSWNq1shBbrlWf6CUvh1+amQ81LI8hNhqQgsBEw==", + "license": "MIT" }, "node_modules/internal-slot": { "version": "1.1.0", @@ -11034,6 +12211,7 @@ "version": "10.7.16", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz", "integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==", + "license": "BSD-3-Clause", "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/fast-memoize": "2.2.7", @@ -11044,12 +12222,14 @@ "node_modules/iota-array": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", - "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" + "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==", + "license": "MIT" }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -11151,8 +12331,7 @@ }, "node_modules/is-buffer": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "license": "MIT" }, "node_modules/is-bun-module": { "version": "1.3.0", @@ -11234,8 +12413,7 @@ }, "node_modules/is-error": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", - "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==" + "license": "MIT" }, "node_modules/is-extglob": { "version": "2.1.1", @@ -11360,6 +12538,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -11600,6 +12779,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz", "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", + "license": "MIT", "engines": { "node": ">=10" } @@ -11722,8 +12902,7 @@ }, "node_modules/javascript-natural-sort": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + "license": "MIT" }, "node_modules/jest-worker": { "version": "27.5.1", @@ -11829,17 +13008,6 @@ "node": ">= 6.0.0" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -11894,8 +13062,7 @@ }, "node_modules/jszip": { "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -11937,9 +13104,8 @@ } }, "node_modules/ktx-parse": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.7.1.tgz", - "integrity": "sha512-FeA3g56ksdFNwjXJJsc1CCc7co+AJYDp6ipIp878zZ2bU8kWROatLYf39TQEd4/XRSUvBXovQ8gaVKWPXsCLEQ==" + "version": "0.0.4", + "license": "MIT" }, "node_modules/levn": { "version": "0.4.1", @@ -11955,8 +13121,7 @@ }, "node_modules/lie": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", "dependencies": { "immediate": "~3.0.5" } @@ -12070,7 +13235,8 @@ "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" }, "node_modules/lodash._basebind": { "version": "2.3.0", @@ -12214,7 +13380,8 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==", - "deprecated": "This package is deprecated. Use destructuring assignment syntax instead." + "deprecated": "This package is deprecated. Use destructuring assignment syntax instead.", + "license": "MIT" }, "node_modules/lodash.support": { "version": "2.3.0", @@ -12226,12 +13393,12 @@ "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" }, "node_modules/long": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==", + "license": "Apache-2.0", "engines": { "node": ">=0.6" } @@ -12257,20 +13424,19 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, "node_modules/lz4js": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz", - "integrity": "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg==", + "license": "ISC", "optional": true }, "node_modules/lzo-wasm": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/lzo-wasm/-/lzo-wasm-0.0.4.tgz", - "integrity": "sha512-VKlnoJRFrB8SdJhlVKvW5vI1gGwcZ+mvChEXcSX6r2xDNc/Q2FD9esfBmGCuPZdrJ1feO+YcVFd2PTk0c137Gw==" + "license": "BSD-2-Clause" }, "node_modules/magic-string": { "version": "0.30.17", @@ -12464,7 +13630,8 @@ "node_modules/material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", - "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", + "license": "ISC" }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -12484,19 +13651,17 @@ }, "node_modules/math.gl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-4.1.0.tgz", - "integrity": "sha512-FtvCJuuAlvn4358e2SkepTv2gnV7VTvu0y/hwkkjS/urDk+nY9x/4Tsn19LmaJl1wqKaqn+QFZhbnjAsuMOkWA==", + "license": "MIT", "dependencies": { "@math.gl/core": "4.1.0" } }, "node_modules/mathjs": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-13.2.3.tgz", - "integrity": "sha512-I67Op0JU7gGykFK64bJexkSAmX498x0oybxfVXn1rroEMZTmfxppORhnk8mEUnPrbTfabDKCqvm18vJKMk2UJQ==", + "version": "13.2.0", + "license": "Apache-2.0", "dependencies": { - "@babel/runtime": "^7.25.7", - "complex.js": "^2.2.5", + "@babel/runtime": "^7.25.6", + "complex.js": "^2.1.1", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", "fraction.js": "^4.3.7", @@ -12514,8 +13679,7 @@ }, "node_modules/md5": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -12526,6 +13690,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -12534,8 +13699,7 @@ }, "node_modules/merge-refs": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.3.0.tgz", - "integrity": "sha512-nqXPXbso+1dcKDpPCXvwZyJILz+vSLqGGOnDrYHQYE+B8n9JTCekVLC65AfCpR4ggVyA/45Y0iR9LDyS2iI+zA==", + "license": "MIT", "funding": { "url": "https://github.com/wojtekmaj/merge-refs?sponsor=1" }, @@ -12576,6 +13740,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", "dependencies": { "bn.js": "^4.0.0", "brorand": "^1.0.1" @@ -12587,7 +13752,8 @@ "node_modules/miller-rabin/node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" }, "node_modules/mime-db": { "version": "1.52.0", @@ -12620,12 +13786,14 @@ "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" }, "node_modules/minimatch": { "version": "3.1.2", @@ -12686,7 +13854,8 @@ "node_modules/mjolnir.js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mjolnir.js/-/mjolnir.js-3.0.0.tgz", - "integrity": "sha512-siX3YCG7N2HnmN1xMH3cK4JkUZJhbkhRFJL+G5N1vH0mh1t5088rJknIoqDFWDIU6NPGvRRgLnYW3ZHjSMEBLA==" + "integrity": "sha512-siX3YCG7N2HnmN1xMH3cK4JkUZJhbkhRFJL+G5N1vH0mh1t5088rJknIoqDFWDIU6NPGvRRgLnYW3ZHjSMEBLA==", + "license": "MIT" }, "node_modules/mkdirp": { "version": "1.0.4", @@ -12846,6 +14015,7 @@ "version": "1.0.19", "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", + "license": "MIT", "dependencies": { "iota-array": "^1.0.0", "is-buffer": "^1.0.2" @@ -12855,6 +14025,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/ndarray-ops/-/ndarray-ops-1.2.2.tgz", "integrity": "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw==", + "license": "MIT", "dependencies": { "cwise-compiler": "^1.0.0" } @@ -12863,6 +14034,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ndarray-pack/-/ndarray-pack-1.2.1.tgz", "integrity": "sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g==", + "license": "MIT", "dependencies": { "cwise-compiler": "^1.1.2", "ndarray": "^1.0.13" @@ -12872,6 +14044,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/ndarray-scratch/-/ndarray-scratch-1.2.0.tgz", "integrity": "sha512-a4pASwB1jQyJcKLYrwrladVfDZDUGc78qLJZbHyb1Q4rhte0URhzc6ALQpBcauwgov0sXLwZz3vYH5jKAhSMIg==", + "license": "MIT", "dependencies": { "ndarray": "^1.0.14", "ndarray-ops": "^1.2.1", @@ -12881,7 +14054,8 @@ "node_modules/ndarray-select": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ndarray-select/-/ndarray-select-1.0.1.tgz", - "integrity": "sha512-bUQH+V2TQnPA0tiB6pliaUSYUTld6Il3UILaH/7T3S7RKzuW9RdA08hayeRJr04Xu4yNTpccLL3YYz7rNd7ffw==" + "integrity": "sha512-bUQH+V2TQnPA0tiB6pliaUSYUTld6Il3UILaH/7T3S7RKzuW9RdA08hayeRJr04Xu4yNTpccLL3YYz7rNd7ffw==", + "license": "MIT" }, "node_modules/needle": { "version": "2.9.1", @@ -12933,6 +14107,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz", "integrity": "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==", + "license": "MIT", "dependencies": { "assert": "^2.0.0", "browser-resolve": "^2.0.0", @@ -12969,12 +14144,14 @@ "node_modules/node-stdlib-browser/node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" }, "node_modules/node-stdlib-browser/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -13079,6 +14256,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -13227,7 +14405,8 @@ "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==" + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "license": "MIT" }, "node_modules/own-keys": { "version": "1.0.1", @@ -13280,8 +14459,7 @@ }, "node_modules/pako": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { "version": "1.0.1", @@ -13303,6 +14481,7 @@ "version": "5.1.7", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "license": "ISC", "dependencies": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", @@ -13353,7 +14532,8 @@ "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", @@ -13431,6 +14611,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "license": "MIT", "dependencies": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -13516,6 +14697,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "license": "MIT", "dependencies": { "find-up": "^5.0.0" }, @@ -13570,6 +14752,21 @@ "node": ">=18" } }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plotly.js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-3.0.1.tgz", @@ -13802,6 +14999,7 @@ "version": "0.15.7", "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.7.tgz", "integrity": "sha512-nhfdr83ECBg6xtqOAJab1tbksbBAOMUltN60bU+llHVOL0e5Onm1WpAXXWXVB39L8AJFssoIhEVuy/S90MmotA==", + "license": "MIT", "dependencies": { "robust-predicates": "^3.0.2", "splaytree": "^3.1.0" @@ -13948,6 +15146,7 @@ "version": "10.26.6", "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz", "integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==", + "license": "MIT", "peer": true, "funding": { "type": "opencollective", @@ -14002,6 +15201,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -14047,6 +15247,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", "dependencies": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", @@ -14059,7 +15260,8 @@ "node_modules/public-encrypt/node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.0", @@ -14073,6 +15275,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -14134,6 +15337,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", "dependencies": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" @@ -14143,6 +15347,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "license": "MIT", "dependencies": { "quickselect": "^2.0.0" } @@ -14171,6 +15376,7 @@ "version": "3.39.0", "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.39.0.tgz", "integrity": "sha512-zXCjR01WnfW4uW0f294uWrvdfwEMHgDFSwMwMBwRafAvmsQea87X5VTAfDmQOAbPa+iQFcngIyH0Pn5CfXNrjw==", + "license": "Apache-2.0", "dependencies": { "@internationalized/string": "^3.2.6", "@react-aria/breadcrumbs": "^3.5.23", @@ -14224,6 +15430,7 @@ "version": "2.19.3", "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "license": "MIT", "dependencies": { "@icons/material": "^0.2.4", "lodash": "^4.17.15", @@ -14268,6 +15475,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14276,6 +15484,7 @@ "version": "9.1.1", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-9.1.1.tgz", "integrity": "sha512-siLzop7i4xIvZIACE/PHTvRegA8QRCEt0TfmvJ/qCIFQJ4U+3NuYcF8tNDmDWxfIn+X1eNCyY2rauH4KRxge8w==", + "license": "MIT", "dependencies": { "lodash": "^4.17.21" }, @@ -14315,6 +15524,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "license": "MIT", "dependencies": { "lodash": "^4.0.1" } @@ -14553,6 +15763,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "license": "MIT", "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -14566,6 +15777,7 @@ "version": "4.40.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==", + "license": "MIT", "dependencies": { "@types/estree": "1.0.7" }, @@ -14756,8 +15968,7 @@ }, "node_modules/seedrandom": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", - "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -14864,13 +16075,13 @@ }, "node_modules/setimmediate": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "license": "MIT" }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -15007,8 +16218,7 @@ }, "node_modules/snappyjs": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.6.1.tgz", - "integrity": "sha512-YIK6I2lsH072UE0aOFxxY1dPDCS43I5ktqHpeAsuLNYWkE5pGxRGWfDM4/vSUfNzXjC1Ivzt3qx31PCLmc9yqg==" + "license": "MIT" }, "node_modules/source-map": { "version": "0.5.7", @@ -15047,12 +16257,12 @@ "node_modules/splaytree": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", - "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A==" + "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A==", + "license": "MIT" }, "node_modules/sprintf-js": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "license": "BSD-3-Clause" }, "node_modules/stable-hash": { "version": "0.0.4", @@ -15086,6 +16296,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/static-kdtree/-/static-kdtree-1.0.2.tgz", "integrity": "sha512-0EnWbb9V5TKZp+SvS59wzXGDFzA02YTpOfE73R1Exj2MK7fNyJELtAeSMW/p23nc2HFV1HqHxTS19aflt6f83g==", + "license": "MIT", "dependencies": { "bit-twiddle": "^1.0.0", "inorder-tree-layout": "^1.0.0", @@ -15108,6 +16319,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" @@ -15117,6 +16329,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -15130,6 +16343,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "license": "MIT", "dependencies": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.4", @@ -15141,6 +16355,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -15411,15 +16626,8 @@ } }, "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ] + "version": "1.0.5", + "license": "MIT" }, "node_modules/strongly-connected-components": { "version": "1.0.1", @@ -15739,8 +16947,7 @@ }, "node_modules/texture-compressor": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/texture-compressor/-/texture-compressor-1.0.2.tgz", - "integrity": "sha512-dStVgoaQ11mA5htJ+RzZ51ZxIZqNOgWKAIvtjLrW1AliQQLCmrDqNzQZ8Jh91YealQ95DXt4MEduLzJmbs6lig==", + "license": "MIT", "dependencies": { "argparse": "^1.0.10", "image-size": "^0.7.4" @@ -15751,8 +16958,7 @@ }, "node_modules/texture-compressor/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -15770,6 +16976,7 @@ "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "license": "MIT", "dependencies": { "setimmediate": "^1.0.4" }, @@ -15779,8 +16986,7 @@ }, "node_modules/tiny-emitter": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + "license": "MIT" }, "node_modules/tiny-invariant": { "version": "1.3.3", @@ -15810,6 +17016,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "license": "MIT", "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" @@ -15825,6 +17032,7 @@ "version": "6.4.4", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -15838,6 +17046,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -16000,12 +17209,14 @@ "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "license": "MIT" }, "node_modules/turf-jsts": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", - "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA==" + "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA==", + "license": "(EDL-1.0 OR EPL-1.0)" }, "node_modules/type": { "version": "2.7.3", @@ -16100,8 +17311,7 @@ }, "node_modules/typed-function": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz", - "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==", + "license": "MIT", "engines": { "node": ">= 18" } @@ -16206,7 +17416,8 @@ "node_modules/uniq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", + "license": "MIT" }, "node_modules/unquote": { "version": "1.1.1", @@ -16261,6 +17472,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" @@ -16277,6 +17489,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -16285,6 +17498,7 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -16309,6 +17523,7 @@ "resolved": "https://registry.npmjs.org/viewport-mercator-project/-/viewport-mercator-project-7.0.4.tgz", "integrity": "sha512-0jzpL6pIMocCKWg1C3mqi/N4UPgZC3FzwghEm1H+XsUo8hNZAyJc3QR7YqC816ibOR8aWT5pCsV+gCu8/BMJgg==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", "dependencies": { "@math.gl/web-mercator": "^3.5.5" } @@ -16317,6 +17532,7 @@ "version": "3.6.3", "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.6.3.tgz", "integrity": "sha512-UVrkSOs02YLehKaehrxhAejYMurehIHPfFQvPFZmdJHglHOU4V2cCUApTVEwOksvCp161ypEqVp+9H6mGhTTcw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.0", "gl-matrix": "^3.4.0" @@ -16326,6 +17542,7 @@ "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -16551,10 +17768,28 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/vite-plugin-glsl": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.3.1.tgz", + "integrity": "sha512-iClII8Idb9X0m6nS0YI2cWWXbBuT5EKKw5kXSAuRu4RJsNe4oypxKXE7jx0XMoyqij2s8WL0ZLfou801mpkREg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">= 16.15.1", + "npm": ">= 8.11.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, "node_modules/vite-plugin-node-polyfills": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.23.0.tgz", "integrity": "sha512-4n+Ys+2bKHQohPBKigFlndwWQ5fFKwaGY6muNDMTb0fSQLyBzS+jjUNRZG9sKF0S/Go4ApG6LFnUGopjkILg3w==", + "license": "MIT", "dependencies": { "@rollup/plugin-inject": "^5.0.5", "node-stdlib-browser": "^1.2.0" @@ -16570,6 +17805,7 @@ "version": "6.4.4", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -16583,6 +17819,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -16668,7 +17905,8 @@ "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.1.0", @@ -16832,7 +18070,8 @@ "node_modules/wgsl_reflect": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.2.0.tgz", - "integrity": "sha512-bDYcmWfbg4WsrBmPv6lsyjqXx02r8dkNAzR77OCNqIcR8snO4aNSBTjir9zqgR7rLnw6PaisiZxtCitSCIUlnQ==" + "integrity": "sha512-bDYcmWfbg4WsrBmPv6lsyjqXx02r8dkNAzR77OCNqIcR8snO4aNSBTjir9zqgR7rLnw6PaisiZxtCitSCIUlnQ==", + "license": "MIT" }, "node_modules/which": { "version": "2.0.2", @@ -16967,7 +18206,8 @@ "node_modules/workerpool": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.2.0.tgz", - "integrity": "sha512-PKZqBOCo6CYkVOwAxWxQaSF2Fvb5Iv2fCeTP7buyWI2GiynWr46NcXSgK/idoV6e60dgCBfgYc+Un3HMvmqP8w==" + "integrity": "sha512-PKZqBOCo6CYkVOwAxWxQaSF2Fvb5Iv2fCeTP7buyWI2GiynWr46NcXSgK/idoV6e60dgCBfgYc+Un3HMvmqP8w==", + "license": "Apache-2.0" }, "node_modules/world-calendars": { "version": "1.0.3", @@ -17079,7 +18319,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "2.7.0", @@ -17107,8 +18348,7 @@ }, "node_modules/zstd-codec": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/zstd-codec/-/zstd-codec-0.1.5.tgz", - "integrity": "sha512-v3fyjpK8S/dpY/X5WxqTK3IoCnp/ZOLxn144GZVlNUjtwAchzrVo03h+oMATFhCIiJ5KTr4V3vDQQYz4RU684g==", + "license": "MIT", "optional": true } } diff --git a/frontend/package.json b/frontend/package.json index 7d9252c9d..35e52552a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,14 @@ "vite-plugin-node-polyfills is only installed as a workaround for an import issue in the map module. See webviz-subsurface-components issue #2540 for details" ], "dependencies": { + "@deck.gl/aggregation-layers": "^9.0.33", + "@deck.gl/core": "^9.0.33", + "@deck.gl/extensions": "^9.0.33", + "@deck.gl/geo-layers": "^9.0.33", + "@deck.gl/json": "^9.0.33", + "@deck.gl/layers": "^9.0.33", + "@deck.gl/mesh-layers": "^9.0.33", + "@deck.gl/react": "^9.0.33", "@equinor/eds-core-react": "^0.45.1", "@equinor/esv-intersection": "^3.1.2", "@headlessui/react": "^1.7.8", @@ -52,7 +60,8 @@ "simplify-js": "^1.2.4", "uuid": "^9.0.0", "vite-plugin-node-polyfills": "^0.23.0", - "wonka": "^6.3.4" + "wonka": "^6.3.4", + "workerpool": "^9.2.0" }, "devDependencies": { "@hey-api/openapi-ts": "^0.61.1", @@ -67,6 +76,7 @@ "@types/react-dom": "^18.2.7", "@types/react-plotly.js": "^2.6.0", "@types/uuid": "^9.0.0", + "@types/workerpool": "^6.4.7", "@vitejs/plugin-react": "^4.4.1", "@vitest/coverage-istanbul": "^3.0.7", "dependency-cruiser": "^14.0.0", @@ -85,6 +95,7 @@ "typescript-eslint": "^8.25.0", "vite": "^6.3.5", "vite-plugin-checker": "^0.9.0", + "vite-plugin-glsl": "^1.3.1", "vitest": "^3.0.7" } } diff --git a/frontend/src/assets/add_path.svg b/frontend/src/assets/add_path.svg new file mode 100644 index 000000000..eaf50c56a --- /dev/null +++ b/frontend/src/assets/add_path.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/add_path_point.svg b/frontend/src/assets/add_path_point.svg new file mode 100644 index 000000000..d7b020df4 --- /dev/null +++ b/frontend/src/assets/add_path_point.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/continue_path.svg b/frontend/src/assets/continue_path.svg new file mode 100644 index 000000000..dc7fe1d08 --- /dev/null +++ b/frontend/src/assets/continue_path.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/path.svg b/frontend/src/assets/path.svg new file mode 100644 index 000000000..2e17d9e11 --- /dev/null +++ b/frontend/src/assets/path.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/remove_path.svg b/frontend/src/assets/remove_path.svg new file mode 100644 index 000000000..0e002bb8a --- /dev/null +++ b/frontend/src/assets/remove_path.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/remove_path_point.svg b/frontend/src/assets/remove_path_point.svg new file mode 100644 index 000000000..0ec59f316 --- /dev/null +++ b/frontend/src/assets/remove_path_point.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/set_path_point.svg b/frontend/src/assets/set_path_point.svg new file mode 100644 index 000000000..c042b4664 --- /dev/null +++ b/frontend/src/assets/set_path_point.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/textures/checkerboard.png b/frontend/src/assets/textures/checkerboard.png new file mode 100644 index 000000000..ed5994dbd Binary files /dev/null and b/frontend/src/assets/textures/checkerboard.png differ diff --git a/frontend/src/framework/components/ColorScaleSelector/colorScaleSelector.tsx b/frontend/src/framework/components/ColorScaleSelector/colorScaleSelector.tsx index 7e02949c2..4b22754b4 100644 --- a/frontend/src/framework/components/ColorScaleSelector/colorScaleSelector.tsx +++ b/frontend/src/framework/components/ColorScaleSelector/colorScaleSelector.tsx @@ -22,7 +22,6 @@ import { convertRemToPixels } from "@lib/utils/screenUnitConversions"; import type { Vec2 } from "@lib/utils/vec2"; import { point2Distance } from "@lib/utils/vec2"; - export type ColorScaleSpecification = { colorScale: ColorScale; areBoundariesUserDefined: boolean; @@ -54,6 +53,7 @@ export function ColorScaleSelector(props: ColorScaleSelectorProps): React.ReactN setPrevColorScaleSpecification(props.colorScaleSpecification); if (props.colorScaleSpecification) { setColorScaleSpecification(props.colorScaleSpecification); + setTempColorScaleSpecification(props.colorScaleSpecification); } } @@ -138,12 +138,12 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo const [lastSelectedSequentialColorPalette, setLastSelectedSequentialColorPalette] = React.useState( props.colorScaleSpecification?.colorScale.getGradientType() === ColorScaleGradientType.Sequential ? props.colorScaleSpecification?.colorScale.getColorPalette() - : props.workbenchSettings.getColorPalettes()[ColorPaletteType.ContinuousSequential][0] ?? "" + : (props.workbenchSettings.getColorPalettes()[ColorPaletteType.ContinuousSequential][0] ?? ""), ); const [lastSelectedDivergingColorPalette, setLastSelectedDivergingColorPalette] = React.useState( props.colorScaleSpecification?.colorScale.getGradientType() === ColorScaleGradientType.Diverging ? props.colorScaleSpecification?.colorScale.getColorPalette() - : props.workbenchSettings.getColorPalettes()[ColorPaletteType.ContinuousDiverging][0] ?? "" + : (props.workbenchSettings.getColorPalettes()[ColorPaletteType.ContinuousDiverging][0] ?? ""), ); if (!isEqual(props.colorScaleSpecification, prevColorScaleSpecification)) { @@ -163,7 +163,7 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo colorScaleSpecification.colorScale.getMax(), colorScaleSpecification.colorScale.getNumSteps(), colorScaleSpecification.colorScale.getDivMidPoint(), - colorScaleSpecification.areBoundariesUserDefined + colorScaleSpecification.areBoundariesUserDefined, ); } @@ -181,7 +181,7 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo colorScaleSpecification.colorScale.getMax(), colorScaleSpecification.colorScale.getNumSteps(), colorScaleSpecification.colorScale.getDivMidPoint(), - colorScaleSpecification.areBoundariesUserDefined + colorScaleSpecification.areBoundariesUserDefined, ); } @@ -199,7 +199,7 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo colorScaleSpecification.colorScale.getMax(), colorScaleSpecification.colorScale.getNumSteps(), colorScaleSpecification.colorScale.getDivMidPoint(), - colorScaleSpecification.areBoundariesUserDefined + colorScaleSpecification.areBoundariesUserDefined, ); } @@ -212,7 +212,7 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo colorScaleSpecification.colorScale.getMax(), numSteps, colorScaleSpecification.colorScale.getDivMidPoint(), - colorScaleSpecification.areBoundariesUserDefined + colorScaleSpecification.areBoundariesUserDefined, ); } @@ -225,7 +225,7 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo max: number, numSteps: number, divMid: number, - areBoundariesUserDefined: boolean + areBoundariesUserDefined: boolean, ) { const colorScale = new ColorScale({ colorPalette, @@ -244,7 +244,7 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo onChange(colorScaleSpecification); } }, - [onChange] + [onChange], ); const handleMinMaxDivMidPointChange = React.useCallback( @@ -257,10 +257,10 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo max, colorScaleSpecification.colorScale.getNumSteps(), divMidPoint ?? colorScaleSpecification.colorScale.getDivMidPoint(), - colorScaleSpecification.areBoundariesUserDefined + colorScaleSpecification.areBoundariesUserDefined, ); }, - [colorScaleSpecification, makeAndPropagateColorScale] + [colorScaleSpecification, makeAndPropagateColorScale], ); const handleAreBoundariesUserDefinedChange = React.useCallback( @@ -273,10 +273,10 @@ function ColorScaleSelectorDialog(props: ColorScaleSelectorProps): React.ReactNo colorScaleSpecification.colorScale.getMax(), colorScaleSpecification.colorScale.getNumSteps(), colorScaleSpecification.colorScale.getDivMidPoint(), - areBoundariesUserDefined + areBoundariesUserDefined, ); }, - [colorScaleSpecification, makeAndPropagateColorScale] + [colorScaleSpecification, makeAndPropagateColorScale], ); return ( @@ -387,15 +387,14 @@ function ColorScaleSetter(props: ColorScaleSetterProps): React.ReactNode { const handleMinMaxDivMidPointChange = React.useCallback(function handleMinMaxDivMidPointChange( min: number, max: number, - divMidPoint?: number + divMidPoint?: number, ) { setMin(min); setMax(max); if (divMidPoint !== undefined) { setDivMidPoint(divMidPoint); } - }, - []); + }, []); return (
@@ -439,10 +438,10 @@ function MinMaxDivMidPointSetter(props: MinMaxDivMidPointSetterProps): React.Rea const [divMidPoint, setDivMidPoint] = React.useState(props.divMidPoint); const [prevDivMidPoint, setPrevDivMidPoint] = React.useState(props.divMidPoint); const [areBoundariesUserDefined, setAreBoundariesUserDefined] = React.useState( - props.areBoundariesUserDefined + props.areBoundariesUserDefined, ); const [prevAreBoundariesUserDefined, setPrevAreBoundariesUserDefined] = React.useState( - props.areBoundariesUserDefined + props.areBoundariesUserDefined, ); const [isDragging, setIsDragging] = React.useState(false); @@ -520,7 +519,7 @@ function MinMaxDivMidPointSetter(props: MinMaxDivMidPointSetterProps): React.Rea const newRelativeDivMidPoint = Math.min( Math.max((dx + convertRemToPixels(0.75) - containerRect.left) / containerRect.width, 0), - 1 + 1, ); newDivMidPoint = min + newRelativeDivMidPoint * (max - min); @@ -550,7 +549,7 @@ function MinMaxDivMidPointSetter(props: MinMaxDivMidPointSetterProps): React.Rea document.removeEventListener("pointerup", handlePointerUp); }; }, - [onChange, onChangePreview, min, max] + [onChange, onChangePreview, min, max], ); function handleMinChange(value: string) { @@ -609,7 +608,7 @@ function MinMaxDivMidPointSetter(props: MinMaxDivMidPointSetterProps): React.Rea "z-50": isDragging, hidden: props.gradientType === ColorScaleGradientType.Sequential || !areBoundariesUserDefined, - } + }, )} style={{ left: `${(Math.abs(divMidPoint - min) / Math.abs(max - min)) * 100}%` }} ref={divMidPointRef} @@ -677,7 +676,7 @@ const ColorScalePaletteSelector: React.FC = (pro const [open, setOpen] = React.useState(false); const [selectedColorPalette, setSelectedColorPalette] = React.useState(props.selectedColorPalette); const [prevSelectedColorPalette, setPrevSelectedColorPalette] = React.useState( - props.selectedColorPalette + props.selectedColorPalette, ); if (prevSelectedColorPalette.getId() !== props.selectedColorPalette.getId()) { @@ -769,7 +768,7 @@ const ColorScalePaletteSelector: React.FC = (pro > {renderColorPalettes()}
- + , )} ); diff --git a/frontend/src/framework/internal/components/Content/private-components/layout.tsx b/frontend/src/framework/internal/components/Content/private-components/layout.tsx index 4b8748488..a2f059e3c 100644 --- a/frontend/src/framework/internal/components/Content/private-components/layout.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/layout.tsx @@ -14,7 +14,7 @@ import type { Rect2D, Size2D } from "@lib/utils/geometry"; import { MANHATTAN_LENGTH, addMarginToRect, pointRelativeToDomRect, rectContainsPoint } from "@lib/utils/geometry"; import { convertRemToPixels } from "@lib/utils/screenUnitConversions"; import type { Vec2 } from "@lib/utils/vec2"; -import { multiplyVec2, point2Distance, scaleVec2NonUniform, subtractVec2, vec2FromPointerEvent } from "@lib/utils/vec2"; +import { multiplyElementWiseVec2, point2Distance, scaleVec2NonUniform, subtractVec2, vec2FromPointerEvent } from "@lib/utils/vec2"; import { ViewWrapper } from "./ViewWrapper"; import { ViewWrapperPlaceholder } from "./viewWrapperPlaceholder"; @@ -91,7 +91,7 @@ export const Layout: React.FC = (props) => { setPosition( subtractVec2( relativePointerPosition, - multiplyVec2(relativePointerToElementDiff, { + multiplyElementWiseVec2(relativePointerToElementDiff, { x: draggedElementSize.width, y: 1, }), @@ -193,7 +193,7 @@ export const Layout: React.FC = (props) => { setPosition( subtractVec2( relativePointerPosition, - multiplyVec2(relativePointerToElementDiff, { + multiplyElementWiseVec2(relativePointerToElementDiff, { x: draggedElementSize.width, y: 1, }), diff --git a/frontend/src/framework/userCreatedItems/IntersectionPolylines.ts b/frontend/src/framework/userCreatedItems/IntersectionPolylines.ts index 7c02070ca..721add8dd 100644 --- a/frontend/src/framework/userCreatedItems/IntersectionPolylines.ts +++ b/frontend/src/framework/userCreatedItems/IntersectionPolylines.ts @@ -1,5 +1,5 @@ import { atom } from "jotai"; -import { cloneDeep } from "lodash"; +import { cloneDeep, isEqual } from "lodash"; import { v4 } from "uuid"; import type { AtomStoreMaster } from "@framework/AtomStoreMaster"; @@ -8,6 +8,7 @@ import type { UserCreatedItemSet } from "@framework/UserCreatedItems"; export type IntersectionPolyline = { id: string; name: string; + color: [number, number, number]; path: number[][]; fieldId: string; }; @@ -55,7 +56,15 @@ export class IntersectionPolylines implements UserCreatedItemSet { this.notifySubscribers(IntersectionPolylinesEvent.CHANGE); } - getPolylines(): IntersectionPolyline[] { + setPolylines(polylines: IntersectionPolyline[]): void { + if (isEqual(this._polylines, polylines)) { + return; + } + this._polylines = polylines; + this.notifySubscribers(IntersectionPolylinesEvent.CHANGE); + } + + getPolylines(): readonly IntersectionPolyline[] { return this._polylines; } diff --git a/frontend/src/lib/components/ToggleButton/toggleButton.tsx b/frontend/src/lib/components/ToggleButton/toggleButton.tsx index 4215f501b..f7381b134 100644 --- a/frontend/src/lib/components/ToggleButton/toggleButton.tsx +++ b/frontend/src/lib/components/ToggleButton/toggleButton.tsx @@ -14,6 +14,12 @@ export type ToggleButtonProps = ButtonUnstyledProps & { function ToggleButtonComponent(props: ToggleButtonProps, ref: React.ForwardedRef) { const { active, onToggle, ...other } = props; const [isActive, setIsActive] = React.useState(active); + const [prevActive, setPrevActive] = React.useState(active); + + if (active !== prevActive) { + setIsActive(active); + setPrevActive(active); + } const buttonRef = React.useRef(null); React.useImperativeHandle( diff --git a/frontend/src/lib/icons/icons/addPathPointIcon.tsx b/frontend/src/lib/icons/icons/addPathPointIcon.tsx new file mode 100644 index 000000000..77d5da3be --- /dev/null +++ b/frontend/src/lib/icons/icons/addPathPointIcon.tsx @@ -0,0 +1,11 @@ +import { createSvgIcon } from "@mui/material"; + +export const AddPathPointIcon = createSvgIcon( + + + + + + , + "AddPathPoint" +); diff --git a/frontend/src/lib/icons/icons/pathIcon.tsx b/frontend/src/lib/icons/icons/pathIcon.tsx new file mode 100644 index 000000000..39b6d38b0 --- /dev/null +++ b/frontend/src/lib/icons/icons/pathIcon.tsx @@ -0,0 +1,10 @@ +import { createSvgIcon } from "@mui/material"; + +export const DrawPathIcon = createSvgIcon( + + + + + , + "DrawPath" +); diff --git a/frontend/src/lib/icons/icons/removePathPointIcon.tsx b/frontend/src/lib/icons/icons/removePathPointIcon.tsx new file mode 100644 index 000000000..c643ac9e6 --- /dev/null +++ b/frontend/src/lib/icons/icons/removePathPointIcon.tsx @@ -0,0 +1,11 @@ +import { createSvgIcon } from "@mui/material"; + +export const RemovePathPointIcon = createSvgIcon( + + + + + + , + "RemovePathPoint" +); diff --git a/frontend/src/lib/icons/index.ts b/frontend/src/lib/icons/index.ts new file mode 100644 index 000000000..efa0a00f2 --- /dev/null +++ b/frontend/src/lib/icons/index.ts @@ -0,0 +1,3 @@ +export { DrawPathIcon } from "./icons/pathIcon"; +export { AddPathPointIcon } from "./icons/addPathPointIcon"; +export { RemovePathPointIcon } from "./icons/removePathPointIcon"; diff --git a/frontend/src/lib/utils/boundingBox.ts b/frontend/src/lib/utils/boundingBox.ts new file mode 100644 index 000000000..b71dddf23 --- /dev/null +++ b/frontend/src/lib/utils/boundingBox.ts @@ -0,0 +1,117 @@ +import { BoundingBox3D_api } from "@api"; + +import * as vec3 from "./vec3"; + +export type BBox = { + min: vec3.Vec3; + max: vec3.Vec3; +}; + +/** + * Creates a new bounding box. + * @param min The minimum point of the bounding box. + * @param max The maximum point of the bounding box. + * @returns A new bounding box. + */ +export function create(min: vec3.Vec3, max: vec3.Vec3): BBox { + return { min, max }; +} + +export function fromBoundingBox3DApi(boundingBox: BoundingBox3D_api): BBox { + return create( + vec3.create(boundingBox.xmin, boundingBox.ymin, boundingBox.zmin), + vec3.create(boundingBox.xmax, boundingBox.ymax, boundingBox.zmax) + ); +} + +export function fromBoundingBox2DApi(boundingBox: BoundingBox3D_api): BBox { + return create( + vec3.create(boundingBox.xmin, boundingBox.ymin, 0), + vec3.create(boundingBox.xmax, boundingBox.ymax, 0) + ); +} + +/** + Returns true if the bounding box contains the given point. + */ +export function containsPoint(box: BBox, point: vec3.Vec3): boolean { + return ( + point.x >= box.min.x && + point.x <= box.max.x && + point.y >= box.min.y && + point.y <= box.max.y && + point.z >= box.min.z && + point.z <= box.max.z + ); +} + +/** + Returns true if the two bounding boxes intersect. + */ +export function intersects(box1: BBox, box2: BBox): boolean { + return ( + box1.min.x <= box2.max.x && + box1.max.x >= box2.min.x && + box1.min.y <= box2.max.y && + box1.max.y >= box2.min.y && + box1.min.z <= box2.max.z && + box1.max.z >= box2.min.z + ); +} + +/** + * Returns true if outerBox contains innerBox. + */ +export function outerBoxcontainsInnerBox(outerBox: BBox, innerBox: BBox): boolean { + return ( + outerBox.min.x <= innerBox.min.x && + outerBox.min.y <= innerBox.min.y && + outerBox.min.z <= innerBox.min.z && + outerBox.max.x >= innerBox.max.x && + outerBox.max.y >= innerBox.max.y && + outerBox.max.z >= innerBox.max.z + ); +} + +/** + Converts a bounding box to an array of numbers. + The array contains the following numbers in the following order: + [min.x, min.y, min.z, max.x, max.y, max.z] + */ +export function toNumArray(box: BBox): [number, number, number, number, number, number] { + return [box.min.x, box.min.y, box.min.z, box.max.x, box.max.y, box.max.z]; +} + +/** + Converts an array of numbers to a bounding box. + The array should contain the following numbers in the following order: + [min.x, min.y, min.z, max.x, max.y, max.z] + */ +export function fromNumArray(array: [number, number, number, number, number, number]): BBox { + return create(vec3.fromArray(array.slice(0, 3)), vec3.fromArray(array.slice(3, 6))); +} + +/** + * Clones the given bounding box. + */ +export function clone(box: BBox): BBox { + return create(vec3.clone(box.min), vec3.clone(box.max)); +} + +/** + * Combines the two bounding boxes into a new bounding box that contains both. + */ +export function combine(box1: BBox, box2: BBox): BBox { + return create( + vec3.create( + Math.min(box1.min.x, box2.min.x), + Math.min(box1.min.y, box2.min.y), + Math.min(box1.min.z, box2.min.z) + ), + vec3.create( + Math.max(box1.max.x, box2.max.x), + Math.max(box1.max.y, box2.max.y), + Math.max(box1.max.z, box2.max.z) + ) + ); +} diff --git a/frontend/src/lib/utils/colorConstants.ts b/frontend/src/lib/utils/colorConstants.ts new file mode 100644 index 000000000..98f7748b9 --- /dev/null +++ b/frontend/src/lib/utils/colorConstants.ts @@ -0,0 +1,11 @@ +// This is used to set colors for different states throughout the application where CSS properties are not accessible (e.g. WebGL). +// However, it should be in sync with what is set in Tailwind CSS. +// The location of this file can still be decided upon. +export type Colors = { + hover: [number, number, number]; + selected: [number, number, number]; +}; +export const COLORS: Colors = { + hover: [191, 219, 254], + selected: [37, 99, 235], +}; diff --git a/frontend/src/lib/utils/geometry.ts b/frontend/src/lib/utils/geometry.ts index 7b1bd5613..97b981b50 100644 --- a/frontend/src/lib/utils/geometry.ts +++ b/frontend/src/lib/utils/geometry.ts @@ -1,10 +1,18 @@ +import { BBox } from "./boundingBox"; import type { Vec2 } from "./vec2"; +import { Vec3 } from "./vec3"; export type Size2D = { width: number; height: number; }; +export type Size3D = { + width: number; + height: number; + depth: number; +}; + export type Rect2D = { x: number; y: number; @@ -12,6 +20,36 @@ export type Rect2D = { height: number; }; +export type Rect3D = { + x: number; + y: number; + z: number; + width: number; + height: number; + depth: number; +}; + +export enum ShapeType { + BOX = "box", +} + +export type Shape = { + type: ShapeType.BOX; + centerPoint: Vec3; + dimensions: Size3D; + normalizedEdgeVectors: { + // along width + u: Vec3; + // along height + v: Vec3; + }; +}; + +export type Geometry = { + shapes: Shape[]; + boundingBox: BBox; +}; + export const ORIGIN = Object.freeze({ x: 0, y: 0 }); export const MANHATTAN_LENGTH = 13.11; @@ -78,7 +116,20 @@ export function addMarginToRect(rect: Rect2D, margin: number): Rect2D { }; } -export function outerRectContainsInnerRect(outerRect: Rect2D, innerRect: Rect2D): boolean { +export function outerRectContainsInnerRect(outerRect: Rect3D, innerRect: Rect3D): boolean; +export function outerRectContainsInnerRect(outerRect: Rect2D, innerRect: Rect2D): boolean; +export function outerRectContainsInnerRect(outerRect: Rect2D | Rect3D, innerRect: Rect2D | Rect3D): boolean { + if ("depth" in outerRect && "depth" in innerRect) { + return ( + outerRect.x <= innerRect.x && + outerRect.y <= innerRect.y && + outerRect.z <= innerRect.z && + outerRect.x + outerRect.width >= innerRect.x + innerRect.width && + outerRect.y + outerRect.height >= innerRect.y + innerRect.height && + outerRect.z + outerRect.depth >= innerRect.z + innerRect.depth + ); + } + return ( outerRect.x <= innerRect.x && outerRect.y <= innerRect.y && diff --git a/frontend/src/lib/utils/index.ts b/frontend/src/lib/utils/index.ts new file mode 100644 index 000000000..a8c6b55be --- /dev/null +++ b/frontend/src/lib/utils/index.ts @@ -0,0 +1 @@ +export * as bbox from "./boundingBox"; diff --git a/frontend/src/lib/utils/mat3.ts b/frontend/src/lib/utils/mat3.ts new file mode 100644 index 000000000..6864161b7 --- /dev/null +++ b/frontend/src/lib/utils/mat3.ts @@ -0,0 +1,83 @@ +/** + * A 3x3 matrix. + */ +export type Mat3 = { + m00: number; + m01: number; + m02: number; + m10: number; + m11: number; + m12: number; + m20: number; + m21: number; + m22: number; +}; + +/** + * Creates a new 3x3 matrix with all elements set to 0. + * + * @returns A new 3x3 matrix. + */ +export function createEmpty(): Mat3 { + return { + m00: 0, + m01: 0, + m02: 0, + m10: 0, + m11: 0, + m12: 0, + m20: 0, + m21: 0, + m22: 0, + }; +} + +/** + * Creates a new 3x3 matrix with the given elements. + * + * @param m00 The element at row 0, column 0. + * @param m01 The element at row 0, column 1. + * @param m02 The element at row 0, column 2. + * @param m10 The element at row 1, column 0. + * @param m11 The element at row 1, column 1. + * @param m12 The element at row 1, column 2. + * @param m20 The element at row 2, column 0. + * @param m21 The element at row 2, column 1. + * @param m22 The element at row 2, column 2. + * @returns A new 3x3 matrix. + */ +export function create( + m00: number, + m01: number, + m02: number, + m10: number, + m11: number, + m12: number, + m20: number, + m21: number, + m22: number +): Mat3 { + return { m00, m01, m02, m10, m11, m12, m20, m21, m22 }; +} + +/** + * Creates a new 3x3 matrix from the given array of numbers. + * + * @param array An array of numbers in the following order: [m00, m01, m02, m10, m11, m12, m20, m21, m22]. + * @returns A new 3x3 matrix. + */ +export function fromArray( + array: ArrayLike | [number, number, number, number, number, number, number, number, number] +): Mat3 { + return { + m00: array[0], + m01: array[1], + m02: array[2], + m10: array[3], + m11: array[4], + m12: array[5], + m20: array[6], + m21: array[7], + m22: array[8], + }; +} \ No newline at end of file diff --git a/frontend/src/lib/utils/orientedBoundingBox.ts b/frontend/src/lib/utils/orientedBoundingBox.ts new file mode 100644 index 000000000..989d15608 --- /dev/null +++ b/frontend/src/lib/utils/orientedBoundingBox.ts @@ -0,0 +1,164 @@ +import * as bbox from "./boundingBox"; +import * as vec3 from "./vec3"; + +export type OBBox = { + centerPoint: vec3.Vec3; + principalAxes: vec3.Vec3[]; + halfExtents: number[]; +}; + +/** + * Creates a new oriented bounding box. + */ +export function create(center: vec3.Vec3, principalAxes: vec3.Vec3[], halfExtents: number[]): OBBox { + return { centerPoint: center, principalAxes, halfExtents }; +} + +/* + * Returns true if the oriented bounding box contains the given point. + */ +export function containsPoint(box: OBBox, point: vec3.Vec3): boolean { + const diff = vec3.subtract(point, box.centerPoint); + return ( + Math.abs(vec3.dot(diff, box.principalAxes[0])) <= box.halfExtents[0] && + Math.abs(vec3.dot(diff, box.principalAxes[1])) <= box.halfExtents[1] && + Math.abs(vec3.dot(diff, box.principalAxes[2])) <= box.halfExtents[2] + ); +} + +/** + * Converts an oriented bounding box to an axis-aligned bounding box. + */ +export function toAxisAlignedBoundingBox(box: OBBox): bbox.BBox { + const absAxisX = vec3.abs(box.principalAxes[0]); + const absAxisY = vec3.abs(box.principalAxes[1]); + const absAxisZ = vec3.abs(box.principalAxes[2]); + + const halfSize: vec3.Vec3 = { + x: box.halfExtents[0] * absAxisX.x + box.halfExtents[1] * absAxisY.x + box.halfExtents[2] * absAxisZ.x, + y: box.halfExtents[0] * absAxisX.y + box.halfExtents[1] * absAxisY.y + box.halfExtents[2] * absAxisZ.y, + z: box.halfExtents[0] * absAxisX.z + box.halfExtents[1] * absAxisY.z + box.halfExtents[2] * absAxisZ.z, + }; + return bbox.create(vec3.subtract(box.centerPoint, halfSize), vec3.add(box.centerPoint, halfSize)); +} + +export function fromAxisAlignedBoundingBox(box: bbox.BBox): OBBox { + const centerPoint = vec3.scale(vec3.add(box.min, box.max), 0.5); + const principalAxes = [vec3.create(1, 0, 0), vec3.create(0, 1, 0), vec3.create(0, 0, 1)]; + const halfExtents = vec3.scale(vec3.subtract(box.max, box.min), 0.5); + return create(centerPoint, principalAxes, [halfExtents.x, halfExtents.y, halfExtents.z]); +} + +/** + * Returns true if outerBox contains innerBox. + */ +export function containsBox(outerBox: OBBox, innerBox: OBBox): boolean { + const points = calcCornerPoints(innerBox); + for (const point of points) { + if (!containsPoint(outerBox, point)) { + return false; + } + } + return true; +} + +/** + * Returns the corner points of the oriented bounding box. + * + * The points are returned in the following order for z-axis up coordinate system: + * 0: bottom front left + * 1: bottom front right + * 2: bottom back left + * 3: bottom back right + * 4: top front left + * 5: top front right + * 6: top back left + * 7: top back right + */ + +export function calcCornerPoints(box: OBBox): vec3.Vec3[] { + const halfExtents = box.halfExtents; + const principalAxes = box.principalAxes; + const centerPoint = box.centerPoint; + + const points: vec3.Vec3[] = []; + for (let i = 0; i < 8; i++) { + const x = (i & 1) === 0 ? -1 : 1; + const y = (i & 2) === 0 ? -1 : 1; + const z = (i & 4) === 0 ? -1 : 1; + const point = vec3.add( + centerPoint, + vec3.add( + vec3.scale(principalAxes[0], x * halfExtents[0]), + vec3.add( + vec3.scale(principalAxes[1], y * halfExtents[1]), + vec3.scale(principalAxes[2], z * halfExtents[2]) + ) + ) + ); + points.push(point); + } + return points; +} + +/** + * Creates an oriented bounding box from the given corner points. + */ +export function fromCornerPoints(points: vec3.Vec3[]): OBBox { + const min = vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); + const max = vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); + for (const point of points) { + min.x = Math.min(min.x, point.x); + min.y = Math.min(min.y, point.y); + min.z = Math.min(min.z, point.z); + max.x = Math.max(max.x, point.x); + max.y = Math.max(max.y, point.y); + max.z = Math.max(max.z, point.z); + } + const center = vec3.scale(vec3.add(min, max), 0.5); + const halfExtents = vec3.scale(vec3.subtract(max, min), 0.5); + const principalAxes = [ + vec3.normalize(vec3.subtract(points[0], points[1])), + vec3.normalize(vec3.subtract(points[0], points[2])), + vec3.normalize(vec3.subtract(points[0], points[4])), + ]; + return create(center, principalAxes, [halfExtents.x, halfExtents.y, halfExtents.z]); +} + +/** + * Combines two oriented bounding boxes into a new oriented bounding box that contains both input boxes. + */ +export function combine(box1: OBBox, box2: OBBox): OBBox { + const points1 = calcCornerPoints(box1); + const points2 = calcCornerPoints(box2); + const allPoints = points1.concat(points2); + const min = vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); + const max = vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); + for (const point of allPoints) { + min.x = Math.min(min.x, point.x); + min.y = Math.min(min.y, point.y); + min.z = Math.min(min.z, point.z); + max.x = Math.max(max.x, point.x); + max.y = Math.max(max.y, point.y); + max.z = Math.max(max.z, point.z); + } + const center = vec3.scale(vec3.add(min, max), 0.5); + const halfExtents = vec3.scale(vec3.subtract(max, min), 0.5); + const principalAxes = [ + vec3.normalize(vec3.subtract(points1[0], points1[1])), + vec3.normalize(vec3.subtract(points1[0], points1[2])), + vec3.normalize(vec3.subtract(points1[0], points1[4])), + ]; + return create(center, principalAxes, [halfExtents.x, halfExtents.y, halfExtents.z]); +} + +/** + * Clones the given oriented bounding box. + */ +export function clone(box: OBBox): OBBox { + return create( + vec3.clone(box.centerPoint), + [vec3.clone(box.principalAxes[0]), vec3.clone(box.principalAxes[1]), vec3.clone(box.principalAxes[2])], + [...box.halfExtents] + ); +} diff --git a/frontend/src/lib/utils/vec2.ts b/frontend/src/lib/utils/vec2.ts index d07b0a03a..9d10e6db1 100644 --- a/frontend/src/lib/utils/vec2.ts +++ b/frontend/src/lib/utils/vec2.ts @@ -40,7 +40,7 @@ export function scaleVec2NonUniform(vector: Vec2, scalarX: number, scalarY: numb return { x: vector.x * scalarX, y: vector.y * scalarY }; } -export function multiplyVec2(vecA: Vec2, vecB: Vec2): Vec2 { +export function multiplyElementWiseVec2(vecA: Vec2, vecB: Vec2): Vec2 { return { x: vecA.x * vecB.x, y: vecA.y * vecB.y }; } diff --git a/frontend/src/lib/utils/vec3.ts b/frontend/src/lib/utils/vec3.ts index 7105315cd..41cbd8e37 100644 --- a/frontend/src/lib/utils/vec3.ts +++ b/frontend/src/lib/utils/vec3.ts @@ -1,3 +1,5 @@ +import { Mat3 } from "./mat3"; + /** * A 3D vector. */ @@ -51,3 +53,195 @@ export function toArray(vector: Vec3): [number, number, number] { export function clone(vector: Vec3): Vec3 { return { x: vector.x, y: vector.y, z: vector.z }; } + +/** + * Calculates the length of the given vector. + * + * @param vector The vector. + * @returns The length of the vector. + */ +export function length(vector: Vec3): number { + return Math.sqrt(vector.x ** 2 + vector.y ** 2 + vector.z ** 2); +} + +/** + * Calculates the squared length of the given vector. + * + * @param vector The vector. + * @returns The squared length of the vector. + */ +export function squaredLength(vector: Vec3): number { + return vector.x ** 2 + vector.y ** 2 + vector.z ** 2; +} + +/** + * Calculates the distance between two points. + * + * @param point1 The first point. + * @param point2 The second point. + * @returns The distance between the two points. + */ +export function distance(point1: Vec3, point2: Vec3): number { + return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2 + (point1.z - point2.z) ** 2); +} + +/** + * Calculates the squared distance between two points. + * + * @param point1 The first point. + * @param point2 The second point. + * @returns The squared distance between the two points. + */ +export function squaredDistance(point1: Vec3, point2: Vec3): number { + return (point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2 + (point1.z - point2.z) ** 2; +} + +/** + * Subtracts the subtrahend from the minuend. + * + * @param minuend The minuend. + * @param subtrahend The subtrahend. + * @returns A new vector that is the result of the subtraction. + */ +export function subtract(minuend: Vec3, subtrahend: Vec3): Vec3 { + return { x: minuend.x - subtrahend.x, y: minuend.y - subtrahend.y, z: minuend.z - subtrahend.z }; +} + +/** + * Adds two vectors. + * + * @param vector1 The first vector. + * @param vector2 The second vector. + * @returns A new vector that is the result of the addition. + */ +export function add(vector1: Vec3, vector2: Vec3): Vec3 { + return { x: vector1.x + vector2.x, y: vector1.y + vector2.y, z: vector1.z + vector2.z }; +} + +/** + * Concatenates multiple vectors. + * + * @param vectors The vectors to concatenate. + * @returns A new vector that is the result of the concatenation. + */ +export function concat(...vectors: Vec3[]): Vec3 { + return vectors.reduce((acc, vector) => add(acc, vector), create(0, 0, 0)); +} + +/** + * Normalizes the given vector. + * + * @param vector The vector. + * @returns A new vector that is the normalized version of the given vector. + */ +export function normalize(vector: Vec3): Vec3 { + const len = length(vector); + return { x: vector.x / len, y: vector.y / len, z: vector.z / len }; +} + +/** + * Negates the given vector. + * + * @param vector The vector. + * @returns A new vector that is the negated version of the given vector. + */ +export function negate(vector: Vec3): Vec3 { + return { x: -vector.x, y: -vector.y, z: -vector.z }; +} + +/** + * Returns the absolute values of the components of the given vector. + * + * @param vector The vector. + * @returns A new vector that is the absolute version of the given vector. + */ +export function abs(vector: Vec3): Vec3 { + return { x: Math.abs(vector.x), y: Math.abs(vector.y), z: Math.abs(vector.z) }; +} + +/** + * Scales the given vector by the given scalar. + * + * @param vector The vector. + * @param scalar The scalar. + * @returns A new vector that is the result of the scaling. + */ +export function scale(vector: Vec3, scalar: number): Vec3 { + return { x: vector.x * scalar, y: vector.y * scalar, z: vector.z * scalar }; +} + +/** + * Scales the given vector components by the respective given scalar. + * + * @param vector The vector. + * @param scalarX The scalar for the x component. + * @param scalarY The scalar for the y component. + * @param scalarZ The scalar for the z component. + * @returns A new vector that is the result of the scaling. + */ +export function scaleNonUniform(vector: Vec3, scalarX: number, scalarY: number, scalarZ: number): Vec3 { + return { x: vector.x * scalarX, y: vector.y * scalarY, z: vector.z * scalarZ }; +} + +/** + * Multiplies two vectors element-wise. + * + * @param vecA The first vector. + * @param vecB The second vector. + * @returns A new vector that is the result of the element-wise multiplication. + */ +export function multiplyElementWise(vecA: Vec3, vecB: Vec3): Vec3 { + return { x: vecA.x * vecB.x, y: vecA.y * vecB.y, z: vecA.z * vecB.z }; +} + +/** + * Returns true if the two vectors are equal. + * + * @param vector1 The first vector. + * @param vector2 The second vector. + * @returns True if the two vectors are equal. + */ +export function equal(vector1: Vec3, vector2: Vec3): boolean { + return vector1.x === vector2.x && vector1.y === vector2.y && vector1.z === vector2.z; +} + +/** + * Calculates the dot product of two vectors. + * + * @param vector1 The first vector. + * @param vector2 The second vector. + * @returns The dot product of the two vectors. + */ +export function dot(vector1: Vec3, vector2: Vec3): number { + return vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z; +} + +/** + * Calculates the cross product of two vectors. + * + * @param vector1 The first vector. + * @param vector2 The second vector. + * @returns A new vector that is the cross product of the two vectors. + */ +export function cross(vector1: Vec3, vector2: Vec3): Vec3 { + return { + x: vector1.y * vector2.z - vector1.z * vector2.y, + y: vector1.z * vector2.x - vector1.x * vector2.z, + z: vector1.x * vector2.y - vector1.y * vector2.x, + }; +} + +/** + * Transforms the given vector by the given matrix. + * + * @param vector A 3D vector to transform. + * @param matrix A 3x3 matrix to transform the vector with. + * @returns A new 3D vector that is the result of the transformation. + */ +export function transform(vector: Vec3, matrix: Mat3): Vec3 { + return { + x: matrix.m00 * vector.x + matrix.m01 * vector.y + matrix.m02 * vector.z, + y: matrix.m10 * vector.x + matrix.m11 * vector.y + matrix.m12 * vector.z, + z: matrix.m20 * vector.x + matrix.m21 * vector.y + matrix.m22 * vector.z, + }; +} diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/annotations/makeColorScaleAnnotation.ts b/frontend/src/modules/2DViewer/DataProviderFramework/annotations/makeColorScaleAnnotation.ts index e43098908..a9f32545a 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/annotations/makeColorScaleAnnotation.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/annotations/makeColorScaleAnnotation.ts @@ -9,6 +9,7 @@ export function makeColorScaleAnnotation({ getSetting, id, name, + getValueRange, }: TransformerArgs<[Setting.COLOR_SCALE], any>): Annotation[] { const colorScale = getSetting(Setting.COLOR_SCALE)?.colorScale; @@ -16,5 +17,13 @@ export function makeColorScaleAnnotation({ return []; } - return [{ id, colorScale: ColorScaleWithName.fromColorScale(colorScale, name) }]; + const range = getValueRange(); + if (!range) { + return []; + } + + const localColorScale = colorScale.clone(); + localColorScale.setRange(range[0], range[1]); + + return [{ id, colorScale: ColorScaleWithName.fromColorScale(localColorScale, name) }]; } diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts index 91ec86689..093c271f5 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider.ts @@ -16,7 +16,6 @@ import type { SurfaceDataFloat_trans } from "@modules/_shared/Surface/queryDataT import { transformSurfaceData } from "@modules/_shared/Surface/queryDataTransforms"; import { encodeSurfAddrStr } from "@modules/_shared/Surface/surfaceAddress"; - const observedSurfaceSettings = [ Setting.ENSEMBLE, Setting.ATTRIBUTE, @@ -199,7 +198,7 @@ export class ObservedSurfaceProvider resample_to_def_str: null, }, }); - + registerQueryKey(surfaceDataOptions.queryKey); const promise = queryClient diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts index 3fd36e43a..63c531156 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider.ts @@ -4,6 +4,7 @@ import { getGridModelsInfoOptions, getGridParameterOptions, getGridSurfaceOption import type { GridMappedProperty_trans, GridSurface_trans } from "@modules/3DViewer/view/queries/queryDataTransforms"; import { transformGridMappedProperty, transformGridSurface } from "@modules/3DViewer/view/queries/queryDataTransforms"; import type { + AreSettingsValidArgs, CustomDataProviderImplementation, DataProviderInformationAccessors, FetchDataParams, @@ -12,7 +13,6 @@ import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramew import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; - const realizationGridSettings = [ Setting.ENSEMBLE, Setting.REALIZATION, @@ -133,13 +133,9 @@ export class RealizationGridProvider registerQueryKey(gridSurfaceOptions.queryKey); - const gridParameterPromise = queryClient - .fetchQuery(gridParameterOptions) - .then(transformGridMappedProperty); + const gridParameterPromise = queryClient.fetchQuery(gridParameterOptions).then(transformGridMappedProperty); - const gridSurfacePromise = queryClient - .fetchQuery(gridSurfaceOptions) - .then(transformGridSurface); + const gridSurfacePromise = queryClient.fetchQuery(gridSurfaceOptions).then(transformGridSurface); return Promise.all([gridSurfacePromise, gridParameterPromise]).then(([gridSurfaceData, gridParameterData]) => ({ gridSurfaceData, @@ -149,7 +145,7 @@ export class RealizationGridProvider areCurrentSettingsValid({ getSetting, - }: DataProviderInformationAccessors): boolean { + }: AreSettingsValidArgs): boolean { return ( getSetting(Setting.ENSEMBLE) !== null && getSetting(Setting.REALIZATION) !== null && diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationPolygonsProvider.ts b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationPolygonsProvider.ts index 80ec813ea..81f27f05f 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationPolygonsProvider.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationPolygonsProvider.ts @@ -10,7 +10,6 @@ import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramew import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; - const realizationPolygonsSettings = [ Setting.ENSEMBLE, Setting.REALIZATION, diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSurfaceProvider.ts b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSurfaceProvider.ts index 844d8fca6..af26f4ea3 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSurfaceProvider.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSurfaceProvider.ts @@ -16,7 +16,6 @@ import type { SurfaceDataFloat_trans } from "@modules/_shared/Surface/queryDataT import { transformSurfaceData } from "@modules/_shared/Surface/queryDataTransforms"; import { encodeSurfAddrStr } from "@modules/_shared/Surface/surfaceAddress"; - const realizationSurfaceSettings = [ Setting.ENSEMBLE, Setting.REALIZATION, @@ -247,9 +246,7 @@ export class RealizationSurfaceProvider registerQueryKey(surfaceDataOptions.queryKey); const promise = queryClient - .fetchQuery( - surfaceDataOptions, - ) + .fetchQuery(surfaceDataOptions) .then((data) => ({ format: this._dataFormat, surfaceData: transformSurfaceData(data) })); return promise as Promise; diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/StatisticalSurfaceProvider.ts b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/StatisticalSurfaceProvider.ts index 081f8f28c..6d62f5ef9 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/StatisticalSurfaceProvider.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/customDataProviderImplementations/StatisticalSurfaceProvider.ts @@ -22,7 +22,6 @@ import type { SurfaceDataFloat_trans } from "@modules/_shared/Surface/queryDataT import { transformSurfaceData } from "@modules/_shared/Surface/queryDataTransforms"; import { encodeSurfAddrStr } from "@modules/_shared/Surface/surfaceAddress"; - const statisicalSurfaceSettings = [ Setting.ENSEMBLE, Setting.STATISTIC_FUNCTION, @@ -65,7 +64,10 @@ export class StatisticalSurfaceProvider return "Statistical Surface"; } - doSettingsChangesRequireDataRefetch(prevSettings: SettingsWithTypes, newSettings: SettingsWithTypes): boolean { + doSettingsChangesRequireDataRefetch( + prevSettings: SettingsWithTypes | null, + newSettings: SettingsWithTypes, + ): boolean { return !isEqual(prevSettings, newSettings); } diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationGridLayer.ts b/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationGridLayer.ts index c8097c353..bf02d1979 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationGridLayer.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationGridLayer.ts @@ -33,7 +33,7 @@ export function makeRealizationGridLayer({ polysData: polysNumberArray, propertiesData: gridParameterData.polyPropsFloat32Arr, ZIncreasingDownwards: false, - gridLines: showGridLines, + gridLines: showGridLines ?? false, material: { ambient: 0.4, diffuse: 0.7, shininess: 8, specularColor: [25, 25, 25] }, pickable: true, colorMapName: "Physics", diff --git a/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationPolygonsLayer.ts b/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationPolygonsLayer.ts index 26bbc2ab2..7109fee24 100644 --- a/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationPolygonsLayer.ts +++ b/frontend/src/modules/2DViewer/DataProviderFramework/visualization/makeRealizationPolygonsLayer.ts @@ -4,13 +4,12 @@ import type { Feature, FeatureCollection, GeoJsonProperties, Geometry } from "ge import type { PolygonData_api } from "@api"; import type { TransformerArgs } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; - import type { RealizationPolygonsData, RealizationPolygonsSettings, } from "../customDataProviderImplementations/RealizationPolygonsProvider"; -function zipCoords(xArr: number[], yArr: number[], zArr: number[]): number[][] { +function zipCoords(xArr: readonly number[], yArr: readonly number[], zArr: readonly number[]): number[][] { const coords: number[][] = []; for (let i = 0; i < xArr.length; i++) { coords.push([xArr[i], yArr[i], -zArr[i]]); diff --git a/frontend/src/modules/2DViewer/settings/settings.tsx b/frontend/src/modules/2DViewer/settings/settings.tsx index df5c4b31a..b5af7cbe4 100644 --- a/frontend/src/modules/2DViewer/settings/settings.tsx +++ b/frontend/src/modules/2DViewer/settings/settings.tsx @@ -9,7 +9,6 @@ import { useEnsembleSet } from "@framework/WorkbenchSession"; import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; import { GroupDelegateTopic } from "@modules/_shared/DataProviderFramework/delegates/GroupDelegate"; - import { DataProviderManager, DataProviderManagerTopic, @@ -19,7 +18,6 @@ import { dataProviderManagerAtom, preferredViewLayoutAtom, userSelectedFieldIden import { selectedFieldIdentifierAtom } from "./atoms/derivedAtoms"; import { DataProviderManagerWrapper } from "./components/dataProviderManagerWrapper"; - export function Settings(props: ModuleSettingsProps): React.ReactNode { const ensembleSet = useEnsembleSet(props.workbenchSession); const queryClient = useQueryClient(); @@ -112,7 +110,6 @@ export function Settings(props: ModuleSettingsProps): React.ReactNode { .makeSubscriberFunction(GroupDelegateTopic.CHILDREN_EXPANSION_STATES)(persistState); return function onUnmountEffect() { - dataProviderManager.beforeDestroy(); unsubscribeDataRev(); unsubscribeExpands(); }; diff --git a/frontend/src/modules/3DViewer/preview.webp b/frontend/src/modules/3DViewer/preview.webp deleted file mode 100644 index d180acb38..000000000 Binary files a/frontend/src/modules/3DViewer/preview.webp and /dev/null differ diff --git a/frontend/src/modules/3DViewer/settings/settings.tsx b/frontend/src/modules/3DViewer/settings/settings.tsx index 798ee02de..1b1f4ecfe 100644 --- a/frontend/src/modules/3DViewer/settings/settings.tsx +++ b/frontend/src/modules/3DViewer/settings/settings.tsx @@ -32,7 +32,6 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePropagateApiErrorToStatusWriter"; import { isoIntervalStringToDateLabel, isoStringToDateLabel } from "@modules/_shared/utils/isoDatetimeStringFormatting"; - import type { Interfaces } from "../interfaces"; import type { GridCellIndexRanges } from "../typesAndEnums"; @@ -71,7 +70,6 @@ import { drilledWellboreHeadersQueryAtom, gridModelInfosQueryAtom } from "./atom import { GridCellIndexFilter } from "./components/gridCellIndexFilter"; import { WellboreSelector } from "./components/wellboreSelector"; - export function Settings(props: ModuleSettingsProps): JSX.Element { const ensembleSet = useEnsembleSet(props.workbenchSession); const statusWriter = useSettingsStatusWriter(props.settingsContext); @@ -515,7 +513,7 @@ function makeWellHeaderOptions(wellHeaders: WellboreHeader_api[]): SelectOption[ } function makeCustomIntersectionPolylineOptions( - polylines: IntersectionPolyline[], + polylines: readonly IntersectionPolyline[], selectedId: string | null, filter: string, actions: React.ReactNode, diff --git a/frontend/src/modules/3DViewer/view/components/PolylineEditingPanel.tsx b/frontend/src/modules/3DViewer/view/components/PolylineEditingPanel.tsx index 9466c49db..bec1fcd29 100644 --- a/frontend/src/modules/3DViewer/view/components/PolylineEditingPanel.tsx +++ b/frontend/src/modules/3DViewer/view/components/PolylineEditingPanel.tsx @@ -15,7 +15,7 @@ export type PolylineEditingPanelProps = { currentlyEditedPolylineName?: string; selectedPolylineIndex: number | null; hoveredPolylineIndex: number | null; - intersectionPolylines: IntersectionPolyline[]; + intersectionPolylines: readonly IntersectionPolyline[]; onPolylinePointSelectionChange: (index: number | null) => void; onPolylineEditingModusChange: (active: boolean) => void; onDeleteCurrentlySelectedPoint: () => void; @@ -204,7 +204,7 @@ function makeSelectOptionsFromPoints(points: number[][]): SelectOption[] { })); } -function makeUniquePolylineName(intersectionPolylines: IntersectionPolyline[]): string { +function makeUniquePolylineName(intersectionPolylines: readonly IntersectionPolyline[]): string { const names = intersectionPolylines.map((polyline) => polyline.name); let i = 1; while (names.includes(`Polyline ${i}`)) { diff --git a/frontend/src/modules/3DViewer/view/components/SubsurfaceViewerWrapper.tsx b/frontend/src/modules/3DViewer/view/components/SubsurfaceViewerWrapper.tsx index cc54a86ec..5ceafdad6 100644 --- a/frontend/src/modules/3DViewer/view/components/SubsurfaceViewerWrapper.tsx +++ b/frontend/src/modules/3DViewer/view/components/SubsurfaceViewerWrapper.tsx @@ -55,7 +55,7 @@ export type SubsurfaceViewerWrapperProps = { onIntersectionPolylineEditCancel?: () => void; onVerticalScaleChange?: (verticalScale: number) => void; intersectionPolyline?: IntersectionPolyline; - intersectionPolylines?: IntersectionPolyline[]; + intersectionPolylines?: readonly IntersectionPolyline[]; }; type IntersectionZValues = { @@ -372,6 +372,7 @@ export function SubsurfaceViewerWrapper(props: SubsurfaceViewerWrapperProps): Re name, fieldId: props.fieldId, path: currentlyEditedPolyline, + color: [255, 0, 0], }); } handlePolylineEditingCancel(); diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicCrosslineProvider.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicCrosslineProvider.ts new file mode 100644 index 000000000..dfaf1ef2b --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicCrosslineProvider.ts @@ -0,0 +1,214 @@ +import { type SeismicCubeMeta_api, getCrosslineSliceOptions, getSeismicCubeMetaListOptions } from "@api"; +import { + type SeismicSliceData_trans, + transformSeismicSlice, +} from "@modules/3DViewerNew/settings/queries/queryDataTransforms"; +import type { + CustomDataProviderImplementation, + DataProviderInformationAccessors, + FetchDataParams, +} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import { Setting, type MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; + +import { isEqual } from "lodash"; + +const realizationSeismicCrosslineSettings = [ + Setting.ENSEMBLE, + Setting.REALIZATION, + Setting.ATTRIBUTE, + Setting.TIME_OR_INTERVAL, + Setting.SEISMIC_CROSSLINE, + Setting.COLOR_SCALE, +] as const; +export type RealizationSeismicCrosslineSettings = typeof realizationSeismicCrosslineSettings; +type SettingsWithTypes = MakeSettingTypesMap; + +export type RealizationSeismicCrosslineData = SeismicSliceData_trans; + +export type RealizationSeismicCrosslineStoredData = { + seismicCubeMeta: SeismicCubeMeta_api[]; +}; + +export class RealizationSeismicCrosslineProvider + implements + CustomDataProviderImplementation< + RealizationSeismicCrosslineSettings, + RealizationSeismicCrosslineData, + RealizationSeismicCrosslineStoredData + > +{ + settings = realizationSeismicCrosslineSettings; + + getDefaultName(): string { + return "Seismic Crossline (realization)"; + } + + doSettingsChangesRequireDataRefetch(prevSettings: SettingsWithTypes, newSettings: SettingsWithTypes): boolean { + return !isEqual(prevSettings, newSettings); + } + + makeValueRange( + accessors: DataProviderInformationAccessors< + RealizationSeismicCrosslineSettings, + RealizationSeismicCrosslineData, + RealizationSeismicCrosslineStoredData + >, + ): [number, number] | null { + const data = accessors.getData(); + if (!data) { + return null; + } + return [data.value_min, data.value_max]; + } + + fetchData({ + getSetting, + registerQueryKey, + queryClient, + }: FetchDataParams< + RealizationSeismicCrosslineSettings, + RealizationSeismicCrosslineData + >): Promise { + const ensembleIdent = getSetting(Setting.ENSEMBLE); + const realizationNum = getSetting(Setting.REALIZATION); + const attribute = getSetting(Setting.ATTRIBUTE); + const timeOrInterval = getSetting(Setting.TIME_OR_INTERVAL); + const crosslineNumber = getSetting(Setting.SEISMIC_CROSSLINE); + + const queryOptions = getCrosslineSliceOptions({ + query: { + case_uuid: ensembleIdent?.getCaseUuid() ?? "", + ensemble_name: ensembleIdent?.getEnsembleName() ?? "", + realization_num: realizationNum ?? 0, + seismic_attribute: attribute ?? "", + time_or_interval_str: timeOrInterval ?? "", + observed: false, + crossline_no: crosslineNumber ?? 0, + }, + }); + + registerQueryKey(queryOptions.queryKey); + + return queryClient + .fetchQuery({ + ...queryOptions, + }) + .then((data) => transformSeismicSlice(data)); + } + + defineDependencies({ + helperDependency, + availableSettingsUpdater, + storedDataUpdater, + queryClient, + }: DefineDependenciesArgs): void { + availableSettingsUpdater(Setting.ENSEMBLE, ({ getGlobalSetting }) => { + const fieldIdentifier = getGlobalSetting("fieldId"); + const ensembles = getGlobalSetting("ensembles"); + + const ensembleIdents = ensembles + .filter((ensemble) => ensemble.getFieldIdentifier() === fieldIdentifier) + .map((ensemble) => ensemble.getIdent()); + + return ensembleIdents; + }); + + availableSettingsUpdater(Setting.REALIZATION, ({ getLocalSetting, getGlobalSetting }) => { + const ensembleIdent = getLocalSetting(Setting.ENSEMBLE); + const realizationFilterFunc = getGlobalSetting("realizationFilterFunction"); + + if (!ensembleIdent) { + return []; + } + + const realizations = realizationFilterFunc(ensembleIdent); + + return [...realizations]; + }); + + const realizationSeismicCrosslineDataDep = helperDependency(async ({ getLocalSetting, abortSignal }) => { + const ensembleIdent = getLocalSetting(Setting.ENSEMBLE); + const realization = getLocalSetting(Setting.REALIZATION); + + if (!ensembleIdent || realization === null) { + return null; + } + + return await queryClient.fetchQuery({ + ...getSeismicCubeMetaListOptions({ + query: { + case_uuid: ensembleIdent.getCaseUuid(), + ensemble_name: ensembleIdent.getEnsembleName(), + }, + signal: abortSignal, + }), + }); + }); + + storedDataUpdater("seismicCubeMeta", ({ getHelperDependency }) => { + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!data) { + return null; + } + + return data; + }); + + availableSettingsUpdater(Setting.ATTRIBUTE, ({ getHelperDependency }) => { + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!data) { + return []; + } + + const availableSeismicAttributes = [ + ...Array.from(new Set(data.map((seismicInfos) => seismicInfos.seismicAttribute))), + ]; + + return availableSeismicAttributes; + }); + + availableSettingsUpdater(Setting.TIME_OR_INTERVAL, ({ getLocalSetting, getHelperDependency }) => { + const seismicAttribute = getLocalSetting(Setting.ATTRIBUTE); + + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!seismicAttribute || !data) { + return []; + } + + const availableTimeOrIntervals = [ + ...Array.from( + new Set( + data + .filter((surface) => surface.seismicAttribute === seismicAttribute) + .map((el) => el.isoDateOrInterval), + ), + ), + ]; + + return availableTimeOrIntervals; + }); + + availableSettingsUpdater(Setting.SEISMIC_CROSSLINE, ({ getLocalSetting, getHelperDependency }) => { + const seismicAttribute = getLocalSetting(Setting.ATTRIBUTE); + const timeOrInterval = getLocalSetting(Setting.TIME_OR_INTERVAL); + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!seismicAttribute || !timeOrInterval || !data) { + return [0, 0, 0]; + } + const seismicInfo = data.filter( + (seismicInfos) => + seismicInfos.seismicAttribute === seismicAttribute && + seismicInfos.isoDateOrInterval === timeOrInterval, + )[0]; + const jMin = 0; + const jMax = seismicInfo.spec.numRows - 1; + + return [jMin, jMax, 1]; + }); + } +} diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicDepthProvider.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicDepthProvider.ts new file mode 100644 index 000000000..4c3685e31 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicDepthProvider.ts @@ -0,0 +1,217 @@ +import { type SeismicCubeMeta_api, getDepthSliceOptions, getSeismicCubeMetaListOptions } from "@api"; +import { + type SeismicSliceData_trans, + transformSeismicSlice, +} from "@modules/3DViewerNew/settings/queries/queryDataTransforms"; +import type { + CustomDataProviderImplementation, + DataProviderInformationAccessors, + FetchDataParams, +} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import { Setting, type MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; + +import { isEqual } from "lodash"; + +const realizationSeismicDepthSliceSettings = [ + Setting.ENSEMBLE, + Setting.REALIZATION, + Setting.ATTRIBUTE, + Setting.TIME_OR_INTERVAL, + Setting.SEISMIC_DEPTH_SLICE, + Setting.COLOR_SCALE, +] as const; +export type RealizationSeismicDepthSliceSettings = typeof realizationSeismicDepthSliceSettings; +type SettingsWithTypes = MakeSettingTypesMap; + +export type RealizationSeismicDepthSliceData = SeismicSliceData_trans; + +export type RealizationSeismicDepthSliceStoredData = { + seismicCubeMeta: SeismicCubeMeta_api[]; +}; + +export class RealizationSeismicDepthSliceProvider + implements + CustomDataProviderImplementation< + RealizationSeismicDepthSliceSettings, + RealizationSeismicDepthSliceData, + RealizationSeismicDepthSliceStoredData + > +{ + settings = realizationSeismicDepthSliceSettings; + + getDefaultName(): string { + return "Seismic Inline (realization)"; + } + + doSettingsChangesRequireDataRefetch(prevSettings: SettingsWithTypes, newSettings: SettingsWithTypes): boolean { + return !isEqual(prevSettings, newSettings); + } + + makeValueRange( + accessors: DataProviderInformationAccessors< + RealizationSeismicDepthSliceSettings, + RealizationSeismicDepthSliceData, + RealizationSeismicDepthSliceStoredData + >, + ): [number, number] | null { + const data = accessors.getData(); + if (!data) { + return null; + } + return [data.value_min, data.value_max]; + } + + fetchData({ + getSetting, + registerQueryKey, + queryClient, + }: FetchDataParams< + RealizationSeismicDepthSliceSettings, + RealizationSeismicDepthSliceData + >): Promise { + const ensembleIdent = getSetting(Setting.ENSEMBLE); + const realizationNum = getSetting(Setting.REALIZATION); + const attribute = getSetting(Setting.ATTRIBUTE); + const timeOrInterval = getSetting(Setting.TIME_OR_INTERVAL); + const depthSlice = getSetting(Setting.SEISMIC_DEPTH_SLICE); + + const queryOptions = getDepthSliceOptions({ + query: { + case_uuid: ensembleIdent?.getCaseUuid() ?? "", + ensemble_name: ensembleIdent?.getEnsembleName() ?? "", + realization_num: realizationNum ?? 0, + seismic_attribute: attribute ?? "", + time_or_interval_str: timeOrInterval ?? "", + observed: false, + depth_slice_no: depthSlice ?? 0, + }, + }); + + registerQueryKey(queryOptions.queryKey); + + return queryClient + .fetchQuery({ + ...queryOptions, + }) + .then((data) => transformSeismicSlice(data)); + } + + defineDependencies({ + helperDependency, + availableSettingsUpdater, + storedDataUpdater, + queryClient, + }: DefineDependenciesArgs): void { + availableSettingsUpdater(Setting.ENSEMBLE, ({ getGlobalSetting }) => { + const fieldIdentifier = getGlobalSetting("fieldId"); + const ensembles = getGlobalSetting("ensembles"); + + const ensembleIdents = ensembles + .filter((ensemble) => ensemble.getFieldIdentifier() === fieldIdentifier) + .map((ensemble) => ensemble.getIdent()); + + return ensembleIdents; + }); + + availableSettingsUpdater(Setting.REALIZATION, ({ getLocalSetting, getGlobalSetting }) => { + const ensembleIdent = getLocalSetting(Setting.ENSEMBLE); + const realizationFilterFunc = getGlobalSetting("realizationFilterFunction"); + + if (!ensembleIdent) { + return []; + } + + const realizations = realizationFilterFunc(ensembleIdent); + + return [...realizations]; + }); + + const realizationSeismicCrosslineDataDep = helperDependency(async ({ getLocalSetting, abortSignal }) => { + const ensembleIdent = getLocalSetting(Setting.ENSEMBLE); + const realization = getLocalSetting(Setting.REALIZATION); + + if (!ensembleIdent || realization === null) { + return null; + } + + return await queryClient.fetchQuery({ + ...getSeismicCubeMetaListOptions({ + query: { + case_uuid: ensembleIdent.getCaseUuid(), + ensemble_name: ensembleIdent.getEnsembleName(), + }, + signal: abortSignal, + }), + }); + }); + + storedDataUpdater("seismicCubeMeta", ({ getHelperDependency }) => { + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!data) { + return null; + } + + return data; + }); + + availableSettingsUpdater(Setting.ATTRIBUTE, ({ getHelperDependency }) => { + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!data) { + return []; + } + + const availableSeismicAttributes = [ + ...Array.from(new Set(data.map((seismicInfos) => seismicInfos.seismicAttribute))), + ]; + + return availableSeismicAttributes; + }); + + availableSettingsUpdater(Setting.TIME_OR_INTERVAL, ({ getLocalSetting, getHelperDependency }) => { + const seismicAttribute = getLocalSetting(Setting.ATTRIBUTE); + + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!seismicAttribute || !data) { + return []; + } + + const availableTimeOrIntervals = [ + ...Array.from( + new Set( + data + .filter((surface) => surface.seismicAttribute === seismicAttribute) + .map((el) => el.isoDateOrInterval), + ), + ), + ]; + + return availableTimeOrIntervals; + }); + + availableSettingsUpdater(Setting.SEISMIC_DEPTH_SLICE, ({ getLocalSetting, getHelperDependency }) => { + const seismicAttribute = getLocalSetting(Setting.ATTRIBUTE); + const timeOrInterval = getLocalSetting(Setting.TIME_OR_INTERVAL); + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!seismicAttribute || !timeOrInterval || !data) { + return [0, 0, 0]; + } + const seismicInfo = data.filter( + (seismicInfos) => + seismicInfos.seismicAttribute === seismicAttribute && + seismicInfos.isoDateOrInterval === timeOrInterval, + )[0]; + const zMin = seismicInfo.spec.zOrigin; + const zMax = + seismicInfo.spec.zOrigin + + seismicInfo.spec.zInc * seismicInfo.spec.zFlip * (seismicInfo.spec.numLayers - 1); + const zInc = seismicInfo.spec.zInc; + + return [zMin, zMax, zInc]; + }); + } +} diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicInlineProvider.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicInlineProvider.ts new file mode 100644 index 000000000..26baf97ed --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicInlineProvider.ts @@ -0,0 +1,214 @@ +import { type SeismicCubeMeta_api, getInlineSliceOptions, getSeismicCubeMetaListOptions } from "@api"; +import { + type SeismicSliceData_trans, + transformSeismicSlice, +} from "@modules/3DViewerNew/settings/queries/queryDataTransforms"; +import type { + CustomDataProviderImplementation, + DataProviderInformationAccessors, + FetchDataParams, +} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation"; +import type { DefineDependenciesArgs } from "@modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler"; +import { Setting, type MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; + +import { isEqual } from "lodash"; + +const realizationSeismicInlineSettings = [ + Setting.ENSEMBLE, + Setting.REALIZATION, + Setting.ATTRIBUTE, + Setting.TIME_OR_INTERVAL, + Setting.SEISMIC_INLINE, + Setting.COLOR_SCALE, +] as const; +export type RealizationSeismicInlineSettings = typeof realizationSeismicInlineSettings; +type SettingsWithTypes = MakeSettingTypesMap; + +export type RealizationSeismicInlineData = SeismicSliceData_trans; + +export type RealizationSeismicInlineStoredData = { + seismicCubeMeta: SeismicCubeMeta_api[]; +}; + +export class RealizationSeismicInlineProvider + implements + CustomDataProviderImplementation< + RealizationSeismicInlineSettings, + RealizationSeismicInlineData, + RealizationSeismicInlineStoredData + > +{ + settings = realizationSeismicInlineSettings; + + getDefaultName(): string { + return "Seismic Inline (realization)"; + } + + doSettingsChangesRequireDataRefetch(prevSettings: SettingsWithTypes, newSettings: SettingsWithTypes): boolean { + return !isEqual(prevSettings, newSettings); + } + + makeValueRange( + accessors: DataProviderInformationAccessors< + RealizationSeismicInlineSettings, + RealizationSeismicInlineData, + RealizationSeismicInlineStoredData + >, + ): [number, number] | null { + const data = accessors.getData(); + if (!data) { + return null; + } + return [data.value_min, data.value_max]; + } + + fetchData({ + getSetting, + registerQueryKey, + queryClient, + }: FetchDataParams< + RealizationSeismicInlineSettings, + RealizationSeismicInlineData + >): Promise { + const ensembleIdent = getSetting(Setting.ENSEMBLE); + const realizationNum = getSetting(Setting.REALIZATION); + const attribute = getSetting(Setting.ATTRIBUTE); + const timeOrInterval = getSetting(Setting.TIME_OR_INTERVAL); + const inlineNumber = getSetting(Setting.SEISMIC_INLINE); + + const queryOptions = getInlineSliceOptions({ + query: { + case_uuid: ensembleIdent?.getCaseUuid() ?? "", + ensemble_name: ensembleIdent?.getEnsembleName() ?? "", + realization_num: realizationNum ?? 0, + seismic_attribute: attribute ?? "", + time_or_interval_str: timeOrInterval ?? "", + observed: false, + inline_no: inlineNumber ?? 0, + }, + }); + + registerQueryKey(queryOptions.queryKey); + + return queryClient + .fetchQuery({ + ...queryOptions, + }) + .then((data) => transformSeismicSlice(data)); + } + + defineDependencies({ + helperDependency, + availableSettingsUpdater, + storedDataUpdater, + queryClient, + }: DefineDependenciesArgs): void { + availableSettingsUpdater(Setting.ENSEMBLE, ({ getGlobalSetting }) => { + const fieldIdentifier = getGlobalSetting("fieldId"); + const ensembles = getGlobalSetting("ensembles"); + + const ensembleIdents = ensembles + .filter((ensemble) => ensemble.getFieldIdentifier() === fieldIdentifier) + .map((ensemble) => ensemble.getIdent()); + + return ensembleIdents; + }); + + availableSettingsUpdater(Setting.REALIZATION, ({ getLocalSetting, getGlobalSetting }) => { + const ensembleIdent = getLocalSetting(Setting.ENSEMBLE); + const realizationFilterFunc = getGlobalSetting("realizationFilterFunction"); + + if (!ensembleIdent) { + return []; + } + + const realizations = realizationFilterFunc(ensembleIdent); + + return [...realizations]; + }); + + const realizationSeismicCrosslineDataDep = helperDependency(async ({ getLocalSetting, abortSignal }) => { + const ensembleIdent = getLocalSetting(Setting.ENSEMBLE); + const realization = getLocalSetting(Setting.REALIZATION); + + if (!ensembleIdent || realization === null) { + return null; + } + + return await queryClient.fetchQuery({ + ...getSeismicCubeMetaListOptions({ + query: { + case_uuid: ensembleIdent.getCaseUuid(), + ensemble_name: ensembleIdent.getEnsembleName(), + }, + signal: abortSignal, + }), + }); + }); + + storedDataUpdater("seismicCubeMeta", ({ getHelperDependency }) => { + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!data) { + return null; + } + + return data; + }); + + availableSettingsUpdater(Setting.ATTRIBUTE, ({ getHelperDependency }) => { + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!data) { + return []; + } + + const availableSeismicAttributes = [ + ...Array.from(new Set(data.map((seismicInfos) => seismicInfos.seismicAttribute))), + ]; + + return availableSeismicAttributes; + }); + + availableSettingsUpdater(Setting.TIME_OR_INTERVAL, ({ getLocalSetting, getHelperDependency }) => { + const seismicAttribute = getLocalSetting(Setting.ATTRIBUTE); + + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!seismicAttribute || !data) { + return []; + } + + const availableTimeOrIntervals = [ + ...Array.from( + new Set( + data + .filter((surface) => surface.seismicAttribute === seismicAttribute) + .map((el) => el.isoDateOrInterval), + ), + ), + ]; + + return availableTimeOrIntervals; + }); + + availableSettingsUpdater(Setting.SEISMIC_INLINE, ({ getLocalSetting, getHelperDependency }) => { + const seismicAttribute = getLocalSetting(Setting.ATTRIBUTE); + const timeOrInterval = getLocalSetting(Setting.TIME_OR_INTERVAL); + const data = getHelperDependency(realizationSeismicCrosslineDataDep); + + if (!seismicAttribute || !timeOrInterval || !data) { + return [0, 0, 0]; + } + const seismicInfo = data.filter( + (seismicInfos) => + seismicInfos.seismicAttribute === seismicAttribute && + seismicInfos.isoDateOrInterval === timeOrInterval, + )[0]; + const iMin = 0; + const iMax = seismicInfo.spec.numCols - 1; + + return [iMin, iMax, 1]; + }); + } +} diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/dataProviderTypes.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/dataProviderTypes.ts new file mode 100644 index 000000000..53e1162e7 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/dataProviderTypes.ts @@ -0,0 +1,5 @@ +export enum CustomDataProviderType { + REALIZATION_SEISMIC_CROSSLINE = "REALIZATION_SEISMIC_CROSSLINE", + REALIZATION_SEISMIC_INLINE = "REALIZATION_SEISMIC_INLINE", + REALIZATION_SEISMIC_DEPTH_SLICE = "REALIZATION_SEISMIC_DEPTH_SLICE", +} diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/registerAllDataProviders.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/registerAllDataProviders.ts new file mode 100644 index 000000000..117d54bb7 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/registerAllDataProviders.ts @@ -0,0 +1,19 @@ +import { DataProviderRegistry } from "@modules/_shared/DataProviderFramework/dataProviders/DataProviderRegistry"; + +import { CustomDataProviderType } from "./dataProviderTypes"; +import { RealizationSeismicCrosslineProvider } from "./RealizationSeismicCrosslineProvider"; +import { RealizationSeismicDepthSliceProvider } from "./RealizationSeismicDepthProvider"; +import { RealizationSeismicInlineProvider } from "./RealizationSeismicInlineProvider"; + +DataProviderRegistry.registerDataProvider( + CustomDataProviderType.REALIZATION_SEISMIC_CROSSLINE, + RealizationSeismicCrosslineProvider, +); +DataProviderRegistry.registerDataProvider( + CustomDataProviderType.REALIZATION_SEISMIC_INLINE, + RealizationSeismicInlineProvider, +); +DataProviderRegistry.registerDataProvider( + CustomDataProviderType.REALIZATION_SEISMIC_DEPTH_SLICE, + RealizationSeismicDepthSliceProvider, +); diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeDrilledWellTrajectoriesLayer.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeDrilledWellTrajectoriesLayer.ts new file mode 100644 index 000000000..aed74af29 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeDrilledWellTrajectoriesLayer.ts @@ -0,0 +1,47 @@ +import type { WellboreTrajectory_api } from "@api"; +import * as bbox from "@lib/utils/bbox"; +import { WellsLayer } from "@modules/_shared/customDeckGlLayers/WellsLayer/WellsLayer"; +import type { WellsLayerData } from "@modules/_shared/customDeckGlLayers/WellsLayer/WellsLayer"; +import { makeDrilledWellTrajectoriesBoundingBox } from "@modules/_shared/DataProviderFramework/visualization/deckgl/boundingBoxes/makeDrilledWellTrajectoriesBoundingBox"; +import type { TransformerArgs } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; + +export function makeDrilledWellTrajectoriesLayer( + args: TransformerArgs, +): WellsLayer | null { + const { id, getData, name } = args; + + const fieldWellboreTrajectoriesData = getData(); + + if (!fieldWellboreTrajectoriesData) { + return null; + } + + const wellsLayerData: WellsLayerData = []; + for (const wellboreData of fieldWellboreTrajectoriesData) { + const properties = { + uuid: wellboreData.wellboreUuid, + name: wellboreData.uniqueWellboreIdentifier, + mdArray: wellboreData.mdArr, + }; + const coordinates: [number, number, number][] = wellboreData.eastingArr.map((easting, index) => { + return [easting, wellboreData.northingArr[index], -wellboreData.tvdMslArr[index]]; + }); + wellsLayerData.push({ properties, coordinates }); + } + + const boundingBox = makeDrilledWellTrajectoriesBoundingBox(args); + + if (!boundingBox) { + return null; + } + + const wellsLayer = new WellsLayer({ + id: id, + name, + data: wellsLayerData, + zIncreaseDownwards: true, + boundingBox: bbox.toNumArray(boundingBox), + }); + + return wellsLayer; +} diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeIntersectionGrid3dLayer.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeIntersectionGrid3dLayer.ts new file mode 100644 index 000000000..5d88a5b0d --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeIntersectionGrid3dLayer.ts @@ -0,0 +1,114 @@ +import type { IntersectionRealizationGridSettings } from "@modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { makeColorMapFunctionFromColorScale } from "@modules/_shared/DataProviderFramework/visualization/utils/colors"; +import type { TransformerArgs } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import { FenceMeshSection_trans, PolylineIntersection_trans } from "@modules/_shared/utils/wellbore"; +import { TGrid3DColoringMode } from "@webviz/subsurface-viewer"; +import { Grid3DLayer } from "@webviz/subsurface-viewer/dist/layers"; + +interface PolyDataVtk { + points: Float32Array; + polys: Uint32Array; + props: Float32Array; +} + +function buildVtkStylePolyDataFromFenceSections(fenceSections: FenceMeshSection_trans[]): PolyDataVtk { + // Calculate sizes of typed arrays + let totNumVertices = 0; + let totNumPolygons = 0; + let totNumConnectivities = 0; + for (const section of fenceSections) { + totNumVertices += section.verticesUzFloat32Arr.length / 2; + totNumPolygons += section.verticesPerPolyUintArr.length; + totNumConnectivities += section.polyIndicesUintArr.length; + } + + const pointsFloat32Arr = new Float32Array(3 * totNumVertices); + const polysUint32Arr = new Uint32Array(totNumPolygons + totNumConnectivities); + const polyPropsFloat32Arr = new Float32Array(totNumPolygons); + + let floatPointsDstIdx = 0; + let polysDstIdx = 0; + let propsDstIdx = 0; + for (const section of fenceSections) { + // uv to xyz + const directionX = section.end_utm_x - section.start_utm_x; + const directionY = section.end_utm_y - section.start_utm_y; + const magnitude = Math.sqrt(directionX ** 2 + directionY ** 2); + const unitDirectionX = directionX / magnitude; + const unitDirectionY = directionY / magnitude; + + const connOffset = floatPointsDstIdx / 3; + + for (let i = 0; i < section.verticesUzFloat32Arr.length; i += 2) { + const u = section.verticesUzFloat32Arr[i]; + const z = section.verticesUzFloat32Arr[i + 1]; + const x = u * unitDirectionX + section.start_utm_x; + const y = u * unitDirectionY + section.start_utm_y; + + pointsFloat32Arr[floatPointsDstIdx++] = x; + pointsFloat32Arr[floatPointsDstIdx++] = y; + pointsFloat32Arr[floatPointsDstIdx++] = z; + } + + // Fix poly indexes for each section + const numPolysInSection = section.verticesPerPolyUintArr.length; + let srcIdx = 0; + for (let i = 0; i < numPolysInSection; i++) { + const numVertsInPoly = section.verticesPerPolyUintArr[i]; + polysUint32Arr[polysDstIdx++] = numVertsInPoly; + + for (let j = 0; j < numVertsInPoly; j++) { + polysUint32Arr[polysDstIdx++] = section.polyIndicesUintArr[srcIdx++] + connOffset; + } + } + + polyPropsFloat32Arr.set(section.polyPropsFloat32Arr, propsDstIdx); + propsDstIdx += numPolysInSection; + } + + return { + points: pointsFloat32Arr, + polys: polysUint32Arr, + props: polyPropsFloat32Arr, + }; +} + +export function makeIntersectionLayer({ + id, + name, + getData, + getSetting, +}: TransformerArgs): Grid3DLayer | null { + const data = getData(); + const colorScale = getSetting(Setting.COLOR_SCALE)?.colorScale; + const showGridLines = getSetting(Setting.SHOW_GRID_LINES); + + if (!data) { + return null; + } + const polyData = buildVtkStylePolyDataFromFenceSections(data.fenceMeshSections); + + const grid3dIntersectionLayer = new Grid3DLayer({ + id, + name, + pointsData: polyData.points, + polysData: polyData.polys, + propertiesData: polyData.props, + colorMapName: "Continuous", + colorMapRange: [data.min_grid_prop_value, data.max_grid_prop_value], + colorMapClampColor: true, + coloringMode: TGrid3DColoringMode.Property, + colorMapFunction: makeColorMapFunctionFromColorScale( + colorScale, + data.min_grid_prop_value, + data.max_grid_prop_value, + ), + ZIncreasingDownwards: false, + gridLines: showGridLines ?? false, + material: { ambient: 0.4, diffuse: 0.7, shininess: 8, specularColor: [25, 25, 25] }, + pickable: true, + }); + + return grid3dIntersectionLayer; +} diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeRealizationSurfaceLayer.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeRealizationSurfaceLayer.ts new file mode 100644 index 000000000..0d9dc5b41 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeRealizationSurfaceLayer.ts @@ -0,0 +1,69 @@ +import { SurfaceDataFormat } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider"; +import type { + RealizationSurfaceData, + RealizationSurfaceSettings, +} from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSurfaceProvider"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { makeColorMapFunctionFromColorScale } from "@modules/_shared/DataProviderFramework/visualization/utils/colors"; +import type { TransformerArgs } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import { MapLayer } from "@webviz/subsurface-viewer/dist/layers"; + +export function makeRealizationSurfaceLayer({ + id, + name, + getData, + getSetting, +}: TransformerArgs): MapLayer | null { + const data = getData(); + const colorScale = getSetting(Setting.COLOR_SCALE)?.colorScale; + + if (!data) { + return null; + } + + if (data.surfaceData.format === SurfaceDataFormat.FLOAT) { + return new MapLayer({ + id, + name, + meshData: data.surfaceData.valuesFloat32Arr, + frame: { + origin: [data.surfaceData.surface_def.origin_utm_x, data.surfaceData.surface_def.origin_utm_y], + count: [data.surfaceData.surface_def.npoints_x, data.surfaceData.surface_def.npoints_y], + increment: [data.surfaceData.surface_def.inc_x, data.surfaceData.surface_def.inc_y], + rotDeg: data.surfaceData.surface_def.rot_deg, + }, + valueRange: [data.surfaceData.value_min, data.surfaceData.value_max], + colorMapRange: [data.surfaceData.value_min, data.surfaceData.value_max], + colorMapFunction: makeColorMapFunctionFromColorScale( + colorScale, + data.surfaceData.value_min, + data.surfaceData.value_max, + ), + gridLines: false, + }); + } + + if (data.surfaceData.format === SurfaceDataFormat.PNG) { + return new MapLayer({ + id, + name, + meshData: data.surfaceData.png_image_base64, + frame: { + origin: [data.surfaceData.surface_def.origin_utm_x, data.surfaceData.surface_def.origin_utm_y], + count: [data.surfaceData.surface_def.npoints_x, data.surfaceData.surface_def.npoints_y], + increment: [data.surfaceData.surface_def.inc_x, data.surfaceData.surface_def.inc_y], + rotDeg: data.surfaceData.surface_def.rot_deg, + }, + valueRange: [data.surfaceData.value_min, data.surfaceData.value_max], + colorMapRange: [data.surfaceData.value_min, data.surfaceData.value_max], + colorMapFunction: makeColorMapFunctionFromColorScale( + colorScale, + data.surfaceData.value_min, + data.surfaceData.value_max, + ), + gridLines: false, + }); + } + + return null; +} diff --git a/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeSeismicFenceMeshLayer.ts b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeSeismicFenceMeshLayer.ts new file mode 100644 index 000000000..b0ca68656 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/DataProviderFramework/visualization/makeSeismicFenceMeshLayer.ts @@ -0,0 +1,278 @@ +import { Layer } from "@deck.gl/core"; +import * as bbox from "@lib/utils/bbox"; +import { Geometry, ShapeType, degreesToRadians } from "@lib/utils/geometry"; +import { rotatePoint2Around } from "@lib/utils/vec2"; +import * as vec3 from "@lib/utils/vec3"; +import { SeismicSliceData_trans } from "@modules/3DViewerNew/settings/queries/queryDataTransforms"; +import { SeismicFenceMeshLayer } from "@modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/SeismicFenceMeshLayer"; + +import type { RealizationSeismicDepthSliceStoredData } from "../customDataProviderImplementations/RealizationSeismicDepthProvider"; +import type { TransformerArgs } from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { makeColorMapFunctionFromColorScale } from "@modules/_shared/DataProviderFramework/visualization/utils/colors"; + +export enum Plane { + CROSSLINE = "CROSSLINE", + INLINE = "INLINE", + DEPTH = "DEPTH", +} + +function predictDepthSliceGeometry({ + getSetting, + getStoredData, +}: TransformerArgs): Geometry | null { + const attribute = getSetting(Setting.ATTRIBUTE); + const timeOrInterval = getSetting(Setting.TIME_OR_INTERVAL); + const seismicDepthSliceNumber = getSetting(Setting.SEISMIC_DEPTH_SLICE); + const seismicCubeMeta = getStoredData("seismicCubeMeta"); + + if (!seismicCubeMeta || seismicDepthSliceNumber === null) { + return null; + } + + const meta = seismicCubeMeta.find( + (m) => m.seismicAttribute === attribute && m.isoDateOrInterval === timeOrInterval, + ); + + if (!meta) { + return null; + } + + const xmin = meta.spec.xOrigin; + const xmax = meta.spec.xOrigin + meta.spec.xInc * (meta.spec.numCols - 1); + + const ymin = meta.spec.yOrigin; + const ymax = meta.spec.yOrigin + meta.spec.yInc * meta.spec.yFlip * (meta.spec.numRows - 1); + + const zmin = seismicDepthSliceNumber; + const zmax = zmin; + + const maxXY = { x: xmax, y: ymax }; + const maxX = { x: xmax, y: ymin }; + const maxY = { x: xmin, y: ymax }; + const origin = { x: meta.spec.xOrigin, y: meta.spec.yOrigin }; + + const rotatedMaxXY = rotatePoint2Around(maxXY, origin, degreesToRadians(meta.spec.rotationDeg)); + const rotatedMaxX = rotatePoint2Around(maxX, origin, degreesToRadians(meta.spec.rotationDeg)); + const rotatedMaxY = rotatePoint2Around(maxY, origin, degreesToRadians(meta.spec.rotationDeg)); + + const boundingBox = bbox.create( + vec3.create(Math.min(origin.x, rotatedMaxXY.x), Math.min(origin.y, rotatedMaxXY.y), zmin), + vec3.create(Math.max(origin.x, rotatedMaxXY.x), Math.max(origin.y, rotatedMaxXY.y), zmax), + ); + + const geometry: Geometry = { + shapes: [ + { + type: ShapeType.BOX, + centerPoint: vec3.create((origin.x + rotatedMaxXY.x) / 2, (origin.y + rotatedMaxXY.y) / 2, zmin), + dimensions: { + width: Math.abs(rotatedMaxX.x - origin.x), + height: Math.abs(rotatedMaxY.y - origin.y), + depth: 0, + }, + normalizedEdgeVectors: { + u: vec3.normalize(vec3.create(rotatedMaxX.x - origin.x, rotatedMaxX.y - origin.y, 0)), + v: vec3.normalize(vec3.create(rotatedMaxY.x - origin.x, rotatedMaxY.y - origin.y, 0)), + }, + }, + ], + boundingBox, + }; + + return geometry; +} + +function predictCrosslineGeometry({ + getSetting, + getStoredData, +}: TransformerArgs): Geometry | null { + const attribute = getSetting(Setting.ATTRIBUTE); + const timeOrInterval = getSetting(Setting.TIME_OR_INTERVAL); + const seismicCrosslineNumber = getSetting(Setting.SEISMIC_CROSSLINE); + const seismicCubeMeta = getStoredData("seismicCubeMeta"); + + if (!seismicCubeMeta || seismicCrosslineNumber === null) { + return null; + } + + const meta = seismicCubeMeta.find( + (m) => m.seismicAttribute === attribute && m.isoDateOrInterval === timeOrInterval, + ); + + if (!meta) { + return null; + } + + const xmin = meta.spec.xOrigin; + const xmax = meta.spec.xOrigin + meta.spec.xInc * (meta.spec.numCols - 1); + + const ymin = meta.spec.yOrigin + meta.spec.yInc * meta.spec.yFlip * seismicCrosslineNumber; + const ymax = ymin; + + const zmin = meta.spec.zOrigin; + const zmax = meta.spec.zOrigin + meta.spec.zInc * meta.spec.zFlip * (meta.spec.numLayers - 1); + + const maxXY = { x: xmax, y: ymax }; + const minXY = { x: xmin, y: ymin }; + const origin = { x: meta.spec.xOrigin, y: meta.spec.yOrigin }; + + const rotatedMinXY = rotatePoint2Around(minXY, origin, degreesToRadians(meta.spec.rotationDeg)); + const rotatedMaxXY = rotatePoint2Around(maxXY, origin, degreesToRadians(meta.spec.rotationDeg)); + + const geometry: Geometry = { + shapes: [ + { + type: ShapeType.BOX, + centerPoint: vec3.create( + (rotatedMinXY.x + rotatedMaxXY.x) / 2, + (rotatedMinXY.y + rotatedMaxXY.y) / 2, + (zmin + zmax) / 2, + ), + dimensions: { + width: vec3.length( + vec3.create(rotatedMaxXY.x - rotatedMinXY.x, rotatedMaxXY.y - rotatedMinXY.y, 0), + ), + height: Math.abs(zmax - zmin), + depth: 0, + }, + normalizedEdgeVectors: { + u: vec3.normalize(vec3.create(rotatedMaxXY.x - rotatedMinXY.x, rotatedMaxXY.y - rotatedMinXY.y, 0)), + v: vec3.create(0, 0, 1), + }, + }, + ], + boundingBox: bbox.create( + vec3.create(Math.min(rotatedMinXY.x, rotatedMaxXY.x), Math.min(rotatedMinXY.y, rotatedMaxXY.y), zmin), + vec3.create(Math.max(rotatedMinXY.x, rotatedMaxXY.x), Math.max(rotatedMinXY.y, rotatedMaxXY.y), zmax), + ), + }; + + return geometry; +} + +function predictInlineGeometry({ + getSetting, + getStoredData, +}: TransformerArgs): Geometry | null { + const attribute = getSetting(Setting.ATTRIBUTE); + const timeOrInterval = getSetting(Setting.TIME_OR_INTERVAL); + const seismicInlineNumber = getSetting(Setting.SEISMIC_INLINE); + const seismicCubeMeta = getStoredData("seismicCubeMeta"); + + if (!seismicCubeMeta || seismicInlineNumber === null) { + return null; + } + + const meta = seismicCubeMeta.find( + (m) => m.seismicAttribute === attribute && m.isoDateOrInterval === timeOrInterval, + ); + + if (!meta) { + return null; + } + + const xmin = meta.spec.xOrigin + meta.spec.yInc * seismicInlineNumber; + const xmax = xmin; + + const ymin = meta.spec.yOrigin; + const ymax = meta.spec.yOrigin + meta.spec.yInc * meta.spec.yFlip * (meta.spec.numRows - 1); + + const zmin = meta.spec.zOrigin; + const zmax = meta.spec.zOrigin + meta.spec.zInc * meta.spec.zFlip * (meta.spec.numLayers - 1); + + const maxXY = { x: xmax, y: ymax }; + const minXY = { x: xmin, y: ymin }; + const origin = { x: meta.spec.xOrigin, y: meta.spec.yOrigin }; + + const rotatedMinXY = rotatePoint2Around(minXY, origin, (meta.spec.rotationDeg / 180.0) * Math.PI); + const rotatedMaxXY = rotatePoint2Around(maxXY, origin, (meta.spec.rotationDeg / 180.0) * Math.PI); + + const geometry: Geometry = { + shapes: [ + { + type: ShapeType.BOX, + centerPoint: vec3.create( + (rotatedMinXY.x + rotatedMaxXY.x) / 2, + (rotatedMinXY.y + rotatedMaxXY.y) / 2, + (zmin + zmax) / 2, + ), + dimensions: { + width: vec3.length( + vec3.create(rotatedMaxXY.x - rotatedMinXY.x, rotatedMaxXY.y - rotatedMinXY.y, 0), + ), + height: Math.abs(zmax - zmin), + depth: 0, + }, + normalizedEdgeVectors: { + u: vec3.normalize(vec3.create(rotatedMaxXY.x - rotatedMinXY.x, rotatedMaxXY.y - rotatedMinXY.y, 0)), + v: vec3.create(0, 0, 1), + }, + }, + ], + boundingBox: bbox.create( + vec3.create(Math.min(rotatedMinXY.x, rotatedMaxXY.x), Math.min(rotatedMinXY.y, rotatedMaxXY.y), zmin), + vec3.create(Math.max(rotatedMinXY.x, rotatedMaxXY.x), Math.max(rotatedMinXY.y, rotatedMaxXY.y), zmax), + ), + }; + + return geometry; +} + +export function makeSeismicFenceMeshLayerFunction(plane: Plane) { + return function makeSeismicFenceMeshLayer( + args: TransformerArgs, + ): Layer | null { + const { id, name, getData, getSetting, isLoading } = args; + const data = getData(); + const colorScale = getSetting("colorScale")?.colorScale; + + if (!data) { + return null; + } + + let bbox: number[][] = [ + [data.bbox_utm[0][0], data.bbox_utm[0][1], data.u_min], + [data.bbox_utm[1][0], data.bbox_utm[1][1], data.u_min], + [data.bbox_utm[0][0], data.bbox_utm[0][1], data.u_max], + [data.bbox_utm[1][0], data.bbox_utm[1][1], data.u_max], + ]; + + let predictedGeometry: Geometry | null = null; + + if (plane === Plane.DEPTH) { + const seismicDepthSlice = getSetting(Setting.SEISMIC_DEPTH_SLICE); + bbox = [ + [data.bbox_utm[0][0], data.bbox_utm[0][1], seismicDepthSlice], + [data.bbox_utm[3][0], data.bbox_utm[3][1], seismicDepthSlice], + [data.bbox_utm[1][0], data.bbox_utm[1][1], seismicDepthSlice], + [data.bbox_utm[2][0], data.bbox_utm[2][1], seismicDepthSlice], + ]; + + predictedGeometry = predictDepthSliceGeometry(args); + } else if (plane === Plane.CROSSLINE) { + predictedGeometry = predictCrosslineGeometry(args); + } else if (plane === Plane.INLINE) { + predictedGeometry = predictInlineGeometry(args); + } + + return new SeismicFenceMeshLayer({ + id, + name, + data: { + sections: [ + { + boundingBox: bbox, + properties: data.dataFloat32Arr, + numSamplesU: data.u_num_samples, + numSamplesV: data.v_num_samples, + }, + ], + }, + colorMapFunction: makeColorMapFunctionFromColorScale(colorScale, data.value_min, data.value_max, false), + zIncreaseDownwards: true, + isLoading, + loadingGeometry: predictedGeometry ?? undefined, + }); + }; +} diff --git a/frontend/src/modules/3DViewerNew/interfaces.ts b/frontend/src/modules/3DViewerNew/interfaces.ts new file mode 100644 index 000000000..64cc6ce89 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/interfaces.ts @@ -0,0 +1,29 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; + +import { providerManagerAtom, preferredViewLayoutAtom } from "./settings/atoms/baseAtoms"; +import { PreferredViewLayout } from "./types"; + +import { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { selectedFieldIdentifierAtom } from "./settings/atoms/derivedAtoms"; + +export type SettingsToViewInterface = { + layerManager: DataProviderManager | null; + fieldIdentifier: string | null; + preferredViewLayout: PreferredViewLayout; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + layerManager: (get) => { + return get(providerManagerAtom); + }, + fieldIdentifier: (get) => { + return get(selectedFieldIdentifierAtom); + }, + preferredViewLayout: (get) => { + return get(preferredViewLayoutAtom); + }, +}; diff --git a/frontend/src/modules/3DViewerNew/loadModule.tsx b/frontend/src/modules/3DViewerNew/loadModule.tsx new file mode 100644 index 000000000..8bb478e21 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/loadModule.tsx @@ -0,0 +1,16 @@ +import { ModuleRegistry } from "@framework/ModuleRegistry"; + +import type { Interfaces } from "./interfaces"; +import { settingsToViewInterfaceInitialization } from "./interfaces"; +import { MODULE_NAME } from "./registerModule"; +import { Settings } from "./settings/settings"; +import { View } from "./view/view"; + +import "./DataProviderFramework/customDataProviderImplementations/registerAllDataProviders"; + +const module = ModuleRegistry.initModule(MODULE_NAME, { + settingsToViewInterfaceInitialization, +}); + +module.settingsFC = Settings; +module.viewFC = View; diff --git a/frontend/src/modules/3DViewerNew/preview.jpg b/frontend/src/modules/3DViewerNew/preview.jpg new file mode 100644 index 000000000..d1e2273e9 Binary files /dev/null and b/frontend/src/modules/3DViewerNew/preview.jpg differ diff --git a/frontend/src/modules/3DViewerNew/preview.tsx b/frontend/src/modules/3DViewerNew/preview.tsx new file mode 100644 index 000000000..831e956e2 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/preview.tsx @@ -0,0 +1,6 @@ +import { DrawPreviewFunc } from "@framework/Preview"; +import previewImg from "./preview.jpg"; + +export const preview: DrawPreviewFunc = function (width: number, height: number) { + return ; +}; diff --git a/frontend/src/modules/3DViewerNew/registerModule.ts b/frontend/src/modules/3DViewerNew/registerModule.ts new file mode 100644 index 000000000..cb06a94f5 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/registerModule.ts @@ -0,0 +1,29 @@ +import { ModuleCategory, ModuleDevState } from "@framework/Module"; +import { ModuleDataTagId } from "@framework/ModuleDataTags"; +import { ModuleRegistry } from "@framework/ModuleRegistry"; + +import { Interfaces } from "./interfaces"; +import { preview } from "./preview"; + +export const MODULE_NAME: string = "3DViewerNew"; + +const description = "Generic 3D viewer for grid, surfaces, and wells."; + +ModuleRegistry.registerModule({ + moduleName: MODULE_NAME, + category: ModuleCategory.MAIN, + devState: ModuleDevState.DEV, + defaultTitle: "3D Viewer (new)", + preview, + description, + dataTagIds: [ + ModuleDataTagId.SURFACE, + ModuleDataTagId.DRILLED_WELLS, + ModuleDataTagId.SEISMIC, + ModuleDataTagId.GRID3D, + ModuleDataTagId.POLYGONS, + ], + onInstanceUnload: (instanceId) => { + window.localStorage.removeItem(`${instanceId}-settings`); + }, +}); diff --git a/frontend/src/modules/3DViewerNew/settings/atoms/baseAtoms.ts b/frontend/src/modules/3DViewerNew/settings/atoms/baseAtoms.ts new file mode 100644 index 000000000..6c7f4e9ed --- /dev/null +++ b/frontend/src/modules/3DViewerNew/settings/atoms/baseAtoms.ts @@ -0,0 +1,8 @@ +import { PreferredViewLayout } from "@modules/2DViewer/types"; +import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; + +import { atom } from "jotai"; + +export const userSelectedFieldIdentifierAtom = atom(null); +export const providerManagerAtom = atom(null); +export const preferredViewLayoutAtom = atom(PreferredViewLayout.VERTICAL); diff --git a/frontend/src/modules/3DViewerNew/settings/atoms/derivedAtoms.ts b/frontend/src/modules/3DViewerNew/settings/atoms/derivedAtoms.ts new file mode 100644 index 000000000..ae64f8ba7 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/settings/atoms/derivedAtoms.ts @@ -0,0 +1,19 @@ +import { EnsembleSetAtom } from "@framework/GlobalAtoms"; + +import { atom } from "jotai"; + +import { userSelectedFieldIdentifierAtom } from "./baseAtoms"; + +export const selectedFieldIdentifierAtom = atom((get) => { + const ensembleSet = get(EnsembleSetAtom); + const userSelectedField = get(userSelectedFieldIdentifierAtom); + + if ( + !userSelectedField || + !ensembleSet.getRegularEnsembleArray().some((ens) => ens.getFieldIdentifier() === userSelectedField) + ) { + return ensembleSet.getRegularEnsembleArray().at(0)?.getFieldIdentifier() ?? null; + } + + return userSelectedField; +}); diff --git a/frontend/src/modules/3DViewerNew/settings/components/dataProviderManagerComponentWrapper.tsx b/frontend/src/modules/3DViewerNew/settings/components/dataProviderManagerComponentWrapper.tsx new file mode 100644 index 000000000..5cf3aaec3 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/settings/components/dataProviderManagerComponentWrapper.tsx @@ -0,0 +1,420 @@ +import type React from "react"; + +import { Icon } from "@equinor/eds-core-react"; +import { color_palette, fault, grid_layer, settings, surface_layer, wellbore } from "@equinor/eds-icons"; +import type { WorkbenchSession } from "@framework/WorkbenchSession"; +import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { Menu } from "@lib/components/Menu"; +import { MenuButton } from "@lib/components/MenuButton"; +import { MenuHeading } from "@lib/components/MenuHeading"; +import { MenuItem } from "@lib/components/MenuItem"; +import { PreferredViewLayout } from "@modules/2DViewer/types"; +import { GroupType } from "@modules/_shared/LayerFramework/groups/groupTypes"; +import { usePublishSubscribeTopicValue } from "@modules/_shared/utils/PublishSubscribeDelegate"; +import { Dropdown } from "@mui/base"; +import { + Check, + Panorama, + SettingsApplications, + Settings as SettingsIcon, + TableRowsOutlined, + ViewColumnOutlined, +} from "@mui/icons-material"; + +import { useAtom } from "jotai"; + +import { preferredViewLayoutAtom } from "../atoms/baseAtoms"; +import type { DataProviderManager } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { GroupDelegateTopic, type GroupDelegate } from "@modules/_shared/DataProviderFramework/delegates/GroupDelegate"; +import { GroupRegistry } from "@modules/_shared/DataProviderFramework/groups/GroupRegistry"; +import { DeltaSurface } from "@modules/_shared/DataProviderFramework/framework/DeltaSurface/DeltaSurface"; +import { SettingsGroup } from "@modules/_shared/DataProviderFramework/framework/SettingsGroup/SettingsGroup"; +import { SharedSetting } from "@modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSetting"; +import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; +import { DataProviderRegistry } from "@modules/_shared/DataProviderFramework/dataProviders/DataProviderRegistry"; +import { DataProviderType } from "@modules/_shared/DataProviderFramework/dataProviders/dataProviderTypes"; +import { + instanceofItemGroup, + type Item, + type ItemGroup, +} from "@modules/_shared/DataProviderFramework/interfacesAndTypes/entities"; +import { DataProvider } from "@modules/_shared/DataProviderFramework/framework/DataProvider/DataProvider"; +import { RealizationSurfaceProvider } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSurfaceProvider"; +import { StatisticalSurfaceProvider } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/StatisticalSurfaceProvider"; +import { ObservedSurfaceProvider } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/ObservedSurfaceProvider"; +import { Group } from "@modules/_shared/DataProviderFramework/framework/Group/Group"; +import { DataProviderManagerComponent } from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManagerComponent"; +import type { ActionGroup } from "@modules/_shared/DataProviderFramework/Actions"; + +export type LayerManagerComponentWrapperProps = { + layerManager: DataProviderManager; + workbenchSession: WorkbenchSession; + workbenchSettings: WorkbenchSettings; +}; + +export function LayerManagerComponentWrapper(props: LayerManagerComponentWrapperProps): React.ReactNode { + const colorSet = props.workbenchSettings.useColorSet(); + const [preferredViewLayout, setPreferredViewLayout] = useAtom(preferredViewLayoutAtom); + + const groupDelegate = props.layerManager.getGroupDelegate(); + usePublishSubscribeTopicValue(groupDelegate, GroupDelegateTopic.CHILDREN); + + function handleLayerAction(identifier: string, groupDelegate: GroupDelegate) { + switch (identifier) { + case "view": + groupDelegate.appendChild( + GroupRegistry.makeGroup(GroupType.VIEW, props.layerManager, colorSet.getNextColor()), + ); + return; + case "delta-surface": + groupDelegate.appendChild(new DeltaSurface("Delta surface", props.layerManager)); + return; + case "settings-group": + groupDelegate.appendChild(new SettingsGroup("Settings group", props.layerManager)); + return; + case "color-scale": + groupDelegate.appendChild(new SharedSetting(Setting.COLOR_SCALE, null, props.layerManager)); + return; + case "statistical-surface": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider(DataProviderType.STATISTICAL_SURFACE_3D, props.layerManager), + ); + return; + case "realization-surface": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider(DataProviderType.REALIZATION_SURFACE_3D, props.layerManager), + ); + return; + case "realization-polygons": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider(DataProviderType.REALIZATION_POLYGONS, props.layerManager), + ); + return; + case "drilled-wellbore-trajectories": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider( + DataProviderType.DRILLED_WELL_TRAJECTORIES, + props.layerManager, + ), + ); + return; + case "drilled-wellbore-picks": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider(DataProviderType.DRILLED_WELLBORE_PICKS, props.layerManager), + ); + return; + case "intersection-realization-grid": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider( + DataProviderType.INTERSECTION_REALIZATION_GRID, + props.layerManager, + ), + ); + return; + case "realization-grid": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider(DataProviderType.REALIZATION_GRID, props.layerManager), + ); + return; + case "realization-seismic-inline": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider( + DataProviderType.REALIZATION_SEISMIC_INLINE, + props.layerManager, + ), + ); + return; + case "realization-seismic-crossline": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider( + DataProviderType.REALIZATION_SEISMIC_CROSSLINE, + props.layerManager, + ), + ); + return; + case "realization-seismic-depth-slice": + groupDelegate.prependChild( + DataProviderRegistry.makeDataProvider( + DataProviderType.REALIZATION_SEISMIC_DEPTH_SLICE, + props.layerManager, + ), + ); + return; + case "ensemble": + groupDelegate.appendChild(new SharedSetting(Setting.ENSEMBLE, null, props.layerManager)); + return; + case "realization": + groupDelegate.appendChild(new SharedSetting(Setting.REALIZATION, null, props.layerManager)); + return; + case "surface-name": + groupDelegate.appendChild(new SharedSetting(Setting.SURFACE_NAME, null, props.layerManager)); + return; + case "attribute": + groupDelegate.appendChild(new SharedSetting(Setting.ATTRIBUTE, null, props.layerManager)); + return; + case "Date": + groupDelegate.appendChild(new SharedSetting(Setting.TIME_OR_INTERVAL, null, props.layerManager)); + return; + } + } + + function checkIfItemMoveAllowed(movedItem: Item, destinationItem: ItemGroup): boolean { + if (destinationItem instanceof DeltaSurface) { + if ( + movedItem instanceof DataProvider && + !( + movedItem instanceof RealizationSurfaceProvider || + movedItem instanceof StatisticalSurfaceProvider || + movedItem instanceof ObservedSurfaceProvider + ) + ) { + return false; + } + + if (instanceofItemGroup(movedItem)) { + return false; + } + + if (destinationItem.getGroupDelegate().findChildren((item) => item instanceof DataProvider).length >= 2) { + return false; + } + } + + return true; + } + + function makeActionsForGroup(group: ItemGroup): ActionGroup[] { + const hasView = + groupDelegate.getDescendantItems((item) => item instanceof Group && item.getGroupType() === GroupType.VIEW) + .length > 0; + + const hasViewAncestor = + group + .getGroupDelegate() + .getAncestors((item) => item instanceof Group && item.getGroupType() === GroupType.VIEW).length > 0; + const actions: ActionGroup[] = []; + + if (!hasView) { + return INITIAL_ACTIONS; + } + + const groupActions: ActionGroup = { + label: "Groups", + children: [], + }; + + if (!hasViewAncestor) { + groupActions.children.push({ + identifier: "view", + icon: , + label: "View", + }); + } + + groupActions.children.push({ + identifier: "settings-group", + icon: , + label: "Settings group", + }); + + actions.push(groupActions); + actions.push(...ACTIONS); + + return actions; + } + + return ( + + + + + + Preferred view layout + setPreferredViewLayout(PreferredViewLayout.HORIZONTAL)} + > + Horizontal + + setPreferredViewLayout(PreferredViewLayout.VERTICAL)} + > + Vertical + + + + } + groupActions={makeActionsForGroup} + onAction={handleLayerAction} + isMoveAllowed={checkIfItemMoveAllowed} + /> + ); +} + +type ViewLayoutMenuItemProps = { + checked: boolean; + onClick: () => void; + children: React.ReactNode; +}; + +function ViewLayoutMenuItem(props: ViewLayoutMenuItemProps): React.ReactNode { + return ( + +
+
{props.checked && }
+
{props.children}
+
+
+ ); +} + +const INITIAL_ACTIONS: ActionGroup[] = [ + { + label: "Groups", + children: [ + { + identifier: "view", + icon: , + label: "View", + }, + { + identifier: "settings-group", + icon: , + label: "Settings group", + }, + ], + }, +]; + +const ACTIONS: ActionGroup[] = [ + { + label: "Layers", + children: [ + { + label: "Wells", + children: [ + { + identifier: "drilled-wellbore-trajectories", + icon: , + label: "Drilled Wellbore Trajectories", + }, + { + identifier: "drilled-wellbore-picks", + icon: , + label: "Drilled Wellbore Picks", + }, + ], + }, + { + label: "Intersection", + children: [ + { + identifier: "intersection-realization-grid", + icon: , + label: "Intersection Realization Grid", + }, + ], + }, + { + label: "Reservoir grid", + children: [ + { + identifier: "realization-grid", + icon: , + label: "Realization Grid", + }, + ], + }, + { + label: "Surface", + children: [ + { + identifier: "statistical-surface", + icon: , + label: "Statistical Surface", + }, + { + identifier: "realization-surface", + icon: , + label: "Realization Surface", + }, + ], + }, + { + label: "Polygons", + children: [ + { + identifier: "realization-polygons", + icon: , + label: "Realization Polygons", + }, + ], + }, + { + label: "Seismic", + children: [ + { + label: "Synthetic", + children: [ + { + identifier: "realization-seismic-inline", + icon: , + label: "Realization Inline", + }, + { + identifier: "realization-seismic-crossline", + icon: , + label: "Realization Crossline", + }, + { + identifier: "realization-seismic-depth-slice", + icon: , + label: "Realization Depth Slice", + }, + ], + }, + ], + }, + ], + }, + { + label: "Shared Settings", + children: [ + { + identifier: "ensemble", + icon: , + label: "Ensemble", + }, + { + identifier: "realization", + icon: , + label: "Realization", + }, + { + identifier: "attribute", + icon: , + label: "Attribute", + }, + { + identifier: "Date", + icon: , + label: "Date", + }, + ], + }, + { + label: "Utilities", + children: [ + { + identifier: "color-scale", + icon: , + label: "Color scale", + }, + ], + }, +]; diff --git a/frontend/src/modules/3DViewerNew/settings/queries/queryDataTransforms.ts b/frontend/src/modules/3DViewerNew/settings/queries/queryDataTransforms.ts new file mode 100644 index 000000000..6dc734f2a --- /dev/null +++ b/frontend/src/modules/3DViewerNew/settings/queries/queryDataTransforms.ts @@ -0,0 +1,20 @@ +import { SeismicSliceData_api } from "@api"; +import { b64DecodeFloatArrayToFloat32 } from "@modules/_shared/base64"; + +export type SeismicSliceData_trans = Omit & { + dataFloat32Arr: Float32Array; +}; + +export function transformSeismicSlice(apiData: SeismicSliceData_api): SeismicSliceData_trans { + const startTS = performance.now(); + + const { slice_traces_b64arr, ...untransformedData } = apiData; + const dataFloat32Arr = b64DecodeFloatArrayToFloat32(slice_traces_b64arr); + + console.debug(`transformSeismicSlice() took: ${(performance.now() - startTS).toFixed(1)}ms`); + + return { + ...untransformedData, + dataFloat32Arr: dataFloat32Arr, + }; +} diff --git a/frontend/src/modules/3DViewerNew/settings/settings.tsx b/frontend/src/modules/3DViewerNew/settings/settings.tsx new file mode 100644 index 000000000..480497e8f --- /dev/null +++ b/frontend/src/modules/3DViewerNew/settings/settings.tsx @@ -0,0 +1,152 @@ +import React from "react"; + +import { ModuleSettingsProps } from "@framework/Module"; +import { useEnsembleSet } from "@framework/WorkbenchSession"; +import { FieldDropdown } from "@framework/components/FieldDropdown"; +import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; +import { useQueryClient } from "@tanstack/react-query"; + +import { useAtom, useAtomValue, useSetAtom } from "jotai"; + +import { providerManagerAtom, preferredViewLayoutAtom, userSelectedFieldIdentifierAtom } from "./atoms/baseAtoms"; +import { selectedFieldIdentifierAtom } from "./atoms/derivedAtoms"; +import { LayerManagerComponentWrapper } from "./components/dataProviderManagerComponentWrapper"; +import { + DataProviderManager, + DataProviderManagerTopic, +} from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { GroupDelegateTopic } from "@modules/_shared/DataProviderFramework/delegates/GroupDelegate"; + +export function Settings(props: ModuleSettingsProps): React.ReactNode { + const ensembleSet = useEnsembleSet(props.workbenchSession); + const queryClient = useQueryClient(); + + const [layerManager, setLayerManager] = useAtom(providerManagerAtom); + + const fieldIdentifier = useAtomValue(selectedFieldIdentifierAtom); + const setFieldIdentifier = useSetAtom(userSelectedFieldIdentifierAtom); + const [preferredViewLayout, setPreferredViewLayout] = useAtom(preferredViewLayoutAtom); + + const persistState = React.useCallback( + function persistLayerManagerState() { + if (!layerManager) { + return; + } + + const serializedState = { + layerManager: layerManager.serializeState(), + fieldIdentifier, + preferredViewLayout, + }; + window.localStorage.setItem( + `${props.settingsContext.getInstanceIdString()}-settings`, + JSON.stringify(serializedState), + ); + }, + [layerManager, fieldIdentifier, preferredViewLayout, props.settingsContext], + ); + + const applyPersistedState = React.useCallback( + function applyPersistedState(layerManager: DataProviderManager) { + const serializedState = window.localStorage.getItem( + `${props.settingsContext.getInstanceIdString()}-settings`, + ); + if (!serializedState) { + return; + } + + const parsedState = JSON.parse(serializedState); + if (parsedState.fieldIdentifier) { + setFieldIdentifier(parsedState.fieldIdentifier); + } + if (parsedState.preferredViewLayout) { + setPreferredViewLayout(parsedState.preferredViewLayout); + } + + if (parsedState.layerManager) { + if (!layerManager) { + return; + } + layerManager.deserializeState(parsedState.layerManager); + } + }, + [setFieldIdentifier, setPreferredViewLayout, props.settingsContext], + ); + + React.useEffect( + function onMountEffect() { + const newLayerManager = new DataProviderManager( + props.workbenchSession, + props.workbenchSettings, + queryClient, + ); + setLayerManager(newLayerManager); + + applyPersistedState(newLayerManager); + + return function onUnmountEffect() { + newLayerManager.beforeDestroy(); + }; + }, + [setLayerManager, props.workbenchSession, props.workbenchSettings, queryClient, applyPersistedState], + ); + + React.useEffect( + function onLayerManagerChangeEffect() { + if (!layerManager) { + return; + } + + persistState(); + + const unsubscribeDataRev = layerManager + .getPublishSubscribeDelegate() + .makeSubscriberFunction(DataProviderManagerTopic.DATA_REVISION)(persistState); + + const unsubscribeExpands = layerManager + .getGroupDelegate() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(GroupDelegateTopic.CHILDREN_EXPANSION_STATES)(persistState); + + return function onUnmountEffect() { + layerManager.beforeDestroy(); + unsubscribeDataRev(); + unsubscribeExpands(); + }; + }, + [layerManager, props.workbenchSession, props.workbenchSettings, persistState], + ); + + React.useEffect( + function onFieldIdentifierChangedEffect() { + if (!layerManager) { + return; + } + layerManager.updateGlobalSetting("fieldId", fieldIdentifier); + }, + [fieldIdentifier, layerManager], + ); + + function handleFieldChange(fieldId: string | null) { + setFieldIdentifier(fieldId); + if (!layerManager) { + return; + } + layerManager.updateGlobalSetting("fieldId", fieldId); + } + + return ( +
+ + + + {layerManager && ( + + )} +
+ ); +} diff --git a/frontend/src/modules/3DViewerNew/types.ts b/frontend/src/modules/3DViewerNew/types.ts new file mode 100644 index 000000000..a457c6616 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/types.ts @@ -0,0 +1,4 @@ +export enum PreferredViewLayout { + HORIZONTAL = "horizontal", + VERTICAL = "vertical", +} diff --git a/frontend/src/modules/3DViewerNew/view/components/ContextMenu.tsx b/frontend/src/modules/3DViewerNew/view/components/ContextMenu.tsx new file mode 100644 index 000000000..2eea7315f --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/components/ContextMenu.tsx @@ -0,0 +1,63 @@ +import React from "react"; + +import { usePublishSubscribeTopicValue } from "@modules/_shared/utils/PublishSubscribeDelegate"; + +import { isEqual } from "lodash"; + +import { + ContextMenu as ContextMenuType, + DeckGlInstanceManager, + DeckGlInstanceManagerTopic, +} from "../utils/DeckGlInstanceManager"; + +export type ContextMenuProps = { + deckGlManager: DeckGlInstanceManager; +}; + +export function ContextMenu(props: ContextMenuProps): React.ReactNode { + const [visible, setVisible] = React.useState(false); + const [prevContextMenu, setPrevContextMenu] = React.useState(null); + const contextMenu = usePublishSubscribeTopicValue(props.deckGlManager, DeckGlInstanceManagerTopic.CONTEXT_MENU); + + React.useEffect(function handleMount() { + function hideContextMenu() { + setVisible(false); + } + + window.addEventListener("blur", hideContextMenu); + + return function handleUnmount() { + window.removeEventListener("blur", hideContextMenu); + }; + }, []); + + if (!isEqual(prevContextMenu, contextMenu)) { + setPrevContextMenu(contextMenu); + setVisible(true); + } + + if (!contextMenu || !visible || !contextMenu.items.length) { + return null; + } + + return ( +
+ {contextMenu.items.map((item, index) => ( +
{ + item.onClick(); + setVisible(false); + }} + > + {item.icon ? React.cloneElement(item.icon, { fontSize: "small" }) : null} + {item.label} +
+ ))} +
+ ); +} diff --git a/frontend/src/modules/3DViewerNew/view/components/InteractionWrapper.tsx b/frontend/src/modules/3DViewerNew/view/components/InteractionWrapper.tsx new file mode 100644 index 000000000..6d77c64b1 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/components/InteractionWrapper.tsx @@ -0,0 +1,163 @@ +import React from "react"; + +import { Layer as DeckGlLayer } from "@deck.gl/core"; +import { DeckGLRef } from "@deck.gl/react"; +import { useIntersectionPolylines } from "@framework/UserCreatedItems"; +import { IntersectionPolylinesEvent } from "@framework/userCreatedItems/IntersectionPolylines"; +import { usePublishSubscribeTopicValue } from "@modules/_shared/utils/PublishSubscribeDelegate"; +import { AxesLayer } from "@webviz/subsurface-viewer/dist/layers"; + +import { converter } from "culori"; + +import { ContextMenu } from "./ContextMenu"; +import { ReadoutWrapperProps, ReadoutWrapper } from "./ReadoutWrapper"; +import { Toolbar } from "./Toolbar"; + +import { DeckGlInstanceManager, DeckGlInstanceManagerTopic } from "../utils/DeckGlInstanceManager"; +import { Polyline, PolylinesPlugin, PolylinesPluginTopic } from "../utils/PolylinesPlugin"; + +export type InteractionWrapperProps = Omit< + ReadoutWrapperProps, + "deckGlManager" | "triggerHome" | "verticalScale" | "deckGlRef" +> & { + fieldIdentifier: string | null; +}; + +export function InteractionWrapper(props: InteractionWrapperProps): React.ReactNode { + const deckGlRef = React.useRef(null); + deckGlRef.current?.deck?.needsRedraw; + const [deckGlManager, setDeckGlManager] = React.useState( + new DeckGlInstanceManager(deckGlRef.current), + ); + const [polylinesPlugin, setPolylinesPlugin] = React.useState(new PolylinesPlugin(deckGlManager)); + + usePublishSubscribeTopicValue(deckGlManager, DeckGlInstanceManagerTopic.REDRAW); + + const intersectionPolylines = useIntersectionPolylines(props.workbenchSession); + const colorSet = props.workbenchSettings.useColorSet(); + + const colorGenerator = React.useCallback( + function* colorGenerator() { + const colors: [number, number, number][] = colorSet.getColorArray().map((c) => { + const rgb = converter("rgb")(c); + if (!rgb) { + return [0, 0, 0]; + } + return [rgb.r * 255, rgb.g * 255, rgb.b * 255]; + }); + let i = 0; + while (true) { + yield colors[i % colors.length]; + i++; + } + }, + [colorSet], + ); + + React.useEffect( + function setupDeckGlManager() { + const manager = new DeckGlInstanceManager(deckGlRef.current); + setDeckGlManager(manager); + + const polylinesPlugin = new PolylinesPlugin(manager, colorGenerator()); + polylinesPlugin.setPolylines(intersectionPolylines.getPolylines()); + manager.addPlugin(polylinesPlugin); + setPolylinesPlugin(polylinesPlugin); + + const unsubscribeFromPolylinesPlugin = polylinesPlugin + .getPublishSubscribeDelegate() + .makeSubscriberFunction(PolylinesPluginTopic.EDITING_POLYLINE_ID)(() => { + if (polylinesPlugin.getCurrentEditingPolylineId() === null && props.fieldIdentifier !== null) { + intersectionPolylines.setPolylines( + polylinesPlugin.getPolylines().map((p) => ({ ...p, fieldId: props.fieldIdentifier! })), + ); + } + }); + + const unsubscribeFromIntersectionPolylines = intersectionPolylines.subscribe( + IntersectionPolylinesEvent.CHANGE, + () => { + polylinesPlugin.setPolylines(intersectionPolylines.getPolylines()); + }, + ); + + return function cleanupDeckGlManager() { + manager.beforeDestroy(); + unsubscribeFromPolylinesPlugin(); + unsubscribeFromIntersectionPolylines(); + }; + }, + [intersectionPolylines, colorGenerator], + ); + + const [triggerHomeCounter, setTriggerHomeCounter] = React.useState(0); + const [gridVisible, setGridVisible] = React.useState(false); + const [verticalScale, setVerticalScale] = React.useState(1); + const [polylines, setPolylines] = React.useState([]); + + function handleFitInViewClick() { + setTriggerHomeCounter((prev) => prev + 1); + } + + function handleGridVisibilityChange(visible: boolean) { + setGridVisible(visible); + } + + function handleVerticalScaleChange(value: number) { + setVerticalScale(value); + } + + const activePolylineId = polylinesPlugin.getCurrentEditingPolylineId(); + + const handlePolylineNameChange = React.useCallback( + function handlePolylineNameChange(name: string): void { + if (!activePolylineId) { + return; + } + + setPolylines((prev) => + prev.map((polyline) => { + if (polyline.id === activePolylineId) { + return { + ...polyline, + name, + }; + } + + return polyline; + }), + ); + }, + [activePolylineId], + ); + + let adjustedLayers: DeckGlLayer[] = [...props.layers]; + if (!gridVisible) { + adjustedLayers = adjustedLayers.filter((layer) => !(layer instanceof AxesLayer)); + } + + return ( + <> + p.id === activePolylineId)?.name} + /> + + + + ); +} diff --git a/frontend/src/modules/3DViewerNew/view/components/LayersWrapper.tsx b/frontend/src/modules/3DViewerNew/view/components/LayersWrapper.tsx new file mode 100644 index 000000000..a5c73b4f7 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/components/LayersWrapper.tsx @@ -0,0 +1,260 @@ +import React from "react"; + +import { View as DeckGlView, type Layer } from "@deck.gl/core"; +import type { ViewContext } from "@framework/ModuleContext"; +import { useViewStatusWriter } from "@framework/StatusWriter"; +import type { WorkbenchSession } from "@framework/WorkbenchSession"; +import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { useElementSize } from "@lib/hooks/useElementSize"; +import * as bbox from "@lib/utils/boundingBox"; +import type { Interfaces } from "@modules/2DViewer/interfaces"; +import { PreferredViewLayout } from "@modules/2DViewer/types"; +import { makeDrilledWellTrajectoriesLayer } from "@modules/3DViewerNew/DataProviderFramework/visualization/makeDrilledWellTrajectoriesLayer"; +import { makeIntersectionLayer } from "@modules/3DViewerNew/DataProviderFramework/visualization/makeIntersectionGrid3dLayer"; +import { makeRealizationSurfaceLayer } from "@modules/3DViewerNew/DataProviderFramework/visualization/makeRealizationSurfaceLayer"; +import { + Plane, + makeSeismicFenceMeshLayerFunction, +} from "@modules/3DViewerNew/DataProviderFramework/visualization/makeSeismicFenceMeshLayer"; +import { ColorLegendsContainer } from "@modules/_shared/components/ColorLegendsContainer"; +import { usePublishSubscribeTopicValue } from "@modules/_shared/utils/PublishSubscribeDelegate"; +import type { ViewportType } from "@webviz/subsurface-viewer"; +import { AxesLayer } from "@webviz/subsurface-viewer/dist/layers"; + +import { InteractionWrapper } from "./InteractionWrapper"; + +import { PlaceholderLayer } from "../../../_shared/customDeckGlLayers/PlaceholderLayer"; +import { + VisualizationAssembler, + VisualizationItemType, + type VisualizationTarget, +} from "@modules/_shared/DataProviderFramework/visualization/VisualizationAssembler"; +import { DataProviderType } from "@modules/_shared/DataProviderFramework/dataProviders/dataProviderTypes"; +import { RealizationSurfaceProvider } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationSurfaceProvider"; +import { makeColorScaleAnnotation } from "@modules/2DViewer/DataProviderFramework/annotations/makeColorScaleAnnotation"; +import { StatisticalSurfaceProvider } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/StatisticalSurfaceProvider"; +import { RealizationPolygonsProvider } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationPolygonsProvider"; +import { makeRealizationPolygonsLayer } from "@modules/2DViewer/DataProviderFramework/visualization/makeRealizationPolygonsLayer"; +import { IntersectionRealizationGridProvider } from "@modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider"; +import { RealizationGridProvider } from "@modules/2DViewer/DataProviderFramework/customDataProviderImplementations/RealizationGridProvider"; +import { makeRealizationGridLayer } from "@modules/2DViewer/DataProviderFramework/visualization/makeRealizationGridLayer"; +import { DrilledWellborePicksProvider } from "@modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellborePicksProvider"; +import { makeDrilledWellborePicksLayer } from "@modules/_shared/DataProviderFramework/visualization/deckgl/makeDrilledWellborePicksLayer"; +import { DrilledWellTrajectoriesProvider } from "@modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellTrajectoriesProvider"; +import { RealizationSeismicDepthSliceProvider } from "@modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicDepthProvider"; +import { RealizationSeismicInlineProvider } from "@modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicInlineProvider"; +import { RealizationSeismicCrosslineProvider } from "@modules/3DViewerNew/DataProviderFramework/customDataProviderImplementations/RealizationSeismicCrosslineProvider"; +import { + DataProviderManagerTopic, + type DataProviderManager, +} from "@modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager"; +import { GroupType } from "@modules/_shared/DataProviderFramework/groups/groupTypes"; +import { makeStatisticalSurfaceLayer } from "@modules/2DViewer/DataProviderFramework/visualization/makeStatisticalSurfaceLayer"; + +const VISUALIZATION_ASSEMBLER = new VisualizationAssembler(); + +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.REALIZATION_SURFACE_3D, + RealizationSurfaceProvider, + { + transformToVisualization: makeRealizationSurfaceLayer, + transformToAnnotations: makeColorScaleAnnotation, + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.STATISTICAL_SURFACE_3D, + StatisticalSurfaceProvider, + { + transformToVisualization: makeStatisticalSurfaceLayer, + transformToAnnotations: makeColorScaleAnnotation, + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.REALIZATION_POLYGONS, + RealizationPolygonsProvider, + { + transformToVisualization: makeRealizationPolygonsLayer, + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.INTERSECTION_REALIZATION_GRID, + IntersectionRealizationGridProvider, + { + transformToVisualization: makeIntersectionLayer, + transformToAnnotations: makeColorScaleAnnotation, + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers(DataProviderType.REALIZATION_GRID, RealizationGridProvider, { + transformToVisualization: makeRealizationGridLayer, + transformToAnnotations: makeColorScaleAnnotation, +}); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.DRILLED_WELLBORE_PICKS, + DrilledWellborePicksProvider, + { + transformToVisualization: makeDrilledWellborePicksLayer, + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.DRILLED_WELL_TRAJECTORIES, + DrilledWellTrajectoriesProvider, + { + transformToVisualization: makeDrilledWellTrajectoriesLayer, + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.REALIZATION_SEISMIC_DEPTH_SLICE, + RealizationSeismicDepthSliceProvider, + { + transformToVisualization: makeSeismicFenceMeshLayerFunction(Plane.DEPTH), + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.REALIZATION_SEISMIC_INLINE, + RealizationSeismicInlineProvider, + { + transformToVisualization: makeSeismicFenceMeshLayerFunction(Plane.INLINE), + }, +); +VISUALIZATION_ASSEMBLER.registerDataProviderTransformers( + DataProviderType.REALIZATION_SEISMIC_CROSSLINE, + RealizationSeismicCrosslineProvider, + { + transformToVisualization: makeSeismicFenceMeshLayerFunction(Plane.CROSSLINE), + }, +); + +export type LayersWrapperProps = { + fieldIdentifier: string | null; + layerManager: DataProviderManager; + preferredViewLayout: PreferredViewLayout; + viewContext: ViewContext; + workbenchSession: WorkbenchSession; + workbenchSettings: WorkbenchSettings; +}; + +export function LayersWrapper(props: LayersWrapperProps): React.ReactNode { + const [prevBoundingBox, setPrevBoundingBox] = React.useState(null); + + const mainDivRef = React.useRef(null); + const mainDivSize = useElementSize(mainDivRef); + const statusWriter = useViewStatusWriter(props.viewContext); + + usePublishSubscribeTopicValue(props.layerManager, DataProviderManagerTopic.DATA_REVISION); + + const viewports: ViewportType[] = []; + const deckGlLayers: Layer[] = []; + const viewportAnnotations: React.ReactNode[] = []; + const globalLayerIds: string[] = ["placeholder"]; + + let numLoadingLayers = 0; + + const assemblerProduct = VISUALIZATION_ASSEMBLER.make(props.layerManager); + + const globalAnnotations = assemblerProduct.annotations; + + const numViews = assemblerProduct.children.filter( + (item) => item.itemType === VisualizationItemType.GROUP && item.groupType === GroupType.VIEW, + ).length; + + let numCols = Math.ceil(Math.sqrt(numViews)); + let numRows = Math.ceil(numViews / numCols); + + for (const item of assemblerProduct.children) { + if (item.itemType === VisualizationItemType.GROUP && item.groupType === GroupType.VIEW) { + const layerIds: string[] = []; + for (const child of item.children) { + if (child.itemType === VisualizationItemType.DATA_PROVIDER_VISUALIZATION) { + const layer = child.visualization; + layerIds.push(layer.id); + deckGlLayers.push(layer); + } + } + viewports.push({ + id: item.id, + name: item.name, + isSync: true, + show3D: true, + layerIds, + }); + + viewportAnnotations.push( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + /* @ts-expect-error */ + + "colorScale" in el), ...globalAnnotations]} + height={((mainDivSize.height / 3) * 2) / numCols - 20} + position="left" + /> +
+
+
+
{item.name}
+
+
+ , + ); + } else if (item.itemType === VisualizationItemType.DATA_PROVIDER_VISUALIZATION) { + deckGlLayers.push(item.visualization); + globalLayerIds.push(item.visualization.id); + } + } + + if (props.preferredViewLayout === PreferredViewLayout.HORIZONTAL) { + [numCols, numRows] = [numRows, numCols]; + } + + if (assemblerProduct.combinedBoundingBox !== null) { + if (prevBoundingBox !== null) { + if (!bbox.outerBoxcontainsInnerBox(prevBoundingBox, assemblerProduct.combinedBoundingBox)) { + setPrevBoundingBox(assemblerProduct.combinedBoundingBox); + } + } else { + setPrevBoundingBox(assemblerProduct.combinedBoundingBox); + } + } + + numLoadingLayers = assemblerProduct.numLoadingDataProviders; + statusWriter.setLoading(assemblerProduct.numLoadingDataProviders > 0); + + for (const message of assemblerProduct.aggregatedErrorMessages) { + statusWriter.addError(message); + } + + deckGlLayers.push(new PlaceholderLayer({ id: "placeholder" })); + deckGlLayers.push( + new AxesLayer({ + id: "axes-layer", + visible: true, + ZIncreasingDownwards: true, + }), + ); + + deckGlLayers.reverse(); + + return ( +
+
+ ({ + ...viewport, + layerIds: [...(viewport.layerIds ?? []), ...globalLayerIds], + })), + showLabel: false, + }} + viewportAnnotations={viewportAnnotations} + layers={deckGlLayers} + workbenchSession={props.workbenchSession} + workbenchSettings={props.workbenchSettings} + /> +
+
+ ); +} diff --git a/frontend/src/modules/3DViewerNew/view/components/ReadoutBoxWrapper.tsx b/frontend/src/modules/3DViewerNew/view/components/ReadoutBoxWrapper.tsx new file mode 100644 index 000000000..86734e4bf --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/components/ReadoutBoxWrapper.tsx @@ -0,0 +1,126 @@ +import React from "react"; + +import { ReadoutBox, ReadoutItem } from "@modules/_shared/components/ReadoutBox"; +import { ExtendedLayerProps, LayerPickInfo } from "@webviz/subsurface-viewer"; + +import { isEqual } from "lodash"; + +// Needs extra distance for the left side; this avoids overlapping with legend elements +const READOUT_EDGE_DISTANCE_REM = { left: 6 }; + +function makePositionReadout(layerPickInfo: LayerPickInfo): ReadoutItem | null { + if (layerPickInfo.coordinate === undefined || layerPickInfo.coordinate.length < 2) { + return null; + } + const readout = { + label: "Position", + info: [ + { + name: "x", + value: layerPickInfo.coordinate[0], + unit: "m", + }, + { + name: "y", + value: layerPickInfo.coordinate[1], + unit: "m", + }, + ], + }; + if (layerPickInfo.coordinate.length > 2) { + readout.info.push({ + name: "z", + value: layerPickInfo.coordinate[2], + unit: "m", + }); + } + + return readout; +} + +export type ReadoutBoxWrapperProps = { + layerPickInfo: LayerPickInfo[]; + maxNumItems?: number; + visible?: boolean; +}; + +export function ReadoutBoxWrapper(props: ReadoutBoxWrapperProps): React.ReactNode { + const [infoData, setInfoData] = React.useState([]); + const [prevLayerPickInfo, setPrevLayerPickInfo] = React.useState([]); + + if (!isEqual(props.layerPickInfo, prevLayerPickInfo)) { + setPrevLayerPickInfo(props.layerPickInfo); + const newReadoutItems: ReadoutItem[] = []; + + if (props.layerPickInfo.length === 0) { + setInfoData([]); + return; + } + + const positionReadout = makePositionReadout(props.layerPickInfo[0]); + if (!positionReadout) { + return; + } + newReadoutItems.push(positionReadout); + + for (const layerPickInfo of props.layerPickInfo) { + const layerName = (layerPickInfo.layer?.props as unknown as ExtendedLayerProps)?.name; + const layerProps = layerPickInfo.properties; + + // pick info can have 2 types of properties that can be displayed on the info card + // 1. defined as propertyValue, used for general layer info (now using for positional data) + // 2. Another defined as array of property object described by type PropertyDataType + + const layerReadout = newReadoutItems.find((item) => item.label === layerName); + + // collecting card data for 1st type + const zValue = (layerPickInfo as LayerPickInfo).propertyValue; + if (zValue !== undefined) { + if (layerReadout) { + layerReadout.info.push({ + name: "Property value", + value: zValue, + }); + } else { + newReadoutItems.push({ + label: layerName ?? "Unknown layer", + info: [ + { + name: "Property value", + value: zValue, + }, + ], + }); + } + } + + // collecting card data for 2nd type + if (!layerProps || layerProps.length === 0) { + continue; + } + if (layerReadout) { + layerProps?.forEach((prop) => { + const property = layerReadout.info?.find((item) => item.name === prop.name); + if (property) { + property.value = prop.value; + } else { + layerReadout.info.push(prop); + } + }); + } else { + newReadoutItems.push({ + label: layerName ?? "Unknown layer", + info: layerProps, + }); + } + } + + setInfoData(newReadoutItems); + } + + if (!props.visible) { + return null; + } + + return ; +} diff --git a/frontend/src/modules/3DViewerNew/view/components/ReadoutWrapper.tsx b/frontend/src/modules/3DViewerNew/view/components/ReadoutWrapper.tsx new file mode 100644 index 000000000..418f6cecf --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/components/ReadoutWrapper.tsx @@ -0,0 +1,78 @@ +import React from "react"; + +import { Layer as DeckGlLayer } from "@deck.gl/core"; +import { DeckGLRef } from "@deck.gl/react"; +import { WorkbenchSession } from "@framework/WorkbenchSession"; +import { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { SubsurfaceViewerWithCameraState } from "@modules/_shared/components/SubsurfaceViewerWithCameraState"; +import { BoundingBox3D, LayerPickInfo, MapMouseEvent, ViewsType } from "@webviz/subsurface-viewer"; + +import { ReadoutBoxWrapper } from "./ReadoutBoxWrapper"; + +import { DeckGlInstanceManager } from "../utils/DeckGlInstanceManager"; + +export type ReadoutWrapperProps = { + views: ViewsType; + viewportAnnotations: React.ReactNode[]; + layers: DeckGlLayer[]; + bounds?: BoundingBox3D; + workbenchSession: WorkbenchSession; + workbenchSettings: WorkbenchSettings; + deckGlManager: DeckGlInstanceManager; + verticalScale: number; + triggerHome: number; + deckGlRef: React.RefObject; +}; + +export function ReadoutWrapper(props: ReadoutWrapperProps): React.ReactNode { + const id = React.useId(); + const deckGlRef = React.useRef(null); + + React.useImperativeHandle(props.deckGlRef, () => deckGlRef.current); + + const [layerPickingInfo, setLayerPickingInfo] = React.useState([]); + + function handleMouseEvent(event: MapMouseEvent) { + const pickingInfo = event.infos; + setLayerPickingInfo(pickingInfo); + } + + return ( + <> + + + {props.viewportAnnotations} + + {props.views.viewports.length === 0 && ( +
+ Please add views and layers in the settings panel. +
+ )} + + ); +} diff --git a/frontend/src/modules/3DViewerNew/view/components/Toolbar.tsx b/frontend/src/modules/3DViewerNew/view/components/Toolbar.tsx new file mode 100644 index 000000000..1b0278b7b --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/components/Toolbar.tsx @@ -0,0 +1,185 @@ +import React from "react"; + +import { Button } from "@lib/components/Button"; +import { HoldPressedIntervalCallbackButton } from "@lib/components/HoldPressedIntervalCallbackButton/holdPressedIntervalCallbackButton"; +import { Input } from "@lib/components/Input"; +import { ToggleButton } from "@lib/components/ToggleButton"; +import { AddPathPointIcon, DrawPathIcon, RemovePathPointIcon } from "@lib/icons/"; +import { resolveClassNames } from "@lib/utils/resolveClassNames"; +import { Toolbar as GenericToolbar, ToolBarDivider } from "@modules/_shared/components/Toolbar"; +import { usePublishSubscribeTopicValue } from "@modules/_shared/utils/PublishSubscribeDelegate"; +import { + Add, + FilterCenterFocus, + GridOff, + GridOn, + KeyboardDoubleArrowLeft, + KeyboardDoubleArrowRight, + Polyline, + Remove, +} from "@mui/icons-material"; + +import { PolylineEditingMode } from "../hooks/editablePolylines/types"; +import { PolylinesPlugin, PolylinesPluginTopic } from "../utils/PolylinesPlugin"; + +export type ToolbarProps = { + verticalScale: number; + hasActivePolyline: boolean; + activePolylineName?: string; + onFitInView: () => void; + polylinesPlugin: PolylinesPlugin; + onGridVisibilityChange: (visible: boolean) => void; + onVerticalScaleChange(value: number): void; + onPolylineNameChange(name: string): void; +}; + +export function Toolbar(props: ToolbarProps): React.ReactNode { + const [expanded, setExpanded] = React.useState(false); + const [gridVisible, setGridVisible] = React.useState(false); + const [polylineName, setPolylineName] = React.useState(null); + const [prevEditingPolylineId, setPrevEditingPolylineId] = React.useState(null); + const polylineEditingMode = usePublishSubscribeTopicValue(props.polylinesPlugin, PolylinesPluginTopic.EDITING_MODE); + const editingPolylineId = usePublishSubscribeTopicValue( + props.polylinesPlugin, + PolylinesPluginTopic.EDITING_POLYLINE_ID + ); + + if (editingPolylineId !== prevEditingPolylineId) { + setPrevEditingPolylineId(editingPolylineId); + const activePolyline = props.polylinesPlugin.getActivePolyline(); + if (activePolyline) { + setPolylineName(activePolyline.name); + } + } + + function handleFitInViewClick() { + props.onFitInView(); + } + + function handleGridToggle() { + props.onGridVisibilityChange(!gridVisible); + setGridVisible(!gridVisible); + } + + function handleVerticalScaleIncrease() { + props.onVerticalScaleChange(props.verticalScale + 0.1); + } + + function handleVerticalScaleDecrease() { + props.onVerticalScaleChange(props.verticalScale - 0.1); + } + + function handleTogglePolylineEditing() { + if (polylineEditingMode !== PolylineEditingMode.NONE) { + props.polylinesPlugin.setEditingMode(PolylineEditingMode.NONE); + return; + } + props.polylinesPlugin.setEditingMode(PolylineEditingMode.IDLE); + } + + function handlePolylineEditingModeChange(mode: PolylineEditingMode) { + props.polylinesPlugin.setEditingMode(mode); + } + + function handlePolylineNameChange(event: React.ChangeEvent) { + props.polylinesPlugin.setActivePolylineName(event.target.value); + } + + return ( + +
+
+ +
+ + {gridVisible ? : } + + + + + + + + + + + {props.verticalScale.toFixed(2)} + + + + +
+ + +
+ {polylineEditingMode !== PolylineEditingMode.NONE && expanded && ( + <> +
+ + handlePolylineEditingModeChange( + active ? PolylineEditingMode.DRAW : PolylineEditingMode.IDLE + ) + } + > + + + + handlePolylineEditingModeChange( + active ? PolylineEditingMode.ADD_POINT : PolylineEditingMode.IDLE + ) + } + > + + + + handlePolylineEditingModeChange( + active ? PolylineEditingMode.REMOVE_POINT : PolylineEditingMode.IDLE + ) + } + > + + + +
+ + )} +
+
+ ); +} diff --git a/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/AnimatedPathLayer.ts b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/AnimatedPathLayer.ts new file mode 100644 index 000000000..53dc19ab1 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/AnimatedPathLayer.ts @@ -0,0 +1,51 @@ +import { UpdateParameters } from "@deck.gl/core"; +import { PathLayer } from "@deck.gl/layers"; + +export class AnimatedPathLayer extends PathLayer { + static layerName = "AnimatedPathLayer"; + + private _dashStart: number = 0; + private _requestId: ReturnType | null = null; + + initializeState() { + super.initializeState(); + this.animate(); + } + + updateState(params: UpdateParameters): void { + super.updateState(params); + if (this._requestId) { + cancelAnimationFrame(this._requestId); + } + this.animate(); + } + + private animate() { + this._dashStart = (Date.now() / 50) % 1000; + + this.setNeedsRedraw(); + this._requestId = requestAnimationFrame(() => this.animate()); + } + + getShaders() { + const shaders = super.getShaders(); + return { + ...shaders, + inject: { + ...shaders.inject, + "vs:#decl": + shaders.inject["vs:#decl"] + + `\ + uniform float dashStart;`, + "vs:#main-end": + shaders.inject["vs:#main-end"] + + `\ + vDashOffset += dashStart;`, + }, + }; + } + + draw({ uniforms }: Record) { + super.draw({ uniforms: { ...uniforms, dashStart: this._dashStart } }); + } +} diff --git a/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/EditablePolylineLayer.ts b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/EditablePolylineLayer.ts new file mode 100644 index 000000000..0ab850d83 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/EditablePolylineLayer.ts @@ -0,0 +1,294 @@ +import { CompositeLayer, GetPickingInfoParams, Layer, PickingInfo } from "@deck.gl/core"; +import { PathStyleExtension } from "@deck.gl/extensions"; +import { LineLayer, PathLayer, ScatterplotLayer } from "@deck.gl/layers"; +import { Polyline } from "@modules/3DViewerNew/view/utils/PolylinesPlugin"; + +import { AnimatedPathLayer } from "./AnimatedPathLayer"; + +export enum AllowHoveringOf { + NONE = "none", + LINES = "line", + POINTS = "point", + LINES_AND_POINTS = "lines-and-points", +} + +export type EditablePolylineLayerProps = { + id: string; + polyline: Polyline; + mouseHoverPoint?: number[]; + referencePathPointIndex?: number; + allowHoveringOf: AllowHoveringOf; +}; + +export type EditablePolylineLayerPickingInfo = PickingInfo & { + editableEntity?: { + type: "line" | "point"; + index: number; + }; +}; + +export function isEditablePolylineLayerPickingInfo(info: PickingInfo): info is EditablePolylineLayerPickingInfo { + return ( + Object.keys(info).includes("editableEntity") && + ((info as EditablePolylineLayerPickingInfo).editableEntity?.type === "line" || + (info as EditablePolylineLayerPickingInfo).editableEntity?.type === "point") + ); +} + +export class EditablePolylineLayer extends CompositeLayer { + static layerName: string = "EditablePolylineLayer"; + + // @ts-expect-error - deck.gl types are wrong + state!: { + hoveredEntity: { + layer: "line" | "point"; + index: number; + } | null; + dashStart: number; + }; + + initializeState(): void { + this.state = { + hoveredEntity: null, + dashStart: 0, + }; + } + + getPickingInfo({ info }: GetPickingInfoParams): EditablePolylineLayerPickingInfo { + if (info && info.sourceLayer && info.index !== undefined && info.index !== -1) { + let layer: "line" | "point" | null = null; + if (info.sourceLayer.id.includes("lines-selection")) { + layer = "line"; + } else if (info.sourceLayer.id.includes("points")) { + layer = "point"; + } + return { + ...info, + editableEntity: layer + ? { + type: layer, + index: info.index, + } + : undefined, + }; + } + + return info; + } + + onHover(info: EditablePolylineLayerPickingInfo): boolean { + if (!info.editableEntity || this.props.allowHoveringOf === AllowHoveringOf.NONE) { + this.setState({ + hoveredEntity: null, + }); + return false; + } + + if (this.props.allowHoveringOf === AllowHoveringOf.LINES && info.editableEntity.type === "point") { + return false; + } + + if (this.props.allowHoveringOf === AllowHoveringOf.POINTS && info.editableEntity.type === "line") { + return false; + } + + this.setState({ + hoveredEntity: { + layer: info.editableEntity.type, + index: info.index, + }, + }); + + return false; + } + + renderLayers() { + const { polyline, mouseHoverPoint, referencePathPointIndex } = this.props; + + const layers: Layer[] = []; + + if (referencePathPointIndex !== undefined && mouseHoverPoint && this.state.hoveredEntity === null) { + layers.push( + new LineLayer({ + id: "line", + data: [{ from: polyline.path[referencePathPointIndex], to: mouseHoverPoint }], + getSourcePosition: (d) => d.from, + getTargetPosition: (d) => d.to, + getColor: [polyline.color[0], polyline.color[1], polyline.color[2], 100], + getWidth: 10, + widthUnits: "meters", + widthMinPixels: 3, + parameters: { + depthTest: false, + }, + }), + new ScatterplotLayer({ + id: "hover-point", + data: [mouseHoverPoint], + getPosition: (d) => d, + getFillColor: [polyline.color[0], polyline.color[1], polyline.color[2], 100], + getRadius: 10, + radiusUnits: "pixels", + radiusMinPixels: 5, + radiusMaxPixels: 10, + pickable: false, + parameters: { + depthTest: false, + }, + }) + ); + } + + const polylinePathLayerData: number[][][] = []; + for (let i = 0; i < polyline.path.length - 1; i++) { + polylinePathLayerData.push([polyline.path[i], polyline.path[i + 1]]); + } + + if (this.state.hoveredEntity && this.state.hoveredEntity.layer === "line") { + const hoveredLine = polylinePathLayerData[this.state.hoveredEntity.index]; + layers.push( + new PathLayer({ + id: "hovered-line", + data: [hoveredLine], + getPath: (d) => d, + getColor: [255, 255, 255, 50], + getWidth: 20, + widthUnits: "meters", + widthMinPixels: 6, + parameters: { + depthTest: false, + }, + pickable: false, + }) + ); + } + + layers.push( + new AnimatedPathLayer({ + id: "lines", + data: polylinePathLayerData, + dashStart: 0, + getColor: polyline.color, + getPath: (d) => d, + getDashArray: [10, 10], + getWidth: 10, + billboard: true, + widthUnits: "meters", + widthMinPixels: 3, + widthMaxPixels: 10, + extensions: [new PathStyleExtension({ highPrecisionDash: true })], + parameters: { + // @ts-expect-error - deck.gl types are wrong + depthTest: false, + }, + pickable: false, + depthTest: false, + }), + new PathLayer({ + id: "lines-selection", + data: polylinePathLayerData, + getColor: [0, 0, 0, 0], + getPath: (d) => d, + getWidth: 50, + widthMinPixels: 10, + widthMaxPixels: 20, + billboard: false, + widthUnits: "meters", + parameters: { + depthTest: false, + }, + pickable: true, + }) + ); + /* + layers.push( + new ScatterplotLayer({ + id: "points-selection", + data: polyline.path, + getPosition: (d) => d, + getRadius: (d, context) => { + if ( + this.state.hoveredEntity?.layer === "point" && + context.index === this.state.hoveredEntity.index + ) { + return 10; + } + return 5; + }, + getFillColor: [255, 255, 255, 1], + getLineColor: [0, 0, 0, 0], + getLineWidth: 30, + lineWidthMinPixels: 10, + radiusUnits: "pixels", + pickable: true, + parameters: { + depthTest: false, + }, + updateTriggers: { + getRadius: [this.state.hoveredEntity, referencePathPointIndex], + }, + }) + ); + */ + + layers.push( + new ScatterplotLayer({ + id: "points", + data: polyline.path, + getPosition: (d) => d, + getFillColor: (_, context) => { + if (context.index === referencePathPointIndex) { + return [255, 255, 255, 255]; + } + return polyline.color; + }, + getLineColor: (_, context) => { + if ( + this.state.hoveredEntity && + this.state.hoveredEntity.layer === "point" && + context.index === this.state.hoveredEntity.index + ) { + return [255, 255, 255, 255]; + } + return [0, 0, 0, 0]; + }, + getLineWidth: (_, context) => { + if ( + this.state.hoveredEntity && + this.state.hoveredEntity.layer === "point" && + context.index === this.state.hoveredEntity.index + ) { + return 5; + } + return 0; + }, + getRadius: (_, context) => { + if ( + this.state.hoveredEntity?.layer === "point" && + context.index === this.state.hoveredEntity.index + ) { + return 12; + } + return 10; + }, + stroked: true, + radiusUnits: "pixels", + lineWidthUnits: "meters", + lineWidthMinPixels: 3, + radiusMinPixels: 5, + pickable: true, + parameters: { + depthTest: false, + }, + updateTriggers: { + getLineWidth: [this.state.hoveredEntity, referencePathPointIndex], + getLineColor: [this.state.hoveredEntity], + getFillColor: [referencePathPointIndex], + getRadius: [this.state.hoveredEntity, referencePathPointIndex], + }, + }) + ); + + return layers; + } +} diff --git a/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/HoverPointLayer.ts b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/HoverPointLayer.ts new file mode 100644 index 000000000..8bc04b19c --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/HoverPointLayer.ts @@ -0,0 +1,33 @@ +import { CompositeLayer, Layer, LayersList } from "@deck.gl/core"; +import { ColumnLayer } from "@deck.gl/layers"; + +export type HoverPointLayerProps = { + point: number[] | null; + color: [number, number, number, number]; +}; + +export class HoverPointLayer extends CompositeLayer { + static layerName: string = "HoverPointLayer"; + + renderLayers(): Layer | null | LayersList { + if (!this.props.point) { + return null; + } + + return new ColumnLayer({ + id: "hover-point", + data: [this.props.point], + diskResolution: 20, + getElevation: 1, + radiusUnits: "pixels", + radius: 20, + extruded: false, + pickable: false, + getPosition: (d) => d, + getFillColor: this.props.color, + parameters: { + depthTest: false, + }, + }); + } +} diff --git a/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/PolylinesLayer.ts b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/PolylinesLayer.ts new file mode 100644 index 000000000..1aa0d48a8 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/deckGlLayers/PolylinesLayer.ts @@ -0,0 +1,185 @@ +import { CompositeLayer, FilterContext, GetPickingInfoParams, Layer, PickingInfo } from "@deck.gl/core"; +import { PathLayer, TextLayer } from "@deck.gl/layers"; +import { Polyline } from "@modules/3DViewerNew/view/utils/PolylinesPlugin"; + +export type PolylinesLayerProps = { + id: string; + polylines: Polyline[]; + selectedPolylineId?: string; + hoverable?: boolean; +}; + +export type PolylinesLayerPickingInfo = PickingInfo & { + polylineId?: string; +}; + +export function isPolylinesLayerPickingInfo(info: PickingInfo): info is PolylinesLayerPickingInfo { + return Object.keys(info).includes("polylineId"); +} + +export class PolylinesLayer extends CompositeLayer { + static layerName: string = "PolylinesLayer"; + + // @ts-expect-error - deck.gl types are wrong + state!: { + hoveredPolylineIndex: number | null; + }; + + onHover(info: PickingInfo): boolean { + if (!this.props.hoverable) { + return false; + } + if (info.index === undefined) { + return false; + } + + const hoveredPolylineIndex = info.index; + this.setState({ hoveredPolylineIndex }); + + return false; + } + + getPickingInfo({ info }: GetPickingInfoParams): PolylinesLayerPickingInfo { + if (info && info.sourceLayer && info.object !== undefined) { + return { + ...info, + polylineId: info.object.id, + }; + } + + return info; + } + + filterSubLayer(context: FilterContext): boolean { + if (context.layer.id.includes("labels")) { + return context.viewport.zoom > -4; + } + + return true; + } + + renderLayers(): Layer[] { + const { hoveredPolylineIndex } = this.state; + + const layers: Layer[] = []; + + if (this.props.selectedPolylineId) { + const selectedPolylineIndex = this.props.polylines.findIndex((p) => p.id === this.props.selectedPolylineId); + if (selectedPolylineIndex !== -1) { + layers.push( + new PathLayer({ + id: `selected`, + data: [this.props.polylines[selectedPolylineIndex]], + getPath: (d) => d.path, + getColor: (d: Polyline) => [d.color[0], d.color[1], d.color[2], 200], + getWidth: 30, + widthUnits: "meters", + widthMinPixels: 5, + parameters: { + depthTest: false, + }, + billboard: true, + }) + ); + } + } + + if (hoveredPolylineIndex !== null && this.props.polylines[hoveredPolylineIndex] && this.props.hoverable) { + layers.push( + new PathLayer({ + id: `hovered`, + data: [this.props.polylines[hoveredPolylineIndex]], + getPath: (d) => d.path, + getColor: (d: Polyline) => [d.color[0], d.color[1], d.color[2], 100], + getWidth: 30, + widthUnits: "meters", + widthMinPixels: 6, + parameters: { + depthTest: false, + }, + billboard: true, + }) + ); + } + + const polylineLabels: { label: string; position: number[]; angle: number; color: number[] }[] = []; + for (const polyline of this.props.polylines) { + const vector = [ + polyline.path[1][0] - polyline.path[0][0], + polyline.path[1][1] - polyline.path[0][1], + polyline.path[1][2] - polyline.path[0][2], + ]; + const length = Math.sqrt(vector[0] ** 2 + vector[1] ** 2); + const unitVector = [vector[0] / length, vector[1] / length, vector[2] / length]; + let angle = Math.atan2(unitVector[1], unitVector[0]) * (180 / Math.PI); + if (angle > 90 || angle < -90) { + angle += 180; + } + polylineLabels.push({ + label: polyline.name, + position: [ + polyline.path[0][0] + (unitVector[0] * length) / 2, + polyline.path[0][1] + (unitVector[1] * length) / 2, + polyline.path[0][2] + (unitVector[2] * length) / 2, + ], + angle, + color: polyline.color, + }); + } + + layers.push( + new PathLayer({ + id: `polylines`, + data: this.props.polylines, + getPath: (d) => d.path, + getColor: (d: Polyline) => d.color, + getWidth: 10, + widthUnits: "meters", + widthMinPixels: 3, + widthMaxPixels: 10, + pickable: false, + parameters: { + depthTest: false, + }, + billboard: true, + }), + new TextLayer({ + id: `polylines-labels`, + data: polylineLabels, + getPosition: (d) => d.position, + getText: (d) => d.label, + getSize: 12, + sizeUnits: "meters", + sizeMinPixels: 16, + getAngle: (d) => d.angle, + getColor: [0, 0, 0], + parameters: { + depthTest: false, + }, + billboard: false, + getBackgroundColor: [255, 255, 255, 100], + getBackgroundPadding: [10, 10], + getBackgroundBorderColor: [0, 0, 0, 255], + getBackgroundBorderWidth: 2, + getBackgroundElevation: 1, + background: true, + }), + new PathLayer({ + id: `polylines-hoverable`, + data: this.props.polylines, + getPath: (d) => d.path, + getColor: (d: Polyline) => [d.color[0], d.color[1], d.color[2], 1], + getWidth: 50, + widthUnits: "meters", + widthMinPixels: 10, + widthMaxPixels: 20, + pickable: true, + parameters: { + depthTest: false, + }, + }) + ); + + return layers; + } +} diff --git a/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/types.ts b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/types.ts new file mode 100644 index 000000000..58f16acde --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/hooks/editablePolylines/types.ts @@ -0,0 +1,15 @@ +import React from "react"; + +export enum PolylineEditingMode { + DRAW = "draw", + ADD_POINT = "add_point", + REMOVE_POINT = "remove_point", + NONE = "none", + IDLE = "idle", +} + +export type ContextMenuItem = { + icon?: React.ReactNode; + label: string; + onClick: () => void; +}; diff --git a/frontend/src/modules/3DViewerNew/view/utils/DeckGlInstanceManager.ts b/frontend/src/modules/3DViewerNew/view/utils/DeckGlInstanceManager.ts new file mode 100644 index 000000000..560794f61 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/utils/DeckGlInstanceManager.ts @@ -0,0 +1,321 @@ +/* +This manager is responsible for managing plugins for DeckGL, forwarding events to them, and adding/adjusting layers based on the plugins' responses. +*/ +import React from "react"; + +import { Layer, PickingInfo } from "@deck.gl/core"; +import { DeckGLProps, DeckGLRef } from "@deck.gl/react"; +import { SubsurfaceViewerWithCameraStateProps } from "@modules/_shared/components/SubsurfaceViewerWithCameraState"; +import { PublishSubscribe, PublishSubscribeDelegate } from "@modules_shared/utils/PublishSubscribeDelegate"; +import { MapMouseEvent } from "@webviz/subsurface-viewer"; + +export type ContextMenuItem = { + icon?: React.ReactElement; + label: string; + onClick: () => void; +}; + +export type ContextMenu = { + position: { x: number; y: number }; + items: ContextMenuItem[]; +}; + +export class DeckGlPlugin { + private _manager: DeckGlInstanceManager; + + constructor(manager: DeckGlInstanceManager) { + this._manager = manager; + } + + protected requireRedraw() { + this._manager.redraw(); + } + + protected requestDisablePanning() { + this._manager.disablePanning(); + } + + protected requestEnablePanning() { + this._manager.enablePanning(); + } + + protected getFirstLayerUnderCursorInfo(x: number, y: number): PickingInfo | undefined { + return this._manager.pickFirstLayerUnderCursorInfo(x, y); + } + + handleDrag?(pickingInfo: PickingInfo): void; + handleLayerHover?(pickingInfo: PickingInfo): void; + handleLayerClick?(pickingInfo: PickingInfo): void; + handleClickAway?(): void; + handleGlobalMouseHover?(pickingInfo: PickingInfo): void; + handleGlobalMouseClick?(pickingInfo: PickingInfo): boolean; + handleKeyUpEvent?(key: string): void; + handleKeyDownEvent?(key: string): void; + getCursor?(pickingInfo: PickingInfo): string | null; + getLayers?(): Layer[]; + getContextMenuItems?(pickingInfo: PickingInfo): ContextMenuItem[]; +} + +export enum DeckGlInstanceManagerTopic { + REDRAW = "REDRAW", + CONTEXT_MENU = "CONTEXT_MENU", +} + +export type DeckGlInstanceManagerPayloads = { + [DeckGlInstanceManagerTopic.REDRAW]: number; + [DeckGlInstanceManagerTopic.CONTEXT_MENU]: ContextMenu | null; +}; + +type HoverPoint = { + worldCoordinates: number[]; + screenCoordinates: [number, number]; +}; + +type KeyboardEventListener = (event: KeyboardEvent) => void; + +export class DeckGlInstanceManager implements PublishSubscribe { + private _publishSubscribeDelegate = new PublishSubscribeDelegate(); + + private _ref: DeckGLRef | null; + private _hoverPoint: HoverPoint | null = null; + private _plugins: DeckGlPlugin[] = []; + private _layersIdPluginMap = new Map(); + private _cursor: string = "auto"; + private _redrawCycle: number = 0; + private _eventListeners: KeyboardEventListener[] = []; + private _contextMenu: ContextMenu | null = null; + + constructor(ref: DeckGLRef | null) { + this._ref = ref; + this.addKeyboardEventListeners(); + } + + setRef(ref: DeckGLRef | null) { + this._ref = ref; + } + + private addKeyboardEventListeners() { + const handleKeyDown = this.handleKeyDown.bind(this); + const handleKeyUp = this.handleKeyUp.bind(this); + + this._eventListeners = [handleKeyDown, handleKeyUp]; + + document.addEventListener("keyup", handleKeyUp); + document.addEventListener("keydown", handleKeyDown); + } + + private maybeRemoveKeyboardEventListeners() { + for (const listener of this._eventListeners) { + document.removeEventListener("keydown", listener); + } + } + + private handleKeyDown(event: KeyboardEvent) { + for (const plugin of this._plugins) { + plugin.handleKeyDownEvent?.(event.key); + } + } + + private handleKeyUp(event: KeyboardEvent) { + for (const plugin of this._plugins) { + plugin.handleKeyUpEvent?.(event.key); + } + } + + addPlugin(plugin: DeckGlPlugin) { + this._plugins.push(plugin); + } + + redraw() { + this._redrawCycle++; + this._publishSubscribeDelegate.notifySubscribers(DeckGlInstanceManagerTopic.REDRAW); + } + + disablePanning() { + if (!this._ref) { + return; + } + + this._ref.deck?.setProps({ + controller: { + dragPan: false, + dragRotate: false, + }, + }); + } + + enablePanning() { + if (!this._ref) { + return; + } + + this._ref.deck?.setProps({ + controller: { + dragRotate: true, + dragPan: true, + }, + }); + } + + getPublishSubscribeDelegate(): PublishSubscribeDelegate { + return this._publishSubscribeDelegate; + } + + makeSnapshotGetter(topic: T): () => DeckGlInstanceManagerPayloads[T] { + const snapshotGetter = (): any => { + if (topic === DeckGlInstanceManagerTopic.REDRAW) { + return this._redrawCycle; + } + if (topic === DeckGlInstanceManagerTopic.CONTEXT_MENU) { + return this._contextMenu; + } + + throw new Error(`Unknown topic ${topic}`); + }; + + return snapshotGetter; + } + + private getLayerIdFromPickingInfo(pickingInfo: PickingInfo): string | undefined { + return pickingInfo.layer?.id; + } + + handleDrag(pickingInfo: PickingInfo): void { + const layerId = this.getLayerIdFromPickingInfo(pickingInfo); + if (!layerId) { + return; + } + + const plugin = this._layersIdPluginMap.get(layerId); + if (!plugin) { + return; + } + + plugin.handleDrag?.(pickingInfo); + } + + handleDragStart(pickingInfo: PickingInfo) { + const layerId = this.getLayerIdFromPickingInfo(pickingInfo); + if (!layerId) { + return; + } + + const plugin = this._layersIdPluginMap.get(layerId); + if (!plugin) { + return; + } + } + + handleMouseEvent(event: MapMouseEvent) { + if (event.type !== "hover") { + this._contextMenu = null; + this._publishSubscribeDelegate.notifySubscribers(DeckGlInstanceManagerTopic.CONTEXT_MENU); + } + + const firstLayerInfo = this.getFirstLayerUnderCursorInfo(event); + if (!firstLayerInfo || !firstLayerInfo.coordinate) { + this._hoverPoint = null; + return; + } + + this._hoverPoint = { + worldCoordinates: firstLayerInfo.coordinate, + screenCoordinates: [firstLayerInfo.x, firstLayerInfo.y], + }; + + const layerId = this.getLayerIdFromPickingInfo(firstLayerInfo); + const plugin = this._layersIdPluginMap.get(layerId ?? ""); + if (layerId && plugin) { + if (event.type === "hover") { + plugin.handleLayerHover?.(firstLayerInfo); + this._cursor = plugin.getCursor?.(firstLayerInfo) ?? "auto"; + } + + if (event.type === "click") { + plugin.handleLayerClick?.(firstLayerInfo); + } + + if (event.type === "contextmenu") { + const contextMenuItems = plugin.getContextMenuItems?.(firstLayerInfo) ?? []; + this._contextMenu = { + position: { x: firstLayerInfo.x, y: firstLayerInfo.y }, + items: contextMenuItems, + }; + this._publishSubscribeDelegate.notifySubscribers(DeckGlInstanceManagerTopic.CONTEXT_MENU); + } + return; + } + + const pluginsThatDidNotAcceptEvent: DeckGlPlugin[] = []; + for (const plugin of this._plugins) { + if (event.type === "hover") { + plugin.handleGlobalMouseHover?.(firstLayerInfo); + this._cursor = "auto"; + } else if (event.type === "click") { + const accepted = plugin.handleGlobalMouseClick?.(firstLayerInfo); + if (!accepted) { + pluginsThatDidNotAcceptEvent.push(plugin); + } + } + } + + if (event.type === "click") { + for (const plugin of pluginsThatDidNotAcceptEvent) { + plugin.handleClickAway?.(); + } + } + } + + pickFirstLayerUnderCursorInfo(x: number, y: number): PickingInfo | undefined { + if (!this._ref?.deck) { + return undefined; + } + + const layer = + this._ref.deck.pickMultipleObjects({ x, y, radius: 10, depth: 1, unproject3D: true }) ?? undefined; + return layer[0]; + } + + private getFirstLayerUnderCursorInfo(event: MapMouseEvent): PickingInfo | undefined { + for (const info of event.infos) { + if (info.coordinate && info.x) { + return info; + } + } + + return undefined; + } + + getCursor(cursorState: Parameters>[0]): string { + if (cursorState.isDragging) { + return "grabbing"; + } + + return this._cursor; + } + + makeDeckGlComponentProps(props: SubsurfaceViewerWithCameraStateProps): SubsurfaceViewerWithCameraStateProps { + const layers = [...(props.layers ?? [])]; + for (const plugin of this._plugins) { + const pluginLayers = plugin.getLayers?.() ?? []; + layers.push(...pluginLayers); + for (const layer of pluginLayers) { + this._layersIdPluginMap.set(layer.id, plugin); + } + } + return { + ...props, + onDrag: this.handleDrag.bind(this), + onMouseEvent: (event) => { + this.handleMouseEvent(event); + props.onMouseEvent?.(event); + }, + getCursor: (state) => this.getCursor(state), + layers, + }; + } + + beforeDestroy() { + this.maybeRemoveKeyboardEventListeners(); + } +} diff --git a/frontend/src/modules/3DViewerNew/view/utils/MoveableLayerPlugin.tsx b/frontend/src/modules/3DViewerNew/view/utils/MoveableLayerPlugin.tsx new file mode 100644 index 000000000..e94e98ddc --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/utils/MoveableLayerPlugin.tsx @@ -0,0 +1,524 @@ +import addPathIcon from "@assets/add_path.svg"; +import continuePathIcon from "@assets/continue_path.svg"; +import removePathIcon from "@assets/remove_path.svg"; +import { Layer, PickingInfo } from "@deck.gl/core"; +import { PublishSubscribe, PublishSubscribeDelegate } from "@modules/_shared/utils/PublishSubscribeDelegate"; +import { Edit, Remove } from "@mui/icons-material"; + +import { isEqual } from "lodash"; +import { v4 } from "uuid"; + +import { ContextMenuItem, DeckGlInstanceManager, DeckGlPlugin } from "./DeckGlInstanceManager"; + +import { + AllowHoveringOf, + EditablePolylineLayer, + isEditablePolylineLayerPickingInfo, +} from "../hooks/editablePolylines/deckGlLayers/EditablePolylineLayer"; +import { PolylinesLayer, isPolylinesLayerPickingInfo } from "../hooks/editablePolylines/deckGlLayers/PolylinesLayer"; + +export type Polyline = { + id: string; + name: string; + color: [number, number, number]; + path: number[][]; +}; + +export enum PolylineEditingMode { + DRAW = "draw", + ADD_POINT = "add_point", + REMOVE_POINT = "remove_point", + NONE = "none", + IDLE = "idle", +} + +export enum PolylinesPluginTopic { + EDITING_POLYLINE_ID = "editing_polyline_id", + EDITING_MODE = "editing_mode", + POLYLINES = "polylines", +} + +export type PolylinesPluginTopicPayloads = { + [PolylinesPluginTopic.EDITING_MODE]: PolylineEditingMode; + [PolylinesPluginTopic.EDITING_POLYLINE_ID]: string | null; + [PolylinesPluginTopic.POLYLINES]: Polyline[]; +}; + +enum AppendToPathLocation { + START = "start", + END = "end", +} + +function* defaultColorGenerator() { + const colors: [number, number, number][] = [ + [255, 0, 0], + [0, 255, 0], + [0, 0, 255], + [255, 255, 0], + [255, 0, 255], + [0, 255, 255], + ]; + + let index = 0; + while (true) { + yield colors[index]; + index = (index + 1) % colors.length; + } +} + +export class PolylinesPlugin extends DeckGlPlugin implements PublishSubscribe { + private _currentEditingPolylineId: string | null = null; + private _currentEditingPolylinePathReferencePointIndex: number | null = null; + private _polylines: Polyline[] = []; + private _editingMode: PolylineEditingMode = PolylineEditingMode.NONE; + private _draggedPathPointIndex: number | null = null; + private _appendToPathLocation: AppendToPathLocation = AppendToPathLocation.END; + private _selectedPolylineId: string | null = null; + private _hoverPoint: number[] | null = null; + private _colorGenerator: Generator<[number, number, number]>; + + private _publishSubscribeDelegate = new PublishSubscribeDelegate(); + + private setCurrentEditingPolylineId(id: string | null): void { + this._currentEditingPolylineId = id; + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.EDITING_POLYLINE_ID); + } + + constructor(manager: DeckGlInstanceManager, colorGenerator?: Generator<[number, number, number]>) { + super(manager); + this._colorGenerator = colorGenerator ?? defaultColorGenerator(); + } + + getActivePolyline(): Polyline | undefined { + return this._polylines.find((polyline) => polyline.id === this._currentEditingPolylineId); + } + + getPolylines(): Polyline[] { + return this._polylines; + } + + setPolylines(polylines: Polyline[]): void { + if (isEqual(this._polylines, polylines)) { + return; + } + this._polylines = polylines; + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + } + + setActivePolylineName(name: string): void { + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + this._polylines = this._polylines.map((polyline) => { + if (polyline.id === activePolyline.id) { + return { + ...polyline, + name, + }; + } + return polyline; + }); + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + } + + getPublishSubscribeDelegate(): PublishSubscribeDelegate { + return this._publishSubscribeDelegate; + } + + setEditingMode(mode: PolylineEditingMode): void { + this._editingMode = mode; + this._hoverPoint = null; + this._currentEditingPolylinePathReferencePointIndex = null; + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.EDITING_MODE); + this.requireRedraw(); + } + + getEditingMode(): PolylineEditingMode { + return this._editingMode; + } + + getCurrentEditingPolylineId(): string | null { + return this._currentEditingPolylineId; + } + + handleKeyUpEvent(key: string): void { + if (key === "Escape") { + if (this._editingMode === PolylineEditingMode.NONE) { + this._currentEditingPolylinePathReferencePointIndex = null; + this.requireRedraw(); + return; + } + if (this._editingMode === PolylineEditingMode.IDLE) { + this._currentEditingPolylinePathReferencePointIndex = null; + this._hoverPoint = null; + this.requireRedraw(); + return; + } + + this._hoverPoint = null; + this.setEditingMode(PolylineEditingMode.IDLE); + this.requireRedraw(); + return; + } + if (key === "Delete") { + if (this._editingMode === PolylineEditingMode.IDLE) { + if (this._selectedPolylineId) { + this._polylines = this._polylines.filter((polyline) => polyline.id !== this._selectedPolylineId); + this._selectedPolylineId = null; + this.requireRedraw(); + } + return; + } + } + } + + handleLayerClick(pickingInfo: PickingInfo): void { + if (this._editingMode === PolylineEditingMode.NONE || this._editingMode === PolylineEditingMode.IDLE) { + if (isPolylinesLayerPickingInfo(pickingInfo)) { + this._selectedPolylineId = pickingInfo.polylineId ?? null; + this.requireRedraw(); + } + return; + } + + if (!isEditablePolylineLayerPickingInfo(pickingInfo)) { + return; + } + + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + if (pickingInfo.editableEntity?.type === "point") { + if (![PolylineEditingMode.DRAW, PolylineEditingMode.REMOVE_POINT].includes(this._editingMode)) { + return; + } + + const index = pickingInfo.editableEntity.index; + if (this._editingMode === PolylineEditingMode.DRAW) { + if ( + (index === 0 || index === activePolyline.path.length - 1) && + this._currentEditingPolylinePathReferencePointIndex !== index + ) { + this._appendToPathLocation = index === 0 ? AppendToPathLocation.START : AppendToPathLocation.END; + this._currentEditingPolylinePathReferencePointIndex = index; + this.requireRedraw(); + return; + } + } + + const newPath = activePolyline.path.filter((_, i) => i !== index); + let newReferencePathPointIndex: number | null = null; + if (this._currentEditingPolylinePathReferencePointIndex !== null) { + newReferencePathPointIndex = Math.max(0, this._currentEditingPolylinePathReferencePointIndex - 1); + if (index > this._currentEditingPolylinePathReferencePointIndex) { + newReferencePathPointIndex = this._currentEditingPolylinePathReferencePointIndex; + } + if (activePolyline.path.length - 1 < 1) { + newReferencePathPointIndex = null; + } + } + + this.updateActivePolylinePath(newPath); + this._currentEditingPolylinePathReferencePointIndex = newReferencePathPointIndex; + this.requireRedraw(); + return; + } + + if (pickingInfo.editableEntity?.type === "line") { + if (![PolylineEditingMode.DRAW, PolylineEditingMode.ADD_POINT].includes(this._editingMode)) { + return; + } + + if (!pickingInfo.coordinate) { + return; + } + + const index = pickingInfo.editableEntity.index; + const newPath = [...activePolyline.path]; + newPath.splice(index + 1, 0, [...pickingInfo.coordinate]); + this.updateActivePolylinePath(newPath); + + let newReferencePathPointIndex: number | null = null; + if (this._currentEditingPolylinePathReferencePointIndex !== null) { + newReferencePathPointIndex = this._currentEditingPolylinePathReferencePointIndex + 1; + if (index > this._currentEditingPolylinePathReferencePointIndex) { + newReferencePathPointIndex = this._currentEditingPolylinePathReferencePointIndex; + } + } + + this._currentEditingPolylinePathReferencePointIndex = newReferencePathPointIndex; + this.requireRedraw(); + } + } + + private updateActivePolylinePath(newPath: number[][]): void { + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + this._polylines = this._polylines.map((polyline) => { + if (polyline.id === activePolyline.id) { + return { + ...polyline, + path: newPath, + }; + } + return polyline; + }); + + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + } + + handleClickAway(): void { + this._selectedPolylineId = null; + this.requireRedraw(); + } + + handleGlobalMouseHover(pickingInfo: PickingInfo): void { + if (this._editingMode !== PolylineEditingMode.DRAW) { + return; + } + + if (!pickingInfo.coordinate) { + return; + } + + this._hoverPoint = pickingInfo.coordinate; + this.requireRedraw(); + } + + handleGlobalMouseClick(pickingInfo: PickingInfo): boolean { + if (this._editingMode === PolylineEditingMode.NONE) { + return false; + } + + if (!pickingInfo.coordinate) { + return false; + } + + const activePolyline = this.getActivePolyline(); + if (!activePolyline && this._editingMode === PolylineEditingMode.DRAW) { + const id = v4(); + this._polylines.push({ + id, + name: "New polyline", + color: this._colorGenerator.next().value, + path: [[...pickingInfo.coordinate]], + }); + this._polylines = [...this._polylines]; + this._currentEditingPolylinePathReferencePointIndex = 0; + this.setCurrentEditingPolylineId(id); + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + } else if (activePolyline) { + if (this._currentEditingPolylinePathReferencePointIndex === null) { + this.setCurrentEditingPolylineId(null); + this.setEditingMode(PolylineEditingMode.IDLE); + this.requireRedraw(); + return true; + } + + if (this._editingMode === PolylineEditingMode.DRAW) { + this.appendToActivePolylinePath(pickingInfo.coordinate); + this.requireRedraw(); + return true; + } + } + + return false; + } + + private appendToActivePolylinePath(point: number[]): void { + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + const newPath = [...activePolyline.path]; + if (this._appendToPathLocation === AppendToPathLocation.START) { + newPath.unshift(point); + this._currentEditingPolylinePathReferencePointIndex = 0; + } else { + newPath.push(point); + this._currentEditingPolylinePathReferencePointIndex = newPath.length - 1; + } + + this.updateActivePolylinePath(newPath); + } + + handleDragStart(pickingInfo: PickingInfo): void { + if (!isEditablePolylineLayerPickingInfo(pickingInfo)) { + return; + } + + if (pickingInfo.editableEntity?.type === "point") { + this._draggedPathPointIndex = pickingInfo.index; + this.requestDisablePanning(); + } + } + + handleDrag(pickingInfo: PickingInfo): void { + if (this._draggedPathPointIndex === null || !pickingInfo.coordinate) { + return; + } + + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + // Take first layer under cursor to get coordinates for the polyline point + // An alternative would be to store a reference to the layer the polyline was first created upon + // and always try to use that layer to get the coordinates + const layerUnderCursor = this.getFirstLayerUnderCursorInfo(pickingInfo.x, pickingInfo.y); + if (!layerUnderCursor || !layerUnderCursor.coordinate) { + return; + } + + const newPath = [...activePolyline.path]; + newPath[this._draggedPathPointIndex] = [...layerUnderCursor.coordinate]; + this.updateActivePolylinePath(newPath); + this.requireRedraw(); + } + + handleDragEnd(): void { + this._draggedPathPointIndex = null; + this.requestEnablePanning(); + } + + getCursor(pickingInfo: PickingInfo): string | null { + if (this._editingMode === PolylineEditingMode.NONE) { + return null; + } + + const activePolyline = this.getActivePolyline(); + + if (isEditablePolylineLayerPickingInfo(pickingInfo) && pickingInfo.editableEntity) { + if ( + [PolylineEditingMode.DRAW, PolylineEditingMode.ADD_POINT].includes(this._editingMode) && + pickingInfo.editableEntity.type === "line" + ) { + return `url(${addPathIcon}) 4 2, crosshair`; + } + + if ( + activePolyline && + [PolylineEditingMode.DRAW, PolylineEditingMode.REMOVE_POINT].includes(this._editingMode) && + pickingInfo.editableEntity.type === "point" + ) { + const index = pickingInfo.index; + if ( + (index === 0 || index === activePolyline.path.length - 1) && + index !== this._currentEditingPolylinePathReferencePointIndex && + this._editingMode === PolylineEditingMode.DRAW + ) { + return `url(${continuePathIcon}) 4 2, crosshair`; + } + + return `url(${removePathIcon}) 4 2, crosshair`; + } + + if (this._editingMode === PolylineEditingMode.IDLE && pickingInfo.editableEntity.type === "point") { + return "grab"; + } + } + + return "auto"; + } + + getContextMenuItems(pickingInfo: PickingInfo): ContextMenuItem[] { + if (this._editingMode !== PolylineEditingMode.IDLE) { + return []; + } + + if (!isPolylinesLayerPickingInfo(pickingInfo) || !pickingInfo.polylineId) { + return []; + } + + return [ + { + icon: , + label: "Edit", + onClick: () => { + this.setCurrentEditingPolylineId(pickingInfo.polylineId ?? null); + this.requireRedraw(); + }, + }, + { + icon: , + label: "Delete", + onClick: () => { + this._polylines = this._polylines.filter((polyline) => polyline.id !== pickingInfo.polylineId); + this.setCurrentEditingPolylineId(null); + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + }, + }, + ]; + } + + getLayers(): Layer[] { + const layers: Layer[] = [ + new PolylinesLayer({ + id: "polylines-layer", + polylines: this._polylines.filter((polyline) => polyline.id !== this._currentEditingPolylineId), + selectedPolylineId: + this._editingMode === PolylineEditingMode.NONE ? undefined : this._selectedPolylineId ?? undefined, + hoverable: this._editingMode === PolylineEditingMode.IDLE, + }), + ]; + + let allowHoveringOf = AllowHoveringOf.NONE; + if ([PolylineEditingMode.DRAW, PolylineEditingMode.ADD_POINT].includes(this._editingMode)) { + allowHoveringOf = AllowHoveringOf.LINES_AND_POINTS; + } + if (this._editingMode === PolylineEditingMode.REMOVE_POINT) { + allowHoveringOf = AllowHoveringOf.POINTS; + } + + const activePolyline = this.getActivePolyline(); + if (activePolyline) { + layers.push( + new EditablePolylineLayer({ + id: "editable-polylines-layer", + polyline: activePolyline, + mouseHoverPoint: this._hoverPoint ?? undefined, + referencePathPointIndex: + this._editingMode === PolylineEditingMode.DRAW + ? this._currentEditingPolylinePathReferencePointIndex ?? undefined + : undefined, + onDragStart: this.handleDragStart.bind(this), + onDragEnd: this.handleDragEnd.bind(this), + allowHoveringOf, + }) + ); + } + + return layers; + } + + makeSnapshotGetter(topic: T): () => PolylinesPluginTopicPayloads[T] { + const snapshotGetter = (): any => { + if (topic === PolylinesPluginTopic.EDITING_MODE) { + return this._editingMode; + } + if (topic === PolylinesPluginTopic.EDITING_POLYLINE_ID) { + return this._currentEditingPolylineId; + } + if (topic === PolylinesPluginTopic.POLYLINES) { + return this._polylines; + } + + throw new Error(`Unknown topic ${topic}`); + }; + + return snapshotGetter; + } +} diff --git a/frontend/src/modules/3DViewerNew/view/utils/PolylinesPlugin.tsx b/frontend/src/modules/3DViewerNew/view/utils/PolylinesPlugin.tsx new file mode 100644 index 000000000..65e45d7a6 --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/utils/PolylinesPlugin.tsx @@ -0,0 +1,526 @@ +import addPathIcon from "@assets/add_path.svg"; +import continuePathIcon from "@assets/continue_path.svg"; +import removePathIcon from "@assets/remove_path.svg"; +import { Layer, PickingInfo } from "@deck.gl/core"; +import { PublishSubscribe, PublishSubscribeDelegate } from "@modules/_shared/utils/PublishSubscribeDelegate"; +import { Edit, Remove } from "@mui/icons-material"; + +import { isEqual } from "lodash"; +import { v4 } from "uuid"; + +import { ContextMenuItem, DeckGlInstanceManager, DeckGlPlugin } from "./DeckGlInstanceManager"; + +import { + AllowHoveringOf, + EditablePolylineLayer, + isEditablePolylineLayerPickingInfo, +} from "../hooks/editablePolylines/deckGlLayers/EditablePolylineLayer"; +import { PolylinesLayer, isPolylinesLayerPickingInfo } from "../hooks/editablePolylines/deckGlLayers/PolylinesLayer"; + +export type Polyline = { + id: string; + name: string; + color: [number, number, number]; + path: number[][]; +}; + +export enum PolylineEditingMode { + DRAW = "draw", + ADD_POINT = "add_point", + REMOVE_POINT = "remove_point", + NONE = "none", + IDLE = "idle", +} + +export enum PolylinesPluginTopic { + EDITING_POLYLINE_ID = "editing_polyline_id", + EDITING_MODE = "editing_mode", + POLYLINES = "polylines", +} + +export type PolylinesPluginTopicPayloads = { + [PolylinesPluginTopic.EDITING_MODE]: PolylineEditingMode; + [PolylinesPluginTopic.EDITING_POLYLINE_ID]: string | null; + [PolylinesPluginTopic.POLYLINES]: Polyline[]; +}; + +enum AppendToPathLocation { + START = "start", + END = "end", +} + +function* defaultColorGenerator() { + const colors: [number, number, number][] = [ + [255, 0, 0], + [0, 255, 0], + [0, 0, 255], + [255, 255, 0], + [255, 0, 255], + [0, 255, 255], + ]; + + let index = 0; + while (true) { + yield colors[index]; + index = (index + 1) % colors.length; + } +} + +export class PolylinesPlugin extends DeckGlPlugin implements PublishSubscribe { + private _currentEditingPolylineId: string | null = null; + private _currentEditingPolylinePathReferencePointIndex: number | null = null; + private _polylines: Polyline[] = []; + private _editingMode: PolylineEditingMode = PolylineEditingMode.NONE; + private _draggedPathPointIndex: number | null = null; + private _appendToPathLocation: AppendToPathLocation = AppendToPathLocation.END; + private _selectedPolylineId: string | null = null; + private _hoverPoint: number[] | null = null; + private _colorGenerator: Generator<[number, number, number]>; + + private _publishSubscribeDelegate = new PublishSubscribeDelegate(); + + private setCurrentEditingPolylineId(id: string | null): void { + this._currentEditingPolylineId = id; + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.EDITING_POLYLINE_ID); + } + + constructor(manager: DeckGlInstanceManager, colorGenerator?: Generator<[number, number, number]>) { + super(manager); + this._colorGenerator = colorGenerator ?? defaultColorGenerator(); + } + + getActivePolyline(): Polyline | undefined { + return this._polylines.find((polyline) => polyline.id === this._currentEditingPolylineId); + } + + getPolylines(): Polyline[] { + return this._polylines; + } + + setPolylines(polylines: readonly Polyline[]): void { + if (isEqual(this._polylines, polylines)) { + return; + } + this._polylines = [...polylines]; + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + } + + setActivePolylineName(name: string): void { + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + this._polylines = this._polylines.map((polyline) => { + if (polyline.id === activePolyline.id) { + return { + ...polyline, + name, + }; + } + return polyline; + }); + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + } + + getPublishSubscribeDelegate(): PublishSubscribeDelegate { + return this._publishSubscribeDelegate; + } + + setEditingMode(mode: PolylineEditingMode): void { + this._editingMode = mode; + this._hoverPoint = null; + this._currentEditingPolylinePathReferencePointIndex = null; + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.EDITING_MODE); + this.requireRedraw(); + } + + getEditingMode(): PolylineEditingMode { + return this._editingMode; + } + + getCurrentEditingPolylineId(): string | null { + return this._currentEditingPolylineId; + } + + handleKeyUpEvent(key: string): void { + if (key === "Escape") { + if (this._editingMode === PolylineEditingMode.NONE) { + this._currentEditingPolylinePathReferencePointIndex = null; + this.requireRedraw(); + return; + } + if (this._editingMode === PolylineEditingMode.IDLE) { + this._currentEditingPolylinePathReferencePointIndex = null; + this._hoverPoint = null; + this.requireRedraw(); + return; + } + + this._hoverPoint = null; + this.setEditingMode(PolylineEditingMode.IDLE); + this.requireRedraw(); + return; + } + if (key === "Delete") { + if (this._editingMode === PolylineEditingMode.IDLE) { + if (this._selectedPolylineId) { + this._polylines = this._polylines.filter((polyline) => polyline.id !== this._selectedPolylineId); + this._selectedPolylineId = null; + this.requireRedraw(); + } + return; + } + } + } + + handleLayerClick(pickingInfo: PickingInfo): void { + if (this._editingMode === PolylineEditingMode.NONE || this._editingMode === PolylineEditingMode.IDLE) { + if (isPolylinesLayerPickingInfo(pickingInfo)) { + this._selectedPolylineId = pickingInfo.polylineId ?? null; + this.requireRedraw(); + } + return; + } + + if (!isEditablePolylineLayerPickingInfo(pickingInfo)) { + return; + } + + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + if (pickingInfo.editableEntity?.type === "point") { + if (![PolylineEditingMode.DRAW, PolylineEditingMode.REMOVE_POINT].includes(this._editingMode)) { + return; + } + + const index = pickingInfo.editableEntity.index; + if (this._editingMode === PolylineEditingMode.DRAW) { + if ( + (index === 0 || index === activePolyline.path.length - 1) && + this._currentEditingPolylinePathReferencePointIndex !== index + ) { + this._appendToPathLocation = index === 0 ? AppendToPathLocation.START : AppendToPathLocation.END; + this._currentEditingPolylinePathReferencePointIndex = index; + this.requireRedraw(); + return; + } + } + + const newPath = activePolyline.path.filter((_, i) => i !== index); + let newReferencePathPointIndex: number | null = null; + if (this._currentEditingPolylinePathReferencePointIndex !== null) { + newReferencePathPointIndex = Math.max(0, this._currentEditingPolylinePathReferencePointIndex - 1); + if (index > this._currentEditingPolylinePathReferencePointIndex) { + newReferencePathPointIndex = this._currentEditingPolylinePathReferencePointIndex; + } + if (activePolyline.path.length - 1 < 1) { + newReferencePathPointIndex = null; + } + } + + this.updateActivePolylinePath(newPath); + this._currentEditingPolylinePathReferencePointIndex = newReferencePathPointIndex; + this.requireRedraw(); + return; + } + + if (pickingInfo.editableEntity?.type === "line") { + if (![PolylineEditingMode.DRAW, PolylineEditingMode.ADD_POINT].includes(this._editingMode)) { + return; + } + + if (!pickingInfo.coordinate) { + return; + } + + const index = pickingInfo.editableEntity.index; + const newPath = [...activePolyline.path]; + newPath.splice(index + 1, 0, [...pickingInfo.coordinate]); + this.updateActivePolylinePath(newPath); + + let newReferencePathPointIndex: number | null = null; + if (this._currentEditingPolylinePathReferencePointIndex !== null) { + newReferencePathPointIndex = this._currentEditingPolylinePathReferencePointIndex + 1; + if (index > this._currentEditingPolylinePathReferencePointIndex) { + newReferencePathPointIndex = this._currentEditingPolylinePathReferencePointIndex; + } + } + + this._currentEditingPolylinePathReferencePointIndex = newReferencePathPointIndex; + this.requireRedraw(); + } + } + + private updateActivePolylinePath(newPath: number[][]): void { + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + this._polylines = this._polylines.map((polyline) => { + if (polyline.id === activePolyline.id) { + return { + ...polyline, + path: newPath, + }; + } + return polyline; + }); + + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + } + + handleClickAway(): void { + this._selectedPolylineId = null; + this.requireRedraw(); + } + + handleGlobalMouseHover(pickingInfo: PickingInfo): void { + if (this._editingMode !== PolylineEditingMode.DRAW) { + return; + } + + if (!pickingInfo.coordinate) { + return; + } + + this._hoverPoint = pickingInfo.coordinate; + this.requireRedraw(); + } + + handleGlobalMouseClick(pickingInfo: PickingInfo): boolean { + if (this._editingMode === PolylineEditingMode.NONE) { + return false; + } + + if (!pickingInfo.coordinate) { + return false; + } + + const activePolyline = this.getActivePolyline(); + if (!activePolyline && this._editingMode === PolylineEditingMode.DRAW) { + const id = v4(); + this._polylines.push({ + id, + name: "New polyline", + color: this._colorGenerator.next().value, + path: [[...pickingInfo.coordinate]], + }); + this._polylines = [...this._polylines]; + this._currentEditingPolylinePathReferencePointIndex = 0; + this.setCurrentEditingPolylineId(id); + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + } else if (activePolyline) { + if (this._currentEditingPolylinePathReferencePointIndex === null) { + this.setCurrentEditingPolylineId(null); + this.setEditingMode(PolylineEditingMode.IDLE); + this.requireRedraw(); + return true; + } + + if (this._editingMode === PolylineEditingMode.DRAW) { + this.appendToActivePolylinePath(pickingInfo.coordinate); + this.requireRedraw(); + return true; + } + } + + return false; + } + + private appendToActivePolylinePath(point: number[]): void { + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + const newPath = [...activePolyline.path]; + if (this._appendToPathLocation === AppendToPathLocation.START) { + newPath.unshift(point); + this._currentEditingPolylinePathReferencePointIndex = 0; + } else { + newPath.push(point); + this._currentEditingPolylinePathReferencePointIndex = newPath.length - 1; + } + + this.updateActivePolylinePath(newPath); + } + + handleDragStart(pickingInfo: PickingInfo): void { + if (!isEditablePolylineLayerPickingInfo(pickingInfo)) { + return; + } + + if (pickingInfo.editableEntity?.type === "point") { + this._draggedPathPointIndex = pickingInfo.index; + this.requestDisablePanning(); + } + } + + handleDrag(pickingInfo: PickingInfo): void { + if (this._draggedPathPointIndex === null || !pickingInfo.coordinate) { + return; + } + + const activePolyline = this.getActivePolyline(); + if (!activePolyline) { + return; + } + + // Take first layer under cursor to get coordinates for the polyline point + // An alternative would be to store a reference to the layer the polyline was first created upon + // and always try to use that layer to get the coordinates + const layerUnderCursor = this.getFirstLayerUnderCursorInfo(pickingInfo.x, pickingInfo.y); + if (!layerUnderCursor || !layerUnderCursor.coordinate) { + return; + } + + const newPath = [...activePolyline.path]; + newPath[this._draggedPathPointIndex] = [...layerUnderCursor.coordinate]; + this.updateActivePolylinePath(newPath); + this.requireRedraw(); + } + + handleDragEnd(): void { + this._draggedPathPointIndex = null; + this.requestEnablePanning(); + } + + getCursor(pickingInfo: PickingInfo): string | null { + if (this._editingMode === PolylineEditingMode.NONE) { + return null; + } + + const activePolyline = this.getActivePolyline(); + + if (isEditablePolylineLayerPickingInfo(pickingInfo) && pickingInfo.editableEntity) { + if ( + [PolylineEditingMode.DRAW, PolylineEditingMode.ADD_POINT].includes(this._editingMode) && + pickingInfo.editableEntity.type === "line" + ) { + return `url(${addPathIcon}) 4 2, crosshair`; + } + + if ( + activePolyline && + [PolylineEditingMode.DRAW, PolylineEditingMode.REMOVE_POINT].includes(this._editingMode) && + pickingInfo.editableEntity.type === "point" + ) { + const index = pickingInfo.index; + if ( + (index === 0 || index === activePolyline.path.length - 1) && + index !== this._currentEditingPolylinePathReferencePointIndex && + this._editingMode === PolylineEditingMode.DRAW + ) { + return `url(${continuePathIcon}) 4 2, crosshair`; + } + + return `url(${removePathIcon}) 4 2, crosshair`; + } + + if (this._editingMode === PolylineEditingMode.IDLE && pickingInfo.editableEntity.type === "point") { + return "grab"; + } + } + + return "auto"; + } + + getContextMenuItems(pickingInfo: PickingInfo): ContextMenuItem[] { + if (this._editingMode !== PolylineEditingMode.IDLE) { + return []; + } + + if (!isPolylinesLayerPickingInfo(pickingInfo) || !pickingInfo.polylineId) { + return []; + } + + return [ + { + icon: , + label: "Edit", + onClick: () => { + this.setCurrentEditingPolylineId(pickingInfo.polylineId ?? null); + this.requireRedraw(); + }, + }, + { + icon: , + label: "Delete", + onClick: () => { + this._polylines = this._polylines.filter((polyline) => polyline.id !== pickingInfo.polylineId); + this.setCurrentEditingPolylineId(null); + this._publishSubscribeDelegate.notifySubscribers(PolylinesPluginTopic.POLYLINES); + this.requireRedraw(); + }, + }, + ]; + } + + getLayers(): Layer[] { + const layers: Layer[] = [ + new PolylinesLayer({ + id: "polylines-layer", + polylines: this._polylines.filter((polyline) => polyline.id !== this._currentEditingPolylineId), + selectedPolylineId: + this._editingMode === PolylineEditingMode.NONE + ? undefined + : (this._selectedPolylineId ?? undefined), + hoverable: this._editingMode === PolylineEditingMode.IDLE, + }), + ]; + + let allowHoveringOf = AllowHoveringOf.NONE; + if ([PolylineEditingMode.DRAW, PolylineEditingMode.ADD_POINT].includes(this._editingMode)) { + allowHoveringOf = AllowHoveringOf.LINES_AND_POINTS; + } + if (this._editingMode === PolylineEditingMode.REMOVE_POINT) { + allowHoveringOf = AllowHoveringOf.POINTS; + } + + const activePolyline = this.getActivePolyline(); + if (activePolyline) { + layers.push( + new EditablePolylineLayer({ + id: "editable-polylines-layer", + polyline: activePolyline, + mouseHoverPoint: this._hoverPoint ?? undefined, + referencePathPointIndex: + this._editingMode === PolylineEditingMode.DRAW + ? (this._currentEditingPolylinePathReferencePointIndex ?? undefined) + : undefined, + onDragStart: this.handleDragStart.bind(this), + onDragEnd: this.handleDragEnd.bind(this), + allowHoveringOf, + }), + ); + } + + return layers; + } + + makeSnapshotGetter(topic: T): () => PolylinesPluginTopicPayloads[T] { + const snapshotGetter = (): any => { + if (topic === PolylinesPluginTopic.EDITING_MODE) { + return this._editingMode; + } + if (topic === PolylinesPluginTopic.EDITING_POLYLINE_ID) { + return this._currentEditingPolylineId; + } + if (topic === PolylinesPluginTopic.POLYLINES) { + return this._polylines; + } + + throw new Error(`Unknown topic ${topic}`); + }; + + return snapshotGetter; + } +} diff --git a/frontend/src/modules/3DViewerNew/view/view.tsx b/frontend/src/modules/3DViewerNew/view/view.tsx new file mode 100644 index 000000000..5ebcc729c --- /dev/null +++ b/frontend/src/modules/3DViewerNew/view/view.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +import { ModuleViewProps } from "@framework/Module"; + +import { LayersWrapper } from "./components/LayersWrapper"; + +import { Interfaces } from "../interfaces"; + +export function View(props: ModuleViewProps): React.ReactNode { + const preferredViewLayout = props.viewContext.useSettingsToViewInterfaceValue("preferredViewLayout"); + const layerManager = props.viewContext.useSettingsToViewInterfaceValue("layerManager"); + const fieldIdentifier = props.viewContext.useSettingsToViewInterfaceValue("fieldIdentifier"); + + if (!layerManager) { + return null; + } + + return ( + + ); +} diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/dataProviderTypes.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/dataProviderTypes.ts index 1c94c8063..6d1af01e2 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/dataProviderTypes.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/dataProviderTypes.ts @@ -1,4 +1,15 @@ export enum DataProviderType { DRILLED_WELLBORE_PICKS = "DRILLED_WELLBORE_PICKS", DRILLED_WELL_TRAJECTORIES = "DRILLED_WELL_TRAJECTORIES", + REALIZATION_GRID = "REALIZATION_GRID", + REALIZATION_SURFACE_2D = "REALIZATION_SURFACE_2D", + REALIZATION_SURFACE_3D = "REALIZATION_SURFACE_3D", + REALIZATION_POLYGONS = "REALIZATION_POLYGONS", + REALIZATION_SEISMIC_DEPTH_SLICE = "REALIZATION_SEISMIC_DEPTH_SLICE", + REALIZATION_SEISMIC_INLINE = "REALIZATION_SEISMIC_INLINE", + REALIZATION_SEISMIC_CROSSLINE = "REALIZATION_SEISMIC_CROSSLINE", + OBSERVED_SURFACE_2D = "OBSERVED_SURFACE_2D", + INTERSECTION_REALIZATION_GRID = "INTERSECTION_REALIZATION_GRID", + STATISTICAL_SURFACE_2D = "STATISTICAL_SURFACE_2D", + STATISTICAL_SURFACE_3D = "STATISTICAL_SURFACE_3D", } diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellTrajectoriesProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellTrajectoriesProvider.ts index 8d91ebda2..8cabbfa9a 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellTrajectoriesProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellTrajectoriesProvider.ts @@ -5,7 +5,6 @@ import { getDrilledWellboreHeadersOptions, getWellTrajectoriesOptions } from "@a import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; - import type { CustomDataProviderImplementation, FetchDataParams, diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellborePicksProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellborePicksProvider.ts index d7c01a733..b50c4b8b1 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellborePicksProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/DrilledWellborePicksProvider.ts @@ -9,7 +9,6 @@ import { import type { MakeSettingTypesMap } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; import { Setting } from "@modules/_shared/DataProviderFramework/settings/settingsDefinitions"; - import type { CustomDataProviderImplementation, DataProviderInformationAccessors, @@ -60,11 +59,9 @@ export class DrilledWellborePicksProvider registerQueryKey(queryOptions.queryKey); - const promise = queryClient - .fetchQuery(queryOptions) - .then((response: WellborePick_api[]) => { - return response.filter((trajectory) => selectedWellboreUuids.includes(trajectory.wellboreUuid)); - }); + const promise = queryClient.fetchQuery(queryOptions).then((response: WellborePick_api[]) => { + return response.filter((trajectory) => selectedWellboreUuids.includes(trajectory.wellboreUuid)); + }); return promise; } diff --git a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts index 5a6e24635..e8965121c 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/dataProviders/implementations/IntersectionRealizationGridProvider.ts @@ -32,8 +32,10 @@ const intersectionRealizationGridSettings = [ Setting.GRID_NAME, Setting.TIME_OR_INTERVAL, Setting.SHOW_GRID_LINES, + Setting.COLOR_SCALE, + Setting.SHOW_GRID_LINES, ] as const; -type IntersectionRealizationGridSettings = typeof intersectionRealizationGridSettings; +export type IntersectionRealizationGridSettings = typeof intersectionRealizationGridSettings; type SettingsWithTypes = MakeSettingTypesMap; export type IntersectionRealizationGridData = PolylineIntersection_trans; diff --git a/frontend/src/modules/_shared/DataProviderFramework/delegates/GroupDelegate.ts b/frontend/src/modules/_shared/DataProviderFramework/delegates/GroupDelegate.ts index e6ac1fe27..863b9a3c5 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/delegates/GroupDelegate.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/delegates/GroupDelegate.ts @@ -1,4 +1,3 @@ - import type { PublishSubscribe } from "../../utils/PublishSubscribeDelegate"; import { PublishSubscribeDelegate } from "../../utils/PublishSubscribeDelegate"; import { DataProvider } from "../framework/DataProvider/DataProvider"; @@ -315,4 +314,11 @@ export class GroupDelegate implements PublishSubscribe implements PublishSubscribe { - private _owner: DataProvider; private _customSettingsHandler: CustomSettingsHandler< TSettings, TStoredData, @@ -73,17 +64,19 @@ export class SettingsContextDelegate< TStoredDataKey >; private _dataProviderManager: DataProviderManager; - private _settings: { [K in TSettingKey]: SettingManager } = {} as { - [K in TSettingKey]: SettingManager; + private _settings: { [K in TSettingKey]: SettingManager } = {} as { + [K in TSettingKey]: SettingManager; }; private _publishSubscribeDelegate = new PublishSubscribeDelegate(); private _unsubscribeHandler: UnsubscribeHandlerDelegate = new UnsubscribeHandlerDelegate(); private _status: SettingsContextStatus = SettingsContextStatus.LOADING; private _storedData: NullableStoredData = {} as NullableStoredData; + private _storedDataLoadingStatus: { [K in TStoredDataKey]: boolean } = {} as { + [K in TStoredDataKey]: boolean; + }; private _dependencies: Dependency[] = []; constructor( - owner: DataProvider, customSettingsHandler: CustomSettingsHandler< TSettings, TStoredData, @@ -94,44 +87,37 @@ export class SettingsContextDelegate< dataProviderManager: DataProviderManager, settings: { [K in TSettingKey]: SettingManager }, ) { - this._owner = owner; this._customSettingsHandler = customSettingsHandler; this._dataProviderManager = dataProviderManager; - for (const key in settings) { + this._settings = settings; + + this._unsubscribeHandler.registerUnsubscribeFunction( + "dependencies", + this.getDataProviderManager() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(DataProviderManagerTopic.GLOBAL_SETTINGS)(() => { + this.handleSettingChanged(); + }), + ); + + for (const key in this._settings) { this._unsubscribeHandler.registerUnsubscribeFunction( "settings", - settings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.VALUE)(() => { + this._settings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.VALUE)(() => { this.handleSettingChanged(); }), ); this._unsubscribeHandler.registerUnsubscribeFunction( "settings", - settings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.IS_LOADING)(() => { - this.handleSettingsLoadingStateChanged(); - }), - ); - this._unsubscribeHandler.registerUnsubscribeFunction( - "data-provider-manager", - dataProviderManager - .getPublishSubscribeDelegate() - .makeSubscriberFunction(DataProviderManagerTopic.SHARED_SETTINGS_CHANGED)(() => { - this.handleSharedSettingsChanged(); - }), - ); - - this._unsubscribeHandler.registerUnsubscribeFunction( - "data-provider-manager", - dataProviderManager - .getPublishSubscribeDelegate() - .makeSubscriberFunction(DataProviderManagerTopic.ITEMS)(() => { - this.handleSharedSettingsChanged(); - }), + this._settings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.IS_LOADING)( + () => { + this.handleSettingsLoadingStateChanged(); + }, + ), ); } - this._settings = settings; - this.createDependencies(); } @@ -158,26 +144,6 @@ export class SettingsContextDelegate< return settings; } - handleSharedSettingsChanged() { - const parentGroup = this._owner.getItemDelegate().getParentGroup(); - if (!parentGroup) { - return; - } - - const sharedSettingsProviders: SharedSettingsProvider[] = parentGroup.getAncestorAndSiblingItems( - (item) => item instanceof SharedSetting, - ) as unknown as SharedSettingsProvider[]; - - const ancestorGroups: SharedSettingsProvider[] = parentGroup.getAncestors( - (item) => item instanceof Group && instanceofSharedSettingsProvider(item), - ) as unknown as SharedSettingsProvider[]; - sharedSettingsProviders.push(...ancestorGroups); - - for (const key in this._settings) { - this._settings[key].checkForOverrides(sharedSettingsProviders); - } - } - areCurrentSettingsValid(): boolean { for (const key in this._settings) { if (!this._settings[key].isValueValid()) { @@ -208,6 +174,16 @@ export class SettingsContextDelegate< return true; } + isAllStoredDataLoaded(): boolean { + for (const key in this._storedDataLoadingStatus) { + if (this._storedDataLoadingStatus[key]) { + return false; + } + } + + return true; + } + areAllSettingsInitialized(): boolean { for (const key in this._settings) { if (!this._settings[key].isInitialized() || this._settings[key].isPersistedValue()) { @@ -247,19 +223,13 @@ export class SettingsContextDelegate< setAvailableValues(key: K, availableValues: AvailableValuesType): void { const settingDelegate = this._settings[key]; settingDelegate.setAvailableValues(availableValues); - - this.getDataProviderManager().publishTopic(DataProviderManagerTopic.AVAILABLE_SETTINGS_CHANGED); } - setStoredData(key: K, data: TStoredData[K] | null): void { + setStoredData(key: K, data: TStoredData[K] | null): void { this._storedData[key] = data; + this._storedDataLoadingStatus[key] = false; - if (!this.areAllDependenciesLoaded()) { - this.setStatus(SettingsContextStatus.LOADING); - return; - } - - this._publishSubscribeDelegate.notifySubscribers(SettingsContextDelegateTopic.STORED_DATA_CHANGED); + this.handleSettingChanged(); } getSettings() { @@ -276,10 +246,7 @@ export class SettingsContextDelegate< makeSnapshotGetter(topic: T): () => SettingsContextDelegatePayloads[T] { const snapshotGetter = (): any => { - if (topic === SettingsContextDelegateTopic.SETTINGS_CHANGED) { - return; - } - if (topic === SettingsContextDelegateTopic.STORED_DATA_CHANGED) { + if (topic === SettingsContextDelegateTopic.SETTINGS_AND_STORED_DATA_CHANGED) { return; } if (topic === SettingsContextDelegateTopic.STATUS) { @@ -323,7 +290,8 @@ export class SettingsContextDelegate< const makeLocalSettingGetter = (key: K, handler: (value: TSettingTypes[K]) => void) => { const handleChange = (): void => { - handler(this._settings[key].getValue() as unknown as TSettingTypes[K]); + const setting = this._settings[key]; + handler(setting.getValue() as unknown as TSettingTypes[K]); }; this._unsubscribeHandler.registerUnsubscribeFunction( "dependencies", @@ -332,6 +300,17 @@ export class SettingsContextDelegate< ), ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "dependencies", + this._settings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.IS_LOADING)( + () => { + if (!this._settings[key].isLoading()) { + handleChange(); + } + }, + ), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( "dependencies", this._settings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.IS_PERSISTED)( @@ -359,14 +338,28 @@ export class SettingsContextDelegate< return handleChange; }; + const loadingStateGetter = (settingKey: K): boolean => { + return this._settings[settingKey].isLoading(); + }; + + const localSettingManagerGetter = (key: K): SettingManager => { + return this._settings[key]; + }; + + const globalSettingGetter = (key: K): GlobalSettings[K] => { + return this.getDataProviderManager.bind(this)().getGlobalSetting(key); + }; + const availableSettingsUpdater = ( settingKey: K, updateFunc: UpdateFunc, TSettings, TSettingTypes, TSettingKey>, ): Dependency, TSettings, TSettingTypes, TSettingKey> => { const dependency = new Dependency, TSettings, TSettingTypes, TSettingKey>( - this, + localSettingManagerGetter, + globalSettingGetter, updateFunc, makeLocalSettingGetter, + loadingStateGetter, makeGlobalSettingGetter, ); this._dependencies.push(dependency); @@ -377,14 +370,14 @@ export class SettingsContextDelegate< return; } this.setAvailableValues(settingKey, availableValues); + this.handleSettingChanged(); }); - dependency.subscribeLoading((loading: boolean, hasDependencies: boolean) => { - this._settings[settingKey].setLoading(loading); - - if (!hasDependencies && !loading) { - this.handleSettingChanged(); + dependency.subscribeLoading((loading: boolean) => { + if (loading) { + this._settings[settingKey].setLoading(loading); } + this.handleSettingChanged(); }); dependency.initialize(); @@ -397,9 +390,11 @@ export class SettingsContextDelegate< updateFunc: UpdateFunc, TSettings, TSettingTypes, TSettingKey>, ): Dependency, TSettings, TSettingTypes, TSettingKey> => { const dependency = new Dependency, TSettings, TSettingTypes, TSettingKey>( - this, + localSettingManagerGetter, + globalSettingGetter, updateFunc, makeLocalSettingGetter, + loadingStateGetter, makeGlobalSettingGetter, ); this._dependencies.push(dependency); @@ -425,13 +420,28 @@ export class SettingsContextDelegate< TSettings, TSettingTypes, TSettingKey - >(this, updateFunc, makeLocalSettingGetter, makeGlobalSettingGetter); + >( + localSettingManagerGetter, + globalSettingGetter, + updateFunc, + makeLocalSettingGetter, + loadingStateGetter, + makeGlobalSettingGetter, + ); this._dependencies.push(dependency); dependency.subscribe((storedData: TStoredData[K] | null) => { this.setStoredData(key, storedData); }); + dependency.subscribeLoading((loading: boolean) => { + if (loading) { + this._storedData[key] = null; + this._storedDataLoadingStatus[key] = loading; + this.handleSettingChanged(); + } + }); + dependency.initialize(); return dependency; @@ -448,13 +458,19 @@ export class SettingsContextDelegate< }) => T, ) => { const dependency = new Dependency( - this, + localSettingManagerGetter, + globalSettingGetter, update, makeLocalSettingGetter, + loadingStateGetter, makeGlobalSettingGetter, ); this._dependencies.push(dependency); + dependency.subscribeLoading(() => { + this.handleSettingChanged(); + }); + dependency.initialize(); return dependency; @@ -475,6 +491,14 @@ export class SettingsContextDelegate< beforeDestroy(): void { this._unsubscribeHandler.unsubscribeAll(); + for (const dependency of this._dependencies) { + dependency.beforeDestroy(); + } + this._dependencies = []; + for (const key in this._settings) { + this._settings[key].beforeDestroy(); + } + this._settings = {} as { [K in TSettingKey]: SettingManager }; } private setStatus(status: SettingsContextStatus) { @@ -487,18 +511,22 @@ export class SettingsContextDelegate< } private handleSettingChanged() { - if (!this.areAllSettingsLoaded() || !this.areAllDependenciesLoaded() || !this.areAllSettingsInitialized()) { + if (!this.areAllSettingsLoaded() || !this.areAllDependenciesLoaded() || !this.isAllStoredDataLoaded()) { this.setStatus(SettingsContextStatus.LOADING); return; } - if (this.isSomePersistedSettingNotValid() || !this.areCurrentSettingsValid()) { + if ( + this.isSomePersistedSettingNotValid() || + !this.areCurrentSettingsValid() || + !this.areAllSettingsInitialized() + ) { this.setStatus(SettingsContextStatus.INVALID_SETTINGS); return; } this.setStatus(SettingsContextStatus.VALID_SETTINGS); - this._publishSubscribeDelegate.notifySubscribers(SettingsContextDelegateTopic.SETTINGS_CHANGED); + this._publishSubscribeDelegate.notifySubscribers(SettingsContextDelegateTopic.SETTINGS_AND_STORED_DATA_CHANGED); } private handleSettingsLoadingStateChanged() { diff --git a/frontend/src/modules/_shared/DataProviderFramework/delegates/SharedSettingsDelegate.ts b/frontend/src/modules/_shared/DataProviderFramework/delegates/SharedSettingsDelegate.ts index c0e6458f3..96cca6d15 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/delegates/SharedSettingsDelegate.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/delegates/SharedSettingsDelegate.ts @@ -1,142 +1,184 @@ - -import { DataProvider } from "../framework/DataProvider/DataProvider"; -import { DataProviderManagerTopic } from "../framework/DataProviderManager/DataProviderManager"; -import { Group } from "../framework/Group/Group"; -import type { SettingManager } from "../framework/SettingManager/SettingManager"; -import { SettingTopic } from "../framework/SettingManager/SettingManager"; +import { DataProviderManagerTopic, type GlobalSettings } from "../framework/DataProviderManager/DataProviderManager"; +import { ExternalSettingController } from "../framework/ExternalSettingController/ExternalSettingController"; +import { SettingTopic, type SettingManager } from "../framework/SettingManager/SettingManager"; +import type { + DefineBasicDependenciesArgs, + SettingAttributes, + UpdateFunc, +} from "../interfacesAndTypes/customSettingsHandler"; import type { Item } from "../interfacesAndTypes/entities"; -import type { AvailableValuesType, SettingsKeysFromTuple } from "../interfacesAndTypes/utils"; -import type { SettingTypes, Settings } from "../settings/settingsDefinitions"; -import { settingCategoryAvailableValuesIntersectionReducerMap } from "../settings/settingsDefinitions"; +import type { SettingsKeysFromTuple } from "../interfacesAndTypes/utils"; +import type { MakeSettingTypesMap, SettingTypes, Settings } from "../settings/settingsDefinitions"; +import { Dependency } from "./_utils/Dependency"; import { UnsubscribeHandlerDelegate } from "./UnsubscribeHandlerDelegate"; export class SharedSettingsDelegate< TSettings extends Settings, + TSettingTypes extends MakeSettingTypesMap = MakeSettingTypesMap, TSettingKey extends SettingsKeysFromTuple = SettingsKeysFromTuple, > { - private _parentItem: Item; - private _wrappedSettings: { [K in TSettingKey]: SettingManager } = {} as { - [K in TSettingKey]: SettingManager; + private _externalSettingControllers: { [K in TSettingKey]: ExternalSettingController } = {} as { + [K in TSettingKey]: ExternalSettingController; + }; + private _wrappedSettings: { [K in TSettingKey]: SettingManager } = {} as { + [K in TSettingKey]: SettingManager; }; private _unsubscribeHandler: UnsubscribeHandlerDelegate = new UnsubscribeHandlerDelegate(); - - constructor(parentItem: Item, wrappedSettings: { [K in TSettingKey]: SettingManager }) { + private _dependencies: Dependency[] = []; + private _parentItem: Item; + private _customDependenciesDefinition: + | ((args: DefineBasicDependenciesArgs) => void) + | null = null; + + constructor( + parentItem: Item, + wrappedSettings: { [K in TSettingKey]: SettingManager }, + customDependenciesDefinition?: ( + args: DefineBasicDependenciesArgs, + ) => void, + ) { this._wrappedSettings = wrappedSettings; this._parentItem = parentItem; + this._customDependenciesDefinition = customDependenciesDefinition ?? null; + + for (const key in wrappedSettings) { + const setting = wrappedSettings[key]; + const externalSettingController = new ExternalSettingController(parentItem, setting); + this._externalSettingControllers[key] = externalSettingController; + } const dataProviderManager = parentItem.getItemDelegate().getDataProviderManager(); if (!dataProviderManager) { throw new Error("SharedSettingDelegate must have a parent item with a data provider manager."); } - for (const key in wrappedSettings) { - this._unsubscribeHandler.registerUnsubscribeFunction( - "setting", - wrappedSettings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.VALUE)(() => { - this.publishValueChange(); - }), - ); - } - - this._unsubscribeHandler.registerUnsubscribeFunction( - "data-provider-manager", - dataProviderManager.getPublishSubscribeDelegate().makeSubscriberFunction(DataProviderManagerTopic.ITEMS)( - () => { - this.makeIntersectionOfAvailableValues(); - }, - ), - ); - this._unsubscribeHandler.registerUnsubscribeFunction( - "data-provider-manager", - dataProviderManager - .getPublishSubscribeDelegate() - .makeSubscriberFunction(DataProviderManagerTopic.SETTINGS_CHANGED)(() => { - this.makeIntersectionOfAvailableValues(); - }), - ); - this._unsubscribeHandler.registerUnsubscribeFunction( - "data-provider-manager", - dataProviderManager - .getPublishSubscribeDelegate() - .makeSubscriberFunction(DataProviderManagerTopic.AVAILABLE_SETTINGS_CHANGED)(() => { - this.makeIntersectionOfAvailableValues(); - }), - ); + this.createDependencies(); } - getWrappedSettings(): { [K in TSettingKey]: SettingManager } { + getWrappedSettings(): { [K in TSettingKey]: SettingManager } { return this._wrappedSettings; } - publishValueChange(): void { - const dataProviderManager = this._parentItem.getItemDelegate().getDataProviderManager(); - if (dataProviderManager) { - dataProviderManager.publishTopic(DataProviderManagerTopic.SHARED_SETTINGS_CHANGED); - } + unsubscribeAll(): void { + this._unsubscribeHandler.unsubscribeAll(); } - private makeIntersectionOfAvailableValues(): void { - let parentGroup = this._parentItem.getItemDelegate().getParentGroup(); - if (this._parentItem instanceof Group) { - parentGroup = this._parentItem.getGroupDelegate(); + beforeDestroy(): void { + this._unsubscribeHandler.unsubscribeAll(); + for (const key in this._externalSettingControllers) { + const externalSettingController = this._externalSettingControllers[key]; + externalSettingController.beforeDestroy(); } - - if (!parentGroup) { - return; + for (const key in this._wrappedSettings) { + const setting = this._wrappedSettings[key]; + setting.beforeDestroy(); + } + for (const dependency of this._dependencies) { + dependency.beforeDestroy(); } + this._dependencies = []; + } - const providers = parentGroup.getDescendantItems((item) => item instanceof DataProvider) as DataProvider< - any, - any - >[]; - const availableValuesMap: { [K in TSettingKey]: AvailableValuesType } = {} as { - [K in TSettingKey]: AvailableValuesType; - }; - const indices: { [K in TSettingKey]: number } = {} as { [K in TSettings[number]]: number }; - - for (const provider of providers) { - for (const key in this._wrappedSettings) { - const wrappedSetting = this._wrappedSettings[key]; - const category = wrappedSetting.getCategory(); - const index = indices[key] ?? 0; - const setting = provider.getSettingsContextDelegate().getSettings()[wrappedSetting.getType()]; - if (setting) { - if (setting.isLoading()) { - wrappedSetting.setLoading(true); - continue; - } + createDependencies(): void { + this._unsubscribeHandler.unsubscribe("dependencies"); - if (setting.getAvailableValues() === null) { - continue; - } + this._dependencies = []; - const reducerDefinition = settingCategoryAvailableValuesIntersectionReducerMap[category]; - if (reducerDefinition) { - const { reducer, startingValue } = reducerDefinition; - if (index === 0) { - availableValuesMap[key] = startingValue as AvailableValuesType; - } - availableValuesMap[key] = reducer( - availableValuesMap[key] as any, - setting.getAvailableValues(), - index, - ) as AvailableValuesType; + const makeLocalSettingGetter = (key: K, handler: (value: TSettingTypes[K]) => void) => { + const handleChange = (): void => { + const setting = this._wrappedSettings[key]; + handler(setting.getValue() as unknown as TSettingTypes[K]); + }; + this._unsubscribeHandler.registerUnsubscribeFunction( + "dependencies", + this._wrappedSettings[key].getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.VALUE)( + handleChange, + ), + ); + + this._unsubscribeHandler.registerUnsubscribeFunction( + "dependencies", + this._wrappedSettings[key] + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.IS_LOADING)(() => { + if (!this._wrappedSettings[key].isLoading()) { + handleChange(); } - indices[key] = index + 1; + }), + ); + + this._unsubscribeHandler.registerUnsubscribeFunction( + "dependencies", + this._wrappedSettings[key] + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.IS_PERSISTED)(handleChange), + ); + + return handleChange; + }; + + const makeGlobalSettingGetter = ( + key: K, + handler: (value: GlobalSettings[K]) => void, + ) => { + const handleChange = (): void => { + handler(this._parentItem.getItemDelegate().getDataProviderManager().getGlobalSetting(key)); + }; + this._unsubscribeHandler.registerUnsubscribeFunction( + "dependencies", + this._parentItem + .getItemDelegate() + .getDataProviderManager() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(DataProviderManagerTopic.GLOBAL_SETTINGS)(handleChange), + ); + + return handleChange.bind(this); + }; + + const loadingStateGetter = (settingKey: K): boolean => { + return this._wrappedSettings[settingKey].isLoading(); + }; + + const localSettingManagerGetter = (key: K): SettingManager => { + return this._wrappedSettings[key]; + }; + + const globalSettingGetter = (key: K): GlobalSettings[K] => { + return this._parentItem.getItemDelegate().getDataProviderManager().getGlobalSetting(key); + }; + + const settingAttributesUpdater = ( + settingKey: K, + updateFunc: UpdateFunc, TSettings, TSettingTypes, TSettingKey>, + ): Dependency, TSettings, TSettingTypes, TSettingKey> => { + const dependency = new Dependency, TSettings, TSettingTypes, TSettingKey>( + localSettingManagerGetter.bind(this), + globalSettingGetter.bind(this), + updateFunc, + makeLocalSettingGetter, + loadingStateGetter, + makeGlobalSettingGetter, + ); + this._dependencies.push(dependency); + + dependency.subscribe((attributes: Partial | null) => { + if (attributes === null) { + return; } - } - } + this._wrappedSettings[settingKey].updateAttributes(attributes); + }); - for (const key in this._wrappedSettings) { - const wrappedSetting = this._wrappedSettings[key]; - wrappedSetting.setLoading(false); - wrappedSetting.setAvailableValues(availableValuesMap[key] ?? []); - this.publishValueChange(); - } - } + dependency.initialize(); - unsubscribeAll(): void { - this._unsubscribeHandler.unsubscribeAll(); + return dependency; + }; + + if (this._customDependenciesDefinition) { + this._customDependenciesDefinition({ + settingAttributesUpdater: settingAttributesUpdater.bind(this), + }); + } } } diff --git a/frontend/src/modules/_shared/DataProviderFramework/delegates/_utils/Dependency.ts b/frontend/src/modules/_shared/DataProviderFramework/delegates/_utils/Dependency.ts index 56e520342..ca769ac98 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/delegates/_utils/Dependency.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/delegates/_utils/Dependency.ts @@ -1,13 +1,12 @@ import { isCancelledError } from "@tanstack/react-query"; -import { isEqual } from "lodash"; import type { GlobalSettings } from "../../framework/DataProviderManager/DataProviderManager"; -import { SettingTopic } from "../../framework/SettingManager/SettingManager"; -import { CancelUpdate } from "../../interfacesAndTypes/customSettingsHandler"; +import { SettingTopic, type SettingManager } from "../../framework/SettingManager/SettingManager"; import type { UpdateFunc } from "../../interfacesAndTypes/customSettingsHandler"; import type { SettingsKeysFromTuple } from "../../interfacesAndTypes/utils"; import type { MakeSettingTypesMap, Settings } from "../../settings/settingsDefinitions"; -import type { SettingsContextDelegate } from "../SettingsContextDelegate"; + +class DependencyLoadingError extends Error {} /* * Dependency class is used to represent a node in the dependency graph of a data provider settings context. @@ -31,9 +30,11 @@ export class Dependency< private _loadingDependencies: Set<(loading: boolean, hasDependencies: boolean) => void> = new Set(); private _isLoading = false; - private _contextDelegate: SettingsContextDelegate; + private _localSettingManagerGetter: (key: K) => SettingManager; + private _globalSettingGetter: (key: K) => GlobalSettings[K]; private _makeLocalSettingGetter: (key: K, handler: (value: TSettingTypes[K]) => void) => void; + private _localSettingLoadingStateGetter: (key: K) => boolean; private _makeGlobalSettingGetter: ( key: K, handler: (value: GlobalSettings[K]) => void, @@ -48,17 +49,21 @@ export class Dependency< private _numChildDependencies = 0; constructor( - contextDelegate: SettingsContextDelegate, + localSettingManagerGetter: (key: K) => SettingManager, + globalSettingGetter: (key: K) => GlobalSettings[K], updateFunc: UpdateFunc, makeLocalSettingGetter: (key: K, handler: (value: TSettingTypes[K]) => void) => void, + localSettingLoadingStateGetter: (key: K) => boolean, makeGlobalSettingGetter: ( key: K, handler: (value: GlobalSettings[K]) => void, ) => void, ) { - this._contextDelegate = contextDelegate; + this._localSettingManagerGetter = localSettingManagerGetter; + this._globalSettingGetter = globalSettingGetter; this._updateFunc = updateFunc; this._makeLocalSettingGetter = makeLocalSettingGetter; + this._localSettingLoadingStateGetter = localSettingLoadingStateGetter; this._makeGlobalSettingGetter = makeGlobalSettingGetter; this.getGlobalSetting = this.getGlobalSetting.bind(this); @@ -66,6 +71,13 @@ export class Dependency< this.getHelperDependency = this.getHelperDependency.bind(this); } + beforeDestroy() { + this._abortController?.abort(); + this._abortController = null; + this._dependencies.clear(); + this._loadingDependencies.clear(); + } + hasChildDependencies(): boolean { return this._numChildDependencies > 0; } @@ -106,19 +118,23 @@ export class Dependency< this._numParentDependencies++; } + if (this._localSettingLoadingStateGetter(settingName)) { + throw new DependencyLoadingError("Setting is loading"); + } + // If the dependency has already subscribed to this setting, return the cached value // that is updated when the setting changes if (this._cachedSettingsMap.has(settingName as string)) { return this._cachedSettingsMap.get(settingName as string); } - const setting = this._contextDelegate.getSettings()[settingName]; + const setting = this._localSettingManagerGetter(settingName); const value = setting.getValue(); this._cachedSettingsMap.set(settingName as string, value); this._makeLocalSettingGetter(settingName, (value) => { this._cachedSettingsMap.set(settingName as string, value); - this.callUpdateFunc(); + this.invalidate(); }); setting.getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.IS_LOADING)(() => { @@ -157,18 +173,11 @@ export class Dependency< } this._makeGlobalSettingGetter(settingName, (value) => { - const cachedValue = this._cachedGlobalSettingsMap.get(settingName as string); - if (isEqual(value, cachedValue)) { - return; - } this._cachedGlobalSettingsMap.set(settingName as string, value); - this.callUpdateFunc(); + this.invalidate(); }); - this._cachedGlobalSettingsMap.set( - settingName as string, - this._contextDelegate.getDataProviderManager().getGlobalSetting(settingName), - ); + this._cachedGlobalSettingsMap.set(settingName as string, this._globalSettingGetter(settingName)); return this._cachedGlobalSettingsMap.get(settingName as string); } @@ -177,6 +186,10 @@ export class Dependency< this._numParentDependencies++; } + if (dep.getIsLoading()) { + throw new DependencyLoadingError("Dependency is loading"); + } + if (this._cachedDependenciesMap.has(dep)) { return this._cachedDependenciesMap.get(dep); } @@ -186,7 +199,7 @@ export class Dependency< dep.subscribe((newValue) => { this._cachedDependenciesMap.set(dep, newValue); - this.callUpdateFunc(); + this.invalidate(); }, true); dep.subscribeLoading((loading) => { @@ -220,6 +233,13 @@ export class Dependency< this._isInitialized = true; } + private invalidate(): void { + if (!this._isLoading) { + this.setLoadingState(true); + } + this.callUpdateFunc(); + } + private async callUpdateFunc() { if (this._abortController) { this._abortController.abort(); @@ -228,9 +248,7 @@ export class Dependency< this._abortController = new AbortController(); - this.setLoadingState(true); - - let newValue: Awaited | null | typeof CancelUpdate = null; + let newValue: Awaited | null = null; try { newValue = await this._updateFunc({ getLocalSetting: this.getLocalSetting, @@ -239,6 +257,10 @@ export class Dependency< abortSignal: this._abortController.signal, }); } catch (e: any) { + if (e instanceof DependencyLoadingError) { + return; + } + if (!isCancelledError(e)) { this.applyNewValue(null); return; @@ -246,20 +268,14 @@ export class Dependency< return; } - if (newValue === CancelUpdate) { - return; - } - this.applyNewValue(newValue); } private applyNewValue(newValue: Awaited | null) { this.setLoadingState(false); - if (!isEqual(newValue, this._cachedValue) || newValue === null) { - this._cachedValue = newValue; - for (const callback of this._dependencies) { - callback(newValue); - } + this._cachedValue = newValue; + for (const callback of this._dependencies) { + callback(newValue); } } } diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProvider.ts b/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProvider.ts index a7b1d51de..977da28a3 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProvider.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProvider.ts @@ -31,6 +31,7 @@ export enum DataProviderTopic { STATUS = "STATUS", DATA = "DATA", SUBORDINATED = "SUBORDINATED", + REVISION_NUMBER = "REVISION_NUMBER", } export enum DataProviderStatus { @@ -45,6 +46,7 @@ export type DataProviderPayloads = { [DataProviderTopic.STATUS]: DataProviderStatus; [DataProviderTopic.DATA]: TData; [DataProviderTopic.SUBORDINATED]: boolean; + [DataProviderTopic.REVISION_NUMBER]: number; }; export function isDataProvider(obj: any): obj is DataProvider { @@ -116,10 +118,13 @@ export class DataProvider< private _status: DataProviderStatus = DataProviderStatus.IDLE; private _data: TData | null = null; private _error: StatusMessage | string | null = null; - private _valueRange: [number, number] | null = null; + private _valueRange: readonly [number, number] | null = null; private _isSubordinated: boolean = false; private _prevSettings: TSettingTypes | null = null; private _prevStoredData: NullableStoredData | null = null; + private _currentTransactionId: number = 0; + private _settingsErrorMessages: string[] = []; + private _revisionNumber: number = 0; constructor(params: DataProviderParams) { const { @@ -131,7 +136,6 @@ export class DataProvider< this._type = type; this._dataProviderManager = dataProviderManager; this._settingsContextDelegate = new SettingsContextDelegate( - this, customDataProviderImplementation, dataProviderManager, makeSettings( @@ -150,16 +154,7 @@ export class DataProvider< "settings-context", this._settingsContextDelegate .getPublishSubscribeDelegate() - .makeSubscriberFunction(SettingsContextDelegateTopic.SETTINGS_CHANGED)(() => { - this.handleSettingsAndStoredDataChange(); - }), - ); - - this._unsubscribeHandler.registerUnsubscribeFunction( - "settings-context", - this._settingsContextDelegate - .getPublishSubscribeDelegate() - .makeSubscriberFunction(SettingsContextDelegateTopic.STORED_DATA_CHANGED)(() => { + .makeSubscriberFunction(SettingsContextDelegateTopic.SETTINGS_AND_STORED_DATA_CHANGED)(() => { this.handleSettingsAndStoredDataChange(); }), ); @@ -172,15 +167,6 @@ export class DataProvider< this.handleSettingsStatusChange(); }), ); - - this._unsubscribeHandler.registerUnsubscribeFunction( - "data-provider-manager", - dataProviderManager - .getPublishSubscribeDelegate() - .makeSubscriberFunction(DataProviderManagerTopic.GLOBAL_SETTINGS)(() => { - this.handleSettingsAndStoredDataChange(); - }), - ); } areCurrentSettingsValid(): boolean { @@ -188,10 +174,23 @@ export class DataProvider< return true; } - return this._customDataProviderImpl.areCurrentSettingsValid(this.makeAccessors()); + this._settingsErrorMessages = []; + const reportError = (message: string) => { + this._settingsErrorMessages.push(message); + }; + return this._customDataProviderImpl.areCurrentSettingsValid({ ...this.makeAccessors(), reportError }); + } + + getSettingsErrorMessages(): string[] { + return this._settingsErrorMessages; } handleSettingsAndStoredDataChange(): void { + if (this._settingsContextDelegate.getStatus() === SettingsContextStatus.LOADING) { + this.setStatus(DataProviderStatus.LOADING); + return; + } + if (!this.areCurrentSettingsValid()) { this._error = "Invalid settings"; this.setStatus(DataProviderStatus.INVALID_SETTINGS); @@ -223,17 +222,27 @@ export class DataProvider< } if (!refetchRequired) { - this._publishSubscribeDelegate.notifySubscribers(DataProviderTopic.DATA); - this._dataProviderManager.publishTopic(DataProviderManagerTopic.DATA_REVISION); + // If the settings have changed but no refetch is required, it might be that the settings changes + // still require a rerender of the data provider. + if (this._status === DataProviderStatus.SUCCESS) { + this.incrementRevisionNumber(); + return; + } this.setStatus(DataProviderStatus.SUCCESS); return; } this._cancellationPending = true; - this._prevSettings = clone(this._settingsContextDelegate.getValues()) as TSettingTypes; - this._prevStoredData = clone(this._settingsContextDelegate.getStoredDataRecord()) as TStoredData; + + // It might be that we started a new transaction while the previous one was still running. + // In this case, we need to make sure that we only use the latest transaction and cancel the previous one. + this._currentTransactionId += 1; + this.maybeCancelQuery().then(() => { - this.maybeRefetchData(); + this.maybeRefetchData().then(() => { + this._prevSettings = clone(this._settingsContextDelegate.getValues()) as TSettingTypes; + this._prevStoredData = clone(this._settingsContextDelegate.getStoredDataRecord()) as TStoredData; + }); }); } @@ -286,7 +295,7 @@ export class DataProvider< this._publishSubscribeDelegate.notifySubscribers(DataProviderTopic.SUBORDINATED); } - getValueRange(): [number, number] | null { + getValueRange(): readonly [number, number] | null { return this._valueRange; } @@ -305,6 +314,9 @@ export class DataProvider< if (topic === DataProviderTopic.SUBORDINATED) { return this._isSubordinated; } + if (topic === DataProviderTopic.REVISION_NUMBER) { + return this._revisionNumber; + } }; return snapshotGetter; @@ -345,6 +357,8 @@ export class DataProvider< } async maybeRefetchData(): Promise { + const thisTransactionId = this._currentTransactionId; + const queryClient = this.getQueryClient(); if (!queryClient) { @@ -371,6 +385,15 @@ export class DataProvider< queryClient, registerQueryKey: (key) => this.registerQueryKey(key), }); + + // This is a security check to make sure that we are not using a stale transaction id. + // This can happen if the transaction id is incremented while the async fetch data function is still running. + // Queries are cancelled in the maybeCancelQuery function and should, hence, throw a cancelled error. + // However, there might me some operations following after the query execution that are not cancelled. + if (this._currentTransactionId !== thisTransactionId) { + return; + } + if (this._customDataProviderImpl.makeValueRange) { this._valueRange = this._customDataProviderImpl.makeValueRange(accessors); } @@ -381,7 +404,6 @@ export class DataProvider< } this._queryKeys = []; this._publishSubscribeDelegate.notifySubscribers(DataProviderTopic.DATA); - this._dataProviderManager.publishTopic(DataProviderManagerTopic.DATA_REVISION); this.setStatus(DataProviderStatus.SUCCESS); } catch (error: any) { if (isCancelledError(error)) { @@ -391,7 +413,7 @@ export class DataProvider< if (apiError) { this._error = apiError.makeStatusMessage(); } else { - this._error = "An error occurred"; + this._error = error.message; } this.setStatus(DataProviderStatus.ERROR); } @@ -417,13 +439,19 @@ export class DataProvider< this._unsubscribeHandler.unsubscribeAll(); } + private incrementRevisionNumber(): void { + this._revisionNumber += 1; + this._publishSubscribeDelegate.notifySubscribers(DataProviderTopic.REVISION_NUMBER); + this._dataProviderManager.publishTopic(DataProviderManagerTopic.DATA_REVISION); + } + private setStatus(status: DataProviderStatus): void { if (this._status === status) { return; } this._status = status; - this._dataProviderManager.publishTopic(DataProviderManagerTopic.DATA_REVISION); + this.incrementRevisionNumber(); this._publishSubscribeDelegate.notifySubscribers(DataProviderTopic.STATUS); } diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProviderComponent.tsx b/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProviderComponent.tsx index 7a8ddd28e..577dada85 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProviderComponent.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/DataProvider/DataProviderComponent.tsx @@ -8,11 +8,10 @@ import { DenseIconButton } from "@lib/components/DenseIconButton"; import { SortableListItem } from "@lib/components/SortableList"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; - import { usePublishSubscribeTopicValue } from "../../../utils/PublishSubscribeDelegate"; import { ItemDelegateTopic } from "../../delegates/ItemDelegate"; import type { SettingManager } from "../SettingManager/SettingManager"; -import { SettingComponent } from "../SettingManager/SettingManagerComponent"; +import { SettingManagerComponent } from "../SettingManager/SettingManagerComponent"; import { EditName } from "../utilityComponents/EditName"; import { RemoveItemButton } from "../utilityComponents/RemoveItemButton"; import { VisibilityToggle } from "../utilityComponents/VisibilityToggle"; @@ -32,7 +31,9 @@ export function DataProviderComponent(props: DataProviderComponentProps): React. if (!manager) { return null; } - return ; + return ( + + ); } function makeSettings(settings: Record>): React.ReactNode[] { @@ -123,13 +124,22 @@ function EndActions(props: EndActionProps): React.ReactNode { } } if (status === DataProviderStatus.INVALID_SETTINGS) { + let errorMessage = "Invalid settings"; + const invalidSettings = props.dataProvider.getSettingsContextDelegate().getInvalidSettings(); + + if (invalidSettings.length > 0) { + errorMessage += `: ${invalidSettings.join(", ")}`; + } + errorMessage += "."; + + const customReportedErrors = props.dataProvider.getSettingsErrorMessages(); + if (customReportedErrors.length > 0) { + errorMessage += `\n${customReportedErrors.join("\n")}`; + } + errorMessage += "\nPlease check the settings."; + return ( -
+
); diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager.ts b/frontend/src/modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager.ts index 60e46f7f8..3ac86855d 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/DataProviderManager/DataProviderManager.ts @@ -12,7 +12,6 @@ import { import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; import { ColorPaletteType } from "@framework/WorkbenchSettings"; - import type { PublishSubscribe } from "../../../utils/PublishSubscribeDelegate"; import { PublishSubscribeDelegate } from "../../../utils/PublishSubscribeDelegate"; import { GroupDelegate, GroupDelegateTopic } from "../../delegates/GroupDelegate"; @@ -24,26 +23,22 @@ import { type SerializedDataProviderManager, SerializedType } from "../../interf export enum DataProviderManagerTopic { ITEMS = "ITEMS", SETTINGS_CHANGED = "SETTINGS_CHANGED", - AVAILABLE_SETTINGS_CHANGED = "AVAILABLE_SETTINGS_CHANGED", DATA_REVISION = "DATA_REVISION", GLOBAL_SETTINGS = "GLOBAL_SETTINGS", - SHARED_SETTINGS_CHANGED = "SHARED_SETTINGS_CHANGED", } export type DataProviderManagerTopicPayload = { [DataProviderManagerTopic.ITEMS]: Item[]; [DataProviderManagerTopic.SETTINGS_CHANGED]: void; - [DataProviderManagerTopic.AVAILABLE_SETTINGS_CHANGED]: void; [DataProviderManagerTopic.DATA_REVISION]: number; [DataProviderManagerTopic.GLOBAL_SETTINGS]: GlobalSettings; - [DataProviderManagerTopic.SHARED_SETTINGS_CHANGED]: void; }; export type GlobalSettings = { fieldId: string | null; ensembles: readonly RegularEnsemble[]; realizationFilterFunction: EnsembleRealizationFilterFunction; - intersectionPolylines: IntersectionPolyline[]; + intersectionPolylines: readonly IntersectionPolyline[]; }; /* @@ -164,18 +159,12 @@ export class DataProviderManager implements ItemGroup, PublishSubscribe { + .makeSubscriberFunction(SettingsContextDelegateTopic.SETTINGS_AND_STORED_DATA_CHANGED)(() => { this.handleSettingsChange(); }), ); diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/ExternalSettingController/ExternalSettingController.ts b/frontend/src/modules/_shared/DataProviderFramework/framework/ExternalSettingController/ExternalSettingController.ts new file mode 100644 index 000000000..afc2e342b --- /dev/null +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/ExternalSettingController/ExternalSettingController.ts @@ -0,0 +1,162 @@ +import { UnsubscribeHandlerDelegate } from "../../delegates/UnsubscribeHandlerDelegate"; +import type { Item } from "../../interfacesAndTypes/entities"; +import type { AvailableValuesType } from "../../interfacesAndTypes/utils"; +import { + type Setting, + type SettingCategories, + type SettingTypes, + settingCategoryAvailableValuesIntersectionReducerMap, +} from "../../settings/settingsDefinitions"; +import { DataProvider } from "../DataProvider/DataProvider"; +import { DataProviderManagerTopic } from "../DataProviderManager/DataProviderManager"; +import { Group } from "../Group/Group"; +import type { SettingManager } from "../SettingManager/SettingManager"; + +export class ExternalSettingController< + TSetting extends Setting, + TValue extends SettingTypes[TSetting] | null = SettingTypes[TSetting] | null, + TCategory extends SettingCategories[TSetting] = SettingCategories[TSetting], +> { + private _parentItem: Item; + private _setting: SettingManager; + private _controlledSettings: Map> = new Map(); + private _availableValuesMap: Map> = new Map(); + private _unsubscribeHandler: UnsubscribeHandlerDelegate = new UnsubscribeHandlerDelegate(); + + constructor(parentItem: Item, setting: SettingManager) { + this._parentItem = parentItem; + this._setting = setting; + + const dataProviderManager = parentItem.getItemDelegate().getDataProviderManager(); + this._unsubscribeHandler.registerUnsubscribeFunction( + "data-provider-manager", + dataProviderManager.getPublishSubscribeDelegate().makeSubscriberFunction(DataProviderManagerTopic.ITEMS)( + () => { + this.updateControlledSettings(); + }, + ), + ); + } + + beforeDestroy(): void { + this._unsubscribeHandler.unsubscribeAll(); + this.unregisterAllControlledSettings(); + } + + getParentItem(): Item { + return this._parentItem; + } + + registerSetting(settingManager: SettingManager): void { + this._controlledSettings.set(settingManager.getId(), settingManager); + settingManager.registerExternalSettingController(this); + } + + getSetting(): SettingManager { + return this._setting; + } + + private updateControlledSettings(): void { + const oldControlledSettings = new Map(this._controlledSettings); + this._controlledSettings.clear(); + this._availableValuesMap.clear(); + + let parentGroup = this._parentItem.getItemDelegate().getParentGroup(); + if (this._parentItem instanceof Group) { + parentGroup = this._parentItem.getGroupDelegate(); + } + + if (!parentGroup) { + return; + } + + const providers = parentGroup.getDescendantItems((item) => item instanceof DataProvider) as DataProvider< + any, + any + >[]; + + for (const provider of providers) { + const setting = provider.getSettingsContextDelegate().getSettings()[this._setting.getType()]; + if (setting) { + this._controlledSettings.set(setting.getId(), setting); + this._availableValuesMap.set(setting.getId(), setting.getAvailableValues()); + setting.registerExternalSettingController(this); + } + } + + for (const settingId of oldControlledSettings.keys()) { + if (!this._controlledSettings.has(settingId)) { + const setting = oldControlledSettings.get(settingId); + if (setting) { + setting.unregisterExternalSettingController(); + } + } + } + + if (this._controlledSettings.size === 0) { + this._setting.setAvailableValues(null); + return; + } + + this.makeIntersectionOfAvailableValues(); + } + + unregisterAllControlledSettings(): void { + for (const setting of this._controlledSettings.values()) { + setting.unregisterExternalSettingController(); + } + this._controlledSettings.clear(); + this._availableValuesMap.clear(); + } + + setAvailableValues(settingId: string, availableValues: AvailableValuesType | null): void { + if (availableValues) { + this._availableValuesMap.set(settingId, availableValues); + } else { + this._availableValuesMap.delete(settingId); + } + + this.makeIntersectionOfAvailableValues(); + } + + makeIntersectionOfAvailableValues(): void { + for (const setting of this._controlledSettings.values()) { + if (!setting.isInitialized(true)) { + return; + } + } + + const category = this._setting.getCategory(); + const reducerDefinition = settingCategoryAvailableValuesIntersectionReducerMap[category]; + + if (this._setting.isStatic()) { + this._setting.maybeResetPersistedValue(); + return; + } + + if (!reducerDefinition) { + return; + } + + const { reducer, startingValue, isValid } = reducerDefinition; + let availableValues: AvailableValuesType = startingValue as AvailableValuesType; + let index = 0; + let isInvalid = false; + + for (const value of this._availableValuesMap.values()) { + if (value === null) { + isInvalid = true; + break; + } + availableValues = reducer(availableValues as any, value as any, index++) as AvailableValuesType; + } + + if (!isValid(availableValues as any) || isInvalid) { + this._setting.setAvailableValues(null); + this._setting.setValue(null as any); + return; + } + + this._setting.setAvailableValues(availableValues); + } +} diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/Group/Group.ts b/frontend/src/modules/_shared/DataProviderFramework/framework/Group/Group.ts index 480ba9d0a..e872e97c9 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/Group/Group.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/Group/Group.ts @@ -9,6 +9,7 @@ import type { CustomGroupImplementationWithSettings, } from "../../interfacesAndTypes/customGroupImplementation"; import { includesSettings } from "../../interfacesAndTypes/customGroupImplementation"; +import type { DefineBasicDependenciesArgs } from "../../interfacesAndTypes/customSettingsHandler"; import type { ItemGroup } from "../../interfacesAndTypes/entities"; import type { SerializedGroup } from "../../interfacesAndTypes/serialization"; import { SerializedType } from "../../interfacesAndTypes/serialization"; @@ -57,7 +58,7 @@ export class Group< private _type: GroupType; private _icon: React.ReactNode | null = null; private _emptyContentMessage: string | null = null; - private _sharedSettingsDelegate: SharedSettingsDelegate | null = null; + private _sharedSettingsDelegate: SharedSettingsDelegate | null = null; constructor(params: GroupParams) { const { dataProviderManager, customGroupImplementation, type } = params; @@ -65,12 +66,15 @@ export class Group< this._groupDelegate.setColor(dataProviderManager.makeGroupColor()); this._itemDelegate = new ItemDelegate(customGroupImplementation.getDefaultName(), 1, dataProviderManager); if (includesSettings(customGroupImplementation)) { - this._sharedSettingsDelegate = new SharedSettingsDelegate( + this._sharedSettingsDelegate = new SharedSettingsDelegate( this, makeSettings( customGroupImplementation.settings as unknown as TSettings, - customGroupImplementation.getDefaultSettingsValues() as unknown as TSettingTypes, + customGroupImplementation.getDefaultSettingsValues?.() ?? {}, ), + customGroupImplementation.defineDependencies as unknown as + | ((args: DefineBasicDependenciesArgs) => void) + | undefined, ); } this._type = type; @@ -95,11 +99,11 @@ export class Group< return this._emptyContentMessage; } - getSharedSettingsDelegate(): SharedSettingsDelegate | null { + getSharedSettingsDelegate(): SharedSettingsDelegate | null { return this._sharedSettingsDelegate; } - getWrappedSettings(): { [K in TSettingKey]: SettingManager } { + getWrappedSettings(): { [K in TSettingKey]: SettingManager } { if (!this._sharedSettingsDelegate) { throw new Error("Group does not have shared settings."); } @@ -125,4 +129,9 @@ export class Group< this._groupDelegate.setColor(serialized.color); this._groupDelegate.deserializeChildren(serialized.children); } + + beforeDestroy(): void { + this._groupDelegate.beforeDestroy(); + this._sharedSettingsDelegate?.beforeDestroy(); + } } diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/Group/GroupComponent.tsx b/frontend/src/modules/_shared/DataProviderFramework/framework/Group/GroupComponent.tsx index a90ca492f..ac294c1d2 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/Group/GroupComponent.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/Group/GroupComponent.tsx @@ -3,7 +3,6 @@ import React from "react"; import { ColorSelect } from "@lib/components/ColorSelect"; import { SortableListGroup } from "@lib/components/SortableList"; - import { usePublishSubscribeTopicValue } from "../../../utils/PublishSubscribeDelegate"; import type { ActionGroup } from "../../Actions"; import { Actions } from "../../Actions"; @@ -11,7 +10,7 @@ import { GroupDelegateTopic } from "../../delegates/GroupDelegate"; import { ItemDelegateTopic } from "../../delegates/ItemDelegate"; import type { Item, ItemGroup } from "../../interfacesAndTypes/entities"; import type { SettingManager } from "../SettingManager/SettingManager"; -import { SettingComponent } from "../SettingManager/SettingManagerComponent"; +import { SettingManagerComponent } from "../SettingManager/SettingManagerComponent"; import { EditName } from "../utilityComponents/EditName"; import { EmptyContent } from "../utilityComponents/EmptyContent"; import { ExpandCollapseAllButton } from "../utilityComponents/ExpandCollapseAllButton"; @@ -49,7 +48,9 @@ export function GroupComponent(props: GroupComponentProps): React.ReactNode { if (!manager) { return null; } - return ; + return ( + + ); } function makeSettings(settings: SettingManager[]): React.ReactNode[] { diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManager.ts b/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManager.ts index c3fbbdd3a..7b00cd042 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManager.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManager.ts @@ -6,13 +6,13 @@ import type { WorkbenchSettings } from "@framework/WorkbenchSettings"; import type { PublishSubscribe } from "@modules/_shared/utils/PublishSubscribeDelegate"; import { PublishSubscribeDelegate } from "@modules/_shared/utils/PublishSubscribeDelegate"; - +import { UnsubscribeHandlerDelegate } from "../../delegates/UnsubscribeHandlerDelegate"; import type { CustomSettingImplementation } from "../../interfacesAndTypes/customSettingImplementation"; import type { SettingAttributes } from "../../interfacesAndTypes/customSettingsHandler"; -import type { SharedSettingsProvider } from "../../interfacesAndTypes/entities"; import type { AvailableValuesType, MakeAvailableValuesTypeBasedOnCategory } from "../../interfacesAndTypes/utils"; import type { Setting, SettingCategories, SettingCategory, SettingTypes } from "../../settings/settingsDefinitions"; import { settingCategoryFixupMap, settingCategoryIsValueValidMap } from "../../settings/settingsDefinitions"; +import type { ExternalSettingController } from "../ExternalSettingController/ExternalSettingController"; import { Group } from "../Group/Group"; export enum SettingTopic { @@ -20,8 +20,8 @@ export enum SettingTopic { VALUE_ABOUT_TO_BE_CHANGED = "VALUE_ABOUT_TO_BE_CHANGED", IS_VALID = "IS_VALID", AVAILABLE_VALUES = "AVAILABLE_VALUES", - OVERRIDDEN_VALUE = "OVERRIDDEN_VALUE", - OVERRIDDEN_VALUE_PROVIDER = "OVERRIDDEN_VALUE_PROVIDER", + IS_EXTERNALLY_CONTROLLED = "IS_EXTERNALLY_CONTROLLED", + EXTERNAL_CONTROLLER_PROVIDER = "EXTERNAL_CONTROLLER_PROVIDER", IS_LOADING = "IS_LOADING", IS_INITIALIZED = "IS_INITIALIZED", IS_PERSISTED = "IS_PERSISTED", @@ -33,8 +33,8 @@ export type SettingTopicPayloads = { [SettingTopic.VALUE_ABOUT_TO_BE_CHANGED]: void; [SettingTopic.IS_VALID]: boolean; [SettingTopic.AVAILABLE_VALUES]: MakeAvailableValuesTypeBasedOnCategory | null; - [SettingTopic.OVERRIDDEN_VALUE]: TValue | undefined; - [SettingTopic.OVERRIDDEN_VALUE_PROVIDER]: OverriddenValueProviderType | undefined; + [SettingTopic.IS_EXTERNALLY_CONTROLLED]: boolean; + [SettingTopic.EXTERNAL_CONTROLLER_PROVIDER]: OverriddenValueProviderType | undefined; [SettingTopic.IS_LOADING]: boolean; [SettingTopic.IS_INITIALIZED]: boolean; [SettingTopic.IS_PERSISTED]: boolean; @@ -66,7 +66,7 @@ export enum OverriddenValueProviderType { */ export class SettingManager< TSetting extends Setting, - TValue extends SettingTypes[TSetting] = SettingTypes[TSetting], + TValue extends SettingTypes[TSetting] | null = SettingTypes[TSetting] | null, TCategory extends SettingCategories[TSetting] = SettingCategories[TSetting], > implements PublishSubscribe> { @@ -78,9 +78,7 @@ export class SettingManager< private _value: TValue; private _isValueValid: boolean = false; private _publishSubscribeDelegate = new PublishSubscribeDelegate>(); - private _availableValues: MakeAvailableValuesTypeBasedOnCategory | null = null; - private _overriddenValue: TValue | undefined = undefined; - private _overriddenValueProviderType: OverriddenValueProviderType | undefined = undefined; + private _availableValues: AvailableValuesType | null = null; private _loading: boolean = false; private _initialized: boolean = false; private _currentValueFromPersistence: TValue | null = null; @@ -89,6 +87,8 @@ export class SettingManager< enabled: true, visible: true, }; + private _externalController: ExternalSettingController | null = null; + private _unsubscribeHandler: UnsubscribeHandlerDelegate = new UnsubscribeHandlerDelegate(); constructor({ type, @@ -109,6 +109,93 @@ export class SettingManager< } } + registerExternalSettingController( + externalController: ExternalSettingController, + ): void { + this._externalController = externalController; + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController.getSetting().getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.VALUE)( + () => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.VALUE); + this._value = externalController.getSetting().getValue(); + }, + ), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController.getSetting().getPublishSubscribeDelegate().makeSubscriberFunction(SettingTopic.IS_VALID)( + () => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.IS_VALID); + }, + ), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController + .getSetting() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.IS_LOADING)(() => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.IS_LOADING); + }), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController + .getSetting() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.ATTRIBUTES)(() => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.ATTRIBUTES); + }), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController + .getSetting() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.VALUE_ABOUT_TO_BE_CHANGED)(() => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.VALUE_ABOUT_TO_BE_CHANGED); + }), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController + .getSetting() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.IS_INITIALIZED)(() => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.IS_INITIALIZED); + }), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController + .getSetting() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.IS_PERSISTED)(() => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.IS_PERSISTED); + }), + ); + this._unsubscribeHandler.registerUnsubscribeFunction( + "external-setting-controller", + externalController + .getSetting() + .getPublishSubscribeDelegate() + .makeSubscriberFunction(SettingTopic.AVAILABLE_VALUES)(() => { + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.AVAILABLE_VALUES); + }), + ); + } + + unregisterExternalSettingController(): void { + this._externalController = null; + this._unsubscribeHandler.unsubscribe("external-setting-controller"); + this.applyAvailableValues(); + } + + beforeDestroy(): void { + this._unsubscribeHandler.unsubscribeAll(); + } + getId(): string { return this._id; } @@ -134,14 +221,17 @@ export class SettingManager< return; } - Object.assign(this._attributes, attributes); + this._attributes = { + ...this._attributes, + ...attributes, + }; this._publishSubscribeDelegate.notifySubscribers(SettingTopic.ATTRIBUTES); } getValue(): TValue { - if (this._overriddenValue !== undefined) { - return this._overriddenValue; + if (this._externalController) { + return this._externalController.getSetting().getValue(); } if (this._currentValueFromPersistence !== null) { @@ -173,10 +263,16 @@ export class SettingManager< } isValueValid(): boolean { + if (this._externalController) { + return this._externalController.getSetting().isValueValid(); + } return this._isValueValid; } isPersistedValue(): boolean { + if (this._externalController) { + return this._externalController.getSetting().isPersistedValue(); + } return this._currentValueFromPersistence !== null; } @@ -210,6 +306,11 @@ export class SettingManager< if (this._loading === loading) { return; } + + if (this._externalController) { + this._externalController.getSetting().setLoading(loading); + } + this._loading = loading; this._publishSubscribeDelegate.notifySubscribers(SettingTopic.IS_LOADING); } @@ -219,14 +320,21 @@ export class SettingManager< return; } this._initialized = true; + this._publishSubscribeDelegate.notifySubscribers(SettingTopic.IS_INITIALIZED); } - isInitialized(): boolean { + isInitialized(itself: boolean = false): boolean { + if (this._externalController && !itself) { + return this._externalController.getSetting().isInitialized(); + } return this._initialized || this._isStatic; } isLoading(): boolean { + if (this._externalController) { + return this._externalController.getSetting().isLoading(); + } return this._loading; } @@ -235,6 +343,12 @@ export class SettingManager< workbenchSession: WorkbenchSession, workbenchSettings: WorkbenchSettings, ): React.ReactNode { + if (this._externalController) { + return this._externalController + .getSetting() + .valueToRepresentation(value, workbenchSession, workbenchSettings); + } + if (this._customSettingImplementation.overriddenValueRepresentation) { return this._customSettingImplementation.overriddenValueRepresentation({ value, @@ -258,63 +372,22 @@ export class SettingManager< return "Value has no string representation"; } - checkForOverrides(sharedSettingsProviders: SharedSettingsProvider[]) { - let overriddenValue: TValue | undefined; - let overriddenValueProviderType: OverriddenValueProviderType | undefined; - - for (const provider of sharedSettingsProviders) { - if (!provider.getSharedSettingsDelegate()) { - continue; - } - for (const sharedSettingKey in provider.getSharedSettingsDelegate().getWrappedSettings()) { - const sharedSetting = provider.getSharedSettingsDelegate().getWrappedSettings()[sharedSettingKey]; - if (sharedSetting.getType() === this._type) { - overriddenValue = sharedSetting.getValue(); - overriddenValueProviderType = OverriddenValueProviderType.SHARED_SETTING; - if (provider instanceof Group) { - overriddenValueProviderType = OverriddenValueProviderType.GROUP; - } - break; + makeSnapshotGetter(topic: T): () => SettingTopicPayloads[T] { + const externalController = this._externalController; + if (externalController) { + return (): any => { + if (topic === SettingTopic.IS_EXTERNALLY_CONTROLLED) { + return true; } - } - } - - this.setOverriddenValue(overriddenValue); - this._overriddenValueProviderType = overriddenValueProviderType; - this._publishSubscribeDelegate.notifySubscribers(SettingTopic.OVERRIDDEN_VALUE_PROVIDER); - } - - setOverriddenValue(overriddenValue: TValue | undefined): void { - if (isEqual(this._overriddenValue, overriddenValue)) { - return; - } - - const prevValue = this._overriddenValue; - this._overriddenValue = overriddenValue; - this._publishSubscribeDelegate.notifySubscribers(SettingTopic.OVERRIDDEN_VALUE); - - if (overriddenValue === undefined) { - // Keep overridden value, if invalid fix it - if (prevValue !== undefined) { - this._value = prevValue; - } - this.maybeFixupValue(); - } - - this.setValueValid(this.checkIfValueIsValid(this.getValue())); - - if (prevValue === undefined && overriddenValue !== undefined && isEqual(this._value, overriddenValue)) { - return; - } - - if (prevValue !== undefined && overriddenValue === undefined && isEqual(this._value, prevValue)) { - return; + if (topic === SettingTopic.EXTERNAL_CONTROLLER_PROVIDER) { + return externalController.getParentItem() instanceof Group + ? OverriddenValueProviderType.GROUP + : OverriddenValueProviderType.SHARED_SETTING; + } + return externalController.getSetting().makeSnapshotGetter(topic)(); + }; } - this._publishSubscribeDelegate.notifySubscribers(SettingTopic.VALUE); - } - - makeSnapshotGetter(topic: T): () => SettingTopicPayloads[T] { const snapshotGetter = (): any => { switch (topic) { case SettingTopic.VALUE: @@ -325,10 +398,12 @@ export class SettingManager< return this._isValueValid; case SettingTopic.AVAILABLE_VALUES: return this._availableValues; - case SettingTopic.OVERRIDDEN_VALUE: - return this._overriddenValue; - case SettingTopic.OVERRIDDEN_VALUE_PROVIDER: - return this._overriddenValueProviderType; + case SettingTopic.IS_EXTERNALLY_CONTROLLED: + return this._externalController !== null; + case SettingTopic.EXTERNAL_CONTROLLER_PROVIDER: + return this._externalController?.getParentItem() instanceof Group + ? OverriddenValueProviderType.GROUP + : OverriddenValueProviderType.SHARED_SETTING; case SettingTopic.IS_LOADING: return this.isLoading(); case SettingTopic.IS_PERSISTED: @@ -350,7 +425,10 @@ export class SettingManager< } getAvailableValues(): AvailableValuesType | null { - return this._availableValues; + if (this._externalController) { + return this._externalController.getSetting().getAvailableValues(); + } + return this._availableValues as AvailableValuesType | null; } maybeResetPersistedValue(): boolean { @@ -372,7 +450,7 @@ export class SettingManager< if (customIsValueValidFunction) { isPersistedValueValid = customIsValueValidFunction( this._currentValueFromPersistence, - this._availableValues, + this._availableValues as any, ); } else { isPersistedValueValid = settingCategoryIsValueValidMap[this._category]( @@ -393,22 +471,38 @@ export class SettingManager< return false; } - setAvailableValues(availableValues: MakeAvailableValuesTypeBasedOnCategory): void { - if (isEqual(this._availableValues, availableValues) && this._initialized) { - return; - } - - this._availableValues = availableValues; + private applyAvailableValues() { let valueChanged = false; - if ((!this.checkIfValueIsValid(this.getValue()) && this.maybeFixupValue()) || this.maybeResetPersistedValue()) { + const valueFixedUp = !this.checkIfValueIsValid(this.getValue()) && this.maybeFixupValue(); + const persistedValueReset = this.maybeResetPersistedValue(); + if (valueFixedUp || persistedValueReset) { valueChanged = true; } const prevIsValid = this._isValueValid; this.setValueValid(this.checkIfValueIsValid(this.getValue())); this.initialize(); - if (valueChanged || this._isValueValid !== prevIsValid) { + this.setLoading(false); + if (valueChanged || this._isValueValid !== prevIsValid || this._value === null) { this._publishSubscribeDelegate.notifySubscribers(SettingTopic.VALUE); } + } + + setAvailableValues(availableValues: AvailableValuesType | null): void { + if (this._externalController) { + this._availableValues = availableValues; + this.initialize(); + this._externalController.setAvailableValues(this.getId(), availableValues); + return; + } + + if (isEqual(this._availableValues, availableValues) && this._initialized) { + this.setLoading(false); + return; + } + + this._availableValues = availableValues; + + this.applyAvailableValues(); this._publishSubscribeDelegate.notifySubscribers(SettingTopic.AVAILABLE_VALUES); } @@ -431,7 +525,7 @@ export class SettingManager< let candidate: TValue; if (this._customSettingImplementation.fixupValue) { - candidate = this._customSettingImplementation.fixupValue(this._value, this._availableValues); + candidate = this._customSettingImplementation.fixupValue(this._value, this._availableValues as any); } else { candidate = settingCategoryFixupMap[this._category]( this._value as any, @@ -454,7 +548,7 @@ export class SettingManager< return false; } if (this._customSettingImplementation.isValueValid) { - return this._customSettingImplementation.isValueValid(value, this._availableValues); + return this._customSettingImplementation.isValueValid(value, this._availableValues as any); } else { return settingCategoryIsValueValidMap[this._category](value as any, this._availableValues as any); } diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManagerComponent.tsx b/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManagerComponent.tsx index d54c14179..79abd9dea 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManagerComponent.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/SettingManager/SettingManagerComponent.tsx @@ -5,7 +5,6 @@ import { Link, Warning } from "@mui/icons-material"; import { PendingWrapper } from "@lib/components/PendingWrapper"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; - import { usePublishSubscribeTopicValue } from "../../../utils/PublishSubscribeDelegate"; import type { SettingComponentProps as SettingComponentPropsInterface } from "../../interfacesAndTypes/customSettingImplementation"; import type { Setting, SettingCategories, SettingTypes } from "../../settings/settingsDefinitions"; @@ -24,7 +23,7 @@ export type SettingComponentProps< sharedSetting: boolean; }; -export function SettingComponent< +export function SettingManagerComponent< TSetting extends Setting, TValue extends SettingTypes[TSetting] = SettingTypes[TSetting], TCategory extends SettingCategories[TSetting] = SettingCategories[TSetting], @@ -37,10 +36,10 @@ export function SettingComponent< const isValid = usePublishSubscribeTopicValue(props.setting, SettingTopic.IS_VALID); const isPersisted = usePublishSubscribeTopicValue(props.setting, SettingTopic.IS_PERSISTED); const availableValues = usePublishSubscribeTopicValue(props.setting, SettingTopic.AVAILABLE_VALUES); - const overriddenValue = usePublishSubscribeTopicValue(props.setting, SettingTopic.OVERRIDDEN_VALUE); - const overriddenValueProvider = usePublishSubscribeTopicValue( + const isExternallyControlled = usePublishSubscribeTopicValue(props.setting, SettingTopic.IS_EXTERNALLY_CONTROLLED); + const externalControllerProvider = usePublishSubscribeTopicValue( props.setting, - SettingTopic.OVERRIDDEN_VALUE_PROVIDER, + SettingTopic.EXTERNAL_CONTROLLER_PROVIDER, ); const isLoading = usePublishSubscribeTopicValue(props.setting, SettingTopic.IS_LOADING); const isInitialized = usePublishSubscribeTopicValue(props.setting, SettingTopic.IS_INITIALIZED); @@ -59,7 +58,7 @@ export function SettingComponent< return null; } - if (props.sharedSetting && isInitialized && availableValues === null) { + if (props.sharedSetting && isInitialized && availableValues === null && !props.setting.isStatic()) { return (
{props.setting.getLabel()}
@@ -68,12 +67,12 @@ export function SettingComponent< ); } - if (overriddenValue !== undefined) { - if (overriddenValueProvider !== OverriddenValueProviderType.SHARED_SETTING) { + if (isExternallyControlled) { + if (externalControllerProvider !== OverriddenValueProviderType.SHARED_SETTING) { return null; } const valueAsString = props.setting.valueToRepresentation( - overriddenValue, + value, props.manager.getWorkbenchSession(), props.manager.getWorkbenchSettings(), ); @@ -109,8 +108,8 @@ export function SettingComponent< onValueChange={handleValueChanged} value={value} isValueValid={isValid} - isOverridden={overriddenValue !== undefined} - overriddenValue={overriddenValue ?? null} + isOverridden={isExternallyControlled} + overriddenValue={value} availableValues={availableValues} globalSettings={globalSettings} workbenchSession={props.manager.getWorkbenchSession()} diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/SettingsGroup/SettingsGroup.ts b/frontend/src/modules/_shared/DataProviderFramework/framework/SettingsGroup/SettingsGroup.ts index 3921c9443..9e1c760c8 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/SettingsGroup/SettingsGroup.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/SettingsGroup/SettingsGroup.ts @@ -53,4 +53,8 @@ export class SettingsGroup implements ItemGroup { this._itemDelegate.deserializeState(serialized); this._groupDelegate.deserializeChildren(serialized.children); } + + beforeDestroy?(): void { + this._groupDelegate.beforeDestroy(); + } } diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSetting.ts b/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSetting.ts index fde3fc23a..8e28b567d 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSetting.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSetting.ts @@ -7,7 +7,7 @@ import type { SerializedSharedSetting } from "../../interfacesAndTypes/serializa import { SerializedType } from "../../interfacesAndTypes/serialization"; import { SettingRegistry } from "../../settings/SettingRegistry"; import type { Setting, SettingTypes } from "../../settings/settingsDefinitions"; -import { type DataProviderManager, DataProviderManagerTopic } from "../DataProviderManager/DataProviderManager"; +import { type DataProviderManager } from "../DataProviderManager/DataProviderManager"; import type { SettingManager } from "../SettingManager/SettingManager"; export function isSharedSetting(obj: any): obj is SharedSetting { @@ -51,13 +51,6 @@ export class SharedSetting implements Item, SharedSett return this._sharedSettingsDelegate; } - publishValueChange(): void { - const dataProviderManager = this._itemDelegate.getDataProviderManager(); - if (dataProviderManager) { - dataProviderManager.publishTopic(DataProviderManagerTopic.SHARED_SETTINGS_CHANGED); - } - } - getWrappedSetting(): SettingManager { return Object.values(this._sharedSettingsDelegate.getWrappedSettings())[0] as SettingManager; } @@ -77,6 +70,7 @@ export class SharedSetting implements Item, SharedSett } beforeDestroy(): void { + this._sharedSettingsDelegate.beforeDestroy(); this._sharedSettingsDelegate.unsubscribeAll(); } } diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSettingComponent.tsx b/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSettingComponent.tsx index f8817db1b..ea78a60db 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSettingComponent.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/SharedSetting/SharedSettingComponent.tsx @@ -9,7 +9,7 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { usePublishSubscribeTopicValue } from "../../../utils/PublishSubscribeDelegate"; import { ItemDelegateTopic } from "../../delegates/ItemDelegate"; -import { SettingComponent } from "../SettingManager/SettingManagerComponent"; +import { SettingManagerComponent } from "../SettingManager/SettingManagerComponent"; import type { SharedSetting } from "./SharedSetting"; @@ -57,7 +57,11 @@ export function SharedSettingComponent(props: SharedSettingComponentProps): Reac hidden: !isExpanded, })} > - +
); diff --git a/frontend/src/modules/_shared/DataProviderFramework/framework/utilityComponents/RemoveItemButton.tsx b/frontend/src/modules/_shared/DataProviderFramework/framework/utilityComponents/RemoveItemButton.tsx index 9909519d0..fe8c27bae 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/framework/utilityComponents/RemoveItemButton.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/framework/utilityComponents/RemoveItemButton.tsx @@ -4,7 +4,6 @@ import { DenseIconButton } from "@lib/components/DenseIconButton"; import { DenseIconButtonColorScheme } from "@lib/components/DenseIconButton/denseIconButton"; import type { Item } from "../../interfacesAndTypes/entities"; -import { DataProvider } from "../DataProvider/DataProvider"; export type RemoveItemButtonProps = { item: Item; @@ -17,9 +16,7 @@ export function RemoveItemButton(props: RemoveItemButtonProps): React.ReactNode parentGroup.removeChild(props.item); } - if (props.item instanceof DataProvider) { - props.item.beforeDestroy(); - } + props.item.beforeDestroy?.(); } return ( diff --git a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation.ts b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation.ts index 8bbbc057c..a44c0f6c9 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customDataProviderImplementation.ts @@ -10,7 +10,6 @@ import type { CustomSettingsHandler } from "./customSettingsHandler"; import type { NullableStoredData, StoredData } from "./sharedTypes"; import type { AvailableValuesType, SettingsKeysFromTuple } from "./utils"; - /** * This type is used to pass parameters to the fetchData method of a CustomDataProviderImplementation. * It contains accessors to the data and settings of the provider and other useful information. @@ -38,7 +37,7 @@ export type DataProviderInformationAccessors< * const value = getSetting("settingName"); * ``` */ - getSetting: (settingName: K) => TSettingTypes[K]; + getSetting: (settingName: K) => TSettingTypes[K] | null; /** * Access the available values of a setting. @@ -91,6 +90,16 @@ export type DataProviderInformationAccessors< getWorkbenchSettings: () => WorkbenchSettings; }; +export type AreSettingsValidArgs< + TSettings extends Settings, + TData, + TStoredData extends StoredData = Record, + TSettingKey extends SettingsKeysFromTuple = SettingsKeysFromTuple, + TSettingTypes extends MakeSettingTypesMap = MakeSettingTypesMap, +> = DataProviderInformationAccessors & { + reportError: (error: string) => void; +}; + /** * This type is used to pass parameters to the fetchData method of a CustomDataProviderImplementation. * It contains accessors to the data and settings of the provider and other useful information. @@ -162,15 +171,16 @@ export interface CustomDataProviderImplementation< */ makeValueRange?( accessors: DataProviderInformationAccessors, - ): [number, number] | null; + ): readonly [number, number] | null; /** * This method is called to check if the current settings are valid. It should return true if the settings are valid * and false if they are not. * As long as the settings are not valid, the provider will not fetch data. * - * @param accessors Accessors to the data and settings of the provider. - * @returns + * @param args Accessors to the data and settings of the provider plus a function that can be used to report an error if + * some settings are not valid. It can be called multiple times if multiple settings are not valid. + * @returns true if the settings are valid, false otherwise. */ - areCurrentSettingsValid?: (accessors: DataProviderInformationAccessors) => boolean; + areCurrentSettingsValid?: (args: AreSettingsValidArgs) => boolean; } diff --git a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation.ts b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation.ts index 7b0354ef4..a6bdacdae 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customGroupImplementation.ts @@ -1,5 +1,7 @@ import type { MakeSettingTypesMap, Settings } from "../settings/settingsDefinitions"; +import type { DefineBasicDependenciesArgs } from "./customSettingsHandler"; + /** * This interface is describing what methods and members a custom group must implement. * A custom group can contain settings but it does not have to. @@ -29,9 +31,11 @@ export interface CustomGroupImplementationWithSettings< * A method that returns the default values of the settings. * @returns The default values of the settings. */ - getDefaultSettingsValues(): TSettingTypes; + getDefaultSettingsValues?(): Partial; + + defineDependencies?(args: DefineBasicDependenciesArgs): void; } export function includesSettings(obj: any): obj is CustomGroupImplementationWithSettings { - return obj.settings !== undefined && obj.getDefaultSettingsValues !== undefined; + return obj.settings !== undefined; } diff --git a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler.ts b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler.ts index 54ca7f99c..c2e28aa8a 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/customSettingsHandler.ts @@ -10,7 +10,6 @@ import type { MakeSettingTypesMap, Settings } from "../settings/settingsDefiniti import type { NullableStoredData, StoredData } from "./sharedTypes"; import type { AvailableValuesType, SettingsKeysFromTuple } from "./utils"; - export interface GetHelperDependency< TSettings extends Settings, TSettingTypes extends MakeSettingTypesMap, @@ -24,8 +23,6 @@ export type SettingAttributes = { enabled: boolean; }; -export const CancelUpdate = Symbol("CancelUpdate"); - export interface UpdateFunc< TReturnValue, TSettings extends Settings, @@ -37,7 +34,18 @@ export interface UpdateFunc< getGlobalSetting: (settingName: T) => GlobalSettings[T]; getHelperDependency: GetHelperDependency; abortSignal: AbortSignal; - }): TReturnValue | typeof CancelUpdate; + }): TReturnValue; +} + +export interface DefineBasicDependenciesArgs< + TSettings extends Settings, + TSettingTypes extends MakeSettingTypesMap = MakeSettingTypesMap, + TKey extends SettingsKeysFromTuple = SettingsKeysFromTuple, +> { + settingAttributesUpdater: ( + settingKey: TSettingKey, + update: UpdateFunc, TSettings, TSettingTypes, TKey>, + ) => Dependency, TSettings, TSettingTypes, TKey>; } export interface DefineDependenciesArgs< @@ -46,7 +54,7 @@ export interface DefineDependenciesArgs< TSettingTypes extends MakeSettingTypesMap = MakeSettingTypesMap, TKey extends SettingsKeysFromTuple = SettingsKeysFromTuple, TStoredDataKey extends keyof TStoredData = keyof TStoredData, -> { +> extends DefineBasicDependenciesArgs { availableSettingsUpdater: ( settingKey: TSettingKey, update: UpdateFunc, TSettings, TSettingTypes, TKey>, @@ -55,10 +63,6 @@ export interface DefineDependenciesArgs< key: K, update: UpdateFunc[TStoredDataKey], TSettings, TSettingTypes, TKey>, ) => Dependency[TStoredDataKey], TSettings, TSettingTypes, TKey>; - settingAttributesUpdater: ( - settingKey: TSettingKey, - update: UpdateFunc, TSettings, TSettingTypes, TKey>, - ) => Dependency, TSettings, TSettingTypes, TKey>; helperDependency: ( update: (args: { getLocalSetting: (settingName: T) => TSettingTypes[T]; diff --git a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/entities.ts b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/entities.ts index 7e17e7bd1..630c5b147 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/entities.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/interfacesAndTypes/entities.ts @@ -1,4 +1,3 @@ - import type { GroupDelegate } from "../delegates/GroupDelegate"; import type { ItemDelegate } from "../delegates/ItemDelegate"; import type { SharedSettingsDelegate } from "../delegates/SharedSettingsDelegate"; @@ -14,6 +13,7 @@ export interface Item { getItemDelegate(): ItemDelegate; serializeState(): SerializedItem; deserializeState(serialized: SerializedItem): void; + beforeDestroy?(): void; } export function instanceofItem(item: any): item is Item { diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/DrilledWellboresSetting.tsx b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/DrilledWellboresSetting.tsx index 044d12803..755bb832e 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/DrilledWellboresSetting.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/DrilledWellboresSetting.tsx @@ -25,14 +25,14 @@ export class DrilledWellboresSetting implements CustomSettingImplementation + availableValues: MakeAvailableValuesTypeBasedOnCategory, ): ValueType { if (!currentValue) { return availableValues; } const matchingValues = currentValue.filter((value) => - availableValues.some((availableValue) => availableValue.wellboreUuid === value.wellboreUuid) + availableValues.some((availableValue) => availableValue.wellboreUuid === value.wellboreUuid), ); if (matchingValues.length === 0) { return availableValues; @@ -65,7 +65,7 @@ export class DrilledWellboresSetting implements CustomSettingImplementation props.value?.map((ident) => ident.wellboreUuid) ?? [], - [props.value] + [props.value], ); return ( diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SensitivitySetting.tsx b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SensitivitySetting.tsx index 6a59608e1..a59bb03cc 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SensitivitySetting.tsx +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/implementations/SensitivitySetting.tsx @@ -18,7 +18,7 @@ type ValueType = SensitivityNameCasePair | null; export class SensitivitySetting implements CustomSettingImplementation { isValueValid( value: ValueType, - availableValues: MakeAvailableValuesTypeBasedOnCategory + availableValues: MakeAvailableValuesTypeBasedOnCategory, ): boolean { if (availableValues.length === 0) { return true; @@ -31,7 +31,7 @@ export class SensitivitySetting implements CustomSettingImplementation sensitivity?.sensitivityName === value.sensitivityName && - sensitivity?.sensitivityCase === value.sensitivityCase + sensitivity?.sensitivityCase === value.sensitivityCase, ); } @@ -50,7 +50,7 @@ export class SensitivitySetting implements CustomSettingImplementation ({ diff --git a/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts b/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts index 1907cf5f9..4ac6cb747 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/settings/settingsDefinitions.ts @@ -5,13 +5,11 @@ import type { ColorScaleSpecification } from "@framework/components/ColorScaleSe import type { RegularEnsembleIdent } from "@framework/RegularEnsembleIdent"; import type { ColorSet } from "@lib/utils/ColorSet"; - import type { AvailableValuesType } from "../interfacesAndTypes/utils"; import type { IntersectionSettingValue } from "./implementations/IntersectionSetting"; import type { SensitivityNameCasePair } from "./implementations/SensitivitySetting"; - export enum SettingCategory { SINGLE_SELECT = "singleSelect", MULTI_SELECT = "multiSelect", @@ -140,6 +138,7 @@ type SettingCategoryAvailableValuesIntersectionReducerMap = { [K in SettingCategory]?: { reducer: AvailableValuesIntersectionReducer; startingValue: AvailableValuesType>; + isValid: (availableValues: AvailableValuesType>) => boolean; }; }; @@ -291,6 +290,7 @@ export const settingCategoryAvailableValuesIntersectionReducerMap: SettingCatego return accumulator.filter((value) => currentAvailableValues.some((av) => isEqual(av, value))); }, startingValue: [], + isValid: (availableValues) => availableValues.length > 0, }, [SettingCategory.MULTI_SELECT]: { reducer: (accumulator, currentAvailableValues) => { @@ -300,6 +300,7 @@ export const settingCategoryAvailableValuesIntersectionReducerMap: SettingCatego return accumulator.filter((value) => currentAvailableValues.some((av) => isEqual(av, value))); }, startingValue: [], + isValid: (availableValues) => availableValues.length > 0, }, [SettingCategory.NUMBER]: { reducer: (accumulator, currentAvailableValues) => { @@ -308,7 +309,8 @@ export const settingCategoryAvailableValuesIntersectionReducerMap: SettingCatego return [Math.max(min, currentMin), Math.min(max, currentMax)]; }, - startingValue: [Number.MIN_VALUE, Number.MAX_VALUE], + startingValue: [-Number.MAX_VALUE, Number.MAX_VALUE], + isValid: (availableValues) => availableValues[0] < availableValues[1], }, [SettingCategory.RANGE]: { reducer: (accumulator, currentAvailableValues) => { @@ -317,7 +319,8 @@ export const settingCategoryAvailableValuesIntersectionReducerMap: SettingCatego return [Math.max(min, currentMin), Math.min(max, currentMax)]; }, - startingValue: [Number.MIN_VALUE, Number.MAX_VALUE], + startingValue: [-Number.MAX_VALUE, Number.MAX_VALUE], + isValid: (availableValues) => availableValues[0] < availableValues[1], }, }; diff --git a/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts b/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts index 861854382..2df753173 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/visualization/VisualizationAssembler.ts @@ -63,7 +63,7 @@ export type TransformerArgs< name: string; isLoading: boolean; getInjectedData: () => TInjectedData; - getValueRange: () => [number, number] | null; + getValueRange: () => Readonly<[number, number]> | null; }; export interface HoverVisualizationsFunction { @@ -302,15 +302,16 @@ export class VisualizationAssembler< accumulatedData = product.accumulatedData; aggregatedErrorMessages.push(...product.aggregatedErrorMessages); hoverVisualizationFunctions.push(product.makeHoverVisualizationsFunction); - annotations.push(...product.annotations); numLoadingDataProviders += product.numLoadingDataProviders; maybeApplyBoundingBox(product.combinedBoundingBox); if (child instanceof Group) { - const group = this.makeGroup(child, product.children, annotations); + const group = this.makeGroup(child, product.children, product.annotations); children.push(group); continue; + } else { + annotations.push(...product.annotations); } children.push(...product.children); @@ -321,6 +322,10 @@ export class VisualizationAssembler< numLoadingDataProviders++; } + if (child.getStatus() === DataProviderStatus.INVALID_SETTINGS) { + continue; + } + if (child.getStatus() === DataProviderStatus.ERROR) { const error = child.getError(); if (error) { diff --git a/frontend/src/modules/_shared/DataProviderFramework/visualization/deckgl/makeDrilledWellTrajectoriesLayer.ts b/frontend/src/modules/_shared/DataProviderFramework/visualization/deckgl/makeDrilledWellTrajectoriesLayer.ts index d9499569e..884467563 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/visualization/deckgl/makeDrilledWellTrajectoriesLayer.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/visualization/deckgl/makeDrilledWellTrajectoriesLayer.ts @@ -53,7 +53,7 @@ function wellTrajectoryToGeojson(wellTrajectory: WellboreTrajectory_api): GeoWel return geometryCollection; } -function zipCoords(xArr: number[], yArr: number[], zArr: number[]): number[][] { +function zipCoords(xArr: readonly number[], yArr: readonly number[], zArr: readonly number[]): number[][] { const coords: number[][] = []; for (let i = 0; i < xArr.length; i++) { coords.push([xArr[i], yArr[i], -zArr[i]]); @@ -64,6 +64,7 @@ function zipCoords(xArr: number[], yArr: number[], zArr: number[]): number[][] { export function makeDrilledWellTrajectoriesLayer({ id, + name, getData, }: TransformerArgs): WellsLayer | null { const fieldWellboreTrajectoriesData = getData(); @@ -102,6 +103,7 @@ export function makeDrilledWellTrajectoriesLayer({ const wellsLayer = new AdvancedWellsLayer({ id: id, + name, data: { type: "FeatureCollection", features: wellLayerDataFeatures, diff --git a/frontend/src/modules/_shared/DataProviderFramework/visualization/utils/colors.ts b/frontend/src/modules/_shared/DataProviderFramework/visualization/utils/colors.ts index 9b2a07c18..f5659d1bb 100644 --- a/frontend/src/modules/_shared/DataProviderFramework/visualization/utils/colors.ts +++ b/frontend/src/modules/_shared/DataProviderFramework/visualization/utils/colors.ts @@ -1,4 +1,3 @@ - import type { Rgb } from "culori"; import { parse } from "culori"; @@ -27,3 +26,4 @@ export function makeColorMapFunctionFromColorScale( return [color.r * 255, color.g * 255, color.b * 255]; }; } +0; diff --git a/frontend/src/modules/_shared/LayerFramework/groups/groupTypes.ts b/frontend/src/modules/_shared/LayerFramework/groups/groupTypes.ts new file mode 100644 index 000000000..9a7ae4eb6 --- /dev/null +++ b/frontend/src/modules/_shared/LayerFramework/groups/groupTypes.ts @@ -0,0 +1,3 @@ +export enum GroupType { + VIEW = "VIEW", +} diff --git a/frontend/src/modules/_shared/components/ColorLegendsContainer/colorLegendsContainer.tsx b/frontend/src/modules/_shared/components/ColorLegendsContainer/colorLegendsContainer.tsx index 9160a4c92..7ba05263a 100644 --- a/frontend/src/modules/_shared/components/ColorLegendsContainer/colorLegendsContainer.tsx +++ b/frontend/src/modules/_shared/components/ColorLegendsContainer/colorLegendsContainer.tsx @@ -1,4 +1,4 @@ -import type React from "react"; +import React from "react"; import type { ColorScale } from "@lib/utils/ColorScale"; import { ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; @@ -34,6 +34,7 @@ function makeMarkers( sectionBottom: number, left: number, barHeight: number, + maxNumLog10: number = 4, ): React.ReactNode[] { const sectionHeight = Math.abs(sectionBottom - sectionTop); @@ -72,7 +73,7 @@ function makeMarkers( fontSize="10" style={TEXT_STYLE} > - {formatLegendValue(value)} + {formatLegendValue(value, maxNumLog10)} , ); @@ -81,7 +82,13 @@ function makeMarkers( return markers; } -function makeDiscreteMarkers(colorScale: ColorScale, left: number, top: number, barHeight: number): React.ReactNode[] { +function makeDiscreteMarkers( + colorScale: ColorScale, + left: number, + top: number, + barHeight: number, + maxNumLog10: number = 4, +): React.ReactNode[] { const minMarkerHeight = STYLE_CONSTANTS.fontSize + 2 * STYLE_CONSTANTS.textGap; const numSteps = colorScale.getNumSteps(); @@ -123,7 +130,7 @@ function makeDiscreteMarkers(colorScale: ColorScale, left: number, top: number, fontSize="10" style={TEXT_STYLE} > - {formatLegendValue(value)} + {formatLegendValue(value, maxNumLog10)} , ); @@ -140,9 +147,11 @@ type ColorLegendProps = { left: number; totalHeight: number; barWidth: number; + maxNumLog10?: number; }; function ColorLegend(props: ColorLegendProps): React.ReactNode { + const clipPathId = React.useId(); const barHeight = props.totalHeight - STYLE_CONSTANTS.offset; const barStartPosition = props.left + STYLE_CONSTANTS.nameLabelWidth + STYLE_CONSTANTS.textGap; @@ -170,7 +179,7 @@ function ColorLegend(props: ColorLegendProps): React.ReactNode { fontSize="10" style={TEXT_STYLE} > - {formatLegendValue(props.colorScale.getMax())} + {formatLegendValue(props.colorScale.getMax(), props.maxNumLog10)} , ); @@ -274,13 +283,13 @@ function ColorLegend(props: ColorLegendProps): React.ReactNode { fontSize="10" style={TEXT_STYLE} > - {formatLegendValue(props.colorScale.getMin())} + {formatLegendValue(props.colorScale.getMin(), props.maxNumLog10)} , ); return ( - + {props.colorScale.getName()} @@ -327,6 +336,7 @@ export type ColorLegendsContainerProps = { colorScales: ColorScaleWithId[]; height: number; position?: "left" | "right"; + maxNumLog10?: number; }; export function ColorLegendsContainer(props: ColorLegendsContainerProps): React.ReactNode { @@ -369,6 +379,7 @@ export function ColorLegendsContainer(props: ColorLegendsContainerProps): React. left={left} totalHeight={height} barWidth={width} + maxNumLog10={props.maxNumLog10} />, ); } @@ -443,9 +454,9 @@ function countDecimalPlaces(value: number): number { return decimalIndex >= 0 ? value.toString().length - decimalIndex - 1 : 0; } -function formatLegendValue(value: number): string { +function formatLegendValue(value: number, maxNumLog10: number = 4): string { const numDecimalPlaces = countDecimalPlaces(value); - if (Math.log10(Math.abs(value)) > 2) { + if (Math.log10(Math.abs(value)) > maxNumLog10) { return value.toExponential(numDecimalPlaces > 2 ? 2 : numDecimalPlaces); } return value.toFixed(numDecimalPlaces > 2 ? 2 : numDecimalPlaces); diff --git a/frontend/src/modules/_shared/customDeckGlLayers/PlaceholderLayer.ts~HEAD b/frontend/src/modules/_shared/customDeckGlLayers/PlaceholderLayer.ts~HEAD new file mode 100644 index 000000000..b146c9fea --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/PlaceholderLayer.ts~HEAD @@ -0,0 +1,21 @@ +import { Layer } from "@deck.gl/core"; + +type PlaceholderLayerProps = { + id: string; +}; + +export class PlaceholderLayer extends Layer { + static layerName: string = "PlaceholderLayer"; + + constructor(props: PlaceholderLayerProps) { + super(props); + } + + initializeState(): void { + return; + } + + render() { + return null; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/PlaceholderLayer.ts~main b/frontend/src/modules/_shared/customDeckGlLayers/PlaceholderLayer.ts~main new file mode 100644 index 000000000..b146c9fea --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/PlaceholderLayer.ts~main @@ -0,0 +1,21 @@ +import { Layer } from "@deck.gl/core"; + +type PlaceholderLayerProps = { + id: string; +}; + +export class PlaceholderLayer extends Layer { + static layerName: string = "PlaceholderLayer"; + + constructor(props: PlaceholderLayerProps) { + super(props); + } + + initializeState(): void { + return; + } + + render() { + return null; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/PreviewLayer/PreviewLayer.ts b/frontend/src/modules/_shared/customDeckGlLayers/PreviewLayer/PreviewLayer.ts new file mode 100644 index 000000000..c4d958110 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/PreviewLayer/PreviewLayer.ts @@ -0,0 +1,55 @@ +import { CompositeLayer, Layer, LayersList } from "@deck.gl/core"; +import { Geometry, ShapeType } from "@lib/utils/geometry"; +import * as vec3 from "@lib/utils/vec3"; + +import { BoxLayer } from "./_private/BoxLayer"; + +export type PreviewLayerProps = { + id: string; + data: { + geometry: Geometry; + }; + zIncreaseDownwards?: boolean; +}; + +export class PreviewLayer extends CompositeLayer { + static layerName = "PreviewLayer"; + + renderLayers(): LayersList { + const { data } = this.props; + + const layers: Layer[] = []; + + const zFactor = this.props.zIncreaseDownwards ? -1 : 1; + + for (const [idx, shape] of data.geometry.shapes.entries()) { + if (shape.type === ShapeType.BOX) { + layers.push( + new BoxLayer({ + id: `${idx}`, + data: { + centerPoint: vec3.toArray( + vec3.multiplyElementWise(shape.centerPoint, vec3.create(1, 1, zFactor)), + ), + dimensions: [ + shape.dimensions.width, + shape.dimensions.height, + shape.dimensions.depth * zFactor, + ], + normalizedEdgeVectors: [ + vec3.toArray( + vec3.multiplyElementWise(shape.normalizedEdgeVectors.u, vec3.create(1, 1, zFactor)), + ), + vec3.toArray( + vec3.multiplyElementWise(shape.normalizedEdgeVectors.v, vec3.create(1, 1, zFactor)), + ), + ], + }, + }), + ); + } + } + + return layers; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/PreviewLayer/_private/BoxLayer.ts b/frontend/src/modules/_shared/customDeckGlLayers/PreviewLayer/_private/BoxLayer.ts new file mode 100644 index 000000000..37302a313 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/PreviewLayer/_private/BoxLayer.ts @@ -0,0 +1,198 @@ +import { CompositeLayer, CompositeLayerProps, Layer, UpdateParameters } from "@deck.gl/core"; +import { SimpleMeshLayer } from "@deck.gl/mesh-layers"; +import * as vec3 from "@lib/utils/vec3"; +import { Geometry } from "@luma.gl/engine"; + +export type RectangleLayerData = { + centerPoint: [number, number, number]; + dimensions: [number, number, number]; + normalizedEdgeVectors: [[number, number, number], [number, number, number]]; +}; + +export type RectangleLayerProps = { + id: string; + data: RectangleLayerData; +}; + +export class BoxLayer extends CompositeLayer { + static layerName = "BoxLayer"; + + // @ts-expect-error - private + state!: { + geometry: Geometry; + }; + + private makeGeometry(): Geometry { + const { data } = this.props; + + const vertices: Float32Array = new Float32Array(8 * 3); + const indices: Uint16Array = new Uint16Array(3 * 2 * 6); + + const [centerX, centerY, centerZ] = data.centerPoint; + const [width, height, depth] = data.dimensions; + const [[uX, uY, uZ], [vX, vY, vZ]] = data.normalizedEdgeVectors; + + const halfWidth = width / 2; + const halfHeight = height / 2; + const halfDepth = depth / 2; + + const center = vec3.fromArray([centerX, centerY, centerZ]); + const vecU = vec3.fromArray([uX, uY, uZ]); + const vecV = vec3.fromArray([vX, vY, vZ]); + + // Make normal vector from u and v + const vecW = vec3.cross(vecU, vecV); + + // Make vertices wrt to vectors + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, halfWidth), + vec3.scale(vecV, halfHeight), + vec3.scale(vecW, halfDepth), + ), + ), + 0, + ); + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, halfWidth), + vec3.scale(vecV, -halfHeight), + vec3.scale(vecW, halfDepth), + ), + ), + 3, + ); + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, -halfWidth), + vec3.scale(vecV, -halfHeight), + vec3.scale(vecW, halfDepth), + ), + ), + 6, + ); + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, -halfWidth), + vec3.scale(vecV, halfHeight), + vec3.scale(vecW, halfDepth), + ), + ), + 9, + ); + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, halfWidth), + vec3.scale(vecV, halfHeight), + vec3.scale(vecW, -halfDepth), + ), + ), + 12, + ); + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, halfWidth), + vec3.scale(vecV, -halfHeight), + vec3.scale(vecW, -halfDepth), + ), + ), + 15, + ); + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, -halfWidth), + vec3.scale(vecV, -halfHeight), + vec3.scale(vecW, -halfDepth), + ), + ), + 18, + ); + vertices.set( + vec3.toArray( + vec3.concat( + center, + vec3.scale(vecU, -halfWidth), + vec3.scale(vecV, halfHeight), + vec3.scale(vecW, -halfDepth), + ), + ), + 21, + ); + + // Front + indices.set([0, 1, 2], 0); + indices.set([0, 2, 3], 3); + + // Back + indices.set([4, 6, 5], 6); + indices.set([4, 7, 6], 9); + + // Left + indices.set([0, 7, 4], 12); + indices.set([0, 3, 7], 15); + + // Right + indices.set([1, 5, 6], 18); + indices.set([1, 6, 2], 21); + + // Top + indices.set([3, 2, 6], 24); + indices.set([3, 6, 7], 27); + + // Bottom + indices.set([0, 4, 5], 30); + indices.set([0, 5, 1], 33); + + return new Geometry({ + topology: "triangle-list", + attributes: { + positions: vertices, + }, + indices, + }); + } + + initializeState(): void { + this.setState({ + ...this.state, + isHovered: false, + isLoaded: false, + }); + } + + updateState({ changeFlags }: UpdateParameters>>) { + if (changeFlags.dataChanged) { + this.setState({ + geometry: this.makeGeometry(), + }); + } + } + + renderLayers() { + return [ + new SimpleMeshLayer({ + id: "mesh", + data: [0], + mesh: this.state.geometry, + getPosition: (d) => [0, 0, 0], + getColor: [100, 100, 100, 100], + material: { ambient: 0.95, diffuse: 1, shininess: 0, specularColor: [0, 0, 0] }, + pickable: false, + }), + ]; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/SeismicFenceMeshLayer.ts b/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/SeismicFenceMeshLayer.ts new file mode 100644 index 000000000..7ec67e274 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/SeismicFenceMeshLayer.ts @@ -0,0 +1,393 @@ +import { + CompositeLayer, + CompositeLayerProps, + GetPickingInfoParams, + Layer, + PickingInfo, + UpdateParameters, +} from "@deck.gl/core"; +import { Geometry as LoadingGeometry } from "@lib/utils/geometry"; +import { Geometry } from "@luma.gl/engine"; +import { ExtendedLayerProps } from "@webviz/subsurface-viewer"; +import { BoundingBox3D, ReportBoundingBoxAction } from "@webviz/subsurface-viewer/dist/components/Map"; + +import { isEqual } from "lodash"; +import workerpool from "workerpool"; + +import { ExtendedSimpleMeshLayer } from "./_private/ExtendedSimpleMeshLayer"; +import { WebworkerParameters, makeMesh } from "./_private/worker"; + +import { PreviewLayer } from "../PreviewLayer/PreviewLayer"; + +export type SeismicFenceMeshLayerPickingInfo = { + properties?: { name: string; value: number }[]; +} & PickingInfo; + +export type SeismicFenceSection = { + numSamplesU: number; + numSamplesV: number; + properties: Float32Array; + boundingBox: number[][]; // [minX, minY, minZ, maxX, maxY, maxZ] +}; + +export interface SeismicFenceMeshLayerProps extends ExtendedLayerProps { + data: { + sections: SeismicFenceSection[]; + }; + colorMapFunction: (value: number) => [number, number, number]; + hoverable?: boolean; + zIncreaseDownwards?: boolean; + isLoading?: boolean; + loadingGeometry?: LoadingGeometry; + + // Non public properties: + reportBoundingBox?: React.Dispatch; +} + +function assert(condition: any, msg?: string): asserts condition { + if (!condition) { + throw new Error(msg); + } +} + +export class SeismicFenceMeshLayer extends CompositeLayer { + static layerName: string = "SeismicFenceMeshLayer"; + + private _pool = workerpool.pool({ + workerType: "web", + maxWorkers: 10, + workerOpts: { + // By default, Vite uses a module worker in dev mode, which can cause your application to fail. + // Therefore, we need to use a module worker in dev mode and a classic worker in prod mode. + type: import.meta.env.PROD ? undefined : "module", + }, + }); + private _numTasks = 0; + private _numTasksCompleted = 0; + private _numTasksFailed = 0; + private _sharedVerticesBuffer: SharedArrayBuffer | null = null; + private _sharedIndicesBuffer: SharedArrayBuffer | null = null; + private _colorsArray: Float32Array = new Float32Array(); + + // @ts-expect-error - This is how deck.gl expects the state to be defined + state!: { + geometry: Geometry; + isHovered: boolean; + meshCreated: boolean; + colorsArrayCreated: boolean; + }; + + initializeState(): void { + this.setState({ + isHovered: false, + meshCreated: false, + colorsArrayCreated: false, + geometry: new Geometry({ + attributes: { + positions: new Float32Array(), + }, + topology: "triangle-list", + }), + }); + + this.rebuildMesh(); + this.recolorMesh(); + } + + updateState({ + props, + oldProps, + }: UpdateParameters>>) { + const meshRecomputationRequired = + !isEqual(oldProps.data?.sections.length, props.data?.sections.length) || + !isEqual(oldProps.data?.sections, props.data?.sections) || + !isEqual(oldProps.zIncreaseDownwards, props.zIncreaseDownwards); + + const colorMapFunctionChanged = !isEqual(oldProps.colorMapFunction, props.colorMapFunction); + + if (!meshRecomputationRequired && !colorMapFunctionChanged) { + return; + } + + if (props.isLoading) { + return; + } + + if (meshRecomputationRequired) { + this.rebuildMesh(); + } + + if (colorMapFunctionChanged) { + this.recolorMesh(); + } + } + + private calcNumVerticesForSection(section: SeismicFenceSection): number { + return section.numSamplesU * section.numSamplesV * 3; + } + + private calcNumIndicesForSection(section: SeismicFenceSection): number { + return (section.numSamplesU - 1) * (section.numSamplesV - 1) * 6; + } + + private initSharedBuffers() { + const { data } = this.props; + + let totalNumVertices = 0; + let totalNumIndices = 0; + + for (const section of data.sections) { + totalNumVertices += this.calcNumVerticesForSection(section); + totalNumIndices += this.calcNumIndicesForSection(section); + } + this._sharedVerticesBuffer = new SharedArrayBuffer(totalNumVertices * Float32Array.BYTES_PER_ELEMENT); + this._sharedIndicesBuffer = new SharedArrayBuffer(totalNumIndices * Uint32Array.BYTES_PER_ELEMENT); + } + + private calcBoundingBox(): BoundingBox3D { + let xmin = Number.MAX_VALUE; + let ymin = Number.MAX_VALUE; + let zmin = Number.MAX_VALUE; + let xmax = Number.MIN_VALUE; + let ymax = Number.MIN_VALUE; + let zmax = Number.MIN_VALUE; + + const zFactor = this.props.zIncreaseDownwards ? -1 : 1; + + for (const section of this.props.data.sections) { + for (const point of section.boundingBox) { + xmin = Math.min(xmin, point[0]); + ymin = Math.min(ymin, point[1]); + zmin = Math.min(zmin, zFactor * point[2]); + xmax = Math.max(xmax, point[0]); + ymax = Math.max(ymax, point[1]); + zmax = Math.max(zmax, zFactor * point[2]); + } + } + + return [xmin, ymin, zmin, xmax, ymax, zmax]; + } + + private maybeUpdateGeometry() { + const { geometry } = this.state; + if (this._numTasks === this._numTasksCompleted) { + const verticesArr = new Float32Array(this._sharedVerticesBuffer!); + const indicesArr = new Uint32Array(this._sharedIndicesBuffer!); + + this.setState({ + ...this.state, + geometry: new Geometry({ + attributes: { + ...geometry.attributes, + positions: verticesArr, + }, + topology: "triangle-list", + indices: indicesArr, + }), + meshCreated: true, + }); + + this.props.reportBoundingBox?.({ + layerBoundingBox: this.calcBoundingBox(), + }); + } + } + + private calcOrigin(): [number, number, number] { + const { data, zIncreaseDownwards } = this.props; + + if (data.sections.length === 0) { + return [0, 0, 0]; + } + + const firstSection = data.sections[0]; + + return [ + firstSection.boundingBox[0][0], + firstSection.boundingBox[0][1], + (zIncreaseDownwards ? -1 : 1) * firstSection.boundingBox[0][2], + ]; + } + + private rebuildMesh() { + const { zIncreaseDownwards } = this.props; + + this.setState({ ...this.state, meshCreated: false }); + + this.initSharedBuffers(); + + assert(this._sharedVerticesBuffer !== null, "Shared vertices buffer is null"); + assert(this._sharedIndicesBuffer !== null, "Shared indices buffer is null"); + + const origin = this.calcOrigin(); + + let verticesIndex = 0; + let indicesIndex = 0; + for (const section of this.props.data.sections) { + this._numTasks++; + + const offset: [number, number, number] = [ + section.boundingBox[0][0] - origin[0], + section.boundingBox[0][1] - origin[1], + (zIncreaseDownwards ? -1 : 1) * section.boundingBox[0][2] - origin[2], + ]; + + const params: WebworkerParameters = { + offset, + numSamplesU: section.numSamplesU, + numSamplesV: section.numSamplesV, + boundingBox: section.boundingBox, + startVerticesIndex: verticesIndex, + startIndicesIndex: indicesIndex, + sharedVerticesBuffer: this._sharedVerticesBuffer, + sharedIndicesBuffer: this._sharedIndicesBuffer, + zIncreasingDownwards: this.props.zIncreaseDownwards ?? false, + }; + + verticesIndex += this.calcNumVerticesForSection(section); + indicesIndex += this.calcNumIndicesForSection(section); + + this._pool + .exec(makeMesh, [{ ...params }]) + .then(() => { + this._numTasksCompleted++; + this.maybeUpdateGeometry(); + }) + .catch(() => { + this._numTasksFailed++; + }); + } + } + + private recolorMesh() { + const { geometry } = this.state; + + this.setState({ ...this.state, colorsArrayCreated: false }); + + this.makeColorsArray().then(() => { + this.setState({ + ...this.state, + geometry: new Geometry({ + attributes: { + ...geometry.attributes, + colors: { + value: this._colorsArray, + size: 4, + }, + }, + topology: "triangle-list", + indices: geometry.indices, + }), + colorsArrayCreated: true, + }); + }); + } + + private async makeColorsArray() { + const { data, colorMapFunction } = this.props; + + this._colorsArray = new Float32Array( + data.sections.reduce((acc, section) => acc + section.properties.length * 4, 0), + ); + + let colorIndex = 0; + for (const section of data.sections) { + for (let i = 0; i < section.properties.length; i++) { + const [r, g, b] = colorMapFunction(section.properties[i]); + this._colorsArray[colorIndex * 4 + 0] = r / 255; + this._colorsArray[colorIndex * 4 + 1] = g / 255; + this._colorsArray[colorIndex * 4 + 2] = b / 255; + this._colorsArray[colorIndex * 4 + 3] = 1; + colorIndex++; + } + } + } + + private getProperty(vertexIndex: number): number { + const { data } = this.props; + + let offset = 0; + for (const section of data.sections) { + if (vertexIndex < offset + section.properties.length) { + return section.properties[vertexIndex - offset]; + } + offset += section.properties.length; + } + + return 0; + } + + getPickingInfo({ info }: GetPickingInfoParams): SeismicFenceMeshLayerPickingInfo { + const { zIncreaseDownwards } = this.props; + if (!info.color) { + return info; + } + + const r = info.color[0]; + const g = info.color[1]; + const b = info.color[2]; + + const vertexIndex = r * 256 * 256 + g * 256 + b; + + const property = this.getProperty(vertexIndex); + + if (property === undefined) { + return info; + } + + const properties: { name: string; value: number }[] = []; + properties.push({ name: "Property", value: property }); + if (info.coordinate?.length === 3) { + properties.push({ name: "Depth", value: (zIncreaseDownwards ? -1 : 1) * info.coordinate[2] }); + } + + return { + ...info, + properties, + }; + } + + onHover(pickingInfo: PickingInfo): boolean { + this.setState({ ...this.state, isHovered: pickingInfo.index !== -1 }); + return false; + } + + renderLayers() { + const { isLoading, zIncreaseDownwards, loadingGeometry } = this.props; + const { geometry, meshCreated, colorsArrayCreated } = this.state; + + const origin = this.calcOrigin(); + + const layers: Layer[] = []; + + if ((isLoading || !meshCreated || !colorsArrayCreated) && loadingGeometry) { + layers.push( + new PreviewLayer({ + id: "seismic-fence-mesh-layer-loading", + data: { + geometry: loadingGeometry, + }, + zIncreaseDownwards, + }), + ); + } else { + layers.push( + new ExtendedSimpleMeshLayer({ + id: "seismic-fence-mesh-layer", + data: [0], + mesh: geometry, + getPosition: origin, + getColor: [255, 255, 255, 255], + material: { ambient: 0.95, diffuse: 1, shininess: 0, specularColor: [0, 0, 0] }, + pickable: true, + }), + ); + } + + return layers; + } + + finalize() { + this._pool.terminate(); + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/_private/ExtendedSimpleMeshLayer.ts b/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/_private/ExtendedSimpleMeshLayer.ts new file mode 100644 index 000000000..19593b53f --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/_private/ExtendedSimpleMeshLayer.ts @@ -0,0 +1,46 @@ +import { SimpleMeshLayer } from "@deck.gl/mesh-layers"; + +export class ExtendedSimpleMeshLayer extends SimpleMeshLayer { + static name = "ExtendedSimpleMeshLayer"; + static componentName = "ExtendedSimpleMeshLayer"; + + getShaders() { + return { + ...super.getShaders(), + inject: { + "vs:#decl": ` + flat out int vertexIndex;`, + "vs:#main-end": ` + vertexIndex = gl_VertexID;`, + "fs:#decl": ` + flat in int vertexIndex; + + vec4 encodeVertexIndexToRGB (int vertexIndex) { + float r = 0.0; + float g = 0.0; + float b = 0.0; + + if (vertexIndex >= (256 * 256) - 1) { + r = floor(float(vertexIndex) / (256.0 * 256.0)); + vertexIndex -= int(r * (256.0 * 256.0)); + } + + if (vertexIndex >= 256 - 1) { + g = floor(float(vertexIndex) / 256.0); + vertexIndex -= int(g * 256.0); + } + + b = float(vertexIndex); + + return vec4(r / 255.0, g / 255.0, b / 255.0, 1.0); + } + `, + "fs:#main-start": ` + if (picking.isActive > 0.5 && !(picking.isAttribute > 0.5)) { + fragColor = encodeVertexIndexToRGB(vertexIndex); + return; + }`, + }, + }; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/_private/worker.ts b/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/_private/worker.ts new file mode 100644 index 000000000..e1e6c4470 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/SeismicFenceMeshLayer/_private/worker.ts @@ -0,0 +1,67 @@ +export type WebworkerParameters = { + offset: [number, number, number]; + sharedVerticesBuffer: SharedArrayBuffer; + sharedIndicesBuffer: SharedArrayBuffer; + startVerticesIndex: number; + startIndicesIndex: number; + numSamplesU: number; + numSamplesV: number; + boundingBox: number[][]; + zIncreasingDownwards: boolean; +}; + +/* +Generates a mesh for a seismic fence. +@param parameters The parameters for generating the mesh +- offset: The offset of the mesh +- sharedVerticesBuffer: The shared vertices buffer +- sharedIndicesBuffer: The shared indices buffer +- startVerticesIndex: The start index of the vertices in the shared memory buffer +- startIndicesIndex: The start index of the indices in the shared memory buffer +- numSamplesU: The number of samples in the U direction +- numSamplesV: The number of samples in the V direction +- boundingBox: The bounding box of the mesh - used to transform UV coordinates to XYZ coordinates +- zIncreasingDownwards: Whether the Z axis increases downwards + +*/ +export function makeMesh(parameters: WebworkerParameters) { + const bbox = parameters.boundingBox; + + const vectorV = [bbox[1][0] - bbox[0][0], bbox[1][1] - bbox[0][1], bbox[1][2] - bbox[0][2]]; + const vectorU = [bbox[2][0] - bbox[0][0], bbox[2][1] - bbox[0][1], bbox[2][2] - bbox[0][2]]; + + function transformUVToXYZ(u: number, v: number): [number, number, number] { + const x = parameters.offset[0] + u * vectorU[0] + v * vectorV[0]; + const y = parameters.offset[1] + u * vectorU[1] + v * vectorV[1]; + const z = parameters.offset[2] + (parameters.zIncreasingDownwards ? -1 : 1) * (v * vectorV[2] + u * vectorU[2]); + return [x, y, z]; + } + + const verticesArray = new Float32Array(parameters.sharedVerticesBuffer); + const indicesArray = new Uint32Array(parameters.sharedIndicesBuffer); + + const stepU = 1.0 / (parameters.numSamplesU - 1); + const stepV = 1.0 / (parameters.numSamplesV - 1); + + let verticesIndex = parameters.startVerticesIndex; + let indicesIndex = parameters.startIndicesIndex; + + for (let v = 0; v < parameters.numSamplesV; v++) { + for (let u = 0; u < parameters.numSamplesU; u++) { + const [x, y, z] = transformUVToXYZ(u * stepU, v * stepV); + verticesArray[verticesIndex++] = x; + verticesArray[verticesIndex++] = y; + verticesArray[verticesIndex++] = z; + + if (u > 0 && v > 0) { + indicesArray[indicesIndex++] = (v - 1) * parameters.numSamplesU + u - 1; + indicesArray[indicesIndex++] = (v - 1) * parameters.numSamplesU + u; + indicesArray[indicesIndex++] = v * parameters.numSamplesU + u - 1; + + indicesArray[indicesIndex++] = v * parameters.numSamplesU + u - 1; + indicesArray[indicesIndex++] = (v - 1) * parameters.numSamplesU + u; + indicesArray[indicesIndex++] = v * parameters.numSamplesU + u; + } + } + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellborePicksLayer.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellborePicksLayer.ts index 1bb9993c7..9ef1b6692 100644 --- a/frontend/src/modules/_shared/customDeckGlLayers/WellborePicksLayer.ts +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellborePicksLayer.ts @@ -1,7 +1,18 @@ -import type { CompositeLayerProps, FilterContext, Layer, UpdateParameters } from "@deck.gl/core"; -import { CompositeLayer } from "@deck.gl/core"; -import { GeoJsonLayer, TextLayer } from "@deck.gl/layers"; -import type { Feature, FeatureCollection } from "geojson"; +import { + CompositeLayer, + CompositeLayerProps, + FilterContext, + Layer, + LayerContext, + PickingInfo, + UpdateParameters, +} from "@deck.gl/core"; +import { PointCloudLayer } from "@deck.gl/layers"; +import { COLORS } from "@lib/utils/colorConstants"; +import { ExtendedLayerProps } from "@webviz/subsurface-viewer"; +import { BoundingBox3D, ReportBoundingBoxAction } from "@webviz/subsurface-viewer/dist/components/Map"; + +import { isEqual } from "lodash"; export type WellborePicksLayerData = { easting: number; @@ -12,20 +23,37 @@ export type WellborePicksLayerData = { slotName: string; }; -type TextLayerData = { - coordinates: [number, number, number]; - name: string; -}; - -export type WellBorePicksLayerProps = { +export interface WellBorePicksLayerProps extends ExtendedLayerProps { id: string; data: WellborePicksLayerData[]; -}; + zIncreaseDownwards?: boolean; + + // Non public properties: + reportBoundingBox?: React.Dispatch; +} + +// properties.name is required to trigger tooltip in Map.tsx component in subsurface-viewer +type PointsData = { coordinates: [number, number, number]; properties: { name: string } }; export class WellborePicksLayer extends CompositeLayer { static layerName: string = "WellborePicksLayer"; - private _textData: TextLayerData[] = []; - private _pointsData: FeatureCollection | null = null; + + // @ts-expect-error - This is how deck.gl expects the state to be defined + // For instance, see her: + // https://github.com/visgl/deck.gl/blob/master/modules/layers/src/point-cloud-layer/point-cloud-layer.ts#L123 + state!: { + pointsData: PointsData[]; + hoveredIndex: number | null; + }; + + initializeState(context: LayerContext): void { + super.initializeState(context); + + this.state = { + pointsData: [], + hoveredIndex: null, + }; + } filterSubLayer(context: FilterContext): boolean { if (context.layer.id.includes("text")) { @@ -35,87 +63,94 @@ export class WellborePicksLayer extends CompositeLayer return true; } - updateState(params: UpdateParameters>>): void { - const features: Feature[] = params.props.data.map((wellPick) => { + private calcBoundingBox(): BoundingBox3D { + const { data } = this.props; + + let minX = Number.MAX_VALUE; + let minY = Number.MAX_VALUE; + let minZ = Number.MAX_VALUE; + let maxX = Number.MIN_VALUE; + let maxY = Number.MIN_VALUE; + let maxZ = Number.MIN_VALUE; + + for (const wellPick of data) { + minX = Math.min(minX, wellPick.easting); + minY = Math.min(minY, wellPick.northing); + minZ = Math.min(minZ, wellPick.tvdMsl); + maxX = Math.max(maxX, wellPick.easting); + maxY = Math.max(maxY, wellPick.northing); + maxZ = Math.max(maxZ, wellPick.tvdMsl); + } + + return [minX, minY, minZ, maxX, maxY, maxZ]; + } + + updateState({ + changeFlags, + props, + oldProps, + }: UpdateParameters>>): void { + if (!changeFlags.dataChanged) { + return; + } + + if (isEqual(props.data, oldProps.data)) { + return; + } + + const pointsData: PointsData[] = props.data.map((wellPick) => { return { - type: "Feature", - geometry: { - type: "Point", - coordinates: [wellPick.easting, wellPick.northing], - }, + coordinates: [wellPick.easting, wellPick.northing, wellPick.tvdMsl], properties: { name: `${wellPick.wellBoreUwi}, TVD_MSL: ${wellPick.tvdMsl}, MD: ${wellPick.md}`, - color: [100, 100, 100, 100], }, }; }); - const pointsData: FeatureCollection = { - type: "FeatureCollection", - features: features, - }; + this.setState({ + pointsData, + }); - const textData: TextLayerData[] = this.props.data.map((wellPick) => { - return { - coordinates: [wellPick.easting, wellPick.northing, wellPick.tvdMsl], - name: wellPick.wellBoreUwi, - }; + this.props.reportBoundingBox?.({ + layerBoundingBox: this.calcBoundingBox(), }); + } + + onHover(info: PickingInfo): boolean { + const { index } = info; + this.setState({ hoveredIndex: index }); - this._pointsData = pointsData; - this._textData = textData; + return false; } renderLayers() { - const fontSize = 16; - const sizeMinPixels = 16; - const sizeMaxPixels = 16; + const { zIncreaseDownwards } = this.props; + const { pointsData, hoveredIndex } = this.state; return [ - new GeoJsonLayer( - this.getSubLayerProps({ - id: "points", - data: this._pointsData ?? undefined, - filled: true, - lineWidthMinPixels: 5, - lineWidthMaxPixels: 5, - lineWidthUnits: "meters", - parameters: { - depthTest: false, - }, - getLineWidth: 1, - depthTest: false, - pickable: true, - getText: (d: Feature) => d.properties?.wellBoreUwi, - getLineColor: [50, 50, 50], - }), - ), - - new TextLayer( - this.getSubLayerProps({ - id: "text", - data: this._textData, - pickable: true, - getColor: [255, 255, 255], - fontWeight: 800, - fontSettings: { - fontSize: fontSize * 2, - sdf: true, - }, - outlineColor: [0, 0, 0], - outlineWidth: 2, - getSize: 12, - sdf: true, - sizeScale: fontSize, - sizeUnits: "meters", - sizeMinPixels: sizeMinPixels, - sizeMaxPixels: sizeMaxPixels, - getAlignmentBaseline: "top", - getTextAnchor: "middle", - getPosition: (d: TextLayerData) => d.coordinates, - getText: (d: TextLayerData) => d.name, - }), - ), + new PointCloudLayer({ + id: `${this.props.id}-points`, + data: pointsData, + pickable: true, + getPosition: (d) => { + const zFactor = zIncreaseDownwards ? -1 : 1; + return [d.coordinates[0], d.coordinates[1], d.coordinates[2] * zFactor]; + }, + getColor: (_, ctx) => { + if (ctx.index === hoveredIndex) { + return COLORS.hover; + } + + return [100, 100, 100]; + }, + pointSize: 15, + sizeUnits: "meters", + material: { ambient: 0.75, diffuse: 0.4, shininess: 0, specularColor: [0, 0, 0] }, + updateTriggers: { + getColor: [hoveredIndex], + getPosition: [zIncreaseDownwards], + }, + }), ]; } } diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/WellsLayer.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/WellsLayer.ts new file mode 100644 index 000000000..8e8d5ba47 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/WellsLayer.ts @@ -0,0 +1,142 @@ +import { + CompositeLayer, + GetPickingInfoParams, + LayersList, + type Material, + PickingInfo, + type UpdateParameters, +} from "@deck.gl/core"; +import * as vec3 from "@lib/utils/vec3"; +import { ExtendedLayerProps, LayerPickInfo } from "@webviz/subsurface-viewer"; +import { BoundingBox3D, ReportBoundingBoxAction } from "@webviz/subsurface-viewer/dist/components/Map"; + +import { type PipeLayerProps, PipesLayer } from "./_private/PipeLayer"; +import { getMd } from "./_private/wellTrajectoryUtils"; + +export type WellsLayerData = { + coordinates: [number, number, number][]; + properties: { uuid: string; name: string; mdArray: number[] }; +}[]; + +export interface WellsLayerProps extends ExtendedLayerProps { + id: string; + data: WellsLayerData; + zIncreaseDownwards?: boolean; + + boundingBox: BoundingBox3D; + + // Non public properties: + reportBoundingBox?: React.Dispatch; +} + +const MATERIAL: Material = { + ambient: 0.2, + diffuse: 0.6, + shininess: 132, + specularColor: [255, 255, 255], +}; + +export class WellsLayer extends CompositeLayer { + static layerName: string = "WellsLayer"; + + // @ts-expect-error - This is how deck.gl expects the state to be defined + state!: { + hoveredPipeIndex: number | null; + pipesLayerData: PipeLayerProps["data"]; + }; + + initializeState(): void { + this.setState({ + hoveredPipeIndex: null, + pipesLayerData: [], + }); + } + + shouldUpdateState({ changeFlags }: UpdateParameters): boolean { + return changeFlags.dataChanged !== false; + } + + updateState(params: UpdateParameters): void { + super.updateState(params); + + const { boundingBox } = this.props; + + if (params.changeFlags.dataChanged) { + const pipesLayerData = this.props.data.map((well) => { + return { + id: well.properties.uuid, + centerLinePath: well.coordinates.map((coord) => { + return { x: coord[0], y: coord[1], z: coord[2] }; + }), + }; + }); + + this.setState({ pipesLayerData }); + } + + this.props.reportBoundingBox?.({ + layerBoundingBox: boundingBox, + }); + } + + getPickingInfo({ info }: GetPickingInfoParams): LayerPickInfo { + if (!info.sourceLayer?.id.includes("pipes-layer")) { + return info; + } + + const wellbore = this.props.data[info.index]; + if (!wellbore) { + return info; + } + info.object = this.props.data[info.index]; + + const coordinate = info.coordinate ?? [0, 0, 0]; + + const trajectory = wellbore.coordinates.map((coord) => vec3.fromArray(coord)); + + const md = getMd(vec3.fromArray(coordinate), wellbore.properties.mdArray, trajectory); + + if (md !== null) { + return { + ...info, + properties: [ + { + name: `MD ${wellbore.properties.name}`, + value: md, + }, + ], + }; + } + + return info; + } + + onHover(info: PickingInfo): boolean { + if (!info.sourceLayer) { + return false; + } + + const { sourceLayer } = info; + if (sourceLayer.id !== "hover-path-layer") { + return false; + } + + const { index } = info; + this.setState({ hoveredPipeIndex: index }); + return false; + } + + renderLayers(): LayersList { + const { pipesLayerData } = this.state; + return [ + new PipesLayer({ + id: "pipes-layer", + data: pipesLayerData, + material: MATERIAL, + pickable: true, + // @ts-expect-error - This is how deck.gl expects the state to be defined + parameters: { depthTest: true }, + }), + ]; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Line.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Line.ts new file mode 100644 index 000000000..3231cf035 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Line.ts @@ -0,0 +1,32 @@ +import * as vec3 from "@lib/utils/vec3"; + +export type Line = { + point: vec3.Vec3; + direction: vec3.Vec3; +}; + +export function fromPoints(p1: vec3.Vec3, p2: vec3.Vec3): Line { + return { + point: p1, + direction: vec3.subtract(p2, p1), + }; +} + +export function fromPointAndDirection(point: vec3.Vec3, direction: vec3.Vec3): Line { + return { + point, + direction, + }; +} + +export function intersect(line1: Line, line2: Line): vec3.Vec3 | null { + const cross = vec3.cross(line1.direction, line2.direction); + const crossLength = vec3.length(cross); + if (crossLength < 1e-6) { + return null; + } + + const line1ToLine2 = vec3.subtract(line2.point, line1.point); + const t = vec3.dot(vec3.cross(line1ToLine2, line2.direction), cross) / crossLength ** 2; + return vec3.add(line1.point, vec3.scale(line1.direction, t)); +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Mat3.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Mat3.ts new file mode 100644 index 000000000..e0310d58c --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Mat3.ts @@ -0,0 +1,113 @@ +/** + * A 3x3 matrix. + * + * The matrix is stored in column-major order. + */ +export type Mat3 = { + m00: number; + m01: number; + m02: number; + m10: number; + m11: number; + m12: number; + m20: number; + m21: number; + m22: number; +}; + +export function setRow(matrix: Mat3, rowIndex: number, x: number, y: number, z: number): Mat3 { + switch (rowIndex) { + case 0: + return { + ...matrix, + m00: x, + m01: y, + m02: z, + }; + case 1: + return { + ...matrix, + m10: x, + m11: y, + m12: z, + }; + case 2: + return { + ...matrix, + m20: x, + m21: y, + m22: z, + }; + default: + throw new Error(`Invalid row index: ${rowIndex}`); + } +} + +export function setColumn(matrix: Mat3, columnIndex: number, x: number, y: number, z: number): Mat3 { + switch (columnIndex) { + case 0: + return { + ...matrix, + m00: x, + m10: y, + m20: z, + }; + case 1: + return { + ...matrix, + m01: x, + m11: y, + m21: z, + }; + case 2: + return { + ...matrix, + m02: x, + m12: y, + m22: z, + }; + default: + throw new Error(`Invalid column index: ${columnIndex}`); + } +} + +export function transpose(matrix: Mat3) { + matrix.m01 = matrix.m10; + matrix.m02 = matrix.m20; + matrix.m10 = matrix.m01; + matrix.m12 = matrix.m21; + matrix.m20 = matrix.m02; + matrix.m21 = matrix.m12; +} + +export function invert(matrix: Mat3) { + const tmp: number[] = []; + + tmp[0] = matrix.m11 * matrix.m22 - matrix.m12 * matrix.m21; + tmp[1] = matrix.m21 * matrix.m02 - matrix.m22 * matrix.m01; + tmp[2] = matrix.m01 * matrix.m12 - matrix.m02 * matrix.m11; + tmp[3] = matrix.m12 * matrix.m20 - matrix.m10 * matrix.m22; + tmp[4] = matrix.m22 * matrix.m00 - matrix.m20 * matrix.m02; + tmp[5] = matrix.m02 * matrix.m10 - matrix.m00 * matrix.m12; + tmp[6] = matrix.m10 * matrix.m21 - matrix.m11 * matrix.m20; + tmp[7] = matrix.m20 * matrix.m01 - matrix.m21 * matrix.m00; + tmp[8] = matrix.m00 * matrix.m11 - matrix.m01 * matrix.m10; + + const det = matrix.m00 * tmp[0] + matrix.m01 * tmp[3] + matrix.m02 * tmp[6]; + + if (det === 0) { + throw new Error("Matrix is not invertible"); + } + + const invDet = 1.0 / det; + + matrix.m00 = tmp[0] * invDet; + matrix.m01 = tmp[1] * invDet; + matrix.m02 = tmp[2] * invDet; + matrix.m10 = tmp[3] * invDet; + matrix.m11 = tmp[4] * invDet; + matrix.m12 = tmp[5] * invDet; + matrix.m20 = tmp[6] * invDet; + matrix.m21 = tmp[7] * invDet; + matrix.m22 = tmp[8] * invDet; +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Mat4.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Mat4.ts new file mode 100644 index 000000000..6fa08f76e --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Mat4.ts @@ -0,0 +1,465 @@ +import * as vec3 from "@lib/utils/vec3"; + +import * as mat3 from "./Mat3"; + +const EPSILON = 0.00001; + +/** + * A 4x4 matrix. + * + * The matrix is stored in column-major order. + * + */ +export type Mat4 = { + m00: number; + m01: number; + m02: number; + m03: number; + m10: number; + m11: number; + m12: number; + m13: number; + m20: number; + m21: number; + m22: number; + m23: number; + m30: number; + m31: number; + m32: number; + m33: number; +}; + +export function identity(): Mat4 { + return { + m00: 1, + m01: 0, + m02: 0, + m03: 0, + m10: 0, + m11: 1, + m12: 0, + m13: 0, + m20: 0, + m21: 0, + m22: 1, + m23: 0, + m30: 0, + m31: 0, + m32: 0, + m33: 1, + }; +} + +export function setRow(matrix: Mat4, rowIndex: number, x: number, y: number, z: number, w: number): Mat4 { + switch (rowIndex) { + case 0: + return { + ...matrix, + m00: x, + m01: y, + m02: z, + m03: w, + }; + case 1: + return { + ...matrix, + m10: x, + m11: y, + m12: z, + m13: w, + }; + case 2: + return { + ...matrix, + m20: x, + m21: y, + m22: z, + m23: w, + }; + case 3: + return { + ...matrix, + m30: x, + m31: y, + m32: z, + m33: w, + }; + default: + return matrix; + } +} + +export function setColumn(matrix: Mat4, columnIndex: number, x: number, y: number, z: number, w: number): Mat4 { + switch (columnIndex) { + case 0: + return { + ...matrix, + m00: x, + m10: y, + m20: z, + m30: w, + }; + case 1: + return { + ...matrix, + m01: x, + m11: y, + m21: z, + m31: w, + }; + case 2: + return { + ...matrix, + m02: x, + m12: y, + m22: z, + m32: w, + }; + case 3: + return { + ...matrix, + m03: x, + m13: y, + m23: z, + m33: w, + }; + default: + return matrix; + } +} + +export function transpose(matrix: Mat4) { + matrix.m01 = matrix.m10; + matrix.m02 = matrix.m20; + matrix.m03 = matrix.m30; + matrix.m10 = matrix.m01; + matrix.m12 = matrix.m21; + matrix.m13 = matrix.m31; + matrix.m20 = matrix.m02; + matrix.m21 = matrix.m12; + matrix.m23 = matrix.m32; + matrix.m30 = matrix.m03; + matrix.m31 = matrix.m13; + matrix.m32 = matrix.m23; +} + +export function invert(matrix: Mat4) { + if (matrix.m03 == 0 && matrix.m13 == 0 && matrix.m23 == 0 && matrix.m33 == 1) { + invertAffine(matrix); + return; + } +} + +export function invertAffine(matrix: Mat4) { + const r: mat3.Mat3 = { + m00: matrix.m00, + m01: matrix.m01, + m02: matrix.m02, + m10: matrix.m10, + m11: matrix.m11, + m12: matrix.m12, + m20: matrix.m20, + m21: matrix.m21, + m22: matrix.m22, + }; + + mat3.invert(r); + + matrix.m00 = r.m00; + matrix.m01 = r.m01; + matrix.m02 = r.m02; + matrix.m10 = r.m10; + matrix.m11 = r.m11; + matrix.m12 = r.m12; + matrix.m20 = r.m20; + matrix.m21 = r.m21; + matrix.m22 = r.m22; + + const x = matrix.m30; + const y = matrix.m31; + const z = matrix.m32; + + matrix.m30 = -(r.m00 * x + r.m10 * y + r.m20 * z); + matrix.m31 = -(r.m01 * x + r.m11 * y + r.m21 * z); + matrix.m32 = -(r.m02 * x + r.m12 * y + r.m22 * z); +} + +export function invertGeneral(matrix: Mat4) { + const cofactor0 = getCofactor( + matrix.m11, + matrix.m12, + matrix.m13, + matrix.m21, + matrix.m22, + matrix.m23, + matrix.m31, + matrix.m32, + matrix.m33 + ); + const cofactor1 = getCofactor( + matrix.m10, + matrix.m12, + matrix.m13, + matrix.m20, + matrix.m22, + matrix.m23, + matrix.m30, + matrix.m32, + matrix.m33 + ); + const cofactor2 = getCofactor( + matrix.m10, + matrix.m11, + matrix.m13, + matrix.m20, + matrix.m21, + matrix.m23, + matrix.m30, + matrix.m31, + matrix.m33 + ); + const cofactor3 = getCofactor( + matrix.m10, + matrix.m11, + matrix.m12, + matrix.m20, + matrix.m21, + matrix.m22, + matrix.m30, + matrix.m31, + matrix.m32 + ); + + const determinant = + matrix.m00 * cofactor0 - matrix.m01 * cofactor1 + matrix.m02 * cofactor2 - matrix.m03 * cofactor3; + if (Math.abs(determinant) <= EPSILON) { + matrix = identity(); + } + + const cofactor4 = getCofactor( + matrix.m01, + matrix.m02, + matrix.m03, + matrix.m21, + matrix.m22, + matrix.m23, + matrix.m31, + matrix.m32, + matrix.m33 + ); + const cofactor5 = getCofactor( + matrix.m00, + matrix.m02, + matrix.m03, + matrix.m20, + matrix.m22, + matrix.m23, + matrix.m30, + matrix.m32, + matrix.m33 + ); + const cofactor6 = getCofactor( + matrix.m00, + matrix.m01, + matrix.m03, + matrix.m20, + matrix.m21, + matrix.m23, + matrix.m30, + matrix.m31, + matrix.m33 + ); + const cofactor7 = getCofactor( + matrix.m00, + matrix.m01, + matrix.m02, + matrix.m20, + matrix.m21, + matrix.m22, + matrix.m30, + matrix.m31, + matrix.m32 + ); + + const cofactor8 = getCofactor( + matrix.m01, + matrix.m02, + matrix.m03, + matrix.m11, + matrix.m12, + matrix.m13, + matrix.m31, + matrix.m32, + matrix.m33 + ); + const cofactor9 = getCofactor( + matrix.m00, + matrix.m02, + matrix.m03, + matrix.m10, + matrix.m12, + matrix.m13, + matrix.m30, + matrix.m32, + matrix.m33 + ); + const cofactor10 = getCofactor( + matrix.m00, + matrix.m01, + matrix.m03, + matrix.m10, + matrix.m11, + matrix.m13, + matrix.m30, + matrix.m31, + matrix.m33 + ); + const cofactor11 = getCofactor( + matrix.m00, + matrix.m01, + matrix.m02, + matrix.m10, + matrix.m11, + matrix.m12, + matrix.m30, + matrix.m31, + matrix.m32 + ); + + const cofactor12 = getCofactor( + matrix.m01, + matrix.m02, + matrix.m03, + matrix.m11, + matrix.m12, + matrix.m13, + matrix.m21, + matrix.m22, + matrix.m23 + ); + const cofactor13 = getCofactor( + matrix.m00, + matrix.m02, + matrix.m03, + matrix.m10, + matrix.m12, + matrix.m13, + matrix.m20, + matrix.m22, + matrix.m23 + ); + const cofactor14 = getCofactor( + matrix.m00, + matrix.m01, + matrix.m03, + matrix.m10, + matrix.m11, + matrix.m13, + matrix.m20, + matrix.m21, + matrix.m23 + ); + const cofactor15 = getCofactor( + matrix.m00, + matrix.m01, + matrix.m02, + matrix.m10, + matrix.m11, + matrix.m12, + matrix.m20, + matrix.m21, + matrix.m22 + ); + + const invDeterminant = 1 / determinant; + + matrix.m00 = cofactor0 * invDeterminant; + matrix.m01 = -cofactor4 * invDeterminant; + matrix.m02 = cofactor8 * invDeterminant; + matrix.m03 = -cofactor12 * invDeterminant; + + matrix.m10 = -cofactor1 * invDeterminant; + matrix.m11 = cofactor5 * invDeterminant; + matrix.m12 = -cofactor9 * invDeterminant; + matrix.m13 = cofactor13 * invDeterminant; + + matrix.m20 = cofactor2 * invDeterminant; + matrix.m21 = -cofactor6 * invDeterminant; + matrix.m22 = cofactor10 * invDeterminant; + matrix.m23 = -cofactor14 * invDeterminant; + + matrix.m30 = -cofactor3 * invDeterminant; + matrix.m31 = cofactor7 * invDeterminant; + matrix.m32 = -cofactor11 * invDeterminant; + matrix.m33 = cofactor15 * invDeterminant; +} + +function getCofactor( + m0: number, + m1: number, + m2: number, + m3: number, + m4: number, + m5: number, + m6: number, + m7: number, + m8: number +): number { + return m0 * (m4 * m8 - m5 * m7) - m1 * (m3 * m8 - m5 * m6) + m2 * (m3 * m7 - m4 * m6); +} + +export function lookAt(matrix: Mat4, target: vec3.Vec3) { + const position = { x: matrix.m30, y: matrix.m31, z: matrix.m32 }; + const forward = vec3.normalize(vec3.subtract(target, position)); + let up: vec3.Vec3 = { x: 0, y: 0, z: 1 }; + let left: vec3.Vec3 = { x: 0, y: 1, z: 0 }; + + if (Math.abs(forward.x) < EPSILON && Math.abs(forward.z) < EPSILON) { + if (forward.y > 0) { + up = { x: 0, y: 0, z: -1 }; + } + } else { + up = { x: 0, y: 1, z: 0 }; + } + + left = vec3.normalize(vec3.cross(up, forward)); + up = vec3.cross(forward, left); + + matrix.m00 = left.x; + matrix.m01 = left.y; + matrix.m02 = left.z; + + matrix.m10 = up.x; + matrix.m11 = up.y; + matrix.m12 = up.z; + + matrix.m20 = forward.x; + matrix.m21 = forward.y; + matrix.m22 = forward.z; +} + +export function translate(matrix: Mat4, v: vec3.Vec3) { + matrix.m00 += matrix.m03 * v.x; + matrix.m01 += matrix.m03 * v.y; + matrix.m02 += matrix.m03 * v.z; + + matrix.m10 += matrix.m13 * v.x; + matrix.m11 += matrix.m13 * v.y; + matrix.m12 += matrix.m13 * v.z; + + matrix.m20 += matrix.m23 * v.x; + matrix.m21 += matrix.m23 * v.y; + matrix.m22 += matrix.m23 * v.z; + + matrix.m30 += matrix.m33 * v.x; + matrix.m31 += matrix.m33 * v.y; + matrix.m32 += matrix.m33 * v.z; +} + +export function multiply(matrix: Mat4, vector: vec3.Vec3): vec3.Vec3 { + return { + x: matrix.m00 * vector.x + matrix.m10 * vector.y + matrix.m20 * vector.z + matrix.m30, + y: matrix.m01 * vector.x + matrix.m11 * vector.y + matrix.m21 * vector.z + matrix.m31, + z: matrix.m02 * vector.x + matrix.m12 * vector.y + matrix.m22 * vector.z + matrix.m32, + }; +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Pipe.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Pipe.ts new file mode 100644 index 000000000..6d5474e91 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Pipe.ts @@ -0,0 +1,105 @@ +import * as vec3 from "@lib/utils/vec3"; + +import { cloneDeep } from "lodash"; + +import * as mat4 from "./Mat4"; +import * as plane from "./Plane"; + +export class Pipe { + private _path: vec3.Vec3[] = []; + private _contour: vec3.Vec3[] = []; + private _contours: vec3.Vec3[][] = []; + private _normals: vec3.Vec3[][] = []; + + constructor(pathPoints: vec3.Vec3[], contourPoints: vec3.Vec3[]) { + this._path = pathPoints; + this._contour = cloneDeep(contourPoints); + + this.generateContours(); + } + + getPath(): vec3.Vec3[] { + return this._path; + } + + getContours(): vec3.Vec3[][] { + return this._contours; + } + + getNormals(): vec3.Vec3[][] { + return this._normals; + } + + getContourCount(): number { + return this._contours.length; + } + + private generateContours() { + this._contours = []; + this._normals = []; + + if (this._path.length < 1) { + return; + } + + this.transformFirstContour(); + + this._contours.push(this._contour); + this._normals.push(this.computeContourNormal(0)); + + for (let i = 1; i < this._path.length; ++i) { + this._contours.push(this.projectContour(i - 1, i)); + this._normals.push(this.computeContourNormal(i)); + } + } + + private projectContour(fromIndex: number, toIndex: number): vec3.Vec3[] { + const dir1 = vec3.subtract(this._path[toIndex], this._path[fromIndex]); + + let dir2 = dir1; + if (toIndex < this._path.length - 1) { + dir2 = vec3.subtract(this._path[toIndex + 1], this._path[toIndex]); + } + + const normal = vec3.add(dir1, dir2); + const pl = plane.fromNormalAndPoint(normal, this._path[toIndex]); + + const fromContour = this._contours[fromIndex]; + let toContour: vec3.Vec3[] = []; + + for (let i = 0; i < fromContour.length; ++i) { + toContour.push(plane.intersectLine(pl, { point: fromContour[i], direction: dir1 })!); + } + + return toContour; + } + + private transformFirstContour() { + const matrix: mat4.Mat4 = mat4.identity(); + + if (this._path.length > 0) { + if (this._path.length > 1) { + mat4.lookAt(matrix, vec3.subtract(this._path[1], this._path[0])); + } + + mat4.translate(matrix, this._path[0]); + + for (let i = 0; i < this._contour.length; ++i) { + this._contour[i] = mat4.multiply(matrix, this._contour[i]); + } + } + } + + private computeContourNormal(pathIndex: number): vec3.Vec3[] { + const contour = this._contours[pathIndex]; + const center = this._path[pathIndex]; + + const contourNormal: vec3.Vec3[] = []; + for (let i = 0; i < contour.length; ++i) { + const normal = vec3.normalize(vec3.subtract(contour[i], center)); + contourNormal.push(normal); + } + + return contourNormal; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/PipeLayer.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/PipeLayer.ts new file mode 100644 index 000000000..d7ed80a81 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/PipeLayer.ts @@ -0,0 +1,288 @@ +import { + GetPickingInfoParams, + Layer, + LayerContext, + LayerProps, + Material, + PickingInfo, + UpdateParameters, + picking, + project32, +} from "@deck.gl/core"; +import { Vec3 } from "@lib/utils/vec3"; +import * as vec3 from "@lib/utils/vec3"; +import { Geometry, Model } from "@luma.gl/engine"; +import { phongLighting } from "@luma.gl/shadertools"; + +import { isEqual } from "lodash"; + +import { Pipe } from "./Pipe"; +import fragmentShader from "./shaders/fragmentShader.glsl?raw"; +import { PipeProps, pipeUniforms } from "./shaders/uniforms"; +import vertexShader from "./shaders/vertexShader.glsl?raw"; + +export type PipeLayerProps = { + id: string; + data: { + id: string; + centerLinePath: Vec3[]; + }[]; + color: [number, number, number, number]; + hoverColor: [number, number, number, number]; + selectedColor: [number, number, number, number]; + hoveredPipeIndex: number | null; + selectedPipeIndex: number | null; + material?: Material; +} & LayerProps; + +export class PipesLayer extends Layer { + static layerName: string = "PipeLayer"; + + // @ts-expect-error - This is how deck.gl expects the state to be defined + state!: { + models: Model[]; + hoveredPipeIndex: number | null; + }; + + initializeState(context: LayerContext): void { + this.setState({ + models: this.makeModels(context), + hoveredPipeIndex: null, + }); + } + + getPickingInfo(params: GetPickingInfoParams): PickingInfo { + const info = super.getPickingInfo(params); + + if (!info.color) { + this.setState({ hoveredPipeIndex: null }); + return info; + } + + const r = info.color[0]; + const g = info.color[1]; + const b = info.color[2]; + + const pipeIndex = r * 256 * 256 + g * 256 + b; + + this.setState({ hoveredPipeIndex: pipeIndex }); + + return { + ...info, + picked: true, + index: pipeIndex, + object: this.props.data[pipeIndex], + }; + } + + draw(): void { + const { models, hoveredPipeIndex } = this.state; + for (const [idx, model] of models.entries()) { + model.shaderInputs.setProps({ + pipe: { + isHovered: hoveredPipeIndex === idx, + }, + }); + model.draw(this.context.renderPass); + } + } + + shouldUpdateState({ changeFlags }: UpdateParameters): boolean { + return changeFlags.dataChanged !== false; + } + + updateState(params: UpdateParameters>): void { + super.updateState(params); + + if (!params.changeFlags.dataChanged) { + return; + } + + if (!isEqual(params.props.data, params.oldProps.data)) { + this.setState({ + models: this.makeModels(params.context), + }); + } + } + + makeModels(context: LayerContext): Model[] { + const pipes = this.makePipes(); + const meshes = this.makeMeshes(pipes); + + const models: Model[] = []; + for (const [idx, mesh] of meshes.entries()) { + const pipeProps: PipeProps = { + pipeIndex: idx, + isHovered: false, + }; + const model = new Model(context.device, { + id: `${this.id}-mesh-${idx}`, + geometry: mesh, + modules: [project32, phongLighting, picking, pipeUniforms], + vs: vertexShader, + fs: fragmentShader, + }); + model.shaderInputs.setProps({ pipe: pipeProps }); + + models.push(model); + } + + return models; + } + + private makePipes(): Pipe[] { + const { data } = this.props; + const circle = this.makeCircle(10, 16); + + const pipes: Pipe[] = []; + for (const pipeData of data) { + pipes.push(new Pipe(pipeData.centerLinePath, circle)); + } + + return pipes; + } + + private makeCircle(radius: number, segments: number): Vec3[] { + const points: Vec3[] = []; + if (segments < 2) { + return points; + } + + const pi2 = Math.PI * 2; + for (let i = 0; i < segments; i++) { + const angle = (i / segments) * pi2; + points.push({ x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, z: 0 }); + } + + return points; + } + + private makeMeshes(pipes: Pipe[]): Geometry[] { + const { data } = this.props; + + const geometries: Geometry[] = []; + for (const [idx, pipe] of pipes.entries()) { + const numContours = pipe.getContourCount(); + const numVerticesPerContour = pipe.getContours()[0].length; + + const vertices = new Float32Array((numContours + 2) * numVerticesPerContour * 3 + 2 * 3); + const indices = new Uint32Array( + (numContours - 1) * numVerticesPerContour * 6 + numVerticesPerContour * 2 * 3, + ); + const normals = new Float32Array((numContours + 2) * numVerticesPerContour * 3 + 2 * 3); + + let verticesIndex: number = 0; + let indicesIndex: number = 0; + let normalsIndex: number = 0; + + const startVertex = pipe.getPath()[0]; + vertices[verticesIndex++] = startVertex.x; + vertices[verticesIndex++] = startVertex.y; + vertices[verticesIndex++] = startVertex.z; + + const normal = vec3.normalize(vec3.cross(pipe.getNormals()[0][1], pipe.getNormals()[0][0])); + normals[normalsIndex++] = normal.x; + normals[normalsIndex++] = normal.y; + normals[normalsIndex++] = normal.z; + + for (let j = 0; j < numVerticesPerContour; j++) { + const vertex = pipe.getContours()[0][j]; + vertices[verticesIndex++] = vertex.x; + vertices[verticesIndex++] = vertex.y; + vertices[verticesIndex++] = vertex.z; + + normals[normalsIndex++] = normal.x; + normals[normalsIndex++] = normal.y; + normals[normalsIndex++] = normal.z; + + indices[indicesIndex++] = 0; + indices[indicesIndex++] = j + 1; + indices[indicesIndex++] = j + 2 > numVerticesPerContour ? 1 : j + 2; + } + + for (let i = 0; i < numContours; i++) { + const contour = pipe.getContours()[i]; + for (let j = 0; j < numVerticesPerContour; j++) { + const vertex = contour[j]; + vertices[verticesIndex++] = vertex.x; + vertices[verticesIndex++] = vertex.y; + vertices[verticesIndex++] = vertex.z; + + const normal = pipe.getNormals()[i][j]; + normals[normalsIndex++] = normal.x; + normals[normalsIndex++] = normal.y; + normals[normalsIndex++] = normal.z; + + if (i > 0) { + const index = 1 + (i + 1) * numVerticesPerContour + j; + if (j === numVerticesPerContour - 1) { + indices[indicesIndex++] = index - numVerticesPerContour; + indices[indicesIndex++] = index; + indices[indicesIndex++] = index - 2 * numVerticesPerContour + 1; + indices[indicesIndex++] = index - 2 * numVerticesPerContour + 1; + indices[indicesIndex++] = index; + indices[indicesIndex++] = (i + 1) * numVerticesPerContour + 1; + } else { + indices[indicesIndex++] = index - numVerticesPerContour; + indices[indicesIndex++] = index; + indices[indicesIndex++] = index - numVerticesPerContour + 1; + indices[indicesIndex++] = index - numVerticesPerContour + 1; + indices[indicesIndex++] = index; + indices[indicesIndex++] = index + 1; + } + } + } + } + + const endNormal = vec3.normalize( + vec3.cross( + pipe.getNormals()[pipe.getNormals().length - 1][0], + pipe.getNormals()[pipe.getNormals().length - 1][1], + ), + ); + for (let j = 0; j < numVerticesPerContour; j++) { + const vertex = pipe.getContours()[numContours - 1][j]; + vertices[verticesIndex++] = vertex.x; + vertices[verticesIndex++] = vertex.y; + vertices[verticesIndex++] = vertex.z; + + normals[normalsIndex++] = endNormal.x; + normals[normalsIndex++] = endNormal.y; + normals[normalsIndex++] = endNormal.z; + } + + const endVertex = pipe.getPath()[pipe.getPath().length - 1]; + vertices[verticesIndex++] = endVertex.x; + vertices[verticesIndex++] = endVertex.y; + vertices[verticesIndex++] = endVertex.z; + + normals[normalsIndex++] = endNormal.x; + normals[normalsIndex++] = endNormal.y; + normals[normalsIndex++] = endNormal.z; + + for (let j = 0; j < numVerticesPerContour; j++) { + const index = numContours * numVerticesPerContour + 1; + indices[indicesIndex++] = verticesIndex / 3 - 1; + indices[indicesIndex++] = index + j; + indices[indicesIndex++] = j + 1 >= numVerticesPerContour ? index : index + j + 1; + } + + geometries.push( + new Geometry({ + topology: "triangle-list", + attributes: { + positions: vertices, + normals: { + size: 3, + value: normals, + }, + }, + indices: indices, + id: data[idx].id, + }), + ); + } + + return geometries; + } +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Plane.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Plane.ts new file mode 100644 index 000000000..5a209b27c --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/Plane.ts @@ -0,0 +1,64 @@ +import * as vec3 from "@lib/utils/vec3"; + +import { Line } from "./Line"; + +export type Plane = { + normal: vec3.Vec3; + point: vec3.Vec3; + d: number; // Coefficient of constant term: d = -(a*x0 + b*y0 + c*z0) + normalLength: number; + distanceToOrigin: number; +}; + +export function fromNormalAndPoint(normal: vec3.Vec3, point: vec3.Vec3): Plane { + const d = -vec3.dot(normal, point); + const normalLength = vec3.length(normal); + const distanceToOrigin = -d / normalLength; + return { normal, point, d, normalLength, distanceToOrigin }; +} + +export function getDistance(plane: Plane, point: vec3.Vec3): number { + const dot = vec3.dot(plane.normal, point); + return (dot + plane.d) / plane.normalLength; +} + +export function normalize(plane: Plane): Plane { + const inversedLength = 1.0 / plane.normalLength; + const normal = vec3.normalize(plane.normal); + const d = plane.d * inversedLength; + return { + normal, + point: plane.point, + d, + normalLength: 1.0, + distanceToOrigin: plane.distanceToOrigin * inversedLength, + }; +} + +export function intersectLine(plane: Plane, line: Line): vec3.Vec3 | null { + const dot1 = vec3.dot(plane.normal, line.point); + const dot2 = vec3.dot(plane.normal, line.direction); + + if (dot2 === 0) { + return null; + } + + const t = -(dot1 + plane.d) / dot2; + + return vec3.add(line.point, vec3.scale(line.direction, t)); +} + +export function intersectPlane(plane1: Plane, plane2: Plane): Line | null { + const v = vec3.cross(plane1.normal, plane2.normal); + + if (v.x === 0 && v.y === 0 && v.z === 0) { + return null; + } + + const dot = vec3.dot(v, v); + const n1 = vec3.scale(plane2.normal, plane1.d); + const n2 = vec3.scale(plane1.normal, -plane2.d); + const p = vec3.scale(vec3.cross(vec3.add(n1, n2), v), 1 / dot); + + return { point: p, direction: v }; +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/fragmentShader.glsl b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/fragmentShader.glsl new file mode 100644 index 000000000..52dae0fce --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/fragmentShader.glsl @@ -0,0 +1,58 @@ +#version 300 es +#define SHADER_NAME wells-layer-fragment-shader + +precision highp float; + +in vec3 cameraPosition; +in vec4 position_commonspace; +flat in vec3 normal; +in float pointingTowardsCamera; + +out vec4 fragColor; + +vec4 encodeVertexAndPipeIndexToRGB(float pipeIndex) { + int index = int(pipeIndex); + float r = 0.0f; + float g = 0.0f; + float b = 0.0f; + + if(index >= (256 * 256) - 1) { + r = floor(float(index) / (256.0f * 256.0f)); + index -= int(r * (256.0f * 256.0f)); + } + + if(index >= 256 - 1) { + g = floor(float(index) / 256.0f); + index -= int(g * 256.0f); + } + + b = float(index); + + return vec4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f); +} + +void main(void) { + if(picking.isActive > 0.5f && !(picking.isAttribute > 0.5f)) { + float pipeIndex = pipe.pipeIndex; + fragColor = encodeVertexAndPipeIndexToRGB(pipeIndex); + return; + } + + vec4 color = vec4(0.85f, 0.85f, 0.85f, 1.0f); + + if(pipe.isHovered) { + color = vec4(0.42f, 0.47f, 0.88f, 1.0f); + /* + This could be a nice effect, but it's not used in the current implementation + if(pointingTowardsCamera > 0.0f) { + color.a = 0.2f; + } + */ + } + + geometry.uv = vec2(0.f); + + DECKGL_FILTER_COLOR(color, geometry); + vec3 lightColor = lighting_getLightColor(color.rgb, cameraPosition, position_commonspace.xyz, normal); + fragColor = vec4(lightColor, color.a); +} \ No newline at end of file diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/uniforms.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/uniforms.ts new file mode 100644 index 000000000..94333492a --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/uniforms.ts @@ -0,0 +1,23 @@ +import type { ShaderModule } from "@luma.gl/shadertools"; + +const uniformBlock = `\ +uniform pipeUniforms { + float pipeIndex; + bool isHovered; +} pipe; + `; + +export type PipeProps = { + pipeIndex?: number; + isHovered?: boolean; +}; + +export const pipeUniforms = { + name: "pipe", + vs: uniformBlock, + fs: uniformBlock, + uniformTypes: { + pipeIndex: "f32", + isHovered: "f32", + }, +} as const satisfies ShaderModule; diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/vertexShader.glsl b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/vertexShader.glsl new file mode 100644 index 000000000..8cc7cb0c1 --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/shaders/vertexShader.glsl @@ -0,0 +1,35 @@ +#version 300 es +#define SHADER_NAME wells-layer-vertex-shader + +precision highp float; + +in vec3 positions; +in vec3 normals; + +flat out vec3 normal; +out vec3 cameraPosition; +out vec4 position_commonspace; +out float pointingTowardsCamera; + +const vec3 pickingColor = vec3(1.0, 1.0, 0.0); + +void main(void) { + cameraPosition = project_uCameraPosition; + + normal = normals; + + vec3 cameraPositionToPosition = normalize(cameraPosition - positions); + pointingTowardsCamera = dot(cameraPositionToPosition, normals); + + position_commonspace = vec4(project_position(positions), 0.0f); + + geometry.position = position_commonspace; + geometry.pickingColor = pickingColor; + + gl_Position = project_common_position_to_clipspace(position_commonspace); + + DECKGL_FILTER_GL_POSITION(gl_Position, geometry); + + vec4 color = vec4(0.0f); + DECKGL_FILTER_COLOR(color, geometry); +} \ No newline at end of file diff --git a/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/wellTrajectoryUtils.ts b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/wellTrajectoryUtils.ts new file mode 100644 index 000000000..dbe581e6d --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/WellsLayer/_private/wellTrajectoryUtils.ts @@ -0,0 +1,110 @@ +import * as vec3 from "@lib/utils/vec3"; + +function squared_distance(a: vec3.Vec3, b: vec3.Vec3): number { + const dx = a.x - b.x; + const dy = a.y - b.y; + const dz = a.z - b.z; + return dx * dx + dy * dy + dz * dz; +} + +function distPointToSegmentSquared(segment: { v: vec3.Vec3; w: vec3.Vec3 }, point: vec3.Vec3): number { + const l2 = squared_distance(segment.v, segment.w); + if (l2 === 0) return squared_distance(point, segment.v); + let t = + ((point.x - segment.v.x) * (segment.w.x - segment.v.x) + + (point.y - segment.v.y) * (segment.w.y - segment.v.y) + + (point.z - segment.v.z) * (segment.w.z - segment.v.z)) / + l2; + t = Math.max(0, Math.min(1, t)); + return squared_distance(point, { + x: segment.v.x + t * (segment.w.x - segment.v.x), + y: segment.v.y + t * (segment.w.y - segment.v.y), + z: segment.v.z + t * (segment.w.z - segment.v.z), + }); +} + +function getSegmentIndex(coord: vec3.Vec3, path: vec3.Vec3[]): number { + let minD = Number.MAX_VALUE; + let segmentIndex = 0; + for (let i = 0; i < path.length - 1; i++) { + const d = distPointToSegmentSquared({ v: path[i], w: path[i + 1] }, coord); + if (d > minD) continue; + + segmentIndex = i; + minD = d; + } + return segmentIndex; +} + +function interpolateDataOnTrajectory(coord: vec3.Vec3, data: number[], trajectory: vec3.Vec3[]): number { + // if number of data points in less than 1 or + // length of data and trajectory are different we cannot interpolate. + if (data.length <= 1 || data.length != trajectory.length) return -1; + + // Identify closest well path leg to coord. + const segmentIndex = getSegmentIndex(coord, trajectory); + + const index0 = segmentIndex; + const index1 = index0 + 1; + + // Get the nearest data. + const data0 = data[index0]; + const data1 = data[index1]; + + // Get the nearest survey points. + const survey0 = trajectory[index0]; + const survey1 = trajectory[index1]; + + const dv = vec3.distance(survey0, survey1) as number; + if (dv === 0) { + return -1; + } + + // Calculate the scalar projection onto segment. + const v0 = vec3.subtract(coord, survey0); + const v1 = vec3.subtract(survey1, survey0); + + // scalar_projection in interval [0,1] + const scalar_projection: number = vec3.dot(v0, v1) / (dv * dv); + + // Interpolate data. + return data0 * (1.0 - scalar_projection) + data1 * scalar_projection; +} + +export function getMd(coord: vec3.Vec3, mdArray: number[], trajectory: vec3.Vec3[]): number | null { + return interpolateDataOnTrajectory(coord, mdArray, trajectory); +} + +export function getCoordinateForMd(md: number, mdArray: number[], trajectory: vec3.Vec3[]): vec3.Vec3 | null { + const numPoints = mdArray.length; + if (numPoints < 2) { + return null; + } + + let segmentIndex = 0; + for (let i = 0; i < numPoints - 1; i++) { + if (mdArray[i] <= md && md <= mdArray[i + 1]) { + segmentIndex = i; + break; + } + } + + const md0 = mdArray[segmentIndex]; + const md1 = mdArray[segmentIndex + 1]; + + const survey0 = trajectory[segmentIndex]; + const survey1 = trajectory[segmentIndex + 1]; + + const dv = vec3.distance(survey0, survey1) as number; + if (dv === 0) { + return null; + } + + const scalar_projection = (md - md0) / (md1 - md0); + + return { + x: survey0.x + scalar_projection * (survey1.x - survey0.x), + y: survey0.y + scalar_projection * (survey1.y - survey0.y), + z: survey0.z + scalar_projection * (survey1.z - survey0.z), + }; +} diff --git a/frontend/src/modules/_shared/customDeckGlLayers/types.ts b/frontend/src/modules/_shared/customDeckGlLayers/types.ts new file mode 100644 index 000000000..0ad973f9c --- /dev/null +++ b/frontend/src/modules/_shared/customDeckGlLayers/types.ts @@ -0,0 +1,10 @@ +import { MeshAttribute, MeshAttributes } from "@loaders.gl/schema"; +import { Geometry } from "@luma.gl/engine"; + +export type Mesh = + | Geometry + | { + attributes: MeshAttributes; + indices?: MeshAttribute; + } + | MeshAttributes; diff --git a/frontend/src/modules/registerAllModules.ts b/frontend/src/modules/registerAllModules.ts index b5eaebca3..fcea0cad1 100644 --- a/frontend/src/modules/registerAllModules.ts +++ b/frontend/src/modules/registerAllModules.ts @@ -2,6 +2,7 @@ import { isDevMode } from "@lib/utils/devMode"; import "./2DViewer/registerModule"; import "./3DViewer/registerModule"; +import "./3DViewerNew/registerModule"; import "./DistributionPlot/registerModule"; import "./FlowNetwork/registerModule"; import "./InplaceVolumetricsPlot/registerModule"; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 371cf9594..459b2c249 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "types": ["vite/client"] }, "include": ["src", "tests"], "exclude": ["tests/ct/playwright/", "**/_playwright.config.ts"], diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 7cc5b0022..7c7539e17 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,6 +6,7 @@ import jotaiDebugLabel from "jotai/babel/plugin-debug-label"; import jotaiReactRefresh from "jotai/babel/plugin-react-refresh"; import { defineConfig } from "vite"; import vitePluginChecker from "vite-plugin-checker"; +import glsl from "vite-plugin-glsl"; import { nodePolyfills } from "vite-plugin-node-polyfills"; import aliases from "./aliases.json"; @@ -31,12 +32,23 @@ export default defineConfig(({ mode }) => { return { plugins: [ tailwindPlugin(), + { + name: "isolation", + configureServer(server) { + server.middlewares.use((_req, res, next) => { + res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + next(); + }); + }, + }, react({ babel: { plugins: [jotaiDebugLabel, jotaiReactRefresh], }, }), vitePluginChecker({ typescript: true }), + glsl(), // Polyfill is only needed to solve an import issue in a nested dep in the subsurface-viewer component // See webviz-subsurface-components issue #2540 for details. nodePolyfills({ diff --git a/nginx.conf b/nginx.conf index 63e284a14..b0fb8c137 100644 --- a/nginx.conf +++ b/nginx.conf @@ -64,6 +64,8 @@ http { add_header X-Frame-Options "DENY"; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "no-referrer"; + add_header "Cross-Origin-Embedder-Policy" "require-corp"; + add_header "Cross-Origin-Opener-Policy" "same-origin; "; } } }