/*
* Copyright 2021, 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 { createPlugin, getMonitoredState } from '@mapstore/framework/utils/PluginsUtils';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { getConfigProp } from '@mapstore/framework/utils/ConfigUtils';
import DetailsPanel from '@js/components/DetailsPanel';
import { userSelector } from '@mapstore/framework/selectors/security';
import {
editTitleResource,
editAbstractResource,
editThumbnailResource,
setFavoriteResource,
setMapThumbnail,
setResourceThumbnail,
enableMapThumbnailViewer,
setResourceExtent
} from '@js/actions/gnresource';
import { processingDownload } from '@js/selectors/resourceservice';
import FaIcon from '@js/components/FaIcon/FaIcon';
import controls from '@mapstore/framework/reducers/controls';
import { setControlProperty } from '@mapstore/framework/actions/controls';
import gnresource from '@js/reducers/gnresource';
import {
canEditResource,
isNewResource,
getResourceId,
isThumbnailChanged,
updatingThumbnailResource,
getViewedResourceType
} from '@js/selectors/resource';
import Button from '@js/components/Button';
import useDetectClickOut from '@js/hooks/useDetectClickOut';
import OverlayContainer from '@js/components/OverlayContainer';
import { withRouter } from 'react-router';
import { hashLocationToHref } from '@js/utils/SearchUtils';
import Message from '@mapstore/framework/components/I18N/Message';
import { layersSelector } from '@mapstore/framework/selectors/layers';
import { mapSelector } from '@mapstore/framework/selectors/map';
import { parsePluginConfigExpressions } from '@js/utils/MenuUtils';
import usePluginItems from '@js/hooks/usePluginItems';
import { getResourceTypesInfo } from '@js/utils/ResourceUtils';
import tabComponents from '@js/plugins/detailviewer/tabComponents';
const ConnectedDetailsPanel = connect(
createSelector([
state => state?.gnresource?.data || null,
state => state?.gnresource?.loading || false,
state => state?.gnresource?.data?.favorite || false,
state => state?.gnsave?.savingThumbnailMap || false,
layersSelector,
isThumbnailChanged,
updatingThumbnailResource,
mapSelector,
state => state?.gnresource?.showMapThumbnail || false,
processingDownload
], (resource, loading, favorite, savingThumbnailMap, layers, thumbnailChanged, resourceThumbnailUpdating, mapData, showMapThumbnail, downloading) => ({
layers: layers,
resource,
loading,
savingThumbnailMap,
favorite: favorite,
isThumbnailChanged: thumbnailChanged,
resourceThumbnailUpdating,
initialBbox: mapData?.bbox,
enableMapViewer: showMapThumbnail,
downloading,
resourceId: resource.pk,
tabComponents
})),
{
closePanel: setControlProperty.bind(null, 'rightOverlay', 'enabled', false),
onFavorite: setFavoriteResource,
onMapThumbnail: setMapThumbnail,
onResourceThumbnail: setResourceThumbnail,
onClose: enableMapThumbnailViewer,
onSetExtent: setResourceExtent
}
)(DetailsPanel);
const ButtonViewer = ({ onClick, hide, variant, size, showMessage, resourceType }) => {
const handleClickButton = () => {
onClick();
};
const { icon = 'info-circle' } = getResourceTypesInfo()[resourceType] || {};
return !hide ? (
<Button
variant={variant}
size={size}
onClick={handleClickButton}
>
{!showMessage ? <FaIcon name={icon} /> : <Message msgId="gnviewer.viewInfo"/>}
</Button>
) : null;
};
const ConnectedButton = connect(
createSelector([isNewResource, getResourceId, getViewedResourceType], (isNew, resourcePk, resourceType) => ({
hide: isNew || !resourcePk,
resourceType
})),
{
onClick: setControlProperty.bind(
null,
'rightOverlay',
'enabled',
'DetailViewer'
)
}
)((ButtonViewer));
/**
* @module DetailViewer
*/
/**
* render a panel for detail information about a resource inside the viewer pages
* @name DetailViewer
* @prop {array} tabs array of tab object representing the structure of the displayed info properties
* @example
* {
* "name": "DetailViewer",
* "cfg": {
* "tabs": [
* {
* "type": "tab",
* "id": "info",
* "labelId": "gnviewer.info",
* "items": [
* {
* "type": "text",
* "labelId": "gnviewer.title",
* "value": "{context.get(state('gnResourceData'), 'title')}"
* },
* {
* "type": "link",
* "labelId": "gnviewer.owner",
* "href": "{'/people/profile/' + context.get(state('gnResourceData'), 'owner.username')}",
* "value": "{context.getUserResourceName(context.get(state('gnResourceData'), 'owner'))}",
* "disableIf": "{!context.get(state('gnResourceData'), 'owner.username')}"
* },
* {
* "type": "date",
* "format": "MMMM Do YYYY",
* "labelId": "gnviewer.published",
* "value": "{context.get(state('gnResourceData'), 'date')}"
* },
* {
* "type": "query",
* "labelId": "gnviewer.resourceType",
* "value": "{context.get(state('gnResourceData'), 'resource_type')}",
* "pathname": "/",
* "query": {
* "f": "{context.get(state('gnResourceData'), 'resource_type')}"
* }
* },
* {
* "type": "html",
* "labelId": "gnviewer.supplementalInformation",
* "value": "{context.get(state('gnResourceData'), 'supplemental_information')}"
* }
* ]
* }
* ]
* }
* }
*/
function DetailViewerPanel({
location,
enabled,
onEditResource,
onEditAbstractResource,
onEditThumbnail,
canEdit,
user,
onClose,
monitoredState,
queryPathname = '/',
tabs = [],
items,
resourceId
}, context) {
const parsedConfig = parsePluginConfigExpressions(monitoredState, { tabs });
const handleTitleValue = (val) => {
onEditResource(val);
};
const handleAbstractValue = (val) => {
onEditAbstractResource(val);
};
const handleEditThumbnail = (val) => {
onEditThumbnail(val, true);
};
const node = useDetectClickOut({
disabled: !enabled,
onClickOut: () => {
onClose();
}
});
const handleFormatHref = (options) => {
return hashLocationToHref({
location,
...options
});
};
const { loadedPlugins } = context;
const configuredItems = usePluginItems({ items, loadedPlugins }, [resourceId]);
const toolbarItems = configuredItems.filter(item => item.target === "toolbar");
return (
<OverlayContainer
enabled={enabled}
ref={node}
className="gn-overlay-wrapper"
>
<ConnectedDetailsPanel
editTitle={handleTitleValue}
editAbstract={handleAbstractValue}
editThumbnail={handleEditThumbnail}
activeEditMode={enabled && canEdit}
enableFavorite={!!user}
formatHref={handleFormatHref}
tabs={parsedConfig.tabs}
pathname={queryPathname}
toolbarItems={toolbarItems}
/>
</OverlayContainer>
);
}
const DetailViewer = ({hide, ...props}) => hide ? null : <DetailViewerPanel {...props}/>;
const DetailViewerPlugin = connect(
createSelector(
[
(state) =>
state?.controls?.rightOverlay?.enabled === 'DetailViewer',
canEditResource,
isNewResource,
getResourceId,
userSelector,
state => getMonitoredState(state, getConfigProp('monitorState')),
state => state?.gnresource?.data || null
],
(enabled, canEdit, isNew, resourcePk, user, monitoredState, resource) => ({
enabled,
canEdit,
hide: isNew || !resourcePk,
user,
monitoredState,
resourceId: resource?.pk
})
),
{
onEditResource: editTitleResource,
onEditAbstractResource: editAbstractResource,
onEditThumbnail: editThumbnailResource,
onClose: setControlProperty.bind(null, 'rightOverlay', 'enabled', false)
}
)(withRouter(DetailViewer));
export default createPlugin('DetailViewer', {
component: DetailViewerPlugin,
containers: {
ActionNavbar: {
name: 'DetailViewerButton',
Component: ConnectedButton
}
},
reducers: {
gnresource,
controls
}
});