Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions geonode_mapstore_client/client/js/api/geonode/v2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,46 @@ export const getDatasets = ({
});
};

export const getDocuments = ({
q,
pageSize = 10,
page = 1,
sort,
f,
customFilters = [],
config,
...params
}) => {
const _params = {
...getQueryParams({...params, f}, customFilters),
...(q && {
search: q,
search_fields: ['title', 'abstract']
}),
...(sort && { sort: isArray(sort) ? sort : [ sort ]}),
page,
page_size: pageSize,
'filter{resource_type.in}': 'document'
};
return axios
.get(
getEndpointUrl(DOCUMENTS), {
params: _params,
...config,
...paramsSerializer()
})
.then(({ data }) => {
return {
total: data.total,
isNextPageAvailable: !!data.links.next,
resources: (data.documents || [])
.map((resource) => {
return resource;
})
};
});
};

export const getDocumentsByDocType = (docType = 'image', {
q,
pageSize = 20,
Expand Down
62 changes: 54 additions & 8 deletions geonode_mapstore_client/client/js/epics/gnresource.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,12 +613,33 @@ export const closeInfoPanelOnMapClick = (action$, store) => action$.ofType(CLICK
.switchMap(() => Observable.of(setControlProperty('rightOverlay', 'enabled', false)));


// Check which control is enabled between annotations and datasetsCatlog
// Check which control is enabled between annotations, datasetsCatalog and documentsCatalog
const oneOfTheOther = (control) => {
if (control === 'rightOverlay') return null;

// Handle three-way alternates
if (control === 'annotations') {
return {
control,
alternates: ['datasetsCatalog', 'documentsCatalog']
};
}
if (control === 'datasetsCatalog') {
return {
control,
alternates: ['annotations', 'documentsCatalog']
};
}
if (control === 'documentsCatalog') {
return {
control,
alternates: ['annotations', 'datasetsCatalog']
};
}

return {
control,
alternate: control === 'annotations' ? 'datasetsCatalog' : 'annotations'
alternates: []
};
};

Expand All @@ -635,14 +656,18 @@ export const closeOpenPanels = (action$, store) => action$.ofType(SET_CONTROL_PR
setActions.push(purgeMapInfoResults(), closeIdentify());
}
const isDatasetCatalogPanelOpen = get(state, "controls.datasetsCatalog.enabled");
const isDocumentsCatalogPanelOpen = get(state, "controls.documentsCatalog.enabled");
const isCatalogOpen = get(state, "controls.metadataexplorer.enabled");
const isVisualStyleEditorOpen = get(state, "controls.visualStyleEditor.enabled");
if ((isDatasetCatalogPanelOpen || isVisualStyleEditorOpen) && isCatalogOpen) {
if ((isDatasetCatalogPanelOpen || isDocumentsCatalogPanelOpen || isVisualStyleEditorOpen) && isCatalogOpen) {
setActions.push(catalogClose());
}
if (isDatasetCatalogPanelOpen && isVisualStyleEditorOpen) {
setActions.push(setControlProperty('datasetsCatalog', 'enabled', false));
}
if (isDocumentsCatalogPanelOpen && isVisualStyleEditorOpen) {
setActions.push(setControlProperty('documentsCatalog', 'enabled', false));
}
const isResourceDetailsOpen = !action.show && getShowDetails(state);
if (isResourceDetailsOpen) {
setActions.push(setShowDetails(false));
Expand All @@ -651,8 +676,13 @@ export const closeOpenPanels = (action$, store) => action$.ofType(SET_CONTROL_PR
if (control?.control || action.show) {
if (state.controls?.rightOverlay?.enabled === 'Share') {
setActions.push(setControlProperty('rightOverlay', 'enabled', false));
} else if (!!state.controls?.[`${control.alternate}`]?.enabled) {
setActions.push(setControlProperty(`${control.alternate}`, 'enabled', false));
} else {
// Close all alternate panels
control.alternates?.forEach(alternate => {
if (state.controls?.[alternate]?.enabled) {
setActions.push(setControlProperty(alternate, 'enabled', false));
}
});
}
}
return setActions;
Expand All @@ -662,12 +692,28 @@ export const closeOpenPanels = (action$, store) => action$.ofType(SET_CONTROL_PR
});

/**
* Close dataset panels on map info panel open
* Close dataset and documents panels on map info panel open
*/
export const closeDatasetCatalogPanel = (action$, store) => action$.ofType(NEW_MAPINFO_REQUEST)
.filter(() => isMapInfoOpen(store.getState()) && get(store.getState(), "controls.datasetsCatalog.enabled"))
.filter(() => {
const state = store.getState();
return isMapInfoOpen(state) &&
(get(state, "controls.datasetsCatalog.enabled") ||
get(state, "controls.documentsCatalog.enabled"));
})
.switchMap(() => {
return Observable.of(setControlProperty('datasetsCatalog', 'enabled', false));
const state = store.getState();
const actions = [];

if (get(state, "controls.datasetsCatalog.enabled")) {
actions.push(setControlProperty('datasetsCatalog', 'enabled', false));
}

if (get(state, "controls.documentsCatalog.enabled")) {
actions.push(setControlProperty('documentsCatalog', 'enabled', false));
}

return Observable.of(...actions);
});

export const closeResourceDetailsOnMapInfoOpen = (action$, store) => action$.ofType(NEW_MAPINFO_REQUEST)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright 2025, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import React, { forwardRef } from 'react';
import { Dropdown, MenuItem } from 'react-bootstrap';

import Message from '@mapstore/framework/components/I18N/Message';
import Spinner from '@mapstore/framework/components/layout/Spinner';
import FlexBox from '@mapstore/framework/components/layout/FlexBox';
import Text from '@mapstore/framework/components/layout/Text';
import Menu from '@mapstore/framework/plugins/ResourcesCatalog/components/Menu';

const ResourcesMenu = forwardRef(({
menuItems,
style,
totalResources,
loading,
orderConfig,
query,
titleId,
theme = 'main',
menuItemsLeft = [],
target,
resourcesFoundMsgId,
onSortChange
}, ref) => {


const {
defaultLabelId,
options: orderOptions = [],
variant: orderVariant,
align: orderAlign = 'right'
} = orderConfig || {};

const selectedSort = orderOptions.find(({ value }) => query?.sort === value);

const orderButtonNode = orderOptions.length > 0 &&
<Dropdown pullRight={orderAlign === 'right'} id="sort-dropdown">
<Dropdown.Toggle
bsStyle={orderVariant || 'default'}
bsSize="sm"
noCaret
>
<Message msgId={selectedSort?.labelId || defaultLabelId} />
</Dropdown.Toggle>
<Dropdown.Menu>
{orderOptions.map(({ labelId, value }) => {
return (
<MenuItem
key={value}
active={value === selectedSort?.value}
onClick={(e) => {
if (onSortChange) {
e.preventDefault();
onSortChange(value);
}
}}
>
<Message msgId={labelId} />
</MenuItem>
);
})}
</Dropdown.Menu>
</Dropdown>;

return (
<FlexBox
ref={ref}
classNames={[
'ms-resources-menu',
'_sticky',
'_corner-tl',
`ms-${theme}-colors`,
'_padding-tb-sm'
]}
column
gap="sm"
style={style}
>
{titleId
? <Text fontSize="lg">
<Message msgId={titleId} />
</Text>
: null}
<FlexBox centerChildrenVertically gap="xs">
<FlexBox.Fill flexBox centerChildrenVertically gap="sm">
{menuItemsLeft.map(({ Component, name }) => {
return (<Component key={name} query={query} />);
})}
{orderAlign === 'left' ? orderButtonNode : null}
<Text fontSize="sm" ellipsis>
{loading
? <Spinner />
: <span><span>{totalResources}</span>{" "}<Message msgId={resourcesFoundMsgId} msgParams={{ count: totalResources }} /></span>
}
</Text>
</FlexBox.Fill>
<Menu
items={menuItems}
containerClass={`ms-menu-list`}
size="md"
alignRight
target={target}
/>
{orderAlign === 'right' ? orderButtonNode : null}
</FlexBox>
</FlexBox>
);
});

ResourcesMenu.defaultProps = {
orderOptions: [
{
label: 'Most recent',
labelId: 'resourcesCatalog.mostRecent',
value: '-date'
},
{
label: 'Less recent',
labelId: 'resourcesCatalog.lessRecent',
value: 'date'
},
{
label: 'A Z',
labelId: 'resourcesCatalog.aZ',
value: 'title'
},
{
label: 'Z A',
labelId: 'resourcesCatalog.zA',
value: '-title'
},
{
label: 'Most popular',
labelId: 'resourcesCatalog.mostPopular',
value: 'popular_count'
}
],
defaultLabelId: 'resourcesCatalog.orderBy'
};

export default ResourcesMenu;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2026, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

export const GEONODE_DOCUMENTS_ROW_VIEWER = 'GEONODE_DOCUMENTS_ROW_VIEWER';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2026, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import MediaViewer from '@js/components/MediaViewer';
import Text from '@mapstore/framework/components/layout/Text';
import FlexBox from '@mapstore/framework/components/layout/FlexBox';

const DocumentInfoViewer = (resource) => {
return (<FlexBox className="gn-document-info-viewer" column gap="sm">
<Text fontSize="md">{resource?.title}</Text>
<MediaViewer resource={resource} />
</FlexBox>);
};

export default DocumentInfoViewer;
Loading