Add useGlobal for storing global UI vars in Redux store.

This commit is contained in:
Aleksej Komarov
2020-04-19 19:36:42 +03:00
parent ad32b84283
commit 2ac6816317
2 changed files with 154 additions and 120 deletions
+105 -120
View File
@@ -1,130 +1,115 @@
import { decodeHtmlEntities } from 'common/string';
import { Component, Fragment } from 'inferno';
import { act } from '../byond';
import { Box, Button, Input, Section, Table, Tabs } from '../components';
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { Box, Button, Input, Section, Table, Tabs } from '../components';
import { Window } from '../layouts';
import { useGlobal } from '../store';
// It's a class because we need to store state in the form of the current
// hovered item, and current search terms
export class Uplink extends Component {
constructor() {
super();
this.state = {
hoveredItem: {},
currentSearch: '',
};
}
setHoveredItem(hoveredItem) {
this.setState({
hoveredItem,
});
}
setSearchText(currentSearch) {
this.setState({
currentSearch,
});
}
render() {
const { act, data } = useBackend(this.context);
const {
compact_mode,
lockable,
telecrystals,
categories = [],
} = data;
const { hoveredItem, currentSearch } = this.state;
return (
<Window
theme="syndicate"
resizable>
<Window.Content scrollable>
<Section
title={(
<Box
inline
color={telecrystals > 0 ? 'good' : 'bad'}>
{telecrystals} TC
</Box>
)}
buttons={(
<Fragment>
Search
<Input
value={currentSearch}
onInput={(e, value) => this.setSearchText(value)}
ml={1}
mr={1} />
export const Uplink = (props, context) => {
const { act, data } = useBackend(context);
const {
compact_mode,
lockable,
telecrystals,
categories = [],
} = data;
const [
hoveredItem,
setHoveredItem,
] = useGlobal(context, 'hoveredItem', {});
const [
searchText,
setSearchText,
] = useGlobal(context, 'searchText', '');
return (
<Window
theme="syndicate"
resizable>
<Window.Content scrollable>
<Section
title={(
<Box
inline
color={telecrystals > 0 ? 'good' : 'bad'}>
{telecrystals} TC
</Box>
)}
buttons={(
<Fragment>
Search
<Input
value={searchText}
onInput={(e, value) => setSearchText(value)}
ml={1}
mr={1} />
<Button
icon={compact_mode ? 'list' : 'info'}
content={compact_mode ? 'Compact' : 'Detailed'}
onClick={() => act('compact_toggle')} />
{!!lockable && (
<Button
icon={compact_mode ? 'list' : 'info'}
content={compact_mode ? 'Compact' : 'Detailed'}
onClick={() => act('compact_toggle')} />
{!!lockable && (
<Button
icon="lock"
content="Lock"
onClick={() => act('lock')} />
)}
</Fragment>
)}>
{currentSearch.length > 0 ? (
<table className="Table">
<ItemList
compact
items={categories
.flatMap(category => {
return category.items || [];
})
.filter(item => {
const searchTerm = currentSearch.toLowerCase();
const searchableString = String(item.name + item.desc)
.toLowerCase();
return searchableString.includes(searchTerm);
})}
hoveredItem={hoveredItem}
onBuyMouseOver={item => this.setHoveredItem(item)}
onBuyMouseOut={item => this.setHoveredItem({})}
onBuy={item => act('buy', {
item: item.name,
})} />
</table>
) : (
<Tabs vertical>
{categories.map(category => {
const { name, items } = category;
if (items === null) {
return;
}
return (
<Tabs.Tab
key={name}
label={`${name} (${items.length})`}>
{() => (
<ItemList
compact={compact_mode}
items={items}
hoveredItem={hoveredItem}
telecrystals={telecrystals}
onBuyMouseOver={item => this.setHoveredItem(item)}
onBuyMouseOut={item => this.setHoveredItem({})}
onBuy={item => act('buy', {
item: item.name,
})} />
)}
</Tabs.Tab>
);
})}
</Tabs>
)}
</Section>
</Window.Content>
</Window>
);
}
}
icon="lock"
content="Lock"
onClick={() => act('lock')} />
)}
</Fragment>
)}>
{searchText.length > 0 ? (
<table className="Table">
<ItemList
compact
items={categories
.flatMap(category => {
return category.items || [];
})
.filter(item => {
const searchTerm = searchText.toLowerCase();
const searchableString = String(item.name + item.desc)
.toLowerCase();
return searchableString.includes(searchTerm);
})}
hoveredItem={hoveredItem}
onBuyMouseOver={item => setHoveredItem(item)}
onBuyMouseOut={item => setHoveredItem({})}
onBuy={item => act('buy', {
item: item.name,
})} />
</table>
) : (
<Tabs vertical>
{categories.map(category => {
const { name, items } = category;
if (items === null) {
return;
}
return (
<Tabs.Tab
key={name}
label={`${name} (${items.length})`}>
{() => (
<ItemList
compact={compact_mode}
items={items}
hoveredItem={hoveredItem}
telecrystals={telecrystals}
onBuyMouseOver={item => setHoveredItem(item)}
onBuyMouseOut={item => setHoveredItem({})}
onBuy={item => act('buy', {
item: item.name,
})} />
)}
</Tabs.Tab>
);
})}
</Tabs>
)}
</Section>
</Window.Content>
</Window>
);
};
const ItemList = props => {
const {
+49
View File
@@ -11,6 +11,7 @@ export const createStore = () => {
// Global state reducers
backendReducer,
hotKeyReducer,
globalStateReducer,
]);
const middleware = [
// loggingMiddleware,
@@ -29,3 +30,51 @@ export class StoreProvider extends Component {
return this.props.children;
}
}
export const useDispatch = context => {
return context.store.dispatch;
};
/**
* Allocates a global variable on Redux store.
*
* Great when you want to store some UI state globally, without having to
* modify DM object you're working with to have that var and export it via
* ui_data.
*
* @param {any} context React context.
* @param {string} key Key which uniquely identifies this state in Redux store.
* @param {any} initialState Initializes your global variable with this value.
*/
export const useGlobal = (context, key, initialState) => {
const { store } = context;
const globalObj = store.getState().global ?? {};
const state = (key in globalObj)
? globalObj[key]
: initialState;
const setState = nextState => {
store.dispatch({
type: 'setGlobal',
payload: { key, nextState },
});
};
return [state, setState];
};
/**
* Reducer, which handles actions coming from useGlobal.
*/
const globalStateReducer = (state, action) => {
const { type, payload } = action;
if (type === 'setGlobal') {
const { key, nextState } = payload;
return {
...state,
global: {
...state.global,
[key]: nextState,
},
};
}
return state;
};