From e4250f0344890799d1af1b2e07e97c037cab229e Mon Sep 17 00:00:00 2001 From: Sebastian Seedorf Date: Wed, 10 Aug 2022 15:58:03 +0200 Subject: [PATCH] Added GroupProvider --- components/contexts/GroupProvider.tsx | 126 ++++++++++++++++++++++++++ components/home/Group/Group.tsx | 103 +++++++++------------ components/home/Home.tsx | 82 ++++------------- pages/_app.tsx | 5 +- src/hooks/useLocalStorage.ts | 6 +- src/types.ts | 12 ++- 6 files changed, 208 insertions(+), 126 deletions(-) create mode 100644 components/contexts/GroupProvider.tsx diff --git a/components/contexts/GroupProvider.tsx b/components/contexts/GroupProvider.tsx new file mode 100644 index 0000000..9e245f8 --- /dev/null +++ b/components/contexts/GroupProvider.tsx @@ -0,0 +1,126 @@ +import {createContext, FC, useContext, useMemo, useState} from "react"; +import {Group, NewGroup} from "../../src/types"; +import {useLocalStorage} from "../../src/hooks/useLocalStorage"; +import {sortByProperty} from "../../src/utils"; +import {ReactNodeLike} from "prop-types"; + +interface Props { + children: ReactNodeLike +} + +interface GroupContextType { + doNotSuggest: Set + + ignoredFactories: string[] + setIgnoredFactories(factories: string[]): void + + baseFactories: string[] + setBaseFactories(factories: string[]): void + + groups: NewGroup[] + addGroup(name: string, exported?: string[], malls?: string[]): void + removeGroup(idx: number): void + renameGroup(idx: number, newName: string): void + + getGroup(idx: number): NewGroup + setFactories(idx: number, factories: string[], type: 'inputs'|'intermediates'|'exports'|'malls'): void + + getInputType(uid: string): 'base'|'produced'|'unknown' +} + +const defaultValues: GroupContextType = { + doNotSuggest: new Set(), + + ignoredFactories: [], + setIgnoredFactories() {}, + + baseFactories: [], + setBaseFactories() {}, + + groups: [], + addGroup() {}, + removeGroup() {}, + renameGroup() {}, + + getGroup() {return {name: 'Lorem', inputs: [], intermediates: [], exports: [], malls: []}}, + setFactories() {}, + getInputType() {return 'unknown'} +} + +const GroupContext = createContext(defaultValues); +export const useGroups = () => useContext(GroupContext) + +export const GroupProvider: FC = ({children}) => { + const [excludedSuggestions, setExcludedSuggestions] = useLocalStorage('excludedSuggestions', []) + const [basicValues, setBasicValues] = useLocalStorage('basicValues', []) + const [oldGroups, setGroups] = useLocalStorage('serviceGroups', []) + const groups = useMemo(() => { + return oldGroups.map(groupToNewGroup) + }, [oldGroups]) + + const doNotSuggest = useMemo>(() => { + return new Set([...groups.flatMap(group => [...group.exports, ...group.malls, ...(group.malls.length ? group.intermediates : [])]), ...excludedSuggestions, ...basicValues]) + }, [basicValues, groups, excludedSuggestions]) + + const exportedFactories = useMemo>(() => { + return new Set([...groups.flatMap(group => [...group.exports])]) + }, [groups]) + + const value: GroupContextType = { + doNotSuggest, + + ignoredFactories: excludedSuggestions, + setIgnoredFactories: setExcludedSuggestions, + + baseFactories: basicValues, + setBaseFactories: setBasicValues, + + groups, + addGroup(name: string, exports: string[] = [], malls: string[] = []) { + setGroups(groups => { + groups.push({ name, inputs: [], intermediates: [], exports, malls }) + return groups.sort(sortByProperty(group => group.name)) + }) + }, + removeGroup(idx: number) { + setGroups(groups => { + groups.splice(idx, 1) + return groups + }) + }, + renameGroup(idx: number, newName: string) { + setGroups(groups => { + groups[idx].name = newName + return groups.sort(sortByProperty(group => group.name)) + }) + }, + + getGroup(idx: number) { + return groups[idx] + }, + setFactories(idx: number, factories: string[], type: 'inputs'|'intermediates'|'exports'|'malls') { + setGroups(groups => { + const group = groupToNewGroup(groups[idx]) + group[type] = factories + groups[idx] = group + return groups + }) + }, + getInputType(uid: string) { + if (basicValues.includes(uid)) return 'base' + else if (exportedFactories.has(uid)) return 'produced' + else return 'unknown' + } + } + return {children} +} + +function groupToNewGroup(group: Group): NewGroup { + return 'malls' in group ? group : { + name: group.name, + inputs: group.inputs, + intermediates: group.intermediates, + exports: group.isExported ? group.factories : [], + malls: !group.isExported ? group.factories : [] + } +} diff --git a/components/home/Group/Group.tsx b/components/home/Group/Group.tsx index 9d7b72a..85d87bf 100644 --- a/components/home/Group/Group.tsx +++ b/components/home/Group/Group.tsx @@ -4,58 +4,45 @@ import {useDetails} from "../../../src/hooks/useDetails"; import {Entity, Group} from "../../../src/types"; import styles from "./Group.module.css" import {EntitySpan} from "../EntitySpan/EntitySpan"; +import {useGroups} from "../../contexts/GroupProvider"; interface Props { - onRemove: () => void - onSetOutputFactories: (uids: string[]) => void - onSetIntermediateFactories: (uids: string[]) => void - onSetInputFactories: (uids: string[]) => void - onToggleExported: () => void - onRename: (name: string) => void - onDoIgnore: (name: string) => void - group: Group - sets: { - doNotSuggest: Set - basic: Set - exported: Set - } + index: number } -export const GroupBox: FC = ({ - onRemove, - onRename, - onSetOutputFactories, - onSetIntermediateFactories, - onSetInputFactories, - onToggleExported, - group: { +export const GroupBox: FC = ({ index }) => { + const details = useDetails() + const { + getGroup, + doNotSuggest, + setFactories, + ignoredFactories, + setIgnoredFactories, + renameGroup, + removeGroup, + getInputType + } = useGroups() + const { + name, inputs, intermediates, - factories, - isExported, - name - }, - sets: { - doNotSuggest, - basic, - exported - }, - onDoIgnore -}) => { - const details = useDetails() + exports, + malls + } = getGroup(index) const calculatedInputs = useMemo(() => { + const allProducingFactories = [...intermediates, ...exports, ...malls] const newData: string[] = details - .filter(detail => intermediates.includes(detail.href) || factories.includes(detail.href)) + .filter(detail => allProducingFactories.includes(detail.href)) .flatMap(detail => Object.keys(detail.recipe?.prerequisites ?? {})) const uniqueInputs = Array.from(new Set(newData)) return uniqueInputs - .filter(input => !intermediates.includes(input) && !factories.includes(input)) - }, [details, intermediates, factories]) + .filter(input => !allProducingFactories.includes(input)) + }, [details, intermediates, exports, malls]) const suggestions = useMemo(() => { - const availableIngredients = Array.from(new Set([...intermediates, ...factories, ...calculatedInputs, ...inputs])) - const selectedValues = Array.from(new Set([...intermediates, ...factories, ...inputs])) + const selectedValues = Array.from(new Set([...inputs, ...intermediates, ...exports, ...malls])) + const availableIngredients = Array.from(new Set([...selectedValues, ...calculatedInputs])) return details .filter(detail => { if (!detail.recipe) return false @@ -64,14 +51,10 @@ export const GroupBox: FC = ({ const prerequisites = Object.keys(detail.recipe?.prerequisites ?? {}) return prerequisites.every(pre => availableIngredients.includes(pre)) }) - }, [inputs, doNotSuggest, details, calculatedInputs, intermediates, factories]) + }, [doNotSuggest, details, calculatedInputs, inputs, intermediates, exports, malls]) - const addIntermediateFactory = (uid: string) => { - onSetIntermediateFactories([...intermediates, uid]) - } - - const addOutputFactory = (uid: string) => { - onSetOutputFactories([...factories, uid]) + const addFactory = (uid: string, type: Parameters[2]) => { + setFactories(index, [...intermediates, uid], 'intermediates') } return
@@ -81,27 +64,31 @@ export const GroupBox: FC = ({ suppressContentEditableWarning={true} onBlur={event => { event.currentTarget.innerText = event.currentTarget.innerText.trim() - onRename(event.currentTarget.innerText); + renameGroup(index, event.currentTarget.innerText); }} > {name} - - + setFactories(index, factories, 'inputs')} /> setFactories(index, factories, 'intermediates')} /> - + setFactories(index, factories, 'exports')} + /> + + setFactories(index, factories, 'malls')} />
@@ -110,8 +97,8 @@ export const GroupBox: FC = ({ { calculatedInputs.map(input =>
  • addIntermediateFactory(input)} - style={{color: basic.has(input) ? 'darkgreen' : exported.has(input) ? 'orange' : undefined}} + onClick={() => addFactory(input, 'intermediates')} + style={{color: {base: 'darkgreen', produced: 'orange', unknown: undefined}[getInputType(input)]}} leftClickText={"Add to intermediate factories"} />
  • ) } @@ -123,12 +110,12 @@ export const GroupBox: FC = ({ {suggestions.map(suggestion =>
  • addOutputFactory(suggestion.href)} + onClick={() => addFactory(suggestion.href, 'exports')} onContextMenu={event => { event.preventDefault() - onDoIgnore(suggestion.href) + setIgnoredFactories([...ignoredFactories, suggestion.href]) }} - leftClickText={"Add to output factories"} + leftClickText={"Add to exported factories"} rightClickText={"Exclude this recipe from suggestions"} />
  • )} diff --git a/components/home/Home.tsx b/components/home/Home.tsx index 00eb225..de7c700 100644 --- a/components/home/Home.tsx +++ b/components/home/Home.tsx @@ -8,60 +8,25 @@ import {useDetails} from "../../src/hooks/useDetails"; import {Entity, Group} from "../../src/types"; import pako from 'pako'; import {EntitySpan} from "./EntitySpan/EntitySpan"; +import {useGroups} from "../contexts/GroupProvider"; export const HomeComponent: FC = () => { const details = useDetails() + const { + groups, + addGroup, + doNotSuggest, + baseFactories, + setBaseFactories, + ignoredFactories, + setIgnoredFactories + } = useGroups() const [newGroupValue, setNewGroupValue] = useState("New group") - const [excludedSuggestions, setExcludedSuggestions] = useLocalStorage('excludedSuggestions', []) - const [basicValues, setBasicValues] = useLocalStorage('basicValues', []) - - const [groups, setGroups] = useLocalStorage('serviceGroups', []) - const [clientGroups, setClientGroups] = useState([]) - - const doNotSuggest = useMemo>(() => { - return new Set([...groups.flatMap(group => [...(group.name.includes('Mall') ? group.intermediates : []), ...group.factories]), ...excludedSuggestions, ...basicValues]) - }, [basicValues, groups, excludedSuggestions]) - - const basicSet = useMemo>(() => { - return new Set(basicValues) - }, [basicValues]) - - const exportedSet = useMemo>(() => { - return new Set(groups - .filter(group => group.isExported) - .flatMap(group => group.factories) - ) - }, [groups]) const missingFactories = useMemo(() => { return details.filter(detail => !doNotSuggest.has(detail.href) && detail.recipe) }, [details, doNotSuggest]) - useEffect(() => setClientGroups(groups), [groups]) - const removeGroup = (idx: number) => setGroups(groups => { - groups.splice(idx, 1) - return groups - }) - const appendGroup = useCallback((name: string, factories: string[] = []) => setGroups(groups => { - groups.push({name, factories, intermediates: [], inputs: []}) - groups.sort(sortByProperty(group => group.name)) - return groups - }), [setGroups]) - const setFactories = useCallback((idx: number, uids: string[], key: 'intermediates'|'factories'|'inputs') => setGroups(groups => { - groups[idx][key] = uids - return groups - }), [setGroups]) - const renameGroup = useCallback((idx: number, name: string) => setGroups(groups => { - groups[idx].name = name - groups.sort(sortByProperty(group => group.name)) - return groups - }), [setGroups]) - const toggleExported = useCallback((idx: number) => setGroups(groups => { - groups[idx].isExported = !groups[idx].isExported - groups.sort(sortByProperty(group => group.name)) - return groups - }), [setGroups]) - const store = () => { const string = JSON.stringify(groups) const btoa = (bin: Uint8Array) => Buffer.from(bin).toString('base64') @@ -78,22 +43,22 @@ export const HomeComponent: FC = () => {
    Basic Values
    Ignored Values
    Add new groups setNewGroupValue(e.target.value)}/>
    { - clientGroups.map((group, idx) => removeGroup(idx)} - onRename={(name: string) => renameGroup(idx, name)} - onSetOutputFactories={uids => setFactories(idx, uids, 'factories')} - onSetIntermediateFactories={uids => setFactories(idx, uids, 'intermediates')} - onSetInputFactories={uids => setFactories(idx, uids, 'inputs')} - onToggleExported={() => toggleExported(idx)} - sets={{doNotSuggest, basic: basicSet, exported: exportedSet}} - onDoIgnore={uid => setExcludedSuggestions([...excludedSuggestions, uid])} - />) + groups.map((group, idx) => ) }
    diff --git a/pages/_app.tsx b/pages/_app.tsx index 3f5c9d5..6c8d204 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,8 +1,11 @@ import '../styles/globals.css' import type { AppProps } from 'next/app' +import {GroupProvider} from "../components/contexts/GroupProvider"; function MyApp({ Component, pageProps }: AppProps) { - return + return + + } export default MyApp diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts index 51c7ec8..944f33f 100644 --- a/src/hooks/useLocalStorage.ts +++ b/src/hooks/useLocalStorage.ts @@ -21,7 +21,7 @@ export function useLocalStorage(key: string, initialValue: T): [T, SetValue { - // Prevent build error "window is undefined" but keep keep working + // Prevent build error "window is undefined" but keep working if (typeof window === 'undefined') { return initialValue } @@ -88,7 +88,9 @@ export function useLocalStorage(key: string, initialValue: T): [T, SetValue(initialValue) + useEffect(() => setClientStoredValue(storedValue), [storedValue]) + return [clientStoredValue, setValue] } // A wrapper for "JSON.parse()"" to support "undefined" value diff --git a/src/types.ts b/src/types.ts index 6a40d73..9ad13e2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,10 +14,20 @@ export interface Entity extends UnfetchedEntity { recipe?: Recipe } -export interface Group { +export type Group = OldGroup|NewGroup + +export interface OldGroup { name: string isExported?: boolean factories: string[] intermediates: string[] inputs: string[] } + +export interface NewGroup { + name: string + inputs: string[] + intermediates: string[] + exports: string[] + malls: string[] +}