Skip to content
2 changes: 1 addition & 1 deletion src/components/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { initializeNeoPlugin } from "../models/plugin-state";
import { isNonEmbedded } from "../utils/embed-check";
import { initializeNeoPlugin } from "../utils/codap-utils";
import { TabContainer } from "./tabs/tab-container";
import { Provider } from "./ui/provider";

Expand Down
2 changes: 1 addition & 1 deletion src/models/data-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
sendMessage,
} from "@concord-consortium/codap-plugin-api";
import { decodePng } from "@concord-consortium/png-codec";
import { kChartGraphName, kDataContextName, kMapPinsCollectionName, kXYGraphName } from "../data/constants";
import { kChartGraphName, kDataContextName, kMapPinsCollectionName, kXYGraphName } from "../data/constants";
import { createOrUpdateGraphs, createOrUpdateDateSlider, createOrUpdateMap, addConnectingLinesToGraph,
addRegionOfInterestToGraphs, updateGraphRegionOfInterest, updateLocationColorMap, rescaleGraph
} from "../utils/codap-utils";
Expand Down
165 changes: 161 additions & 4 deletions src/models/plugin-state.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,67 @@
import { getAllItems, IResult } from "@concord-consortium/codap-plugin-api";
import { makeAutoObservable } from "mobx";
import { addDataContextChangeListener, getAllItems, getCaseByID, getCaseBySearch, getSelectionList, initializePlugin,
IResult, sendMessage } from "@concord-consortium/codap-plugin-api";
import { makeAutoObservable, reaction } from "mobx";
import {
kPinColorAttributeName, kPinDataContextName, kPinLatAttributeName, kPinLongAttributeName
kDataContextName,
kInitialDimensions,
kMapPinsCollectionName,
kPinColorAttributeName, kPinDataContextName, kPinLatAttributeName, kPinLongAttributeName,
kPluginName,
kVersion
} from "../data/constants";
import { createOrUpdateMap, createSelectionList, deleteSelectionList, updateSelectionList } from "../utils/codap-utils";
import { NeoDataset } from "./neo-types";

interface IMapPin {
export interface IMapPin {
color: string;
id: string;
lat: number;
long: number;
}

export interface ILocationCase {
label: string;
pinColor: string;
}

export function pinLabel(pin: IMapPin) {
return `${pin.lat.toFixed(2)}, ${pin.long.toFixed(2)}`;
}


class PluginState {
neoDataset: NeoDataset | undefined;
neoDatasetName = "";
pins: IMapPin[] = [];
selectedPins: IMapPin[] = [];
selectedCases: any[] =[];

constructor() {
makeAutoObservable(this);
// Reaction to changes in selectedPins from MapPinDataContext
reaction(
() => this.selectedPins,
(selectedPins) => {
this.handleSelectionChange(
selectedPins,
kDataContextName,
kMapPinsCollectionName,
(pin) => `label == ${pinLabel(pin)}`
);
}

Check warning on line 51 in src/models/plugin-state.ts

View check run for this annotation

Codecov / codecov/patch

src/models/plugin-state.ts#L45-L51

Added lines #L45 - L51 were not covered by tests
);
// Reaction to changes in selectedCases NEOPluginDataContext
reaction(
() => this.selectedCases,
(selectedCases) => {
this.handleSelectionChange(
selectedCases,
kPinDataContextName,
kMapPinsCollectionName,
(sCase) => `pinColor == ${sCase.pinColor}`
);
}

Check warning on line 63 in src/models/plugin-state.ts

View check run for this annotation

Codecov / codecov/patch

src/models/plugin-state.ts#L57-L63

Added lines #L57 - L63 were not covered by tests
);
}

setNeoDataset(neoDataset: NeoDataset | undefined) {
Expand All @@ -45,6 +84,124 @@
});
}
}

setSelectedPins(selectedPins: IMapPin[]) {
this.selectedPins = selectedPins;
}

Check warning on line 90 in src/models/plugin-state.ts

View check run for this annotation

Codecov / codecov/patch

src/models/plugin-state.ts#L89-L90

Added lines #L89 - L90 were not covered by tests

setSelectedCases(selectedCases: any[]) {
this.selectedCases = selectedCases;
}

Check warning on line 94 in src/models/plugin-state.ts

View check run for this annotation

Codecov / codecov/patch

src/models/plugin-state.ts#L93-L94

Added lines #L93 - L94 were not covered by tests

async handleSelectionChange<T>(
selectedItems: T[], dataContextName: string, collectionName: string, searchQueryFn: (item: T) => string
): Promise<void> {
if (selectedItems.length === 0) {
deleteSelectionList(dataContextName);
return;
}

for (const item of selectedItems) {
const searchQuery = searchQueryFn(item);
const result = await getCaseBySearch(dataContextName, collectionName, searchQuery);

if (result.success) {
const selectedItemIds = result.values.map((val: any) => val.id);
if (selectedItems.length === 1) {
createSelectionList(dataContextName, selectedItemIds);
return;
} else {
const updateSelection = await updateSelectionList(dataContextName, selectedItemIds);
if (!updateSelection.success) {
createSelectionList(dataContextName, selectedItemIds);
}
}
}
}
}

Check warning on line 121 in src/models/plugin-state.ts

View check run for this annotation

Codecov / codecov/patch

src/models/plugin-state.ts#L97-L121

Added lines #L97 - L121 were not covered by tests
}

export async function initializeNeoPlugin() {
initializePlugin({ pluginName: kPluginName, version: kVersion, dimensions: kInitialDimensions });

// Create the pin dataset
await sendMessage("create", `dataContext`, {
name: kPinDataContextName,
collections: [
{
name: "Map Pins",
attrs: [
{ name: kPinLatAttributeName, type: "numeric" },
{ name: kPinLongAttributeName, type: "numeric" },
{ name: kPinColorAttributeName, type: "color" }
]
}
]
});

// Create map if it doesn't exist
await createOrUpdateMap("Map");

// See if there are any existing pins
pluginState.updatePins();

// Set up a listener for changes to the pin dataset
addDataContextChangeListener(kPinDataContextName, notification => {
const { operation } = notification.values;

if (["createCases", "deleteCases", "updateCases"].includes(operation)) {
pluginState.updatePins();
}
});
// Set up a listener for pin selection
addDataContextChangeListener(kPinDataContextName, async notification => {
const { operation, result } = notification.values;
if (operation === "selectCases" && result.success) {
const selectedPins = await getSelectionList(kPinDataContextName);
const selectedPinValues: IMapPin[] = await Promise.all(
selectedPins.values.map(async (pin: any) => {
const pinItem = await getCaseByID(kPinDataContextName, pin.caseID);
if (pinItem.success) {
const pinValues = pinItem.values;
const pinCase = (pinValues as any).case;
return {
id: pinCase.id,
lat: pinCase.values.pinLat,
long: pinCase.values.pinLong,
color: pinCase.values.pinColor,
};
}
return null;
})
);
pluginState.setSelectedPins(selectedPinValues);
}

Check warning on line 178 in src/models/plugin-state.ts

View check run for this annotation

Codecov / codecov/patch

src/models/plugin-state.ts#L160-L178

Added lines #L160 - L178 were not covered by tests
});

// Set up a listener for case selection
addDataContextChangeListener(kDataContextName, async notification => {
const { operation, result } = notification.values;
if (operation === "selectCases" && result.success) {
const selectedCases = await getSelectionList(kDataContextName);
const selectedPinCases = selectedCases.values
.filter((sCase: any) => sCase.collectionName === kMapPinsCollectionName);
const selectedCaseValues: any[] = await Promise.all(
selectedPinCases.map(async (sCase: any) => {
const caseItem = await getCaseByID(kDataContextName, sCase.caseID);
if (caseItem.success) {
const caseValues = caseItem.values;
return {
id: caseValues.id,
label: caseValues.case.values.label,
pinColor: caseValues.case.values.pinColor,
};
}
return null;
})
);
pluginState.setSelectedCases(selectedCaseValues);
}

Check warning on line 203 in src/models/plugin-state.ts

View check run for this annotation

Codecov / codecov/patch

src/models/plugin-state.ts#L185-L203

Added lines #L185 - L203 were not covered by tests
});
}

export const pluginState = new PluginState();
71 changes: 27 additions & 44 deletions src/utils/codap-utils.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,7 @@
import { addDataContextChangeListener, codapInterface, initializePlugin, sendMessage
} from "@concord-consortium/codap-plugin-api";
import {
kPinDataContextName, kPinLatAttributeName, kPinLongAttributeName, kPinColorAttributeName,
kPluginName, kInitialDimensions, kVersion,
kDataContextName, kMapPinsCollectionName, kOneMonthInSeconds,
kMapName, kSliderComponentName, kChartGraphName, kXYGraphName,
} from "../data/constants";
import { pluginState } from "../models/plugin-state";

export async function initializeNeoPlugin() {
initializePlugin({ pluginName: kPluginName, version: kVersion, dimensions: kInitialDimensions });

// Create the pin dataset
await sendMessage("create", `dataContext`, {
name: kPinDataContextName,
collections: [
{
name: "Map Pins",
attrs: [
{ name: kPinLatAttributeName, type: "numeric" },
{ name: kPinLongAttributeName, type: "numeric" },
{ name: kPinColorAttributeName, type: "color" }
]
}
]
});

// Create map if it doesn't exist
await createOrUpdateMap("Map");

// See if there are any existing pins
pluginState.updatePins();

// Set up a listener for changes to the pin dataset
addDataContextChangeListener(kPinDataContextName, notification => {
const { operation } = notification.values;

if (["createCases", "deleteCases", "updateCases"].includes(operation)) {
pluginState.updatePins();
}
});

}
import { codapInterface, sendMessage }
from "@concord-consortium/codap-plugin-api";
import { kOneMonthInSeconds, kMapName, kMapPinsCollectionName, kDataContextName, kSliderComponentName,
kChartGraphName, kXYGraphName } from "../data/constants";

export async function createOrUpdateMap(title: string, url?: string): Promise<void> {
const mapProps: Record<string, any> = {
Expand Down Expand Up @@ -209,3 +169,26 @@
{ colormap: colorMap });
return updateColorMap;
};

export const getSelectionList = async (dataContext: string) => {
const selectionList = await sendMessage("get", `dataContext[${dataContext}].selectionList`);
if (selectionList.success) {
return selectionList.values;
} else {
console.error("Error getting selection list");
return [];
}
};

Check warning on line 181 in src/utils/codap-utils.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/codap-utils.ts#L174-L181

Added lines #L174 - L181 were not covered by tests

export const deleteSelectionList = async (dataContext: string) => {
await sendMessage("create", `dataContext[${dataContext}].selectionList`, []);
};

Check warning on line 185 in src/utils/codap-utils.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/codap-utils.ts#L184-L185

Added lines #L184 - L185 were not covered by tests

export const createSelectionList = async (dataContext: string, selectedCaseIds: string[]) => {
await sendMessage("create", `dataContext[${dataContext}].selectionList`, selectedCaseIds);
};

Check warning on line 189 in src/utils/codap-utils.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/codap-utils.ts#L188-L189

Added lines #L188 - L189 were not covered by tests

export const updateSelectionList = async (dataContext: string, selectedCaseIds: string[]) => {
const result = await sendMessage("update", `dataContext[${dataContext}].selectionList`, selectedCaseIds);
return result;
};

Check warning on line 194 in src/utils/codap-utils.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/codap-utils.ts#L192-L194

Added lines #L192 - L194 were not covered by tests
Loading