mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 21:18:37 +01:00
Add useGlobal for storing global UI vars in Redux store.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user