diff --git a/src/EsiProvider/EsiProvider.tsx b/src/EsiProvider/EsiProvider.tsx index 229a9ba..99a1160 100644 --- a/src/EsiProvider/EsiProvider.tsx +++ b/src/EsiProvider/EsiProvider.tsx @@ -8,6 +8,8 @@ import { getSkills } from "./EsiSkills"; import { getCharFittings } from "./EsiFittings"; import { EveDataContext } from "../EveDataProvider"; +import { useLocalStorage } from "../Helpers/LocalStorage"; + export interface EsiCharacter { name: string; skills?: Record; @@ -48,32 +50,6 @@ export interface EsiProps { children: React.ReactNode; } -const useLocalStorage = function (key: string, initialValue: T) { - const [storedValue, setStoredValue] = React.useState(() => { - if (typeof window === 'undefined') return initialValue; - - const item = window.localStorage.getItem(key); - return item ? JSON.parse(item) : initialValue; - }); - - const setValue = React.useCallback((value: T | ((val: T) => T)) => { - if (typeof window === 'undefined') return; - if (storedValue == value) return; - - const valueToStore = value instanceof Function ? value(storedValue) : value; - setStoredValue(valueToStore); - - if (valueToStore === undefined) { - window.localStorage.removeItem(key); - return; - } - - window.localStorage.setItem(key, JSON.stringify(valueToStore)); - }, [key, storedValue]); - - return [ storedValue, setValue ] as const; -} - /** * Keeps track (in local storage) of ESI characters and their refresh token. */ diff --git a/src/Helpers/LocalStorage.tsx b/src/Helpers/LocalStorage.tsx new file mode 100644 index 0000000..90947d3 --- /dev/null +++ b/src/Helpers/LocalStorage.tsx @@ -0,0 +1,27 @@ +import React from "react"; + +export const useLocalStorage = function (key: string, initialValue: T) { + const [storedValue, setStoredValue] = React.useState(() => { + if (typeof window === 'undefined') return initialValue; + + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + }); + + const setValue = React.useCallback((value: T | ((val: T) => T)) => { + if (typeof window === 'undefined') return; + if (storedValue == value) return; + + const valueToStore = value instanceof Function ? value(storedValue) : value; + setStoredValue(valueToStore); + + if (valueToStore === undefined) { + window.localStorage.removeItem(key); + return; + } + + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + }, [key, storedValue]); + + return [ storedValue, setValue ] as const; +} diff --git a/src/HullListing/HullListing.stories.tsx b/src/HullListing/HullListing.stories.tsx index 40f42e5..3386a28 100644 --- a/src/HullListing/HullListing.stories.tsx +++ b/src/HullListing/HullListing.stories.tsx @@ -4,10 +4,11 @@ import React from "react"; import { fullFit } from '../../.storybook/fits'; import { HullListing } from './'; +import { DogmaEngineProvider } from '../DogmaEngineProvider'; import { EsiProvider } from '../EsiProvider'; import { EveDataProvider } from '../EveDataProvider'; +import { LocalFitProvider } from '../LocalFitProvider'; import { ShipSnapshotProvider } from '../ShipSnapshotProvider'; -import { DogmaEngineProvider } from '../DogmaEngineProvider'; const meta: Meta = { component: HullListing, @@ -22,13 +23,15 @@ const withEsiProvider: Decorator> = (Story, context) => { return ( - - -
- -
-
-
+ + + +
+ +
+
+
+
); diff --git a/src/HullListing/HullListing.tsx b/src/HullListing/HullListing.tsx index 65342c0..c6b1a32 100644 --- a/src/HullListing/HullListing.tsx +++ b/src/HullListing/HullListing.tsx @@ -6,6 +6,7 @@ import { EsiFit, ShipSnapshotContext } from "../ShipSnapshotProvider"; import { EveDataContext } from "../EveDataProvider"; import { Icon } from "../Icon"; import { TreeListing, TreeHeader, TreeHeaderAction, TreeLeaf } from "../TreeListing"; +import { LocalFitContext } from "../LocalFitProvider"; import styles from "./HullListing.module.css"; @@ -93,18 +94,39 @@ const HullGroup = (props: { name: string, entries: ListingGroup }) => { */ export const HullListing = () => { const esi = React.useContext(EsiContext); + const localFit = React.useContext(LocalFitContext); const eveData = React.useContext(EveDataContext); const shipSnapShot = React.useContext(ShipSnapshotContext); const [hullGroups, setHullGroups] = React.useState({}); const [search, setSearch] = React.useState(""); const [filter, setFilter] = React.useState({ + localCharacter: false, esiCharacter: false, currentHull: false, }); + const [localCharacterFits, setLocalCharacterFits] = React.useState>({}); const [esiCharacterFits, setEsiCharacterFits] = React.useState>({}); + React.useEffect(() => { + if (!localFit.loaded) return; + if (!localFit.fittings) return; + + const newLocalCharacterFits: Record = {}; + for (const fit of localFit.fittings) { + if (fit.ship_type_id === undefined) continue; + + if (newLocalCharacterFits[fit.ship_type_id] === undefined) { + newLocalCharacterFits[fit.ship_type_id] = []; + } + + newLocalCharacterFits[fit.ship_type_id].push(fit); + } + + setLocalCharacterFits(newLocalCharacterFits); + }, [localFit]); + React.useEffect(() => { if (!esi.loaded) return; if (!esi.currentCharacter) return; @@ -127,7 +149,7 @@ export const HullListing = () => { React.useEffect(() => { if (!eveData.loaded) return; - const anyFilter = filter.esiCharacter; + const anyFilter = filter.localCharacter || filter.esiCharacter; const newHullGroups: ListingGroups = {}; @@ -141,11 +163,13 @@ export const HullListing = () => { const fits: EsiFit[] = []; if (anyFilter) { + if (filter.localCharacter && Object.keys(localCharacterFits).includes(typeId)) fits.push(...localCharacterFits[typeId]); if (filter.esiCharacter && Object.keys(esiCharacterFits).includes(typeId)) fits.push(...esiCharacterFits[typeId]); if (fits.length == 0) { if (!filter.currentHull || shipSnapShot.fit?.ship_type_id !== parseInt(typeId)) continue; } } else { + if (Object.keys(localCharacterFits).includes(typeId)) fits.push(...localCharacterFits[typeId]); if (Object.keys(esiCharacterFits).includes(typeId)) fits.push(...esiCharacterFits[typeId]); } @@ -168,15 +192,15 @@ export const HullListing = () => { } setHullGroups(newHullGroups); - }, [eveData, search, filter, esiCharacterFits, shipSnapShot.fit?.ship_type_id]); + }, [eveData, search, filter, localCharacterFits, esiCharacterFits, shipSnapShot.fit?.ship_type_id]); return
setSearch(e.target.value)} />
- - + setFilter({...filter, localCharacter: !filter.localCharacter})}> + setFilter({...filter, esiCharacter: !filter.esiCharacter})}> diff --git a/src/LocalFitProvider/LocalFitProvider.stories.tsx b/src/LocalFitProvider/LocalFitProvider.stories.tsx new file mode 100644 index 0000000..ff745ea --- /dev/null +++ b/src/LocalFitProvider/LocalFitProvider.stories.tsx @@ -0,0 +1,42 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from "react"; + +import { LocalFitContext, LocalFitProvider } from './'; + +const meta: Meta = { + component: LocalFitProvider, + tags: ['autodocs'], + title: 'Provider/LocalFitProvider', +}; + +export default meta; +type Story = StoryObj; + +const TestLocalFit = () => { + const localFit = React.useContext(LocalFitContext); + + if (!localFit.loaded) { + return ( +
+ LocalFit: loading
+
+ ); + } + + return ( +
+ LocalFit: loaded
+
{JSON.stringify(localFit, null, 2)}
+
+ ); +} + +export const Default: Story = { + args: { + }, + render: (args) => ( + + + + ), +}; diff --git a/src/LocalFitProvider/LocalFitProvider.tsx b/src/LocalFitProvider/LocalFitProvider.tsx new file mode 100644 index 0000000..ad0e534 --- /dev/null +++ b/src/LocalFitProvider/LocalFitProvider.tsx @@ -0,0 +1,44 @@ +import React from "react"; + +import { EsiFit } from "../ShipSnapshotProvider"; + +import { useLocalStorage } from "../Helpers/LocalStorage"; + +export interface LocalFit { + loaded?: boolean; + fittings: EsiFit[]; +} + +export const LocalFitContext = React.createContext({ + loaded: undefined, + fittings: [], +}); + +export interface LocalFitProps { + /** Children that can use this provider. */ + children: React.ReactNode; +} + +/** + * Keeps track (in local storage) of fits. + */ +export const LocalFitProvider = (props: LocalFitProps) => { + const [localFit, setLocalFit] = React.useState({ + loaded: undefined, + fittings: [], + }); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [localFitValue, setLocalFitValue] = useLocalStorage("fits", []); + + React.useEffect(() => { + setLocalFit({ + loaded: true, + fittings: localFitValue, + }); + }, [localFitValue]); + + return + {props.children} + +}; diff --git a/src/LocalFitProvider/index.ts b/src/LocalFitProvider/index.ts new file mode 100644 index 0000000..f62440c --- /dev/null +++ b/src/LocalFitProvider/index.ts @@ -0,0 +1,2 @@ +export { LocalFitContext, LocalFitProvider } from "./LocalFitProvider"; +export type { LocalFit } from "./LocalFitProvider";