geosolutions-it / geonode-mapstore-client

MapStore Client for GeoNode
Other
2 stars 131 forks source link

Design and plan the development for the new (React based SPA) client #187

Closed giohappy closed 3 years ago

giohappy commented 4 years ago

The following high level plan refers to the activities discussed inside "Ideas Round 2" for the new GeoNode Client.

The first steps for the new client includes:

@allyoucanmap and @afabiani please liaise and write down the list of steps to bootstrap the work. @afabiani I've merged the upstream master here. Let's create a new branch for the new client

allyoucanmap commented 4 years ago

@giohappy @tdipisa @afabiani

I would like to ask to @mbarto, @offtherailz and @MV88 some feedbacks on the whole app setup described in this comment because it include an sdk proposal for MapStore.

Below some notes about the design and setup of the SPA. I worked on this branch https://github.com/geosolutions-it/geonode-mapstore-client/tree/spa-setup

App Setup

This branch contains a first setup for the SPA GeoNode MapStore client.

The folder structure is the same used by MapStore API with some additional files and directories:

Routing

The proposed routing of the SPA for this phase is the hash path. The hash path makes each html template containing a single application independent and it does not interfere with the current paths in django. We should use the hash path as much as possible to store information of current state or filters, this will help the browser navigation and it adds the possibility to directly share url.

Here an example of possible structure (*):

(*) This is a proposal, real paths need to be evaluated while developing. (**) we could use directly the type instead of the path /builder/ for the root html template and isolate each builder context (eg /map/#/view/:id). Check if possible with all MapStore context: dashboard, geostory and map.

Note: the test branch uses /spa/ as root path for testing purpose

Use of MapStore SDK in the geonode routes

MapStore should provide via SDK pure react component (eg Map), utils and plugins. We needs also plugins because some functionality are stricly related to the redux state and epics. To support all the functionality of the plugins we need to wrap the whole application in a redux Provider and add support for epics and use the PluginsContainer as root of the plugin context. These are the investigated possible way we could include mapstore using a sdk inside a route page:

image

image

image

Following the requests for the SPA homepage we should provide at least following modules (this does not coult app component listed above):

<Map
    {...props} >
  <Draw
    {...drawProps}
    active={active}
    geometryType={geometryType}
    onChange={(...args) => console.log(args)}
  />
</Map>

Probably also all the utils, actions, epics and reducers related to plugins and component must be included in the SDK.

General issues encountered in current components of MapStore:

Bundle size - use lazy plugins (experimental proposal)

Current setup of mapstore expect that all used plugins are imported directly in the root of the app. Using this approach we load directly all the epics and reducers at the initialization of the app. This could increase the size of the bundle if we import all the plugin of mapsore in the app. We should evaluate the implementation of a way to load a complete plugin asynchronously including its own reducers and epics. In this way we can create an index js with all the available plugins of MapStore made of dynamic import(). While playing around with this repository I tried to apply the load workflow of extensions to the loading of plugin imported dynamically. So I ended up combining a loading workflow for lazy plugins in this react hook .

There are still some issues in this load workflow:

so current implementation has still inconsistent behaviours.

@mbarto What do you think about this proposal? Do you think we could implement something that could load plugins as discribed above?

If this approach can't be applied we could import plugins as usual.

UI/UX Design (homepage and theme)

https://github.com/geosolutions-it/geonode-mapstore-client/issues/188

Error boundaries component

// TODO

Loader component

// TODO

Empty view component

// TODO

Extend frontend application

We need to discuss this section between backend and frontend

mbarto commented 4 years ago

@allyoucanmap I think your proposal is going in the right direction. I agree with most of your thinking.

About lazy plugins loading, did you have a look at this example: https://github.com/geosolutions-it/MapStore2/tree/master/web/client/examples/lazyplugins ?

That one is where I started implementing the ability to dinamically load and activate a whole plugin on demand (reducers and epics included). Don't consider the product simplified implementation (everything loaded at startup) as the state of the art.

Extensions are not loaded as lazy plugins by the product, but the underlying SDK should be already able to do so (if you find any issue with that we can work together to make it fully functional for your use case).

Another point I would like to discuss in depth will all of you is how to slice properly the actual MapStore2 big monster into separated entities (and how many slices are needed to make everyone happy (@giohappy included of course)).

These are my candidates:

Obviously, I am not saying we need all 4 slices, but we at least need to know they should logically exist and how to better organize them in physical repositories / packages.

reminders[bot] commented 4 years ago

:wave: @giohappy, answer Florian

allyoucanmap commented 4 years ago

About lazy plugins loading, did you have a look at this example: https://github.com/geosolutions-it/MapStore2/tree/master/web/client/examples/lazyplugins ?

That one is where I started implementing the ability to dinamically load and activate a whole plugin on demand (reducers and epics included). Don't consider the product simplified implementation (everything loaded at startup) as the state of the art.

@mbarto

Lazy plugins

Yes, I took a look to that code and the one related to extensions while implementing the useLazyPlugins hook helper. I focused the implementation trying to use an existing function augmentStore (just noticed you used updateStore instead) and import only the plugins declared in the config of the selected route (the solution provided in the lazy plugings example imports dynamically all the plugin index). Here what I tried to achieve:

const plugins = {
    TestPlugin: () => import(/* webpackChunkName: 'plugins/test-plugin' */ '@js/plugins/Test'),
    // ... others
};

if possible it would be nice that specific root reducers transformations will be handled by the plugin itself.

eg. the mapState manipulations should be included in the Map.jsx plugin and applied only if the map plugin is requested but currently they are in the StandardStore.jsx

// this code is currently in the StandardStore of MapStore
// https://github.com/geosolutions-it/MapStore2/blob/53e8f3905b26fb374d93799481abc09e47afc259/web/client/stores/StandardStore.js#L64-L74
let mapState = createHistory(LayersUtils.splitMapAndLayers(mapConfig(state, action)));
let newState = {
    ...allReducers(state, action),
    map: mapState && mapState.map ? map(mapState.map, action) : null,
    mapInitialConfig: mapState && mapState.mapInitialConfig || mapState && mapState.loadingError && {
        loadingError: mapState.loadingError,
        mapId: mapState.loadingError.mapId
    } || null,
    mapConfigRawData: mapState && mapState.mapConfigRawData || null,
    layers: mapState ? layers(mapState.layers, action) : null
};

I tried to apply some fixes with overrides and function wrappers but the results were not consistent due to asynch updates. This experiment was mainly a transposition of the bahaviors of extensions to plugins.

SDK structure

Another point I would like to discuss in depth will all of you is how to slice properly the actual MapStore2 big monster into separated entities (and how many slices are needed to make everyone happy (@giohappy included of course)).

There was a discussion about the struture of SDK internally with @offtherailz and @MV88. These were the first proposal (summary):

I have not a preference on the structure of SDK because I think the two proposals are really similar but I agree that we should find an agreement on the structure.

mbarto commented 4 years ago

@allyoucanmap I agree your work is a step further than the lazy-plugins example, I just wanted to check if you think the basic machinery needs any improvement to implement your ideas.

About the redurcers in StandardStore, currently only a very limited set is included there, but we can decide to move also those ones into plugins definitions.

The idea driving the inclusion was: map and layers state is needed in every MapStore application, but now this is not always true: we also want the ability to build pages without any map on it, so it makes sense to move forward and remove the standard reducers from StandardStore. Remember that plugins reducers list is a sort of dependency list, so we should list them not only in Map, but in all the plugins that use them (connect to their state via redux or hooks).