Skip to content

Commit

Permalink
Move useCodapState hook to context provider + create components for t…
Browse files Browse the repository at this point in the history
…able cells instead of using mapping functions.
  • Loading branch information
lublagg committed Sep 16, 2024
1 parent 2e11cab commit fca3b76
Show file tree
Hide file tree
Showing 18 changed files with 585 additions and 367 deletions.
53 changes: 53 additions & 0 deletions src/components/CodapContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { createContext, ReactNode, useContext, useEffect } from "react";
import { ICollection, ICollections, IDataSet } from "../types";
import { InteractiveState, useCodapState } from "../hooks/useCodapState";

export type CodapState = {
init: () => Promise<void>,
handleSelectSelf: () => Promise<void>,
dataSets: IDataSet[],
selectedDataSet: any,
collections: ICollections,
handleSetCollections: (collections: ICollections) => void,
handleSelectDataSet: (name: string) => void,
getCollectionNameFromId: (id: number) => string | undefined,
handleUpdateInteractiveState: (update: Partial<InteractiveState>) => void,
interactiveState: InteractiveState,
cases: any[],
connected: boolean,
handleUpdateAttributePosition: (coll: ICollection, attrName: string, position: number) => Promise<void>,
handleAddCollection: (newCollectionName: string) => Promise<void>,
handleSortAttribute: (context: string, attrId: number, isDescending: boolean) => Promise<void>,
handleAddAttribute: (collection: ICollection, attrName: string) => Promise<void>,
updateTitle: (title: string) => Promise<void>,
selectCODAPCases: (caseIds: number[]) => Promise<void>,
listenForSelectionChanges: (callback: (notification: any) => void) => void,
handleCreateCollectionFromAttribute: (collection: ICollection, attr: any, parent: number|string) => Promise<void>,
handleUpdateCollections: () => Promise<void>
};

const CodapContext = createContext({} as CodapState);


export const useCodapContext = () => {
return useContext(CodapContext);
};

export const CodapProvider = ({ children }: { children: ReactNode }) => {
const codapState = useCodapState(); // Now useCodapState is called here

useEffect(() => {
codapState.init();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (!codapState.connected) {
return <div>Loading...</div>; // Handle loading state in the provider itself
}

return (
<CodapContext.Provider value={codapState}>
{children}
</CodapContext.Provider>
);
};
37 changes: 37 additions & 0 deletions src/components/FocusContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { createContext, useContext, useRef, useState } from "react";

type FocusedCellInfo = {
caseId: string;
attrTitle: string;
};

interface FocusContextValue {
inputRefs: React.MutableRefObject<Record<string, HTMLInputElement | null>>;
focusedCell: FocusedCellInfo|null;
setFocusedCell: (cellInfo: FocusedCellInfo) => void;
}

const defaultContextValue: FocusContextValue = {
inputRefs: { current: {} },
focusedCell: null,
// eslint-disable-next-line @typescript-eslint/no-empty-function
setFocusedCell: (cellInfo: FocusedCellInfo) => {},
};

// Create a context for managing focus
const FocusContext = createContext(defaultContextValue);

export const useFocusContext = () => {
return useContext(FocusContext);
};

export const FocusProvider = ({children}: {children: React.ReactNode}) => {
const inputRefs = useRef<Record<string, HTMLInputElement | null>>({});
const [focusedCell, setFocusedCell] = useState<FocusedCellInfo | null>(null);

return (
<FocusContext.Provider value={{ inputRefs, focusedCell, setFocusedCell }}>
{children}
</FocusContext.Provider>
);
};
110 changes: 24 additions & 86 deletions src/components/app.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,27 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { InteractiveState, useCodapState } from "../hooks/useCodapState";
import React, { useCallback, useEffect, useState } from "react";
import { InteractiveState } from "../hooks/useCodapState";
import { NestedTable } from "./nested-table";
import { Hierarchy } from "./hierarchy-view/hierarchy";
import { CardView } from "./card-view/card-view";
import { ICaseObjCommon } from "../types";
import { useCodapContext } from "./CodapContext";

import css from "./app.scss";

function App() {
const {connected, selectedDataSet, dataSets, collections, cases, interactiveState,
updateInteractiveState: _updateInteractiveState, init,
handleSelectDataSet: _handleSelectDataSet, handleUpdateAttributePosition,
handleAddCollection, handleAddAttribute, handleSetCollections, handleSelectSelf,
updateTitle, selectCODAPCases, listenForSelectionChanges,
handleCreateCollectionFromAttribute, handleUpdateCollections
} = useCodapState();
const { handleUpdateInteractiveState, handleSelectDataSet, interactiveState, selectedDataSet, dataSets,
connected, handleSelectSelf } = useCodapContext();

useEffect(() => {
init();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const updateInteractiveState = useCallback((update: Partial<InteractiveState>) => {
const newState = {...interactiveState, ...update};
if (JSON.stringify(newState) !== JSON.stringify(interactiveState)) {
_updateInteractiveState(newState);
}
}, [interactiveState, _updateInteractiveState]);
const [view, setView] = useState<InteractiveState["view"]>(null);

const handleSetView = useCallback((view: InteractiveState["view"]) => {
updateInteractiveState({view});
}, [updateInteractiveState]);
const handleSetView = useCallback((newView: InteractiveState["view"]) => {
setView(newView);
handleUpdateInteractiveState({view: newView});
}, [handleUpdateInteractiveState]);

const handleSelectDataSet = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
const dataSetName = e.target.value;
_handleSelectDataSet(dataSetName);
updateInteractiveState({dataSetName});
}, [_handleSelectDataSet, updateInteractiveState]);

const handleShowComponent = () => {
handleSelectSelf();
};
const onSelectDataSet = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
const name = e.target.value;
handleSelectDataSet(name);
}, [handleSelectDataSet]);

const renderSelectView = () => {
return (
Expand All @@ -58,91 +39,48 @@ function App() {
// select the saved dataset on startup
useEffect(() => {
if (interactiveState?.dataSetName && !selectedDataSet) {
_handleSelectDataSet(interactiveState.dataSetName);
handleSelectDataSet(interactiveState.dataSetName);
}
}, [interactiveState, selectedDataSet, _handleSelectDataSet]);
}, [interactiveState, selectedDataSet, handleSelectDataSet]);

// unselect the dataset if it is deleted
useEffect(() => {
if (selectedDataSet && !dataSets.find(ds => ds.id === selectedDataSet.id)) {
_handleSelectDataSet("");
handleSelectDataSet("");
}
}, [interactiveState, dataSets, selectedDataSet, _handleSelectDataSet]);

const listeningToDataSetId = useRef(0);
const [codapSelectedCase, setCodapSelectedCase] = useState<ICaseObjCommon|undefined>(undefined);
useEffect(() => {
if (selectedDataSet && listeningToDataSetId.current !== selectedDataSet.id) {
listenForSelectionChanges((notification) => {
const result = notification?.values?.result;
let newCase: ICaseObjCommon|undefined = undefined;
if (result?.success && result.cases?.length >= 0) {
newCase = result.cases[0];
}
setCodapSelectedCase(newCase);
});
listeningToDataSetId.current = selectedDataSet.id;
}
}, [selectedDataSet, listenForSelectionChanges, setCodapSelectedCase]);
}, [interactiveState, dataSets, selectedDataSet, handleSelectDataSet]);

if (!connected) {
return <div className={css.loading}>Loading...</div>;
}

switch (interactiveState.view) {
switch (view) {
case "nested-table":
return (
<NestedTable
onSelectDataSet={onSelectDataSet}
selectedDataSet={selectedDataSet}
dataSets={dataSets}
collections={collections}
cases={cases}
interactiveState={interactiveState}
handleSelectDataSet={handleSelectDataSet}
updateInteractiveState={updateInteractiveState}
handleShowComponent={handleShowComponent}
handleUpdateAttributePosition={handleUpdateAttributePosition}
handleSetCollections={handleSetCollections}
handleCreateCollectionFromAttribute={handleCreateCollectionFromAttribute}
handleUpdateCollections={handleUpdateCollections}
/>
);

case "hierarchy":
return (
<Hierarchy
selectedDataSet={selectedDataSet}
dataSets={dataSets}
collections={collections}
interactiveState={interactiveState}
handleSelectDataSet={handleSelectDataSet}
handleUpdateAttributePosition={handleUpdateAttributePosition}
updateInteractiveState={updateInteractiveState}
handleAddCollection={handleAddCollection}
handleAddAttribute={handleAddAttribute}
handleSetCollections={handleSetCollections}
handleShowComponent={handleShowComponent}
onSelectDataSet={onSelectDataSet}
/>
);

case "card-view":
return (
<CardView
selectedDataSet={selectedDataSet}
dataSets={dataSets}
collections={collections}
interactiveState={interactiveState}
handleSelectDataSet={handleSelectDataSet}
updateTitle={updateTitle}
selectCases={selectCODAPCases}
codapSelectedCase={codapSelectedCase}
onSelectDataSet={onSelectDataSet}
/>
);

default:
return (
<div onClick={handleShowComponent}>
{renderSelectView()}
<div onClick={handleSelectSelf}>
{ dataSets.length === 0 ? <p>No datasets available</p> : renderSelectView() }
</div>
);
}
Expand Down
42 changes: 25 additions & 17 deletions src/components/card-view/card-view.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import React, { useEffect, useMemo } from "react";
import { InteractiveState } from "../../hooks/useCodapState";
import { IDataSet, ICollections, ICaseObjCommon, ICollection } from "../../types";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useCodapState } from "../../hooks/useCodapState";
import { ICaseObjCommon, ICollection } from "../../types";
import { Menu } from "../menu";
import { CaseView } from "./case-view";

import css from "./card-view.scss";

interface ICardViewProps {
selectedDataSet: any;
dataSets: IDataSet[];
collections: ICollections;
interactiveState: InteractiveState
handleSelectDataSet: (e: React.ChangeEvent<HTMLSelectElement>) => void
updateTitle: (title: string) => Promise<void>
selectCases: (caseIds: number[]) => Promise<void>
codapSelectedCase: ICaseObjCommon|undefined;
onSelectDataSet: (e: React.ChangeEvent<HTMLSelectElement>) => void
}

export const CardView = (props: ICardViewProps) => {
const {collections, dataSets, selectedDataSet, updateTitle, selectCases, codapSelectedCase,
handleSelectDataSet} = props;
const { onSelectDataSet } = props;
const { dataSets, selectedDataSet, collections, selectCODAPCases, listenForSelectionChanges,

Check warning on line 15 in src/components/card-view/card-view.tsx

View workflow job for this annotation

GitHub Actions / Build and Run Jest Tests

'dataSets' is assigned a value but never used

Check warning on line 15 in src/components/card-view/card-view.tsx

View workflow job for this annotation

GitHub Actions / S3 Deploy

'dataSets' is assigned a value but never used
updateTitle } = useCodapState();
const listeningToDataSetId = useRef(0);
const [codapSelectedCase, setCodapSelectedCase] = useState<ICaseObjCommon|undefined>(undefined);

const rootCollection = useMemo(() => {
return collections.find((c: ICollection) => !c.parent);
Expand All @@ -41,6 +37,20 @@ export const CardView = (props: ICardViewProps) => {
}
}, [selectedDataSet, updateTitle]);

useEffect(() => {
if (selectedDataSet && listeningToDataSetId.current !== selectedDataSet.id) {
listenForSelectionChanges((notification) => {
const result = notification?.values?.result;
let newCase: ICaseObjCommon|undefined = undefined;
if (result?.success && result.cases?.length >= 0) {
newCase = result.cases[0];
}
setCodapSelectedCase(newCase);
});
listeningToDataSetId.current = selectedDataSet.id;
}
}, [selectedDataSet, listenForSelectionChanges, setCodapSelectedCase]);

// array of case ids from root collection down to selected case
const codapSelectedCaseLineage = useMemo<number[]>(() => {
if (!codapSelectedCase) {
Expand Down Expand Up @@ -69,9 +79,7 @@ export const CardView = (props: ICardViewProps) => {
if (!selectedDataSet || !rootCollection) {
return (
<Menu
dataSets={dataSets}
selectedDataSet={selectedDataSet}
handleSelectDataSet={handleSelectDataSet}
onSelectDataSet={onSelectDataSet}
/>
);
}
Expand All @@ -83,7 +91,7 @@ export const CardView = (props: ICardViewProps) => {
cases={rootCollection.cases}
attrs={attrs}
level={0}
selectCases={selectCases}
selectCases={selectCODAPCases}
codapSelectedCaseLineage={codapSelectedCaseLineage}
/>
</div>
Expand Down
7 changes: 1 addition & 6 deletions src/components/draggable-table-tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,10 @@ interface DraggagleTableDataProps {
isParent?: boolean;
resizeCounter?: number;
parentLevel?: number;
selectedDataSetName: string;
handleUpdateCollections: () => void;
}

export const DraggagleTableData: React.FC<PropsWithChildren<DraggagleTableDataProps>> = (props) => {
const {collectionId, attrTitle, children, caseId, isParent, resizeCounter, parentLevel=0,
selectedDataSetName, handleUpdateCollections} = props;
const {collectionId, attrTitle, children, caseId, isParent, resizeCounter, parentLevel=0} = props;
const {dragOverId, dragSide} = useDraggableTableContext();
const {style} = getIdAndStyle(collectionId, attrTitle, dragOverId, dragSide);
const {tableScrollTop, scrollY} = useTableTopScrollTopContext();
Expand Down Expand Up @@ -213,9 +210,7 @@ export const DraggagleTableData: React.FC<PropsWithChildren<DraggagleTableDataPr
return (
<EditableTableCell
attrTitle={attrTitle}
handleUpdateCollections={handleUpdateCollections}
caseId={caseId}
selectedDataSetName={selectedDataSetName}
>
{children}
</EditableTableCell>
Expand Down
Loading

0 comments on commit fca3b76

Please sign in to comment.