semantic-ui-react#Table TypeScript Examples
The following examples show how to use
semantic-ui-react#Table.
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: items.tsx From website with MIT License | 6 votes |
renderTableRow(item: any): JSX.Element {
return (
<Table.Row key={item.symbol}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${item.imageUrl}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<Link to={`/item_info?symbol=${item.symbol}`}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{item.rarity > 0 && (
<span>
{item.rarity} <Icon name="star" />{' '}
</span>
)}
{item.name}
</span>
</Link>
</div>
<div style={{ gridArea: 'description' }}>{item.flavor}</div>
</div>
</Table.Cell>
<Table.Cell>{CONFIG.REWARDS_ITEM_TYPE[item.type]}</Table.Cell>
<Table.Cell>{CONFIG.RARITIES[item.rarity].name}</Table.Cell>
<Table.Cell>{item.flavor}</Table.Cell>
</Table.Row>
);
}
Example #2
Source File: threshold_rewards.tsx From website with MIT License | 6 votes |
function ThresholdRewardsTab({eventData}) {
const {threshold_rewards} = eventData;
return (
<Table celled striped compact='very'>
<Table.Body>
{threshold_rewards.map(row => (
<Table.Row key={row.points}>
<Table.Cell>{row.points}</Table.Cell>
<Table.Cell>
{row.rewards.map(reward => (
<Label key={`reward_${reward.id}`} color="black">
<Image
src={getIconPath(reward.icon)}
size="small"
inline
spaced="right"
bordered
style={{
borderColor: getRarityColor(reward.rarity),
maxWidth: '27px',
maxHeight: '27px'
}}
alt={reward.full_name}
/>
{reward.full_name}
{reward.quantity > 1 ? ` x ${reward.quantity}` : ''}
</Label>
))}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
);
}
Example #3
Source File: ranked_rewards.tsx From website with MIT License | 6 votes |
function RankedRewardsTab({eventData}) {
const {ranked_brackets} = eventData;
return (
<Table celled striped compact='very'>
<Table.Body>
{ranked_brackets.map(row => (
<Table.Row key={`bracket_${row.first}_${row.last}`}>
<Table.Cell width={2}>{getBracketLabel(row)}</Table.Cell>
<Table.Cell width={14}>
{row.rewards.map(reward => (
<Label key={`reward_${reward.id}`} color="black">
<Image
src={getIconPath(reward.icon)}
size="small"
inline
spaced="right"
bordered
style={{
borderColor: getRarityColor(reward.rarity ?? 0),
maxWidth: '27px',
maxHeight: '27px'
}}
alt={reward.full_name}
/>
{reward.full_name}
{reward.quantity > 1 ? ` x ${reward.quantity}` : ''}
</Label>
))}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
);}
Example #4
Source File: crewchallenge.tsx From website with MIT License | 6 votes |
GuessTable = (props) => {
const { solveState, solvedCrew } = props;
const guessesEvaluated = props.guessesEvaluated.slice();
if (solveState === SolveState.Loser) guessesEvaluated.push({... solvedCrew, evaluation: { crew: EvaluationState.Exact }});
if (guessesEvaluated.length === 0) return (<></>);
return (
<div style={{ overflow: 'auto' }}>
<Table striped celled unstackable>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Your Guesses</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Series</Table.HeaderCell>
<Table.HeaderCell>Rarity</Table.HeaderCell>
<Table.HeaderCell colSpan={3} textAlign='center'>Skill Order</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Traits in Common</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{guessesEvaluated.map(guess => (
<GuessRow key={guess.symbol} guess={guess} solveState={solveState} guessCount={props.guessesEvaluated.length} />
))}
</Table.Body>
</Table>
</div>
);
}
Example #5
Source File: crewchallenge.tsx From website with MIT License | 5 votes |
GuessRow = (props: GuessRowProps) => {
const { guess, solveState, guessCount } = props;
const isSolution = guess.evaluation.crew === EvaluationState.Exact;
const traits = guess.evaluation.traits ?? guess.traits;
return (
<Table.Row {...styleRow()}>
<Table.Cell {...styleCell(guess.evaluation.variant)}>
{isSolution && (
<div>
{solveState === SolveState.Winner && (<span style={{ whiteSpace: 'nowrap' }}>You got it in {guessCount} tr{guessCount !== 1 ? 'ies' : 'y'}!</span>)}
{solveState === SolveState.Loser && (<span style={{ whiteSpace: 'nowrap' }}>You lose! The correct answer is:</span>)}
</div>
)}
<div style={{ margin: '.5em 0', whiteSpace: 'nowrap' }}>
<img width={48} height={48} src={`${process.env.GATSBY_ASSETS_URL}${guess.imageUrlPortrait}`} style={{ verticalAlign: 'middle' }} />
<span style={{ padding: '0 .5em', fontSize: '1.25em' }}>{guess.name}</span>
</div>
{isSolution && guess.flavor && (
<div>{guess.flavor}</div>
)}
</Table.Cell>
<Table.Cell textAlign='center' {...styleCell(guess.evaluation.series)}>
{guess.series && <Image src={`/media/series/${guess.series}.png`} size='small' style={{ margin: '0 auto' }} />}
</Table.Cell>
<Table.Cell {...styleCell(guess.evaluation.rarity)}>
<Rating defaultRating={guess.rarity} maxRating={guess.rarity} icon='star' size='large' disabled />
</Table.Cell>
{guess.skills.map((skill, idx) => (
<Table.Cell key={idx} textAlign='center' {...styleCell(guess.evaluation.skills ? guess.evaluation.skills[idx] : 0)}>
{skill.skill !== '' && <img alt={idx} src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${skill.skill}.png`} style={{ height: '2em' }} />}
{skill.skill === '' && <Icon name='minus' />}
</Table.Cell>
))}
<Table.Cell textAlign='center'>
{traits.map((trait, idx) => (
<span key={idx} style={{ whiteSpace: 'nowrap' }}>
{formatTrait(trait)}{idx < traits.length-1 ? ',' : ''}
</span>
)).reduce((prev, curr) => [prev, ' ', curr], [])}
</Table.Cell>
</Table.Row>
);
function styleRow(): any {
if (!isSolution) return {};
const attributes = {};
attributes.style = solveState === SolveState.Winner ? STYLE_SOLVED : STYLE_LOSER;
return attributes;
}
function styleCell(evaluationState: number): any {
const attributes = {};
if (evaluationState === EvaluationState.Exact)
attributes.style = STYLE_SOLVED;
else if (evaluationState === EvaluationState.Adjacent)
attributes.style = STYLE_ADJACENT;
return attributes;
}
function formatTrait(trait: string): string {
const simpleName = (trait: string) => {
return trait.replace(/[^A-Z]/gi, '').toLowerCase();
};
const properName = (trait: string) => {
return trait.replace(/_/g, ' ').split(' ').map(word => word.substr(0, 1).toUpperCase()+word.substr(1)).join(' ');
};
// Display short_name instead of variant trait when appropriate
if (guess.variants.includes(trait)) {
if (simpleName(trait).indexOf(simpleName(guess.short_name)) >= 0
|| simpleName(guess.short_name).indexOf(simpleName(trait)) >= 0)
return guess.short_name;
}
return properName(trait);
}
}
Example #6
Source File: leaderboard.tsx From website with MIT License | 5 votes |
function LeaderboardTab({leaderboard}) {
return (
<>
<Message>
If this event is currently active, the leaderboard below might be out of date.
(Data is updated only a couple of times a week)
</Message>
<Table celled striped compact='very'>
<Table.Body>
{leaderboard.map(member => (
<Table.Row key={member.dbid}>
<Table.Cell style={{ fontSize: '1.25em' }}>
Rank: {member.rank}
</Table.Cell>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}>
<div style={{ gridArea: 'icon' }}>
<img
width={48}
src={member.avatar ? getIconPath(member.avatar) : `${process.env.GATSBY_ASSETS_URL}crew_portraits_cm_empty_sm.png`}
/>
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{member.display_name}
</span>
</div>
<div style={{ gridArea: 'description' }}>
Level {member.level}
</div>
</div>
</Table.Cell>
<Table.Cell style={{ fontSize: '1.25em' }}>
Score: {member.score}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</>
);
}
Example #7
Source File: index.tsx From website with MIT License | 5 votes |
renderTableRow(crew: any, idx: number, highlighted: boolean): JSX.Element {
const { customColumns } = this.state;
const attributes = {
positive: highlighted
};
const counts = [
{ name: 'event', count: crew.events },
{ name: 'collection', count: crew.collections.length }
];
const formattedCounts = counts.map((count, idx) => (
<span key={idx} style={{ whiteSpace: 'nowrap' }}>
{count.count} {count.name}{count.count != 1 ? 's' : ''}{idx < counts.length-1 ? ',' : ''}
</span>
)).reduce((prev, curr) => [prev, ' ', curr]);
return (
<Table.Row key={crew.symbol} style={{ cursor: 'zoom-in' }} onClick={() => navigate(`/crew/${crew.symbol}/`)} {...attributes}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}><Link to={`/crew/${crew.symbol}/`}>{crew.name}</Link></span>
</div>
<div style={{ gridArea: 'description' }}>
{formattedCounts}
</div>
</div>
</Table.Cell>
<Table.Cell>
<Rating icon='star' rating={crew.max_rarity} maxRating={crew.max_rarity} size='large' disabled />
</Table.Cell>
<Table.Cell textAlign="center">
<b>{formatTierLabel(crew.bigbook_tier)}</b>
</Table.Cell>
<Table.Cell style={{ textAlign: 'center' }}>
<b>{crew.cab_ov}</b><br />
<small>{rarityLabels[parseInt(crew.max_rarity)-1]} #{crew.cab_ov_rank}</small>
</Table.Cell>
<Table.Cell style={{ textAlign: 'center' }}>
<b>#{crew.ranks.voyRank}</b><br />
{crew.ranks.voyTriplet && <small>Triplet #{crew.ranks.voyTriplet.rank}</small>}
</Table.Cell>
{CONFIG.SKILLS_SHORT.map(skill =>
crew.base_skills[skill.name] ? (
<Table.Cell key={skill.name} textAlign='center'>
<b>{crew.base_skills[skill.name].core}</b>
<br />
+({crew.base_skills[skill.name].range_min}-{crew.base_skills[skill.name].range_max})
</Table.Cell>
) : (
<Table.Cell key={skill.name} />
)
)}
{customColumns.map(column => {
const value = column.split('.').reduce((prev, curr) => prev && prev[curr] ? prev[curr] : undefined, crew);
if (value) {
return (
<Table.Cell key={column} textAlign='center'>
<b>{value}</b>
</Table.Cell>
);
}
else {
return (<Table.Cell key={column} />);
}
})}
</Table.Row>
);
}
Example #8
Source File: profile_other.tsx From website with MIT License | 5 votes |
render() {
const { playerData } = this.props;
const { missions } = this.state;
return (
<div>
<Table celled selectable striped collapsing unstackable compact="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell width={2}>Activity</Table.HeaderCell>
<Table.HeaderCell width={1}>Status</Table.HeaderCell>
<Table.HeaderCell width={3}>Description</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{playerData.player.character.daily_activities.map((da, idx) =>
(da.status && da.lifetime !== 0) ? (
<Table.Row key={idx}>
<Table.Cell>{da.name}</Table.Cell>
<Table.Cell>{da.status}</Table.Cell>
<Table.Cell>{da.description}</Table.Cell>
</Table.Row>
) : (
undefined
)
)}
</Table.Body>
</Table>
<Table celled selectable striped collapsing unstackable compact="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell width={3}>Completed missions</Table.HeaderCell>
<Table.HeaderCell width={3}>Status</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{missions.map((mission, idx) => (
<Table.Row key={idx}>
<Table.Cell>{mission.name}</Table.Cell>
<Table.Cell>
Completed {mission.stars_earned} of {mission.total_stars} missions
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</div>
);
}
Example #9
Source File: voyagestats.tsx From website with MIT License | 5 votes |
_renderEstimate(needsRevive: boolean = false) {
const estimate = this.props.estimate ?? this.state.estimate;
if (!estimate)
return (<div>Calculating estimate. Please wait...</div>);
const renderEst = (label, refills) => {
const est = estimate['refills'][refills];
return (
<tr>
<td>{label}: {this._formatTime(est.result)}</td>
{!isMobile && <td>90%: {this._formatTime(est.safeResult)}</td>}
<td>99%: {this._formatTime(est.saferResult)}</td>
<td>Chance of {est.lastDil} hour dilemma: {Math.floor(est.dilChance)}%</td>
<td>{est.refillCostResult == 0 || 'Costing ' + est.refillCostResult + ' dilithium'}</td>
</tr>
);
};
if (estimate.deterministic) {
let extendTime = estimate['refills'][1].result - estimate['refills'][0].result;
return (
<div>
The voyage will end at {this._formatTime(estimate['refills'][0].result)}.
Subsequent refills will extend it by {this._formatTime(extendTime)}.
For a 20 hour voyage you need {estimate['20hrrefills']} refills at a cost of {estimate['20hrdil']} dilithium.
</div>
);
} else {
let refill = 0;
return (
<div>
<Table><tbody>
{!needsRevive && renderEst("Estimate", refill++)}
{renderEst("1 Refill", refill++)}
{renderEst("2 Refills", refill++)}
</tbody></Table>
<p>The 20 hour voyage needs {estimate['20hrrefills']} refills at a cost of {estimate['20hrdil']} dilithium.</p>
{this._renderChart()}
<small>Powered by Chewable C++</small>
</div>
);
}
}
Example #10
Source File: eventplanner.tsx From website with MIT License | 5 votes |
EventCrewMatrix = (props: EventCrewMatrixProps) => {
const { crew, bestCombos, phaseType, handleClick } = props;
return (
<React.Fragment>
<Header as='h4'>Skill Matrix</Header>
<p>This table shows your best crew for each possible skill combination. Use this table to identify your best crew for this event{phaseType === 'shuttles' ? ` and the best candidates to share in a faction event if you are a squad leader` : ''}.</p>
<Table definition celled striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell />
{CONFIG.SKILLS_SHORT.map((skill, cellId) => (
<Table.HeaderCell key={cellId} textAlign='center'>
<img alt={`${skill.name}`} src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${skill.name}.png`} style={{ height: '1.1em' }} />
</Table.HeaderCell>
))}
</Table.Row>
</Table.Header>
<Table.Body>
{CONFIG.SKILLS_SHORT.map((skillA, rowId) => (
<Table.Row key={rowId}>
<Table.Cell width={1} textAlign='center'><img alt={`${skillA.name}`} src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${skillA.name}.png`} style={{ height: '1.1em' }} /></Table.Cell>
{CONFIG.SKILLS_SHORT.map((skillB, cellId) => renderCell(skillA.name, skillB.name))}
</Table.Row>
))}
</Table.Body>
</Table>
</React.Fragment>
);
function renderCell(skillA: string, skillB: string) : JSX.Element {
let key, best;
if (skillA === skillB) {
key = skillA;
best = bestCombos[skillA];
}
else {
key = skillA+skillB;
best = bestCombos[skillA+skillB] ?? bestCombos[skillB+skillA];
}
if (!best) best = {score: 0};
if (best.score > 0) {
let bestCrew = crew.find(c => c.id === best.id);
let icon = (<></>);
if (bestCrew.immortal) icon = (<Icon name='snowflake' />);
if (bestCrew.prospect) icon = (<Icon name='add user' />);
return (
<Table.Cell key={key} textAlign='center' style={{ cursor: 'zoom-in' }} onClick={() => handleClick(skillA, skillB)}>
<img width={36} src={`${process.env.GATSBY_ASSETS_URL}${bestCrew.imageUrlPortrait}`} /><br/>{icon} {bestCrew.name} <small>({phaseType === 'gather' ? `${calculateGalaxyChance(best.score)}%` : Math.floor(best.score)})</small>
</Table.Cell>
);
}
return (
<Table.Cell key={key} textAlign='center'>-</Table.Cell>
);
}
}
Example #11
Source File: voyagehof.tsx From website with MIT License | 5 votes |
VoyageStatsForPeriod = ({ period, stats, allCrew }) => {
const rankedCrew = stats.map((s) => {
const crew = allCrew.find((c) => c.symbol === s.crewSymbol);
if (!crew) {
return undefined;
}
return {
...s,
...crew
}
}).filter((s) => s).sort((a, b) => b.crewCount - a.crewCount).slice(0,100);
const rowColors = {
'0': '#AF9500',
'1': '#B4B4B4',
'2': '#AD8A56'
};
return (
<>
<Header textAlign="center">Voyage stats for {niceNamesForPeriod[period]}</Header>
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Rank</Table.HeaderCell>
<Table.HeaderCell textAlign="right">Crew</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{rankedCrew.map((crew, index) => (
<Table.Row>
<Table.Cell>
<Header as='h2' textAlign='center' style={{ color: rowColors[index] }}>{index+1}</Header>
</Table.Cell>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '80px auto',
gridTemplateAreas: `'icon name'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}/${crew.imageUrlPortrait}`} />
</div>
<div style={{ gridArea: 'name' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>{crew.name}</span>
<Header as='h3' style={{marginTop: '10px'}}>{crew.crewCount} voyages</Header>
</div>
</div>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</>
)
}
Example #12
Source File: factions.tsx From website with MIT License | 5 votes |
render() {
const { factionInfo, shuttleBays } = this.props;
const { successOdds } = this.state;
const updateSuccessOdds = odds => this.setState({successOdds: odds});
return (
<>
<p><span>Running shuttles at average odds of </span>
<Dropdown text={`${successOdds}%`}>
<Dropdown.Menu>
{oddsValues.map(val => (<Dropdown.Item onClick={(e, { value }) => updateSuccessOdds(value)} text={`${val}%`} value={val} />))}
</Dropdown.Menu>
</Dropdown>
<p>(Note: Shuttles cannot be run with a probability of success less than 14%. Shuttles need a probability of less than 60% to be tanked.)</p>
</p>
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Faction</Table.HeaderCell>
<Table.HeaderCell>Reputation</Table.HeaderCell>
<Table.HeaderCell>Shuttles needed</Table.HeaderCell>
<Table.HeaderCell>Time needed</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{factionInfo.map((faction, index) => {
let shuttlesNeededToMaxRep = this._shuttlesToHonouredStatus(faction.reputation);
let hoursNeededToMaxRep = Math.ceil(shuttlesNeededToMaxRep/shuttleBays)*3;
let shuttlesNeededToTank = Math.ceil(faction.completed_shuttle_adventures/this._expectedCSA(successOdds/100));
let hoursNeededToTank = Math.ceil(shuttlesNeededToTank/shuttleBays)*3;
return (
<Table.Row key={index}>
<Table.Cell><span><Image floated='left' size='mini' src={`${process.env.GATSBY_ASSETS_URL}icons_icon_faction_${factionImageLocations[index]}.png`} />{faction.name}</span></Table.Cell>
<Table.Cell>{this._reputations(faction.reputation)}</Table.Cell>
<Table.Cell>
{faction.reputation < 980 && <p>You need {shuttlesNeededToMaxRep} successful shuttle missions to achieve honored status.</p>}
{shuttlesNeededToTank > 0 && <p>To tank your shuttles you need to run {shuttlesNeededToTank} shuttles.</p>}
{shuttlesNeededToTank == 0 && <p>Already tanked</p>}
</Table.Cell>
<Table.Cell>
{faction.reputation < 980 && <p>{this._formatTime(hoursNeededToMaxRep)}</p>}
<p>{this._formatTime(hoursNeededToTank)}</p>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table>
<p>Note: <a href="https://www.reddit.com/r/StarTrekTimelines/comments/aq5qzg/guide_tanked_shuttles_why_and_how/">Tanking</a> shuttles is the process of deliberately failing shuttles so that the difficulty and duration of shuttle missions go down.</p>
</>
);
}
Example #13
Source File: PermanentRoomModal.tsx From watchparty with MIT License | 5 votes |
render() {
const { closeModal } = this.props;
return (
<Modal open={true} onClose={closeModal as any} closeIcon>
<Modal.Header>Permanent Rooms</Modal.Header>
<Modal.Content image>
<Modal.Description>
<div>
Registered users have the ability to make their rooms permanent.
Subscribed users can create multiple permanent rooms.
</div>
<Table definition unstackable striped celled>
<Table.Header>
<Table.Row>
<Table.HeaderCell />
<Table.HeaderCell>Temporary</Table.HeaderCell>
<Table.HeaderCell>Permanent</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>Expiry</Table.Cell>
<Table.Cell>After 24 hours of inactivity</Table.Cell>
<Table.Cell>Never</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Room Passwords</Table.Cell>
<Table.Cell></Table.Cell>
<Table.Cell>
<Icon name="check" />
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Disable Chat</Table.Cell>
<Table.Cell></Table.Cell>
<Table.Cell>
<Icon name="check" />
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Kick Users</Table.Cell>
<Table.Cell></Table.Cell>
<Table.Cell>
<Icon name="check" />
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Custom Room URLs (subscribers)</Table.Cell>
<Table.Cell></Table.Cell>
<Table.Cell>
<Icon name="check" />
</Table.Cell>
</Table.Row>
{/* <Table.Row>
<Table.Cell>Max Room Capacity (subscribers)</Table.Cell>
<Table.Cell>20</Table.Cell>
<Table.Cell>100</Table.Cell>
</Table.Row> */}
</Table.Body>
</Table>
</Modal.Description>
</Modal.Content>
</Modal>
);
}
Example #14
Source File: searchabletable.tsx From website with MIT License | 4 votes |
SearchableTable = (props: SearchableTableProps) => {
let data = [...props.data];
const tableId = props.id ?? '';
const hasDate = data.length > 0 && data[0].date_added;
const defaultSort = {
column: data.length > 0 && hasDate ? 'date_added' : 'name',
direction: data.length > 0 && hasDate ? 'descending' : 'ascending'
};
const [searchFilter, setSearchFilter] = useStateWithStorage(tableId+'searchFilter', '');
const [filterType, setFilterType] = useStateWithStorage(tableId+'filterType', 'Any match');
const [column, setColumn] = useStateWithStorage(tableId+'column', defaultSort.column);
const [direction, setDirection] = useStateWithStorage(tableId+'direction', defaultSort.direction);
const [pagination_rows, setPaginationRows] = useStateWithStorage(tableId+'paginationRows', 10);
const [pagination_page, setPaginationPage] = useStateWithStorage(tableId+'paginationPage', 1);
const [activeLock, setActiveLock] = React.useState(undefined);
// Override stored values with custom initial options and reset all others to defaults
// Previously stored values will be rendered before an override triggers a re-render
React.useEffect(() => {
if (props.initOptions) {
setSearchFilter(props.initOptions['search'] ?? '');
setFilterType(props.initOptions['filter'] ?? 'Any match');
setColumn(props.initOptions['column'] ?? defaultSort.column);
setDirection(props.initOptions['direction'] ?? defaultSort.direction);
setPaginationRows(props.initOptions['rows'] ?? 10);
setPaginationPage(props.initOptions['page'] ?? 1);
}
}, [props.initOptions]);
// Activate lock by default if only 1 lockable
React.useEffect(() => {
setActiveLock(props.lockable?.length === 1 ? props.lockable[0] : undefined);
}, [props.lockable]);
// Update column and/or toggle direction, and store new values in state
// Actual sorting of full dataset will occur on next render before filtering and pagination
function onHeaderClick(newColumn) {
if (!newColumn.column) return;
const lastColumn = column, lastDirection = direction;
const sortConfig = {
field: newColumn.column,
direction: lastDirection === 'ascending' ? 'descending' : 'ascending'
};
if (newColumn.pseudocolumns && newColumn.pseudocolumns.includes(lastColumn)) {
if (direction === 'descending') {
const nextIndex = newColumn.pseudocolumns.indexOf(lastColumn) + 1; // Will be 0 if previous column was not a pseudocolumn
sortConfig.field = newColumn.pseudocolumns[nextIndex === newColumn.pseudocolumns.length ? 0 : nextIndex];
sortConfig.direction = 'ascending';
}
else {
sortConfig.field = lastColumn;
sortConfig.direction = 'descending';
}
}
else if (newColumn.column !== lastColumn) {
sortConfig.direction = newColumn.reverse ? 'descending' : 'ascending';
}
setColumn(sortConfig.field);
setDirection(sortConfig.direction);
setPaginationPage(1);
}
function onChangeFilter(value) {
setSearchFilter(value);
setPaginationPage(1);
}
function renderTableHeader(column: any, direction: 'descending' | 'ascending' | null): JSX.Element {
return (
<Table.Row>
{props.config.map((cell, idx) => (
<Table.HeaderCell
key={idx}
width={cell.width as any}
sorted={((cell.pseudocolumns && cell.pseudocolumns.includes(column)) || (column === cell.column)) ? direction : null}
onClick={() => onHeaderClick(cell)}
textAlign={cell.width === 1 ? 'center' : 'left'}
>
{cell.title}{cell.pseudocolumns?.includes(column) && <><br/><small>{column.replace('_',' ').replace('.length', '')}</small></>}
</Table.HeaderCell>
))}
</Table.Row>
);
}
function renderPermalink(): JSX.Element {
// Will not catch custom options (e.g. highlight)
const params = new URLSearchParams();
if (searchFilter != '') params.append('search', searchFilter);
if (filterType != 'Any match') params.append('filter', filterType);
if (column != defaultSort.column) params.append('column', column);
if (direction != defaultSort.direction) params.append('direction', direction);
if (pagination_rows != 10) params.append('rows', pagination_rows);
if (pagination_page != 1) params.append('page', pagination_page);
let permalink = window.location.protocol + '//' + window.location.host + window.location.pathname;
if (params.toString() != '') permalink += '?' + params.toString();
return (
<Link to={permalink}>
<Icon name='linkify' /> Permalink
</Link>
);
}
function onLockableClick(lock: any): void {
if (lock) {
setActiveLock(lock);
}
else {
setActiveLock(undefined);
// Remember active page after removing lock
setPaginationPage(activePage);
}
}
function isRowActive(row: any, highlight: any): boolean {
if (!highlight) return false;
let isMatch = true;
Object.keys(highlight).forEach(key => {
if (row[key] !== highlight[key]) isMatch = false;
});
return isMatch;
}
// Sorting
if (column) {
const sortConfig: IConfigSortData = {
field: column,
direction: direction,
keepSortOptions: true
};
// Define tiebreaker rules with names in alphabetical order as default
// Hack here to sort rarity in the same direction as max_rarity
let subsort = [];
const columnConfig = props.config.find(col => col.column === column);
if (columnConfig && columnConfig.tiebreakers) {
subsort = columnConfig.tiebreakers.map(subfield => {
const subdirection = subfield.substr(subfield.length-6) === 'rarity' ? direction : 'ascending';
return { field: subfield, direction: subdirection };
});
}
if (column != 'name') subsort.push({ field: 'name', direction: 'ascending' });
sortConfig.subsort = subsort;
// Use original dataset for sorting
const sorted: IResultSortDataBy = sortDataBy([...props.data], sortConfig);
data = sorted.result;
// Sorting by pre-calculated ranks should filter out crew without matching skills
// Otherwise crew without skills show up first (because 0 comes before 1)
if (column.substr(0, 5) === 'ranks') {
const rank = column.split('.')[1];
data = data.filter(row => row.ranks[rank] > 0);
}
}
// Filtering
let filters = [];
if (searchFilter) {
let grouped = searchFilter.split(/\s+OR\s+/i);
grouped.forEach(group => {
filters.push(SearchString.parse(group));
});
}
data = data.filter(row => props.filterRow(row, filters, filterType));
// Pagination
let activePage = pagination_page;
if (activeLock) {
const index = data.findIndex(row => isRowActive(row, activeLock));
// Locked crew is not viewable in current filter
if (index < 0) {
setActiveLock(undefined);
return (<></>);
}
activePage = Math.floor(index / pagination_rows) + 1;
}
let totalPages = Math.ceil(data.length / pagination_rows);
if (activePage > totalPages) activePage = totalPages;
data = data.slice(pagination_rows * (activePage - 1), pagination_rows * activePage);
return (
<div>
<Input
style={{ width: isMobile ? '100%' : '50%' }}
iconPosition="left"
placeholder="Search..."
value={searchFilter}
onChange={(e, { value }) => onChangeFilter(value)}>
<input />
<Icon name='search' />
<Button icon onClick={() => onChangeFilter('')} >
<Icon name='delete' />
</Button>
</Input>
{props.showFilterOptions && (
<span style={{ paddingLeft: '2em' }}>
<Dropdown inline
options={filterTypeOptions}
value={filterType}
onChange={(event, {value}) => setFilterType(value as number)}
/>
</span>
)}
<Popup wide trigger={<Icon name="help" />}
header={'Advanced search'}
content={props.explanation ? props.explanation : renderDefaultExplanation()}
/>
{props.lockable && <LockButtons lockable={props.lockable} activeLock={activeLock} setLock={onLockableClick} />}
<Table sortable celled selectable striped collapsing unstackable compact="very">
<Table.Header>{renderTableHeader(column, direction)}</Table.Header>
<Table.Body>{data.map((row, idx) => props.renderTableRow(row, idx, isRowActive(row, activeLock)))}</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan={props.config.length}>
<Pagination
totalPages={totalPages}
activePage={activePage}
onPageChange={(event, { activePage }) => {
setPaginationPage(activePage as number);
setActiveLock(undefined); // Remove lock when changing pages
}}
/>
<span style={{ paddingLeft: '2em'}}>
Rows per page:{' '}
<Dropdown
inline
options={pagingOptions}
value={pagination_rows}
onChange={(event, {value}) => {
setPaginationPage(1);
setPaginationRows(value as number);
}}
/>
</span>
{props.showPermalink && (<span style={{ paddingLeft: '5em'}}>{renderPermalink()}</span>)}
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
</div>
);
}
Example #15
Source File: event_info.tsx From website with MIT License | 4 votes |
render() {
const { event_instace, errorMessage, event_data } = this.state;
if (event_instace === undefined || event_data === undefined || errorMessage !== undefined) {
return (
<Layout title='Event information'>
<Header as='h4'>Event information</Header>
{errorMessage && (
<Message negative>
<Message.Header>Unable to load event information</Message.Header>
<pre>{errorMessage.toString()}</pre>
</Message>
)}
{!errorMessage && (
<div>
<Icon loading name='spinner' /> Loading...
</div>
)}
</Layout>
);
}
return (
<Layout title={event_data.ev_inst.event_name}>
<Header as='h3'>{event_data.ev_inst.event_name}</Header>
<Image size='large' src={`${process.env.GATSBY_ASSETS_URL}${event_data.ev_inst.image}`} />
{this.renderEventDetails()}
{this.renderEventLog()}
<Header as='h4'>Leaderboard</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={3}>Name</Table.HeaderCell>
<Table.HeaderCell width={1}>Rank</Table.HeaderCell>
<Table.HeaderCell width={1}>Score</Table.HeaderCell>
<Table.HeaderCell width={2}>Fleet</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event_data.ev_lead.leaderboard.map((member, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}>
<div style={{ gridArea: 'icon' }}>
<img
width={48}
src={`${process.env.GATSBY_ASSETS_URL}${member.avatar ? member.avatar.file.substr(1).replace(/\//g, '_') + '.png' : 'crew_portraits_cm_empty_sm.png'
}`}
/>
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{member.last_update ? (
<Link to={`/profile?dbid=${member.dbid}`}>{member.display_name}</Link>
) : (
<span>{member.display_name}</span>
)}
</span>
</div>
<div style={{ gridArea: 'description' }}>
Level {member.level}
{member.last_update && (
<Label size='tiny'>Last profile upload: {new Date(Date.parse(member.last_update)).toLocaleDateString()}</Label>
)}
</div>
</div>
</Table.Cell>
<Table.Cell>{member.rank}</Table.Cell>
<Table.Cell>{member.score}</Table.Cell>
<Table.Cell>
{member.fleetname ? <Link to={`/fleet_info?fleetid=${member.fleetid}`}>
<b>{member.fleetname}</b>
</Link> : <span>-</span>}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
{event_data.ev_flead &&
<div>
<Message>
<Message.Header>TODO: Fleet Leaderboard is experimental</Message.Header>
This data may be incomplete or out of date!
</Message>
<Header as='h4'>Fleet leaderboard</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={3}>Name</Table.HeaderCell>
<Table.HeaderCell width={1}>Rank</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event_data.ev_flead.fleet_ranks.map((fleet, idx) => (
<Table.Row key={idx}>
<Table.Cell>{fleet.fleet_rank}</Table.Cell>
<Table.Cell>
<Link to={`/fleet_info?fleetid=${fleet.id}`}>
<b>{fleet.name}</b>
</Link>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</div>}
</Layout>
);
}
Example #16
Source File: assignmentslist.tsx From website with MIT License | 4 votes |
AssignmentsList = (props: AssignmentsList) => {
const { shuttlers, setShuttlers, assigned, setAssigned, crewScores, updateCrewScores } = props;
const [shuttleScores, setShuttleScores] = React.useState([]);
const [editAssignment, setEditAssignment] = React.useState(undefined);
const [scoreLoadQueue, setScoreLoadQueue] = React.useState('');
React.useEffect(() => {
updateShuttleScores();
}, [assigned]);
const myCrew = props.crew;
const SeatAssignmentRow = (props: { shuttleId: string, seatNum: number, seat: ShuttleSeat }) => {
const { shuttleId, seatNum, seat } = props;
let assignedCrew;
const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum === seatNum);
if (seated) {
assignedCrew = myCrew.find(crew => crew.id === seated.assignedId && crew.symbol === seated.assignedSymbol);
if (!assignedCrew) assignedCrew = myCrew.find(crew => crew.symbol === seated.assignedSymbol);
}
const lockAttributes = {};
if (seated?.locked) lockAttributes.color = 'yellow';
return (
<Table.Row key={seatNum} style={{ cursor: 'pointer' }}
onClick={() => { if (seat.skillA) setEditAssignment({shuttleId, seatNum}); }}
>
<Table.Cell textAlign='center'>
<SeatSkillView seat={seat} />
</Table.Cell>
<Table.Cell textAlign={assignedCrew ? 'left' : 'right'}>
{assignedCrew && (<SeatCrewView crew={assignedCrew} />)}
{!assignedCrew && (<span style={{ color: 'gray' }}>(Open seat)</span>)}
</Table.Cell>
<Table.Cell>
{assignedCrew?.immortal > 0 && (<Icon name='snowflake' />)}
{assignedCrew?.prospect && (<Icon name='add user' />)}
</Table.Cell>
<Table.Cell textAlign='center'>
{assignedCrew && (
<Button.Group>
<Button compact icon='lock' {... lockAttributes}
onClick={(e) => { toggleAssignmentLock(shuttleId, seatNum); e.stopPropagation(); }} />
<Button compact icon='x'
onClick={(e) => { updateAssignment(shuttleId, seatNum); e.stopPropagation(); }} />
</Button.Group>
)}
</Table.Cell>
</Table.Row>
);
};
const SeatAssignmentPicker = () => {
const { shuttleId, seatNum } = editAssignment;
const [paginationPage, setPaginationPage] = React.useState(1);
const seat = shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId).seats[seatNum];
const ssId = getSkillSetId(seat);
const scores = crewScores.skillsets[ssId];
if (!scores) {
if (scoreLoadQueue === '') {
setScoreLoadQueue(ssId);
updateCrewScores([seat], () => setScoreLoadQueue(''));
}
return (<></>);
}
const shuttle = shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId);
return (
<Modal
open={true}
onClose={() => setEditAssignment(undefined)}
>
<Modal.Header>
{shuttle.name}
{shuttleScores[shuttleId] ?
<span style={{ fontSize: '.95em', fontWeight: 'normal', paddingLeft: '1em' }}>
({(shuttleScores[shuttleId].chance*100).toFixed(1)}% Chance)
</span>
: ''}
</Modal.Header>
<Modal.Content scrolling>
{scores && renderTable()}
</Modal.Content>
<Modal.Actions>
<Button icon='forward' content='Next Seat' onClick={() => cycleShuttleSeat()} />
<Button positive onClick={() => setEditAssignment(undefined)}>
Close
</Button>
</Modal.Actions>
</Modal>
);
function renderTable(): JSX.Element {
let assignedCrew;
const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum == seatNum);
if (seated) {
assignedCrew = myCrew.find(crew => crew.id === seated.assignedId && crew.symbol === seated.assignedSymbol);
if (!assignedCrew) assignedCrew = myCrew.find(crew => crew.symbol === seated.assignedSymbol);
}
// Pagination
const rowsPerPage = 10;
const totalPages = Math.ceil(scores.length / rowsPerPage);
const data = scores.slice(rowsPerPage * (paginationPage - 1), rowsPerPage * paginationPage).map(score => {
const scoreCrew = myCrew.find(crew => crew.id === score.id);
return {...scoreCrew, ...score}
});
return (
<React.Fragment>
<Table striped selectable singleLine compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell />
<Table.HeaderCell colSpan={2}>Best <span style={{ padding: '0 .5em' }}><SeatSkillView seat={seat} /></span> Crew</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Here<Popup trigger={<Icon name='help' />} content='Using this crew here will result in this net change to the success chance of this shuttle' /></Table.HeaderCell>
<Table.HeaderCell>Current Assignment</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>There<Popup trigger={<Icon name='help' />} content='Removing this crew from their current assignment will leave an open seat on that shuttle, resulting in this success chance' /></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{data.map((crew, idx) => renderRow(crew, idx, assignedCrew))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan={6}>
<Pagination
totalPages={totalPages}
activePage={paginationPage}
onPageChange={(e, { activePage }) => setPaginationPage(activePage)}
/>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
</React.Fragment>
);
}
function renderRow(crew: any, idx: number, assignedCrew: any): JSX.Element {
const currentSeat = assigned.find(seat => seat.assignedId === crew.id && seat.assignedSymbol === crew.symbol);
const currentShuttle = currentSeat ? shuttlers.shuttles.find(shuttle => shuttle.id === currentSeat.shuttleId) : undefined;
return (
<Table.Row key={idx} style={{ cursor: 'pointer' }}
onClick={() => {
if (!assignedCrew || crew.id !== assignedCrew.id)
updateAssignment(shuttleId, seatNum, crew, true);
setEditAssignment(undefined);
}}
>
<Table.Cell textAlign='center'>
{assignedCrew?.id === crew.id && (<Icon color='green' name='check' />)}
</Table.Cell>
<Table.Cell><SeatCrewView crew={crew} /></Table.Cell>
<Table.Cell>
{crew.immortal > 0 && (<Icon name='snowflake' />)}
{crew.prospect && (<Icon name='add user' />)}
</Table.Cell>
<Table.Cell textAlign='center'>
{renderScoreChange(shuttleId, seatNum, crew.score)}
</Table.Cell>
<Table.Cell>
{currentShuttle?.name}
{currentShuttle?.id === shuttleId && <span style={{ paddingLeft: '1em' }}><i>(This Shuttle)</i></span>}
{currentSeat?.locked && <span style={{ paddingLeft: '1em' }}><Icon name='lock' /></span>}
</Table.Cell>
<Table.Cell textAlign='center'>
{currentSeat && renderScoreChange(currentSeat.shuttleId, currentSeat.seatNum, 0)}
</Table.Cell>
</Table.Row>
);
}
function renderScoreChange(shuttleId: string, seatNum: number, replacementScore: number = 0): JSX.Element {
if (!shuttleScores[shuttleId]) return (<></>);
const newScores = [...shuttleScores[shuttleId].scores];
newScores[seatNum] = replacementScore;
const DIFFICULTY = 2000;
const dAvgSkill = newScores.reduce((a, b) => (a + b), 0)/newScores.length;
const dChance = 1/(1+Math.pow(Math.E, 3.5*(0.5-dAvgSkill/DIFFICULTY)));
const attributes = {};
if (replacementScore === 0) {
if (dChance*100 >= 90) attributes.style = { color: 'green', fontWeight: 'bold' };
return (<span {...attributes}>{Math.floor(dChance*100)}%</span>);
}
const dDelta = dChance - shuttleScores[shuttleId].chance;
if (dDelta > 0 && dChance*100 >= 90)
attributes.style = { color: 'green', fontWeight: 'bold' };
return (<span {...attributes}>{dDelta > 0 ? '+' : ''}{(dDelta*100).toFixed(1)}%</span>);
}
function cycleShuttleSeat(): void {
const nextAssignment = {
shuttleId: shuttleId,
seatNum: seatNum + 1 >= shuttle.seats.length ? 0 : seatNum + 1
};
setEditAssignment(nextAssignment);
}
};
const data = shuttlers.shuttles.slice()
.filter(shuttle => shuttle.groupId === props.groupId && shuttle.priority > 0)
.sort((a, b) => a.priority - b.priority);
return (
<React.Fragment>
<p>You can rearrange crew to balance shuttle chances as you see fit. Click a seat to change the crew assigned to it. Lock an assignment to keep that crew in that seat when requesting new recommendations.</p>
<Table celled striped compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Mission</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Faction</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Seat Assignments</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Success Chance</Table.HeaderCell>
<Table.HeaderCell />
</Table.Row>
</Table.Header>
<Table.Body>
{data.length === 0 && (
<Table.Row>
<Table.Cell colSpan={6} textAlign='center'>
No missions selected.
</Table.Cell>
</Table.Row>
)}
{data.map(shuttle => (
<Table.Row key={shuttle.id}>
<Table.Cell><b>{shuttle.name}</b></Table.Cell>
<Table.Cell textAlign='center'>
<ShuttleFactionView factionId={shuttle.faction} size={3} />
</Table.Cell>
<Table.Cell>
<Table striped selectable singleLine compact='very' style={{ margin: '0 auto' }}>
<Table.Body>
{shuttle.seats.map((seat, seatNum) =>
<SeatAssignmentRow key={seatNum} shuttleId={shuttle.id} seatNum={seatNum} seat={seat} />
)}
</Table.Body>
</Table>
</Table.Cell>
<Table.Cell textAlign='center'>
{shuttleScores[shuttle.id]?.chance > 0 ? <b>{Math.floor(shuttleScores[shuttle.id].chance*100)}%</b> : <></>}
</Table.Cell>
<Table.Cell textAlign='right'>
<Button compact icon='ban' content='Dismiss' onClick={() => dismissShuttle(shuttle.id)} />
</Table.Cell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan={3}>
<Button compact icon='backward' content='Change Missions' onClick={() => props.setActiveStep('missions')} />
</Table.HeaderCell>
<Table.HeaderCell colSpan={3} textAlign='right'>
{data.length > 0 && (<Button compact icon='rocket' color='green' content='Recommend Crew' onClick={() => props.recommendShuttlers()} />)}
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
{editAssignment && (<SeatAssignmentPicker />)}
</React.Fragment>
);
function dismissShuttle(shuttleId: string): void {
shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId).priority = 0;
setShuttlers({...shuttlers});
}
function updateAssignment(shuttleId: string, seatNum: number, assignedCrew: any, locked: boolean): void {
// Unassign crew from previously assigned seat, if necessary
if (assignedCrew) {
const current = assigned.find(seat => seat.assignedId === assignedCrew.id);
if (current) {
current.assignedId = -1;
current.assignedSymbol = '';
current.seatScore = 0;
current.locked = false;
}
}
const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum === seatNum);
if (assignedCrew && !seated) {
assigned.push({
shuttleId,
seatNum,
ssId: assignedCrew.ssId,
assignedId: assignedCrew.id,
assignedSymbol: assignedCrew.symbol,
seatScore: assignedCrew.score,
locked
});
}
else if (assignedCrew) {
seated.assignedId = assignedCrew.id;
seated.assignedSymbol = assignedCrew.symbol;
seated.seatScore = assignedCrew.score;
seated.locked = locked;
}
else {
seated.assignedId = -1;
seated.assignedSymbol = '';
seated.seatScore = 0;
seated.locked = false;
}
setAssigned([...assigned]);
}
function toggleAssignmentLock(shuttleId: string, seatNum: number): void {
const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum === seatNum);
seated.locked = !seated.locked;
setAssigned([...assigned]);
}
function updateShuttleScores(): void {
const DIFFICULTY = 2000;
const newScores = [];
assigned.forEach(seated => {
if (!newScores[seated.shuttleId]) {
const seatCount = shuttlers.shuttles.find(shuttle => shuttle.id === seated.shuttleId).seats.length;
newScores[seated.shuttleId] = { chance: 0, scores: Array(seatCount).fill(0) };
}
newScores[seated.shuttleId].scores[seated.seatNum] = seated.seatScore;
const dAvgSkill = newScores[seated.shuttleId].scores.reduce((a, b) => (a + b), 0)/newScores[seated.shuttleId].scores.length;
const dChance = 1/(1+Math.pow(Math.E, 3.5*(0.5-dAvgSkill/DIFFICULTY)));
newScores[seated.shuttleId].chance = dAvgSkill > 0 ? dChance : 0;
});
setShuttleScores(newScores);
}
}
Example #17
Source File: missionslist.tsx From website with MIT License | 4 votes |
MissionsList = (props: MissionsListProps) => {
const { groupId, shuttlers, setShuttlers, activeShuttles } = props;
const [editMission, setEditMission] = React.useState(undefined);
const [state, dispatch] = React.useReducer(reducer, {
data: shuttlers.shuttles.filter(shuttle => shuttle.groupId === groupId),
column: null,
direction: null
});
const { data, column, direction } = state;
React.useEffect(() => {
dispatch({ type: 'UPDATE_DATA', data: shuttlers.shuttles.filter(shuttle => shuttle.groupId === groupId), column, direction });
}, [shuttlers]);
const CheckDropdown = () => {
if (data.length === 0) return (<></>);
const checkOptions = [];
const threeSeaters = [], fourSeaters = [];
data.forEach(shuttle => {
if (shuttle.seats.length <= 4)
fourSeaters.push(shuttle.id);
if (shuttle.seats.length === 3)
threeSeaters.push(shuttle.id);
});
if (threeSeaters.length > 0)
checkOptions.push({ key: 'three-seaters', text: `Select only 3-seaters (${threeSeaters.length})`, ids: threeSeaters });
if (fourSeaters.length > 0)
checkOptions.push({ key: 'four-seaters', text: `Select only 3- and 4- seaters (${fourSeaters.length})`, ids: fourSeaters });
if (activeShuttles?.length > 0) {
const openIds = activeShuttles.map(adventure => adventure.symbol);
checkOptions.push({ key: `open-adventures`, text: `Select only open in-game (${openIds.length})`, ids: openIds });
}
const factions = [];
data.forEach(shuttle => {
if (shuttle.faction > 0 && !factions.includes(shuttle.faction)) factions.push(shuttle.faction);
});
if (factions.length > 1) {
factions.forEach(factionId => {
const ids = data.filter(shuttle => shuttle.faction === factionId).map(shuttle => shuttle.id);
const faction = allFactions.find(af => af.id === factionId);
checkOptions.push({ key: `faction-${factionId}`, text: `Select only ${faction.name} (${ids.length})`, ids });
});
}
return (
<Dropdown
icon='check'
floating
>
<Dropdown.Menu>
<Dropdown.Item icon='check' text={`Select all (${data.length})`} onClick={() => checkMissions([])} />
{missionsSelected > 0 && (
<Dropdown.Item icon='x' text='Unselect all' onClick={() => checkMissions([], false)} />
)}
{checkOptions.length > 0 && <Dropdown.Divider />}
{checkOptions.map(option => (
<Dropdown.Item key={option.key} text={option.text} onClick={() => checkMissions(option.ids)} />
))}
</Dropdown.Menu>
</Dropdown>
);
};
const tableConfig = [
{ title: <CheckDropdown />, align: 'center' },
{ column: 'name', title: 'Mission' },
{ column: 'faction', title: 'Faction', align: 'center' },
{ column: 'seats.length', title: 'Seats', align: 'center' },
{ column: 'skills', title: 'Skills', span: 5 },
{ title: '' }
];
const MissionEditor = (props: { shuttle: Shuttle }) => {
const [shuttle, setShuttle] = React.useState(JSON.parse(JSON.stringify(props.shuttle)));
const factionOptions = allFactions.sort((a, b) => a.name.localeCompare(b.name)).map(faction => {
return { key: faction.id, value: faction.id, text: (<span style={{ whiteSpace: 'nowrap' }}>{faction.name}</span>) };
});
const EditorSeat = (props: { seat: ShuttleSeat, seatNum: number }) => {
const { seatNum, seat } = props;
const skillOptions = [
{ key: 'CMD', text: 'CMD', value: 'command_skill' },
{ key: 'DIP', text: 'DIP', value: 'diplomacy_skill' },
{ key: 'ENG', text: 'ENG', value: 'engineering_skill' },
{ key: 'MED', text: 'MED', value: 'medicine_skill' },
{ key: 'SCI', text: 'SCI', value: 'science_skill' },
{ key: 'SEC', text: 'SEC', value: 'security_skill' }
];
return (
<Grid textAlign='center' columns={3}>
<Grid.Column>
<Dropdown
direction='right'
compact
selection
options={skillOptions}
value={seat.skillA}
onChange={(e, { value }) => updateMissionSeat(seatNum, 'skillA', value)}
/>
</Grid.Column>
<Grid.Column>
<Button circular
disabled={seat.skillB == '' ? true : false}
onClick={() => updateMissionSeat(seatNum, 'operand', seat.operand == 'AND' ? 'OR' : 'AND')}
>
{seat.skillB == '' ? '' : seat.operand}
</Button>
</Grid.Column>
<Grid.Column>
<Dropdown
compact
selection
clearable
options={skillOptions}
value={seat.skillB}
onChange={(e, { value }) => updateMissionSeat(seatNum, 'skillB', value)}
/>
</Grid.Column>
</Grid>
);
};
return (
<Modal
open={true}
onClose={() => applyEdits()}
>
<Modal.Header>Edit Mission</Modal.Header>
<Modal.Content scrolling>
{renderContent()}
</Modal.Content>
<Modal.Actions>
<Button positive onClick={() => applyEdits()}>
Close
</Button>
</Modal.Actions>
</Modal>
);
function renderContent(): void {
return (
<React.Fragment>
<Grid columns={2} divided stackable>
<Grid.Column>
<div>
<Header as='h4'>Mission name</Header>
<Input style={{ marginTop: '-1em' }}
placeholder='Mission name...'
value={shuttle.name}
onChange={(e, { value }) => updateMissionName(value)}>
<input />
<Button icon onClick={() => updateMissionName('')} >
<Icon name='delete' />
</Button>
</Input>
</div>
<div style={{ marginTop: '1em' }}>
<Header as='h4'>Faction</Header>
<Dropdown
style={{ marginTop: '-1em' }}
selection
options={factionOptions}
value={shuttle.faction}
onChange={(e, { value }) => updateFaction(value)}
/>
</div>
</Grid.Column>
<Grid.Column>
<Header as='h4'>Seats</Header>
<p style={{ marginTop: '-1em' }}>Set each seat to the skills required. Add seats as necessary.</p>
<Table collapsing unstackable compact='very' size='small'>
<Table.Body>
{shuttle.seats.map((seat, seatNum) => (
<Table.Row key={seatNum}>
<Table.Cell textAlign='right'>{seatNum+1}</Table.Cell>
<Table.Cell textAlign='center'>
<EditorSeat seatNum={seatNum} seat={seat} />
</Table.Cell>
<Table.Cell textAlign='right'>
{shuttle.seats.length > 1 && <Button compact icon='trash' color='red' onClick={() => deleteMissionSeat(seatNum)} />}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Button compact icon='plus square outline' content='Add Seat' onClick={() => addMissionSeat()} />
</Grid.Column>
</Grid>
<div style={{ marginTop: '1em' }}>
<Divider />
<p>If you no longer need this mission, you can delete it here. Note: missions will be automatically deleted after the event has concluded.</p>
<p><Button icon='trash' color='red' content='Delete Mission' onClick={() => deleteMission(shuttle.id)} /></p>
</div>
</React.Fragment>
);
}
function updateMissionName(newName: string): void {
shuttle.name = newName;
setShuttle({...shuttle});
}
function updateFaction(newFaction: number): void {
shuttle.faction = newFaction;
setShuttle({...shuttle});
}
function updateMissionSeat(seatNum: number, key: string, value: string): void {
shuttle.seats[seatNum][key] = value;
setShuttle({...shuttle});
}
function addMissionSeat(): void {
shuttle.seats.push(new ShuttleSeat());
setShuttle({...shuttle});
}
function deleteMissionSeat(seatNum: number): void {
shuttle.seats.splice(seatNum, 1);
setShuttle({...shuttle});
}
function applyEdits(): void {
if (shuttle.priority === 0) shuttle.priority = missionsSelected + 1;
const shuttleNum = shuttlers.shuttles.findIndex(s => s.id === shuttle.id);
shuttlers.shuttles[shuttleNum] = shuttle;
updateShuttlers();
setEditMission(undefined);
}
};
const missionsSelected = data.filter(shuttle => shuttle.priority > 0).length;
return (
<React.Fragment>
<div>Click all the missions that you want to run, then click 'Recommend Crew' to see the best seats for your crew.</div>
<Table celled striped selectable sortable singleLine>
<Table.Header>
<Table.Row>
{tableConfig.map((cell, idx) => (
<Table.HeaderCell key={idx}
sorted={column === cell.column ? direction : null}
onClick={() => dispatch({ type: 'CHANGE_SORT', column: cell.column, reverse: cell.reverse })}
colSpan={cell.span ?? 1}
textAlign={cell.align ?? 'left'}
>
{cell.title}
</Table.HeaderCell>
))}
</Table.Row>
</Table.Header>
<Table.Body>
{data.length === 0 && (
<Table.Row>
<Table.Cell colSpan={10} textAlign='center'>
No missions available.
</Table.Cell>
</Table.Row>
)}
{data.map(shuttle => (
<Table.Row key={shuttle.id} style={{ cursor: 'pointer' }}
onClick={() => toggleMissionStatus(shuttle.id)}
onDoubleClick={() => { toggleMissionStatus(shuttle.id); props.recommendShuttlers(); }}
>
<Table.Cell textAlign='center'>
{shuttle.priority > 0 && (<Icon color='green' name='check' />)}
</Table.Cell>
<Table.Cell>
<span style={{ fontSize: '1.1em' }}><b>{shuttle.name}</b></span>
</Table.Cell>
<Table.Cell textAlign='center'>
<ShuttleFactionView factionId={shuttle.faction} size={1.5} />
</Table.Cell>
<Table.Cell textAlign='center'>{shuttle.seats.length}</Table.Cell>
{[0, 1, 2, 3, 4].map(seatNum => (
<Table.Cell key={seatNum} textAlign='center'>
{shuttle.seats.length > seatNum && (
<SeatSkillView seat={shuttle.seats[seatNum]} />
)}
</Table.Cell>
))}
<Table.Cell textAlign='right'>
{!shuttle.readonly && (
<Button icon='edit' content='Edit' onClick={(e) => { setEditMission(shuttle); e.stopPropagation(); }}/>
)}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan={10} textAlign='right'>
{missionsSelected > 0 && (<Button compact icon='rocket' color='green' content='Recommend Crew' onClick={() => props.recommendShuttlers()} />)}
{missionsSelected === 0 && (<Button compact icon='rocket' content='Recommend Crew' />)}
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
{editMission && <MissionEditor shuttle={editMission} />}
<p>If the mission you want isn't listed here, click 'Create Mission' to input the mission parameters manually. Tip: open shuttle missions in-game before uploading your player data to DataCore so that this tool can import the missions automatically.</p>
<Button icon='plus square' content='Create Mission' onClick={() => createMission() } />
</React.Fragment>
);
function reducer(state, action): any {
switch (action.type) {
case 'UPDATE_DATA':
//const defaultColumn = action.data.filter(shuttle => shuttle.priority > 0).length ? 'priority' : 'name';
const updatedData = action.data.slice();
firstSort(updatedData, action.column ?? 'name', action.direction ?? 'ascending');
return {
column: action.column ?? 'name',
data: updatedData,
direction: action.direction ?? 'ascending'
};
case 'CHANGE_SORT':
if (!action.column) {
return {
column: state.column,
data: state.data,
direction: state.direction
};
}
if (state.column === action.column && action.column !== 'priority') {
return {
...state,
data: state.data.slice().reverse(),
direction: state.direction === 'ascending' ? 'descending' : 'ascending'
};
}
else {
const data = state.data.slice();
firstSort(data, action.column, action.reverse);
return {
column: action.column,
data: data,
direction: action.reverse ? 'descending' : 'ascending'
};
}
default:
throw new Error();
}
}
function firstSort(data: any[], column: string, reverse: boolean = false): any[] {
data.sort((a, b) => {
if (column === 'name') return a.name.localeCompare(b.name);
let aValue = column.split('.').reduce((prev, curr) => prev.hasOwnProperty(curr) ? prev[curr] : undefined, a);
let bValue = column.split('.').reduce((prev, curr) => prev.hasOwnProperty(curr) ? prev[curr] : undefined, b);
// Always show selected missions at the top when sorting by priority
if (column === 'priority') {
if (aValue === 0) aValue = 100;
if (bValue === 0) bValue = 100;
}
if (column === 'skills') {
aValue = a.seats.length;
bValue = b.seats.length;
}
// Tiebreaker goes to name ascending
if (aValue === bValue) return a.name.localeCompare(b.name);
if (reverse) bValue - aValue;
return aValue - bValue;
});
}
function checkMissions(shuttleIds: string[], checkState: boolean = true): void {
let priority = 0;
shuttlers.shuttles.forEach(shuttle => {
if (shuttleIds.length === 0)
shuttle.priority = checkState ? ++priority : 0;
else
shuttle.priority = checkState && shuttleIds.includes(shuttle.id) ? ++priority : 0;
});
updateShuttlers();
if (shuttleIds.length !== 0)
dispatch({ type: 'CHANGE_SORT', column: 'priority' });
}
function createMission(): void {
const shuttle = new Shuttle(groupId);
shuttle.seats.push(new ShuttleSeat());
shuttlers.shuttles.push(shuttle);
updateShuttlers();
setEditMission(shuttle);
}
function deleteMission(shuttleId: string): void {
const shuttleNum = shuttlers.shuttles.findIndex(shuttle => shuttle.id === shuttleId);
shuttlers.shuttles.splice(shuttleNum, 1);
updateShuttlers();
setEditMission(undefined);
}
function toggleMissionStatus(shuttleId: string): void {
const shuttle = shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId);
shuttle.priority = shuttle.priority === 0 ? missionsSelected+1 : 0;
updateShuttlers();
}
function updateShuttlers(): void {
setShuttlers({...shuttlers});
}
}
Example #18
Source File: voyagecalculator.tsx From website with MIT License | 4 votes |
VoyageEditConfigModal = (props: VoyageEditConfigModalProps) => {
const { updateConfig } = props;
const [voyageConfig, setVoyageConfig] = React.useState(props.voyageConfig);
const [modalIsOpen, setModalIsOpen] = React.useState(false);
const [updateOnClose, setUpdateOnClose] = React.useState(false);
const [options, setOptions] = React.useState(undefined);
React.useEffect(() => {
if (!modalIsOpen && updateOnClose) {
updateConfig(voyageConfig);
setUpdateOnClose(false);
}
}, [modalIsOpen]);
const defaultSlots = [
{ symbol: 'captain_slot', name: 'First Officer', skill: 'command_skill', trait: '' },
{ symbol: 'first_officer', name: 'Helm Officer', skill: 'command_skill', trait: '' },
{ symbol: 'chief_communications_officer', name: 'Communications Officer', skill: 'diplomacy_skill', trait: '' },
{ symbol: 'communications_officer', name: 'Diplomat', skill: 'diplomacy_skill', trait: '' },
{ symbol: 'chief_security_officer', name: 'Chief Security Officer', skill: 'security_skill', trait: '' },
{ symbol: 'security_officer', name: 'Tactical Officer', skill: 'security_skill', trait: '' },
{ symbol: 'chief_engineering_officer', name: 'Chief Engineer', skill: 'engineering_skill', trait: '' },
{ symbol: 'engineering_officer', name: 'Engineer', skill: 'engineering_skill', trait: '' },
{ symbol: 'chief_science_officer', name: 'Chief Science Officer', skill: 'science_skill', trait: '' },
{ symbol: 'science_officer', name: 'Deputy Science Officer', skill: 'science_skill', trait: '' },
{ symbol: 'chief_medical_officer', name: 'Chief Medical Officer', skill: 'medicine_skill', trait: '' },
{ symbol: 'medical_officer', name: 'Ship\'s Counselor', skill: 'medicine_skill', trait: '' }
];
const crewSlots = voyageConfig.crew_slots ?? defaultSlots;
crewSlots.sort((s1, s2) => CONFIG.VOYAGE_CREW_SLOTS.indexOf(s1.symbol) - CONFIG.VOYAGE_CREW_SLOTS.indexOf(s2.symbol));
return (
<Modal
open={modalIsOpen}
onClose={() => setModalIsOpen(false)}
onOpen={() => setModalIsOpen(true)}
trigger={<Button size='small'><Icon name='edit' />Edit</Button>}
>
<Modal.Header>Edit Voyage</Modal.Header>
<Modal.Content scrolling>
{renderContent()}
</Modal.Content>
<Modal.Actions>
<Button positive onClick={() => setModalIsOpen(false)}>
Close
</Button>
</Modal.Actions>
</Modal>
);
function renderContent(): JSX.Element {
if (!modalIsOpen) return (<></>);
if (!options) {
// Renders a lot faster by using known voyage traits rather than calculate list from all possible traits
const knownShipTraits = ['andorian','battle_cruiser','borg','breen','cardassian','cloaking_device',
'dominion','emp','explorer','federation','ferengi','freighter','historic','hologram',
'klingon','malon','maquis','orion_syndicate','pioneer','reman','romulan','ruthless',
'scout','spore_drive','terran','tholian','transwarp','vulcan','warship','war_veteran','xindi'];
const knownCrewTraits = ['android','astrophysicist','bajoran','borg','brutal',
'cardassian','civilian','communicator','costumed','crafty','cultural_figure','cyberneticist',
'desperate','diplomat','doctor','duelist','exobiology','explorer','federation','ferengi',
'gambler','hero','hologram','human','hunter','innovator','inspiring','jury_rigger','klingon',
'marksman','maverick','pilot','prodigy','resourceful','romantic','romulan',
'saboteur','scoundrel','starfleet','survivalist','tactician','telepath','undercover_operative',
'veteran','villain','vulcan'];
const skillsList = [];
for (let skill in CONFIG.SKILLS) {
skillsList.push({
key: skill,
value: skill,
text: CONFIG.SKILLS[skill]
});
}
const shipTraitsList = knownShipTraits.map(trait => {
return {
key: trait,
value: trait,
text: allTraits.ship_trait_names[trait]
};
});
shipTraitsList.sort((a, b) => a.text.localeCompare(b.text));
const crewTraitsList = knownCrewTraits.map(trait => {
return {
key: trait,
value: trait,
text: allTraits.trait_names[trait]
};
});
crewTraitsList.sort((a, b) => a.text.localeCompare(b.text));
setOptions({ skills: skillsList, ships: shipTraitsList, traits: crewTraitsList });
return (<></>);
}
return (
<React.Fragment>
<Message>Editing this voyage will reset all existing recommendations and estimates.</Message>
<Form>
<Form.Group>
<Form.Select
label='Primary skill'
options={options.skills}
value={voyageConfig.skills.primary_skill ?? 'command_skill'}
onChange={(e, { value }) => setSkill('primary_skill', value)}
placeholder='Primary'
/>
<Form.Select
label='Secondary skill'
options={options.skills}
value={voyageConfig.skills.secondary_skill ?? 'science_skill'}
onChange={(e, { value }) => setSkill('secondary_skill', value)}
placeholder='Secondary'
/>
<Form.Select
search clearable
label='Ship trait'
options={options.ships}
value={voyageConfig.ship_trait}
onChange={(e, { value }) => setShipTrait(value)}
placeholder='Ship trait'
/>
</Form.Group>
</Form>
<Table compact striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell textAlign='center'>Skill</Table.HeaderCell>
<Table.HeaderCell>Seat</Table.HeaderCell>
<Table.HeaderCell>Trait</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{crewSlots.map((seat, idx) => (
<Table.Row key={seat.symbol}>
{ idx % 2 == 0 ?
(
<Table.Cell rowSpan='2' textAlign='center'>
<img alt="{seat.skill}" src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${seat.skill}.png`} style={{ height: '2em' }} />
</Table.Cell>
)
: (<></>)
}
<Table.Cell>{seat.name}</Table.Cell>
<Table.Cell>
<Dropdown search selection clearable
options={options.traits}
value={seat.trait}
onChange={(e, { value }) => setSeatTrait(seat.symbol, value)}
placeholder='Trait'
/>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</React.Fragment>
);
}
function setSkill(prime: string, value: string): void {
// Flip skill values if changing to value that's currently set as the other prime
if (prime == 'primary_skill' && value == voyageConfig.skills.secondary_skill)
voyageConfig.skills.secondary_skill = voyageConfig.skills.primary_skill;
else if (prime == 'secondary_skill' && value == voyageConfig.skills.primary_skill)
voyageConfig.skills.primary_skill = voyageConfig.skills.secondary_skill;
voyageConfig.skills[prime] = value;
setVoyageConfig({...voyageConfig});
setUpdateOnClose(true);
}
function setShipTrait(value: string): void {
voyageConfig.ship_trait = value;
setVoyageConfig({...voyageConfig});
setUpdateOnClose(true);
}
function setSeatTrait(seat: symbol, value: string): void {
voyageConfig.crew_slots.find(s => s.symbol === seat).trait = value;
setVoyageConfig({...voyageConfig});
setUpdateOnClose(true);
}
}
Example #19
Source File: profile_items.tsx From website with MIT License | 4 votes |
render() {
const { column, direction, pagination_rows, pagination_page } = this.state;
let { data } = this.state;
let totalPages = Math.ceil(data.length / this.state.pagination_rows);
// Pagination
data = data.slice(pagination_rows * (pagination_page - 1), pagination_rows * pagination_page);
return (
<Table sortable celled selectable striped collapsing unstackable compact="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell
width={3}
sorted={column === 'name' ? direction : null}
onClick={() => this._handleSort('name')}
>
Item
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'quantity' ? direction : null}
onClick={() => this._handleSort('quantity')}
>
Quantity
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'type' ? direction : null}
onClick={() => this._handleSort('type')}
>
Item type
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'rarity' ? direction : null}
onClick={() => this._handleSort('rarity')}
>
Rarity
</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{data.map((item, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${item.imageUrl}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<Link to={`/item_info?symbol=${item.symbol}`}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{item.rarity > 0 && (
<span>
{item.rarity} <Icon name="star" />{' '}
</span>
)}
{item.name}
</span>
</Link>
</div>
<div style={{ gridArea: 'description' }}>{item.flavor}</div>
</div>
</Table.Cell>
<Table.Cell>{item.quantity}</Table.Cell>
<Table.Cell>{CONFIG.REWARDS_ITEM_TYPE[item.type]}</Table.Cell>
<Table.Cell>{CONFIG.RARITIES[item.rarity].name}</Table.Cell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan="8">
<Pagination
totalPages={totalPages}
activePage={pagination_page}
onPageChange={(event, { activePage }) => this._onChangePage(activePage)}
/>
<span style={{ paddingLeft: '2em' }}>
Items per page:{' '}
<Dropdown
inline
options={pagingOptions}
value={pagination_rows}
onChange={(event, { value }) =>
this.setState({ pagination_page: 1, pagination_rows: value as number })
}
/>
</span>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
);
}
Example #20
Source File: fleet_info.tsx From website with MIT License | 4 votes |
render() {
const { fleet_id, errorMessage, fleet_data, factions, events } = this.state;
if (fleet_id === undefined || fleet_data === undefined || errorMessage !== undefined) {
return (
<Layout title='Fleet information'>
<Header as="h4">Fleet information</Header>
{errorMessage && (
<Message negative>
<Message.Header>Unable to load fleet profile</Message.Header>
<pre>{errorMessage.toString()}</pre>
</Message>
)}
{!errorMessage && (
<div>
<Icon loading name="spinner" /> Loading...
</div>
)}
<p>
Are you looking to share your player profile? Go to the <Link to={`/playertools`}>Player Tools page</Link> to
upload your player.json and access other useful player tools.
</p>
</Layout>
);
}
let imageUrl = 'icons_icon_faction_starfleet.png';
if (factions && factions[fleet_data.nicon_index]) {
imageUrl = factions[fleet_data.nicon_index].icon;
}
let event1;
let event2;
let event3;
if (events[0].event_name === fleet_data.leaderboard[0].event_name) {
event1 = events[0];
event2 = events[1];
event3 = events[2];
} else {
event1 = events.find(ev => ev.event_name === fleet_data.leaderboard[0].event_name);
event2 = events.find(ev => ev.event_name === fleet_data.leaderboard[1].event_name);
event3 = events.find(ev => ev.event_name === fleet_data.leaderboard[2].event_name);
}
return (
<Layout title={fleet_data.name}>
<Item.Group>
<Item>
<Item.Image size="tiny" src={`${process.env.GATSBY_ASSETS_URL}${imageUrl}`} />
<Item.Content>
<Item.Header>{fleet_data.name}</Item.Header>
<Item.Meta>
<Label>Starbase level: {fleet_data.nstarbase_level}</Label>
<Label>
Size: {fleet_data.cursize} / {fleet_data.maxsize}
</Label>
<Label>Created: {new Date(fleet_data.created).toLocaleDateString()}</Label>
<Label>
Enrollment {fleet_data.enrollment} (min level: {fleet_data.nmin_level})
</Label>
</Item.Meta>
</Item.Content>
</Item>
</Item.Group>
{event1 && <table>
<tbody>
<tr>
<th>
{' '}
<Link to={`/event_info?instance_id=${event1.instance_id}`}>
{fleet_data.leaderboard[0].event_name}
</Link>
</th>
<th>
{' '}
<Link to={`/event_info?instance_id=${event2.instance_id}`}>
{fleet_data.leaderboard[1].event_name}
</Link>
</th>
<th>
{' '}
<Link to={`/event_info?instance_id=${event3.instance_id}`}>
{fleet_data.leaderboard[2].event_name}
</Link>
</th>
</tr>
<tr>
<td><Image size="medium" src={`${process.env.GATSBY_ASSETS_URL}${event1.image}`} /></td>
<td><Image size="medium" src={`${process.env.GATSBY_ASSETS_URL}${event2.image}`} /></td>
<td><Image size="medium" src={`${process.env.GATSBY_ASSETS_URL}${event3.image}`} /></td>
</tr>
<tr>
<td align="center">Fleet rank: {fleet_data.leaderboard[0].fleet_rank}</td>
<td align="center">Fleet rank: {fleet_data.leaderboard[1].fleet_rank}</td>
<td align="center">Fleet rank: {fleet_data.leaderboard[2].fleet_rank}</td>
</tr>
</tbody>
</table>}
<Header as="h4">Members</Header>
<Table celled selectable striped collapsing unstackable compact="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell width={3}>Name</Table.HeaderCell>
<Table.HeaderCell width={1}>Rank</Table.HeaderCell>
<Table.HeaderCell width={1}>Profile</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{fleet_data.members.map((member, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img
width={48}
src={`${process.env.GATSBY_ASSETS_URL}${member.crew_avatar || 'crew_portraits_cm_empty_sm.png'}`}
/>
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{member.last_update ? (
<Link to={`/profile?dbid=${member.dbid}`}>{member.display_name}</Link>
) : (
<span>{member.display_name}</span>
)}
</span>
</div>
<div style={{ gridArea: 'description' }}>
{member.last_update && (
<Label size="tiny">
Last profile upload: {new Date(Date.parse(member.last_update)).toLocaleDateString()}
</Label>
)}
</div>
</div>
</Table.Cell>
<Table.Cell>{member.rank}</Table.Cell>
<Table.Cell>
{member.last_update
? `Last profile upload: ${new Date(Date.parse(member.last_update)).toLocaleDateString()}`
: 'Never'}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</Layout>
);
}
Example #21
Source File: event_info.tsx From website with MIT License | 4 votes |
renderEventDetails() {
const { event_data } = this.state;
if (event_data === undefined || event_data.event_details === undefined) {
return <span />;
}
let event = event_data.event_details;
return (
<div>
<Message icon warning>
<Icon name='exclamation triangle' />
<Message.Content>
<Message.Header>Work in progress!</Message.Header>
This section is under development and not fully functional yet.
</Message.Content>
</Message>
<p>{event.description}</p>
<p>{event.rules}</p>
<Label>{event.bonus_text}</Label>
{event.content.map((cnt, idx) => {
let crew_bonuses = undefined;
if (cnt.shuttles) {
crew_bonuses = cnt.shuttles[0].crew_bonuses;
} else if (cnt.crew_bonuses) {
crew_bonuses = cnt.crew_bonuses;
} else if (cnt.bonus_crew && cnt.bonus_traits) {
// Skirmishes
crew_bonuses = {};
cnt.bonus_crew.forEach(element => {
crew_bonuses[element] = 10;
});
// TODO: crew from traits
} else if (cnt.special_crew) {
// Expeditions
crew_bonuses = {};
cnt.special_crew.forEach(element => {
crew_bonuses[element] = 50;
});
}
return (
<div key={idx}>
<Header as='h5'>Phase {idx + 1}</Header>
<Label>Type: {cnt.content_type}</Label>
<Header as='h6'>Bonus crew</Header>
{crew_bonuses && (
<p>
{Object.entries(crew_bonuses).map(([bonus, val], idx) => (
<span key={idx}>
{bonus} ({val}),
</span>
))}
</p>
)}
</div>
);
})}
<Header as='h4'>Threshold rewards</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={2}>Points</Table.HeaderCell>
<Table.HeaderCell width={4}>Reward</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event.threshold_rewards.filter(reward => reward.rewards && reward.rewards.length > 0).map((reward, idx) => (
<Table.Row key={idx}>
<Table.Cell>{reward.points}</Table.Cell>
<Table.Cell>
{reward.rewards[0].quantity} {reward.rewards[0].full_name}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Header as='h4'>Ranked rewards</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={2}>Ranks</Table.HeaderCell>
<Table.HeaderCell width={4}>Rewards</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event.ranked_brackets.map((bracket, idx) => (
<Table.Row key={idx}>
<Table.Cell>
{bracket.first} - {bracket.last}
</Table.Cell>
<Table.Cell>
{bracket.rewards.map((reward, idx) => (
<span key={idx}>
{reward.quantity} {reward.full_name},
</span>
))}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Header as='h4'>Quest</Header>
{event.quest ? event.quest.map((quest, idx) => (
<div key={idx}>
{quest && quest.screens && quest.screens.map((screen, idx) => (
<p key={idx}>
<b>{screen.speaker_name}: </b>
{screen.text}
</p>
))}
</div>
)) : <span>Mini-events don't include quest information.</span>}
<Message>
<Message.Header>TODO: Leaderboard out of date</Message.Header>
If this event is currently active, the leaderboard below is out of date (updated only a couple of times a week).
</Message>
</div>
);
}
Example #22
Source File: prospectpicker.tsx From website with MIT License | 4 votes |
ProspectPicker = (props: ProspectPickerProps) => {
const { pool, prospects, setProspects } = props;
enum OptionsState {
Uninitialized,
Initializing,
Ready
};
const [selection, setSelection] = React.useState('');
const [options, setOptions] = React.useState({
state: OptionsState.Uninitialized,
list: []
});
if (pool.length == 0) return (<></>);
const placeholder = options.state === OptionsState.Initializing ? 'Loading. Please wait...' : 'Select Crew';
return (
<React.Fragment>
<Dropdown search selection clearable
placeholder={placeholder}
options={options.list}
value={selection}
onFocus={() => { if (options.state === OptionsState.Uninitialized) populateOptions(); }}
onChange={(e, { value }) => setSelection(value)}
/>
<Button compact icon='add user' color='green' content='Add Crew' onClick={() => { addProspect(); }} style={{ marginLeft: '1em' }} />
<Table celled striped collapsing unstackable compact="very">
<Table.Body>
{prospects.map((p, prospectNum) => (
<Table.Row key={prospectNum}>
<Table.Cell><img width={24} src={`${process.env.GATSBY_ASSETS_URL}${p.imageUrlPortrait}`} /></Table.Cell>
<Table.Cell><Link to={`/crew/${p.symbol}/`}>{p.name}</Link></Table.Cell>
<Table.Cell>
<Rating size='large' icon='star' rating={p.rarity} maxRating={p.max_rarity}
onRate={(e, {rating, maxRating}) => { fuseProspect(prospectNum, rating); }} />
</Table.Cell>
<Table.Cell>
<Button compact icon='trash' color='red' onClick={() => deleteProspect(prospectNum)} />
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</React.Fragment>
);
function populateOptions(): void {
setOptions({
state: OptionsState.Initializing,
list: []
});
// Populate inside a timeout so that UI can update with a "Loading" placeholder first
setTimeout(() => {
const populatePromise = new Promise((resolve, reject) => {
const poolList = pool.map((c) => (
{
key: c.symbol,
value: c.symbol,
image: { avatar: true, src: `${process.env.GATSBY_ASSETS_URL}${c.imageUrlPortrait}` },
text: c.name
}
));
resolve(poolList);
});
populatePromise.then((poolList) => {
setOptions({
state: OptionsState.Initialized,
list: poolList
});
});
}, 0);
}
function addProspect(): void {
if (selection == '') return;
let valid = pool.find((c) => c.symbol == selection);
if (valid) {
let prospect = {
symbol: valid.symbol,
name: valid.name,
imageUrlPortrait: valid.imageUrlPortrait,
rarity: valid.max_rarity,
max_rarity: valid.max_rarity
};
prospects.push(prospect);
setProspects([...prospects]);
};
setSelection('');
}
function fuseProspect(prospectNum: number, rarity: number): void {
if (rarity == 0) return;
prospects[prospectNum].rarity = rarity;
setProspects([...prospects]);
}
function deleteProspect(prospectNum: number): void {
prospects.splice(prospectNum, 1);
setProspects([...prospects]);
}
}
Example #23
Source File: profile_ships.tsx From website with MIT License | 4 votes |
render() {
const { column, direction, pagination_rows, pagination_page } = this.state;
let { data } = this.state;
let totalPages = Math.ceil(data.length / this.state.pagination_rows);
// Pagination
data = data.slice(pagination_rows * (pagination_page - 1), pagination_rows * pagination_page);
return (
<Table sortable celled selectable striped collapsing unstackable compact="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell
width={3}
sorted={column === 'name' ? direction : null}
onClick={() => this._handleSort('name')}
>
Ship
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'antimatter' ? direction : null}
onClick={() => this._handleSort('antimatter')}
>
Antimatter
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'accuracy' ? direction : null}
onClick={() => this._handleSort('accuracy')}
>
Accuracy
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'attack' ? direction : null}
onClick={() => this._handleSort('attack')}
>
Attack
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'evasion' ? direction : null}
onClick={() => this._handleSort('evasion')}
>
Evasion
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'hull' ? direction : null}
onClick={() => this._handleSort('hull')}
>
Hull
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'shields' ? direction : null}
onClick={() => this._handleSort('shields')}
>
Shields
</Table.HeaderCell>
<Table.HeaderCell
width={1}
sorted={column === 'max_level' ? direction : null}
onClick={() => this._handleSort('max_level')}
>
Level
</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{data.map((ship, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${ship.icon.file.substr(1).replace('/', '_')}.png`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>{ship.name}</span>
</div>
<div style={{ gridArea: 'description' }}>{ship.traits_named.join(', ')}</div>
</div>
</Table.Cell>
<Table.Cell>{ship.antimatter}</Table.Cell>
<Table.Cell>{ship.accuracy}</Table.Cell>
<Table.Cell>{ship.attack} ({ship.attacks_per_second}/s)</Table.Cell>
<Table.Cell>{ship.evasion}</Table.Cell>
<Table.Cell>{ship.hull}</Table.Cell>
<Table.Cell>{ship.shields} (regen {ship.shield_regen})</Table.Cell>
<Table.Cell>{ship.level} / {ship.max_level}</Table.Cell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan="8">
<Pagination
totalPages={totalPages}
activePage={pagination_page}
onPageChange={(event, { activePage }) => this._onChangePage(activePage)}
/>
<span style={{ paddingLeft: '2em' }}>
Ships per page:{' '}
<Dropdown
inline
options={pagingOptions}
value={pagination_rows}
onChange={(event, { value }) =>
this.setState({ pagination_page: 1, pagination_rows: value as number })
}
/>
</span>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
);
}
Example #24
Source File: profile_crew.tsx From website with MIT License | 4 votes |
ProfileCrewTable = (props: ProfileCrewTableProps) => {
const pageId = props.pageId ?? 'crew';
const [showFrozen, setShowFrozen] = useStateWithStorage(pageId+'/showFrozen', true);
const [findDupes, setFindDupes] = useStateWithStorage(pageId+'/findDupes', false);
const myCrew = JSON.parse(JSON.stringify(props.crew));
const tableConfig: ITableConfigRow[] = [
{ width: 3, column: 'name', title: 'Crew', pseudocolumns: ['name', 'events', 'collections.length'] },
{ width: 1, column: 'max_rarity', title: 'Rarity', reverse: true, tiebreakers: ['rarity'] },
{ width: 1, column: 'bigbook_tier', title: 'Tier' },
{ width: 1, column: 'cab_ov', title: <span>CAB <CABExplanation /></span>, reverse: true, tiebreakers: ['cab_ov_rank'] },
{ width: 1, column: 'ranks.voyRank', title: 'Voyage' }
];
CONFIG.SKILLS_SHORT.forEach((skill) => {
tableConfig.push({
width: 1,
column: `${skill.name}.core`,
title: <img alt={CONFIG.SKILLS[skill.name]} src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${skill.name}.png`} style={{ height: '1.1em' }} />,
reverse: true
});
});
function showThisCrew(crew: any, filters: [], filterType: string): boolean {
if (!showFrozen && crew.immortal > 0) {
return false;
}
if (findDupes) {
if (myCrew.filter((c) => c.symbol === crew.symbol).length === 1)
return false;
}
return crewMatchesSearchFilter(crew, filters, filterType);
}
function renderTableRow(crew: any, idx: number, highlighted: boolean): JSX.Element {
const attributes = {
positive: highlighted
};
return (
<Table.Row key={idx} style={{ cursor: 'zoom-in' }} onClick={() => navigate(`/crew/${crew.symbol}/`)} {...attributes}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}><Link to={`/crew/${crew.symbol}/`}>{crew.name}</Link></span>
</div>
<div style={{ gridArea: 'description' }}>{descriptionLabel(crew)}</div>
</div>
</Table.Cell>
<Table.Cell>
<Rating icon='star' rating={crew.rarity} maxRating={crew.max_rarity} size="large" disabled />
</Table.Cell>
<Table.Cell textAlign="center">
<b>{formatTierLabel(crew.bigbook_tier)}</b>
</Table.Cell>
<Table.Cell style={{ textAlign: 'center' }}>
<b>{crew.cab_ov}</b><br />
<small>{rarityLabels[parseInt(crew.max_rarity)-1]} #{crew.cab_ov_rank}</small>
</Table.Cell>
<Table.Cell style={{ textAlign: 'center' }}>
<b>#{crew.ranks.voyRank}</b><br />
{crew.ranks.voyTriplet && <small>Triplet #{crew.ranks.voyTriplet.rank}</small>}
</Table.Cell>
{CONFIG.SKILLS_SHORT.map(skill =>
crew[skill.name].core > 0 ? (
<Table.Cell key={skill.name} textAlign='center'>
<b>{crew[skill.name].core}</b>
<br />
+({crew[skill.name].min}-{crew[skill.name].max})
</Table.Cell>
) : (
<Table.Cell key={skill.name} />
)
)}
</Table.Row>
);
}
function descriptionLabel(crew: any): JSX.Element {
if (crew.immortal) {
return (
<div>
<Icon name="snowflake" /> <span>{crew.immortal} frozen</span>
</div>
);
} else {
const counts = [
{ name: 'event', count: crew.events },
{ name: 'collection', count: crew.collections.length }
];
const formattedCounts = counts.map((count, idx) => (
<span key={idx} style={{ whiteSpace: 'nowrap' }}>
{count.count} {count.name}{count.count != 1 ? 's' : ''}{idx < counts.length-1 ? ',' : ''}
</span>
)).reduce((prev, curr) => [prev, ' ', curr]);
return (
<div>
{crew.favorite && <Icon name="heart" />}
{crew.prospect && <Icon name="add user" />}
<span>Level {crew.level}, </span>
{formattedCounts}
</div>
);
}
}
return (
<React.Fragment>
<div style={{ margin: '.5em 0' }}>
<Form.Group grouped>
<Form.Field
control={Checkbox}
label='Show frozen (vaulted) crew'
checked={showFrozen}
onChange={(e, { checked }) => setShowFrozen(checked)}
/>
<Form.Field
control={Checkbox}
label='Only show duplicate crew'
checked={findDupes}
onChange={(e, { checked }) => setFindDupes(checked)}
/>
</Form.Group>
</div>
<SearchableTable
id={`${pageId}/table_`}
data={myCrew}
config={tableConfig}
renderTableRow={(crew, idx, highlighted) => renderTableRow(crew, idx, highlighted)}
filterRow={(crew, filters, filterType) => showThisCrew(crew, filters, filterType)}
showFilterOptions="true"
initOptions={props.initOptions}
lockable={props.lockable}
/>
</React.Fragment>
);
}
Example #25
Source File: profile_charts.tsx From website with MIT License | 4 votes |
render() {
const {
data_ownership,
skill_distribution,
flat_skill_distribution,
r4_stars,
r5_stars,
radar_skill_rarity,
radar_skill_rarity_owned,
honordebt,
excludeFulfilled,
} = this.state;
let { demands } = this.state;
let totalHonorDebt = 0;
let readableHonorDebt = '';
if (honordebt) {
totalHonorDebt = honordebt.totalStars
.map((val, idx) => (val - honordebt.ownedStars[idx]) * CONFIG.CITATION_COST[idx])
.reduce((a, b) => a + b, 0);
let totalHonorDebtDays = totalHonorDebt / 2000;
let years = Math.floor(totalHonorDebtDays / 365);
let months = Math.floor((totalHonorDebtDays - years * 365) / 30);
let days = totalHonorDebtDays - years * 365 - months * 30;
readableHonorDebt = `${years} years ${months} months ${Math.floor(days)} days`;
}
let totalChronCost = 0;
let factionRec = [];
demands.forEach((entry) => {
let cost = entry.equipment.item_sources.map((its: any) => its.avg_cost).filter((cost) => !!cost);
if (cost && cost.length > 0) {
totalChronCost += Math.min(...cost) * entry.count;
} else {
let factions = entry.equipment.item_sources.filter((e) => e.type === 1);
if (factions && factions.length > 0) {
let fe = factionRec.find((e: any) => e.name === factions[0].name);
if (fe) {
fe.count += entry.count;
} else {
factionRec.push({
name: factions[0].name,
count: entry.count,
});
}
}
}
});
if (excludeFulfilled) {
demands = demands.filter((d) => d.count > d.have);
}
factionRec = factionRec.sort((a, b) => b.count - a.count).filter((e) => e.count > 0);
totalChronCost = Math.floor(totalChronCost);
return (
<ErrorBoundary>
<h3>Owned vs. Not Owned crew per rarity</h3>
<div style={{ height: '320px' }}>
<ResponsiveBar
data={data_ownership}
theme={themes.dark}
keys={['Owned', 'Not Owned', 'Not Owned - Portal']}
indexBy='rarity'
layout='horizontal'
margin={{ top: 50, right: 130, bottom: 50, left: 100 }}
padding={0.3}
axisBottom={{
legend: 'Number of crew',
legendPosition: 'middle',
legendOffset: 32,
}}
labelSkipWidth={12}
labelSkipHeight={12}
legends={[
{
dataFrom: 'keys',
anchor: 'bottom-right',
direction: 'column',
justify: false,
translateX: 120,
translateY: 0,
itemsSpacing: 2,
itemWidth: 100,
itemHeight: 20,
itemDirection: 'left-to-right',
symbolSize: 20,
effects: [
{
on: 'hover',
style: {
itemOpacity: 1,
},
},
],
},
]}
animate={false}
/>
</div>
<h3>Honor debt</h3>
{honordebt && (
<div>
<Table basic='very' striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Rarity</Table.HeaderCell>
<Table.HeaderCell>Required stars</Table.HeaderCell>
<Table.HeaderCell>Honor cost</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{honordebt.totalStars.map((val, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<Header as='h4'>{CONFIG.RARITIES[idx + 1].name}</Header>
</Table.Cell>
<Table.Cell>
{val - honordebt.ownedStars[idx]}{' '}
<span>
<i>
({honordebt.ownedStars[idx]} / {val})
</i>
</span>
</Table.Cell>
<Table.Cell>{(val - honordebt.ownedStars[idx]) * CONFIG.CITATION_COST[idx]}</Table.Cell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell />
<Table.HeaderCell>
Owned {honordebt.ownedStars.reduce((a, b) => a + b, 0)} out of {honordebt.totalStars.reduce((a, b) => a + b, 0)}
</Table.HeaderCell>
<Table.HeaderCell>{totalHonorDebt}</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
<Message info>
<Message.Header>{readableHonorDebt}</Message.Header>
<p>That's how long will it take you to max all remaining crew in the vault at 2000 honor / day</p>
</Message>
</div>
)}
<h3>Items required to level all owned crew</h3>
<h5>Note: this may over-include already equipped items from previous level bands for certain crew</h5>
<Message info>
<Message.Header>Cost and faction recommendations</Message.Header>
<p>
Total chroniton cost to farm all these items: {totalChronCost}{' '}
<span style={{ display: 'inline-block' }}>
<img src={`${process.env.GATSBY_ASSETS_URL}atlas/energy_icon.png`} height={14} />
</span>
</p>
{honordebt && (
<p>
Total number of credits required to craft all the recipes: {honordebt.craftCost}{' '}
<span style={{ display: 'inline-block' }}>
<img src={`${process.env.GATSBY_ASSETS_URL}atlas/soft_currency_icon.png`} height={14} />
</span>
</p>
)}
</Message>
<h4>Factions with most needed non-mission items</h4>
<ul>
{factionRec.map((e) => (
<li key={e.name}>
{e.name}: {e.count} items
</li>
))}
</ul>
<div>
<Checkbox
label='Exclude already fulfilled'
onChange={() => this.setState({ excludeFulfilled: !excludeFulfilled })}
checked={this.state.excludeFulfilled}
/>
<Grid columns={3} centered padded>
{demands.map((entry, idx) => (
<Grid.Column key={idx}>
<Popup
trigger={
<Header
style={{ display: 'flex', cursor: 'zoom-in' }}
icon={
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${entry.equipment.imageUrl}`}
size={48}
maxRarity={entry.equipment.rarity}
rarity={entry.equipment.rarity}
/>
}
content={entry.equipment.name}
subheader={`Need ${entry.count} ${entry.factionOnly ? ' (FACTION)' : ''} (have ${entry.have})`}
/>
}
header={CONFIG.RARITIES[entry.equipment.rarity].name + ' ' + entry.equipment.name}
content={<ItemSources item_sources={entry.equipment.item_sources} />}
on='click'
wide
/>
</Grid.Column>
))}
</Grid>
</div>
<h3>Skill coverage per rarity (yours vs. every crew in vault)</h3>
<div>
<div style={{ height: '320px', width: '50%', display: 'inline-block' }}>
<ResponsiveRadar
data={radar_skill_rarity_owned}
theme={themes.dark}
keys={['Common', 'Uncommon', 'Rare', 'Super Rare', 'Legendary']}
indexBy='name'
maxValue='auto'
margin={{ top: 70, right: 80, bottom: 40, left: 80 }}
curve='linearClosed'
borderWidth={2}
borderColor={{ from: 'color' }}
gridLevels={5}
gridShape='circular'
gridLabelOffset={36}
enableDots={true}
dotSize={10}
dotColor={{ theme: 'background' }}
dotBorderWidth={2}
dotBorderColor={{ from: 'color' }}
enableDotLabel={true}
dotLabel='value'
dotLabelYOffset={-12}
colors={{ scheme: 'nivo' }}
fillOpacity={0.25}
blendMode='multiply'
animate={true}
motionStiffness={90}
motionDamping={15}
isInteractive={true}
legends={[
{
anchor: 'top-left',
direction: 'column',
translateX: -50,
translateY: -40,
itemWidth: 80,
itemHeight: 20,
itemTextColor: '#999',
symbolSize: 12,
symbolShape: 'circle',
effects: [
{
on: 'hover',
style: {
itemTextColor: '#000',
},
},
],
},
]}
/>
</div>
<div style={{ height: '320px', width: '50%', display: 'inline-block' }}>
<ResponsiveRadar
data={radar_skill_rarity}
theme={themes.dark}
keys={['Common', 'Uncommon', 'Rare', 'Super Rare', 'Legendary']}
indexBy='name'
maxValue='auto'
margin={{ top: 70, right: 80, bottom: 40, left: 80 }}
curve='linearClosed'
borderWidth={2}
borderColor={{ from: 'color' }}
gridLevels={5}
gridShape='circular'
gridLabelOffset={36}
enableDots={true}
dotSize={10}
dotColor={{ theme: 'background' }}
dotBorderWidth={2}
dotBorderColor={{ from: 'color' }}
enableDotLabel={true}
dotLabel='value'
dotLabelYOffset={-12}
colors={{ scheme: 'nivo' }}
fillOpacity={0.25}
blendMode='multiply'
animate={true}
motionStiffness={90}
motionDamping={15}
isInteractive={true}
legends={[
{
anchor: 'top-left',
direction: 'column',
translateX: -50,
translateY: -40,
itemWidth: 80,
itemHeight: 20,
itemTextColor: '#999',
symbolSize: 12,
symbolShape: 'circle',
effects: [
{
on: 'hover',
style: {
itemTextColor: '#000',
},
},
],
},
]}
/>
</div>
</div>
<h3>Number of stars (fused rarity) for your Super Rare and Legendary crew</h3>
<div>
<div style={{ height: '320px', width: '50%', display: 'inline-block' }}>
<ResponsivePie
data={r4_stars}
theme={themes.dark}
margin={{ top: 40, right: 80, bottom: 80, left: 80 }}
innerRadius={0.2}
padAngle={2}
cornerRadius={2}
borderWidth={1}
animate={false}
slicesLabelsTextColor='#333333'
legends={[
{
anchor: 'bottom',
direction: 'row',
translateY: 56,
itemWidth: 100,
itemHeight: 18,
itemTextColor: '#999',
symbolSize: 18,
symbolShape: 'circle',
effects: [
{
on: 'hover',
style: {
itemTextColor: '#000',
},
},
],
},
]}
/>
</div>
<div style={{ height: '320px', width: '50%', display: 'inline-block' }}>
<ResponsivePie
data={r5_stars}
theme={themes.dark}
margin={{ top: 40, right: 80, bottom: 80, left: 80 }}
innerRadius={0.2}
padAngle={2}
cornerRadius={2}
borderWidth={1}
animate={false}
slicesLabelsTextColor='#333333'
legends={[
{
anchor: 'bottom',
direction: 'row',
translateY: 56,
itemWidth: 100,
itemHeight: 18,
itemTextColor: '#999',
symbolSize: 18,
symbolShape: 'circle',
effects: [
{
on: 'hover',
style: {
itemTextColor: '#000',
},
},
],
},
]}
/>
</div>
</div>
<h3>Skill distribution for owned crew (number of characters per skill combos Primary > Secondary)</h3>
<Checkbox label='Include tertiary skill' onChange={() => this._onIncludeTertiary()} checked={this.state.includeTertiary} />
<div>
<div style={{ height: '420px', width: '50%', display: 'inline-block' }}>
<ResponsiveSunburst
data={skill_distribution}
theme={themes.dark}
margin={{ top: 40, right: 20, bottom: 20, left: 20 }}
identity='name'
value='loc'
cornerRadius={2}
borderWidth={1}
borderColor='white'
colors={{ scheme: 'nivo' }}
childColor={{ from: 'color' }}
animate={true}
motionStiffness={90}
motionDamping={15}
isInteractive={true}
/>
</div>
<div style={{ height: '420px', width: '50%', display: 'inline-block' }}>
<ResponsiveBar
data={flat_skill_distribution}
keys={['Count']}
theme={themes.dark}
indexBy='name'
layout='horizontal'
margin={{ top: 50, right: 130, bottom: 50, left: 100 }}
padding={0.3}
axisBottom={{
legend: 'Number of crew',
legendPosition: 'middle',
legendOffset: 32,
}}
labelSkipWidth={12}
labelSkipHeight={12}
legends={[
{
dataFrom: 'keys',
anchor: 'bottom-right',
direction: 'column',
justify: false,
translateX: 120,
translateY: 0,
itemsSpacing: 2,
itemWidth: 100,
itemHeight: 20,
itemDirection: 'left-to-right',
symbolSize: 20,
effects: [
{
on: 'hover',
style: {
itemOpacity: 1,
},
},
],
},
]}
animate={false}
/>
</div>
</div>
</ErrorBoundary>
);
}
Example #26
Source File: extracrewdetails.tsx From website with MIT License | 4 votes |
renderOptimalPolestars() {
if (!this.state.constellation || !this.state.optimalpolestars) {
return <span />;
}
const { optimalpolestars, constellation, pagination_rows, pagination_page } = this.state;
let data = JSON.parse(JSON.stringify(optimalpolestars));
let crewPolestars = constellation.keystones.concat(constellation.raritystone.concat(constellation.skillstones));
data.forEach((optimal) => {
optimal.combos = optimal.polestars.map((trait) => {
const polestar = crewPolestars.find((op) => filterTraits(op, trait));
// Catch when optimal combos include a polestar that isn't yet in DataCore's keystones list
return polestar ?? {
short_name: trait.substr(0, 1).toUpperCase()+trait.substr(1),
icon: {
file: '/items_keystones_'+trait+'.png'
}
};
})
});
// Pagination
let totalPages = Math.ceil(data.length / this.state.pagination_rows);
data = data.slice(pagination_rows * (pagination_page - 1), pagination_rows * pagination_page);
return (
<Segment>
<Header as='h4'>Optimal Polestars for Crew Retrieval</Header>
<div>All the polestars that give the best chance of retrieving this crew</div>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={1}>Best Chance</Table.HeaderCell>
<Table.HeaderCell width={3} textAlign='center'>Polestar Combination</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{data.map((optimal, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<div style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{(1/optimal.count*100).toFixed()}%
</div>
{optimal.count > 1 && (
<div style={{ gridArea: 'description' }}>Shared with{' '}
{optimal.alts.map((alt) => (
<Link key={alt.symbol} to={`/crew/${alt.symbol}/`}>
{alt.name}
</Link>
)).reduce((prev, curr) => [prev, ', ', curr])}
</div>
)}
</Table.Cell>
<Table.Cell>
<Grid columns={4} centered padded>
{optimal.combos.map((polestar, idx) => (
<Grid.Column key={idx} textAlign='center' mobile={8} tablet={5} computer={4}>
<img width={32} src={`${process.env.GATSBY_ASSETS_URL}${polestar.icon.file.substr(1).replace(/\//g, '_')}`} />
<br />{polestar.short_name}
</Grid.Column>
))}
</Grid>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan="8">
<Pagination
totalPages={totalPages}
activePage={pagination_page}
onPageChange={(event, { activePage }) => this._onChangePage(activePage)}
/>
<span style={{ paddingLeft: '2em' }}>
Rows per page:{' '}
<Dropdown
inline
options={pagingOptions}
value={pagination_rows}
onChange={(event, { value }) =>
this.setState({ pagination_page: 1, pagination_rows: value as number })
}
/>
</span>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
</Segment>
);
}
Example #27
Source File: eventplanner.tsx From website with MIT License | 4 votes |
EventCrewTable = (props: EventCrewTableProps) => {
const { eventData, phaseIndex, buffConfig } = props;
const [showBonus, setShowBonus] = useStateWithStorage('eventplanner/showBonus', true);
const [applyBonus, setApplyBonus] = useStateWithStorage('eventplanner/applyBonus', true);
const [showPotential, setShowPotential] = useStateWithStorage('eventplanner/showPotential', false);
const [showFrozen, setShowFrozen] = useStateWithStorage('eventplanner/showFrozen', true);
const [initOptions, setInitOptions] = React.useState({});
const crewAnchor = React.useRef(null);
React.useEffect(() => {
setInitOptions({});
}, [eventData, phaseIndex]);
if (eventData.bonus.length == 0)
return (
<div style={{ marginTop: '1em' }}>
Featured crew not yet identified for this event.
</div>
);
const tableConfig: ITableConfigRow[] = [
{ width: 3, column: 'name', title: 'Crew', pseudocolumns: ['name', 'max_rarity', 'level'] },
{ width: 1, column: 'bonus', title: 'Bonus', reverse: true },
{ width: 1, column: 'bestSkill.score', title: 'Best', reverse: true },
{ width: 1, column: 'bestPair.score', title: 'Pair', reverse: true }
];
CONFIG.SKILLS_SHORT.forEach((skill) => {
tableConfig.push({
width: 1,
column: `${skill.name}.core`,
title: <img alt={CONFIG.SKILLS[skill.name]} src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${skill.name}.png`} style={{ height: '1.1em' }} />,
reverse: true
});
});
// Check for custom column (i.e. combo from crew matrix click)
let customColumn = '';
if (initOptions?.column && tableConfig.findIndex(col => col.column === initOptions.column) == -1) {
customColumn = initOptions.column;
const customSkills = customColumn.replace('combos.', '').split(',');
tableConfig.push({
width: 1,
column: customColumn,
title:
<span>
<img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${customSkills[0]}.png`} style={{ height: '1.1em' }} />
+<img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${customSkills[1]}.png`} style={{ height: '1.1em' }} />
</span>,
reverse: true
});
}
const phaseType = phaseIndex < eventData.content_types.length ? eventData.content_types[phaseIndex] : eventData.content_types[0];
let bestCombos = {};
const zeroCombos = {};
for (let first = 0; first < CONFIG.SKILLS_SHORT.length; first++) {
let firstSkill = CONFIG.SKILLS_SHORT[first];
zeroCombos[firstSkill.name] = 0;
for (let second = first+1; second < CONFIG.SKILLS_SHORT.length; second++) {
let secondSkill = CONFIG.SKILLS_SHORT[second];
zeroCombos[firstSkill.name+','+secondSkill.name] = 0;
}
}
let myCrew = JSON.parse(JSON.stringify(props.crew));
// Filter crew by bonus, frozen here instead of searchabletable callback so matrix can use filtered crew list
if (showBonus) myCrew = myCrew.filter((c) => eventData.bonus.indexOf(c.symbol) >= 0);
if (!showFrozen) myCrew = myCrew.filter((c) => c.immortal == 0);
const getPairScore = (crew: any, primary: string, secondary: string) => {
if (phaseType === 'shuttles') {
if (secondary) return crew[primary].core+(crew[secondary].core/4);
return crew[primary].core;
}
if (secondary) return (crew[primary].core+crew[secondary].core)/2;
return crew[primary].core/2;
};
myCrew.forEach(crew => {
// First adjust skill scores as necessary
if (applyBonus || showPotential) {
crew.bonus = 1;
if (applyBonus && eventData.featured.indexOf(crew.symbol) >= 0) {
if (phaseType == 'gather') crew.bonus = 10;
else if (phaseType == 'shuttles') crew.bonus = 3;
}
else if (applyBonus && eventData.bonus.indexOf(crew.symbol) >= 0) {
if (phaseType == 'gather') crew.bonus = 5;
else if (phaseType == 'shuttles') crew.bonus = 2;
}
if (crew.bonus > 1 || showPotential) {
CONFIG.SKILLS_SHORT.forEach(skill => {
if (crew[skill.name].core > 0) {
if (showPotential && crew.immortal === 0 && !crew.prospect) {
crew[skill.name].current = crew[skill.name].core*crew.bonus;
crew[skill.name] = applySkillBuff(buffConfig, skill.name, crew.skill_data[crew.rarity-1].base_skills[skill.name]);
}
crew[skill.name].core = crew[skill.name].core*crew.bonus;
}
});
}
}
// Then calculate skill combination scores
let combos = {...zeroCombos};
let bestPair = { score: 0 };
let bestSkill = { score: 0 };
for (let first = 0; first < CONFIG.SKILLS_SHORT.length; first++) {
const firstSkill = CONFIG.SKILLS_SHORT[first];
const single = {
score: crew[firstSkill.name].core,
skillA: firstSkill.name
};
combos[firstSkill.name] = single.score;
if (!bestCombos[firstSkill.name] || single.score > bestCombos[firstSkill.name].score)
bestCombos[firstSkill.name] = { id: crew.id, score: single.score };
if (single.score > bestSkill.score) bestSkill = { score: single.score, skill: single.skillA };
for (let second = first+1; second < CONFIG.SKILLS_SHORT.length; second++) {
const secondSkill = CONFIG.SKILLS_SHORT[second];
let pair = {
score: getPairScore(crew, firstSkill.name, secondSkill.name),
skillA: firstSkill.name,
skillB: secondSkill.name
}
if (crew[secondSkill.name].core > crew[firstSkill.name].core) {
pair = {
score: getPairScore(crew, secondSkill.name, firstSkill.name),
skillA: secondSkill.name,
skillB: firstSkill.name
}
}
combos[firstSkill.name+','+secondSkill.name] = pair.score;
if (pair.score > bestPair.score) bestPair = pair;
const pairId = firstSkill.name+secondSkill.name;
if (!bestCombos[pairId] || pair.score > bestCombos[pairId].score)
bestCombos[pairId] = { id: crew.id, score: pair.score };
}
}
crew.combos = combos;
crew.bestPair = bestPair;
crew.bestSkill = bestSkill;
});
return (
<React.Fragment>
<div ref={crewAnchor} />
<Header as='h4'>Your Crew</Header>
<div style={{ margin: '.5em 0' }}>
<Form.Group grouped>
<Form.Field
control={Checkbox}
label={`Only show event crew (${eventData.bonus_text.replace('Crew Bonus: ', '')})`}
checked={showBonus}
onChange={(e, { checked }) => setShowBonus(checked)}
/>
<Form.Field
control={Checkbox}
label='Apply event bonus to skills'
checked={applyBonus}
onChange={(e, { checked }) => setApplyBonus(checked)}
/>
<Form.Field
control={Checkbox}
label='Show potential skills of unleveled crew'
checked={showPotential}
onChange={(e, { checked }) => setShowPotential(checked)}
/>
<Form.Field
control={Checkbox}
label='Show frozen (vaulted) crew'
checked={showFrozen}
onChange={(e, { checked }) => setShowFrozen(checked)}
/>
</Form.Group>
</div>
<SearchableTable
id='eventplanner'
data={myCrew}
config={tableConfig}
renderTableRow={(crew, idx, highlighted) => renderTableRow(crew, idx, highlighted)}
filterRow={(crew, filters, filterType) => showThisCrew(crew, filters, filterType)}
initOptions={initOptions}
showFilterOptions={true}
lockable={props.lockable}
/>
{phaseType !== 'skirmish' && (<EventCrewMatrix crew={myCrew} bestCombos={bestCombos} phaseType={phaseType} handleClick={sortByCombo} />)}
</React.Fragment>
);
function renderTableRow(crew: any, idx: number, highlighted: boolean): JSX.Element {
const attributes = {
positive: highlighted
};
return (
<Table.Row key={idx} style={{ cursor: 'zoom-in' }} onClick={() => navigate(`/crew/${crew.symbol}/`)} {...attributes}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}><Link to={`/crew/${crew.symbol}/`}>{crew.name}</Link></span>
</div>
<div style={{ gridArea: 'description' }}>{descriptionLabel(crew)}</div>
</div>
</Table.Cell>
<Table.Cell textAlign='center'>
{crew.bonus > 1 ? `x${crew.bonus}` : ''}
</Table.Cell>
<Table.Cell textAlign='center'>
<b>{scoreLabel(crew.bestSkill.score)}</b>
<br /><img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${crew.bestSkill.skill}.png`} style={{ height: '1em' }} />
</Table.Cell>
<Table.Cell textAlign='center'>
<b>{scoreLabel(crew.bestPair.score)}</b>
<br /><img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${crew.bestPair.skillA}.png`} style={{ height: '1em' }} />
{crew.bestPair.skillB != '' && (<span>+<img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${crew.bestPair.skillB}.png`} style={{ height: '1em' }} /></span>)}
</Table.Cell>
{CONFIG.SKILLS_SHORT.map(skill =>
crew.base_skills[skill.name] ? (
<Table.Cell key={skill.name} textAlign='center'>
<b>{scoreLabel(crew[skill.name].core)}</b>
{phaseType != 'gather' && (<span><br /><small>+({crew[skill.name].min}-{crew[skill.name].max})</small></span>)}
</Table.Cell>
) : (
<Table.Cell key={skill.name} />
)
)}
{customColumn != '' && (
<Table.Cell key='custom' textAlign='center'>
<b>{scoreLabel(customColumn.split('.').reduce((prev, curr) => prev.hasOwnProperty(curr) ? prev[curr] : undefined, crew))}</b>
</Table.Cell>
)
}
</Table.Row>
);
}
function descriptionLabel(crew: any): JSX.Element {
return (
<div>
<div><Rating icon='star' rating={crew.rarity} maxRating={crew.max_rarity} size='large' disabled /></div>
<div>
{crew.favorite && <Icon name='heart' />}
{crew.immortal > 0 && <Icon name='snowflake' />}
{crew.prospect && <Icon name='add user' />}
<span>{crew.immortal ? (`${crew.immortal} frozen`) : (`Level ${crew.level}`)}</span>
</div>
</div>
);
}
function scoreLabel(score: number): JSX.Element {
if (!score || score === 0) return (<></>);
if (phaseType === 'gather') return (<>{`${calculateGalaxyChance(score)}%`}</>);
return (<>{Math.floor(score)}</>);
}
function showThisCrew(crew: any, filters: [], filterType: string): boolean {
// Bonus, frozen crew filtering now handled before rendering entire table instead of each row
return crewMatchesSearchFilter(crew, filters, filterType);
}
function sortByCombo(skillA: string, skillB: string): void {
if (skillA === skillB) {
setInitOptions({
column: `${skillA}.core`,
direction: 'descending'
});
}
else {
// Order of combo match order of skills in CONFIG
const customSkills = [];
CONFIG.SKILLS_SHORT.forEach((skill) => {
if (skillA === skill.name || skillB === skill.name)
customSkills.push(skill.name);
});
setInitOptions({
column: `combos.${customSkills[0]},${customSkills[1]}`,
direction: 'descending'
});
}
if (!crewAnchor.current) return;
crewAnchor.current.scrollIntoView({
behavior: 'smooth'
}, 500);
}
}
Example #28
Source File: crewretrieval.tsx From website with MIT License | 4 votes |
CrewTable = (props: CrewTableProps) => {
const { data, polestars } = props;
const [activeCrew, setActiveCrew] = React.useState(null);
const [activeCollections, setActiveCollections] = React.useState(null);
if (!data) return (<></>);
const tableConfig: ITableConfigRow[] = [
{ width: 3, column: 'name', title: 'Crew' },
{ width: 1, column: 'max_rarity', title: 'Rarity', reverse: true, tiebreakers: ['highest_owned_rarity'] },
{ width: 1, column: 'bigbook_tier', title: 'Tier' },
{ width: 1, column: 'cab_ov', title: 'CAB', reverse: true, tiebreakers: ['cab_ov_rank'] },
{ width: 1, column: 'ranks.voyRank', title: 'Voyage' },
{ width: 1, column: 'collections.length', title: 'Collections', reverse: true },
{ width: 1, title: 'Useable Combos' }
];
return (
<SearchableTable
id={"crewretrieval"}
data={data}
config={tableConfig}
renderTableRow={(crew, idx) => renderTableRow(crew, idx)}
filterRow={(crew, filters, filterType) => crewMatchesSearchFilter(crew, filters, filterType)}
showFilterOptions={true}
/>
);
function renderTableRow(crew: any, idx: number): JSX.Element {
return (
<Table.Row key={idx}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}><Link to={`/crew/${crew.symbol}/`}>{crew.name}</Link></span>
</div>
<div style={{ gridArea: 'description' }}>{getCoolStats(crew, false, false)}</div>
</div>
</Table.Cell>
<Table.Cell>
<Rating icon='star' rating={crew.highest_owned_rarity} maxRating={crew.max_rarity} size="large" disabled />
</Table.Cell>
<Table.Cell textAlign="center" style={{ display: activeCrew === crew.symbol ? 'none' : 'table-cell' }}>
<b>{formatTierLabel(crew.bigbook_tier)}</b>
</Table.Cell>
<Table.Cell textAlign="center" style={{ display: activeCrew === crew.symbol ? 'none' : 'table-cell' }}>
<b>{crew.cab_ov}</b><br />
<small>{rarityLabels[parseInt(crew.max_rarity)-1]} #{crew.cab_ov_rank}</small>
</Table.Cell>
<Table.Cell textAlign="center" style={{ display: activeCrew === crew.symbol ? 'none' : 'table-cell' }}>
<b>#{crew.ranks.voyRank}</b><br />
{crew.ranks.voyTriplet && <small>Triplet #{crew.ranks.voyTriplet.rank}</small>}
</Table.Cell>
<Table.Cell textAlign="center"
style={{ cursor: activeCollections === crew.symbol ? 'zoom-out' : 'zoom-in', display: activeCrew === crew.symbol ? 'none' : 'table-cell' }}
onClick={() => { setActiveCollections(activeCollections === crew.symbol ? null : crew.symbol) }}
>
{showCollectionsForCrew(crew)}
</Table.Cell>
<Table.Cell textAlign="center" style={{ display: activeCrew === crew.symbol ? 'table-cell' : 'none' }}
colSpan={activeCrew === crew.symbol ? 4 : undefined}
>
{showCombosForCrew(crew)}
</Table.Cell>
<Table.Cell textAlign="center" style={{ cursor: activeCrew === crew.symbol ? 'zoom-out' : 'zoom-in' }}
onClick={(e) => { setActiveCrew(activeCrew === crew.symbol ? null : crew.symbol); e.stopPropagation(); }}
>
{activeCrew === crew.symbol ? 'Hide' : 'View'}
</Table.Cell>
</Table.Row>
);
}
function showCombosForCrew(crew: any): JSX.Element {
if (activeCrew !== crew.symbol) return (<></>);
let combos = crew.unique_polestar_combos?.filter(
(upc) => upc.every(
(trait) => polestars.some(op => filterTraits(op, trait))
)
).map((upc) => upc.map((trait) => polestars.find((op) => filterTraits(op, trait))));
// Exit here if activecrew has 0 combos after changing filters
if (combos.length == 0) return (<></>);
let fuseGroups = groupByFuses(combos, 0, []);
return (<ComboGrid crew={crew} combos={combos} fuseGroups={fuseGroups} />);
}
function groupByFuses(combos: any[], start: number, group: number[]): any {
const fuseGroups = {};
const consumed = {};
group.forEach((comboId) => {
combos[comboId].forEach((polestar) => {
if (consumed[polestar.symbol])
consumed[polestar.symbol]++;
else
consumed[polestar.symbol] = 1;
});
});
combos.forEach((combo, comboId) => {
if (comboId >= start) {
let consumable = 0;
combo.forEach((polestar) => {
if (!consumed[polestar.symbol] || polestar.quantity-consumed[polestar.symbol] >= 1)
consumable++;
});
if (consumable == combo.length) {
const parentGroup = [...group, comboId];
const parentId = 'x'+parentGroup.length;
if (fuseGroups[parentId])
fuseGroups[parentId].push(parentGroup);
else
fuseGroups[parentId] = [parentGroup];
// Only collect combo groups up to 5 fuses
if (parentGroup.length < 5) {
let childGroups = groupByFuses(combos, comboId, parentGroup);
for (let childId in childGroups) {
if (fuseGroups[childId])
fuseGroups[childId] = fuseGroups[childId].concat(childGroups[childId]);
else
fuseGroups[childId] = childGroups[childId];
}
}
}
}
});
return fuseGroups;
}
function showCollectionsForCrew(crew: any): JSX.Element {
if (activeCollections !== crew.symbol || crew.collections.length == 0)
return (<b>{crew.collections.length}</b>);
const formattedCollections = crew.collections.map((c, idx) => (
<span key={idx}>{c}{idx < crew.collections.length-1 ? ',' : ''}</span>
)).reduce((prev, curr) => [prev, ' ', curr]);
return (
<div>
{formattedCollections}
</div>
);
}
}
Example #29
Source File: crewretrieval.tsx From website with MIT License | 4 votes |
PolestarProspectModal = (props: PolestarProspectModalProps) => {
const { ownedPolestars, allCrew, updateProspects } = props;
const [addedPolestars, setAddedPolestars] = React.useState(props.addedPolestars);
const [modalIsOpen, setModalIsOpen] = React.useState(false);
const [activeCrew, setActiveCrew] = React.useState('');
const [activeConstellation, setActiveConstellation] = React.useState('');
const [activePolestar, setActivePolestar] = React.useState('');
const [allKeystones, setAllKeystones] = React.useState(undefined);
const [control, setControl] = React.useState([]);
const [crewCrates, setCrewCrates] = React.useState(0);
const [ownedConstellations, setOwnedConstellations] = React.useState([]);
React.useEffect(() => {
if (allKeystones) {
// Chances assume you can't get rarity, skill constellations from scans
setCrewCrates(allKeystones.filter(k => k.type == 'crew_keystone_crate').length);
const owned = allKeystones.filter(k => (k.type == 'crew_keystone_crate' || k.type == 'keystone_crate') && k.quantity > 0)
.sort((a, b) => a.name.localeCompare(b.name));
setOwnedConstellations([...owned]);
}
}, [allKeystones]);
// Recalculate combos only when modal gets closed
React.useEffect(() => {
if (!modalIsOpen) {
updateProspects([...addedPolestars]);
}
}, [modalIsOpen]);
return (
<Modal
open={modalIsOpen}
onClose={() => setModalIsOpen(false)}
onOpen={() => setModalIsOpen(true)}
trigger={<Button><Icon name='add' />{addedPolestars.length}</Button>}
size='large'
>
<Modal.Header>Add Prospective Polestars</Modal.Header>
<Modal.Content scrolling>
{renderContent()}
</Modal.Content>
<Modal.Actions>
{activePolestar != '' && (<Button icon='backward' content='Return to polestars' onClick={() => setActivePolestar('')} />)}
<Button positive onClick={() => setModalIsOpen(false)}>Close</Button>
</Modal.Actions>
</Modal>
);
function renderContent(): JSX.Element {
if (!modalIsOpen) return (<></>);
if (!allKeystones) {
calculateKeystoneOdds();
calculateControl();
return (<></>);
}
if (activePolestar != '')
return renderPolestarDetail();
return renderPolestarFinder();
}
function calculateKeystoneOdds(): void {
const allkeystones = JSON.parse(JSON.stringify(props.allKeystones));
let totalCrates = 0, totalDrops = 0;
allkeystones.forEach(keystone => {
if (keystone.type == 'crew_keystone_crate') {
totalCrates++;
totalDrops += keystone.keystones.length;
}
});
allkeystones.filter(k => k.type == 'keystone').forEach(polestar => {
const crates = allkeystones.filter(k => (k.type == 'crew_keystone_crate' || k.type == 'keystone_crate') && k.keystones.includes(polestar.id));
const nochance = polestar.filter.type == 'rarity' || polestar.filter.type == 'skill' || crates.length == 0;
polestar.crate_count = nochance ? 0 : crates.length;
//polestar.scan_odds = nochance ? 0 : crates.length/totalDrops; // equal chance of dropping
polestar.scan_odds = nochance ? 0 : crates.reduce((prev, curr) => prev + (1/curr.keystones.length), 0)/totalCrates; // conditional probability
const owned = crates.filter(k => k.quantity > 0);
polestar.owned_crate_count = owned.reduce((prev, curr) => prev + curr.quantity, 0);
polestar.owned_best_odds = owned.length == 0 ? 0 : 1/owned.reduce((prev, curr) => Math.min(prev, curr.keystones.length), 100);
polestar.owned_total_odds = owned.length == 0 ? 0 : 1-owned.reduce((prev, curr) => prev*(((curr.keystones.length-1)/curr.keystones.length)**curr.quantity), 1);
if (polestar.filter.type === 'rarity')
polestar.crew_count = allCrew.filter(c => c.in_portal && c.max_rarity == polestar.filter.rarity).length;
else if (polestar.filter.type === 'skill')
polestar.crew_count = allCrew.filter(c => c.in_portal && c.base_skills[polestar.filter.skill]).length;
else if (polestar.filter.type === 'trait')
polestar.crew_count = allCrew.filter(c => c.in_portal && c.traits.some(trait => trait === polestar.filter.trait)).length;
});
setAllKeystones([...allkeystones]);
}
function calculateControl(): void {
// Control is a list of crew that you can't retrieve, which includes crew not in portal
const retrievable = getRetrievable(allCrew, ownedPolestars);
const unretrievable = allCrew.filter(pc => !retrievable.some(cc => cc === pc));
setControl([...unretrievable]);
}
function renderPolestarFinder(): JSX.Element {
const polestarTable: ITableConfigRow[] = [
{ width: 2, column: 'name', title: 'Polestar' },
{ width: 1, column: 'crew_count', title: 'Crew in Portal', reverse: true },
{ width: 1, column: 'crate_count', title: 'Constellation Chance', reverse: true },
{ width: 1, column: 'scan_odds', title: 'Scan Chance', reverse: true },
{ width: 1, column: 'owned_best_odds', title: 'Best Chance', reverse: true },
{ width: 1, column: 'quantity', title: 'Owned', reverse: true },
{ width: 1, column: 'loaned', title: 'Added', reverse: true }
];
const constellationList = ownedConstellations.map(c => {
return { key: c.symbol, value: c.symbol, text: c.name };
});
// !! Always filter polestars by crew_count to hide deprecated polestars !!
let data = allKeystones.filter(k => k.type == 'keystone' && k.crew_count > 0);
if (activeCrew != '') {
const crew = allCrew.find(c => c.symbol === activeCrew);
data = data.filter(k => (k.filter.type == 'trait' && crew.traits.includes(k.filter.trait))
|| (k.filter.type == 'rarity' && k.filter.rarity == crew.max_rarity)
|| (k.filter.type == 'skill' && k.filter.skill in crew.base_skills));
}
if (activeConstellation != '') {
const crewKeystones = allKeystones.find(k => k.symbol === activeConstellation).keystones;
data = data.filter(k => crewKeystones.includes(k.id));
}
data.forEach(p => {
p.loaned = addedPolestars.filter(added => added === p.symbol).length;
});
return (
<React.Fragment>
<CrewPicker crew={control} value={activeCrew} updateCrew={updateCrew} />
{activeCrew == '' && constellationList.length > 0 && (
<React.Fragment>
<span style={{ margin: '0 1em' }}>or</span>
<Dropdown
placeholder='Filter polestars by owned constellation'
style={{ minWidth: '20em' }}
selection
clearable
options={constellationList}
value={activeConstellation}
onChange={(e, { value }) => setAsActive('constellation', value) }
/>
</React.Fragment>
)}
{renderCrewMessage(data)}
{renderConstellationMessage(data)}
<div style={{ marginTop: '1em' }}>
<SearchableTable
data={data}
config={polestarTable}
renderTableRow={(polestar, idx) => renderPolestarRow(polestar, idx)}
filterRow={(polestar, filter) => filterText(polestar, filter)}
explanation={
<div>
<p>Search for polestars by name.</p>
</div>
}
/>
<p>
<i>Constellation Chance</i>: your chance of acquiring any constellation with the polestar from a successful scan.
<br /><i>Scan Chance</i>: your overall chance of acquiring the polestar from a successful scan.
<br /><i>Best Chance</i>: your best chance of acquiring the polestar from a constellation in your inventory.
</p>
</div>
</React.Fragment>
);
}
function filterText(polestar: any, filters: []): boolean {
if (filters.length == 0) return true;
const matchesFilter = (input: string, searchString: string) =>
input.toLowerCase().indexOf(searchString.toLowerCase()) >= 0;
let meetsAnyCondition = false;
for (let filter of filters) {
let meetsAllConditions = true;
if (filter.conditionArray.length === 0) {
// text search only
for (let segment of filter.textSegments) {
let segmentResult = matchesFilter(polestar.name, segment.text);
meetsAllConditions = meetsAllConditions && (segment.negated ? !segmentResult : segmentResult);
}
}
if (meetsAllConditions) {
meetsAnyCondition = true;
break;
}
}
return meetsAnyCondition;
}
function renderPolestarRow(polestar: any, idx: number): JSX.Element {
return (
<Table.Row key={polestar.symbol}
style={{ cursor: activePolestar != polestar.symbol ? 'zoom-in' : 'zoom-out' }}
onClick={() => setActivePolestar(activePolestar != polestar.symbol ? polestar.symbol : '')}
>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '30px auto',
gridTemplateAreas: `'icon stats'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={24} src={`${process.env.GATSBY_ASSETS_URL}${polestar.icon.file.substr(1).replace(/\//g, '_')}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.1em' }}>{polestar.short_name}</span>
</div>
</div>
</Table.Cell>
<Table.Cell textAlign='center'>{polestar.crew_count}</Table.Cell>
<Table.Cell textAlign='center'>{(polestar.crate_count/crewCrates*100).toFixed(1)}%</Table.Cell>
<Table.Cell textAlign='center'>{(polestar.scan_odds*100).toFixed(2)}%</Table.Cell>
<Table.Cell textAlign='center'>{(polestar.owned_best_odds*100).toFixed(1)}%</Table.Cell>
<Table.Cell textAlign='center'>{polestar.quantity}</Table.Cell>
<Table.Cell textAlign='center'>
<ProspectInventory polestar={polestar.symbol} loaned={polestar.loaned} updateProspect={updateProspect} />
</Table.Cell>
</Table.Row>
);
}
function renderCrewMessage(data: any[]): JSX.Element {
if (activeCrew == '') return (<></>);
const crew = allCrew.find(c => c.symbol === activeCrew);
if (!crew.in_portal)
return (<Message>{crew.name} is not available by crew retrieval.</Message>);
if (crew.unique_polestar_combos?.length == 0)
return (<Message>{crew.name} has no guaranteed retrieval options.</Message>);
const unownedPolestars = data.filter(p => p.quantity === 0);
if (unownedPolestars.length == 0)
return (<Message>You can already retrieve {crew.name} with the polestars in your inventory.</Message>);
crew.unique_polestar_combos.forEach(upc => {
const needs = unownedPolestars.filter(p => upc.some(trait => filterTraits(p, trait)));
needs.forEach(p => {
p.useful = p.useful ? p.useful + 1: 1;
if (needs.length == 1) p.useful_alone = true;
});
});
// "Useful" polestars are all unowned polestars that unlock retrievals by themselves (i.e. `useful_alone`)
// or other unowned polestars that together unlock retrievals WITHOUT also relying on a `useful_alone` polestar
const usefulPolestars = unownedPolestars.filter(p => p.useful_alone ||
crew.unique_polestar_combos.filter(upc => !upc.some(trait =>
unownedPolestars.filter(p => p.useful_alone).some(p => filterTraits(p, trait))
)).some(upc => upc.some(trait => filterTraits(p, trait))))
.sort((a, b) => b.useful - a.useful);
const showUsefulPolestars = () => {
if (usefulPolestars.length == 0)
return (<p>No unowned polestars will help you retrieve {crew.name}.</p>); // What case is this?
const usefulAlone = usefulPolestars.filter(p => p.useful_alone);
const usefulWithOthers = usefulPolestars.filter(p => !p.useful_alone); // Should either be 0 or 2+
return (
<p>
{usefulAlone.length > 0 && (<span>You need exactly one of the following polestars to retrieve {crew.name}: {renderPolestarsInline(usefulAlone)}</span>)}
{usefulAlone.length > 0 && usefulWithOthers.length > 0 && (<span><br />Or some combination of the following polestars: {renderPolestarsInline(usefulWithOthers)}</span>)}
{usefulAlone.length == 0 && (<span>You need some combination of the following polestars to retrieve {crew.name}: {renderPolestarsInline(usefulWithOthers)}</span>)}
</p>
);
};
// "Usable" constellations are owned constellations that include useful polestars
const showUsableConstellations = () => {
const usablePolestars = usefulPolestars.filter(p => p.owned_crate_count > 0);
if (usablePolestars.length == 0) return (<></>);
const constellations = ownedConstellations.filter(k => k.keystones.some(kId => usablePolestars.find(p => p.id === kId)));
if (constellations.length == 1)
return constellations.map(k => renderPolestarsFromConstellation(k, usablePolestars.filter(p => k.keystones.some(kId => kId === p.id))));
return usablePolestars.sort((a, b) => {
if (b.owned_total_odds == a.owned_total_odds)
return b.owned_best_odds - a.owned_best_odds;
return b.owned_total_odds - a.owned_total_odds;
}).map(p =>
renderConstellationsWithPolestar(p)
);
};
return (
<Message>
{showUsefulPolestars()}
{showUsableConstellations()}
</Message>
);
}
function renderPolestarsInline(polestars: any[]): JSX.Element[] {
return polestars.map((p, pdx) => (
<span key={pdx} onClick={() => setActivePolestar(p.symbol) }>
<b>{p.short_name}</b>{pdx < polestars.length-1 ? ',' : ''}
</span>
))
.reduce((prev, curr) => [prev, ' ', curr]);
}
function renderConstellationMessage(data: any[]): JSX.Element {
if (activeConstellation == '') return (<></>);
const constellation = allKeystones.find(k => k.symbol === activeConstellation);
const unownedPolestars = data.filter(p => p.quantity === 0);
if (unownedPolestars.length == 0)
return (<Message>You already own all polestars in the {constellation.name}.</Message>);
return (
<Message>
{renderPolestarsFromConstellation(constellation, unownedPolestars)}
</Message>
);
}
function renderPolestarDetail(): JSX.Element {
const polestar = allKeystones.find(k => k.symbol === activePolestar);
polestar.loaned = addedPolestars.filter(added => added === polestar.symbol).length;
return (
<div style={{ marginTop: '1em' }}>
<Table celled striped unstackable compact="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell>Polestar</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Crew in Portal</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Constellation Chance</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Scan Chance</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Best Chance</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Owned</Table.HeaderCell>
<Table.HeaderCell textAlign='center'>Added</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{renderPolestarRow(polestar, 1)}
</Table.Body>
</Table>
{polestar.owned_crate_count > 0 && (<Message>{renderConstellationsWithPolestar(polestar)}</Message>)}
{renderNewRetrievals(polestar)}
</div>
);
}
function renderPolestarsFromConstellation(constellation: any, polestars: any[]): JSX.Element {
const clarify = activeCrew != '' ? 'a needed' : 'an unowned';
return (
<div key={constellation.symbol}>
Open the <b><span onClick={() => setAsActive('constellation', constellation.symbol) }>{constellation.name}</span></b>{` `}
for a <b>{(polestars.length/constellation.keystones.length*100).toFixed(1)}%</b> chance of acquiring {clarify} polestar:{` `}
<Grid centered padded stackable>
{
polestars.map((p, pdx) => (
<Grid.Column key={pdx} width={2} textAlign='center' onClick={() => setActivePolestar(p.symbol)}>
<img width={32} src={`${process.env.GATSBY_ASSETS_URL}${p.icon.file.substr(1).replace(/\//g, '_')}`} />
<br /><b>{p.short_name}</b><br /><small>({(1/constellation.keystones.length*100).toFixed(1)}%)</small>
</Grid.Column>
))
}
</Grid>
{activeCrew == '' && constellation.quantity > 1 && (<p>You own {constellation.quantity} of this constellation.</p>)}
</div>
);
}
function renderConstellationsWithPolestar(polestar: any): JSX.Element {
const constellations = [];
ownedConstellations.filter(k => k.keystones.includes(polestar.id))
.forEach(k => {
for (let i = 0; i < k.quantity; i++) {
const newName = k.quantity > 1 ? k.name + " #"+(i+1) : k.name;
constellations.push({...k, name: newName});
}
});
return (
<p key={polestar.symbol}>
Open{` `}
{
constellations.sort((a, b) => 1/b.keystones.length - 1/a.keystones.length).map((k, kdx) => (
<span key={kdx} onClick={() => setAsActive('constellation', k.symbol) }>
<b>{k.name}</b> ({(1/k.keystones.length*100).toFixed(1)}%){kdx < constellations.length-1 ? ' or ' : ''}
</span>
)).reduce((prev, curr) => [prev, ' ', curr])
}{` `}
for a chance of acquiring the <b><span onClick={() => setActivePolestar(polestar.symbol)}>{polestar.name}</span></b>
{constellations.length > 1 && (<span>; open all for a <b>{(polestar.owned_total_odds*100).toFixed(1)}%</b> chance</span>)}
</p>
);
}
function renderNewRetrievals(polestar: any): JSX.Element {
const ownedPlus = JSON.parse(JSON.stringify(ownedPolestars));
ownedPlus.push({...polestar, quantity: 1});
const newRetrievables = getRetrievable(control, ownedPlus).filter(c => c.in_portal);
if (newRetrievables.length == 0)
return (
<p>
{polestar.quantity > 0 ? `You own ${polestar.quantity} of the ${polestar.name}. ` : ''}
Acquiring{polestar.quantity > 0 ? ` more of ` : ` `}this polestar will not unlock guaranteed retrievals for any new crew.
</p>
);
return (
<React.Fragment>
<p>Acquire the <b>{polestar.name}</b> to unlock guaranteed retrievals for the following crew:</p>
<Grid centered padded stackable>
{newRetrievables.sort((a, b) => a.name.localeCompare(b.name)).map((crew, cdx) => (
<Grid.Column key={crew.symbol} width={2} textAlign='center' onClick={() => setAsActive('crew', crew.symbol) }>
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`}
size={64}
maxRarity={crew.max_rarity}
rarity={crew.highest_owned_rarity}
/>
<div>{crew.name}</div>
</Grid.Column>
))}
</Grid>
</React.Fragment>
);
}
function getRetrievable(crewpool: any[], polestars: any[]): any[] {
return crewpool.filter(crew =>
crew.unique_polestar_combos?.some(upc =>
upc.every(trait => polestars.some(op => filterTraits(op, trait)))
));
}
function setAsActive(activeType: string, activeValue: string): void {
setActiveCrew(activeType == 'crew' ? activeValue : '');
setActiveConstellation(activeType == 'constellation' ? activeValue : '');
setActivePolestar('');
}
function updateCrew(symbol: string): void {
setAsActive('crew', symbol);
}
function updateProspect(polestar: string, increase: boolean): void {
if (polestar == '') return;
if (increase) {
addedPolestars.push(polestar);
}
else {
const prospectNum = addedPolestars.indexOf(polestar);
if (prospectNum >= 0) addedPolestars.splice(prospectNum, 1);
}
}
}