semantic-ui-react#Card TypeScript Examples
The following examples show how to use
semantic-ui-react#Card.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: crew_card.tsx From website with MIT License | 6 votes |
function CrewCard({crew}) {
return (
<Card>
<Card.Content>
<Image
floated="left"
size="tiny"
src={crew.image}
bordered
style={{
borderColor: `${getRarityColor(crew.rarity)}`
}}
/>
<Card.Header>{crew.name}</Card.Header>
<Card.Meta>
<p>{getRarityStars(crew.rarity)}</p>
<p>
{crew.skills.map(skill => (
<Image key={skill.key} width={30} height={30} inline spaced src={skill.imageUrl} />
))}
</p>
</Card.Meta>
<Card.Description>
{crew.traits.join(', ')}
</Card.Description>
</Card.Content>
</Card>
);
}
Example #2
Source File: MemberList.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
MemberList: VFC<{ users: User[] }> = ({ users = [] }) => (
<>
<Card.Group>
{users.map((user) => (
<Card
key={user.id}
href={`https://github.com/${user.login}`}
target="_blank"
rel="noopener noreferrer"
>
<Card.Content>
<Image floated="right" size="mini" src={user.avatarUrl} />
<Card.Header>{user.login}</Card.Header>
<Card.Meta>GitHub ID: {user.id}</Card.Meta>
</Card.Content>
</Card>
))}
</Card.Group>
</>
)
Example #3
Source File: App.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
render(): ReactElement {
const { count } = this.state;
return (
<div className="container">
<header>
<h1>カウンター</h1>
</header>
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>
<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={() => this.reset()}>
Reset
</Button>
<Button color="green" onClick={() => this.increment()}>
+1
</Button>
</div>
</Card.Content>
</Card>
</div>
);
}
Example #4
Source File: App.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
render = (): ReactElement => {
const { timeLeft } = this.state;
return (
<div className="container">
<header>
<h1>タイマー</h1>
</header>
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value>{timeLeft}</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={this.reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
</div>
);
};
Example #5
Source File: Counter.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
Counter: VFC = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((c) => c + 1);
const reset = () => setCount(0);
return (
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>
<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={reset}>
Reset
</Button>
<Button color="green" onClick={increment}>
+1
</Button>
</div>
</Card.Content>
</Card>
);
}
Example #6
Source File: Timer.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
Timer: VFC<{ limit: number }> = ({ limit }) => {
const [timeLeft, setTimeLeft] = useState(limit);
const reset = (): void => setTimeLeft(limit);
const tick = (): void => setTimeLeft((t) => t - 1);
useEffect(() => {
const timerId = setInterval(tick, 1000);
return () => clearInterval(timerId);
}, []);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (timeLeft === 0) setTimeLeft(limit);
});
return (
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value>{timeLeft}</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
);
}
Example #7
Source File: Timer.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
Timer: VFC<TimerProps> = ({ limit }) => {
const [timeLeft, setTimeLeft] = useState(limit);
const primes = useMemo(() => getPrimes(limit), [limit]);
const reset = () => setTimeLeft(limit);
const tick = () => setTimeLeft((t) => t - 1);
useEffect(() => {
const timerId = setInterval(tick, 1000);
return () => clearInterval(timerId);
}, []);
useEffect(() => {
if (timeLeft === 0) setTimeLeft(limit);
}, [timeLeft, limit]);
return (
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value
className={primes.includes(timeLeft) ? 'prime-number' : undefined}
>
{timeLeft}
</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
);
}
Example #8
Source File: MemberList.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
MemberList: VFC<{ users: User[] }> = ({ users = [] }) => (
<>
<Card.Group>
{users.map((user) => (
<Card
key={user.id}
href={`https://github.com/${user.login}`}
target="_blank"
>
<Card.Content>
<Image floated="right" size="mini" src={user.avatarUrl} />
<Card.Header>{user.login}</Card.Header>
<Card.Meta>GitHub ID: {user.id}</Card.Meta>
</Card.Content>
</Card>
))}
</Card.Group>
</>
)
Example #9
Source File: MemberList.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
MemberList: VFC<{ users: User[] }> = ({ users = [] }) => (
<>
<Card.Group>
{users.map((user) => (
<Card
key={user.id}
href={`https://github.com/${user.login}`}
target="_blank"
>
<Card.Content>
<Image floated="right" size="mini" src={user.avatarUrl} />
<Card.Header>{user.login}</Card.Header>
<Card.Meta>GitHub ID: {user.id}</Card.Meta>
</Card.Content>
</Card>
))}
</Card.Group>
</>
)
Example #10
Source File: Timer.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
Timer: VFC<{ limit: number }> = ({ limit }) => {
const [timeLeft, isPrime, reset] = useTimer(limit);
return (
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value className={isPrime ? 'prime-number' : undefined}>
{timeLeft}
</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
);
}
Example #11
Source File: Timer.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
Timer: VFC<Props> = ({
timeLeft = 0,
isPrime = false,
reset = () => undefined,
}) => (
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value className={isPrime ? 'prime-number' : undefined}>
{timeLeft}
</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
)
Example #12
Source File: CounterBoard.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
CounterBoard: VFC<Props> = ({
count = 0,
add = () => undefined,
decrement = () => undefined,
increment = () => undefined,
}) => (
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>
<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={decrement}>
-1
</Button>
<Button color="green" onClick={increment}>
+1
</Button>
</div>
<div className="fluid-button">
<Button fluid color="grey" onClick={() => add(BULK_UNIT)}>
+{BULK_UNIT}
</Button>
</div>
</Card.Content>
</Card>
)
Example #13
Source File: CounterBoard.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
CounterBoard: VFC<Props> = ({
count = 0,
add = () => undefined,
decrement = () => undefined,
increment = () => undefined,
}) => (
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>
<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={decrement}>
-1
</Button>
<Button color="green" onClick={increment}>
+1
</Button>
</div>
<div className="fluid-button">
<Button fluid color="grey" onClick={() => add(BULK_UNIT)}>
+{BULK_UNIT}
</Button>
</div>
</Card.Content>
</Card>
)
Example #14
Source File: CounterBoard.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
CounterBoard: VFC<CounterBoardProps> = ({
count = 0,
add = () => undefined,
decrement = () => undefined,
increment = () => undefined,
}) => (
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>
<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={decrement}>
-1
</Button>
<Button color="green" onClick={increment}>
+1
</Button>
</div>
<div className="fluid-button">
<Button fluid color="grey" onClick={() => add(BULK_UNIT)}>
+{BULK_UNIT}
</Button>
</div>
</Card.Content>
</Card>
)
Example #15
Source File: LobbyUserPanel.tsx From FLECT_Amazon_Chime_Meeting with Apache License 2.0 | 5 votes |
render(){
const props = this.props as any
return(
<div>
{this.state.open ?
(
<div>
<Card width="100%">
<Button basic size="tiny" compact onClick={()=>{this.handleClick()}} >
{/* <Header as='h5'> */}
<Icon name="angle up" />Configurations
{/* </Header> */}
</Button>
<Card.Content>
<p>
<MicControl {...props} />
</p>
<p>
<VideoControl {...props} />
</p>
<p>
<SpeakerControl {...props} />
</p>
<Divider />
<p>
<VideoResolutionControl {...props} />
</p>
<p>
<SettingControl {...props}/>
</p>
</Card.Content>
</Card>
</div>
)
:
(
<div>
<Card >
<Button basic size="tiny" compact onClick={()=>{this.handleClick()}} >
{/* <Header as='h5'> */}
<Icon name="angle down" />Configurations
{/* </Header> */}
</Button>
</Card>
</div>
)
}
</div>
)
}
Example #16
Source File: voyagecalculator.tsx From website with MIT License | 5 votes |
VoyageMain = (props: VoyageMainProps) => {
const { myCrew, allShips } = props;
const [voyageData, setVoyageData] = useStateWithStorage('tools/voyageData', undefined);
const [voyageConfig, setVoyageConfig] = React.useState(undefined);
const [voyageState, setVoyageState] = React.useState('input'); // input or from voyageData: started, recalled
if (!voyageConfig) {
if (voyageData) {
// Voyage started, config will be full voyage data
if (voyageData.voyage && voyageData.voyage.length > 0) {
setVoyageConfig(voyageData.voyage[0]);
setVoyageState(voyageData.voyage[0].state);
}
// Voyage awaiting input, config will be input parameters only
else {
setVoyageConfig(voyageData.voyage_descriptions[0]);
}
}
else {
// voyageData not found in cache, config will be blank voyage
setVoyageConfig({ skills: {} });
}
return (<></>);
}
return (
<React.Fragment>
{voyageState == 'input' &&
<Grid columns={2} stackable>
<Grid.Column width={14}>
<Card.Group>
<Card>
<Card.Content>
<Image floated='right' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${voyageConfig.skills.primary_skill}.png`} style={{ height: '2em' }} />
<Card.Header>{CONFIG.SKILLS[voyageConfig.skills.primary_skill]}</Card.Header>
<p>primary</p>
</Card.Content>
</Card>
<Card>
<Card.Content>
<Image floated='right' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${voyageConfig.skills.secondary_skill}.png`} style={{ height: '2em' }} />
<Card.Header>{CONFIG.SKILLS[voyageConfig.skills.secondary_skill]}</Card.Header>
<p>secondary</p>
</Card.Content>
</Card>
<Card>
<Card.Content>
<Card.Header>{allTraits.ship_trait_names[voyageConfig.ship_trait]}</Card.Header>
<p>ship trait</p>
</Card.Content>
</Card>
</Card.Group>
</Grid.Column>
<Grid.Column width={2} textAlign='right'>
<VoyageEditConfigModal voyageConfig={voyageConfig} updateConfig={updateConfig} />
</Grid.Column>
</Grid>
}
{voyageState != 'input' && (<VoyageExisting voyageConfig={voyageConfig} allShips={allShips} useCalc={() => setVoyageState('input')} />)}
{voyageState == 'input' && (<VoyageInput voyageConfig={voyageConfig} myCrew={myCrew} allShips={allShips} useInVoyage={() => setVoyageState(voyageConfig.state)} />)}
</React.Fragment>
);
function updateConfig(voyageConfig: any): void {
setVoyageConfig({...voyageConfig});
// Update stored voyageData with new voyageConfig
setVoyageData({
voyage_descriptions: [{...voyageConfig}],
voyage: []
});
setVoyageState('input');
}
}
Example #17
Source File: Timer3.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 5 votes |
Timer: VFC<{ limit: number }> = ({ limit }) => {
const [timeLeft, setTimeLeft] = useState(limit);
const primes = useMemo(() => getPrimes(limit), [limit]);
const timerId = useRef<NodeJS.Timeout>();
const tick = () => setTimeLeft((t) => t - 1);
const clearTimer = () => {
if (timerId.current) clearInterval(timerId.current);
};
const reset = useCallback(() => {
clearTimer();
timerId.current = setInterval(tick, 1000);
setTimeLeft(limit);
}, [limit]);
useEffect(() => {
reset();
return clearTimer;
}, [reset]);
useEffect(() => {
if (timeLeft === 0) reset();
}, [timeLeft, reset]);
return (
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value
className={primes.includes(timeLeft) ? 'prime-number' : undefined}
>
{timeLeft}
</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
);
}
Example #18
Source File: Timer2.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 5 votes |
Timer: VFC<{ limit: number }> = ({ limit }) => {
const [timeLeft, setTimeLeft] = useState(limit);
const primes = useMemo(() => getPrimes(limit), [limit]);
const timerId = useRef<NodeJS.Timeout>();
const reset = useCallback(() => setTimeLeft(limit), [limit]);
const tick = () => setTimeLeft((t) => t - 1);
useEffect(() => {
const clearTimer = () => {
if (timerId.current) clearInterval(timerId.current);
};
reset();
clearTimer();
timerId.current = setInterval(tick, 1000);
return clearTimer;
}, [limit, reset]);
useEffect(() => {
if (timeLeft === 0) reset();
}, [timeLeft, reset]);
return (
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value
className={primes.includes(timeLeft) ? 'prime-number' : undefined}
>
{timeLeft}
</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
);
}
Example #19
Source File: LobbyUserPanel.tsx From FLECT_Amazon_Chime_Meeting with Apache License 2.0 | 5 votes |
render(){
const props = this.props as any
return(
<div>
{this.state.open ?
(
<div>
<Card width="100%">
<Button basic size="tiny" compact onClick={()=>{this.handleClick()}} >
{/* <Header as='h5'> */}
<Icon name="angle up" />Actions
{/* </Header> */}
</Button>
<Card.Content>
<VideoShareControl {...props} />
<DisplayShareControl {...props} />
<StampAccordion {...props} />
<SendTextAccordion {...props}/>
<SecondaryCameraAccordion {...props} />
<StampAccordionBySignal {...props} />
<FileShareControl {...props} />
</Card.Content>
</Card>
</div>
)
:
(
<div>
<Card >
<Button basic size="tiny" compact onClick={()=>{this.handleClick()}} >
{/* <Header as='h5'> */}
<Icon name="angle down" />Actions
{/* </Header> */}
</Button>
</Card>
</div>
)
}
</div>
)
}
Example #20
Source File: LobbyUserPanel.tsx From FLECT_Amazon_Chime_Meeting with Apache License 2.0 | 5 votes |
render(){
const gs = this.props as GlobalState
const props = this.props as any
const appState = props.appState as AppState
return(
<div>
{/* {appState.isSafari ?
// <video ref={appState.localVideoElementRef} style={{ display: "block", width: "720px", margin: "auto" }} playsInline />
appState.localVideoElement
:
<div/>
} */}
{this.state.open ?
(
<div>
<Card width="100%">
<Button basic size="tiny" compact onClick={()=>{this.handleClick()}} >
{/* <Header as='h5'> */}
<Icon name="angle up" />Preview
{/* </Header> */}
</Button>
<canvas ref={this.previewCanvasRef} style={{ display: "block" }} width="100%" height="100%" />
<Card.Content>
<Card.Header>{gs.userName} </Card.Header>
<Card.Meta>[email protected]</Card.Meta>
<Card.Description>
xxxxx
</Card.Description>
</Card.Content>
<Card.Content extra>
xxxxxx
</Card.Content>
</Card>
</div>
)
:
(
<div>
<Card >
<Button basic size="tiny" compact onClick={()=>{this.handleClick()}} >
{/* <Header as='h5'> */}
<Icon name="angle down" />Preview
{/* </Header> */}
</Button>
</Card>
</div>
)
}
</div>
)
}
Example #21
Source File: ProfileWidgetPlus.tsx From communitymap-ui with Apache License 2.0 | 4 votes |
EditUserProfile: React.FC<{ user: firebase.User }> = ({ user }) => {
const [toggled, setToggle] = useState(false);
const info = useUserPublicInfo(user.uid, true);
const [changed, setChanged] = useState<{ name: string } | null>(null);
const { status, func: saveInfo } = useAsyncStatus(async () => {
return saveUserPublicInfo({
...(info || { id: user.uid }),
...(changed || { name: '' }),
});
});
if (info === undefined) return <Loader active />;
// if (info === null) return <div>User not found :(</div>;
const UserForm = () => (
<Form
onSubmit={() => saveInfo()}
loading={status.pending}
error={!!status.error}
success={status.success}
>
<Form.Input
label="Name/Nickname"
required
value={changed?.name || info?.name || ''}
onChange={(e, { value }) => setChanged({ ...changed, name: value })}
/>
{/* <Form.Input label="Gender"/> */}
<Message error>{status.error}</Message>
<Message success>Successfully saved</Message>
<Form.Group>
<Form.Button primary disabled={!changed}>
Save
</Form.Button>
<Form.Button
type="button"
disabled={!changed}
onClick={() => setChanged(null)}
>
Clear changes
</Form.Button>
</Form.Group>
</Form>
);
const BtnSettings = () => (
<Button
circular
icon="settings"
onClick={() => setToggle((toggled) => !toggled)}
/>
);
return (
<Card>
<Image
src="http://dwinery.com/ocm/wp-content/uploads/elementor/thumbs/avatar-ookvknm0adkt2o1cwyctxzbsfccgeo4fo00qr8xhao.png"
wrapped
ui={false}
/>
<Card.Content>
<Card.Header>
<Grid>
<Grid.Column floated="left" width={8}>
{info?.name}
</Grid.Column>
<Grid.Column floated="right" width={3}>
{BtnSettings()}
</Grid.Column>
</Grid>
</Card.Header>
<Card.Meta>
<span className="date">Joined in: {info?.created}</span>
</Card.Meta>
<Card.Description>{toggled && <>{UserForm()}</>}</Card.Description>
</Card.Content>
<Card.Content extra>
<Grid columns={2} divided>
<Grid.Row>
<Grid.Column>
<Icon name="handshake" /> 11 People
</Grid.Column>
<Grid.Column>
<Icon name="globe" /> 12 Places
</Grid.Column>
</Grid.Row>
</Grid>
</Card.Content>
</Card>
);
}
Example #22
Source File: event_information.tsx From website with MIT License | 4 votes |
function EventInformationTab({ eventData }) {
const { crewJson } = useStaticQuery(graphql`
query {
crewJson: allCrewJson {
edges {
node {
name
max_rarity
imageUrlPortrait
symbol
traits
traits_hidden
traits_named
base_skills {
security_skill {
core
}
command_skill {
core
}
diplomacy_skill {
core
}
engineering_skill {
core
}
medicine_skill {
core
}
science_skill {
core
}
}
}
}
}
}
`);
const crewData = crewJson.edges.map(edge => edge.node);
const crewMap = {};
crewData.forEach(crew => {
crewMap[crew.symbol] = crew;
})
const {
name,
description,
bonus_text,
content_types,
} = eventData;
const { bonus, featured } = getEventData(eventData, crewData);
const featuredCrewData = featured.map(symbol => {
const crew = crewMap[symbol];
return {
key: `crew_${crew.symbol}`,
name: crew.name,
image: getIconPath({file: crew.imageUrlPortrait}),
rarity: crew.max_rarity,
skills: Object.keys(crew.base_skills)
.filter(skill => !!crew.base_skills[skill])
.sort((a, b) => crew.base_skills[a].core > crew.base_skills[b].core ? -1 : 1)
.map(skill => ({
key: skill,
imageUrl: `${process.env.GATSBY_ASSETS_URL}atlas/icon_${skill}.png`
})),
traits: crew.traits_named,
};
});
const bonusCrew = crewData.filter(crew => bonus.includes(crew.symbol) && !featured.includes(crew.symbol));
return (
<>
<Card fluid raised>
<Card.Content>
<Card.Header>{name}</Card.Header>
<Card.Meta>{getEventType(content_types)}</Card.Meta>
<Card.Description>{description}</Card.Description>
</Card.Content>
<Card.Content extra>
<p>{bonus_text}</p>
</Card.Content>
</Card>
<Header as="h3">Featured Crew</Header>
<Card.Group>
{featuredCrewData.map(crew => (
<CrewCard key={crew.key} crew={crew} />
))}
</Card.Group>
<Header as="h3">Bonus Crew</Header>
{bonusCrew.length === 0 && (
<p>Bonus crew not yet determined for this event.</p>
)}
{sortCrew(bonusCrew).map(crew => (
<Label key={`crew_${crew.symbol}`} color="black" style={{ marginBottom: '5px' }}>
<Image
src={getIconPath({ file: crew.imageUrlPortrait })}
size="massive"
inline
spaced="right"
bordered
style={{
borderColor: getRarityColor(crew.max_rarity)
}}
alt={crew.name}
/>
{crew.name}
</Label>
))}
</>
);
}
Example #23
Source File: voyagecalculator.tsx From website with MIT License | 4 votes |
VoyageInput = (props: VoyageInputProps) => {
const { voyageConfig, myCrew, allShips, useInVoyage } = props;
const [bestShip, setBestShip] = React.useState(undefined);
const [consideredCrew, setConsideredCrew] = React.useState([]);
const [calculator, setCalculator] = React.useState(isMobile ? 'ussjohnjay' : 'iampicard');
const [calcOptions, setCalcOptions] = React.useState({});
const [telemetryOptOut, setTelemetryOptOut] = useStateWithStorage('telemetryOptOut', false, { rememberForever: true });
const [requests, setRequests] = React.useState([]);
const [results, setResults] = React.useState([]);
React.useEffect(() => {
// Note that allShips is missing the default ship for some reason (1* Constellation Class)
// This WILL break voyagecalculator if that's the only ship a player owns
const consideredShips = [];
allShips.filter(ship => ship.owned).forEach(ship => {
const traited = ship.traits.find(trait => trait === voyageConfig.ship_trait);
let entry = {
ship: ship,
score: ship.antimatter + (traited ? 150 : 0),
traited: traited,
bestIndex: Math.min(ship.index.left, ship.index.right)
};
consideredShips.push(entry);
});
consideredShips.sort((a, b) => {
if (a.score === b.score) return a.bestIndex - b.bestIndex;
return b.score - a.score;
});
setBestShip(consideredShips[0]);
setRequests([]);
setResults([]);
}, [voyageConfig]);
React.useEffect(() => {
return function cleanup() {
// Cancel active calculations when leaving page
requests.forEach(request => {
if (request.calcState == CalculatorState.InProgress)
request.abort();
});
}
}, []);
// Scroll here when calculator started, finished
const topAnchor = React.useRef(null);
const calculators = CALCULATORS.helpers.map(helper => {
return { key: helper.id, value: helper.id, text: helper.name };
});
calculators.push({ key: 'all', value: 'all', text: 'All calculators (slower)' });
return (
<React.Fragment>
<div ref={topAnchor} />
{renderBestShip()}
{renderResults()}
{requests.length > 0 && <Header as='h3'>Options</Header>}
<Form>
<InputCrewOptions myCrew={myCrew} updateConsideredCrew={setConsideredCrew} />
<Form.Group inline>
<Form.Field
control={Select}
label='Calculator'
options={calculators}
value={calculator}
onChange={(e, { value }) => setCalculator(value)}
placeholder='Select calculator'
/>
{CALCULATORS.fields.filter(field => field.calculators.includes(calculator) || calculator == 'all').map(field => (
<Form.Field
key={field.id}
control={Select} /* Only control allowed at the moment */
label={field.name}
options={field.options}
value={calcOptions[field.id] ?? field.default}
placeholder={field.description}
onChange={(e, { value }) => setCalcOptions(prevOptions =>
{
const newValue = { [field.id]: value };
return {...prevOptions, ...newValue};
}
)}
/>
))}
</Form.Group>
<Form.Group>
<Form.Button primary onClick={() => startCalculation()}>
Calculate best crew selection
</Form.Button>
{voyageConfig.state &&
<Form.Button onClick={()=> useInVoyage()}>
Return to in voyage calculator
</Form.Button>
}
</Form.Group>
</Form>
<Message style={{ marginTop: '2em' }}>
<Message.Content>
<Message.Header>Privacy Notice</Message.Header>
<p>We use anonymous statistics aggregated from voyage calculations to improve DataCore and power our <b><Link to='/hall_of_fame'>Voyage Hall of Fame</Link></b>.</p>
<Form>
<Form.Field
control={Checkbox}
label={<label>Permit DataCore to collect anonymous voyage stats</label>}
checked={!telemetryOptOut}
onChange={(e, { checked }) => setTelemetryOptOut(!checked) }
/>
</Form>
</Message.Content>
</Message>
</React.Fragment>
);
function renderBestShip(): JSX.Element {
if (!bestShip) return (<></>);
const direction = bestShip.ship.index.right < bestShip.ship.index.left ? 'right' : 'left';
const index = bestShip.ship.index[direction] ?? 0;
return (
<Card fluid>
<Card.Content>
<Image floated='left' src={`${process.env.GATSBY_ASSETS_URL}${bestShip.ship.icon.file.substr(1).replace('/', '_')}.png`} style={{ height: '4em' }} />
<Card.Header>{bestShip.ship.name}</Card.Header>
<p>best ship{bestShip.traited && (<span style={{ marginLeft: '1em' }}>{` +`}{allTraits.ship_trait_names[voyageConfig.ship_trait]}</span>)}</p>
<p style={{ marginTop: '.5em' }}>Tap <Icon name={`arrow ${direction}`} />{index} time{index != 1 ? 's' : ''} on your voyage ship selection screen to select {bestShip.ship.name}.</p>
</Card.Content>
</Card>
);
}
function renderResults(): JSX.Element {
if (results.length == 0)
return (<></>);
const showPopup = (result) => <Popup basic content={<p>{result.result.postscript}</p>} trigger={<p>{result.name}</p>} />
const panes = results.map(result => ({
menuItem: { key: result.id, content: result.result ? showPopup(result) : result.name },
render: () => (
<VoyageResultPane result={result.result}
requests={requests} requestId={result.requestId}
calcState={result.calcState} abortCalculation={abortCalculation}
/>
)
}));
return (
<React.Fragment>
<Header as='h3'>Recommended Lineups</Header>
<Tab menu={{ pointing: true }}
panes={panes}
/>
</React.Fragment>
);
}
function scrollToAnchor(): void {
if (!topAnchor.current) return;
topAnchor.current.scrollIntoView({
behavior: 'smooth'
}, 500);
}
function startCalculation(): void {
const helperConfig = {
voyageConfig, bestShip, consideredCrew, calcOptions,
resultsCallback: handleResults,
isMobile
};
CALCULATORS.helpers.forEach(helper => {
if (helper.id == calculator || calculator == 'all') {
const request = helper.helper(helperConfig);
requests.push(request);
results.push({
id: request.id,
requestId: request.id,
name: 'Calculating...',
calcState: CalculatorState.InProgress
});
request.start();
}
});
setRequests([...requests]);
setResults([...results]);
scrollToAnchor();
}
function handleResults(requestId: string, reqResults: any[], calcState: number): void {
reqResults.forEach((reqResult, idx) => {
// Update existing pane with results
if (idx == 0) {
setResults(prevResults => {
const result = prevResults.find(r => r.id == requestId);
if (calcState == CalculatorState.Done) {
result.name = formatTime(reqResult.estimate.refills[0].result);
result.calcState = CalculatorState.Done;
sendTelemetry(requestId, reqResult);
}
result.result = reqResult;
return [...prevResults];
});
}
// Add new panes if multiple results generated by this request
else {
setResults(prevResults => [...prevResults, {
id: requestId+'-'+idx,
requestId,
name: formatTime(reqResult.estimate.refills[0].result),
calcState: CalculatorState.Done,
result: reqResult
}]);
}
});
if (calcState == CalculatorState.Done) scrollToAnchor();
}
function abortCalculation(requestId: string): void {
const request = requests.find(r => r.id == requestId);
if (request) {
request.abort();
setResults(prevResults => {
const result = prevResults.find(prev => prev.id == requestId);
if (result.result) {
result.name = formatTime(result.result.estimate.refills[0].result);
result.calcState = CalculatorState.Done;
}
else {
const index = prevResults.findIndex(prev => prev.id == requestId);
prevResults.splice(index, 1);
}
return [...prevResults];
});
}
}
function sendTelemetry(requestId: string, result: any): void {
if (telemetryOptOut) return;
const request = requests.find(r => r.id == requestId);
const estimatedDuration = result.estimate.refills[0].result*60*60;
try {
fetch(`${process.env.GATSBY_DATACORE_URL}api/telemetry`, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'voyageCalc',
data: {
voyagers: result.entries.map((entry) => entry.choice.symbol),
estimatedDuration,
calculator: request ? request.calculator : ''
}
})
});
}
catch (err) {
console.log('An error occurred while sending telemetry', err);
}
}
}