mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 21:18:37 +01:00
WIP
This commit is contained in:
@@ -2,13 +2,15 @@
|
||||
* Limits a number to the range between 'min' and 'max'.
|
||||
*/
|
||||
export const clamp = (value, min, max) => {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
return value < min ? min : value > max ? max : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Limits a number between 0 and 1.
|
||||
*/
|
||||
export const clamp01 = value => clamp(value, 0, 1);
|
||||
export const clamp01 = value => {
|
||||
return value < 0 ? 0 : value > 1 ? 1 : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scales a number to fit into the range between min and max.
|
||||
@@ -18,10 +20,35 @@ export const scale = (value, min, max) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a rounded number.
|
||||
* TODO: Replace this native rounding function with a more robust one.
|
||||
* Robust number rounding.
|
||||
*
|
||||
* Adapted from Locutus, see: http://locutus.io/php/math/round/
|
||||
*
|
||||
* @param {number} value
|
||||
* @param {number} precision
|
||||
* @return {number}
|
||||
*/
|
||||
export const round = value => Math.round(value);
|
||||
export const round = (value, precision) => {
|
||||
if (!value || isNaN(value)) {
|
||||
return value;
|
||||
}
|
||||
// helper variables
|
||||
let m, f, isHalf, sgn;
|
||||
// making sure precision is integer
|
||||
precision |= 0;
|
||||
m = Math.pow(10, precision);
|
||||
value *= m;
|
||||
// sign of the number
|
||||
sgn = (value > 0) | -(value < 0);
|
||||
// isHalf = value % 1 === 0.5 * sgn;
|
||||
isHalf = Math.abs(value % 1) >= 0.4999999999854481;
|
||||
f = Math.floor(value);
|
||||
if (isHalf) {
|
||||
// rounds .5 away from zero
|
||||
value = f + (sgn > 0);
|
||||
}
|
||||
return (isHalf ? value : Math.round(value)) / m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representing a number in fixed point notation.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { clamp, keyOfMatchingRange, toFixed } from 'common/math';
|
||||
import { clamp01, scale, keyOfMatchingRange, toFixed } from 'common/math';
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
|
||||
@@ -13,7 +13,7 @@ export const ProgressBar = props => {
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
const scaledValue = (value - minValue) / (maxValue - minValue);
|
||||
const scaledValue = scale(value, minValue, maxValue);
|
||||
const hasContent = children !== undefined;
|
||||
const effectiveColor = color
|
||||
|| keyOfMatchingRange(value, ranges)
|
||||
@@ -28,9 +28,9 @@ export const ProgressBar = props => {
|
||||
])}
|
||||
{...computeBoxProps(rest)}>
|
||||
<div
|
||||
className="ProgressBar__fill"
|
||||
className="ProgressBar__fill ProgressBar__fill--animated"
|
||||
style={{
|
||||
width: (clamp(scaledValue, 0, 1) * 100) + '%',
|
||||
width: clamp01(scaledValue) * 100 + '%',
|
||||
}} />
|
||||
<div className="ProgressBar__content">
|
||||
{hasContent
|
||||
|
||||
@@ -1,139 +1,51 @@
|
||||
import { classes, normalizeChildren } from 'common/react';
|
||||
import { Component } from 'inferno';
|
||||
import { Box } from './Box';
|
||||
import { classes } from 'common/react';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
import { Button } from './Button';
|
||||
|
||||
// A magic value for enforcing type safety
|
||||
const TAB_MAGIC_TYPE = 'Tab';
|
||||
|
||||
const validateTabs = tabs => {
|
||||
for (let tab of tabs) {
|
||||
if (!tab.props || tab.props.__type__ !== TAB_MAGIC_TYPE) {
|
||||
const json = JSON.stringify(tab, null, 2);
|
||||
throw new Error('<Tabs> only accepts children of type <Tabs.Tab>.'
|
||||
+ 'This is what we received: ' + json);
|
||||
}
|
||||
}
|
||||
export const Tabs = props => {
|
||||
const {
|
||||
className,
|
||||
vertical,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
'Tabs',
|
||||
vertical
|
||||
? 'Tabs--vertical'
|
||||
: 'Tabs--horizontal',
|
||||
className,
|
||||
computeBoxClassName(rest),
|
||||
])}
|
||||
{...computeBoxProps(rest)}>
|
||||
<div className="Tabs__tabBox">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export class Tabs extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeTabKey: null,
|
||||
};
|
||||
}
|
||||
|
||||
getActiveTab() {
|
||||
const { state, props } = this;
|
||||
const tabs = normalizeChildren(props.children);
|
||||
validateTabs(tabs);
|
||||
// Get active tab
|
||||
let activeTabKey = props.activeTab || state.activeTabKey;
|
||||
// Verify that active tab exists
|
||||
let activeTab = tabs
|
||||
.find(tab => {
|
||||
const key = tab.key || tab.props.label;
|
||||
return key === activeTabKey;
|
||||
});
|
||||
// Set first tab as the active tab
|
||||
if (!activeTab) {
|
||||
activeTab = tabs[0];
|
||||
activeTabKey = activeTab && (activeTab.key || activeTab.props.label);
|
||||
}
|
||||
return {
|
||||
tabs,
|
||||
activeTab,
|
||||
activeTabKey,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const {
|
||||
className,
|
||||
vertical,
|
||||
altSelection,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
const {
|
||||
tabs,
|
||||
activeTab,
|
||||
activeTabKey,
|
||||
} = this.getActiveTab();
|
||||
// Retrieve tab content
|
||||
let content = null;
|
||||
if (activeTab) {
|
||||
content = activeTab.props.content || activeTab.props.children;
|
||||
}
|
||||
// Get children by calling a wrapper function
|
||||
if (typeof content === 'function') {
|
||||
content = content(activeTabKey);
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
className={classes([
|
||||
'Tabs',
|
||||
vertical && 'Tabs--vertical',
|
||||
className,
|
||||
])}
|
||||
{...rest}>
|
||||
<div className="Tabs__tabBox">
|
||||
{tabs.map(tab => {
|
||||
const {
|
||||
className,
|
||||
label,
|
||||
content, // ignored
|
||||
children, // ignored
|
||||
onClick,
|
||||
highlight,
|
||||
...rest
|
||||
} = tab.props;
|
||||
const key = tab.key || tab.props.label;
|
||||
const active = tab.active || key === activeTabKey;
|
||||
const altSelectionStyle = 'Button--altSelected'
|
||||
+ (vertical ? '--right' : '--bottom');
|
||||
return (
|
||||
<Button
|
||||
key={key}
|
||||
className={classes([
|
||||
'Tabs__tab',
|
||||
active && 'Tabs__tab--active',
|
||||
highlight && !active && 'color-yellow',
|
||||
altSelection && active && altSelectionStyle,
|
||||
className,
|
||||
])}
|
||||
selected={!altSelection && active}
|
||||
color="transparent"
|
||||
onClick={e => {
|
||||
this.setState({ activeTabKey: key });
|
||||
if (onClick) {
|
||||
onClick(e, tab);
|
||||
}
|
||||
}}
|
||||
{...rest}>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="Tabs__content">
|
||||
{content || null}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy component, which is used for carrying props for the
|
||||
* tab container.
|
||||
*/
|
||||
export const Tab = props => null;
|
||||
|
||||
Tab.defaultProps = {
|
||||
__type__: TAB_MAGIC_TYPE,
|
||||
const Tab = props => {
|
||||
const {
|
||||
className,
|
||||
selected,
|
||||
altSelection,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<Button
|
||||
className={classes([
|
||||
'Tabs__tab',
|
||||
selected && 'Tabs__tab--selected',
|
||||
altSelection && selected && 'Tabs__tab--altSelection',
|
||||
className,
|
||||
])}
|
||||
selected={!altSelection && selected}
|
||||
color="transparent"
|
||||
{...rest} />
|
||||
);
|
||||
};
|
||||
|
||||
Tabs.Tab = Tab;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { clamp, toFixed } from 'common/math';
|
||||
import { clamp, round, toFixed } from 'common/math';
|
||||
|
||||
const SI_SYMBOLS = [
|
||||
'f', // femto
|
||||
@@ -54,3 +54,12 @@ const formatSiUnit = (value, minBase1000 = -SI_BASE_INDEX, unit = '') => {
|
||||
export const formatPower = (value, minBase1000 = 0) => {
|
||||
return formatSiUnit(value, minBase1000, 'W');
|
||||
};
|
||||
|
||||
export const formatMoney = (value, precision = 0) => {
|
||||
return round(value, precision)
|
||||
.toLocaleString('en', {
|
||||
minimumFractionDigits: precision,
|
||||
})
|
||||
// Thin space
|
||||
.replace(/,/g, '\u2009');
|
||||
};
|
||||
|
||||
@@ -3,7 +3,11 @@ import 'core-js/web/immediate';
|
||||
import 'core-js/web/queue-microtask';
|
||||
import 'core-js/web/timers';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import './polyfills';
|
||||
|
||||
// This one is necessary for Inferno.
|
||||
if (!window.Int32Array) {
|
||||
window.Int32Array = Array;
|
||||
}
|
||||
|
||||
import { loadCSS } from 'fg-loadcss';
|
||||
import { render } from 'inferno';
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Icon, Table, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { Fragment } from 'inferno';
|
||||
|
||||
export const Achievements = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const {
|
||||
achievements,
|
||||
categories,
|
||||
highscore,
|
||||
user_ckey,
|
||||
} = data;
|
||||
const { categories } = data;
|
||||
const [
|
||||
selectedCategory,
|
||||
setSelectedCategory,
|
||||
] = useLocalState(context, 'category', categories[0]);
|
||||
const achievements = data.achievements
|
||||
.filter(x => x.category === selectedCategory);
|
||||
return (
|
||||
<Window resizable>
|
||||
<Window.Content scrollable>
|
||||
@@ -17,81 +19,40 @@ export const Achievements = (props, context) => {
|
||||
{categories.map(category => (
|
||||
<Tabs.Tab
|
||||
key={category}
|
||||
label={category}>
|
||||
<Table>
|
||||
{achievements
|
||||
.filter(x => x.category === category)
|
||||
.map(achievement => {
|
||||
if (achievement.score) {
|
||||
return (
|
||||
<Score
|
||||
key={achievement.name}
|
||||
achievement={achievement} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Achievement
|
||||
key={achievement.name}
|
||||
achievement={achievement} />
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
selected={selectedCategory === category}
|
||||
onClick={() => setSelectedCategory(category)}>
|
||||
{category}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
<Tabs.Tab
|
||||
label="High Scores">
|
||||
<Tabs vertical>
|
||||
{highscore.map(highscore => (
|
||||
<Tabs.Tab
|
||||
key={highscore.name}
|
||||
label={highscore.name}>
|
||||
<Table>
|
||||
<Table.Row className="candystripe">
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
#
|
||||
</Table.Cell>
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
Key
|
||||
</Table.Cell>
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
Score
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{Object.keys(highscore.scores).map((key, index) => (
|
||||
<Table.Row
|
||||
key={key}
|
||||
className="candystripe"
|
||||
m={2}>
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
{index + 1}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
color={key === user_ckey && 'green'}
|
||||
textAlign="center">
|
||||
{index === 0 && (
|
||||
<Icon name="crown" color="gold" mr={2} />
|
||||
)}
|
||||
{key}
|
||||
{index === 0 && (
|
||||
<Icon name="crown" color="gold" ml={2} />
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="center">
|
||||
{highscore.scores[key]}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
selected={selectedCategory === 'High Scores'}
|
||||
onClick={() => setSelectedCategory('High Scores')}>
|
||||
High Scores
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
{selectedCategory === 'High Scores' && (
|
||||
<HighScoreTable />
|
||||
) || (
|
||||
<AchievementTable achievements={achievements} />
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const AchievementTable = (props, context) => {
|
||||
const { achievements } = props;
|
||||
return (
|
||||
<Table>
|
||||
{achievements.map(achievement => (
|
||||
<Achievement
|
||||
key={achievement.name}
|
||||
achievement={achievement} />
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
const Achievement = props => {
|
||||
const { achievement } = props;
|
||||
const {
|
||||
@@ -99,6 +60,7 @@ const Achievement = props => {
|
||||
desc,
|
||||
icon_class,
|
||||
value,
|
||||
score,
|
||||
} = achievement;
|
||||
return (
|
||||
<tr key={name}>
|
||||
@@ -108,34 +70,89 @@ const Achievement = props => {
|
||||
<td style={{ 'vertical-align': 'top' }}>
|
||||
<h1>{name}</h1>
|
||||
{desc}
|
||||
<Box color={value ? 'good' : 'bad'}>
|
||||
{value ? 'Unlocked' : 'Locked'}
|
||||
</Box>
|
||||
{score && (
|
||||
<Box color={value > 0 ? 'good' : 'bad'}>
|
||||
{value > 0 ? `Earned ${value} times` : 'Locked'}
|
||||
</Box>
|
||||
) || (
|
||||
<Box color={value ? 'good' : 'bad'}>
|
||||
{value ? 'Unlocked' : 'Locked'}
|
||||
</Box>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const Score = props => {
|
||||
const { achievement } = props;
|
||||
const HighScoreTable = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const {
|
||||
name,
|
||||
desc,
|
||||
icon_class,
|
||||
value,
|
||||
} = achievement;
|
||||
highscore: highscores,
|
||||
user_ckey,
|
||||
} = data;
|
||||
const [
|
||||
highScoreIndex,
|
||||
setHighScoreIndex,
|
||||
] = useLocalState(context, 'highscore', 0);
|
||||
const highscore = highscores[highScoreIndex];
|
||||
if (!highscore) {
|
||||
return null;
|
||||
}
|
||||
const scores = Object
|
||||
.keys(highscore.scores)
|
||||
.map(key => ({
|
||||
ckey: key,
|
||||
value: highscore.scores[key],
|
||||
}));
|
||||
return (
|
||||
<tr key={name}>
|
||||
<td style={{ 'padding': '6px' }}>
|
||||
<Box className={icon_class} />
|
||||
</td>
|
||||
<td style={{ 'vertical-align': 'top' }}>
|
||||
<h1>{name}</h1>
|
||||
{desc}
|
||||
<Box color={value > 0 ? 'good' : 'bad'}>
|
||||
{value > 0 ? `Earned ${value} times` : 'Locked'}
|
||||
</Box>
|
||||
</td>
|
||||
</tr>
|
||||
<Fragment>
|
||||
<Tabs vertical>
|
||||
{highscores.map((highscore, i) => (
|
||||
<Tabs.Tab
|
||||
key={highscore.name}
|
||||
selected={highScoreIndex === i}
|
||||
onClick={() => setHighScoreIndex(i)}>
|
||||
{highscore.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
<Table>
|
||||
<Table.Row className="candystripe">
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
#
|
||||
</Table.Cell>
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
Key
|
||||
</Table.Cell>
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
Score
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{scores.map((score, i) => (
|
||||
<Table.Row
|
||||
key={score.ckey}
|
||||
className="candystripe"
|
||||
m={2}>
|
||||
<Table.Cell color="label" textAlign="center">
|
||||
{i + 1}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
color={score.ckey === user_ckey && 'green'}
|
||||
textAlign="center">
|
||||
{i === 0 && (
|
||||
<Icon name="crown" color="gold" mr={2} />
|
||||
)}
|
||||
{score.ckey}
|
||||
{i === 0 && (
|
||||
<Icon name="crown" color="gold" ml={2} />
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="center">
|
||||
{score.value}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { toArray } from 'common/collections';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Divider, Flex, LabeledList, Section, Table } from '../components';
|
||||
import { AnimatedNumber, Box, Button, Flex, LabeledList, Section, Table, Tabs } from '../components';
|
||||
import { formatMoney } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const Cargo = (props, context) => {
|
||||
@@ -16,40 +17,34 @@ export const Cargo = (props, context) => {
|
||||
<Window resizable>
|
||||
<Window.Content scrollable>
|
||||
<CargoStatus />
|
||||
<Box mb={0.5}>
|
||||
<Button
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
color="transparent"
|
||||
lineHeight="23px"
|
||||
selected={tab === 'catalog'}
|
||||
onClick={() => setTab('catalog')}>
|
||||
Catalog
|
||||
</Button>
|
||||
<Button
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="envelope"
|
||||
color="transparent"
|
||||
textColor={tab !== 'requests'
|
||||
&& requests.length > 0
|
||||
&& 'yellow'}
|
||||
lineHeight="23px"
|
||||
selected={tab === 'requests'}
|
||||
onClick={() => setTab('requests')}>
|
||||
Requests ({requests.length})
|
||||
</Button>
|
||||
</Tabs.Tab>
|
||||
{!requestonly && (
|
||||
<Button
|
||||
<Tabs.Tab
|
||||
icon="shopping-cart"
|
||||
color="transparent"
|
||||
textColor={tab !== 'cart'
|
||||
&& cart.length > 0
|
||||
&& 'yellow'}
|
||||
lineHeight="23px"
|
||||
selected={tab === 'cart'}
|
||||
onClick={() => setTab('cart')}>
|
||||
Checkout ({cart.length})
|
||||
</Button>
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
</Box>
|
||||
</Tabs>
|
||||
{tab === 'catalog' && (
|
||||
<CargoCatalog />
|
||||
)}
|
||||
@@ -81,7 +76,10 @@ const CargoStatus = (props, context) => {
|
||||
title="Cargo"
|
||||
buttons={(
|
||||
<Box inline bold>
|
||||
<AnimatedNumber value={Math.round(points)} /> credits
|
||||
<AnimatedNumber
|
||||
value={points}
|
||||
format={value => formatMoney(value)} />
|
||||
{' credits'}
|
||||
</Box>
|
||||
)}>
|
||||
<LabeledList>
|
||||
@@ -143,19 +141,16 @@ export const CargoCatalog = (props, context) => {
|
||||
)}>
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
{supplies.map(supply => (
|
||||
<Button
|
||||
fluid
|
||||
key={supply.name}
|
||||
color="transparent"
|
||||
selected={supply.name === activeSupplyName}
|
||||
onClick={() => setActiveSupplyName(supply.name)}>
|
||||
{supply.name} ({supply.packs.length})
|
||||
</Button>
|
||||
))}
|
||||
</Flex.Item>
|
||||
<Flex.Item mr={1}>
|
||||
<Divider vertical />
|
||||
<Tabs vertical>
|
||||
{supplies.map(supply => (
|
||||
<Tabs.Tab
|
||||
key={supply.name}
|
||||
selected={supply.name === activeSupplyName}
|
||||
onClick={() => setActiveSupplyName(supply.name)}>
|
||||
{supply.name} ({supply.packs.length})
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
<Table>
|
||||
@@ -190,9 +185,9 @@ export const CargoCatalog = (props, context) => {
|
||||
onClick={() => act('add', {
|
||||
id: pack.id,
|
||||
})}>
|
||||
{self_paid
|
||||
{formatMoney(self_paid
|
||||
? Math.round(pack.cost * 1.1)
|
||||
: pack.cost}
|
||||
: pack.cost)}
|
||||
{' cr'}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
@@ -247,7 +242,7 @@ const CargoRequests = (props, context) => {
|
||||
<i>{request.reason}</i>
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="right">
|
||||
{request.cost} cr.
|
||||
{formatMoney(request.cost)} cr
|
||||
</Table.Cell>
|
||||
{!requestonly && (
|
||||
<Table.Cell collapsing>
|
||||
@@ -290,7 +285,7 @@ const CargoCartButtons = (props, context) => {
|
||||
{cart.length === 1 && '1 item'}
|
||||
{cart.length >= 2 && cart.length + ' items'}
|
||||
{' '}
|
||||
{total > 0 && `(${total} cr)`}
|
||||
{total > 0 && `(${formatMoney(total)} cr)`}
|
||||
</Box>
|
||||
<Button
|
||||
icon="times"
|
||||
@@ -339,7 +334,7 @@ const CargoCart = (props, context) => {
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="right">
|
||||
{entry.cost} cr.
|
||||
{formatMoney(entry.cost)} cr
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { BlockQuote, Box, Button, ByondUi, Collapsible, Icon, Input, Knob, LabeledList, NumberInput, ProgressBar, Section, Slider, Tabs, Tooltip } from '../components';
|
||||
import { BlockQuote, Box, Button, ByondUi, Collapsible, Icon, Input, Knob, LabeledList, NumberInput, ProgressBar, Section, Slider, Tabs, Tooltip, Flex } from '../components';
|
||||
import { DraggableControl } from '../components/DraggableControl';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
@@ -72,26 +72,31 @@ const PAGES = [
|
||||
|
||||
export const KitchenSink = (props, context) => {
|
||||
const [theme] = useLocalState(context, 'kitchenSinkTheme');
|
||||
const [pageIndex, setPageIndex] = useLocalState(context, 'pageIndex', 0);
|
||||
const PageComponent = PAGES[pageIndex].component();
|
||||
return (
|
||||
<Window
|
||||
theme={theme}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<Section>
|
||||
<Tabs vertical>
|
||||
{PAGES.map(page => (
|
||||
<Tabs.Tab
|
||||
key={page.title}
|
||||
label={page.title}>
|
||||
{() => {
|
||||
const Component = page.component();
|
||||
return (
|
||||
<Component {...props} />
|
||||
);
|
||||
}}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
<Tabs vertical>
|
||||
{PAGES.map((page, i) => (
|
||||
<Tabs.Tab
|
||||
key={i}
|
||||
selected={i === pageIndex}
|
||||
onClick={() => setPageIndex(i)}>
|
||||
{page.title}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
<PageComponent />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
@@ -216,46 +221,39 @@ class KitchenSinkProgressBar extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
class KitchenSinkTabs extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
vertical: true,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { vertical } = this.state;
|
||||
const TAB_KEYS = [1, 2, 3, 4, 5].map(x => 'tab_' + x);
|
||||
return (
|
||||
<Box>
|
||||
{'Vertical: '}
|
||||
<Button
|
||||
const KitchenSinkTabs = (props, context) => {
|
||||
const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
|
||||
const [vertical, setVertical] = useLocalState(context, 'tabVert');
|
||||
const [altSelection, setAltSelection] = useLocalState(context, 'tabAlt');
|
||||
const TAB_RANGE = [1, 2, 3, 4, 5];
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={2}>
|
||||
<Button.Checkbox
|
||||
inline
|
||||
content={String(vertical)}
|
||||
onClick={() => this.setState(prevState => ({
|
||||
vertical: !prevState.vertical,
|
||||
}))} />
|
||||
<Box mb={2} />
|
||||
<Tabs vertical={vertical}>
|
||||
{TAB_KEYS.map(key => (
|
||||
<Tabs.Tab
|
||||
key={key}
|
||||
label={'Label ' + key}>
|
||||
{() => (
|
||||
<Box>
|
||||
{'Active tab: '}
|
||||
<Box inline color="green">{key}</Box>
|
||||
<BoxOfSampleText mt={2} />
|
||||
</Box>
|
||||
)}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
content="vertical"
|
||||
checked={vertical}
|
||||
onClick={() => setVertical(!vertical)} />
|
||||
<Button.Checkbox
|
||||
inline
|
||||
content="altSelection"
|
||||
checked={altSelection}
|
||||
onClick={() => setAltSelection(!altSelection)} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
<Tabs vertical={vertical}>
|
||||
{TAB_RANGE.map((number, i) => (
|
||||
<Tabs.Tab
|
||||
key={i}
|
||||
altSelection={altSelection}
|
||||
selected={i === tabIndex}
|
||||
onClick={() => setTabIndex(i)}>
|
||||
Tab #{number}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkTooltip = props => {
|
||||
const positions = [
|
||||
@@ -421,13 +419,13 @@ const KitchenSinkCollapsible = props => {
|
||||
<Button icon="cog" />
|
||||
)}>
|
||||
<Section>
|
||||
<BoxOfSampleText />
|
||||
<BoxWithSampleText />
|
||||
</Section>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
|
||||
const BoxOfSampleText = props => {
|
||||
const BoxWithSampleText = props => {
|
||||
return (
|
||||
<Box {...props}>
|
||||
<Box italic>
|
||||
@@ -445,7 +443,7 @@ const BoxOfSampleText = props => {
|
||||
const KitchenSinkBlockQuote = props => {
|
||||
return (
|
||||
<BlockQuote>
|
||||
<BoxOfSampleText />
|
||||
<BoxWithSampleText />
|
||||
</BlockQuote>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// This one is necessary for Inferno to do complex DOM patching on IE8.
|
||||
// Not the fastest one or most spec compliant, but hey, it works!
|
||||
if (!window.Int32Array) {
|
||||
window.Int32Array = Array;
|
||||
}
|
||||
@@ -113,27 +113,3 @@ $bg-map: colors.$bg-map !default;
|
||||
.Button--selected {
|
||||
@include button-color($color-selected);
|
||||
}
|
||||
|
||||
.Button--altSelected--bottom::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: colors.$white;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.Button--altSelected--right::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
background-color: colors.$white;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,56 @@
|
||||
.Tabs__content {
|
||||
padding-top: 6px;
|
||||
border-top: 2px solid rgba(255, 255, 255, 0.1);
|
||||
@use '../base.scss';
|
||||
|
||||
$border-radius: base.$border-radius !default;
|
||||
|
||||
.Tabs--horizontal {
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||
margin-bottom: 6px;
|
||||
|
||||
.Tabs__tab--altSelection::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.Tabs--vertical {
|
||||
display: table-row;
|
||||
}
|
||||
margin-right: 9px;
|
||||
|
||||
// NOTE: These selectors have to be specific in order to stop cascading
|
||||
// from influencing nested tabs.
|
||||
.Tabs--vertical > .Tabs__content {
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
padding-top: 0;
|
||||
padding-left: 9px;
|
||||
border-top: 0;
|
||||
}
|
||||
.Tabs__tabBox {
|
||||
// padding-right: 2px;
|
||||
border-right: 2px solid rgba(255, 255, 255, 0.1);
|
||||
// Disable baseline alignment when doing vertical tabs
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.Tabs--vertical > .Tabs__tabBox {
|
||||
display: table-cell;
|
||||
// padding-right: 2px;
|
||||
border-right: 2px solid rgba(255, 255, 255, 0.1);
|
||||
// Disable baseline alignment when doing vertical tabs
|
||||
vertical-align: top;
|
||||
}
|
||||
.Tabs__tab {
|
||||
// Force display block because Button theme overrides it via cascading.
|
||||
display: block !important;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 1px 9px 0px 6px;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
.Tabs--vertical > .Tabs__tabBox > .Tabs__tab {
|
||||
// Force display block because Button theme overrides it via cascading.
|
||||
display: block !important;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 1px 9px 0px 6px;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
.Tabs__tab--altSelection::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
background-color: #fff;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user