Skip to content

Commit

Permalink
refactor(anni): persistent state with contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
jannis-baum committed Jun 20, 2022
1 parent 1d5d687 commit 77d4c4a
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 129 deletions.
7 changes: 0 additions & 7 deletions annotation-interface/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,3 @@ export const brickUsages = [
'Recommendation',
] as const;
export type BrickUsage = typeof brickUsages[number];

export const displayCategories = ['All', ...brickUsages] as const;
export type DisplayCategory = typeof displayCategories[number];
export const displayCategoryForIndex = (index: number): DisplayCategory =>
displayCategories[index];
export const indexForDisplayCategory = (category: DisplayCategory): number =>
displayCategories.indexOf(category);
38 changes: 13 additions & 25 deletions annotation-interface/components/FilterTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,25 @@
import { Tab } from '@headlessui/react';
import { Dispatch, SetStateAction } from 'react';

import { supportedLanguages } from '../common/constants';
import {
supportedLanguages,
SupportedLanguage,
useBrickFilterContext,
displayCategories,
} from '../common/constants';
} from '../contexts/brickFilter';
import { useLanguageContext } from '../contexts/language';
import SelectionPopover from './SelectionPopover';

export type DisplayFilterProps = {
display: {
categoryIndex: number;
setCategoryIndex: Dispatch<SetStateAction<number>>;
language: SupportedLanguage;
setLanguage: Dispatch<SetStateAction<SupportedLanguage>>;
};
};
type Props = React.PropsWithChildren<
DisplayFilterProps & {
withLanguagePicker?: boolean;
withAllOption?: boolean;
}
>;
type Props = React.PropsWithChildren<{
withLanguagePicker?: boolean;
withAllOption?: boolean;
}>;

const FilterTabs: React.FC<Props> = ({
display,
withLanguagePicker,
withAllOption,
children,
}: Props) => {
const { language, setLanguage } = useLanguageContext();
const { categoryIndex, setCategoryIndex } = useBrickFilterContext();
const tabs = displayCategories.map((category, index) => {
if (!withAllOption && category === 'All') {
return <Tab key={index} />;
Expand All @@ -46,17 +37,14 @@ const FilterTabs: React.FC<Props> = ({
});

return (
<Tab.Group
selectedIndex={display.categoryIndex}
onChange={display.setCategoryIndex}
>
<Tab.Group selectedIndex={categoryIndex} onChange={setCategoryIndex}>
<Tab.List className="flex justify-between">
<div>{tabs}</div>
{withLanguagePicker && (
<SelectionPopover
options={[...supportedLanguages]}
selectedOption={display.language}
onSelect={(language) => display.setLanguage(language)}
selectedOption={language}
onSelect={setLanguage}
/>
)}
</Tab.List>
Expand Down
85 changes: 85 additions & 0 deletions annotation-interface/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import { createElement, PropsWithChildren } from 'react';

import { BrickFilterContextProvider } from '../contexts/brickFilter';
import { LanguageContextProvider } from '../contexts/language';

export type ContextProvider = ({ children }: PropsWithChildren) => JSX.Element;

interface TabDefinition {
activePaths: RegExp;
title: string;
linkPath: string;
providers: ContextProvider[];
}

export function ResolvedProviders({
providers,
children,
}: PropsWithChildren<{ providers: ContextProvider[] }>) {
if (providers.length === 0) return <>{children}</>;
return createElement(
providers[0],
null,
<ResolvedProviders providers={providers.slice(1)}>
{children}
</ResolvedProviders>,
);
}

const tabDefinitions: TabDefinition[] = [
{ activePaths: /^\/$/, title: 'Home', linkPath: '/', providers: [] },
{
activePaths: /^\/annotations.*$/,
title: 'Annotations',
linkPath: '/annotations',
providers: [LanguageContextProvider],
},
{
activePaths: /^\/bricks.*$/,
title: 'Bricks',
linkPath: '/bricks',
providers: [LanguageContextProvider, BrickFilterContextProvider],
},
];

const Layout = ({ children }: PropsWithChildren) => {
const router = useRouter();
const activeIndex = tabDefinitions.findIndex((definition) =>
router.pathname.match(definition.activePaths),
);
return (
<>
<div className="h-screen fixed px-8 py-16">
<ul className="space-y-2">
{tabDefinitions.map((tabDefinition, index) => (
<li
key={index}
className={`font-medium ${
index === activeIndex && 'underline'
}`}
>
<Link href={tabDefinition.linkPath}>
<a>{tabDefinition.title}</a>
</Link>
</li>
))}
</ul>
</div>
<div className="max-w-screen-md mx-auto pt-4">
{activeIndex === -1 ? (
children
) : (
<ResolvedProviders
providers={tabDefinitions[activeIndex].providers}
>
{children}
</ResolvedProviders>
)}
</div>
</>
);
};

export default Layout;
42 changes: 0 additions & 42 deletions annotation-interface/components/NavBar.tsx

This file was deleted.

43 changes: 43 additions & 0 deletions annotation-interface/contexts/brickFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
createContext,
Dispatch,
SetStateAction,
useContext,
useState,
} from 'react';

import { brickUsages } from '../common/constants';
import { ContextProvider } from '../components/Layout';

export const displayCategories = ['All', ...brickUsages] as const;
export type DisplayCategory = typeof displayCategories[number];
export const displayCategoryForIndex = (index: number): DisplayCategory =>
displayCategories[index];
export const indexForDisplayCategory = (category: DisplayCategory): number =>
displayCategories.indexOf(category);

interface IBrickFilterContext {
categoryIndex: number;
setCategoryIndex: Dispatch<SetStateAction<number>>;
}

const BrickFilterContext = createContext<IBrickFilterContext | undefined>(
undefined,
);

export const BrickFilterContextProvider: ContextProvider = ({ children }) => {
const [categoryIndex, setCategoryIndex] = useState(0);
return (
<BrickFilterContext.Provider
value={{ categoryIndex, setCategoryIndex }}
>
{children}
</BrickFilterContext.Provider>
);
};

export const useBrickFilterContext = (): IBrickFilterContext => {
const context = useContext(BrickFilterContext);
if (!context) throw Error('Missing provider for language context');
return context;
};
32 changes: 32 additions & 0 deletions annotation-interface/contexts/language.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
createContext,
Dispatch,
SetStateAction,
useContext,
useState,
} from 'react';

import { SupportedLanguage } from '../common/constants';
import { ContextProvider } from '../components/Layout';

interface ILanguageContext {
language: SupportedLanguage;
setLanguage: Dispatch<SetStateAction<SupportedLanguage>>;
}

const LanguageContext = createContext<ILanguageContext | undefined>(undefined);

export const LanguageContextProvider: ContextProvider = ({ children }) => {
const [language, setLanguage] = useState<SupportedLanguage>('English');
return (
<LanguageContext.Provider value={{ language, setLanguage }}>
{children}
</LanguageContext.Provider>
);
};

export const useLanguageContext = (): ILanguageContext => {
const context = useContext(LanguageContext);
if (!context) throw Error('Missing provider for language context');
return context;
};
24 changes: 4 additions & 20 deletions annotation-interface/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { useState } from 'react';

import { SupportedLanguage, supportedLanguages } from '../common/constants';
import NavBar from '../components/NavBar';
import Layout from '../components/Layout';

function AnnotationInterface({ Component, pageProps }: AppProps) {
const [displayLanguage, setDisplayLanguage] = useState<SupportedLanguage>(
supportedLanguages[0],
);
const [displayCategoryIndex, setDisplayCategoryIndex] = useState(0);

return (
<>
<Head>
<title>PharMe: Annotation Interface</title>
</Head>
<NavBar />
<div className="max-w-screen-md mx-auto pt-4">
<Component
{...pageProps}
display={{
categoryIndex: displayCategoryIndex,
setCategoryIndex: setDisplayCategoryIndex,
language: displayLanguage,
setLanguage: setDisplayLanguage,
}}
/>
</div>
<Layout>
<Component {...pageProps} />
</Layout>
</>
);
}
Expand Down
26 changes: 11 additions & 15 deletions annotation-interface/pages/bricks/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,28 @@ import {
InferGetServerSidePropsType,
} from 'next';

import { BrickUsage, brickUsages } from '../../common/constants';
import { useMountEffect } from '../../common/react-helpers';
import BrickForm from '../../components/BrickForm';
import FilterTabs from '../../components/FilterTabs';
import PageHeading from '../../components/PageHeading';
import {
BrickUsage,
brickUsages,
useBrickFilterContext,
DisplayCategory,
displayCategoryForIndex,
indexForDisplayCategory,
} from '../../common/constants';
import { useMountEffect } from '../../common/react-helpers';
import BrickForm from '../../components/BrickForm';
import FilterTabs, { DisplayFilterProps } from '../../components/FilterTabs';
import PageHeading from '../../components/PageHeading';
} from '../../contexts/brickFilter';
import dbConnect from '../../database/connect';
import TextBrick, { ITextBrick } from '../../database/models/TextBrick';

const EditBrick = ({
brick,
display,
}: InferGetServerSidePropsType<typeof getServerSideProps> &
DisplayFilterProps) => {
const categoryString: string = displayCategoryForIndex(
display.categoryIndex,
);
}: InferGetServerSidePropsType<typeof getServerSideProps>) => {
const { categoryIndex, setCategoryIndex } = useBrickFilterContext();
const categoryString: string = displayCategoryForIndex(categoryIndex);
useMountEffect(() => {
if (!(brickUsages as readonly string[]).includes(categoryString)) {
display.setCategoryIndex(
setCategoryIndex(
indexForDisplayCategory(brick.usage as DisplayCategory),
);
}
Expand All @@ -43,7 +40,6 @@ const EditBrick = ({
<FilterTabs
withLanguagePicker={false}
withAllOption={false}
display={display}
></FilterTabs>
<BrickForm usage={categoryString as BrickUsage} brick={brick} />
</>
Expand Down
Loading

0 comments on commit 77d4c4a

Please sign in to comment.