-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
187887881 selected row sync #24
Changes from all commits
91564c2
9522b37
9537a87
b5b247b
0c23785
11e2bdb
5923296
56709a0
7405f54
43a003a
7b64ef9
da37943
8c9d5db
b8fefac
a2159f6
38df5f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,86 @@ | ||
import React, { useEffect, useState, useRef } from "react"; | ||
import React, { useEffect, useState, useRef, useCallback } from "react"; | ||
import { clsx } from "clsx"; | ||
import { ILocation } from "../types"; | ||
import { kInitialDimensions, kVersion, kPluginName, kDefaultOnAttributes, kSimulationTabDimensions, kDataContextName } from "../constants"; | ||
import { initializePlugin, codapInterface, addDataContextChangeListener, ClientNotification } from "@concord-consortium/codap-plugin-api"; | ||
import { ICurrentDayLocation, ILocation } from "../types"; | ||
import { debounce } from "../grasp-seasons/utils/utils"; | ||
import { kInitialDimensions, kVersion, kPluginName, kDefaultOnAttributes, kSimulationTabDimensions, kDataContextName, kChildCollectionName } from "../constants"; | ||
import { initializePlugin, codapInterface, selectSelf, addDataContextChangeListener, ClientNotification, getCaseByID } from "@concord-consortium/codap-plugin-api"; | ||
import { useCodapData } from "../hooks/useCodapData"; | ||
import { LocationTab } from "./location-tab"; | ||
import { SimulationTab } from "./simulation-tab"; | ||
import { Header } from "./header"; | ||
|
||
import "../assets/scss/App.scss"; | ||
|
||
const updateRowSelectionInCodap = (latitude: string, longitude: string, day: number) => { | ||
// TODO: Issue CODAP API request to highlight appropriate case in the case table, using combination of | ||
// Math.floor(day), latitude, and longitude. | ||
} | ||
const debouncedUpdateRowSelectionInCodap = debounce(( | ||
latitude: string, | ||
longitude: string, | ||
day: number | ||
) => { | ||
codapInterface.sendRequest({ | ||
action: "get", | ||
resource: `dataContext[${kDataContextName}].collection[${kChildCollectionName}].caseSearch[calcId==${latitude},${longitude},${Math.floor(day)}]` | ||
}).then((result: any) => { | ||
if (result.success && result.values.length > 0) { | ||
const caseID = result.values[0].id; | ||
return codapInterface.sendRequest({ | ||
action: "create", | ||
resource: `dataContext[${kDataContextName}].selectionList`, | ||
values: [caseID] | ||
}); | ||
} else { | ||
return null; | ||
} | ||
}).then((selectionResult: any) => { | ||
if (!selectionResult.success) { | ||
console.warn("Selection result was not successful", selectionResult); | ||
} | ||
}).catch((error: any) => { | ||
console.error("Error in selection process:", error); | ||
}); | ||
}, 250); | ||
|
||
export const App: React.FC = () => { | ||
const [activeTab, setActiveTab] = useState<"location" | "simulation">("location"); | ||
const [latitude, setLatitude] = useState(""); | ||
const [longitude, setLongitude] = useState(""); | ||
const [dayOfYear, /*setDayOfYear */] = useState(171); | ||
const [dayOfYear, setDayOfYear] = useState(171); | ||
const [locations, setLocations] = useState<ILocation[]>([]); | ||
const [locationSearch, setLocationSearch] = useState<string>(""); | ||
const [selectedAttrs, setSelectedAttributes] = useState<string[]>(kDefaultOnAttributes); | ||
const [dataContext, setDataContext] = useState<any>(null); | ||
|
||
const handleDayUpdateInTheSimTab = (day: number) => { | ||
// console.log("The day of the year has been updated in the simulation tab to: ", day); // TODO: implement this | ||
// We might to debounce this call, as if the animation is on, or user is dragging the slider, there will be | ||
// lot of events and API calls to CODAP. | ||
updateRowSelectionInCodap(latitude, longitude, Math.floor(day)); | ||
// Note that we do not need to update dayOfYear state variable. It's useful only for the opposite direction | ||
// of the sync process, when user select a row in CODAP and we want to update the day in the simulation tab. | ||
}; | ||
|
||
// TODO: Handle case selection - sync sim tab with CODAP selection | ||
// const handleCaseSelectionInCodap = (_latitude: string, _longitude: string, day: number) => { | ||
// // Option 1. Update as much of the plugin state as we can when user selects a case in CODAP. I think this might | ||
// // be too much, as it'll clear all the inputs in all the tabs and the user will have to re-enter everything | ||
// // if they were in the middle of something. | ||
// // setDayOfYear(day); | ||
// // setLatitude(_latitude); | ||
// // setLongitude(_longitude); | ||
// // ...OR... | ||
// // Option 2. Update only the day of the year, as that's reasonably unobtrusive and useful. We can first check | ||
// // if user actually selected the case from the same location, and only then update the day of the year. | ||
// if (latitude === _latitude && longitude === _longitude) { | ||
// setDayOfYear(day); | ||
// } | ||
// } | ||
const currentDayLocationRef = useRef<ICurrentDayLocation>({ | ||
_latitude: "", | ||
_longitude: "", | ||
_dayOfYear: 171 | ||
}); | ||
|
||
const { getUniqueLocationsInCodapData } = useCodapData(); | ||
// Store a ref to getUniqueLocationsInCodapData so we can call inside useEffect without triggering unnecessary re-runs | ||
const getUniqueLocationsRef = useRef(getUniqueLocationsInCodapData); | ||
|
||
const handleDayUpdateInTheSimTab = (day: number) => { | ||
currentDayLocationRef.current._dayOfYear = day; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this line can be anywhere in the functional component body, e.g. right below state declarations. As any state update will trigger re-render, and this line will be executed during this re-render. |
||
debouncedUpdateRowSelectionInCodap( | ||
currentDayLocationRef.current._latitude, | ||
currentDayLocationRef.current._longitude, | ||
day | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is not part of any |
||
); | ||
}; | ||
|
||
const handleCaseSelectionInCodap = useCallback(( | ||
selectedLatitude: string, | ||
selectedLongitude: string, | ||
selectedDay: number | ||
) => { | ||
const { _latitude, _longitude, _dayOfYear } = currentDayLocationRef.current; | ||
const rowInLocation = `${_latitude},${_longitude}` === `${selectedLatitude},${selectedLongitude}`; | ||
const newDayChoice = `${_dayOfYear}` !== `${selectedDay}`; | ||
if (rowInLocation && newDayChoice) { | ||
setDayOfYear(selectedDay); | ||
currentDayLocationRef.current._dayOfYear = selectedDay; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, probably you don't need this ref update here. |
||
} | ||
}, []); | ||
|
||
useEffect(() => { | ||
const initialize = async () => { | ||
try { | ||
|
@@ -65,38 +92,66 @@ | |
} catch (e) { | ||
console.error("Failed to initialize plugin, error:", e); | ||
} | ||
|
||
const casesDeletedFromCodapListener = async (listenerRes: ClientNotification) => { | ||
const { resource, values } = listenerRes; | ||
const isResource = resource === `dataContextChangeNotice[${kDataContextName}]`; | ||
if (!isResource) return; | ||
|
||
const casesDeleted = | ||
values.operation === "selectCases" | ||
&& values.result.cases | ||
&& values.result.cases.length === 0 | ||
&& values.result.success; | ||
|
||
if ( casesDeleted ) { | ||
const uniqeLocations = await getUniqueLocationsRef.current(); | ||
if (uniqeLocations) setLocations(uniqeLocations); | ||
} | ||
}; | ||
addDataContextChangeListener(kDataContextName, casesDeletedFromCodapListener); | ||
}; | ||
|
||
initialize(); | ||
}, []); | ||
|
||
const handleDataContextChange = useCallback(async (listenerRes: ClientNotification) => { | ||
console.log("| dataContextChangeNotice: ", listenerRes); | ||
Check warning on line 101 in src/components/App.tsx GitHub Actions / Build and Run Jest Tests
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. console.log to remove? |
||
const { resource, values } = listenerRes; | ||
const isResource = resource === `dataContextChangeNotice[${kDataContextName}]`; | ||
if (!isResource || !values.result.success) return; | ||
|
||
const casesDeleted = values.operation === "selectCases" && values.result.cases.length === 0 | ||
const caseSelected = values.operation === "selectCases" && values.result.cases.length === 1; | ||
|
||
//TODO: there is an unhandled path when we edit the location name | ||
// we can use this to update the location name in the UI | ||
|
||
if (casesDeleted) { | ||
const uniqueLocations = await getUniqueLocationsRef.current(); | ||
if (uniqueLocations) setLocations(uniqueLocations); | ||
} | ||
else if (caseSelected) { | ||
const parentCaseId = values.result.cases[0].parent; | ||
const selectedDay = values.result.cases[0].values.dayOfYear; | ||
const parentCase = await getCaseByID(kDataContextName, parentCaseId); | ||
const selectedLatitude = parentCase.values.case.values.latitude; | ||
const selectedLongitude = parentCase.values.case.values.longitude; | ||
handleCaseSelectionInCodap( | ||
selectedLatitude, | ||
selectedLongitude, | ||
selectedDay | ||
); | ||
} | ||
}, [handleCaseSelectionInCodap]); | ||
|
||
useEffect(() => { | ||
addDataContextChangeListener(kDataContextName, handleDataContextChange); | ||
}, [handleDataContextChange]); | ||
|
||
useEffect(() => { | ||
currentDayLocationRef.current = { | ||
_latitude: latitude, | ||
_longitude: longitude, | ||
_dayOfYear: dayOfYear | ||
}; | ||
}, [latitude, longitude, dayOfYear]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you don't need to wrap that in useEffect. It can be anywhere in the functional component body. I usually place it right after the ref declaration. |
||
|
||
const handleTabClick = (tab: "location" | "simulation") => { | ||
setActiveTab(tab); | ||
// Update dimensions of the plugin window when switching tabs. | ||
codapInterface.sendRequest({ | ||
action: "update", | ||
resource: "interactiveFrame", | ||
values: { | ||
dimensions: tab === "location" ? kInitialDimensions : kSimulationTabDimensions | ||
} | ||
}).then(() => { | ||
// This brings the plugin window to the front within CODAP | ||
selectSelf(); | ||
}).catch((error) => { | ||
console.error("Error updating dimensions or selecting self:", error); | ||
}); | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { useState } from "react"; | ||
import { useCallback, useState } from "react"; | ||
import { kDataContextName, kChildCollectionName, kParentCollectionName, kParentCollectionAttributes, kChildCollectionAttributes } from "../constants"; | ||
import { DaylightCalcOptions, ILocation } from "../types"; | ||
import { getDayLightInfo, locationsEqual } from "../utils/daylight-utils"; | ||
|
@@ -23,6 +23,7 @@ | |
if (result.success) { | ||
let dc = result.values; | ||
let lastCollection = dc.collections[dc.collections.length - 1]; | ||
console.trace(); | ||
Check warning on line 26 in src/hooks/useCodapData.ts GitHub Actions / Build and Run Jest Tests
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. debug line to remove? |
||
return await codapInterface.sendRequest({ | ||
action: "delete", | ||
resource: `dataContext[${kDataContextName}].collection[${lastCollection.name}].allCases` | ||
|
@@ -37,7 +38,7 @@ | |
const calcOptions: DaylightCalcOptions = { | ||
latitude: location.latitude, | ||
longitude: location.longitude, | ||
year: 2024 // NOTE: If data are to be historical, add dynamic year attribute | ||
year: new Date().getFullYear() | ||
}; | ||
|
||
const solarEvents = getDayLightInfo(calcOptions); | ||
|
@@ -67,6 +68,7 @@ | |
longitude: location.longitude, | ||
location: location.name, | ||
date: solarEvent.day, | ||
dayOfYear: solarEvent.dayOfYear, | ||
rawSunrise: solarEvent.rawSunrise, | ||
rawSunset: solarEvent.rawSunset, | ||
"Day length": solarEvent.dayLength, | ||
|
@@ -83,7 +85,7 @@ | |
} | ||
}; | ||
|
||
const updateAttributeVisibility = (attributeName: string, hidden: boolean) => { | ||
const updateAttributeVisibility = useCallback((attributeName: string, hidden: boolean) => { | ||
if (!dataContext) return; | ||
|
||
try { | ||
|
@@ -97,7 +99,7 @@ | |
} catch (error) { | ||
console.error("Error updating attribute visibility:", error); | ||
} | ||
}; | ||
}, [dataContext]); | ||
|
||
const extractUniqueLocations = (allItems: any): ILocation[] => { | ||
const uniqueLocations: ILocation[] = []; | ||
|
@@ -117,7 +119,6 @@ | |
return uniqueLocations; | ||
} | ||
|
||
|
||
const getUniqueLocationsInCodapData = async () => { | ||
const locationAttr = await getAttribute(kDataContextName, kParentCollectionName, "location"); | ||
if (locationAttr.success){ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't that line fail with
null
passed as selectionResult when the block above returnsnull
?