Skip to content

Commit c986b4d

Browse files
authored
Merge pull request #22 from bcdev/forman-20-support_app_state
Support application state
2 parents 6caf53b + 775b3c1 commit c986b4d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1472
-796
lines changed

dashi/TODO.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Dashi UI TODOs
2+
3+
- Keep state flat, avoid deep nesting!
4+
5+
- Why: Changing a deeply nested property is more complex, error prone,
6+
and takes more time to execute.
7+
Deeply nested state models are harder to understand too.
8+
It will also require changing all parent objects and arrays that contain the changed value
9+
which potentially causes more UI renders and/or more tests whether component properties changed.
10+
11+
- How: Go back to former design where initial contributions that stay constant
12+
are separate from changing contribution states and components.
13+
14+
- Consequently use `propertyPath: string[]` instead of `propertyName: string`
15+
16+
- Add true actions from `src/actions` to store state so that lib users
17+
know what actions are public.

dashi/package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashi/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@emotion/styled": "^11.13.0",
5050
"@fontsource/roboto": "^5.1.0",
5151
"@mui/material": "^6.1.5",
52+
"memoize-one": "^6.0.0",
5253
"microdiff": "^1.4.0",
5354
"react": "^18.3.1",
5455
"react-dom": "^18.3.1",

dashi/src/demo/App.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { CssBaseline, ThemeProvider, createTheme } from "@mui/material";
22
import Typography from "@mui/material/Typography";
33

4-
import { configureLogging, initSystemStore } from "@/lib";
4+
import { initializeContributions } from "@/lib";
55
import ExtensionsInfo from "./components/ExtensionInfo";
6+
import ControlBar from "@/demo/components/ControlBar";
67
import PanelsControl from "./components/PanelsControl";
78
import PanelsRow from "./components/PanelsRow";
9+
import { appStore } from "@/demo/store";
810

9-
configureLogging();
10-
11-
initSystemStore();
11+
initializeContributions({
12+
hostStore: appStore,
13+
logging: { enabled: true },
14+
});
1215

1316
// MUI's default font family
1417
const fontFamily = "Roboto, Arial, sans-serif";
@@ -32,6 +35,7 @@ function App() {
3235
Dashi Demo
3336
</Typography>
3437
<ExtensionsInfo />
38+
<ControlBar />
3539
<PanelsControl />
3640
<PanelsRow />
3741
</ThemeProvider>
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { setComponentVisibility } from "@/lib";
1+
import { updateContributionState } from "@/lib";
2+
import type { PanelState } from "@/demo/types";
23

34
export function hidePanel(panelIndex: number) {
4-
setComponentVisibility("panels", panelIndex, false);
5+
updateContributionState<PanelState>("panels", panelIndex, { visible: false });
56
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { setComponentVisibility } from "@/lib";
1+
import { updateContributionState } from "@/lib";
2+
import type { PanelState } from "@/demo/types";
23

34
export function showPanel(panelIndex: number) {
4-
setComponentVisibility("panels", panelIndex, true);
5+
updateContributionState<PanelState>("panels", panelIndex, { visible: true });
56
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import FormControl from "@mui/material/FormControl";
2+
import InputLabel from "@mui/material/InputLabel";
3+
import Select from "@mui/material/Select";
4+
import MenuItem from "@mui/material/MenuItem";
5+
6+
import { useAppStore } from "@/demo/store";
7+
8+
function ControlBar() {
9+
const { datasets, selectedDatasetId, setSelectedDatasetId } = useAppStore();
10+
11+
return (
12+
<div>
13+
<FormControl variant="filled" size="small" style={{ minWidth: 180 }}>
14+
<InputLabel id="dataset">Dataset (App State)</InputLabel>
15+
<Select
16+
labelId="dataset"
17+
value={selectedDatasetId}
18+
onChange={(event) =>
19+
void setSelectedDatasetId(event.target.value || null)
20+
}
21+
>
22+
{datasets.map(({ id, title }) => (
23+
<MenuItem key={id} value={id}>
24+
{title}
25+
</MenuItem>
26+
))}
27+
</Select>
28+
</FormControl>
29+
</div>
30+
);
31+
}
32+
33+
export default ControlBar;

dashi/src/demo/components/Panel.tsx

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { type CSSProperties, type ReactElement } from "react";
1+
import type { CSSProperties, ReactElement } from "react";
22
import CircularProgress from "@mui/material/CircularProgress";
33

44
import {
5-
type Contribution,
5+
type ComponentChangeHandler,
66
type ContributionState,
7-
type PropertyChangeHandler,
8-
DashiComponent,
7+
Component,
98
} from "@/lib";
9+
import type { PanelState } from "@/demo/types";
1010

1111
const panelContainerStyle: CSSProperties = {
1212
display: "flex",
@@ -32,40 +32,39 @@ const panelContentStyle: CSSProperties = {
3232
padding: 2,
3333
};
3434

35-
interface PanelProps {
36-
panelModel: Contribution;
37-
panelState: ContributionState;
38-
onPropertyChange: PropertyChangeHandler;
35+
interface PanelProps extends ContributionState<PanelState> {
36+
onChange: ComponentChangeHandler;
3937
}
4038

41-
function Panel({ panelModel, panelState, onPropertyChange }: PanelProps) {
42-
if (!panelState.visible) {
39+
function Panel({
40+
name,
41+
state,
42+
component,
43+
componentResult,
44+
onChange,
45+
}: PanelProps) {
46+
if (!state.visible) {
4347
return null;
4448
}
45-
const componentState = panelState.componentState;
4649
let panelElement: ReactElement | null = null;
47-
const componentModelResult = panelState.componentStateResult;
48-
if (componentModelResult.data && componentState) {
49-
panelElement = (
50-
<DashiComponent {...componentState} onPropertyChange={onPropertyChange} />
51-
);
52-
} else if (componentModelResult.error) {
50+
if (component) {
51+
panelElement = <Component {...component} onChange={onChange} />;
52+
} else if (componentResult.error) {
5353
panelElement = (
5454
<span>
55-
Error loading {panelModel.name}: {componentModelResult.error.message}
55+
Error loading {name}: {componentResult.error.message}
5656
</span>
5757
);
58-
} else if (componentModelResult.status === "pending") {
58+
} else if (componentResult.status === "pending") {
5959
panelElement = (
6060
<span>
61-
<CircularProgress size={30} color="secondary" /> Loading{" "}
62-
{panelModel.name}...
61+
<CircularProgress size={30} color="secondary" /> Loading {name}...
6362
</span>
6463
);
6564
}
6665
return (
6766
<div style={panelContainerStyle}>
68-
<div style={panelHeaderStyle}>{panelState.title}</div>
67+
<div style={panelHeaderStyle}>{state.title}</div>
6968
<div style={panelContentStyle}>{panelElement}</div>
7069
</div>
7170
);

dashi/src/demo/components/PanelsControl.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ import Checkbox from "@mui/material/Checkbox";
22
import FormControlLabel from "@mui/material/FormControlLabel";
33
import FormGroup from "@mui/material/FormGroup";
44

5-
import { useContributionStatesRecord } from "@/lib";
65
import { hidePanel } from "@/demo/actions/hidePanel";
76
import { showPanel } from "@/demo/actions/showPanel";
8-
9-
const contribPoint = "panels";
7+
import { usePanelStates } from "@/demo/hooks";
108

119
function PanelsControl() {
12-
const contributionStatesRecord = useContributionStatesRecord();
13-
const panelStates = contributionStatesRecord[contribPoint];
10+
const panelStates = usePanelStates();
1411
if (!panelStates) {
1512
// Ok, not ready yet
1613
return null;
@@ -23,12 +20,12 @@ function PanelsControl() {
2320
return (
2421
<FormControlLabel
2522
key={panelIndex}
26-
label={panelState.title}
23+
label={panelState.state.title}
2724
control={
2825
<Checkbox
2926
color="secondary"
3027
id={id}
31-
checked={panelState.visible || false}
28+
checked={panelState.state.visible || false}
3229
value={panelIndex}
3330
onChange={(e) => {
3431
if (e.currentTarget.checked) {

dashi/src/demo/components/PanelsRow.tsx

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,30 @@
1-
import React from "react";
1+
import type { JSX } from "react";
22

3-
import {
4-
type PropertyChangeEvent,
5-
applyPropertyChange,
6-
useContributionModelsRecord,
7-
useContributionStatesRecord,
8-
} from "@/lib";
3+
import { type ComponentChangeEvent, handleComponentChange } from "@/lib";
4+
import { usePanelStates } from "@/demo/hooks";
95
import Panel from "./Panel";
106

11-
const contribPoint = "panels";
12-
137
function PanelsRow() {
14-
const contributionModelsRecord = useContributionModelsRecord();
15-
const contributionStatesRecord = useContributionStatesRecord();
16-
const panelModels = contributionModelsRecord[contribPoint];
17-
const panelStates = contributionStatesRecord[contribPoint];
18-
if (!panelModels || !panelStates) {
8+
const panelStates = usePanelStates();
9+
if (!panelStates) {
1910
// Ok, not ready yet
2011
return null;
2112
}
22-
// TODO: assert panelModels.length === panelStates.length
23-
if (panelModels.length != panelStates?.length) {
24-
throw Error("internal state error");
25-
}
2613

27-
const handlePropertyChange = (
14+
const handlePanelChange = (
2815
panelIndex: number,
29-
panelEvent: PropertyChangeEvent,
16+
panelEvent: ComponentChangeEvent,
3017
) => {
31-
applyPropertyChange(contribPoint, panelIndex, panelEvent);
18+
handleComponentChange("panels", panelIndex, panelEvent);
3219
};
33-
const visiblePanels: React.JSX.Element[] = [];
20+
const visiblePanels: JSX.Element[] = [];
3421
panelStates.forEach((panelState, panelIndex) => {
35-
if (panelState.visible) {
22+
if (panelState.state.visible) {
3623
visiblePanels.push(
3724
<Panel
3825
key={panelIndex}
39-
panelState={panelState}
40-
panelModel={panelModels[panelIndex]}
41-
onPropertyChange={(e) => handlePropertyChange(panelIndex, e)}
26+
{...panelState}
27+
onChange={(e) => handlePanelChange(panelIndex, e)}
4228
/>,
4329
);
4430
}

0 commit comments

Comments
 (0)