Skip to content

Commit

Permalink
Using Chartlets 0.0.28; generating developer reference
Browse files Browse the repository at this point in the history
  • Loading branch information
forman committed Nov 26, 2024
1 parent 4a7ee41 commit e0bd8bd
Show file tree
Hide file tree
Showing 14 changed files with 215 additions and 79 deletions.
17 changes: 16 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
## Changes in version 1.4.0 (in development)

### New Features

* Starting with xcube Server 1.8 and xcube Viewer it is possible to enhance
the viewer UI by _server-side contributions_ programmed in Python.
For this to work, service providers can now configure xcube Server to load
one or more Python modules that provide UI-contributions of type
`xcube.webapi.viewer.contrib.Panel`.
Users can create `Panel` objects and use the two decorators
`layout()` and `callback()` to implement the UI and the interaction
behaviour, respectively. The new functionality is provided by the
[Chartlets](https://bcdev.github.io/chartlets/) Python library.

A working example can be found in the
[xcube repository](https://github.com/xcube-dev/xcube/tree/5ebf4c76fdccebdd3b65f4e04218e112410f561b/examples/serve/panels-demo).

### Other changes

* Updated dependencies, development dependencies and updated TypeScript code base accordingly.
Expand All @@ -8,7 +23,7 @@
- `mui` from v5 to v6
- `testing-library/react` from v12 to v16

* Tooltips (incl. translation) have been added to the toogle buttons that
* Tooltips (incl. translation) have been added to the toggle buttons that
control the format of the metadata information in the sidebar.

## Changes in version 1.3.1
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"classnames": "^2.2.6",
"codemirror": "^6.0.1",
"color-rgba": "^2.2.3",
"chartlets": "0.0.27",
"chartlets": "0.0.28",
"date-fns": "^2.29.3",
"fast-memoize": "^2.5.2",
"fflate": "^0.7.4",
Expand Down
25 changes: 25 additions & 0 deletions public/docs/dev-reference.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Server-side UI Contributions

Starting with xcube Server 1.8 and xcube Viewer it is possible to enhance
the viewer UI by _server-side contributions_ programmed in Python.
For this to work, service providers can now configure xcube Server to load
one or more Python modules that provide UI-contributions of type
`xcube.webapi.viewer.contrib.Panel`.
Users can create `Panel` objects and use the two decorators
`layout()` and `callback()` to implement the UI and the interaction
behaviour, respectively. The new functionality is provided by the
[Chartlets](https://bcdev.github.io/chartlets/) Python library.

A working example can be found in the
[xcube repository](https://github.com/xcube-dev/xcube/tree/5ebf4c76fdccebdd3b65f4e04218e112410f561b/examples/serve/panels-demo).

## Available State Properties

In the following, xcube Viewer's state properties are listed.
These properties can be accessed in the input and state channels you pass
to the `layout()` and `callback()` decorators. To trigger a callback
call when a state property changes use the input syntax
`Input("@app", <property>)`. To just read a property from the state use
`State("@app", <property>)`.

${text}
46 changes: 7 additions & 39 deletions src/actions/dataActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { Action, Dispatch, Store } from "redux";
import * as geojson from "geojson";
import JSZip from "jszip";
import { saveAs } from "file-saver";
import { initializeContributions, type HostStore } from "chartlets";

import * as api from "@/api";
import i18n from "@/i18n";
Expand All @@ -47,25 +46,24 @@ import {
TimeSeriesGroup,
timeSeriesGroupsToTable,
} from "@/model/timeSeries";
import { initializeExtensions } from "@/ext/actions";
import {
mapProjectionSelector,
selectedDatasetSelector,
selectedDatasetTimeDimensionSelector,
selectedVariableSelector,
selectedDatasetTimeLabelSelector,
selectedPlaceGroupPlacesSelector,
selectedPlaceGroupsSelector,
selectedPlaceIdSelector,
selectedPlaceInfoSelector,
selectedPlaceSelector,
selectedServerSelector,
selectedTimeChunkSizeSelector,
selectedVariableSelector,
userPlacesFormatNameSelector,
userPlacesFormatOptionsCsvSelector,
userPlacesFormatOptionsGeoJsonSelector,
userPlacesFormatOptionsWktSelector,
selectedDatasetTimeLabelSelector,
selectedPlaceInfoSelector,
selectedPlaceGeometrySelector,
selectedDatasetIdSelector,
} from "@/selectors/controlSelectors";
import {
datasetsSelector,
Expand All @@ -76,7 +74,7 @@ import { AppState } from "@/states/appState";
import { VolumeRenderMode } from "@/states/controlState";
import { ColorBarNorm } from "@/model/variable";
import { StatisticsRecord } from "@/model/statistics";
import { UserVariable, ExpressionCapabilities } from "@/model/userVariable";
import { ExpressionCapabilities, UserVariable } from "@/model/userVariable";
import { loadUserVariables, storeUserVariables } from "@/states/userSettings";
import { MessageLogAction, postMessage } from "./messageLogActions";
import { renameUserPlaceInLayer, restyleUserPlaceInLayer } from "./mapActions";
Expand Down Expand Up @@ -809,42 +807,12 @@ export function _configureServers(
////////////////////////////////////////////////////////////////////////////////

export function syncWithServer(store: Store) {
return (dispatch: Dispatch, getState: () => AppState) => {
return (dispatch: Dispatch) => {
dispatch(updateServerInfo() as unknown as Action);
dispatch(updateDatasets() as unknown as Action);
dispatch(updateExpressionCapabilities() as unknown as Action);
dispatch(updateColorBars() as unknown as Action);

const apiServer = selectedServerSelector(getState());
initializeContributions({
hostStore: newHostStore(store),
logging: { enabled: import.meta.env.DEV },
api: { serverUrl: apiServer.url, endpointName: "viewer/ext" },
});
};
}

function newHostStore(store: Store<AppState>): HostStore {
return {
subscribe(listener: () => void): () => void {
return store.subscribe(listener);
},
get(property: string): unknown {
// TODO: turn this into a global Map<string, Selector>.
// Provide some view for developers that documents the
// available properties, e.g., a help menu item
// "For Developers...".
if (property === "selectedDatasetId") {
return selectedDatasetIdSelector(store.getState());
}
if (property === "selectedPlaceGeometry") {
return selectedPlaceGeometrySelector(store.getState());
}
if (property === "selectedTimeLabel") {
return selectedDatasetTimeLabelSelector(store.getState());
}
return undefined;
},
dispatch(initializeExtensions(store) as unknown as Action);
};
}

Expand Down
27 changes: 0 additions & 27 deletions src/components/ContributedPanel/index.tsx

This file was deleted.

12 changes: 10 additions & 2 deletions src/components/MarkdownPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,26 @@ const Transition = React.forwardRef(function Transition(

interface MarkdownPageProps {
title: string;
href: string;
text?: string;
href?: string;
open: boolean;
onClose: () => void;
}

const MarkdownPage: React.FC<MarkdownPageProps> = ({
title,
text,
href,
open,
onClose,
}) => {
const markdownText = useFetchText(href);
let markdownText = useFetchText(href);

if (markdownText && text) {
markdownText = markdownText.replace("${text}", text);
} else if (!markdownText) {
markdownText = text;
}

return (
<Dialog
Expand Down
47 changes: 45 additions & 2 deletions src/connected/AppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { connect } from "react-redux";
import { SxProps, Theme, styled } from "@mui/material";
import AppBarComponent from "@mui/material/AppBar";
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Toolbar from "@mui/material/Toolbar";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
Expand All @@ -38,14 +40,15 @@ import { deepOrange } from "@mui/material/colors";
import CloudDownloadIcon from "@mui/icons-material/CloudDownload";

import i18n from "@/i18n";
import { makeStyles } from "@/util/styles";
import { Config } from "@/config";
import { getDerivedStateMarkdown } from "@/ext/store";
import { AppState } from "@/states/appState";
import { WithLocale } from "@/util/lang";
import { openDialog } from "@/actions/controlActions";
import { updateResources } from "@/actions/dataActions";
import MarkdownPage from "@/components/MarkdownPage";
import UserControl from "./UserControl";
import { makeStyles } from "@/util/styles";

interface AppBarProps extends WithLocale {
appName: string;
Expand Down Expand Up @@ -136,15 +139,39 @@ const _AppBar: React.FC<AppBarProps> = ({
updateResources,
}: AppBarProps) => {
const [imprintOpen, setImprintOpen] = React.useState(false);
const [helpMenuOpen, setHelpMenuOpen] = React.useState(false);
const [devRefOpen, setDevRefOpen] = React.useState(false);
const helpButtonEl = React.useRef<HTMLButtonElement | null>(null);
const derivedStateMarkdown = React.useMemo(
() => getDerivedStateMarkdown(),
[],
);

const handleSettingsButtonClicked = () => {
openDialog("settings");
};

const handleOpenHelpMenu = () => {
setHelpMenuOpen(true);
};

const handleCloseHelpMenu = () => {
setHelpMenuOpen(false);
};

const handleOpenManual = () => {
setHelpMenuOpen(false);
window.open("https://xcube-dev.github.io/xcube-viewer/", "Manual");
};

const handleOpenDevRef = () => {
setDevRefOpen(true);
};

const handleCloseDevRef = () => {
setDevRefOpen(false);
};

const handleOpenImprint = () => {
setImprintOpen(true);
};
Expand Down Expand Up @@ -201,9 +228,10 @@ const _AppBar: React.FC<AppBarProps> = ({
)}
<Tooltip arrow title={i18n.get("Help")}>
<IconButton
onClick={handleOpenManual}
onClick={handleOpenHelpMenu}
size="small"
sx={styles.iconButton}
ref={helpButtonEl}
>
<HelpOutlineIcon />
</IconButton>
Expand Down Expand Up @@ -233,6 +261,21 @@ const _AppBar: React.FC<AppBarProps> = ({
open={imprintOpen}
onClose={handleCloseImprint}
/>
<MarkdownPage
title={i18n.get("Developer Reference")}
href={i18n.get("docs/dev-reference.en.md")}
text={derivedStateMarkdown}
open={devRefOpen}
onClose={handleCloseDevRef}
/>
<Menu
anchorEl={helpButtonEl.current}
open={helpMenuOpen}
onClose={handleCloseHelpMenu}
>
<MenuItem onClick={handleOpenManual}>Documentation</MenuItem>
<MenuItem onClick={handleOpenDevRef}>Developer Reference</MenuItem>
</Menu>
</AppBarComponent>
);
};
Expand Down
2 changes: 0 additions & 2 deletions src/connected/ControlBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import ControlBarComponent from "@/components/ControlBar";
import { WithLocale } from "@/util/lang";
import DatasetSelect from "./DatasetSelect";
import VariableSelect from "./VariableSelect";
// import RgbSwitch from "./RgbSwitch";
import PlaceGroupsSelect from "./PlaceGroupsSelect";
import PlaceSelect from "./PlaceSelect";
import MapInteractionsBar from "./MapInteractionsBar";
Expand Down Expand Up @@ -60,7 +59,6 @@ const _ControlBar: React.FC<ControlBarProps> = ({ show }) => {
<ControlBarComponent>
<DatasetSelect />
<VariableSelect />
{/*<RgbSwitch />*/}
<PlaceGroupsSelect />
<PlaceSelect />
<MapInteractionsBar />
Expand Down
2 changes: 1 addition & 1 deletion src/connected/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import i18n from "@/i18n";
import { makeStyles } from "@/util/styles";
import { setSidebarPanelId } from "@/actions/controlActions";
import { SidebarPanelId, sidebarPanelIds } from "@/states/controlState";
import ContributedPanel from "@/components/ContributedPanel";
import ContributedPanel from "@/ext/components/ContributedPanel";
import InfoPanel from "./InfoPanel";
import TimeSeriesPanel from "./TimeSeriesPanel";
import StatisticsPanel from "./StatisticsPanel";
Expand Down
20 changes: 20 additions & 0 deletions src/ext/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Dispatch, Store } from "redux";
import { initializeContributions } from "chartlets";

import { AppState } from "@/states/appState";
import { selectedServerSelector } from "@/selectors/controlSelectors";
import { newDerivedStore } from "./store";

export function initializeExtensions(store: Store) {
return (_dispatch: Dispatch, getState: () => AppState) => {
const apiServer = selectedServerSelector(getState());
initializeContributions({
hostStore: newDerivedStore(store),
logging: { enabled: import.meta.env.DEV },
api: {
serverUrl: apiServer.url,
endpointName: "viewer/ext",
},
});
};
}
File renamed without changes.
Loading

0 comments on commit e0bd8bd

Please sign in to comment.