semantic-ui-react#Rating TypeScript Examples

The following examples show how to use semantic-ui-react#Rating. 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: crewpopup.tsx    From website with MIT License 6 votes vote down vote up
render() {
		const { crew, useBase } = this.props;
		//console.log(crew);
		if (!crew || !crew.symbol) {
			return <span>ERROR!</span>;
		}

		return (
			<Popup trigger={<span style={{ cursor: 'help', fontWeight: 'bolder' }}>{crew.name}</span>}>
				<Popup.Header>{crew.name}</Popup.Header>
				<Popup.Content>
					<Image size='small' src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
					<Rating icon='star' defaultRating={crew.rarity} maxRating={crew.max_rarity} />
					<p>{formatCrewStats(crew, useBase ?? true)}</p>
				</Popup.Content>
			</Popup>
		);
	}
Example #2
Source File: bigbook.tsx    From website with MIT License 6 votes vote down vote up
render() {
		const { crew, markdownRemark } = this.props;

		return (
			<React.Fragment>
				<Divider horizontal>
					<Header>
						<Link to={`/crew${markdownRemark.fields.slug}`}>
							{crew.name} <Rating defaultRating={crew.max_rarity} maxRating={5} icon='star' size='large' disabled />
						</Link>
					</Header>
				</Divider>
				<Grid columns={2}>
					<Grid.Row>
						<Grid.Column width={4}>
							{crew.series && <Image src={`/media/series/${crew.series}.png`} size='small' />}
							<Link to={`/crew${markdownRemark.fields.slug}`}>
								<Image src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlFullBody}`} size='small' />
							</Link>
						</Grid.Column>
						<Grid.Column width={12}>
							<CommonCrewData crew={crew} markdownRemark={markdownRemark} />

							<div dangerouslySetInnerHTML={{ __html: markdownRemark.html }} />
						</Grid.Column>
					</Grid.Row>
				</Grid>
			</React.Fragment>
		);
	}
Example #3
Source File: behold.tsx    From website with MIT License 5 votes vote down vote up
render() {
		if (this.state.allcrew.length === 0) {
			return (
				<Layout title='Behold helper / crew comparison'>
					<div className='ui medium centered text active inline loader'>Loading data...</div>
				</Layout>
			);
		}

		let peopleToShow = [...this.state.peopleList];
		if (this.state.minRarity) {
			peopleToShow = peopleToShow.filter((crew) => crew.max_rarity >= this.state.minRarity);
		}

		return (
			<Layout title='Behold helper / crew comparison'>
				<Header as='h2'>Behold helper / crew comparison</Header>
				<Form>
					<Form.Group>
						<Dropdown
							clearable
							fluid
							multiple
							search
							selection
							closeOnChange
							options={peopleToShow}
							placeholder='Search for crew to compare'
							label='Behold crew'
							value={this.state.currentSelectedItems}
							onChange={(e, { value }) => this._selectionChanged(value)}
						/>
						<Form.Field
							control={Dropdown}
							placeholder={this.state.minRarity ? `Minimum rarity: ${this.state.minRarity}` : `Minimum rarity`}
							selection
							options={rarityOptions}
							value={this.state.minRarity}
							onChange={(e, { value }) => this.setState({ minRarity: value })}
						/>
					</Form.Group>
				</Form>
				{this.state.currentSelectedItems?.length > 0 && (
					<Button compact icon='add user' color='green' content='Preview all in your roster' onClick={() => { this._addProspects(); }} />
				)}

				<Divider horizontal hidden />

				<Grid columns={3} stackable centered padded divided>
					{this.state.entries.map((entry, idx) => (
						<Grid.Column key={idx}>
							<Header as='h5'>
								<Link to={`/crew/${entry.crew.symbol}/`}>
									{entry.crew.name}{' '}
									<Rating defaultRating={entry.crew.max_rarity} maxRating={entry.crew.max_rarity} icon='star' size='small' disabled />
								</Link>
							</Header>
							<CommonCrewData compact={true} crewDemands={entry.crewDemands} crew={entry.crew} markdownRemark={entry.markdownRemark} roster={this.state.roster}/>
							{entry.markdown && (
								<React.Fragment>
									<div dangerouslySetInnerHTML={{ __html: entry.markdown }} />
									<div style={{ marginTop: '1em' }}>
										<a href={`https://www.bigbook.app/crew/${entry.crew.symbol}`}>View {entry.crew.name} on Big Book</a>
									</div>
								</React.Fragment>
							)}
						</Grid.Column>
					))}
				</Grid>
			</Layout>
		);
	}
Example #4
Source File: bigbook2.tsx    From website with MIT License 5 votes vote down vote up
renderCrew(entry): JSX.Element {
		let markdownRemark = {
			frontmatter: {
				bigbook_tier: entry.bcrew.bigbook_tier,
				events: entry.bcrew.events,
				in_portal: entry.bcrew.in_portal
			}
		};

		return (
			<Grid.Column key={entry.crew.symbol}>
				<div style={{ textAlign: 'center', fontSize: '0.75em' }}>
					<Popup
						trigger={
							<Image
								src={`${process.env.GATSBY_ASSETS_URL}${entry.crew.imageUrlPortrait}`}
								size='small'
								style={{
									borderColor: CONFIG.RARITIES[entry.crew.max_rarity].color,
									borderWidth: '1px',
									borderRadius: '4px',
									borderStyle: 'solid'
								}}
							/>
						}
						wide='very'
						on='click'>
						<Header>
							<Link to={`/crew/${entry.crew.symbol}/`}>
								{entry.crew.name}{' '}
								<Rating rating={entry.crew.max_rarity} maxRating={entry.crew.max_rarity} icon='star' size='large' disabled />
							</Link>
						</Header>
						<CommonCrewData crew={entry.crew} markdownRemark={markdownRemark} />

						<div dangerouslySetInnerHTML={{ __html: marked(entry.bcrew.markdownContent) }} />
					</Popup>
					<Link to={`/crew/${entry.crew.symbol}/`}>{entry.crew.name}</Link>
				</div>
			</Grid.Column>
		);
	}
Example #5
Source File: crewchallenge.tsx    From website with MIT License 5 votes vote down vote up
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: index.tsx    From website with MIT License 5 votes vote down vote up
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 #7
Source File: citeoptimizer.tsx    From website with MIT License 4 votes vote down vote up
renderTable(data, training = true) {
		const createStateAccessors = (name) => [
			this.state[name],
			(value) => this.setState((prevState) => { prevState[name] = value; return prevState; })
		];
		const [paginationPage, setPaginationPage] = createStateAccessors(training ? 'trainingPage' : 'citePage');
		const [otherPaginationPage, setOtherPaginationPage] = createStateAccessors(training ? 'citePage' : 'trainingPage');
		const [paginationRows, setPaginationRows] = createStateAccessors('paginationRows');

		const baseRow = (paginationPage - 1) * paginationRows;
		const totalPages = Math.ceil(data.length / paginationRows);

		return (
			<Table sortable celled selectable striped collapsing unstackable compact="very">
				<Table.Header>
					<Table.Row>
						<Table.HeaderCell>Rank</Table.HeaderCell>
						<Table.HeaderCell>Crew</Table.HeaderCell>
						<Table.HeaderCell>Rarity</Table.HeaderCell>
						<Table.HeaderCell>Final EV</Table.HeaderCell>
						{!training &&
						<React.Fragment>
							<Table.HeaderCell>Remaining EV</Table.HeaderCell>
							<Table.HeaderCell>EV Per Citation</Table.HeaderCell>
						</React.Fragment>
						}
						<Table.HeaderCell>Voyages Improved</Table.HeaderCell>
					</Table.Row>
				</Table.Header>
				<Table.Body>
					{data.slice(baseRow, baseRow + paginationRows).map((row, idx) => {
						const crew = this.props.playerData.player.character.crew.find(c => c.name == row.name);

						return (
							<Table.Row>
								<Table.Cell>{baseRow + idx + 1}</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={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
										</div>
										<div style={{ gridArea: 'stats' }}>
											<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>{crew.name}</span>
										</div>
									</div>
								</Table.Cell>
								<Table.Cell>
									<Rating icon='star' rating={crew.rarity} maxRating={crew.max_rarity} size='large' disabled />
								</Table.Cell>
								<Table.Cell>
									{Math.ceil(training ? row.addedEV : row.totalEVContribution)}
								</Table.Cell>
								{
									!training &&
									<React.Fragment>
										<Table.Cell>
											{Math.ceil(row.totalEVRemaining)}
										</Table.Cell>
										<Table.Cell>
											{Math.ceil(row.evPerCitation)}
										</Table.Cell>
									</React.Fragment>
								}
								<Table.Cell>
									<Popup trigger={<b>{row.voyagesImproved.length}</b>} content={row.voyagesImproved.join(', ')} />
								</Table.Cell>
							</Table.Row>
						);
					})}
				</Table.Body>
				<Table.Footer>
					<Table.Row>
						<Table.HeaderCell colSpan={9}>
							<Pagination
								totalPages={totalPages}
								activePage={paginationPage}
								onPageChange={(event, { activePage }) => setPaginationPage(activePage as number)}
							/>
							<span style={{ paddingLeft: '2em' }}>
								Rows per page:{' '}
								<Dropdown
									inline
									options={pagingOptions}
									value={paginationRows}
									onChange={(event, { value }) => {
										setPaginationPage(1);
										setOtherPaginationPage(1);
										setPaginationRows(value as number);
									}}
								/>
							</span>
						</Table.HeaderCell>
					</Table.Row>
				</Table.Footer>
			</Table>
		);
	}
Example #8
Source File: collectionstool.tsx    From website with MIT License 4 votes vote down vote up
CrewTable = (props: CrewTableProps) => {
	const { playerCollections, collectionCrew, collectionsFilter, setCollectionsFilter } = props;

	const [showPortalOnly, setShowPortalOnly] = useStateWithStorage('collectionstool/showPortalOnly', false);

	const tableConfig: ITableConfigRow[] = [
		{ width: 2, column: 'name', title: 'Crew' },
		{ width: 1, column: 'max_rarity', title: 'Rarity', reverse: true, tiebreakers: ['highest_owned_rarity'] },
		{ width: 1, column: 'unmaxedIds.length', title: 'Collections', reverse: true },
		{ width: 3, column: 'immortalRewards.length', title: <span>Immortal Rewards <Popup trigger={<Icon name='help' />} content='Rewards you can claim if you immortalize this crew right now' /></span>, reverse: true }
	];

	const collectionsOptions = playerCollections.filter(collection => collection.milestone.goal > 0).sort((a, b) => a.name.localeCompare(b.name)).map(collection => {
		return {
			key: collection.id,
			value: collection.id,
			text: collection.name + ' (' + collection.progress + ' / ' + collection.milestone.goal + ')'
		};
	});

	return (
		<React.Fragment>
			<Header as='h4'>Collection Crew</Header>
			<p>Search for crew that will help you make progress on collections and see what rewards you could claim by immortalizing certain crew right now. Note: maxed collections and immortalized crew will not be shown in this table.</p>
			<div style={{ margin: '1em 0' }}>
				<Form.Field
					placeholder='Filter by collections'
					control={Dropdown}
					clearable
					multiple
					search
					selection
					options={collectionsOptions}
					value={collectionsFilter}
					onChange={(e, { value }) => setCollectionsFilter(value) }
					closeOnChange
				/>
			</div>
			<div style={{ margin: '1em 0' }}>
				<Form.Group inline>
					<Form.Field
						control={Checkbox}
						label='Only show crew in portal'
						checked={showPortalOnly}
						onChange={(e, { checked }) => setShowPortalOnly(checked)}
					/>
				</Form.Group>
			</div>
			<SearchableTable
				id='collections/crew'
				data={collectionCrew}
				config={tableConfig}
				renderTableRow={(crew, idx) => renderCrewRow(crew, idx)}
				filterRow={(crew, filters, filterType) => showThisCrew(crew, filters, filterType)}
			/>
		</React.Fragment>
	);

	function showThisCrew(crew: any, filters: [], filterType: string): boolean {
		if (crew.immortal) return false;
		if (showPortalOnly && !crew.in_portal) return false;
		if (collectionsFilter.length > 0) {
			let hasAllCollections = true;
			for (let i = 0; i < collectionsFilter.length; i++) {
				if (!crew.unmaxedIds.includes(collectionsFilter[i])) {
					hasAllCollections = false;
					break;
				}
			}
			if (!hasAllCollections) return false;
		}
		return crewMatchesSearchFilter(crew, filters, filterType);
	}

	function renderCrewRow(crew: any, idx: number): JSX.Element {
		const unmaxed = crew.unmaxedIds.map(id => { return playerCollections.find(pc => pc.id === id) });
		const tabledProgress = unmaxed.sort((a, b) => a.needed - b.needed).map(collection => {
			return (
				<tr key={collection.id}>
					<td style={{ whiteSpace: 'nowrap', fontSize: '.95em' }}>{collection.name}</td>
					<td style={{ textAlign: 'right', fontSize: '.95em' }}>{collection.progress} / {collection.milestone.goal}</td>
				</tr>
			);
		});

		return (
			<Table.Row key={crew.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}${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.highest_owned_rarity} maxRating={crew.max_rarity} size='large' disabled />
				</Table.Cell>
				<Table.Cell>
					{tabledProgress && (
						<table style={{ width: '100%' }}>
							<tbody>{tabledProgress}</tbody>
						</table>
					)}
				</Table.Cell>
				<Table.Cell textAlign='center'>
					<RewardsGrid rewards={crew.immortalRewards} />
				</Table.Cell>
			</Table.Row>
		);
	}

	function descriptionLabel(crew: any): JSX.Element {
		if (crew.immortal) {
			return (
				<div>
					<Icon name='snowflake' /> <span>{crew.immortal} frozen</span>
				</div>
			);
		} else {
			return (
				<div>
					{crew.highest_owned_rarity > 0 && (<span>Level {crew.highest_owned_level}</span>)}
				</div>
			);
		}
	}
}
Example #9
Source File: commoncrewdata.tsx    From website with MIT License 4 votes vote down vote up
render() {
		const { markdownRemark, crew, compact, crewDemands, roster } = this.props;

		let panels = [
			{
				index: 0,
				key: 0,
				title: getCoolStats(crew, false),
				content: { content: <div style={{ paddingBottom: '1em' }}>{this.renderOtherRanks(crew)}</div> }
			}
		];

		if (roster && roster.length > 0) {
			panels.push(
				{
					index: 1,
					key: 1,
					title: this.rosterComparisonTitle(crew, roster),
					content: { content: <div style={{ paddingBottom: '1em' }}>{this.renderOtherRanks(crew, roster)}</div> }
				});
		}

		return (
			<React.Fragment>
				{compact ? (
					<Segment>
						<Grid columns={2}>
							<Grid.Column width={4}>
								<Image src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlFullBody}`} size="tiny" />
							</Grid.Column>
							<Grid.Column width={12}>
								<CrewStat
									skill_name="security_skill"
									data={crew.base_skills.security_skill}
									scale={compact ? 0.75 : 1}
								/>
								<CrewStat skill_name="command_skill" data={crew.base_skills.command_skill} scale={compact ? 0.75 : 1} />
								<CrewStat
									skill_name="diplomacy_skill"
									data={crew.base_skills.diplomacy_skill}
									scale={compact ? 0.75 : 1}
								/>
								<CrewStat skill_name="science_skill" data={crew.base_skills.science_skill} scale={compact ? 0.75 : 1} />
								<CrewStat
									skill_name="medicine_skill"
									data={crew.base_skills.medicine_skill}
									scale={compact ? 0.75 : 1}
								/>
								<CrewStat
									skill_name="engineering_skill"
									data={crew.base_skills.engineering_skill}
									scale={compact ? 0.75 : 1}
								/>
							</Grid.Column>
						</Grid>
					</Segment>
				) : (
						<Segment>
							<CrewStat skill_name="security_skill" data={crew.base_skills.security_skill} scale={compact ? 0.75 : 1} />
							<CrewStat skill_name="command_skill" data={crew.base_skills.command_skill} scale={compact ? 0.75 : 1} />
							<CrewStat skill_name="diplomacy_skill" data={crew.base_skills.diplomacy_skill} scale={compact ? 0.75 : 1} />
							<CrewStat skill_name="science_skill" data={crew.base_skills.science_skill} scale={compact ? 0.75 : 1} />
							<CrewStat skill_name="medicine_skill" data={crew.base_skills.medicine_skill} scale={compact ? 0.75 : 1} />
							<CrewStat
								skill_name="engineering_skill"
								data={crew.base_skills.engineering_skill}
								scale={compact ? 0.75 : 1}
							/>
						</Segment>
					)}

				{crew.skill_data && crew.skill_data.length > 0 && (
					<Accordion
						defaultActiveIndex={-1}
						panels={[
							{
								index: 0,
								key: 0,
								title: 'Other fuse levels',
								content: {
									content: (
										<Segment.Group raised>
											{crew.skill_data.map((sk: any, idx: number) => (
												<Segment key={idx}>
													<Rating
														defaultRating={sk.rarity}
														maxRating={crew.max_rarity}
														icon="star"
														size="small"
														disabled
													/>
													<CrewStat skill_name="security_skill" data={sk.base_skills.security_skill} scale={0.75} />
													<CrewStat skill_name="command_skill" data={sk.base_skills.command_skill} scale={0.75} />
													<CrewStat skill_name="diplomacy_skill" data={sk.base_skills.diplomacy_skill} scale={0.75} />
													<CrewStat skill_name="science_skill" data={sk.base_skills.science_skill} scale={0.75} />
													<CrewStat skill_name="medicine_skill" data={sk.base_skills.medicine_skill} scale={0.75} />
													<CrewStat
														skill_name="engineering_skill"
														data={sk.base_skills.engineering_skill}
														scale={0.75}
													/>
												</Segment>
											))}
										</Segment.Group>
									)
								}
							}
						]}
					/>
				)}

				{crew.flavor && !compact && <p>{crew.flavor}</p>}

				{compact && (
					<div style={{ textAlign: 'center' }}>
						<StatLabel title="Voyage rank" value={crew.ranks.voyRank} />
						<StatLabel title="Gauntlet rank" value={crew.ranks.gauntletRank} />
						<StatLabel title="Big book tier" value={formatTierLabel(markdownRemark.frontmatter.bigbook_tier)} />
						{markdownRemark.frontmatter.events !== null && (
							<StatLabel title="Events" value={markdownRemark.frontmatter.events} />
						)}
					</div>
				)}

				{!compact && (
					<>
					<Statistic.Group size="tiny">
						{markdownRemark.frontmatter.events !== null && (
							<Statistic>
								<Statistic.Label>Events</Statistic.Label>
								<Statistic.Value>{markdownRemark.frontmatter.events}</Statistic.Value>
							</Statistic>
						)}
						<Statistic>
							<Statistic.Label>Big Book Tier</Statistic.Label>
							<Statistic.Value>{formatTierLabel(markdownRemark.frontmatter.bigbook_tier)}</Statistic.Value>
						</Statistic>
						<Statistic>
							<Statistic.Label>CAB Rating <CABExplanation /></Statistic.Label>
							<Statistic.Value>{crew.cab_ov ?? 'None'}</Statistic.Value>
						</Statistic>
						{!compact && markdownRemark.frontmatter.in_portal !== null && (
							<Statistic color={markdownRemark.frontmatter.in_portal ? 'green' : 'red'}>
								<Statistic.Label>Portal</Statistic.Label>
								<Statistic.Value>{markdownRemark.frontmatter.in_portal ? 'YES' : 'NO'}</Statistic.Value>
							</Statistic>
						)}
						</Statistic.Group>
						<Statistic.Group style={{ paddingBottom: '2em' }} size="tiny">
						<Statistic>
							<Statistic.Label>CAB Rank <CABExplanation /></Statistic.Label>
							<Statistic.Value>{crew.cab_ov_rank ? rankLinker(false, crew.cab_ov_rank, crew.symbol, 'cab_ov', 'descending', 'rarity:'+crew.max_rarity) : 'None'}</Statistic.Value>
						</Statistic>
						<Statistic>
							<Statistic.Label>Voyage Rank</Statistic.Label>
							<Statistic.Value>{rankLinker(false, crew.ranks.voyRank, crew.symbol, 'ranks.voyRank')}</Statistic.Value>
						</Statistic>
						<Statistic>
							<Statistic.Label>Gauntlet Rank</Statistic.Label>
							<Statistic.Value>{rankLinker(false, crew.ranks.gauntletRank, crew.symbol, 'ranks.gauntletRank')}</Statistic.Value>
						</Statistic>
					</Statistic.Group>
					</>
				)}

				{crewDemands && (
					<p>
						<b>{crewDemands.factionOnlyTotal}</b>
						{' faction items, '}
						<span style={{ display: 'inline-block' }}>
							<img src={`${process.env.GATSBY_ASSETS_URL}atlas/energy_icon.png`} height={14} />
						</span>{' '}
						<b>{crewDemands.totalChronCost}</b>
						{', '}
						<span style={{ display: 'inline-block' }}>
							<img src={`${process.env.GATSBY_ASSETS_URL}currency_sc_currency_0.png`} height={16} />
						</span>{' '}
						<b>{crewDemands.craftCost}</b>
					</p>
				)}

				<Accordion
					exclusive={false}
					panels={panels}
				/>

				<p>
					<b>Traits: </b>
					{crew.traits_named
						.map(trait => (
							<a key={trait} href={`/?search=trait:${trait}`}>
								{trait}
							</a>
						))
						.reduce((prev, curr) => [prev, ', ', curr])}
					{', '}
					{crew.traits_hidden
						.map(trait => (
							<a style={{ color: 'lightgray' }} key={trait} href={`/?search=trait:${trait}`}>
								{trait}
							</a>
						))
						.reduce((prev, curr) => [prev, ', ', curr])}
				</p>

				{crew.cross_fuse_targets && crew.cross_fuse_targets.symbol && (
					<p>
						Can cross-fuse with{' '}
						<Link to={`/crew/${crew.cross_fuse_targets.symbol}/`}>{crew.cross_fuse_targets.name}</Link>.
					</p>
				)}

				{crew.collections.length > 0 && (
					<p>
						<b>Collections: </b>
						{crew.collections
							.map(col => (
								<Link key={col} to={`/collections/#${encodeURIComponent(col)}`}>
									{col}
								</Link>
							))
							.reduce((prev, curr) => [prev, ', ', curr])}
					</p>
				)}

				<p>
					<b>Date added: </b>{new Date(crew.date_added).toLocaleDateString("en-US")} (<b>Obtained: </b>{crew.obtained})
				</p>

				{crew.nicknames && crew.nicknames.length > 0 && (
					<p>
						<b>Also known as: </b>
						{crew.nicknames
							.map((nick, idx) => (
							<span key={idx}>{nick.cleverThing}{nick.creator ? <> (coined by <i>{nick.creator}</i>)</> : ''}</span>
						))
						.reduce((prev, curr) => [prev, ', ', curr])}
					</p>
				)}
			</React.Fragment>
		);
	}
Example #10
Source File: crewretrieval.tsx    From website with MIT License 4 votes vote down vote up
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 #11
Source File: eventplanner.tsx    From website with MIT License 4 votes vote down vote up
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 #12
Source File: profile_crew.tsx    From website with MIT License 4 votes vote down vote up
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 #13
Source File: prospectpicker.tsx    From website with MIT License 4 votes vote down vote up
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 #14
Source File: item_info.tsx    From website with MIT License 4 votes vote down vote up
render() {
		const { errorMessage, item_data, items } = this.state;

		if (item_data === undefined || errorMessage !== undefined) {
			return (
				<Layout title='Item information'>
					<Header as="h4">Item information</Header>
					{errorMessage && (
						<Message negative>
							<Message.Header>Unable to load item information</Message.Header>
							<pre>{errorMessage.toString()}</pre>
						</Message>
					)}
					{!errorMessage && (
						<div>
							<Icon loading name="spinner" /> Loading...
						</div>
					)}
				</Layout>
			);
		}

		console.log(item_data);

		let bonusText = [];
		if (item_data.item.bonuses) {
			for (let [key, value] of Object.entries(item_data.item.bonuses)) {
				let bonus = CONFIG.STATS_CONFIG[Number.parseInt(key)];
				if (bonus) {
					bonusText.push(`+${value} ${bonus.symbol}`);
				} else {
					// TODO: what kind of bonus is this?
				}
			}
		}

		// TODO: share this code with equipment.ts
		let demands = [];
		if (item_data.item.recipe) {
			for (let iter of item_data.item.recipe.list) {
				let recipeEquipment = items.find(item => item.symbol === iter.symbol);
				demands.push({
					count: iter.count,
					symbol: iter.symbol,
					equipment: recipeEquipment,
					factionOnly: iter.factionOnly
				});
			}
		}

		return (
			<Layout title={item_data.item.name}>
				<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>
				<Header as="h3">
					{item_data.item.name}{' '}
					<Rating icon='star' rating={item_data.item.rarity} maxRating={item_data.item.rarity} size="large" disabled />
				</Header>
				<Image size="small" src={`${process.env.GATSBY_ASSETS_URL}${item_data.item.imageUrl}`} />

				<br />

				{bonusText.length > 0 && (
					<div>
						<p>Bonuses: {bonusText.join(', ')}</p>
						<br />
					</div>
				)}

				{item_data.item.recipe && item_data.item.recipe.list && (
					<div>
						<Header as="h4">Craft it for {item_data.item.recipe.craftCost} chrons using this recipe:</Header>
						<Grid columns={3} 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)' : ''}`}
											/>
										}
										header={
											<Link to={`/item_info?symbol=${entry.symbol}`}>
												{CONFIG.RARITIES[entry.equipment.rarity].name + ' ' + entry.equipment.name}
											</Link>
										}
										content={<ItemSources item_sources={entry.equipment.item_sources} />}
										on="click"
										wide
									/>
								</Grid.Column>
							))}
						</Grid>
					</div>
				)}

				{item_data.item.item_sources.length > 0 && (
					<div>
						<Header as="h4">Item sources</Header>
						<ItemSources item_sources={item_data.item.item_sources} />
						<br />
					</div>
				)}

				{item_data.crew_levels.length > 0 && (
					<div>
						<Header as="h4">Equippable by this crew:</Header>
						<Grid columns={3} padded>
							{item_data.crew_levels.map((entry, idx) => (
								<Grid.Column key={idx}>
									<Header
										style={{ display: 'flex' }}
										icon={
											<ItemDisplay
												src={`${process.env.GATSBY_ASSETS_URL}${entry.crew.imageUrlPortrait}`}
												size={60}
												maxRarity={entry.crew.max_rarity}
												rarity={entry.crew.max_rarity}
											/>
										}
										content={<Link to={`/crew/${entry.crew.symbol}/`}>{entry.crew.name}</Link>}
										subheader={`Level ${entry.level}`}
									/>
								</Grid.Column>
							))}
						</Grid>
					</div>
				)}

				{item_data.builds.length > 0 && (
					<div>
						<Header as="h4">Is used to build these</Header>
						<Grid columns={3} padded>
							{item_data.builds.map((entry, idx) => (
								<Grid.Column key={idx}>
									<Header
										style={{ display: 'flex', cursor: 'zoom-in' }}
										icon={
											<ItemDisplay
												src={`${process.env.GATSBY_ASSETS_URL}${entry.imageUrl}`}
												size={48}
												maxRarity={entry.rarity}
												rarity={entry.rarity}
											/>
										}
										content={
											<Link to={`/item_info?symbol=${entry.symbol}`}>
												{CONFIG.RARITIES[entry.rarity].name + ' ' + entry.name}
											</Link>
										}
									/>
								</Grid.Column>
							))}
						</Grid>
					</div>
				)}
			</Layout>
		);
	}
Example #15
Source File: crewpage.tsx    From website with MIT License 4 votes vote down vote up
render() {
		const { location } = this.props;
		const { markdownRemark, crewJson, site: { siteMetadata } } = this.props.data;
		if (crewJson.edges.length === 0) {
			return <span>Crew not found!</span>;
		}

		const { comments } = this.state;

		let hasBigBookEntry = markdownRemark && markdownRemark.frontmatter && markdownRemark.frontmatter.published;

		const userName = this._getCurrentUsername();

		const crew = crewJson.edges[0].node;
		return (
			<Layout narrowLayout={true}>
				<Helmet titleTemplate={siteMetadata.titleTemplate} defaultTitle={siteMetadata.defaultTitle}>
					<title>{crew.name}</title>
					<meta property='og:type' content='website' />
					<meta property='og:title' content={`${crew.name} - ${siteMetadata.defaultTitle}`} />
					<meta property='og:site_name' content='DataCore' />
					<meta property='og:image' content={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
					<meta property='og:description' content={markdownRemark.rawMarkdownBody.trim() || siteMetadata.defaultDescription} />
					<meta property='og:url' content={`${siteMetadata.baseUrl}${location.pathname}`} />
				</Helmet>
				<CrewFullEquipTree
					visible={this.state.modalVisible}
					items={this.state.items}
					crew={crew}
					onClosed={() => this.setState({ modalVisible: false })}
				/>
				<Grid columns={2}>
					<Grid.Row stretched>
						<Grid.Column width={16}>
							<Header>
								{crew.name} <Rating defaultRating={crew.max_rarity} maxRating={crew.max_rarity} icon='star' size='large' disabled />
							</Header>
						</Grid.Column>
					</Grid.Row>
					<Grid.Row>
						<Grid.Column width={4}>
							{crew.series && <Image src={`/media/series/${crew.series}.png`} size='small' />}
							<Image src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlFullBody}`} size='small' />
						</Grid.Column>
						<Grid.Column width={12}>
							<CommonCrewData crew={crew} markdownRemark={markdownRemark} />

							<div style={{ margin: '1em 0', textAlign: 'right' }}>
								<Button icon='add user' color='green' content='Preview in your roster' onClick={() => { this._addProspect(crew); }} />
							</div>

							{this.state.items.length > 0 ? (
								<React.Fragment>
									{this.renderEquipment(crew)}
									{this.renderEquipmentDetails(crew)}
									<Button
										onClick={() => this.setState({ modalVisible: true })}
										style={{ marginTop: '1em' }}
										content='Full equipment tree'
										icon='right arrow'
										labelPosition='right'
									/>
								</React.Fragment>
							) : (
									<div className='ui medium centered text active inline loader'>Loading items...</div>
								)}

							<Segment>
								<Header as='h4'>{crew.action.name}</Header>
								<p>
									Boosts {CONFIG.CREW_SHIP_BATTLE_BONUS_TYPE[crew.action.bonus_type]} by {crew.action.bonus_amount}
								</p>
								<p>
									Initialize: {crew.action.initial_cooldown}s, Cooldown: {crew.action.cooldown}s, Duration: {crew.action.duration}s
									</p>
								{crew.action.limit && <p>Uses Per Battle: {crew.action.limit}</p>}

								{crew.action.ability && (
									<p>
										Bonus ability:
										{CONFIG.CREW_SHIP_BATTLE_ABILITY_TYPE[crew.action.ability.type].replace('%VAL%', crew.action.ability.amount)}{' '}
										{crew.action.ability.condition > 0 && (
											<span>Trigger: {CONFIG.CREW_SHIP_BATTLE_TRIGGER[crew.action.ability.condition]}</span>
										)}
									</p>
								)}

								<p>
									<b>Accuracy:</b> +{crew.ship_battle.accuracy} <b>Crit Bonus:</b> +{crew.ship_battle.crit_bonus}{' '}
									{crew.ship_battle.crit_chance && (
										<span>
											<b>Crit Rating:</b> +{crew.ship_battle.crit_chance}{' '}
										</span>
									)}
									<b>Evasion:</b> +{crew.ship_battle.evasion}
								</p>
								{crew.action.penalty && (
									<p>
										Decrease {CONFIG.CREW_SHIP_BATTLE_BONUS_TYPE[crew.action.penalty.type]} by {crew.action.penalty.amount}
									</p>
								)}

								{this.renderChargePhases(crew.action, crew.action.charge_phases)}
							</Segment>
						</Grid.Column>
					</Grid.Row>
				</Grid>
				<Divider horizontal hidden />
				{hasBigBookEntry && (
					<React.Fragment>
						<div dangerouslySetInnerHTML={{ __html: markdownRemark.html }} />
						<div style={{ marginTop: '1em', textAlign: 'right' }}>
							-- <a href={`https://www.bigbook.app/crew/${crew.symbol}`}>The Big Book of Behold Advice</a>
						</div>
					</React.Fragment>
				)}
				{/*userName && (
						<div>
							<br />
							<p>Hello, {userName}. You can edit your comment below:</p>
							<SimpleMDE
								value={this.state.commentMarkdown}
								onChange={value => this._handleMarkDownChange(value)}
								options={{ hideIcons: ['fullscreen', 'guide', 'image', 'side-by-side'] }}
							/>
							<Button onClick={() => this._saveComment(crew.symbol, window.localStorage.getItem('token'))} content='Save comment' />
						</div>
					)}
					{comments && (
						<Comment.Group>
							<Header as='h3' dividing>
								Comments
							</Header>

							{comments.map(comment => (
								<Comment key={comment.id}>
									<Comment.Avatar src={comment.user.avatar || `${process.env.GATSBY_ASSETS_URL}crew_portraits_cm_empty_sm.png`} />
									<Comment.Content>
										<Comment.Author>{comment.user.loginUserName}</Comment.Author>
										<Comment.Metadata>
											<div>{comment.lastUpdate}</div>
										</Comment.Metadata>
										<Comment.Text>
											<div dangerouslySetInnerHTML={{ __html: marked(comment.markdown) }} />
										</Comment.Text>
									</Comment.Content>
								</Comment>
							))}
						</Comment.Group>
							)*/}
				<Divider horizontal hidden style={{ marginTop: '4em' }} />
				<ExtraCrewDetails
					crew_archetype_id={crew.archetype_id}
					max_rarity={crew.max_rarity}
					base_skills={crew.base_skills}
					traits={crew.traits} traits_hidden={crew.traits_hidden}
					unique_polestar_combos={crew.unique_polestar_combos}
				/>
			</Layout>
		);
	}