Skip to content

Commit

Permalink
WIP Integrate geonode-mapstore-client dependency
Browse files Browse the repository at this point in the history
See also GeoNode/geonode-mapstore-client#1546

Need to link local geonode-mapstore-client and add the path to webpack's `@js` alias mapping within the geonode-project.

See the limitations in my comment GeoNode/geonode-mapstore-client#1546 (comment)
  • Loading branch information
ridoo committed Mar 15, 2024
1 parent dc26d93 commit ccd2808
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 31 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.env

__pycache__
__pycache__

geonode/geonode/static/mapstore/extensions/LitterAssessmentPlugin/
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends 'geonode-mapstore-client/_geonode_config.html' %}
{% block override_local_config %}
<script>
// https://geonode.org/geonode-mapstore-client/master/tutorial-03-override-local-config.html
(function() {

// Overriding or patching a component's cfg takes place in the context_processor.
Expand Down Expand Up @@ -186,9 +187,21 @@
overrideLocalConfig(localConfig, _)
}

localConfig.plugins.dataset_viewer.push({
name: "LitterAssessmentPlugin",
//disableIf: "{context.get(state('gnLayerResourceData'), 'subtype') !== 'raster'}"
// #######################################
// UI integration of litter assessment app

Object.keys(localConfig.plugins).forEach(pageName => {
if (["dataset_viewer"].includes(pageName)) {
localConfig.plugins[pageName].forEach(plugin => {
if (["ActionNavbar"].includes(plugin.name)) {
plugin.cfg.leftMenuItems.push({
type: "plugin",
name: "LitterAssessmentPlugin",
disableIf: "{context.get(state('gnResourceData'), 'subtype') !== 'raster'}"
});
}
});
}
});

// IMPORTANT!!! REMOVE AFTER DEVELOPMENT
Expand All @@ -198,6 +211,50 @@
// ensure that the http://localhost:8082 endpoint will not use proxy but direct requests
localConfig.proxyUrl.useCORS.push('http://localhost:8082');


// ###########################################
// UI integration of external applications app

Object.keys(localConfig.plugins).forEach(pageName => {
if (["catalogue"].includes(pageName)) {
localConfig.plugins[pageName].forEach(plugin => {
if (["ResourcesGrid"].includes(plugin.name)) {

const filtersFormItems = resourcesGridPluginFiltersFormItems
filtersFormItems[0].items.push({
id: "externalapplication",
labelId: "externalapplications.filter",
type: "filter"
});
localConfig.geoNodeCustomFilters = Object.assign(localConfig.geoNodeCustomFilters, {
"externalapplication": {
"filter{resource_type.in}": "externalapplication"
}
});

const menuItems = resourceGridPluginMenuItems
menuItems[0].items.push({
labelId: "externalapplications.register",
value: "externalapplication",
type: "link",
href: "/externalapplications/create",
authenticated: true,
perms: [
{
"type": "user",
"value": "add_resource"
}
]
});

plugin.cfg.menuItems = menuItems;
plugin.cfg.filtersFormItems = filtersFormItems
}
});
}
});

{% comment %}
var pluginPageName = "catalogue";
var selectedPluginName = "ResourcesGrid";

Expand Down Expand Up @@ -239,6 +296,7 @@
plugin.cfg.menuItems = menuItems;
}
}
{% endcomment %}

return localConfig;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"locale": "en-US",
"messages": {
"extension": {
"message": "Message!"
"litterassessment": {
"title": "Litter Assessment"
}
}
}

This file was deleted.

255 changes: 241 additions & 14 deletions geonode/apps/litter_assessment/client/js/extension/plugins/Extension.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,252 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
/*
* Copyright 2020, 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, { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Message from "@mapstore/framework/components/I18N/Message";
import '@js/extension/assets/style.css';
import { createSelector } from 'reselect';
import { findIndex } from 'lodash';
import { Glyphicon } from 'react-bootstrap';
import { createPlugin } from '@mapstore/framework/utils/PluginsUtils';
import { setControlProperty } from '@mapstore/framework/actions/controls';
import Message from '@mapstore/framework/components/I18N/Message';
import controls from '@mapstore/framework/reducers/controls';
import Button from '@js/components/Button';
import { mapInfoSelector } from '@mapstore/framework/selectors/map';
import { layersSelector } from '@mapstore/framework/selectors/layers';
import OverlayContainer from '@js/components/OverlayContainer';
import {
isNewResource,
getResourceId,
getCompactPermissions,
canManageResourcePermissions,
getResourceData,
getViewedResourceType
} from '@js/selectors/resource';
import { updateResourceCompactPermissions } from '@js/actions/gnresource';
import Permissions from '@js/components/Permissions';
import { getUsers, getGroups, getResourceTypes } from '@js/api/geonode/v2';
import { resourceToPermissionEntry, availableResourceTypes, getResourcePermissions, cleanUrl, getDownloadUrlInfo } from '@js/utils/ResourceUtils';
import SharePageLink from '@js/plugins/share/SharePageLink';
import { getCurrentResourcePermissionsLoading } from '@js/selectors/resourceservice';

const entriesTabs = [
{
id: 'user',
labelId: 'gnviewer.users',
request: ({ entries, groups, ...params }) => {
const exclude = entries.filter(({ type }) => type === 'user').map(({ id }) => id);
return getUsers({
...params,
'filter{-pk.in}': [...exclude, -1],
'filter{is_superuser}': false
});
},
responseToEntries: ({ response, entries }) => {
return response?.users.map(user => {
const { permissions } = entries.find(entry => entry.id === user.pk) || {};
return {
...resourceToPermissionEntry('user', user),
permissions
};
});
}
},
{
id: 'group',
labelId: 'gnviewer.groups',
request: ({ entries, groups, ...params }) => {
const excludeEntries = entries.filter(({ type }) => type === 'group').map(({ id }) => id);
const excludeGroups = groups.map(({ id }) => id);
const exclude = [
...(excludeEntries || []),
...(excludeGroups || [])
];
return getGroups({
...params,
'filter{-group.pk.in}': exclude
});
},
responseToEntries: ({ response, entries }) => {
return response?.groups.map(group => {
const { permissions } = entries.find(entry => entry.id === group.group.pk) || {};
return {
...resourceToPermissionEntry('group', group),
permissions
};
});
}
}
];
function LitterAssessment({
enabled,
resourceId,
compactPermissions,
layers,
onChangePermissions,
enableGeoLimits,
onClose,
canEdit,
permissionsLoading,
resourceType,
embedUrl,
downloadUrl
}) {

const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);

const [permissionsObject, setPermissionsObject] = useState({});
useEffect(() => {
getResourceTypes().then((data) => {
const resourceIndex = findIndex(data, { name: resourceType });
let responseOptions;
if (resourceIndex !== - 1) {
responseOptions = getResourcePermissions(data[resourceIndex].allowed_perms.compact);
} else { // set a default permission object
responseOptions = getResourcePermissions(data[0].allowed_perms.compact);
}
if (isMounted.current) {
setPermissionsObject(responseOptions);
}
});
}, [availableResourceTypes]);

const pageUrl = cleanUrl(window.location.href);


function Extension() {
return (
<div className="extension">
<Message msgId="extension.message" />
</div>
<OverlayContainer
enabled={enabled}
className="gn-overlay-wrapper"
>
<section
className="gn-share-panel"
>

Hello Litter Assessment
{/* <div className="gn-share-panel-head">
<h2><Message msgId="gnviewer.shareThisResource" /></h2>
<Button className="square-button" onClick={() => onClose()}>
<Glyphicon glyph="1-close" />
</Button>
</div>
<div className="gn-share-panel-body">
<SharePageLink url={pageUrl} label={<Message msgId="gnviewer.thisPage" />} />
<SharePageLink url={embedUrl} label={<Message msgId={`gnviewer.embed${resourceType}`} />} />
{(resourceType === 'document' && !!downloadUrl) && <SharePageLink url={downloadUrl} label={<Message msgId={`gnviewer.directLink`} />} />}
{canEdit && <>
<Permissions
compactPermissions={compactPermissions}
layers={layers} entriesTabs={entriesTabs}
onChange={onChangePermissions}
enableGeoLimits={enableGeoLimits}
resourceId={resourceId}
loading={permissionsLoading}
permissionOptions={permissionsObject}
/>
</>}
</div> */}
</section>
</OverlayContainer>
);
}

const ConnectedExtension = connect(() => ({}))(Extension);
LitterAssessment.propTypes = {
resourceId: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
enabled: PropTypes.bool,
onClose: PropTypes.func
};

export default {
name: __MAPSTORE_EXTENSION_CONFIG__.name,
component: ConnectedExtension,
reducers: {},
epics: {},
containers: {}
LitterAssessment.defaultProps = {
resourceId: null,
enabled: false,
onClose: () => {}
};

const LitterAssessmentPlugin = connect(
createSelector([
state => state?.controls?.rightOverlay?.enabled === 'LitterAssessment',
getResourceId,
mapInfoSelector,
getCompactPermissions,
layersSelector,
canManageResourcePermissions,
getCurrentResourcePermissionsLoading,
getResourceData,
getViewedResourceType
], (enabled, resourceId, mapInfo, compactPermissions, layers, canEdit, permissionsLoading, resource, type) => ({
enabled,
resourceId: resourceId || mapInfo?.id,
compactPermissions,
layers,
canEdit,
permissionsLoading,
embedUrl: resource?.embed_url,
resourceType: type,
downloadUrl: getDownloadUrlInfo(resource)?.url
})),
{
onClose: setControlProperty.bind(null, 'rightOverlay', 'enabled', false),
onChangePermissions: updateResourceCompactPermissions
}
)(LitterAssessment);

function LitterAssessmentButton({
enabled,
variant,
onClick,
size
}) {
return enabled
? <Button
variant={variant || "primary"}
size={size}
onClick={() => onClick()}
>
<Message msgId="litterassessment.title"/>
</Button>
: null
;
}

const ConnectedLitterAssessmentButton = connect(
createSelector(
isNewResource,
getResourceId,
mapInfoSelector,
(isNew, resourceId, mapInfo) => ({
enabled: !isNew && (resourceId || mapInfo?.id)
})
),
{
onClick: setControlProperty.bind(null, 'rightOverlay', 'enabled', 'Share')
}
)((LitterAssessmentButton));

export default createPlugin(__MAPSTORE_EXTENSION_CONFIG__.name, {
component: LitterAssessmentPlugin,
containers: {
ActionNavbar: {
name: __MAPSTORE_EXTENSION_CONFIG__.name,
Component: ConnectedLitterAssessmentButton
}
},
epics: {},
reducers: {
controls
}
});
3 changes: 2 additions & 1 deletion geonode/apps/litter_assessment/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"test:watch": "mapstore-project test:watch extension"
},
"devDependencies": {
"@mapstore/project": "1.0.27"
"@mapstore/project": "1.0.27",
"geonode-mapstore-client": "/home/ridoo/data/coding/projects/52n_geonode/geonode-mapstore-client/geonode_mapstore_client/client"
},
"dependencies": {
"mapstore": "git+https://github.com/geosolutions-it/MapStore2.git#2022.02.xx"
Expand Down

0 comments on commit ccd2808

Please sign in to comment.