semantic-ui-react#Checkbox TypeScript Examples

The following examples show how to use semantic-ui-react#Checkbox. 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: index.tsx    From frontegg-react with MIT License 5 votes vote down vote up
SwitchToggle: FC<SwitchToggleProps> = (props) => {
  const ref = useRef<HTMLInputElement>(null);
  const { labels } = props;
  const { className, ...checkboxProps } = mapper(props);

  const toggle = (
    <Ref innerRef={ref}>
      <Checkbox className={classNames('fe-switch-toggle', className)} {...checkboxProps} />
    </Ref>
  );

  if (labels) {
    return (
      <div
        className={classNames('fe-switch-toggle__with_labels', {
          'fe-switch-toggle__active-left': !props.value,
          'fe-switch-toggle__active-right': props.value,
          'fe-switch-toggle__disabled': props.disabled || props.loading,
          'fe-switch-toggle__loading': props.loading,
        })}
      >
        <span
          className='fe-switch-toggle__label'
          onClick={() => {
            props.value && ref?.current?.querySelector('input')?.click();
          }}
        >
          {labels[0]}
        </span>
        {toggle}
        <span
          className='fe-switch-toggle__label'
          onClick={() => {
            !props.value && ref?.current?.querySelector('input')?.click();
          }}
        >
          {labels[1]}
        </span>
      </div>
    );
  }
  return toggle;
}
Example #2
Source File: voyagecalculator.tsx    From website with MIT License 5 votes vote down vote up
InputCrewOptions = (props: InputCrewOptionsProps) => {
	const { myCrew, updateConsideredCrew } = props;

	const [considerActive, setConsiderActive] = React.useState(false);
	const [considerFrozen, setConsiderFrozen] = React.useState(false);
	const [excludedCrew, setExcludedCrew] = React.useState([]);

	React.useEffect(() => {
		const consideredCrew = myCrew.filter(crewman => {
			if (!considerActive && crewman.active_status == 2)
				return false;

			if (!considerFrozen && crewman.immortal > 0)
				return false;

			if (excludedCrew && excludedCrew.includes(crewman.id))
				return false;

			return true;
		});
		updateConsideredCrew(consideredCrew);
	}, [considerActive, considerFrozen, excludedCrew]);

	const activeCount = myCrew.filter(crew => crew.active_status == 2).length;

	return (
		<React.Fragment>
			<Form.Group grouped>
				{activeCount > 0 && (
					<Form.Field
						control={Checkbox}
						label='Consider crew on active shuttles'
						checked={considerActive}
						onChange={(e, { checked }) => setConsiderActive(checked)}
					/>
				)}
				<Form.Field
					control={Checkbox}
					label='Consider frozen (vaulted) crew'
					checked={considerFrozen}
					onChange={(e, { checked }) => setConsiderFrozen(checked)}
				/>
			</Form.Group>
			<InputCrewExcluder myCrew={myCrew} excludedCrew={excludedCrew} updateExclusions={setExcludedCrew} showFrozen={considerFrozen} />
		</React.Fragment>
	);
}
Example #3
Source File: LobbyRoomList.tsx    From FLECT_Amazon_Chime_Meeting with Apache License 2.0 4 votes vote down vote up
render() {
        const gs = this.props as GlobalState
        const props = this.props as any
        const appState = props.appState as AppState

        const joinedMeetingIds = Object.keys(appState.joinedMeetings)


        const meetings = gs.meetings.map((meeting:MeetingInfo)=>{
            let joinButton

            const currentMeetingId = meeting.meetingId

            if(joinedMeetingIds.includes(currentMeetingId)){
                joinButton = (
                    <Button basic color="red" floated='right'
                        onClick={()=>{
                                console.log("CLICK LEAVE", meeting.meetingId)
                                props._leaveMeeting(meeting.meetingId, appState.joinedMeetings[currentMeetingId].meetingSession.configuration.credentials!.attendeeId)
                            }}                    
                    >
                        leave
                        <Icon name='chevron left' />
                    </Button>
                )
            }else{
                joinButton = (
                    <Button basic color="teal" floated='right'
                        onClick={()=>{
                                console.log("CLICK JOIN", meeting.meetingId)
                                props._joinMeeting(meeting.meetingId, gs)
                            }}>                    
                        join
                        <Icon name='chevron right' />
                    </Button>

                )
            }
            return (
                <Item>
                    {/* <Item.Image size='mini' src='/' /> */}
                    <Item.Content>

                        <Item.Header>
                            <Icon name="lock open" />
                            {meeting.meetingName}
                        </Item.Header>
                        <Item.Meta>
                            <div>
                                <b>Owner: </b> 
                                {meeting.metaData.UserName} 
                            </div>
                            <div>
                                <b>Open Date: </b> 
                                <span>{new Date(Number(meeting.metaData.StartTime)).toLocaleDateString()}</span>
                                <span>{new Date(Number(meeting.metaData.StartTime)).toLocaleTimeString()}</span>
                            </div>
                        </Item.Meta>
                        <Item.Extra>
                            {joinButton}
                        </Item.Extra>
                    </Item.Content>
                </Item>
            )
        })


        return (
            <div>
                <div>

                <Modal dimmer={'blurring'} size={'small'} open={this.state.open} onClose={this.close}>
                    <Modal.Header>Create New Meeting</Modal.Header>
                    <Modal.Content>
                        <Form>
                            <Form.Field>
                                <label>Room Name</label>
                                <input placeholder='name' ref={this.roomNameRef}/>
                            </Form.Field>
                            <Form.Field>
                                <Checkbox label='Use Passcode?(not implement)' checked={this.state.usePasscodeChecked}
                                    onClick={()=>{this.setState({ usePasscodeChecked: !this.state.usePasscodeChecked })}}
                                />
                                <label>Pass Code(not implement)</label>
                                <input placeholder='pass' ref={this.roomPassCodeRef}/>
                            </Form.Field>
                            <Form.Field>
                            <Checkbox label='Secret?(not implement)' checked={this.state.secretRoomCreateChecked}
                                    onClick={()=>{this.setState({ secretRoomCreateChecked: !this.state.secretRoomCreateChecked })}}
                                />
                            </Form.Field>
                        </Form>

                    </Modal.Content>
                    <Modal.Actions>
                        <Button negative onClick={this.close}>Cancel</Button>
                        <Button positive icon='checkmark' labelPosition='right' content='Create' onClick={this.createMeeting}/>
                    </Modal.Actions>
                </Modal>
                </div>
                <div>
                    <Segment padded>
                        <Divider horizontal>
                            <Header as='h2' textAlign="left">
                                Lobby
                            </Header>
                        </Divider>
                        <Header as='h3' textAlign="left">
                            Actions
                        </Header>
                        <List link>
                            <List.Item as='a' active onClick={(e, d)=>{this.show()}}>
                                <Header as='h5' textAlign={'left'}>
                                    <Icon name="chat"  active />New Meeting!
                                </Header>
                            </List.Item>
                            <List.Item as='a' active onClick={()=>{props.refreshRoomList()}}>
                                <Header as='h5' textAlign={'left'}>
                                    <Icon name="refresh"  active />Refresh Meeting List
                                </Header>
                            </List.Item>

                        </List>

                        <Divider hidden />


                        <Header as='h3' textAlign="left">
                            Meetings
                        </Header>
  
                        <div>
                            <Item.Group >
                                {meetings}

                            </Item.Group>                            

                        </div>
                    </Segment>
                </div>
            </div>
        )
    }
Example #4
Source File: collectionstool.tsx    From website with MIT License 4 votes vote down vote up
ProgressTable = (props: ProgressTableProps) => {
	const { playerCollections, filterCrewByCollection } = props;

	const [rewardFilter, setRewardFilter] = useStateWithStorage('collectionstool/rewardFilter', undefined);
	const [showMaxed, setShowMaxed] = useStateWithStorage('collectionstool/showMaxed', false);

	const tableConfig: ITableConfigRow[] = [
		{ width: 2, column: 'name', title: 'Collection' },
		{ width: 1, column: 'owned', title: 'Total Owned', reverse: true },
		{ width: 1, column: 'progressPct', title: 'Progress', reverse: true },
		{ width: 1, column: 'needed', title: 'Needed', tiebreakers: ['neededPct'] },
		{ width: 3, column: 'totalRewards', title: <span>Milestone Rewards <Popup trigger={<Icon name='help' />} content='Rewards you can claim after immortalizing the needed number of crew to reach the next milestone' /></span>, reverse: true }
	];

	// Rewards will test value against literal symbol string, except when prefixed by:
	//	= Regular expression against symbol, * Special test case
	const rewardOptions = [
		{ key: 'roAnyr', value: '*any', text: 'Any reward' },
		{ key: 'roBuff', value: '*buffs', text: 'Buffs' },
		{ key: 'roEner', value: 'energy', text: 'Chronitons' },
		{ key: 'roCred', value: 'nonpremium', text: 'Credits' },
		{ key: 'roCrew', value: '=_crew$', text: 'Crew' },
		{ key: 'roDili', value: 'premium_purchasable', text: 'Dilithium' },
		{ key: 'roHono', value: 'honor', text: 'Honor' },
		{ key: 'roMeri', value: 'premium_earnable', text: 'Merits' },
		{ key: 'roPort', value: '=premium_\\d+x_bundle', text: 'Portals' },
		{ key: 'roRepl', value: '=^replicator_fuel', text: 'Replicator Fuel' },
		{ key: 'roSche', value: '=_ship_schematic$', text: 'Ship schematics' },
		{ key: 'roBoos', value: '=minor_consumables_\\d+x_bundle', text: 'Shuttle boosts' },
		{ key: 'roTrai', value: '=_production_training$', text: 'Training' }
	];

	return (
		<React.Fragment>
			<p>Search for collections by name or description. You can also filter collections by milestone reward types. Click a row to view crew that will help you make progress on that collection.</p>
			<div style={{ margin: '.5em 0' }}>
				<Form>
					<Form.Group inline>
						<Form.Field
							control={Dropdown}
							placeholder='Filter by reward'
							selection
							clearable
							options={rewardOptions}
							value={rewardFilter}
							onChange={(e, { value }) => setRewardFilter(value)}
						/>
						<Form.Field
							control={Checkbox}
							label='Show maxed collections'
							checked={showMaxed}
							onChange={(e, { checked }) => setShowMaxed(checked)}
						/>
					</Form.Group>
				</Form>
			</div>
			<SearchableTable
				id='collections/progress'
				data={playerCollections}
				config={tableConfig}
				renderTableRow={(collection, idx) => renderCollectionRow(collection, idx)}
				filterRow={(collection, filter) => showCollectionRow(collection, filter)}
				explanation={
					<div>
						<p>Search for collections by name or trait.</p>
					</div>
				}
			/>
		</React.Fragment>
	);

	function showCollectionRow(collection: any, filters: []): boolean {
		if (!showMaxed && collection.milestone.goal == 0) return false;

		if (rewardFilter && rewardFilter != '*any') {
			let re;
			if (rewardFilter == '*buffs') {
				if (collection.milestone.buffs.length == 0) return false;
			}
			else if (rewardFilter.substr(0, 1) == '=') {
				re = new RegExp(rewardFilter.substr(1));
				if (!collection.milestone.rewards.find(reward => re.test(reward.symbol))) return false;
			}
			else if (!collection.milestone.rewards.find(reward => reward.symbol == rewardFilter)) {
				return false;
			}
		}

		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(collection.name, segment.text) ||
						matchesFilter(collection.simpleDescription, segment.text) ||
						collection.traits?.some(t => matchesFilter(t, segment.text));
					meetsAllConditions = meetsAllConditions && (segment.negated ? !segmentResult : segmentResult);
				}
			}
			if (meetsAllConditions) {
				meetsAnyCondition = true;
				break;
			}
		}

		return meetsAnyCondition;
	}

	function renderCollectionRow(collection: any, idx: number): JSX.Element {
		const rewards = collection.totalRewards > 0 ? collection.milestone.buffs.concat(collection.milestone.rewards) : [];

		return (
			<Table.Row key={collection.id} style={{ cursor: 'zoom-in' }} onClick={() => filterCrewByCollection(collection.id)}>
				<Table.Cell>
					<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}><Link to={`/collections/#${encodeURI(collection.name)}`}>{collection.name}</Link></span>
					<br/>{collection.simpleDescription}
				</Table.Cell>
				<Table.Cell textAlign='center'>{collection.owned} / {collection.crew.length}</Table.Cell>
				<Table.Cell textAlign='center'>{collection.milestone.goal > 0 ? `${collection.progress} / ${collection.milestone.goal}` : 'MAX'}</Table.Cell>
				<Table.Cell textAlign='center'>{collection.needed}</Table.Cell>
				<Table.Cell textAlign='center'>
					<RewardsGrid rewards={rewards} />
				</Table.Cell>
			</Table.Row>
		);
	}
}
Example #5
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 #6
Source File: crewretrieval.tsx    From website with MIT License 4 votes vote down vote up
PolestarFilterModal = (props: PolestarFilterModalProps) => {
	const { ownedPolestars, updateDisableds } = props;

	const [modalIsOpen, setModalIsOpen] = React.useState(false);
	const [disabledPolestars, setDisabledPolestars] = React.useState(props.disabledPolestars);

	// Recalculate combos only when modal gets closed
	React.useEffect(() => {
		if (!modalIsOpen && JSON.stringify(disabledPolestars) != JSON.stringify(props.disabledPolestars)) {
			updateDisableds([...disabledPolestars]);
		}
	}, [modalIsOpen]);

	const rarityIds = [14502, 14504, 14506, 14507, 14509];
	const skillIds = [14511, 14512, 14513, 14514, 14515, 14516];
	const grouped = [
		{
			title: "Rarity",
			polestars: [],
			anyDisabled: false
		},
		{
			title: "Skills",
			polestars: [],
			anyDisabled: false
		},
		{
			title: "Traits",
			polestars: [],
			anyDisabled: false
		},
	];
	ownedPolestars.forEach(p => {
		let group = 2;
		if (rarityIds.indexOf(p.id) !== -1) group = 0;
		if (skillIds.indexOf(p.id) !== -1) group = 1;
		grouped[group].polestars.push(p);
		if (disabledPolestars.indexOf(p.id) !== -1) grouped[group].anyDisabled = true;
	});

	return (
		<Modal
			open={modalIsOpen}
			onClose={() => setModalIsOpen(false)}
			onOpen={() => setModalIsOpen(true)}
			trigger={<Button><Icon name='filter' />{ownedPolestars.length-disabledPolestars.length} / {ownedPolestars.length}</Button>}
			size='large'
		>
			<Modal.Header>Filter Owned Polestars</Modal.Header>
			<Modal.Content scrolling>
				<Grid columns={4} stackable padded>
					{createFilterCheckboxes()}
				</Grid>
			</Modal.Content>
			<Modal.Actions>
				<Button positive onClick={() => setModalIsOpen(false)}>
					Close
				</Button>
			</Modal.Actions>
		</Modal>
	);

	function filterCheckbox(p: any): JSX.Element {
		return (
			<Grid.Column key={p.id}>
				<Checkbox
					toggle
					id={`polestar_filter_id_${p.id}`}
					label={`${p.short_name} (${p.quantity})`}
					checked={disabledPolestars.indexOf(p.id)===-1}
					onChange={(e) => checkOne(p.id, e.target.checked)}
				/>
			</Grid.Column>
		)
	}

	function filterCheckboxGroupHeader(t: string): JSX.Element {
		let group = grouped.find(group => group.title === t);
		let groupLink = group ? (<Button style={{ marginLeft: '1em' }} size='mini' onClick={() => checkGroup(t, group.anyDisabled)}>{group.anyDisabled ? 'Check' : 'Uncheck'} All</Button>): (<></>);
		return (
			<Grid.Column largeScreen={16} mobile={4} key={t}>
				<strong>{t}</strong> {groupLink}
			</Grid.Column>
		)
	}

	function createFilterCheckboxes(): JSX.Element[] {
		const checkboxes = [];
		grouped.map((group) => {
			if(group.polestars.length > 0) {
				checkboxes.push(filterCheckboxGroupHeader(group.title));
				group.polestars.map((polestar) => {
					checkboxes.push(filterCheckbox(polestar));
				});
			}
		});
		return checkboxes;
	}

	function checkOne(id: number, checked: boolean): void {
		handleFilterChange(id, checked);
		setDisabledPolestars([...disabledPolestars]);
	}

	function checkGroup(t: string, checkAll: boolean): void {
		let group = grouped.find(group => group.title === t);
		group.polestars.forEach(p => handleFilterChange(p.id, checkAll));
		setDisabledPolestars([...disabledPolestars]);
	}

	function handleFilterChange(id: number, checked: boolean): void {
		if(checked === true && disabledPolestars.indexOf(id) !== -1) {
			disabledPolestars.splice(disabledPolestars.indexOf(id), 1);
		}
		if(checked === false && disabledPolestars.indexOf(id) === -1) {
			disabledPolestars.push(id);
		}
	}
}
Example #7
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 #8
Source File: profile_charts.tsx    From website with MIT License 4 votes vote down vote up
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 #9
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 #10
Source File: shuttlehelper.tsx    From website with MIT License 4 votes vote down vote up
ShuttleHelper = (props: ShuttleHelperProps) => {
	const [shuttlers, setShuttlers] = useStateWithStorage(props.dbid+'/shuttlers/setups', new Shuttlers(), { rememberForever: true, onInitialize: variableReady });
	const [assigned, setAssigned] = useStateWithStorage(props.dbid+'/shuttlers/assigned', [], { rememberForever: true, onInitialize: variableReady });
	const [activeShuttles, setActiveShuttles] = useStateWithStorage('tools/activeShuttles', undefined);

	const [considerActive, setConsiderActive] = useStateWithStorage(props.helperId+'/considerActive', true);
	const [considerVoyage, setConsiderVoyage] = useStateWithStorage(props.helperId+'/considerVoyage', false);
	const [considerFrozen, setConsiderFrozen] = useStateWithStorage(props.helperId+'/considerFrozen', false);

	const [loadState, setLoadState] = React.useState(0);
	const [calcState, setCalcState] = React.useState(0);
	const [crewScores, setCrewScores] = React.useState(new CrewScores());
	const [activeStep, setActiveStep] = React.useState('missions');

	React.useEffect(() => {
		setCrewScores(new CrewScores());
	}, [props.crew, considerActive, considerVoyage, considerFrozen]);

	// Prune old shuttles from stored values, import open shuttles from player data
	React.useEffect(() => {
		if (loadState === 2) initializeShuttlers();
	}, [loadState]);

	// Prune assignments from other events, dismissed shuttles
	//	recommendShuttlers will prune assignments from other events anyway
	React.useEffect(() => {
		if (loadState === 2) {
			const assignable = shuttlers.shuttles.filter(shuttle => shuttle.groupId === props.groupId && shuttle.priority > 0).map(shuttle => shuttle.id);
			const newAssigned = assigned.filter(seat => assignable.includes(seat.shuttleId));
			setAssigned([...newAssigned]);
		}
	}, [shuttlers]);

	if (loadState < 2) return (<><Icon loading name='spinner' /> Loading...</>);

	if (calcState === 1) updateAssignments();

	return (
		<React.Fragment>
			<Form>
				<Form.Group grouped>
					<Form.Field
						control={Checkbox}
						label='Consider crew on active shuttles'
						checked={considerActive}
						onChange={(e, { checked }) => setConsiderActive(checked)}
					/>
					<Form.Field
						control={Checkbox}
						label='Consider crew on active voyage'
						checked={considerVoyage}
						onChange={(e, { checked }) => setConsiderVoyage(checked)}
					/>
					<Form.Field
						control={Checkbox}
						label='Consider frozen (vaulted) crew'
						checked={considerFrozen}
						onChange={(e, { checked }) => setConsiderFrozen(checked)}
					/>
				</Form.Group>
			</Form>
			<Step.Group>
				<Step active={activeStep === 'missions'} onClick={() => setActiveStep('missions')}>
					<Icon name='list' />
					<Step.Content>
						<Step.Title>Mission List</Step.Title>
						<Step.Description>Select your preferred missions</Step.Description>
					</Step.Content>
				</Step>
				<Step active={activeStep === 'assignments'} onClick={() => setActiveStep('assignments')}>
					<Icon name='rocket' />
					<Step.Content>
						<Step.Title>Shuttle Assignments</Step.Title>
						<Step.Description>See the best seats for your crew</Step.Description>
					</Step.Content>
				</Step>
			</Step.Group>
			{activeStep === 'missions' && (
				<MissionsList groupId={props.groupId}
					setActiveStep={setActiveStep} recommendShuttlers={recommendShuttlers}
					shuttlers={shuttlers} setShuttlers={setShuttlers} activeShuttles={activeShuttles} />
			)}
			{activeStep === 'assignments' && (
				<AssignmentsList groupId={props.groupId} crew={props.crew}
					setActiveStep={setActiveStep} recommendShuttlers={recommendShuttlers}
					shuttlers={shuttlers} setShuttlers={setShuttlers} assigned={assigned} setAssigned={setAssigned}
					crewScores={crewScores} updateCrewScores={updateCrewScores} />
			)}
		</React.Fragment>
	);

	function variableReady(keyName: string): void {
		setLoadState(prevState => Math.min(prevState + 1, 2));
	}

	function initializeShuttlers(): void {
		// Prune old shuttles
		const DAYS_TO_EXPIRE = 14;
		const expireDate = new Date();
		expireDate.setDate(expireDate.getDate()-DAYS_TO_EXPIRE);

		const oldIds = [];
		shuttlers.shuttles.forEach(shuttle => {
			if (!shuttle.groupId || shuttle.created < expireDate.getTime())
				oldIds.push(shuttle.id);
		});
		oldIds.forEach(shuttleId => {
			const shuttleNum = shuttlers.shuttles.findIndex(shuttle => shuttle.id === shuttleId);
			shuttlers.shuttles.splice(shuttleNum, 1);
		});

		// Import missions from player data (during an active event, if specified)
		if (activeShuttles && (!props.eventData || props.eventData.seconds_to_start === 0)) {
			activeShuttles.forEach(adventure => {
				if (!shuttlers.shuttles.find(shuttle => shuttle.id === adventure.symbol)) {
					const shuttle = new Shuttle(props.groupId, adventure.symbol, true);
					shuttle.name = adventure.name;
					shuttle.faction = adventure.faction_id;
					adventure.shuttles[0].slots.forEach(slot => {
						const seat = new ShuttleSeat();
						if (slot.skills.length > 1) {
							seat.operand = 'OR';
							seat.skillA = slot.skills[0];
							seat.skillB = slot.skills[1];
						}
						else {
							const skills = slot.skills[0].split(',');
							seat.skillA = skills[0];
							if (skills.length > 1) seat.skillB = skills[1];
						}
						shuttle.seats.push(seat);
					});
					shuttlers.shuttles.push(shuttle);
				}
			});
		}
		setShuttlers({...shuttlers});
		if (shuttlers.shuttles.filter(shuttle => shuttle.groupId === props.groupId && shuttle.priority > 0).length > 0)
			setActiveStep('assignments');
	}

	function recommendShuttlers(): void {
		if (calcState > 0) return;

		const todo = [], todoIds = [];
		shuttlers.shuttles.filter(shuttle => shuttle.groupId === props.groupId && shuttle.priority > 0).forEach(shuttle => {
			for (let seatNum = 0; seatNum < shuttle.seats.length; seatNum++) {
				const seat = shuttle.seats[seatNum];
				if (seat.skillA === '' && seat.skillB === '') continue;
				const ssId = getSkillSetId(seat);
				if (!crewScores.skillsets[ssId] && !todoIds.includes(ssId)) {
					todo.push(seat);
					todoIds.push(ssId);
				}
			}
		});
		if (todo.length > 0) {
			setCalcState(1);
			updateCrewScores(todo);
			return;
		}

		updateAssignments();
	}

	function updateCrewScores(todo: ShuttleSeat[] = [], doneCallback?: () => void): void {
		const newSkills = {};
		const newScores = [];

		for (let i = 0; i < props.crew.length; i++) {
			if (!considerActive && props.crew[i].active_status === 2)
				continue;

			if (!considerVoyage && props.crew[i].active_status === 3)
				continue;

			if (!considerFrozen && props.crew[i].immortal > 0)
				continue;

			todo.forEach(seat => {
				const skillOperand = seat.operand;
				const primarySkill = seat.skillA;
				const secondarySkill = seat.skillB;
				const ssId = getSkillSetId(seat);

				let iHigherSkill = 0, iLowerSkill = 0;
				for (let skill in CONFIG.SKILLS) {
					if (skill !== primarySkill && skill !== secondarySkill) continue;
					if (props.crew[i][skill].core === 0) continue;

					let iMultiplier = 1;
					if (props.eventData?.featured.indexOf(props.crew[i].symbol) >= 0)
						iMultiplier = 3;
					else if (props.eventData?.bonus.indexOf(props.crew[i].symbol) >= 0)
						iMultiplier = 2;
					const iSkillScore = props.crew[i][skill].core*iMultiplier;

					if (iSkillScore > iHigherSkill) {
						iLowerSkill = iHigherSkill;
						iHigherSkill = iSkillScore;
					}
					else if (iSkillScore > iLowerSkill) {
						iLowerSkill = iSkillScore;
					}
				}

				let iSeatScore = 0;
				if (skillOperand === 'OR')
					iSeatScore = iHigherSkill;
				else
					iSeatScore = iHigherSkill+(iLowerSkill/4);

				const currentIndex = crewScores.ranked.findIndex(score => score.id === props.crew[i].id && score.ssId === ssId);
				if (currentIndex >= 0) crewScores.ranked.splice(currentIndex, 1);

				if (iSeatScore > 0) {
					const crewman = {
						id: props.crew[i].id,
						symbol: props.crew[i].symbol,
						name: props.crew[i].name,
						score: iSeatScore,
						ssId
					};
					if (!newSkills[ssId]) newSkills[ssId] = [];
					newSkills[ssId].push(crewman);
					newScores.push(crewman);
				}
			});
		}

		todo.forEach(seat => {
			const ssId = getSkillSetId(seat);
			crewScores.skillsets[ssId] = newSkills[ssId].sort((a, b) => b.score - a.score);
		});
		crewScores.ranked = crewScores.ranked.concat(newScores);
		crewScores.ranked.sort((a, b) => b.score - a.score);
		setCrewScores({...crewScores});
		if (doneCallback) doneCallback();
	}

	function updateAssignments(): void {
		const data = shuttlers.shuttles.slice()
			.filter(shuttle => shuttle.groupId === props.groupId && shuttle.priority > 0)
			.sort((a, b) => a.priority - b.priority);

		const seats = [];
		data.forEach(shuttle => {
			for (let seatNum = 0; seatNum < shuttle.seats.length; seatNum++) {
				const ssId = getSkillSetId(shuttle.seats[seatNum]);
				const newSeat = {
					shuttleId: shuttle.id,
					seatNum,
					ssId,
					assignedId: -1,
					assignedSymbol: '',
					seatScore: 0
				};
				const seated = assigned.find(seat => seat.shuttleId === shuttle.id && seat.seatNum === seatNum);
				if (seated?.locked) {
					newSeat.assignedId = seated.assignedId;
					newSeat.assignedSymbol = seated.assignedSymbol;
					newSeat.seatScore = seated.seatScore;
					newSeat.locked = true;
				}
				seats.push(newSeat);
			}
		});
		if (seats.length === 0) return;

		const scores = JSON.parse(JSON.stringify(crewScores.ranked));
		let iAssigned = 0;
		while (scores.length > 0 && iAssigned < seats.length) {
			const testScore = scores.shift();
			const alreadyAssigned = seats.find(seat => seat.assignedId === testScore.id);
			if (alreadyAssigned) continue;

			const openSeat = seats.find(seat => seat.ssId === testScore.ssId && seat.assignedId === -1);
			if (openSeat) {
				openSeat.assignedId = testScore.id;
				openSeat.assignedSymbol = testScore.symbol;
				openSeat.seatScore = testScore.score;
				iAssigned++;
			}
		}
		setAssigned([...seats]);
		setCalcState(0);
		setActiveStep('assignments');
	}
}
Example #11
Source File: voyagecalculator.tsx    From website with MIT License 4 votes vote down vote up
VoyageInput = (props: VoyageInputProps) => {
	const { voyageConfig, myCrew, allShips, useInVoyage } = props;

	const [bestShip, setBestShip] = React.useState(undefined);
	const [consideredCrew, setConsideredCrew] = React.useState([]);
	const [calculator, setCalculator] = React.useState(isMobile ? 'ussjohnjay' : 'iampicard');
	const [calcOptions, setCalcOptions] = React.useState({});
	const [telemetryOptOut, setTelemetryOptOut] = useStateWithStorage('telemetryOptOut', false, { rememberForever: true });
	const [requests, setRequests] = React.useState([]);
	const [results, setResults] = React.useState([]);

	React.useEffect(() => {
		// Note that allShips is missing the default ship for some reason (1* Constellation Class)
		//	This WILL break voyagecalculator if that's the only ship a player owns
		const consideredShips = [];
		allShips.filter(ship => ship.owned).forEach(ship => {
			const traited = ship.traits.find(trait => trait === voyageConfig.ship_trait);
			let entry = {
				ship: ship,
				score: ship.antimatter + (traited ? 150 : 0),
				traited: traited,
				bestIndex: Math.min(ship.index.left, ship.index.right)
			};
			consideredShips.push(entry);
		});
		consideredShips.sort((a, b) => {
			if (a.score === b.score) return a.bestIndex - b.bestIndex;
			return b.score - a.score;
		});
		setBestShip(consideredShips[0]);
		setRequests([]);
		setResults([]);
	}, [voyageConfig]);

	React.useEffect(() => {
		return function cleanup() {
			// Cancel active calculations when leaving page
			requests.forEach(request => {
				if (request.calcState == CalculatorState.InProgress)
					request.abort();
			});
		}
	}, []);

	// Scroll here when calculator started, finished
	const topAnchor = React.useRef(null);

	const calculators = CALCULATORS.helpers.map(helper => {
		return { key: helper.id, value: helper.id, text: helper.name };
	});
	calculators.push({ key: 'all', value: 'all', text: 'All calculators (slower)' });

	return (
		<React.Fragment>
			<div ref={topAnchor} />
			{renderBestShip()}
			{renderResults()}
			{requests.length > 0 && <Header as='h3'>Options</Header>}
			<Form>
				<InputCrewOptions myCrew={myCrew} updateConsideredCrew={setConsideredCrew} />
				<Form.Group inline>
					<Form.Field
						control={Select}
						label='Calculator'
						options={calculators}
						value={calculator}
						onChange={(e, { value }) => setCalculator(value)}
						placeholder='Select calculator'
					/>
					{CALCULATORS.fields.filter(field => field.calculators.includes(calculator) || calculator == 'all').map(field => (
						<Form.Field
							key={field.id}
							control={Select}	/* Only control allowed at the moment */
							label={field.name}
							options={field.options}
							value={calcOptions[field.id] ?? field.default}
							placeholder={field.description}
							onChange={(e, { value }) => setCalcOptions(prevOptions =>
								{
									const newValue = { [field.id]: value };
									return {...prevOptions, ...newValue};
								}
							)}
						/>
					))}
				</Form.Group>

				<Form.Group>
					<Form.Button primary onClick={() => startCalculation()}>
						Calculate best crew selection
					</Form.Button>
					{voyageConfig.state &&
						<Form.Button onClick={()=> useInVoyage()}>
							Return to in voyage calculator
						</Form.Button>
					}
				</Form.Group>
			</Form>
			<Message style={{ marginTop: '2em' }}>
				<Message.Content>
					<Message.Header>Privacy Notice</Message.Header>
					<p>We use anonymous statistics aggregated from voyage calculations to improve DataCore and power our <b><Link to='/hall_of_fame'>Voyage Hall of Fame</Link></b>.</p>
					<Form>
						<Form.Field
							control={Checkbox}
							label={<label>Permit DataCore to collect anonymous voyage stats</label>}
							checked={!telemetryOptOut}
							onChange={(e, { checked }) => setTelemetryOptOut(!checked) }
						/>
					</Form>
				</Message.Content>
			</Message>
		</React.Fragment>
	);

	function renderBestShip(): JSX.Element {
		if (!bestShip) return (<></>);

		const direction = bestShip.ship.index.right < bestShip.ship.index.left ? 'right' : 'left';
		const index = bestShip.ship.index[direction] ?? 0;

		return (
			<Card fluid>
				<Card.Content>
					<Image floated='left' src={`${process.env.GATSBY_ASSETS_URL}${bestShip.ship.icon.file.substr(1).replace('/', '_')}.png`} style={{ height: '4em' }} />
					<Card.Header>{bestShip.ship.name}</Card.Header>
					<p>best ship{bestShip.traited && (<span style={{ marginLeft: '1em' }}>{` +`}{allTraits.ship_trait_names[voyageConfig.ship_trait]}</span>)}</p>
					<p style={{ marginTop: '.5em' }}>Tap <Icon name={`arrow ${direction}`} />{index} time{index != 1 ? 's' : ''} on your voyage ship selection screen to select {bestShip.ship.name}.</p>
				</Card.Content>
			</Card>
		);
	}

	function renderResults(): JSX.Element {
		if (results.length == 0)
			return (<></>);

		const showPopup = (result) => <Popup basic content={<p>{result.result.postscript}</p>} trigger={<p>{result.name}</p>} />
		const panes = results.map(result => ({
			menuItem: { key: result.id, content: result.result ? showPopup(result) : result.name },
			render: () => (
				<VoyageResultPane result={result.result}
					requests={requests} requestId={result.requestId}
					calcState={result.calcState} abortCalculation={abortCalculation}
				/>
			)
		}));

		return (
			<React.Fragment>
				<Header as='h3'>Recommended Lineups</Header>
				<Tab menu={{ pointing: true }}
					panes={panes}
				/>
			</React.Fragment>
		);
	}

	function scrollToAnchor(): void {
		if (!topAnchor.current) return;
		topAnchor.current.scrollIntoView({
			behavior: 'smooth'
		}, 500);
	}

	function startCalculation(): void {
		const helperConfig = {
			voyageConfig, bestShip, consideredCrew, calcOptions,
			resultsCallback: handleResults,
			isMobile
		};
		CALCULATORS.helpers.forEach(helper => {
			if (helper.id == calculator || calculator == 'all') {
				const request = helper.helper(helperConfig);
				requests.push(request);
				results.push({
					id: request.id,
					requestId: request.id,
					name: 'Calculating...',
					calcState: CalculatorState.InProgress
				});
				request.start();
			}
		});
		setRequests([...requests]);
		setResults([...results]);
		scrollToAnchor();
	}

	function handleResults(requestId: string, reqResults: any[], calcState: number): void {
		reqResults.forEach((reqResult, idx) => {
			// Update existing pane with results
			if (idx == 0) {
				setResults(prevResults => {
					const result = prevResults.find(r => r.id == requestId);
					if (calcState == CalculatorState.Done) {
						result.name = formatTime(reqResult.estimate.refills[0].result);
						result.calcState = CalculatorState.Done;
						sendTelemetry(requestId, reqResult);
					}
					result.result = reqResult;
					return [...prevResults];
				});
			}
			// Add new panes if multiple results generated by this request
			else {
				setResults(prevResults => [...prevResults, {
					id: requestId+'-'+idx,
					requestId,
					name: formatTime(reqResult.estimate.refills[0].result),
					calcState: CalculatorState.Done,
					result: reqResult
				}]);
			}
		});
		if (calcState == CalculatorState.Done) scrollToAnchor();
	}

	function abortCalculation(requestId: string): void {
		const request = requests.find(r => r.id == requestId);
		if (request) {
			request.abort();
			setResults(prevResults => {
				const result = prevResults.find(prev => prev.id == requestId);
				if (result.result) {
					result.name = formatTime(result.result.estimate.refills[0].result);
					result.calcState = CalculatorState.Done;
				}
				else {
					const index = prevResults.findIndex(prev => prev.id == requestId);
					prevResults.splice(index, 1);
				}
				return [...prevResults];
			});
		}
	}

	function sendTelemetry(requestId: string, result: any): void {
		if (telemetryOptOut) return;
		const request = requests.find(r => r.id == requestId);
		const estimatedDuration = result.estimate.refills[0].result*60*60;
		try {
			fetch(`${process.env.GATSBY_DATACORE_URL}api/telemetry`, {
				method: 'post',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({
					type: 'voyageCalc',
					data: {
						voyagers: result.entries.map((entry) => entry.choice.symbol),
						estimatedDuration,
						calculator: request ? request.calculator : ''
					}
				})
			});
		}
		catch (err) {
			console.log('An error occurred while sending telemetry', err);
		}
	}
}
Example #12
Source File: playertools.tsx    From website with MIT License 4 votes vote down vote up
PlayerToolsPanes = (props: PlayerToolsPanesProps) => {
	const { playerData, strippedPlayerData, voyageData, eventData, activeCrew, dataSource,
			allCrew, allItems, requestShowForm, requestClearData } = props;

	const [showIfStale, setShowIfStale] = useStateWithStorage('tools/showStale', true);

	const [showShare, setShowShare] = useStateWithStorage(playerData.player.dbid+'/tools/showShare', true, { rememberForever: true, onInitialize: variableReady });
	const [profileAutoUpdate, setProfileAutoUpdate] = useStateWithStorage(playerData.player.dbid+'/tools/profileAutoUpdate', false, { rememberForever: true });
	const [profileUploaded, setProfileUploaded] = React.useState(false);
	const [profileUploading, setProfileUploading] = React.useState(false);
	const [profileShared, setProfileShared] = useStateWithStorage('tools/profileShared', false);

	const [varsReady, setVarsReady] = React.useState(false);
	const [activeTool, setActiveTool] = React.useState('voyage');

	React.useEffect(() => {
		if (dataSource == 'input' && profileAutoUpdate && !profileUploaded) {
			console.log('Uploading profile');
			shareProfile();
		}
	}, [profileAutoUpdate, strippedPlayerData]);

	const tools = playerTools;
	React.useEffect(() => {
		const urlParams = new URLSearchParams(window.location.search);
		if (urlParams.has('tool') && tools[urlParams.get('tool')])
			setActiveTool(urlParams.get('tool'));
	}, [window.location.search]);

	const StaleMessage = () => {
		const STALETHRESHOLD = 3;	// in hours
		if (showIfStale && new Date().getTime()-playerData.calc.lastModified.getTime() > STALETHRESHOLD*60*60*1000) {
			return (
				<Message
					warning
					icon='clock'
					header='Update your player data'
					content="It's been a few hours since you last updated your player data. We recommend that you update now to make sure our tools are providing you recent information about your crew."
					onDismiss={() => setShowIfStale(false)}
				/>
			);
		}
		else {
			return (<></>);
		}
	};

	const ShareMessage = () => {
		if (!showShare) return (<></>);

		// The option to auto-share profile only appears after a profile is uploaded or if previously set to auto-update
		const bShowUploaded = profileUploaded || profileAutoUpdate;

		return (
			<Message icon onDismiss={() => setShowShare(false)}>
				<Icon name='share alternate' />
				<Message.Content>
					<Message.Header>Share your player profile!</Message.Header>
					{!bShowUploaded && (
						<div>
							<p>
								Click here to{' '}
								<Button size='small' color='green' onClick={() => shareProfile()}>
									{profileUploading && <Icon loading name='spinner' />}share your profile
									</Button>{' '}
									and unlock more tools and export options for items and ships. More details:
							</p>
							<Message.List>
								<Message.Item>
									Once shared, the profile will be publicly accessible, will be accessible by your DBID link, and linked on related pages (such as fleet pages & event pages)
									</Message.Item>
								<Message.Item>
									There is no private information included in the player profile; information being shared is limited to:{' '}
									<b>captain name, level, vip level, fleet name and role, achievements, completed missions, your crew, items and ships.</b>
								</Message.Item>
							</Message.List>
						</div>
					)}
					{bShowUploaded && (
						<Form.Group>
							<p>
								Your profile was uploaded. Share the link:{' '}
								<a
									href={`${process.env.GATSBY_DATACORE_URL}profile/?dbid=${playerData.player.dbid}`}
									target='_blank'>{`${process.env.GATSBY_DATACORE_URL}profile/?dbid=${playerData.player.dbid}`}</a>
							</p>
							<Form.Field
								control={Checkbox}
								label='Automatically share profile after every import'
								checked={profileAutoUpdate}
								onChange={(e, { checked }) => setProfileAutoUpdate(checked)}
							/>
						</Form.Group>
					)}
				</Message.Content>
			</Message>
		);
	};

	if (!varsReady)
		return (<PlayerToolsLoading />);

	const PlayerLevelProgress = () => {
		const endingValue = playerData.player.character.xp_for_next_level - playerData.player.character.xp_for_current_level;
		const currentValue = playerData.player.character.xp - playerData.player.character.xp_for_current_level;
		const percent = (currentValue / endingValue) * 100;
		return (
		  <Progress
			percent={percent.toPrecision(3)}
			label={`Level ${playerData.player.character.level}: ${playerData.player.character.xp} / ${playerData.player.character.xp_for_next_level}`}
			progress
		  />
		);
	 };

	return (
		<Layout title='Player tools'>
			<Header as='h4'>Hello, {playerData.player.character.display_name}</Header>
			<PlayerLevelProgress />
			<StaleMessage />
			<Menu compact stackable>
				<Menu.Item>
					Last imported: {playerData.calc.lastModified.toLocaleString()}
				</Menu.Item>
				<Dropdown item text='Profile options'>
					<Dropdown.Menu>
						<Dropdown.Item onClick={() => requestShowForm(true)}>Update now...</Dropdown.Item>
						{!showShare && (<Dropdown.Item onClick={() => setShowShare(true)}>Share profile...</Dropdown.Item>)}
						<Dropdown.Item onClick={() => requestClearData()}>Clear player data</Dropdown.Item>
					</Dropdown.Menu>
				</Dropdown>
			  <Dropdown item text='Export'>
				<Dropdown.Menu>
					<Popup basic content='Download crew data as traditional comma delimited CSV file' trigger={
						<Dropdown.Item onClick={() => exportCrewTool()} content='Download CSV...' />
					} />
					<Popup basic content='Copy crew data to clipboard in Google Sheets format' trigger={
						<Dropdown.Item onClick={() => exportCrewToClipboard()} content='Copy to clipboard' />
					} />
				</Dropdown.Menu>
			</Dropdown>
			</Menu>

			<React.Fragment>
				<ShareMessage />
				<Header as='h3'>{tools[activeTool].title}</Header>
				{tools[activeTool].render(props)}
			</React.Fragment>
		</Layout>
	);

	function variableReady(keyName: string) {
		setVarsReady(true);
	}

	function shareProfile() {
		setProfileUploading(true);

		let jsonBody = JSON.stringify({
			dbid: playerData.player.dbid,
			player_data: strippedPlayerData
		});

		fetch(`${process.env.GATSBY_DATACORE_URL}api/post_profile`, {
			method: 'post',
			headers: {
				'Content-Type': 'application/json'
			},
			body: jsonBody
		}).then(() => {
			if (!profileAutoUpdate) window.open(`${process.env.GATSBY_DATACORE_URL}profile/?dbid=${playerData.player.dbid}`, '_blank');
			setProfileUploading(false);
			setProfileUploaded(true);
			setProfileShared(true);
		});
	}

	function exportCrewTool() {
		let text = exportCrew(playerData.player.character.crew.concat(playerData.player.character.unOwnedCrew));
		downloadData(`data:text/csv;charset=utf-8,${encodeURIComponent(text)}`, 'crew.csv');
	}

	function exportCrewToClipboard() {
		let text = exportCrew(playerData.player.character.crew.concat(playerData.player.character.unOwnedCrew), '\t');
		navigator.clipboard.writeText(text);
	}
}
Example #13
Source File: stats.tsx    From website with MIT License 4 votes vote down vote up
render() {
		let faction_items = [];
		let mostused_items = [];
		let mostused_traits = [];
		if (this.state.misc_stats) {
			faction_items = this.state.misc_stats.perFaction.map((pf: any) => ({
				Exclusive: pf.exclusive,
				'Not exclusive': pf.count - pf.exclusive,
				name: pf.name
			}));

			mostused_traits = this.state.misc_stats.perTrait.map((pt: any) => ({
				Count: pt.count,
				name: pt.name
			}));

			mostused_traits = this.state.includeHidden
				? mostused_traits
				: mostused_traits.filter((trait: any) => trait.name[0].toLowerCase() !== trait.name[0]);

			mostused_traits = mostused_traits.slice(0, 15);
			mostused_traits.reverse();

			let mostusedsrc = this.state.factionOnly
				? this.state.misc_stats.alldemands.filter((d: any) => d.factionOnly)
				: this.state.misc_stats.alldemands;

			mostused_items = mostusedsrc.slice(0, 15).map((pf: any) => ({
				Component: pf.symbol.endsWith('compon') ? pf.count : 0,
				Equipment: pf.symbol.endsWith('compon') ? 0 : pf.count,
				name: this._getItemName(pf.symbol)
			}));

			mostused_items.reverse();
		}

		return (
			<Layout title='Miscellaneous stats'>
				<Header as="h2">Miscellaneous stats</Header>
				<p>
					Contains miscellaneous information, statistics, breakdowns and charts. Stats are fresh and always
					automatically updated whenever new crew gets added.
					</p>

				{!this.state.misc_stats && (
					<div>
						<Icon loading name="spinner" /> Loading...
					</div>
				)}
				{this.state.misc_stats && (
					<div>
						<Header as="h3">Recipe needs for faction-only items</Header>
						<p>
							This breaks down all recipe trees for every equipment on every level of every crew, filtered to
							equipment that is only obtainable from a faction transmission.
							</p>
						<p>
							<i>Exclusive</i> means that the item can only be obtained from that particular faction;{' '}
							<i>Not exclusive</i> means that the item can be obtained from more than one faction.
							</p>
						<div style={{ height: '500px' }}>
							<ResponsiveBar
								data={faction_items}
								theme={themes.dark}
								keys={['Exclusive', 'Not exclusive']}
								indexBy="name"
								layout="horizontal"
								margin={{ top: 20, right: 130, bottom: 50, left: 170 }}
								padding={0.3}
								axisBottom={{
									legend: 'Number of items',
									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>

						<Header as="h3">Most used items</Header>
						<p>This breaks down all recipe trees and lists the most used 20 items.</p>
						<Checkbox
							label="Faction only"
							onChange={() => this.setState({ factionOnly: !this.state.factionOnly })}
							checked={this.state.factionOnly}
						/>
						<div style={{ height: '500px' }}>
							<ResponsiveBar
								data={mostused_items}
								theme={themes.dark}
								keys={['Component', 'Equipment']}
								indexBy="name"
								layout="horizontal"
								margin={{ top: 20, right: 130, bottom: 50, left: 200 }}
								padding={0.3}
								axisBottom={{
									legend: 'Number of times item is needed in crew equipment recipes',
									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>

						<Header as="h3">Skill distribution</Header>
						<p>This lists the total number of crew that have each skill.</p>
						<div style={{ height: '500px' }}>
							<ResponsiveBar
								data={this.state.skillDistrib}
								theme={themes.dark}
								keys={['Count']}
								indexBy="name"
								layout="horizontal"
								margin={{ top: 20, right: 130, bottom: 50, left: 170 }}
								padding={0.3}
								axisBottom={{
									legend: 'Number of crew with skill',
									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>

						<Header as="h3">Skill value distribution</Header>
						<p>This lists the total skill value across all crew.</p>
						<div style={{ height: '500px' }}>
							<ResponsiveBar
								data={this.state.skillValueDistrib}
								theme={themes.dark}
								keys={['Count']}
								indexBy="name"
								layout="horizontal"
								margin={{ top: 20, right: 130, bottom: 50, left: 170 }}
								padding={0.3}
								axisBottom={{
									legend: 'Total skill value',
									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>

						<Header as="h3">Most used traits</Header>
						<p>The most popular traits across all crew.</p>
						<Checkbox
							label="Include Hidden Traits"
							onChange={() => this.setState({ includeHidden: !this.state.includeHidden })}
							checked={this.state.includeHidden}
						/>
						<div style={{ height: '420px' }}>
							<ResponsiveBar
								data={mostused_traits}
								theme={themes.dark}
								keys={['Count']}
								indexBy="name"
								layout="horizontal"
								margin={{ top: 20, right: 130, bottom: 50, left: 200 }}
								padding={0.3}
								axisBottom={{
									legend: 'Number of times the trait shows up for a 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>
				)}
			</Layout>
		);
	}