Skip to content

Commit 2776c6e

Browse files
committed
Moved chartlets lib files; adjusting deps
1 parent 99754e7 commit 2776c6e

Some content is hidden

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

50 files changed

+3103
-1218
lines changed

chartlets.js/package-lock.json

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

chartlets.js/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chartlets",
3-
"version": "0.0.27",
3+
"version": "0.0.28",
44
"description": "An experimental library for integrating interactive charts into existing JavaScript applications.",
55
"type": "module",
66
"files": [
@@ -90,7 +90,6 @@
9090
"react": "^18.3.1",
9191
"react-dom": "^18.3.1",
9292
"react-vega": "^7.6.0",
93-
9493
"typescript": "^5.6.2",
9594
"vite": "^5.4.6",
9695
"vite-plugin-dts": "^4.2.4",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { store } from "@/store";
2+
import type { FrameworkOptions } from "@/types/state/options";
3+
import { configureLogging } from "@/actions/helpers/configureLogging";
4+
import { handleHostStoreChange } from "./handleHostStoreChange";
5+
6+
export function configureFramework(options: FrameworkOptions) {
7+
if (options.logging) {
8+
configureLogging(options.logging);
9+
}
10+
if (options.hostStore) {
11+
options.hostStore.subscribe(handleHostStoreChange);
12+
}
13+
store.setState({
14+
configuration: { ...options } as FrameworkOptions,
15+
});
16+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { store } from "@/store";
2+
import { type ContribPoint } from "@/types/model/extension";
3+
import { type CallbackRequest } from "@/types/model/callback";
4+
import { type ComponentChangeEvent } from "@/types/state/event";
5+
import { getInputValues } from "@/actions/helpers/getInputValues";
6+
import { applyStateChangeRequests } from "@/actions/helpers/applyStateChangeRequests";
7+
import { invokeCallbacks } from "@/actions/helpers/invokeCallbacks";
8+
import { equalObjPaths } from "@/utils/objPath";
9+
10+
export function handleComponentChange(
11+
contribPoint: ContribPoint,
12+
contribIndex: number,
13+
changeEvent: ComponentChangeEvent,
14+
) {
15+
if (store.getState().extensions.length === 0) {
16+
// Exit immediately if there are no extensions (yet)
17+
return;
18+
}
19+
// Apply actual component state change immediately
20+
applyStateChangeRequests([
21+
{
22+
contribPoint,
23+
contribIndex,
24+
stateChanges: [
25+
{
26+
id: changeEvent.id,
27+
property: changeEvent.property,
28+
value: changeEvent.value,
29+
},
30+
],
31+
},
32+
]);
33+
const callbackRequests = getCallbackRequests(
34+
contribPoint,
35+
contribIndex,
36+
changeEvent,
37+
);
38+
if (callbackRequests && callbackRequests.length > 0) {
39+
invokeCallbacks(callbackRequests);
40+
}
41+
}
42+
43+
/**
44+
* Collect callback requests for the callbacks of
45+
* the contribution that are triggered by the change event.
46+
*
47+
* @param contribPoint Name of the contribution point.
48+
* @param contribIndex Index of the contribution.
49+
* @param changeEvent The change event.
50+
*/
51+
function getCallbackRequests(
52+
contribPoint: ContribPoint,
53+
contribIndex: number,
54+
changeEvent: ComponentChangeEvent,
55+
): CallbackRequest[] {
56+
const { configuration, contributionsRecord } = store.getState();
57+
const { hostStore } = configuration;
58+
const contributions = contributionsRecord[contribPoint];
59+
const contribution = contributions[contribIndex];
60+
const callbackRequests: CallbackRequest[] = [];
61+
(contribution.callbacks || []).forEach((callback, callbackIndex) => {
62+
if (callback.inputs && callback.inputs.length) {
63+
const inputs = callback.inputs;
64+
const inputIndex = inputs.findIndex(
65+
(input) =>
66+
!input.noTrigger &&
67+
input.id &&
68+
!input.id.startsWith("@") &&
69+
input.id === changeEvent.id &&
70+
equalObjPaths(input.property, changeEvent.property),
71+
);
72+
if (inputIndex >= 0) {
73+
// Collect triggered callback
74+
callbackRequests.push({
75+
contribPoint,
76+
contribIndex,
77+
callbackIndex,
78+
inputIndex,
79+
inputValues: getInputValues(inputs, contribution, hostStore),
80+
});
81+
}
82+
}
83+
});
84+
return callbackRequests;
85+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { store } from "@/store";
2+
import type {
3+
CallbackRef,
4+
CallbackRequest,
5+
ContribRef,
6+
InputRef,
7+
} from "@/types/model/callback";
8+
import type { Input } from "@/types/model/channel";
9+
import { getInputValues } from "@/actions/helpers/getInputValues";
10+
import { formatObjPath } from "@/utils/objPath";
11+
import { invokeCallbacks } from "@/actions/helpers/invokeCallbacks";
12+
import type { ContributionState } from "@/types/state/contribution";
13+
import type { HostStore } from "@/types/state/options";
14+
15+
/**
16+
* A reference to a property of an input of a callback of a contribution.
17+
*/
18+
export interface PropertyRef extends ContribRef, CallbackRef, InputRef {
19+
/** The property. */
20+
property: string;
21+
}
22+
23+
export function handleHostStoreChange() {
24+
const { extensions, configuration, contributionsRecord } = store.getState();
25+
const { hostStore } = configuration;
26+
if (!hostStore || extensions.length === 0) {
27+
// Exit if no host store configured or
28+
// there are no extensions (yet)
29+
return;
30+
}
31+
const propertyRefs = getHostStorePropertyRefs();
32+
if (!propertyRefs || propertyRefs.length === 0) {
33+
// Exit if there are is nothing to be changed
34+
return;
35+
}
36+
const callbackRequests = getCallbackRequests(
37+
propertyRefs,
38+
contributionsRecord,
39+
hostStore,
40+
);
41+
if (callbackRequests && callbackRequests.length > 0) {
42+
invokeCallbacks(callbackRequests);
43+
}
44+
}
45+
46+
function getCallbackRequests(
47+
propertyRefs: PropertyRef[],
48+
contributionsRecord: Record<string, ContributionState[]>,
49+
hostStore: HostStore,
50+
): CallbackRequest[] {
51+
return propertyRefs.map((propertyRef) => {
52+
const contributions = contributionsRecord[propertyRef.contribPoint];
53+
const contribution = contributions[propertyRef.contribIndex];
54+
const callback = contribution.callbacks![propertyRef.callbackIndex];
55+
const inputValues = getInputValues(
56+
callback.inputs!,
57+
contribution,
58+
hostStore,
59+
);
60+
return { ...propertyRef, inputValues };
61+
});
62+
}
63+
64+
// TODO: use a memoized selector to get hostStorePropertyRefs
65+
// Note that this will only be effective and once we split the
66+
// static contribution infos and dynamic contribution states.
67+
// The hostStorePropertyRefs only depend on the static
68+
// contribution infos.
69+
70+
/**
71+
* Get the static list of host state property references for all contributions.
72+
*/
73+
function getHostStorePropertyRefs(): PropertyRef[] {
74+
const { contributionsRecord } = store.getState();
75+
const propertyRefs: PropertyRef[] = [];
76+
Object.getOwnPropertyNames(contributionsRecord).forEach((contribPoint) => {
77+
const contributions = contributionsRecord[contribPoint];
78+
contributions.forEach((contribution, contribIndex) => {
79+
(contribution.callbacks || []).forEach(
80+
(callback, callbackIndex) =>
81+
(callback.inputs || []).forEach((input, inputIndex) => {
82+
if (!input.noTrigger && input.id === "@app" && input.property) {
83+
propertyRefs.push({
84+
contribPoint,
85+
contribIndex,
86+
callbackIndex,
87+
inputIndex,
88+
property: formatObjPath(input.property),
89+
});
90+
}
91+
}),
92+
[] as Input[],
93+
);
94+
});
95+
});
96+
return propertyRefs;
97+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { describe, it, expect } from "vitest";
2+
3+
import { type ContribPoint } from "@/types/model/extension";
4+
import { type StateChangeRequest } from "@/types/model/callback";
5+
import { type ComponentState } from "@/types/state/component";
6+
import { type ContributionState } from "@/types/state/contribution";
7+
import {
8+
applyComponentStateChange,
9+
applyContributionChangeRequests,
10+
} from "./applyStateChangeRequests";
11+
12+
const componentTree = {
13+
type: "Box",
14+
id: "b1",
15+
children: [
16+
{ type: "Plot", id: "p1", chart: null },
17+
{
18+
type: "Box",
19+
id: "b2",
20+
children: [
21+
{ type: "Checkbox", id: "cb1", value: true },
22+
{ type: "Select", id: "dd1", value: 13 },
23+
],
24+
},
25+
],
26+
};
27+
28+
describe("Test that applyContributionChangeRequests()", () => {
29+
const contributionsRecord: Record<ContribPoint, ContributionState[]> = {
30+
panels: [
31+
{
32+
name: "",
33+
extension: "",
34+
componentResult: { status: "ok" },
35+
container: { visible: true },
36+
component: componentTree,
37+
},
38+
],
39+
};
40+
41+
const stateChangeRequest1: StateChangeRequest = {
42+
contribPoint: "panels",
43+
contribIndex: 0,
44+
stateChanges: [
45+
{
46+
id: "dd1",
47+
property: "value",
48+
value: 14,
49+
},
50+
],
51+
};
52+
53+
const stateChangeRequest2: StateChangeRequest = {
54+
contribPoint: "panels",
55+
contribIndex: 0,
56+
stateChanges: [
57+
{
58+
id: "dd1",
59+
property: "value",
60+
value: 13,
61+
},
62+
],
63+
};
64+
65+
it("changes state if values are different", () => {
66+
const newState = applyContributionChangeRequests(contributionsRecord, [
67+
stateChangeRequest1,
68+
]);
69+
expect(newState).not.toBe(contributionsRecord);
70+
expect(
71+
(
72+
(newState["panels"][0].component!.children![1] as ComponentState)
73+
.children![1] as ComponentState
74+
).value,
75+
).toEqual(14);
76+
});
77+
78+
it("doesn't change the state if value stays the same", () => {
79+
const newState = applyContributionChangeRequests(contributionsRecord, [
80+
stateChangeRequest2,
81+
]);
82+
expect(newState).toBe(contributionsRecord);
83+
});
84+
});
85+
86+
describe("Test that applyComponentStateChange()", () => {
87+
it("changes state if values are different", () => {
88+
const newState = applyComponentStateChange(componentTree, {
89+
id: "cb1",
90+
property: "value",
91+
value: false,
92+
});
93+
expect(newState).not.toBe(componentTree);
94+
expect(
95+
((newState.children![1] as ComponentState).children![0] as ComponentState)
96+
.value,
97+
).toEqual(false);
98+
});
99+
100+
it("doesn't change the state if value stays the same", () => {
101+
const newState = applyComponentStateChange(componentTree, {
102+
id: "cb1",
103+
property: "value",
104+
value: true,
105+
});
106+
expect(newState).toBe(componentTree);
107+
});
108+
109+
it("replaces state if property is empty string", () => {
110+
const value = {
111+
type: "Box",
112+
id: "b1",
113+
children: ["Hello", "World"],
114+
};
115+
const newState = applyComponentStateChange(componentTree, {
116+
id: "b1",
117+
property: "",
118+
value,
119+
});
120+
expect(newState).toBe(value);
121+
expect(newState).toEqual({
122+
type: "Box",
123+
id: "b1",
124+
children: ["Hello", "World"],
125+
});
126+
});
127+
});

0 commit comments

Comments
 (0)