Skip to content

Commit cabd9c6

Browse files
authored
Merge pull request #28 from bcdev/forman-25-property_path
`property` of inputs and outputs can be a path
2 parents 7cb183f + 84bf1f5 commit cabd9c6

39 files changed

+532
-276
lines changed

dashi/package-lock.json

Lines changed: 2 additions & 9 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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dashipopashi",
3-
"version": "0.0.11",
3+
"version": "0.0.15",
44
"description": "An experimental library for integrating interactive charts into existing JavaScript applications.",
55
"type": "module",
66
"files": [
@@ -49,7 +49,6 @@
4949
"@emotion/styled": "^11.13.0",
5050
"@fontsource/roboto": "^5.1.0",
5151
"@mui/material": "^6.1.5",
52-
"memoize-one": "^6.0.0",
5352
"microdiff": "^1.4.0",
5453
"react": "^18.3.1",
5554
"react-dom": "^18.3.1",

dashi/src/demo/actions/hidePanel.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { updateContributionContainer } from "@/lib";
22
import type { PanelState } from "@/demo/types";
33

44
export function hidePanel(panelIndex: number) {
5-
updateContributionContainer<PanelState>("panels", panelIndex, {
6-
visible: false,
7-
});
5+
updateContributionContainer<PanelState>(
6+
"panels",
7+
panelIndex,
8+
{
9+
visible: false,
10+
},
11+
false,
12+
);
813
}

dashi/src/lib/actions/handleComponentChange.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
import { store } from "@/lib/store";
22
import { type ContribPoint } from "@/lib/types/model/extension";
33
import { type CallbackRequest } from "@/lib/types/model/callback";
4-
import { type ComponentChangeEvent } from "@/lib/types/model/event";
4+
import { type ComponentChangeEvent } from "@/lib/types/state/event";
55
import { getInputValues } from "@/lib/actions/helpers/getInputValues";
66
import { applyStateChangeRequests } from "@/lib/actions/helpers/applyStateChangeRequests";
77
import { invokeCallbacks } from "@/lib/actions/helpers/invokeCallbacks";
8+
import { equalObjPaths } from "@/lib/utils/objPath";
89

910
export function handleComponentChange(
1011
contribPoint: ContribPoint,
1112
contribIndex: number,
1213
changeEvent: ComponentChangeEvent,
1314
) {
15+
// Apply actual component state change immediately
1416
applyStateChangeRequests([
1517
{
1618
contribPoint,
1719
contribIndex,
1820
stateChanges: [
1921
{
2022
link: "component",
21-
id: changeEvent.componentId,
22-
property: changeEvent.propertyName,
23-
value: changeEvent.propertyValue,
23+
id: changeEvent.id,
24+
property: changeEvent.property,
25+
value: changeEvent.value,
2426
},
2527
],
2628
},
@@ -59,10 +61,11 @@ function getCallbackRequests(
5961
(input) =>
6062
!input.noTrigger &&
6163
(!input.link || input.link === "component") &&
62-
input.id === changeEvent.componentId &&
63-
input.property === changeEvent.propertyName,
64+
input.id === changeEvent.id &&
65+
equalObjPaths(input.property, changeEvent.property),
6466
);
6567
if (inputIndex >= 0) {
68+
// Collect triggered callback
6669
callbackRequests.push({
6770
contribPoint,
6871
contribIndex,

dashi/src/lib/actions/handleHostStoreChange.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import memoizeOne from "memoize-one";
2-
31
import { store } from "@/lib/store";
42
import type {
53
CallbackRef,
@@ -9,16 +7,16 @@ import type {
97
} from "@/lib/types/model/callback";
108
import type { Input } from "@/lib/types/model/channel";
119
import { getInputValues } from "@/lib/actions/helpers/getInputValues";
12-
import { getValue, type PropertyPath } from "@/lib/utils/getValue";
10+
import { getValue, type ObjPath, toObjPath } from "@/lib/utils/objPath";
1311
import { invokeCallbacks } from "@/lib/actions/helpers/invokeCallbacks";
14-
import type { ContributionState } from "@/lib";
12+
import type { ContributionState } from "@/lib/types/state/contribution";
1513

1614
/**
1715
* A reference to a property of an input of a callback of a contribution.
1816
*/
1917
export interface PropertyRef extends ContribRef, CallbackRef, InputRef {
2018
/** The property name as path. */
21-
propertyPath: PropertyPath;
19+
propertyPath: ObjPath;
2220
}
2321

2422
export function handleHostStoreChange<S extends object = object>(
@@ -39,30 +37,29 @@ function getCallbackRequests<S extends object = object>(
3937
hostState: S,
4038
prevHostState: S,
4139
): CallbackRequest[] {
42-
const propertyRefs = getHostStorePropertyRefs().filter((propertyRef) =>
43-
hasPropertyChanged(propertyRef.propertyPath, hostState, prevHostState),
44-
);
45-
const callbackRequest: CallbackRequest[] = [];
46-
propertyRefs.forEach((propertyRef) => {
47-
const contributions = contributionsRecord[propertyRef.contribPoint];
48-
const contribution = contributions[propertyRef.contribIndex];
49-
const callback = contribution.callbacks![propertyRef.callbackIndex];
50-
const inputValues = getInputValues(
51-
callback.inputs!,
52-
contribution,
53-
hostState,
54-
);
55-
callbackRequest.push({ ...propertyRef, inputValues });
56-
});
57-
return callbackRequest;
40+
return getHostStorePropertyRefs()
41+
.filter((propertyRef) =>
42+
hasPropertyChanged(propertyRef.propertyPath, hostState, prevHostState),
43+
)
44+
.map((propertyRef) => {
45+
const contributions = contributionsRecord[propertyRef.contribPoint];
46+
const contribution = contributions[propertyRef.contribIndex];
47+
const callback = contribution.callbacks![propertyRef.callbackIndex];
48+
const inputValues = getInputValues(
49+
callback.inputs!,
50+
contribution,
51+
hostState,
52+
);
53+
return { ...propertyRef, inputValues };
54+
});
5855
}
5956

60-
const getHostStorePropertyRefs = memoizeOne(_getHostStorePropertyRefs);
57+
// const getHostStorePropertyRefs = memoizeOne(_getHostStorePropertyRefs);
6158

6259
/**
6360
* Get the static list of host state property references for all contributions.
6461
*/
65-
function _getHostStorePropertyRefs(): PropertyRef[] {
62+
function getHostStorePropertyRefs(): PropertyRef[] {
6663
const { contributionsRecord } = store.getState();
6764
const propertyRefs: PropertyRef[] = [];
6865
Object.getOwnPropertyNames(contributionsRecord).forEach((contribPoint) => {
@@ -77,7 +74,7 @@ function _getHostStorePropertyRefs(): PropertyRef[] {
7774
contribIndex,
7875
callbackIndex,
7976
inputIndex,
80-
propertyPath: input.property!.split("."),
77+
propertyPath: toObjPath(input.property!),
8178
});
8279
}
8380
}),
@@ -89,7 +86,7 @@ function _getHostStorePropertyRefs(): PropertyRef[] {
8986
}
9087

9188
function hasPropertyChanged<S extends object = object>(
92-
propertyPath: PropertyPath,
89+
propertyPath: ObjPath,
9390
currState: S,
9491
prevState: S,
9592
): boolean {

dashi/src/lib/actions/helpers/applyStateChangeRequests.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { ContribPoint } from "@/lib/types/model/extension";
1111
import type { ContributionState } from "@/lib";
1212
import { updateArray } from "@/lib/utils/updateArray";
1313
import { isContainerState } from "@/lib/actions/helpers/isContainerState";
14+
import { getValue, setValue } from "@/lib/utils/objPath";
1415

1516
export function applyStateChangeRequests(
1617
stateChangeRequests: StateChangeRequest[],
@@ -106,12 +107,10 @@ export function applyComponentStateChange(
106107
): ComponentState {
107108
if (component.id === stateChange.id) {
108109
const property = stateChange.property;
109-
const valueOld = (component as unknown as Record<string, unknown>)[
110-
property
111-
];
110+
const valueOld = getValue(component, property);
112111
const valueNew = stateChange.value;
113112
if (valueOld !== valueNew) {
114-
return { ...component, [property]: valueNew };
113+
return setValue(component, property, valueNew);
115114
}
116115
} else if (isContainerState(component)) {
117116
const containerOld: ContainerState = component;
@@ -135,17 +134,13 @@ export function applyComponentStateChange(
135134
}
136135

137136
// we export for testing only
138-
export function applyStateChanges<S extends object>(
139-
state: S | undefined,
137+
export function applyStateChanges<S extends object | undefined>(
138+
state: S,
140139
stateChanges: StateChange[],
141-
): S | undefined {
140+
): S {
142141
stateChanges.forEach((stateChange) => {
143-
if (
144-
!state ||
145-
(state as unknown as Record<string, unknown>)[stateChange.property] !==
146-
stateChange.value
147-
) {
148-
state = { ...state, [stateChange.property]: stateChange.value } as S;
142+
if (!state || getValue(state, stateChange.property) !== stateChange.value) {
143+
state = setValue(state, stateChange.property, stateChange.value) as S;
149144
}
150145
});
151146
return state;

dashi/src/lib/actions/helpers/configureLogging.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,26 @@ function logState(next: StoreState, prev: StoreState) {
2727
const delta = diff(prev, next);
2828
const nDiffs = delta.length;
2929
console.groupCollapsed(
30-
`state changed (${nDiffs} difference${nDiffs === 1 ? "" : "s"})`,
30+
`dashi: state changed (${nDiffs} difference${nDiffs === 1 ? "" : "s"})`,
3131
);
3232
delta.forEach(logDiff);
33-
console.debug("Details:", { prev, next, delta });
33+
console.debug("dashi: change details:", { prev, next, delta });
3434
console.groupEnd();
3535
}
3636

3737
function logDiff(v: Difference, index: number) {
3838
const wherePart = `%c${index + 1} %c${v.type} %c${v.path.join(".")}`;
3939
if (v.type === "CREATE") {
40-
console.log(wherePart, indexStyle, typeStyle, pathStyle, {
40+
console.debug("dashi:", wherePart, indexStyle, typeStyle, pathStyle, {
4141
value: v.value,
4242
});
4343
} else if (v.type === "CHANGE") {
44-
console.log(wherePart, indexStyle, typeStyle, pathStyle, {
44+
console.debug("dashi:", wherePart, indexStyle, typeStyle, pathStyle, {
4545
value: v.value,
4646
oldValue: v.oldValue,
4747
});
4848
} else if (v.type === "REMOVE") {
49-
console.log(wherePart, indexStyle, typeStyle, pathStyle, {
49+
console.debug("dashi:", wherePart, indexStyle, typeStyle, pathStyle, {
5050
oldValue: v.oldValue,
5151
});
5252
}

dashi/src/lib/actions/helpers/getInputValues.ts

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { Input } from "@/lib/types/model/channel";
22
import type { ContributionState } from "@/lib/types/state/contribution";
33
import type { ComponentState } from "@/lib/types/state/component";
4-
import { isSubscriptable } from "@/lib/utils/isSubscriptable";
54
import { isContainerState } from "@/lib/actions/helpers/isContainerState";
6-
import { getValue } from "@/lib/utils/getValue";
5+
import { getValue } from "@/lib/utils/objPath";
6+
import { isObject } from "@/lib/utils/isObject";
77

88
export function getInputValues<S extends object = object>(
99
inputs: Input[],
@@ -47,10 +47,8 @@ export function getInputValueFromComponent(
4747
input: Input,
4848
componentState: ComponentState,
4949
): unknown {
50-
if (componentState.id === input.id && input.property) {
51-
return (componentState as unknown as Record<string, unknown>)[
52-
input.property
53-
];
50+
if (componentState.id === input.id) {
51+
return getValue(componentState, input.property);
5452
} else if (isContainerState(componentState)) {
5553
for (let i = 0; i < componentState.components.length; i++) {
5654
const item = componentState.components[i];
@@ -69,20 +67,11 @@ export function getInputValueFromState(
6967
state: unknown,
7068
): unknown | undefined {
7169
let inputValue: unknown = state;
72-
if (input.id && isSubscriptable(inputValue)) {
70+
if (input.id && isObject(inputValue)) {
7371
inputValue = inputValue[input.id];
7472
}
75-
if (isSubscriptable(inputValue)) {
76-
const property = input.property;
77-
// TODO: The Input.property should be normalized to be a path
78-
// and have type string[].
79-
// See also interface Input in types/model/channel.ts
80-
if (property.includes(".")) {
81-
const path = property.split(".");
82-
inputValue = getValue(inputValue, path);
83-
} else {
84-
inputValue = inputValue[property];
85-
}
73+
if (isObject(inputValue)) {
74+
inputValue = getValue(inputValue, input.property);
8675
}
8776
return inputValue;
8877
}

dashi/src/lib/actions/helpers/invokeCallbacks.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
import { store } from "@/lib/store";
22
import type { CallbackRequest } from "@/lib/types/model/callback";
3-
import { fetchApiResult } from "@/lib/utils/fetchApiResult";
4-
import { fetchStateChangeRequests } from "@/lib/api";
3+
import { fetchCallback } from "@/lib/api/fetchCallback";
54
import { applyStateChangeRequests } from "@/lib/actions/helpers/applyStateChangeRequests";
65

76
export function invokeCallbacks(callbackRequests: CallbackRequest[]) {
87
const { configuration } = store.getState();
9-
const invocationId = getInvocationId();
10-
if (import.meta.env.DEV) {
11-
console.debug(`invokeCallbacks (${invocationId})-->`, callbackRequests);
8+
const shouldLog = configuration.logging?.enabled;
9+
if (!callbackRequests.length) {
10+
if (shouldLog) {
11+
console.info(`dashi: invokeCallbacks - no requests`, callbackRequests);
12+
}
13+
return;
1214
}
13-
if (callbackRequests.length) {
14-
fetchApiResult(
15-
fetchStateChangeRequests,
15+
const invocationId = getInvocationId();
16+
if (shouldLog) {
17+
console.info(
18+
`dashi: invokeCallbacks (${invocationId})-->`,
1619
callbackRequests,
17-
configuration.api,
18-
).then((changeRequestsResult) => {
20+
);
21+
}
22+
fetchCallback(callbackRequests, configuration.api).then(
23+
(changeRequestsResult) => {
1924
if (changeRequestsResult.data) {
20-
if (import.meta.env.DEV) {
21-
console.debug(
22-
`invokeCallbacks <--(${invocationId})`,
25+
if (shouldLog) {
26+
console.info(
27+
`dashi: invokeCallbacks <--(${invocationId})`,
2328
changeRequestsResult.data,
2429
);
2530
}
@@ -32,8 +37,8 @@ export function invokeCallbacks(callbackRequests: CallbackRequest[]) {
3237
callbackRequests,
3338
);
3439
}
35-
});
36-
}
40+
},
41+
);
3742
}
3843

3944
let invocationCounter = 0;

0 commit comments

Comments
 (0)