import {createContext, FC, useCallback, useContext, useEffect, useMemo, useState} from "react"; import {Group} from "../../src/types"; import {ReactNodeLike} from "prop-types"; import pako from "pako"; import {Dict} from "../../src/types"; import {fixedEncodeURIComponent} from "../../src/utils"; import {GroupRenameBody, GroupSetFactoryArrayBody, SetFactoryArrayBody} from "../../src/types/ApiSchemasFrontend"; interface Props { children: ReactNodeLike id: string initial: { groups: Dict ignored: string[] base: string[] } } interface GroupContextType { doNotSuggest: Set exportedFactories: Set ignoredFactories: string[] setIgnoredFactories(factories: string[]): void baseFactories: string[] setBaseFactories(factories: string[]): void groups: Dict addGroup(name: string, exported?: string[], malls?: string[]): void removeGroup(name: string): void renameGroup(name: string, newName: string): void setFactories(name: string, factories: string[], type: 'exports'|'malls'): void getInputType(uid: string): 'base'|'produced'|'unknown' store(): Uint8Array load(str: Uint8Array): void } const defaultValues: GroupContextType = { doNotSuggest: new Set(), exportedFactories: new Set(), ignoredFactories: [], setIgnoredFactories() {}, baseFactories: [], setBaseFactories() {}, groups: {}, addGroup() {}, removeGroup() {}, renameGroup() {}, setFactories() {}, getInputType() {return 'unknown'}, store() {return new Uint8Array(0)}, load() {} } const GroupContext = createContext(defaultValues); export const useGroups = () => useContext(GroupContext) interface StoredFile { groups: Dict, basicValues: string[], excludedSuggestions: string[] } export const postFetchJson = async (url: string, body: Dict) => { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) return res.json() } export const GroupProvider: FC = ({children, id, initial}) => { const [excludedSuggestions, _setExcludedSuggestions] = useState(initial.ignored) const [basicValues, _setBasicValues] = useState(initial.base) const [groups, setGroups] = useState>(initial.groups) const doNotSuggest = useMemo>(() => { return new Set([...Object.values(groups).flatMap(group => [...group.exports, ...group.malls]), ...excludedSuggestions, ...basicValues]) }, [basicValues, groups, excludedSuggestions]) const exportedFactories = useMemo>(() => { return new Set([...Object.values(groups).flatMap(group => [...group.exports])]) }, [groups]) const setExcludedSuggestions = useCallback((val) => { _setExcludedSuggestions(val) postFetchJson(`/api/${fixedEncodeURIComponent(id)}/factories`, { type: 'ignored', factories: val } as SetFactoryArrayBody) .catch(console.error) }, [id]) const setBasicValues = useCallback((val) => { _setBasicValues(val) postFetchJson(`/api/${fixedEncodeURIComponent(id)}/factories`, { type: 'base', factories: val } as SetFactoryArrayBody) .catch(console.error) }, [id]) const addGroup = useCallback((name: string, exports: string[] = [], malls: string[] = []) => { name = name.replace(/[.$]/g, '') if (name in groups) return false setGroups(groups => { groups[name] = { name, exports, malls } return {...groups} }) ;(async () => { await postFetchJson(`/api/${fixedEncodeURIComponent(id)}/group/${fixedEncodeURIComponent(name)}/add`, {}) if (exports.length) { await postFetchJson(`/api/${fixedEncodeURIComponent(id)}/group/${fixedEncodeURIComponent(name)}/factories`, { type: 'exports', factories: exports } as GroupSetFactoryArrayBody) } if (malls.length) { await postFetchJson(`/api/${fixedEncodeURIComponent(id)}/group/${fixedEncodeURIComponent(name)}/factories`, { type: 'malls', factories: exports } as GroupSetFactoryArrayBody) } })().catch(console.error) return true }, [groups, id]) const removeGroup = useCallback((name: string) => { name = name.replace(/[.$]/g, '') setGroups(groups => { delete groups[name] console.log(groups[name]) return {...groups} }) postFetchJson(`/api/${fixedEncodeURIComponent(id)}/group/${fixedEncodeURIComponent(name)}/remove`, {}) .catch(console.error) }, [id]) const renameGroup = useCallback((name: string, newName: string) => { name = name.replace(/[.$]/g, '') newName = newName.replace(/[.$]/g, '') if (newName in groups) return setGroups(groups => { groups[newName] = {...groups[name], name: newName} delete groups[name] return {...groups} }) postFetchJson(`/api/${fixedEncodeURIComponent(id)}/group/${fixedEncodeURIComponent(name)}/rename`, { newName } as GroupRenameBody) .catch(console.error) }, [groups, id]) const setFactories = useCallback((name: string, factories: string[], type: 'exports'|'malls') => { name = name.replace(/[.$]/g, '') setGroups(groups => { groups[name] = {...groups[name], [type]: factories} return {...groups} }) postFetchJson(`/api/${fixedEncodeURIComponent(id)}/group/${fixedEncodeURIComponent(name)}/factories`, { type, factories } as GroupSetFactoryArrayBody) .catch(console.error) }, [id]) const getInputType = useCallback((uid: string) => { if (basicValues.includes(uid)) return 'base' else if (exportedFactories.has(uid)) return 'produced' else return 'unknown' }, [basicValues, exportedFactories]) const store = useCallback(() => { const btoa = (bin: Uint8Array) => Buffer.from(bin).toString('base64') const value: StoredFile = { groups, basicValues, excludedSuggestions } const uncompressed = JSON.stringify(value) return pako.deflate(uncompressed) }, [basicValues, excludedSuggestions, groups]) const load = useCallback((compressed: Uint8Array) => { const atob = (str: string) => Buffer.from(str, 'base64') const uncompressed = pako.inflate(compressed, {to: "string"}) const value: StoredFile = JSON.parse(uncompressed) if (!value.groups || !value.basicValues || !value.excludedSuggestions) return setGroups(value.groups) setBasicValues(value.basicValues) setExcludedSuggestions(value.excludedSuggestions) }, []) const value: GroupContextType = useMemo(() => ({ doNotSuggest, exportedFactories, ignoredFactories: excludedSuggestions, setIgnoredFactories: setExcludedSuggestions, baseFactories: basicValues, setBaseFactories: setBasicValues, groups, addGroup, removeGroup, renameGroup, setFactories, getInputType, store, load }), [addGroup, basicValues, doNotSuggest, excludedSuggestions, exportedFactories, getInputType, groups, load, removeGroup, renameGroup, setBasicValues, setExcludedSuggestions, setFactories, store]) return {children} }