diff --git a/.prettierignore b/.prettierignore index 8b972f3dd..bfeb36a58 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,8 @@ schemas/** confd/** helm/** public/** +build +node_modules # generated files src/discrete-layer/models/**/* diff --git a/src/common/actions/context.actions.ts b/src/common/actions/context.actions.ts index 777887de5..a134028d1 100644 --- a/src/common/actions/context.actions.ts +++ b/src/common/actions/context.actions.ts @@ -71,11 +71,10 @@ export const isActionGroup = ( const DEFAULT_MINIMUM_ITEMS_IN_MENU = 2; const defaultContextActionProps: IAction = { - action: '', - class: '', + action: '' as ContextActions, + symbol: {}, frequent: false, - icon: '', - titleTranslationId: '', + title: { translationId: '' }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }; @@ -107,6 +106,7 @@ const CONTEXT_ACTIONS_CONFIG: IContextActions[] = [ ...defaultContextActionProps, templateId: ContextActionsTemplates.WFS_QUERY_FEATURES, action: ContextActions.QUERY_WFS_FEATURE, + title: { translationId: '' }, }, ], }, @@ -121,7 +121,7 @@ const CONTEXT_ACTIONS_CONFIG: IContextActions[] = [ actions: [ { ...defaultContextActionProps, - titleTranslationId: 'map-context-menu.query-dem-height.title', + title: { translationId: 'map-context-menu.query-dem-height.title' }, action: ContextActions.QUERY_DEM_HEIGHT, }, ], @@ -147,28 +147,28 @@ const CONTEXT_ACTIONS_CONFIG: IContextActions[] = [ actions: [ { ...defaultContextActionProps, - titleTranslationId: 'map-context-menu.polygon-parts.title', + title: { translationId: 'map-context-menu.polygon-parts.title' }, action: ContextActions.QUERY_POLYGON_PARTS, separator: 'AFTER', }, { ...defaultContextActionProps, - titleTranslationId: 'map-context-menu.layer-up.title', + title: { translationId: 'map-context-menu.layer-up.title' }, action: ContextActions.MOVE_LAYER_UP, }, { ...defaultContextActionProps, - titleTranslationId: 'map-context-menu.layer-down.title', + title: { translationId: 'map-context-menu.layer-down.title' }, action: ContextActions.MOVE_LAYER_DOWN, }, { ...defaultContextActionProps, - titleTranslationId: 'map-context-menu.layer-to-top.title', + title: { translationId: 'map-context-menu.layer-to-top.title' }, action: ContextActions.MOVE_LAYER_TO_TOP, }, { ...defaultContextActionProps, - titleTranslationId: 'map-context-menu.layer-to-bottom.title', + title: { translationId: 'map-context-menu.layer-to-bottom.title' }, action: ContextActions.MOVE_LAYER_TO_BOTTOM, }, ], diff --git a/src/common/actions/entity.actions.ts b/src/common/actions/entity.actions.ts index 835b36f58..3c57cfb4c 100644 --- a/src/common/actions/entity.actions.ts +++ b/src/common/actions/entity.actions.ts @@ -1,5 +1,8 @@ import { TabViews } from '../../discrete-layer/views/tab-views'; +import { UserAction } from '../../discrete-layer/models/userStore'; import { RecordStatus } from '../../discrete-layer/models'; +import { ExportActions } from '../../discrete-layer/components/export-layer/hooks/useDomainExportActionsConfig'; +import { ContextActions } from './context.actions'; interface DependentFieldWithValue { field: string; @@ -8,13 +11,38 @@ interface DependentFieldWithValue { } export type DependentField = string | DependentFieldWithValue; +export enum GeneralAction { + flyTo = 'flyTo', + viewer = 'viewer', + export = 'export', +} + +export enum JobAction { + restore = 'restore', + abort = 'abort', + retry = 'retry', + download_details = 'download_details', +} + +export enum CRUDAction { + delete = 'delete', + update = 'update', + edit = 'edit', +} + export interface IAction { - action: string; + action: ContextActions | UserAction | ExportActions | GeneralAction | JobAction | CRUDAction; frequent: boolean; - icon: string; - class: string; - titleTranslationId: string; + symbol: { + icon?: string; + class?: string; + }; + title: { + translationId: string; + class?: string; + }; views: TabViews[]; + disabled?: boolean; dependentField?: DependentField; } @@ -49,25 +77,50 @@ export const isDependentFieldWithValue = ( ); }; +export const getActionsWithDisable = ( + entityActions: IActionGroup[], + disabledActions: Array<[string, boolean]> +): IActionGroup[] => { + const actions = structuredClone(entityActions); + + for (const actionGroup of actions) { + for (const [actionName, isDisabled] of disabledActions) { + const action = actionGroup.group.find((action) => action.action === actionName); + + if (action) { + action.disabled = isDisabled; + } + } + } + + return actions; +}; + const GENERAL_ACTIONS_GROUP: IActionGroup = { id: 0, titleTranslationId: 'layerCatalogToMap', type: EntityActionsTypes.GENERAL_ACTIONS, group: [ { - action: 'flyTo', + action: GeneralAction.flyTo, frequent: true, - icon: '', - class: 'mc-icon-Fly-to', - titleTranslationId: 'action.flyTo.tooltip', + symbol: { + class: 'mc-icon-Fly-to', + }, + title: { + translationId: 'action.flyTo.tooltip', + }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, { - action: 'viewer', + action: GeneralAction.viewer, frequent: false, - icon: '', - class: 'mc-icon-Earth', - titleTranslationId: 'action.viewer.tooltip', + symbol: { + class: 'mc-icon-Earth', + }, + title: { + translationId: 'action.viewer.tooltip', + }, dependentField: { field: 'productStatus', expectedValue: RecordStatus.BEING_DELETED, @@ -76,11 +129,14 @@ const GENERAL_ACTIONS_GROUP: IActionGroup = { views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, { - action: 'export', + action: GeneralAction.export, frequent: false, - icon: '', - class: 'mc-icon-Export', - titleTranslationId: 'action.export.tooltip', + symbol: { + class: 'mc-icon-Export', + }, + title: { + translationId: 'action.export.tooltip', + }, dependentField: { field: 'layerURLMissing', expectedValue: false }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, @@ -98,20 +154,26 @@ const ACTIONS_CONFIG: IEntityActions[] = [ type: EntityActionsTypes.CRUD, group: [ { - action: 'edit', + action: CRUDAction.edit, frequent: true, - icon: '', - class: 'mc-icon-Edit1', - titleTranslationId: 'action.edit.tooltip', + symbol: { class: 'mc-icon-Edit1' }, + title: { translationId: 'action.edit.tooltip' }, + dependentField: { field: 'layerURLMissing', expectedValue: false }, + views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], + }, + { + action: CRUDAction.update, + frequent: false, + symbol: { class: 'mc-icon-Update' }, + title: { translationId: 'action.update.tooltip' }, dependentField: { field: 'layerURLMissing', expectedValue: false }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, { - action: 'update', + action: CRUDAction.delete, frequent: false, - icon: '', - class: 'mc-icon-Update', - titleTranslationId: 'action.update.tooltip', + symbol: { class: 'mc-icon-Delete error' }, + title: { translationId: 'action.delete.tooltip', class: 'error' }, dependentField: { field: 'layerURLMissing', expectedValue: false }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, @@ -129,11 +191,10 @@ const ACTIONS_CONFIG: IEntityActions[] = [ type: EntityActionsTypes.CRUD, group: [ { - action: 'edit', + action: CRUDAction.edit, frequent: true, - icon: '', - class: 'mc-icon-Edit1', - titleTranslationId: 'action.edit.tooltip', + symbol: { class: 'mc-icon-Edit1' }, + title: { translationId: 'action.edit.tooltip' }, dependentField: { field: 'productStatus', expectedValue: RecordStatus.BEING_DELETED, @@ -142,11 +203,10 @@ const ACTIONS_CONFIG: IEntityActions[] = [ views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, { - action: 'delete', + action: CRUDAction.delete, frequent: false, - icon: '', - class: 'mc-icon-Delete', - titleTranslationId: 'action.delete.tooltip', + symbol: { class: 'mc-icon-Delete error' }, + title: { translationId: 'action.delete.tooltip', class: 'error' }, dependentField: { field: 'productStatus', expectedValue: RecordStatus.BEING_DELETED, @@ -168,11 +228,10 @@ const ACTIONS_CONFIG: IEntityActions[] = [ type: EntityActionsTypes.CRUD, group: [ { - action: 'edit', + action: CRUDAction.edit, frequent: true, - icon: '', - class: 'mc-icon-Edit1', - titleTranslationId: 'action.edit.tooltip', + symbol: { class: 'mc-icon-Edit1' }, + title: { translationId: 'action.edit.tooltip' }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, ], @@ -189,11 +248,10 @@ const ACTIONS_CONFIG: IEntityActions[] = [ type: EntityActionsTypes.CRUD, group: [ { - action: 'edit', + action: CRUDAction.edit, frequent: true, - icon: '', - class: 'mc-icon-Edit1', - titleTranslationId: 'action.edit.tooltip', + symbol: { class: 'mc-icon-Edit1' }, + title: { translationId: 'action.edit.tooltip' }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, ], @@ -210,21 +268,12 @@ const ACTIONS_CONFIG: IEntityActions[] = [ type: EntityActionsTypes.CRUD, group: [ { - action: 'edit', + action: CRUDAction.edit, frequent: true, - icon: '', - class: 'mc-icon-Edit1', - titleTranslationId: 'action.edit.tooltip', + symbol: { class: 'mc-icon-Edit1' }, + title: { translationId: 'action.edit.tooltip' }, views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS], }, - // { - // action: 'delete', - // frequent: false, - // icon: '', - // class: 'mc-icon-Delete', - // titleTranslationId: 'action.delete.tooltip', - // views: [TabViews.CATALOG, TabViews.SEARCH_RESULTS] - // }, ], }, ], @@ -238,11 +287,10 @@ const ACTIONS_CONFIG: IEntityActions[] = [ type: EntityActionsTypes.JOB_ACTIONS, group: [ { - action: 'download_details', + action: JobAction.download_details, frequent: false, - icon: '', - class: 'mc-icon-Download', - titleTranslationId: 'action.job.download_details', + symbol: { class: 'mc-icon-Download' }, + title: { translationId: 'action.job.download_details' }, views: [], }, ], @@ -253,29 +301,26 @@ const ACTIONS_CONFIG: IEntityActions[] = [ type: EntityActionsTypes.JOB_ACTIONS, group: [ { - action: 'retry', + action: JobAction.retry, frequent: false, - icon: '', - class: 'mc-icon-Job-Resume', - titleTranslationId: 'action.job.retry', + symbol: { class: 'mc-icon-Job-Resume' }, + title: { translationId: 'action.job.retry' }, dependentField: 'availableActions.isResumable', views: [], }, { - action: 'abort', + action: JobAction.abort, frequent: false, - icon: '', - class: 'mc-icon-Job-Abort', - titleTranslationId: 'action.job.abort', + symbol: { class: 'mc-icon-Job-Abort' }, + title: { translationId: 'action.job.abort' }, dependentField: 'availableActions.isAbortable', views: [], }, { - action: 'restore', + action: JobAction.restore, frequent: false, - icon: '', - class: 'mc-icon-Upload', - titleTranslationId: 'action.job.restore', + symbol: { class: 'mc-icon-Upload' }, + title: { translationId: 'action.job.restore' }, dependentField: 'availableActions.isRestorable', views: [], }, diff --git a/src/common/components/error/error-presentor.css b/src/common/components/error/error-presentor.css index 034df865f..5978ef84b 100644 --- a/src/common/components/error/error-presentor.css +++ b/src/common/components/error/error-presentor.css @@ -16,6 +16,7 @@ height: 60px; min-width: 264px; overflow-y: auto; + overflow-x: hidden; } body[dir='rtl'] .errorsList { diff --git a/src/common/components/grid/cell-renderer/actions.cell-renderer.css b/src/common/components/grid/cell-renderer/actions.cell-renderer.css index 85e2f0078..494311860 100644 --- a/src/common/components/grid/cell-renderer/actions.cell-renderer.css +++ b/src/common/components/grid/cell-renderer/actions.cell-renderer.css @@ -106,3 +106,8 @@ body[dir='rtl'] #gridActionsCellRenderer .actionMenuItemTitle { .actionMenuItemContainer:hover { background: var(--mdc-theme-gc-alternative-surface); } + +#gridActionsCellRenderer .disabled { + opacity: 0.5; + pointer-events: none; +} diff --git a/src/common/components/grid/cell-renderer/actions.cell-renderer.tsx b/src/common/components/grid/cell-renderer/actions.cell-renderer.tsx index 048442be7..f4d5feea9 100644 --- a/src/common/components/grid/cell-renderer/actions.cell-renderer.tsx +++ b/src/common/components/grid/cell-renderer/actions.cell-renderer.tsx @@ -17,7 +17,7 @@ const FIRST = 0; const EMPTY_ACTION_GROUP = 0; interface IActionsRendererParams extends ICellRendererParams { - actions: Record; + actions: IActionGroup[]; actionHandler: (action: Record) => void; } @@ -50,13 +50,10 @@ export const ActionsRenderer: React.FC = (props) => { return filteredActionGroups; }; - const actions = useMemo( - () => filterActionsByDependentFields(props.actions[entity]), - [props.actions[entity]] - ); + const actions = useMemo(() => filterActionsByDependentFields(props.actions), [props.actions]); let frequentActions: IAction[] = []; let allFlatActions: IAction[] = []; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (actions !== undefined) { actions.forEach((actionGroup) => { frequentActions = [ @@ -83,11 +80,12 @@ export const ActionsRenderer: React.FC = (props) => { return ( { sendAction(entity, action, props.data); @@ -114,6 +112,11 @@ export const ActionsRenderer: React.FC = (props) => { { + if (action.disabled) { + evt.preventDefault(); + evt.stopPropagation(); + return; + } sendAction(entity, action, props.data); setOpenActionsMenu(false); }} @@ -121,15 +124,22 @@ export const ActionsRenderer: React.FC = (props) => { > - - {action.titleTranslationId} + + {action.title.translationId} diff --git a/src/common/components/ol-map/fly-to.tsx b/src/common/components/ol-map/fly-to.tsx index 23d16bf2a..ce65b35cf 100644 --- a/src/common/components/ol-map/fly-to.tsx +++ b/src/common/components/ol-map/fly-to.tsx @@ -16,19 +16,26 @@ export const FlyTo: React.FC = ({ feature, flyOnce = false }) => { if (!feature || (flyOnce && hasFlownRef.current)) { return; } - try { - const geometry = new GeoJSON().readGeometry(feature?.geometry); - const view = map.getView(); - map.getView().fit(geometry.getExtent(), { - duration: 250, - maxZoom: view.getMaxZoom() ?? 18, - padding: [32, 32, 32, 32], - }); - hasFlownRef.current = true; - } catch { - return; - } - }, [feature]); + + const listener = () => { + try { + const geometry = new GeoJSON().readGeometry(feature.geometry); + const view = map.getView(); + + view.fit(geometry.getExtent(), { + duration: 250, + maxZoom: view.getMaxZoom() ?? 18, + padding: [32, 32, 32, 32], + }); + + hasFlownRef.current = true; + } finally { + map.un('rendercomplete', listener); + } + }; + + map.once('rendercomplete', listener); + }, [feature, map]); return null; }; diff --git a/src/common/components/ol-map/ol-tile-layer.utils.tsx b/src/common/components/ol-map/ol-tile-layer.utils.tsx new file mode 100644 index 000000000..3894e3b29 --- /dev/null +++ b/src/common/components/ol-map/ol-tile-layer.utils.tsx @@ -0,0 +1,86 @@ +import { useMemo } from 'react'; +import { get } from 'lodash'; +import { + getWMTSOptions, + getXYZOptions, + IBaseMap, + IBaseMaps, + TileLayer, + TileWMTS, + TileXYZ, +} from '@map-colonies/react-components'; +import { Options as WMTSOptions } from 'ol/source/WMTS'; +import { Options as XYZOptions } from 'ol/source/XYZ'; + +export const DEFAULT_PROJECTION = 'EPSG:4326'; + +interface BuildOlTileLayerParams { + key: string; + protocol: 'WMTS' | 'XYZ'; + options: WMTSOptions | XYZOptions; + layerOptions?: Record; +} + +export const OlTileLayer: React.FC = ({ + key, + protocol, + options, + layerOptions, +}): JSX.Element => { + const tileOptions = { ...options, crossOrigin: 'anonymous' }; + return ( + + {protocol === 'WMTS' ? ( + + ) : ( + + )} + + ); +}; + +export const usePreviewBaseMapTiles = (baseMaps: IBaseMaps | undefined): JSX.Element[] => { + return useMemo(() => { + const olBaseMap: JSX.Element[] = []; + let baseMap = baseMaps?.maps.find((map: IBaseMap) => map.isForPreview); + if (!baseMap) { + baseMap = baseMaps?.maps.find((map: IBaseMap) => map.isCurrent); + } + if (baseMap) { + baseMap.baseRasterLayers.forEach((layer) => { + if (layer.type === 'WMTS_LAYER') { + const wmtsOptions = getWMTSOptions({ + url: layer.options.url as string, + layer: '', + matrixSet: get(layer.options, 'tileMatrixSetID') as string, + format: get(layer.options, 'format'), + projection: DEFAULT_PROJECTION, // Should be taken from map-server capabilities (MAPCO-3780) + style: get(layer.options, 'style'), + }); + olBaseMap.push( + + ); + } + if (layer.type === 'XYZ_LAYER') { + const xyzOptions = getXYZOptions({ + url: layer.options.url as string, + }); + olBaseMap.push( + + ); + } + }); + } + return olBaseMap; + }, []); +}; diff --git a/src/common/components/ol-map/toggle-base-map.css b/src/common/components/ol-map/toggle-base-map.css new file mode 100644 index 000000000..443143d0a --- /dev/null +++ b/src/common/components/ol-map/toggle-base-map.css @@ -0,0 +1,9 @@ +#baseMap { + top: 0px; + right: 8px; + margin-top: 88px; +} + +#baseMap.ol-control button.mdc-icon-button { + cursor: pointer; +} diff --git a/src/common/components/ol-map/toggle-base-map.tsx b/src/common/components/ol-map/toggle-base-map.tsx new file mode 100644 index 000000000..1c58c51ba --- /dev/null +++ b/src/common/components/ol-map/toggle-base-map.tsx @@ -0,0 +1,40 @@ +import { Box } from '@map-colonies/react-components'; +import { IconButton, Tooltip, useTheme } from '@map-colonies/react-core'; +import { ActiveLayersIcon } from '../../../icons/4font/ActiveLayers'; + +import './toggle-base-map.css'; + +interface ToggleBaseMapProps { + enableLabel: string; + disableLabel: string; + isBaseMapVisible: boolean; + onToggle: () => void; +} + +export const ToggleBaseMap: React.FC = (props) => { + const theme = useTheme(); + + return ( + <> + + + + } + onClick={(): void => { + props.onToggle(); + }} + /> + + + + ); +}; diff --git a/src/common/components/tree/icon-renderers/actions.button-renderer.css b/src/common/components/tree/icon-renderers/actions.button-renderer.css index d019c1808..1cd0f37e3 100644 --- a/src/common/components/tree/icon-renderers/actions.button-renderer.css +++ b/src/common/components/tree/icon-renderers/actions.button-renderer.css @@ -65,3 +65,8 @@ body[dir='rtl'] .actionMenuItemTitle { padding-left: unset; padding-right: 8px; } + +.actionsContainer .disabled { + opacity: 0.5; + pointer-events: none; +} diff --git a/src/common/components/tree/icon-renderers/actions.button-renderer.tsx b/src/common/components/tree/icon-renderers/actions.button-renderer.tsx index 8f2ceb617..3688db1e3 100644 --- a/src/common/components/tree/icon-renderers/actions.button-renderer.tsx +++ b/src/common/components/tree/icon-renderers/actions.button-renderer.tsx @@ -47,11 +47,12 @@ export const ActionsRenderer: React.FC = ({ return ( { sendAction(entity, action, node); @@ -72,21 +73,31 @@ export const ActionsRenderer: React.FC = ({ { + if (action.disabled) { + evt.preventDefault(); + evt.stopPropagation(); + return; + } sendAction(entity, action, node); setOpenActionsMenu(false); }} className="actionMenuItem" > - - {action.titleTranslationId} + + {action.title.translationId} ); diff --git a/src/common/i18n/en.json b/src/common/i18n/en.json index 001f1f140..3fa19ac62 100644 --- a/src/common/i18n/en.json +++ b/src/common/i18n/en.json @@ -179,6 +179,9 @@ "delete.dialog.action": "Delete", "delete.dialog.checkbox": "Confirm deleting", + "delete.approver-name": "Executor name", + "delete.approval-code": "Deletion code", + "snack.message.success": "SUCCESS", "snack.message.failed": "FAILURE", "snack.dismiss-btn.text": "Dismiss", @@ -237,6 +240,8 @@ "polygon-parts.map-preview-legend.LOW_RESOLUTION_PP_EXCEEDED": "Exceeded polygon parts", "polygon-parts.map-preview.zoom-before-fetch": "Zoom in to see the parts", "polygon-parts.map-preview.no-feature-properties": "No properties available", + "polygon-parts.map-preview.base-map": "Show base map", + "polygon-parts.map-preview.without.base-map": "Remove base map", "field.actions.edit-draft": "Edit draft", "field.actions.more": "More options", diff --git a/src/common/i18n/he.json b/src/common/i18n/he.json index c28be6f59..f40688901 100644 --- a/src/common/i18n/he.json +++ b/src/common/i18n/he.json @@ -179,6 +179,9 @@ "delete.dialog.action": "מחיקת", "delete.dialog.checkbox": "אישור מחיקה", + "delete.approver-name": "שם מבצע", + "delete.approval-code": "קוד מחיקה", + "snack.message.success": "הצלחה", "snack.message.failed": "כשלון", "snack.dismiss-btn.text": "אישור", @@ -237,6 +240,8 @@ "polygon-parts.map-preview-legend.LOW_RESOLUTION_PP_EXCEEDED": "חלקי תיחום חורגים", "polygon-parts.map-preview.zoom-before-fetch": "התקרב כדי לראות את חלקי התיחום", "polygon-parts.map-preview.no-feature-properties": "אין מאפיינים להצגה", + "polygon-parts.map-preview.base-map": "הצג מפת בסיס", + "polygon-parts.map-preview.without.base-map": "הסר מפת בסיס", "field.actions.edit-draft": "ערוך טיוטה", "field.actions.more": "פעולות נוספות", diff --git a/src/common/i18n/helpers/helpers.ts b/src/common/i18n/helpers/helpers.ts deleted file mode 100644 index 6fb34a986..000000000 --- a/src/common/i18n/helpers/helpers.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const isRtl = (locale: string) => { - return locale === 'he'; -}; diff --git a/src/discrete-layer/components/catalog-tree/catalog-tree.tsx b/src/discrete-layer/components/catalog-tree/catalog-tree.tsx index 64a3afd1d..0400bcf00 100644 --- a/src/discrete-layer/components/catalog-tree/catalog-tree.tsx +++ b/src/discrete-layer/components/catalog-tree/catalog-tree.tsx @@ -7,7 +7,6 @@ import { changeNodeAtPath, getNodeAtPath, find, ExtendedNodeData } from 'react-s import { useIntl } from 'react-intl'; import { Box } from '@map-colonies/react-components'; import { useTheme } from '@map-colonies/react-core'; -import { IActionGroup } from '../../../common/actions/entity.actions'; import { TreeComponent, TreeItem } from '../../../common/components/tree'; import { ActionsRenderer } from '../../../common/components/tree/icon-renderers/actions.button-renderer'; import { FootprintRenderer } from '../../../common/components/tree/icon-renderers/footprint.icon-renderer'; @@ -15,13 +14,18 @@ import { LayerImageRenderer } from '../../../common/components/tree/icon-rendere import { ProductTypeRenderer } from '../../../common/components/tree/icon-renderers/product-type.icon-renderer'; import { Error } from '../../../common/components/tree/statuses/error'; import { Loading } from '../../../common/components/tree/statuses/loading'; -import { getTextStyle } from '../../../common/helpers/style'; +import { getTextStyle, isUnpublished } from '../../../common/helpers/style'; import { isValidLayerMetadata } from '../../../common/helpers/layer-url'; import { LinkType } from '../../../common/models/link-type.enum'; import { getResponseErrorMesssage, getResponseErrorURL, } from '../../../common/helpers/server-error'; +import { + CRUDAction, + getActionsWithDisable, + IActionGroup, +} from '../../../common/actions/entity.actions'; import { IDispatchAction } from '../../models/actionDispatcherStore'; import { ILayerImage } from '../../models/layerImage'; import { useStore } from '../../models/RootStore'; @@ -117,9 +121,12 @@ export const CatalogTreeComponent: React.FC = observe .map((action) => { return { ...action, - titleTranslationId: intl.formatMessage({ - id: action.titleTranslationId, - }), + title: { + ...action.title, + translationId: intl.formatMessage({ + id: action.title.translationId, + }), + }, }; }), }; @@ -310,9 +317,10 @@ export const CatalogTreeComponent: React.FC = observe hoveredNode.parentPath === rowInfo.path.slice(0, -1).toString() && ( diff --git a/src/discrete-layer/components/export-layer/export-layer-toolbar.component.tsx b/src/discrete-layer/components/export-layer/export-layer-toolbar.component.tsx index dc176fdc2..bada18c3a 100644 --- a/src/discrete-layer/components/export-layer/export-layer-toolbar.component.tsx +++ b/src/discrete-layer/components/export-layer/export-layer-toolbar.component.tsx @@ -74,9 +74,9 @@ const MenuActionPresentor: React.FC = ({ ); })} - + setIsActionMenuOpen((isMenuOpen) => !isMenuOpen)} disabled={action.disabled} @@ -111,7 +111,9 @@ const ToggleActionPresentor: React.FC = ({ checked={isChecked} onClick={(): void => dispatchAction({ action: action.action, data })} label={checkboxLabel} - className={`exportAction exportToggle ${action.disabled ? 'disabled' : ''} ${action.class}`} + className={`exportAction exportToggle ${action.disabled ? 'disabled' : ''} ${ + action.symbol.class + }`} key={listKey} disabled={action.disabled} /> @@ -142,7 +144,7 @@ const ActionPresentor: React.FC = (props) => { default: presentor = ( = (props) => { } return ( - {presentor} + + {presentor} + ); }, [action]); diff --git a/src/discrete-layer/components/export-layer/hooks/useDomainExportActionsConfig.ts b/src/discrete-layer/components/export-layer/hooks/useDomainExportActionsConfig.ts index d951b5734..9095daec2 100644 --- a/src/discrete-layer/components/export-layer/hooks/useDomainExportActionsConfig.ts +++ b/src/discrete-layer/components/export-layer/hooks/useDomainExportActionsConfig.ts @@ -46,14 +46,15 @@ const EXPORT_ACTIONS: ExportAction[] = [ { action: ExportActions.TOGGLE_FULL_LAYER_EXPORT, frequent: true, - icon: '', - class: '', + symbol: {}, toggleExportStoreFieldOptions: { field: 'isFullLayerExportEnabled', labelChecked: 'action.export.full-layer.label', labelUnchecked: 'action.export.full-layer.label', }, - titleTranslationId: 'action.export.full-layer.tooltip', + title: { + translationId: 'action.export.full-layer.tooltip', + }, disabled: false, views: [TabViews.EXPORT_LAYER], }, @@ -61,54 +62,72 @@ const EXPORT_ACTIONS: ExportAction[] = [ { action: ExportActions.DRAW_FOOTPRINT, frequent: true, - icon: '', - class: 'mc-icon-Map-Orthophoto', - titleTranslationId: 'action.export.footprint.tooltip', + symbol: { + class: 'mc-icon-Map-Orthophoto', + }, + title: { + translationId: 'action.export.footprint.tooltip', + }, disabled: false, views: [TabViews.EXPORT_LAYER], }, { action: ExportActions.DRAW_RECTANGLE, frequent: true, - icon: '', - class: 'mc-icon-Rectangle', - titleTranslationId: 'action.export.box.tooltip', + symbol: { + class: 'mc-icon-Rectangle', + }, + title: { + translationId: 'action.export.box.tooltip', + }, disabled: false, views: [TabViews.EXPORT_LAYER], }, { action: ExportActions.DRAW_POLYGON, frequent: true, - icon: '', - class: 'mc-icon-Polygon', - titleTranslationId: 'action.export.polygon.tooltip', + symbol: { + class: 'mc-icon-Polygon', + }, + title: { + translationId: 'action.export.polygon.tooltip', + }, disabled: false, views: [TabViews.EXPORT_LAYER], }, { action: ExportActions.DRAW_BY_COORDINATES, frequent: true, - icon: '', - class: 'mc-icon-Coordinates', - titleTranslationId: 'action.export.bbox-corners.tooltip', + symbol: { + class: 'mc-icon-Coordinates', + }, + title: { + translationId: 'action.export.bbox-corners.tooltip', + }, disabled: false, views: [TabViews.EXPORT_LAYER], }, { action: ExportActions.IMPORT_FROM_SHAPE_FILE, frequent: true, - icon: '', - class: 'mc-icon-Upload', - titleTranslationId: 'action.export.import-shape.tooltip', + symbol: { + class: 'mc-icon-Upload', + }, + title: { + translationId: 'action.export.import-shape.tooltip', + }, disabled: false, views: [TabViews.EXPORT_LAYER], }, { action: ExportActions.OPEN_HOT_AREAS_MENU, frequent: true, - icon: '', - class: 'mc-icon-Predefined-AOIs', - titleTranslationId: 'action.export.select-hot-area.tooltip', + symbol: { + class: 'mc-icon-Predefined-AOIs', + }, + title: { + translationId: 'action.export.select-hot-area.tooltip', + }, menuActionOptions: { items: new Map(), dispatchOnItemClick: { @@ -122,9 +141,12 @@ const EXPORT_ACTIONS: ExportAction[] = [ { action: ExportActions.CLEAR_DRAWINGS, frequent: true, - icon: '', - class: 'mc-icon-Delete', - titleTranslationId: 'action.clear.tooltip', + symbol: { + class: 'mc-icon-Delete', + }, + title: { + translationId: 'action.clear.tooltip', + }, disabled: false, views: [TabViews.EXPORT_LAYER], }, @@ -208,13 +230,11 @@ const useDomainExportActionsConfig = (): ExportAction[] => { const newActionList = domainActionList.map((action) => { if (action === 'SEPARATOR' || action.action === ExportActions.CLEAR_DRAWINGS) return action; - const rasterAction: ExportAction = { - ...action, - class: - action.action === ExportActions.DRAW_FOOTPRINT - ? 'mc-icon-Map-Orthophoto' - : action.class, - }; + const rasterClass: string = + action.action === ExportActions.DRAW_FOOTPRINT + ? 'mc-icon-Map-Orthophoto' + : action.symbol.class ?? ''; + const rasterAction: ExportAction = { ...action, symbol: { class: rasterClass } }; if (!isEmpty(selectionsFeatures)) { if ( @@ -251,10 +271,11 @@ const useDomainExportActionsConfig = (): ExportAction[] => { const newActionList = domainActionList.map((action) => { if (action === 'SEPARATOR') return action; - const action3D: ExportAction = { - ...action, - class: action.action === ExportActions.DRAW_FOOTPRINT ? 'mc-icon-Map-3D' : action.class, - }; + const action3DClass: string = + action.action === ExportActions.DRAW_FOOTPRINT + ? 'mc-icon-Map-3D' + : action.symbol.class ?? ''; + const action3D: ExportAction = { ...action, symbol: { class: action3DClass } }; return { ...action3D, disabled: true }; }); @@ -273,11 +294,11 @@ const useDomainExportActionsConfig = (): ExportAction[] => { const newActionList = domainActionList.map((action) => { if (action === 'SEPARATOR' || action.action === ExportActions.CLEAR_DRAWINGS) return action; - const demAction: ExportAction = { - ...action, - class: - action.action === ExportActions.DRAW_FOOTPRINT ? 'mc-icon-Map-DTM' : action.class, - }; + const demClass: string = + action.action === ExportActions.DRAW_FOOTPRINT + ? 'mc-icon-Map-DTM' + : action.symbol.class ?? ''; + const demAction: ExportAction = { ...action, symbol: { class: demClass } }; if (!isEmpty(selectionsFeatures)) { if ( diff --git a/src/discrete-layer/components/helpers/layersUtils.ts b/src/discrete-layer/components/helpers/layersUtils.ts index 105037cd6..2ef675cc0 100644 --- a/src/discrete-layer/components/helpers/layersUtils.ts +++ b/src/discrete-layer/components/helpers/layersUtils.ts @@ -151,11 +151,20 @@ export const getTokenResource = (url: string, ver?: string): CesiumResource => { return new CesiumResource({ ...(tokenProps as unknown as CesiumResource) }); }; -export const getWMTSOptions = ( +export interface ResolvedWmtsCapabilityParams { + url: string; + layer: string; + style: string; + format: string; + tileMatrixSetID: string; + tileMatrixLabels: string[]; +} + +export const resolveWmtsCapabilityParams = ( layer: LayerRasterRecordModelType, url: string, capability: CapabilityModelType | undefined -): RCesiumWMTSLayerOptions => { +): ResolvedWmtsCapabilityParams => { let style = 'default'; let format = 'image/jpeg'; let { tileMatrixSetID, tileMatrixLabels } = { @@ -207,13 +216,29 @@ export const getWMTSOptions = ( )?.template ?? url; } return { - url: getTokenResource(url, layer.productVersion as string), + url, layer: `${layer.productId as string}-${layer.productVersion as string}`, style, format, tileMatrixSetID, - // tileMatrixLabels, - maximumLevel: Math.max(...tileMatrixLabels.map(Number)), + tileMatrixLabels, + }; +}; + +export const getWMTSOptions = ( + layer: LayerRasterRecordModelType, + url: string, + capability: CapabilityModelType | undefined +): RCesiumWMTSLayerOptions => { + const resolved = resolveWmtsCapabilityParams(layer, url, capability); + return { + url: getTokenResource(resolved.url, layer.productVersion as string), + layer: resolved.layer, + style: resolved.style, + format: resolved.format, + tileMatrixSetID: resolved.tileMatrixSetID, + // tileMatrixLabels: resolved.tileMatrixLabels, + maximumLevel: Math.max(...resolved.tileMatrixLabels.map(Number)), tilingScheme: new CesiumGeographicTilingScheme(), }; }; diff --git a/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx b/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx index 4996fb202..62f4497cc 100644 --- a/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx +++ b/src/discrete-layer/components/job-manager/grids/job-manager-grid.common.tsx @@ -25,12 +25,11 @@ import { PriorityRenderer } from '../cell-renderer/priority.cell-renderer'; import PlaceholderCellRenderer from '../cell-renderer/placeholder.cell-renderer'; import { StatusRenderer } from '../cell-renderer/status.cell-renderer'; import { TooltippedCellRenderer } from '../cell-renderer/tool-tipped.cell-renderer'; -import { JOB_ENTITY } from '../job.types'; export interface ICommonJobManagerGridProps { rowData: unknown[]; dispatchAction: (action: Record | undefined) => void; - getJobActions: { [JOB_ENTITY]: IActionGroup[] }; + getJobActions: IActionGroup[]; updateJobCB: (updateParam: Record) => void; rowDataChangeCB?: () => void; gridOptionsOverride?: Partial; diff --git a/src/discrete-layer/components/job-manager/jobs.dialog.tsx b/src/discrete-layer/components/job-manager/jobs.dialog.tsx index 07d636677..5f25098d1 100644 --- a/src/discrete-layer/components/job-manager/jobs.dialog.tsx +++ b/src/discrete-layer/components/job-manager/jobs.dialog.tsx @@ -96,17 +96,18 @@ export const JobsDialog: React.FC = observer((props: JobsDialog const groupsWithTranslation = action.group.map((action) => { return { ...action, - titleTranslationId: intl.formatMessage({ - id: action.titleTranslationId, - }), + title: { + ...action.title, + translationId: intl.formatMessage({ + id: action.title.translationId, + }), + }, }; }); return { ...action, group: groupsWithTranslation }; }); - return { - [JOB_ENTITY]: actions, - }; + return actions; }, []); useEffect(() => { diff --git a/src/discrete-layer/components/layer-details/3D/entity.3d.delete-dialog.css b/src/discrete-layer/components/layer-details/3D/entity.3d.delete-dialog.css new file mode 100644 index 000000000..77bbd842b --- /dev/null +++ b/src/discrete-layer/components/layer-details/3D/entity.3d.delete-dialog.css @@ -0,0 +1,76 @@ +#dialog3DDelete { + --warning-message-height: 60px; + --header-height: 150px; + --footer-height: 60px; + --modal-height: 100vh; + --modal-width: 888px; + --map-height: calc( + 100% - var(--header-height) - var(--footer-height) - var(--warning-message-height) + ); +} + +#dialog3DDelete .icon { + color: var(--mdc-theme-gc-error-high); + font-size: 28px; + margin: 12px; +} + +#dialog3DDelete .footer { + display: flex; + width: 100%; + height: var(--footer-height); +} + +#dialog3DDelete .buttons { + display: flex; + gap: 20px; + align-items: center; + margin-left: auto; +} + +body[dir='rtl'] #dialog3DDelete .buttons { + margin-left: unset; + margin-right: auto; +} + +#dialog3DDelete .closeIcon { + margin-left: auto; +} + +body[dir='rtl'] #dialog3DDelete .closeIcon { + margin-left: unset; + margin-right: auto; +} + +#dialog3DDelete .headerWarning { + height: var(--warning-message-height); + display: flex; + align-items: center; +} + +#dialog3DDelete .mdc-dialog .mdc-dialog__surface { + max-width: var(--modal-width); + height: var(--modal-height); +} + +#deleteLayerDetailsContainer { + position: relative; + display: flex; + background-color: var(--mdc-theme-text-primary-on-light); + padding: 8px; + align-items: center; + justify-content: center; + width: 100%; + height: var(--header-height); +} + +#deleteLayerDetails { + margin: 0 0; + width: 100%; + height: 100%; + overflow-y: auto; +} + +#dialog3DDelete .errors { + width: 520px; +} diff --git a/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx b/src/discrete-layer/components/layer-details/3D/entity.3d.delete-dialog.tsx similarity index 57% rename from src/discrete-layer/components/layer-details/entity.delete-dialog.tsx rename to src/discrete-layer/components/layer-details/3D/entity.3d.delete-dialog.tsx index 5526b68ee..5c3f83804 100644 --- a/src/discrete-layer/components/layer-details/entity.delete-dialog.tsx +++ b/src/discrete-layer/components/layer-details/3D/entity.3d.delete-dialog.tsx @@ -1,75 +1,47 @@ -import React, { useCallback, useState, useMemo, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { observer } from 'mobx-react'; -import { - Button, - Checkbox, - CircularProgress, - DialogActions, - DialogContent, - Tooltip, -} from '@map-colonies/react-core'; -import { Dialog, DialogTitle, Icon, IconButton, Typography } from '@map-colonies/react-core'; +import { DialogContent } from '@material-ui/core'; +import { Button, Checkbox, CircularProgress, DialogActions } from '@map-colonies/react-core'; +import { Dialog, Icon, Typography } from '@map-colonies/react-core'; import { Box } from '@map-colonies/react-components'; -import { GraphQLError } from '../../../common/components/error/graphql.error-presentor'; -import { emphasizeByHTML } from '../../../common/helpers/formatters'; -import { Mode } from '../../../common/models/mode.enum'; -import { ILayerImage } from '../../models/layerImage'; -import { IDispatchAction } from '../../models/actionDispatcherStore'; -import { UserAction } from '../../models/userStore'; +import { GraphQLError } from '../../../../common/components/error/graphql.error-presentor'; +import { Mode } from '../../../../common/models/mode.enum'; +import { getTextStyle } from '../../../../common/helpers/style'; +import { ILayerImage } from '../../../models/layerImage'; +import { UserAction } from '../../../models/userStore'; import { EntityDescriptorModelType, RecordStatus, RecordType, useQuery, useStore, -} from '../../models'; -import { GeoJsonMapValuePresentorComponent } from './field-value-presentors/geojson-map.value-presentor'; -import { LayersDetailsComponent } from './layer-details'; +} from '../../../models'; +import { GeoJsonMapValuePresentorComponent } from '../field-value-presentors/geojson-map.value-presentor'; +import { LayersDetailsComponent } from '../layer-details'; +import { DialogActionTitle } from '../dialog.helpers'; +import { useDeleteLayer, VALID } from '../delete.hook'; -import './entity.delete-dialog.css'; +import './entity.3d.delete-dialog.css'; -interface EntityDeleteDialogProps { +export interface EntityDeleteDialogProps { isOpen: boolean; onSetOpen: (open: boolean) => void; - recordType?: RecordType; layerRecord: ILayerImage; + onSuccess?: () => void; + recordType?: RecordType; } -const VALID = 'ok'; - -export const EntityDeleteDialog: React.FC = observer( +export const EntityDelete3DDialog: React.FC = observer( (props: EntityDeleteDialogProps) => { const { isOpen, onSetOpen, layerRecord } = props; const store = useStore(); - const mutationQuery = useQuery(); const intl = useIntl(); + const mutationQuery = useQuery(); const [allowDeleting, setAllowDeleting] = useState(false); - const [recordType] = useState( - props.recordType ?? (layerRecord?.type as RecordType) - ); - - const dialogTitleParam = recordType; - const dialogTitleParamTranslation = intl.formatMessage({ - id: `record-type.${(dialogTitleParam as string).toLowerCase()}.label`, - }); - - const dialogTitle = intl.formatMessage( - { id: `general.title.delete` }, - { value: dialogTitleParamTranslation } - ); - - const closeDialog = useCallback(() => { - onSetOpen(false); - }, [onSetOpen, store.discreteLayersStore]); - - const dispatchAction = (action: Record): void => { - store.actionDispatcherStore.dispatchAction({ - action: action.action, - data: action.data, - } as IDispatchAction); - }; + const { dialogTitleParamTranslation, closeDialog, dispatchAction, warningMessage } = + useDeleteLayer({ onSetOpen, layerRecord, recordType: props.recordType }); useEffect(() => { if ( @@ -91,7 +63,7 @@ export const EntityDeleteDialog: React.FC = observer( const deleteLayer = (): void => { mutationQuery.setQuery( - store.mutateDeleteLayer({ + store.mutateDelete3DLayer({ data: { id: layerRecord.id, type: layerRecord.type as RecordType, @@ -100,34 +72,21 @@ export const EntityDeleteDialog: React.FC = observer( ); }; - const deleteMessage = useMemo((): string => { - return intl.formatMessage( - { id: 'delete.dialog.message' }, - { action: emphasizeByHTML(`${intl.formatMessage({ id: 'delete.dialog.action' })}`) } - ); - }, []); - return ( -
+ - - {dialogTitle} - { - closeDialog(); - }} - /> - + - - - + @@ -147,11 +106,10 @@ export const EntityDeleteDialog: React.FC = observer( mode={Mode.VIEW} jsonValue={JSON.stringify(props.layerRecord?.footprint)} fitOptions={{ padding: [80, 160, 80, 160] }} - style={{ width: '100%', height: '480px' }} + style={{ width: '100%', height: 'var(--map-height)' }} /> - = observer( -
+ ); } ); diff --git a/src/discrete-layer/components/layer-details/delete.hook.ts b/src/discrete-layer/components/layer-details/delete.hook.ts new file mode 100644 index 000000000..8729e4b4f --- /dev/null +++ b/src/discrete-layer/components/layer-details/delete.hook.ts @@ -0,0 +1,56 @@ +import { useCallback, useMemo, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { emphasizeByHTML } from '../../../common/helpers/formatters'; +import { ILayerImage } from '../../models/layerImage'; +import { IDispatchAction } from '../../models/actionDispatcherStore'; +import { RecordType, useStore } from '../../models'; + +export const VALID = 'ok'; + +interface DeleteLayerDialogOptions { + onSetOpen: (open: boolean) => void; + layerRecord: ILayerImage; + recordType?: RecordType; +} + +export const useDeleteLayer = ({ + onSetOpen, + layerRecord, + recordType: recordTypeProp, +}: DeleteLayerDialogOptions) => { + const store = useStore(); + + const intl = useIntl(); + + const [recordType] = useState(recordTypeProp ?? (layerRecord?.type as RecordType)); + + const dialogTitleParamTranslation = intl.formatMessage({ + id: `record-type.${(recordType as string).toLowerCase()}.label`, + }); + + const closeDialog = useCallback(() => { + onSetOpen(false); + }, [onSetOpen, store.discreteLayersStore]); + + const dispatchAction = (action: Record): void => { + store.actionDispatcherStore.dispatchAction({ + action: action.action, + data: action.data, + } as IDispatchAction); + }; + + const warningMessage = useMemo((): string => { + return intl.formatMessage( + { id: 'delete.dialog.message' }, + { action: emphasizeByHTML(`${intl.formatMessage({ id: 'delete.dialog.action' })}`) } + ); + }, []); + + return { + recordType, + dialogTitleParamTranslation, + closeDialog, + dispatchAction, + warningMessage, + }; +}; diff --git a/src/discrete-layer/components/layer-details/dialog.helpers.tsx b/src/discrete-layer/components/layer-details/dialog.helpers.tsx new file mode 100644 index 000000000..0714b0ff0 --- /dev/null +++ b/src/discrete-layer/components/layer-details/dialog.helpers.tsx @@ -0,0 +1,35 @@ +import { useIntl } from 'react-intl'; +import { Mode } from '../../../common/models/mode.enum'; +import { DialogTitle, DialogTitleProps, IconButton } from '@map-colonies/react-core'; + +export interface DeleteTitleProps extends DialogTitleProps { + domain: string; + action: Mode; + onClose: () => void; +} + +export const DialogActionTitle: React.FC = ({ + action, + domain, + onClose, + ...dialogTitleProps +}) => { + const intl = useIntl(); + const title = intl.formatMessage( + { id: `general.title.${action.toLowerCase()}` }, + { value: domain } + ); + + return ( + + {title} + { + onClose(); + }} + /> + + ); +}; diff --git a/src/discrete-layer/components/layer-details/entity.delete-dialog.css b/src/discrete-layer/components/layer-details/entity.delete-dialog.css deleted file mode 100644 index 2504f12cb..000000000 --- a/src/discrete-layer/components/layer-details/entity.delete-dialog.css +++ /dev/null @@ -1,85 +0,0 @@ -#entityDeleteDialog { - --update-layer-header-height: 150px; - --footer-height: 60px; - --footer-offset: 4px; - --modal-height: calc(100vh - 80px); - --modal-width: 888px; -} - -#entityDeleteDialog .icon { - color: var(--mdc-theme-gc-warning-high); - font-size: 28px; - margin: 12px; -} - -#entityDeleteDialog .footer { - display: flex; - margin-top: var(--footer-offset); - width: 100%; - height: var(--footer-height); -} - -body[dir='rtl'] #entityDeleteDialog .footer { - left: 20px; -} - -#entityDeleteDialog .buttons { - display: flex; - gap: 20px; - align-items: center; - margin-left: auto; -} - -body[dir='rtl'] #entityDeleteDialog .buttons { - margin-left: unset; - margin-right: auto; -} - -#entityDeleteDialog .closeIcon { - margin-left: auto; -} - -body[dir='rtl'] #entityDeleteDialog .closeIcon { - margin-left: unset; - margin-right: auto; -} - -#entityDeleteDialog .headerWarning { - height: 100px; - display: flex; - align-items: center; -} - -@media (min-width: 900px) { - #entityDeleteDialog .mdc-dialog .mdc-dialog__surface { - max-width: var(--modal-width); - height: var(--modal-height); - } - - #entityDeleteDialog .mdc-dialog .mdc-dialog__surface .mdc-dialog .mdc-dialog__surface { - max-width: var(--modal-width); - height: var(--chonky-modal-height); - } -} - -#deleteLayerDetailsContainer { - position: relative; - display: flex; - background-color: var(--mdc-theme-text-primary-on-light); - padding: 8px; - align-items: center; - justify-content: center; - width: 100%; - height: var(--update-layer-header-height); -} - -#deleteLayerDetails { - margin: 0 0; - width: 100%; - height: 100%; - overflow-y: auto; -} - -#entityDeleteDialog .errors { - width: 520px; -} diff --git a/src/discrete-layer/components/layer-details/field-value-presentors/geojson-map.value-presentor.tsx b/src/discrete-layer/components/layer-details/field-value-presentors/geojson-map.value-presentor.tsx index 023cb5b46..91e7ad4e5 100644 --- a/src/discrete-layer/components/layer-details/field-value-presentors/geojson-map.value-presentor.tsx +++ b/src/discrete-layer/components/layer-details/field-value-presentors/geojson-map.value-presentor.tsx @@ -1,14 +1,8 @@ -import { CSSProperties, useEffect, useMemo, useState } from 'react'; +import { CSSProperties, useEffect, useState } from 'react'; import { Box, GeoJSONFeature, - getWMTSOptions, - getXYZOptions, - IBaseMap, Map, - TileLayer, - TileWMTS, - TileXYZ, VectorLayer, VectorSource, } from '@map-colonies/react-components'; @@ -16,8 +10,8 @@ import { Geometry } from 'geojson'; import { FitOptions } from 'ol/View'; import { validateGeoJSONString } from '../../../../common/utils/geojson.validation'; import { Mode } from '../../../../common/models/mode.enum'; +import { usePreviewBaseMapTiles } from '../../../../common/components/ol-map/ol-tile-layer.utils'; import { useStore } from '../../../models/RootStore'; -import { get } from 'lodash'; interface GeoJsonMapValuePresentorProps { mode: Mode; @@ -26,8 +20,6 @@ interface GeoJsonMapValuePresentorProps { fitOptions?: FitOptions | undefined; } -const DEFAULT_PROJECTION = 'EPSG:4326'; - export const GeoJsonMapValuePresentorComponent: React.FC = ({ mode, jsonValue, @@ -36,6 +28,7 @@ export const GeoJsonMapValuePresentorComponent: React.FC { const [geoJsonValue, setGeoJsonValue] = useState(); const store = useStore(); + useEffect(() => { if (jsonValue && validateGeoJSONString(jsonValue).valid) { //Postpone feature generation till OL-viewer present in DOM @@ -50,56 +43,7 @@ export const GeoJsonMapValuePresentorComponent: React.FC { - // eslint-disable-next-line @typescript-eslint/no-array-constructor - const olBaseMap = new Array(); - let baseMap = store.discreteLayersStore.baseMaps?.maps.find( - (map: IBaseMap) => map.isForPreview - ); - if (!baseMap) { - baseMap = store.discreteLayersStore.baseMaps?.maps.find((map: IBaseMap) => map.isCurrent); - } - if (baseMap) { - baseMap.baseRasterLayers.forEach((layer) => { - if (layer.type === 'WMTS_LAYER') { - const wmtsOptions = getWMTSOptions({ - url: layer.options.url as string, - layer: '', - matrixSet: get(layer.options, 'tileMatrixSetID') as string, - format: get(layer.options, 'format'), - projection: DEFAULT_PROJECTION, // Should be taken from map-server capabilities (MAPCO-3780) - style: get(layer.options, 'style'), - }); - olBaseMap.push( - - - - ); - } - if (layer.type === 'XYZ_LAYER') { - const xyzOptions = getXYZOptions({ - url: layer.options.url as string, - }); - olBaseMap.push( - - - - ); - } - }); - } - return olBaseMap; - }, []); + const previewBaseMap = usePreviewBaseMapTiles(store.discreteLayersStore.baseMaps); return ( diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.css b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.css new file mode 100644 index 000000000..cc9791b98 --- /dev/null +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.css @@ -0,0 +1,94 @@ +#rasterDeleteDialog { + --warning-message-height: 60px; + --header-height: 150px; + --fields-height: 36px; + --fields-offset: 8px; + --footer-height: 60px; + --modal-height: 100vh; + --modal-width: 888px; + --map-height: calc( + 100% - var(--header-height) - var(--warning-message-height) - var(--fields-height) - + var(--fields-offset) - var(--footer-height) + ); +} + +#rasterDeleteDialog .icon { + color: var(--mdc-theme-gc-error-high); + font-size: 28px; + margin: 12px; +} + +#rasterDeleteDialog .footer { + display: flex; + width: 100%; + align-items: center; + height: var(--footer-height); +} + +#rasterDeleteDialog .buttons { + display: flex; + gap: 20px; +} + +#rasterDeleteDialog .closeIcon { + margin-left: auto; +} + +body[dir='rtl'] #rasterDeleteDialog .closeIcon { + margin-left: unset; + margin-right: auto; +} + +#rasterDeleteDialog .deleteWarning { + height: var(--warning-message-height); + display: flex; + align-items: center; +} + +#rasterDeleteDialog .mdc-dialog .mdc-dialog__surface { + max-width: var(--modal-width); + height: var(--modal-height); +} + +#rasterDeleteDialog .deleteLayerDetailsContainer { + position: relative; + display: flex; + background-color: var(--mdc-theme-text-primary-on-light); + padding: 8px; + align-items: center; + justify-content: center; + width: 100%; + height: var(--header-height); +} + +#rasterDeleteDialog .deleteLayerDetails { + margin: 0 0; + width: 100%; + height: 100%; + overflow-y: auto; +} + +#rasterDeleteDialog .errors { + width: 100%; + margin: 0px 12px; +} + +#rasterDeleteDialog .fields { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + height: var(--fields-height); + gap: 16px; + margin-top: var(--fields-offset); +} + +#rasterDeleteDialog .field { + display: flex; + flex-direction: row; + width: 100%; + padding: 4px 4px; +} + +#rasterDeleteDialog .field .mdc-text-field { + flex: 1; +} diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx new file mode 100644 index 000000000..b0f15444d --- /dev/null +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx @@ -0,0 +1,285 @@ +import React, { useRef, useEffect, useMemo } from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { observer } from 'mobx-react'; +import { Feature } from 'geojson'; +import { Formik, FormikProps } from 'formik'; +import { DialogContent } from '@material-ui/core'; +import { Button, CircularProgress, TextField } from '@map-colonies/react-core'; +import { Dialog, Icon, Typography } from '@map-colonies/react-core'; +import { Box, getWMTSOptions, getXYZOptions } from '@map-colonies/react-components'; +import { Mode } from '../../../../common/models/mode.enum'; +import { LinkType } from '../../../../common/models/link-type.enum'; +import { getTextStyle } from '../../../../common/helpers/style'; +import { FlyTo } from '../../../../common/components/ol-map/fly-to'; +import { FieldLabelComponent } from '../../../../common/components/form/field-label'; +import { GraphQLError } from '../../../../common/components/error/graphql.error-presentor'; +import { + OlTileLayer, + DEFAULT_PROJECTION, +} from '../../../../common/components/ol-map/ol-tile-layer.utils'; +import { + EntityDescriptorModelType, + LayerRasterRecordModelType, + LinkModelType, + RecordType, + useQuery, + useStore, +} from '../../../models'; +import { + getLayerLink, + getLinkUrlWithToken, + resolveWmtsCapabilityParams, +} from '../../helpers/layersUtils'; +import { DialogActionTitle } from '../dialog.helpers'; +import { LayersDetailsComponent } from '../layer-details'; +import { EntityDeleteDialogProps } from '../3D/entity.3d.delete-dialog'; +import { useDeleteLayer } from '../delete.hook'; +import { GeoFeaturesPresentorComponent } from './pp-map'; +import { START_RASTER_LAYER_ZINDEX } from './pp-map.utils'; + +import './entity.raster.delete-dialog.css'; + +export const EntityDeleteRasterDialog: React.FC = observer( + (props: EntityDeleteDialogProps) => { + const { isOpen, onSetOpen, layerRecord } = props; + const store = useStore(); + const intl = useIntl(); + const mutationQuery = useQuery(); + + const formikRef = useRef>() as any; + const [initialDeleteValues] = React.useState({ approvalCode: '', approverName: '' }); + + const { dialogTitleParamTranslation, closeDialog, warningMessage } = useDeleteLayer({ + onSetOpen, + layerRecord, + recordType: props.recordType, + }); + + const deleteLayer = (approverName: string, approvalCode: string): void => { + mutationQuery.setQuery( + store.mutateDeleteRasterLayer({ + data: { + id: layerRecord.id, + type: layerRecord.type as RecordType, + approverName, + approvalCode, + }, + }) + ); + }; + + useEffect(() => { + if (mutationQuery.data && !mutationQuery.error) { + props.onSetOpen(false); + props.onSuccess?.(); + } + }, [mutationQuery.data]); + + const flyToFeature = useMemo(() => { + return { + type: 'Feature', + properties: {}, + geometry: layerRecord.footprint, + } as Feature; + }, []); + + const layerImage = useMemo(() => { + if (!layerRecord) { + return undefined; + } + const layerLink = getLayerLink(layerRecord); + if (!layerLink?.url) { + return undefined; + } + if (layerLink.protocol === LinkType.XYZ_LAYER) { + const xyzOptions = getXYZOptions({ + url: getLinkUrlWithToken([layerLink]) as string, + }); + return ( + + ); + } + if (layerLink.protocol === LinkType.WMTS_LAYER || layerLink.protocol === LinkType.WMTS) { + const capability = store.discreteLayersStore.capabilities?.find( + (item) => layerLink.name === item.id + ); + const resolved = resolveWmtsCapabilityParams( + layerRecord as LayerRasterRecordModelType, + layerLink.url as string, + capability + ); + const wmtsOptions = getWMTSOptions({ + url: getLinkUrlWithToken([ + { ...layerLink, url: resolved.url } as LinkModelType, + ]) as string, + layer: resolved.layer, + matrixSet: resolved.tileMatrixSetID, + format: resolved.format, + projection: DEFAULT_PROJECTION, + style: resolved.style, + }); + + return ( + + ); + } + return undefined; + }, [layerRecord]); + + return ( + + + + + + + + + + + + + + + <> + {layerImage} + + + + { + if (instance) { + formikRef.current = instance; + } + }} + validateOnMount + validate={(values) => { + const errors: any = {}; + + if (!values.approverName?.trim()) { + errors.approverName = true; + } + + if (!values.approvalCode?.trim()) { + errors.approvalCode = true; + } + + return errors; + }} + onSubmit={(values, actions) => { + if (formikRef.current?.isValid) { + deleteLayer( + formikRef.current?.values.approverName, + formikRef.current?.values.approvalCode + ); + } + }} + > + {(props) => ( +
+ + + + + + + + + + + + + + + + + + + +
+ )} +
+
+
+
+ ); + } +); diff --git a/src/discrete-layer/components/layer-details/raster/feature-type.enum.ts b/src/discrete-layer/components/layer-details/raster/feature-type.enum.ts index 0edacac05..938bd9565 100644 --- a/src/discrete-layer/components/layer-details/raster/feature-type.enum.ts +++ b/src/discrete-layer/components/layer-details/raster/feature-type.enum.ts @@ -4,5 +4,6 @@ export enum FeatureType { SOURCE_EXTENT = 'SOURCE_EXTENT', SOURCE_EXTENT_MARKER = 'SOURCE_EXTENT_MARKER', EXISTING_PP = 'EXISTING_PP', + EXISTING_PP_FOOTPRINT_MARKER = 'EXISTING_PP_FOOTPRINT_MARKER', LOW_RESOLUTION_PP = 'LOW_RESOLUTION_PP', } diff --git a/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css b/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css index 5023721ba..127ff6eea 100644 --- a/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css +++ b/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.css @@ -108,37 +108,10 @@ body[dir='rtl'] #layerDetailsFormRaster .enumOptions .mdc-select__dropdown-icon border-bottom: 1px solid var(--mdc-theme-gc-selection-background); } -#layerDetailsFormRaster .map { - border: 1px solid var(--mdc-theme-gc-selection-background, #fff); -} - #layerDetailsFormRaster .ReactVirtualized__Grid.ReactVirtualized__List { direction: inherit !important; } -#layerDetailsFormRaster .mdc-checkbox__ripple { - display: none; -} - -#layerDetailsFormRaster .flexCheckItem { - flex: 1; - position: relative; -} - -#layerDetailsFormRaster .mdc-checkbox__background { - width: 12px; - height: 12px; -} - -#layerDetailsFormRaster .showOnMapContainer .mdc-checkbox { - padding-left: 0px; -} - -#layerDetailsFormRaster .showOnMapContainer label { - padding-bottom: 6px; - color: var(--mdc-theme-text-icon-on-dark); -} - #layerDetailsFormRaster .remainingTime { width: auto; position: absolute; diff --git a/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx b/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx index 631fc5e48..6c86eef1b 100644 --- a/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx +++ b/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx @@ -11,6 +11,7 @@ import { polygon } from '@turf/helpers'; import bboxPolygon from '@turf/bbox-polygon'; import { GeoJSONFeature, useMap, VectorLayer, VectorSource } from '@map-colonies/react-components'; import CONFIG from '../../../../common/config'; +import { getFirstPoint } from '../../../../common/utils/geo.tools'; import { useStore } from '../../../models'; import useZoomLevelsTable from '../../export-layer/hooks/useZoomLevelsTable'; import { createTextStyle, FEATURE_LABEL_CONFIG, getStyleByFeatureType } from './pp-map.utils'; @@ -88,6 +89,22 @@ export const PolygonPartsExtentQueryVectorLayer: React.FC< const geoJsonFormat = useMemo(() => new GeoJSON(), []); + const existingPolygonPartsMarker = useMemo((): Feature | undefined => { + if (!outerPerimeter) { + return undefined; + } + return { + type: 'Feature', + properties: { + _featureType: FeatureType.EXISTING_PP_FOOTPRINT_MARKER, + }, + geometry: { + type: 'Point', + coordinates: getFirstPoint(outerPerimeter), + }, + }; + }, [outerPerimeter]); + const getCurrentExtent = (): BBox => { return mapOl.getView().calculateExtent(mapOl.getSize()) as BBox; }; @@ -221,6 +238,14 @@ export const PolygonPartsExtentQueryVectorLayer: React.FC< /> ) : null; })} + {existingPolygonPartsMarker && polygonParts.length > 0 && ( + + )} ); diff --git a/src/discrete-layer/components/layer-details/raster/pp-map.css b/src/discrete-layer/components/layer-details/raster/pp-map.css index 8b6bb6dd0..222c13601 100644 --- a/src/discrete-layer/components/layer-details/raster/pp-map.css +++ b/src/discrete-layer/components/layer-details/raster/pp-map.css @@ -1,4 +1,9 @@ -#entityRasterDialog .geoFeaturesMapContainer .checkbox { +#geoFeaturesMapContainer { + position: relative; + border: 1px solid var(--mdc-theme-gc-selection-background, #fff); +} + +#geoFeaturesMapContainer .checkbox { display: inline-block; position: absolute; bottom: 1px; @@ -9,22 +14,36 @@ padding-right: 12px; } -body[dir='rtl'] #entityRasterDialog .geoFeaturesMapContainer .checkbox { +body[dir='rtl'] #geoFeaturesMapContainer .checkbox { direction: rtl; padding-right: unset; padding-left: 12px; } -.geoFeaturesMapContainer { - position: relative; +#geoFeaturesMapContainer .ol-zoom button, +#geoFeaturesMapContainer .ol-legend button { + cursor: pointer; } -.geoFeaturesMapContainer .ol-zoom button, -.geoFeaturesMapContainer .ol-legend button { - cursor: pointer; +#geoFeaturesMapContainer .mdc-checkbox__ripple { + display: none; +} + +#geoFeaturesMapContainer .showOnMapContainer .mdc-checkbox { + padding-left: 0px; +} + +#geoFeaturesMapContainer .mdc-checkbox__background { + width: 12px; + height: 12px; +} + +#geoFeaturesMapContainer .showOnMapContainer label { + padding-bottom: 6px; + color: var(--mdc-theme-text-icon-on-dark); } -.featurePropertiesPopup { +#geoFeaturesMapContainer .featurePropertiesPopup { position: absolute; top: 8px; left: 8px; diff --git a/src/discrete-layer/components/layer-details/raster/pp-map.tsx b/src/discrete-layer/components/layer-details/raster/pp-map.tsx index ccaa3471c..fec7f0438 100644 --- a/src/discrete-layer/components/layer-details/raster/pp-map.tsx +++ b/src/discrete-layer/components/layer-details/raster/pp-map.tsx @@ -14,15 +14,9 @@ import bboxPolygon from '@turf/bbox-polygon'; import { FitOptions } from 'ol/View'; import { Box, - getWMTSOptions, - getXYZOptions, - IBaseMap, Legend, LegendItem, Map, - TileLayer, - TileWMTS, - TileXYZ, VectorLayer, VectorSource, } from '@map-colonies/react-components'; @@ -39,6 +33,8 @@ import { ILayerImage } from '../../../models/layerImage'; import { useStore } from '../../../models/RootStore'; import { GeojsonFeatureInput } from '../../../models/RootStore.base'; import useZoomLevelsTable from '../../export-layer/hooks/useZoomLevelsTable'; +import { usePreviewBaseMapTiles } from '../../../../common/components/ol-map/ol-tile-layer.utils'; +import { ToggleBaseMap } from '../../../../common/components/ol-map/toggle-base-map'; import { FeaturePropertiesPopupComponent } from './feature-properties-popup.component'; import { FlyToPP } from './fly-to-pp'; import { GeoFeaturesInnerComponent } from './geo-features-inner.component'; @@ -48,7 +44,7 @@ import { } from './polygon-parts-extent-query-vector-layer'; import { FEATURE_LABEL_CONFIG, - GeometryZIndex, + VectorLayerZIndex, getText, getWFSFeatureTypeName, PPMapStyles, @@ -64,13 +60,14 @@ interface GeoFeaturesPresentorProps { selectedItem?: Feature; onMapFeatureClick?: (feature: Feature | undefined) => void; showFeaturePropertiesPopup?: boolean; + toggleBaseMap?: boolean; showPolygonParts?: boolean; + defaultShowBaseMap?: boolean; fitOptions?: FitOptions | undefined; style?: CSSProperties | undefined; children?: JSX.Element | null; } -const DEFAULT_PROJECTION = 'EPSG:4326'; const MIN_FEATURES_NUMBER = 4; // minimal set of fetures (source, source_marker, perimeter, perimeter_marker) const CHILDREN_WITH_ZOOM_INDICATION = ['PolygonPartsExtentQueryVectorLayer']; @@ -82,6 +79,8 @@ export const GeoFeaturesPresentorComponent: React.FC onMapFeatureClick, showFeaturePropertiesPopup = false, showPolygonParts = false, + defaultShowBaseMap = true, + toggleBaseMap = false, fitOptions, style, children, @@ -94,6 +93,7 @@ export const GeoFeaturesPresentorComponent: React.FC const [selectedFeature, setSelectedFeature] = useState(undefined); const [showExistingPolygonParts, setShowExistingPolygonParts] = useState(showPolygonParts); + const [showBaseMap, setShowBaseMap] = useState(defaultShowBaseMap); const [childrenWithZoomIndication, setChildrenWithZoomIndication] = useState(false); const [isOpenProperties, setIsOpenProperties] = useState(true); @@ -167,56 +167,7 @@ export const GeoFeaturesPresentorComponent: React.FC setIsOpenProperties(false); }, []); - const previewBaseMap = useMemo(() => { - // eslint-disable-next-line @typescript-eslint/no-array-constructor - const olBaseMap = new Array(); - let baseMap = store.discreteLayersStore.baseMaps?.maps.find( - (map: IBaseMap) => map.isForPreview - ); - if (!baseMap) { - baseMap = store.discreteLayersStore.baseMaps?.maps.find((map: IBaseMap) => map.isCurrent); - } - if (baseMap) { - baseMap.baseRasterLayers.forEach((layer) => { - if (layer.type === 'WMTS_LAYER') { - const wmtsOptions = getWMTSOptions({ - url: layer.options.url as string, - layer: '', - matrixSet: get(layer.options, 'tileMatrixSetID') as string, - format: get(layer.options, 'format'), - projection: DEFAULT_PROJECTION, // Should be taken from map-server capabilities (MAPCO-3780) - style: get(layer.options, 'style'), - }); - olBaseMap.push( - - - - ); - } - if (layer.type === 'XYZ_LAYER') { - const xyzOptions = getXYZOptions({ - url: layer.options.url as string, - }); - olBaseMap.push( - - - - ); - } - }); - } - return olBaseMap; - }, []); + const previewBaseMap = usePreviewBaseMapTiles(store.discreteLayersStore.baseMaps); const LegendsArray = useMemo(() => { const res: LegendItem[] = []; @@ -262,9 +213,9 @@ export const GeoFeaturesPresentorComponent: React.FC }; return ( - + - {previewBaseMap} + {showBaseMap && previewBaseMap} legendItems={LegendsArray} title={intl.formatMessage({ id: 'polygon-parts.map-preview-legend.title' })} /> + {toggleBaseMap && ( + setShowBaseMap(!showBaseMap)} + enableLabel={intl.formatMessage({ + id: 'polygon-parts.map-preview.without.base-map', + })} + disableLabel={intl.formatMessage({ id: 'polygon-parts.map-preview.base-map' })} + /> + )} /> - {mode === Mode.UPDATE && ( + {mode !== Mode.NEW && ( ): void => { @@ -308,7 +269,7 @@ export const GeoFeaturesPresentorComponent: React.FC outerPerimeter={layerRecord?.footprint as Geometry | undefined} options={{ properties: { id: FeatureType.EXISTING_PP }, - zIndex: GeometryZIndex.EXISTING_GEOMETRY_ZINDEX, + zIndex: VectorLayerZIndex.EXISTING, }} /> )} @@ -321,7 +282,7 @@ export const GeoFeaturesPresentorComponent: React.FC feature={selectedFeature} options={{ properties: { id: 'SELECTED_PP' }, - zIndex: GeometryZIndex.SELECTED_GEOMETRY_ZINDEX, + zIndex: VectorLayerZIndex.SELECTED, }} /> diff --git a/src/discrete-layer/components/layer-details/raster/pp-map.utils.ts b/src/discrete-layer/components/layer-details/raster/pp-map.utils.ts index c0303fbee..4d7af67b2 100644 --- a/src/discrete-layer/components/layer-details/raster/pp-map.utils.ts +++ b/src/discrete-layer/components/layer-details/raster/pp-map.utils.ts @@ -10,11 +10,12 @@ import { FeatureType } from './feature-type.enum'; export const EXCEEDED_PROPERTY_NAME = 'res_exceed'; export const EXCEEDED_PROPERTY_VALUE = 'true'; -export enum GeometryZIndex { - EXISTING_GEOMETRY_ZINDEX = 1, - LOW_RESOLUTION_GEOMETRY_ZINDEX = 2, - EXCEEDED_GEOMETRY_ZINDEX = 3, - SELECTED_GEOMETRY_ZINDEX = 4, +export const START_RASTER_LAYER_ZINDEX = 10; +export enum VectorLayerZIndex { + EXISTING = 20, + LOW_RESOLUTION = 30, + EXCEEDED = 40, + SELECTED = 50, } interface IStyleByProp { @@ -24,6 +25,7 @@ interface IStyleByProp { value: string | number | boolean; style: Style; }[]; + // zIndex?: GeometryZIndex; } export const PPMapStyles = new Map([ @@ -87,6 +89,18 @@ export const PPMapStyles = new Map([ }), }, ], + [ + FeatureType.EXISTING_PP_FOOTPRINT_MARKER, + { + style: new Style({ + image: new Icon({ + scale: 0.2, + anchor: [0.5, 1], + src: 'assets/img/map-marker.gif', + }), + }), + }, + ], [ FeatureType.LOW_RESOLUTION_PP, { diff --git a/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx b/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx index 5807ab91d..02a7d1d4f 100644 --- a/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx @@ -34,7 +34,7 @@ import { PolygonPartsExtentQueryVectorLayer, } from './polygon-parts-extent-query-vector-layer'; import { GeoFeaturesPresentorComponent } from './pp-map'; -import { EXCEEDED_PROPERTY_NAME, EXCEEDED_PROPERTY_VALUE, GeometryZIndex } from './pp-map.utils'; +import { EXCEEDED_PROPERTY_NAME, EXCEEDED_PROPERTY_VALUE, VectorLayerZIndex } from './pp-map.utils'; import { ProgressCurtain } from './progressCurtain/progressCurtain'; import { RasterWorkflowContext } from './state-machine/context'; import { Events } from './state-machine/types'; @@ -721,13 +721,13 @@ const ResolutionConflictDialogComponent: React.FC outerPerimeter={outerPerimeter?.geometry} options={{ properties: { id: FeatureType.LOW_RESOLUTION_PP }, - zIndex: GeometryZIndex.LOW_RESOLUTION_GEOMETRY_ZINDEX, + zIndex: VectorLayerZIndex.LOW_RESOLUTION, }} /> diff --git a/src/discrete-layer/components/layers-results/layers-results.tsx b/src/discrete-layer/components/layers-results/layers-results.tsx index 0aa4841a6..31e9b1aad 100644 --- a/src/discrete-layer/components/layers-results/layers-results.tsx +++ b/src/discrete-layer/components/layers-results/layers-results.tsx @@ -28,11 +28,16 @@ import { Loading } from '../../../common/components/tree/statuses/loading'; import CONFIG from '../../../common/config'; import { getMax } from '../../../common/helpers/array'; import { dateFormatter } from '../../../common/helpers/formatters'; -import { isPolygonPartsShown } from '../../../common/helpers/style'; +import { isPolygonPartsShown, isUnpublished } from '../../../common/helpers/style'; import { getResponseErrorMesssage, getResponseErrorURL, } from '../../../common/helpers/server-error'; +import { + CRUDAction, + getActionsWithDisable, + IActionGroup, +} from '../../../common/actions/entity.actions'; // import { usePrevious } from '../../../common/hooks/previous.hook'; import { LayerRasterRecordModelType } from '../../models'; import { IDispatchAction } from '../../models/actionDispatcherStore'; @@ -123,7 +128,10 @@ export const LayersResults: React.FC = observer((props) => { .map((action) => { return { ...action, - titleTranslationId: intl.formatMessage({ id: action.titleTranslationId }), + title: { + ...action.title, + translationId: intl.formatMessage({ id: action.title.translationId }), + }, }; }), }; @@ -259,10 +267,13 @@ export const LayersResults: React.FC = observer((props) => { headerName: '', width: 0, cellRenderer: 'actionsRenderer', - cellRendererParams: { - actions: entityPermittedActions, + cellRendererParams: (params: any) => ({ + actions: getActionsWithDisable( + entityPermittedActions[params.data.__typename] as IActionGroup[], + [[CRUDAction.delete, !isUnpublished(params.data)]] + ), actionHandler: dispatchAction, - }, + }), }, ]; diff --git a/src/discrete-layer/models/RootStore.base.ts b/src/discrete-layer/models/RootStore.base.ts index b2d4dcbbe..618ed384c 100644 --- a/src/discrete-layer/models/RootStore.base.ts +++ b/src/discrete-layer/models/RootStore.base.ts @@ -426,10 +426,16 @@ export type LayerDemRecordInput = { keywords?: (string | null) links?: LinkInput[] | null } -export type RecordDeletePartial = { +export type RecordDelete3D = { id: string type: RecordType } +export type RecordDeleteRaster = { + id: string + type: RecordType + approverName: string + approvalCode: string +} export type JobUpdateData = { parameters?: (any | null) status?: (string | null) @@ -504,7 +510,8 @@ mutateStartRasterIngestion="mutateStartRasterIngestion", mutateStartRasterUpdateGeopkg="mutateStartRasterUpdateGeopkg", mutateStart3DIngestion="mutateStart3DIngestion", mutateStartDemIngestion="mutateStartDemIngestion", -mutateDeleteLayer="mutateDeleteLayer", +mutateDelete3DLayer="mutateDelete3DLayer", +mutateDeleteRasterLayer="mutateDeleteRasterLayer", mutateUpdateJob="mutateUpdateJob", mutateJobAbort="mutateJobAbort", mutateJobRetry="mutateJobRetry", @@ -717,8 +724,11 @@ export const RootStoreBase = withTypedRefs()(MSTGQLStore mutateStartDemIngestion(variables: { data: IngestionDemData }, optimisticUpdate?: () => void) { return self.mutate<{ startDemIngestion: string }>(`mutation startDemIngestion($data: IngestionDemData!) { startDemIngestion(data: $data) }`, variables, optimisticUpdate) }, - mutateDeleteLayer(variables: { data: RecordDeletePartial }, optimisticUpdate?: () => void) { - return self.mutate<{ deleteLayer: string }>(`mutation deleteLayer($data: RecordDeletePartial!) { deleteLayer(data: $data) }`, variables, optimisticUpdate) + mutateDelete3DLayer(variables: { data: RecordDelete3D }, optimisticUpdate?: () => void) { + return self.mutate<{ delete3DLayer: string }>(`mutation delete3DLayer($data: RecordDelete3D!) { delete3DLayer(data: $data) }`, variables, optimisticUpdate) + }, + mutateDeleteRasterLayer(variables: { data: RecordDeleteRaster }, optimisticUpdate?: () => void) { + return self.mutate<{ deleteRasterLayer: string }>(`mutation deleteRasterLayer($data: RecordDeleteRaster!) { deleteRasterLayer(data: $data) }`, variables, optimisticUpdate) }, mutateUpdateJob(variables: { data: JobUpdateData, id: string }, optimisticUpdate?: () => void) { return self.mutate<{ updateJob: string }>(`mutation updateJob($data: JobUpdateData!, $id: String!) { updateJob(data: $data, id: $id) }`, variables, optimisticUpdate) diff --git a/src/discrete-layer/models/actionDispatcherStore.ts b/src/discrete-layer/models/actionDispatcherStore.ts index 2b06ef369..39533a52a 100644 --- a/src/discrete-layer/models/actionDispatcherStore.ts +++ b/src/discrete-layer/models/actionDispatcherStore.ts @@ -9,7 +9,7 @@ import CONTEXT_ACTIONS_CONFIG, { IContextActionGroup, IContextActions } from '.. export interface IDispatchAction { action: string; - data: Record; + data: Record; }; export type CombinedActionsType = (IEntityActions | IContextActions)[]; @@ -50,8 +50,8 @@ export const actionDispatcherStore = ModelBase const actions = self.actionsConfig?.find(entityActions => entityActions.entity === entity); return actions ?? undefined; }; - - function dispatchAction(action:IDispatchAction | undefined): void { + + function dispatchAction(action: IDispatchAction | undefined): void { self.action = cloneDeep(action); }; diff --git a/src/discrete-layer/models/mapMenusManagerStore.ts b/src/discrete-layer/models/mapMenusManagerStore.ts index e7ef6172e..92ccac6ae 100644 --- a/src/discrete-layer/models/mapMenusManagerStore.ts +++ b/src/discrete-layer/models/mapMenusManagerStore.ts @@ -103,8 +103,8 @@ export const mapMenusManagerStore = ModelBase const item: MenuItem = { action: {...groupOrAction}, - title: groupOrAction.titleTranslationId, - icon: groupOrAction.icon, + title: groupOrAction.title.translationId, + icon: groupOrAction.symbol.class ?? groupOrAction.symbol.icon, templateId: groupOrAction.templateId, disabled: !store.servicesAvailabilityStore.isActionAvailable(groupOrAction.action) }; diff --git a/src/discrete-layer/models/userStore.ts b/src/discrete-layer/models/userStore.ts index 96359060e..222e32bca 100644 --- a/src/discrete-layer/models/userStore.ts +++ b/src/discrete-layer/models/userStore.ts @@ -116,7 +116,7 @@ const ROLES: IRole[] = [ [UserAction.ENTITY_ACTION_VECTORBESTRECORD_EDIT]: {enabled: false}, [UserAction.ENTITY_ACTION_QUANTIZEDMESHBESTRECORD_EDIT]: {enabled: true}, [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_UPDATE]: {enabled: true}, - [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_DELETE]: {enabled: false}, + [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_DELETE]: {enabled: true}, [UserAction.ENTITY_ACTION_LAYER3DRECORD_DELETE]: {enabled: true}, [UserAction.ENTITY_ACTION_LAYERDEMRECORD_DELETE]: {enabled: false}, [UserAction.ENTITY_ACTION_LAYERRASTERRECORD_PUBLISH]: {enabled: true}, diff --git a/src/discrete-layer/views/components/action-resolver.component.tsx b/src/discrete-layer/views/components/action-resolver.component.tsx index d0aabf932..c8da3402e 100644 --- a/src/discrete-layer/views/components/action-resolver.component.tsx +++ b/src/discrete-layer/views/components/action-resolver.component.tsx @@ -206,40 +206,38 @@ export const ActionResolver: React.FC = observer((props) => break; case 'LayerRasterRecord.edit': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, LayerRasterRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, LayerRasterRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.EDIT); handleOpenEntityDialog(RecordType.RECORD_RASTER, true); break; case 'Layer3DRecord.edit': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, Layer3DRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, Layer3DRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.EDIT); handleOpenEntityDialog(RecordType.RECORD_3D, true); break; case 'LayerDemRecord.edit': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, LayerDemRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, LayerDemRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.EDIT); handleOpenEntityDialog(RecordType.RECORD_DEM, true); break; case 'VectorBestRecord.edit': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, VectorBestRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, VectorBestRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.EDIT); handleOpenEntityDialog(RecordType.RECORD_VECTOR, true); break; case 'QuantizedMeshBestRecord.edit': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, QuantizedMeshBestRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity( + data, + QuantizedMeshBestRecordModelKeys + ) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.EDIT); handleOpenEntityDialog(RecordType.RECORD_DEM, true); @@ -254,49 +252,51 @@ export const ActionResolver: React.FC = observer((props) => break; case 'LayerRasterRecord.flyTo': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, LayerRasterRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, LayerRasterRecordModelKeys) as unknown as LayerMetadataMixedUnion ); handleFlyTo(); break; case 'Layer3DRecord.flyTo': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, Layer3DRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, Layer3DRecordModelKeys) as unknown as LayerMetadataMixedUnion ); handleFlyTo(); break; case 'LayerDemRecord.flyTo': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, LayerDemRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, LayerDemRecordModelKeys) as unknown as LayerMetadataMixedUnion ); handleFlyTo(); break; case 'VectorBestRecord.flyTo': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, VectorBestRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity(data, VectorBestRecordModelKeys) as unknown as LayerMetadataMixedUnion ); handleFlyTo(); break; case 'QuantizedMeshBestRecord.flyTo': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, QuantizedMeshBestRecordModelKeys) as LayerMetadataMixedUnion + cleanUpEntity( + data, + QuantizedMeshBestRecordModelKeys + ) as unknown as LayerMetadataMixedUnion ); handleFlyTo(); break; case 'LayerRasterRecord.update': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, LayerRasterRecordModelKeys) as LayerMetadataMixedUnion, - // @ts-ignore - true + cleanUpEntity(data, LayerRasterRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.UPDATE); handleOpenEntityDialog(RecordType.RECORD_RASTER, true); break; + case 'LayerRasterRecord.delete': + store.discreteLayersStore.selectLayer( + cleanUpEntity(data, LayerRasterRecordModelKeys) as unknown as LayerMetadataMixedUnion + ); + store.discreteLayersStore.setSelectedLayerOperationMode(Mode.DELETE); + handleOpenEntityDialog(RecordType.RECORD_RASTER, true); + break; case 'Layer3DRecord.viewer': window.open( `${CONFIG.WEB_TOOLS_URL}/${CONFIG.MODEL_VIEWER_ROUTE}?model_ids=${data.productId}&token=${CONFIG.MODEL_VIEWER_TOKEN_VALUE}` @@ -304,11 +304,7 @@ export const ActionResolver: React.FC = observer((props) => break; case 'Layer3DRecord.delete': store.discreteLayersStore.selectLayer( - // @ts-ignore - cleanUpEntity(data, Layer3DRecordModelKeys) as LayerMetadataMixedUnion, - // @ts-ignore - false, - true + cleanUpEntity(data, Layer3DRecordModelKeys) as unknown as LayerMetadataMixedUnion ); store.discreteLayersStore.setSelectedLayerOperationMode(Mode.DELETE); handleOpenEntityDialog(RecordType.RECORD_3D, true); diff --git a/src/discrete-layer/views/discrete-layer-view.tsx b/src/discrete-layer/views/discrete-layer-view.tsx index 59bbe613b..c124a98e1 100644 --- a/src/discrete-layer/views/discrete-layer-view.tsx +++ b/src/discrete-layer/views/discrete-layer-view.tsx @@ -56,7 +56,6 @@ import { ExportLayerComponent } from '../components/export-layer/export-layer.co import ExportPolygonsRenderer from '../components/export-layer/export-polygons-renderer.component'; // import { Filters } from '../components/filters/filters'; import { JobsDialog } from '../components/job-manager/jobs.dialog'; -import { EntityDeleteDialog } from '../components/layer-details/entity.delete-dialog'; import { EntityDialog } from '../components/layer-details/entity.dialog'; import { EntityRasterDialog } from '../components/layer-details/raster/entity.raster.dialog'; import { LayersResults } from '../components/layers-results/layers-results'; @@ -84,12 +83,14 @@ import { ProductType } from '../models/ProductTypeEnum'; import { useStore } from '../models/RootStore'; import { FilterField } from '../models/RootStore.base'; import { UserAction, UserRole } from '../models/userStore'; +import { EntityDeleteRasterDialog } from '../components/layer-details/raster/entity.raster.delete-dialog'; import { ActionResolver } from './components/action-resolver.component'; import AppTitle from './components/app-title/app-title.component'; import { DetailsPanel } from './components/details-panel.component'; import { MapActionResolver } from './components/map-action-resolver.component'; import { TabViewsSwitcher } from './components/tabs-views-switcher.component'; import UserModeSwitch from './components/user-mode-switch/user-mode-switch.component'; +import { EntityDelete3DDialog } from '../components/layer-details/3D/entity.3d.delete-dialog'; import { TabViews } from './tab-views'; import '@material/tab-bar/dist/mdc.tab-bar.css'; @@ -145,7 +146,8 @@ const DiscreteLayerView: React.FC = observer(() => { const [is3DIngestDialogOpen, setIs3DIngestDialogOpen] = useState(false); const [isDemIngestDialogOpen, setIsDemIngestDialogOpen] = useState(false); const [isEntityDialogOpen, setIsEntityDialogOpen] = useState(false); - const [isEntityDeleteDialogOpen, setIsEntityDeleteDialogOpen] = useState(false); + const [isEntityDelete3DDialogOpen, setIsEntityDelete3DDialogOpen] = useState(false); + const [isEntityDeleteRasterDialogOpen, setIsDeleteRasterDialogOpen] = useState(false); const [isSystemsJobsDialogOpen, setIsSystemsJobsDialogOpen] = useState(false); const [isSystemCoreInfoDialogOpen, setIsSystemCoreInfoDialogOpen] = useState(false); const [isCreateEntityMenuOpen, setIsCreateEntityMenuOpen] = useState(false); @@ -497,12 +499,13 @@ const DiscreteLayerView: React.FC = observer(() => { [Mode.UPDATE]: setIsRasterDialogOpen, [Mode.EDIT]: setIsEntityDialogOpen, [Mode.VIEW]: setIsEntityDialogOpen, + [Mode.DELETE]: setIsDeleteRasterDialogOpen, }, [RecordType.RECORD_3D]: { [Mode.NEW]: setIs3DIngestDialogOpen, [Mode.EDIT]: setIsEntityDialogOpen, [Mode.VIEW]: setIsEntityDialogOpen, - [Mode.DELETE]: setIsEntityDeleteDialogOpen, + [Mode.DELETE]: setIsEntityDelete3DDialogOpen, }, [RecordType.RECORD_DEM]: { [Mode.NEW]: setIsDemIngestDialogOpen, @@ -1485,16 +1488,31 @@ const DiscreteLayerView: React.FC = observer(() => { )} {permissions.isDeleteAllowed && store.discreteLayersStore.selectedLayer && - isEntityDeleteDialogOpen && ( - { - setIsEntityDeleteDialogOpen(open); + setIsEntityDelete3DDialogOpen(open); onCloseDialog(); }} layerRecord={store.discreteLayersStore.selectedLayer} /> )} + {permissions.isDeleteAllowed && + store.discreteLayersStore.selectedLayer && + isEntityDeleteRasterDialogOpen && ( + { + setIsDeleteRasterDialogOpen(open); + onCloseDialog(); + }} + onSuccess={() => { + setCatalogRefresh(catalogRefresh + 1); + }} + layerRecord={store.discreteLayersStore.selectedLayer} + /> + )} {isSystemsJobsDialogOpen && (