semantic-ui-react#Form TypeScript Examples
The following examples show how to use
semantic-ui-react#Form.
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: SendTextAccordion.tsx From FLECT_Amazon_Chime_Meeting with Apache License 2.0 | 6 votes |
////////////////////////////////
/// UI
///////////////////////////////
generateAccordion = () =>{
const props = this.props as any
const appState = props.appState as AppState
const grid = (
<Accordion>
<Accordion.Title
active={this.state.open}
index={0}
onClick={()=>{this.handleClick()}}
>
<Icon name='dropdown' />
SendText
</Accordion.Title>
<Accordion.Content active={this.state.open}>
<Form onSubmit={(e)=>{props.sendText( appState.focusedMeeting, appState.joinedMeetings[appState.focusedMeeting].focusAttendeeId, this.state.message); this.setState({message: ""})}}>
<Form.Input placeholder='message' value={this.state.message} onChange={(e)=>{this.setState({message: e.target.value})}} />
</Form>
</Accordion.Content>
</Accordion>
)
return grid
}
Example #2
Source File: index.tsx From frontegg-react with MIT License | 6 votes |
render() {
const { children, inForm } = this.props;
const buttonProps = mapper(this.props);
let ButtonComponent: any = SemanticButton;
if (inForm) {
ButtonComponent = Form.Button;
}
return <ButtonComponent {...buttonProps}>{children}</ButtonComponent>;
}
Example #3
Source File: Entrance.tsx From FLECT_Amazon_Chime_Meeting with Apache License 2.0 | 6 votes |
render() {
return (
<div style={{ width: "60%", height: "100%", margin: "auto"}}>
<Form>
<Form.Field>
<label>User Name</label>
<input placeholder='name' ref={this.userNameRef}/>
</Form.Field>
<Form.Field>
<label>Code</label>
<input placeholder='code' ref={this.codeRef}/>
</Form.Field>
<Button type='submit' onClick={()=>this.login()}>Submit</Button>
</Form>
</div>
)
}
Example #4
Source File: LocalSettings.tsx From watchparty with MIT License | 6 votes |
SettingsModal = ({ trigger }: any) => (
<Modal trigger={trigger} basic closeIcon size="small">
<Header icon="setting" content="Settings" />
<Modal.Content>
<Form>
<TextArea rows={10} id="settings_textarea">
{window.localStorage.getItem('watchparty-setting') ||
JSON.stringify(getDefaultSettings(), null, 2)}
</TextArea>
</Form>
</Modal.Content>
<Modal.Actions>
<Button
color="green"
inverted
onClick={() => {
const newSetting = (document.getElementById(
'settings_textarea'
) as HTMLTextAreaElement)!.value;
try {
validateSettingsString(newSetting);
updateSettings(newSetting);
window.location.reload();
} catch (e) {
alert(e);
}
}}
>
<Icon name="checkmark" />
Save
</Button>
</Modal.Actions>
</Modal>
)
Example #5
Source File: Comments.tsx From communitymap-ui with Apache License 2.0 | 6 votes |
PostCommentWidget: React.FC<{
onComment: (comment: string) => Promise<any>;
}> = ({ onComment }) => {
const [comment, setComment] = useState<string | null>(null);
return (
<div
onClick={(e) => {
// when put into map item click in form input causes the item to expand
e.stopPropagation();
}}
>
<Form
onSubmit={(e) => {
e.preventDefault();
!!comment &&
onComment(comment)
.then(() => setComment(null))
.catch(reportError);
}}
>
<Form.Input
value={comment || ''}
placeholder="Your comment here"
action={<Button icon="send" />}
onChange={(e) => setComment(e.target.value)}
/>
</Form>
</div>
);
}
Example #6
Source File: DepositForm.tsx From multi-sig-wallet with MIT License | 5 votes |
DepositForm: React.FC<Props> = () => {
const {
state: { web3, account },
} = useWeb3Context();
const [input, setInput] = useState("");
const { pending, call } = useAsync<DepositParams, void>(
({ web3, account, value }) => deposit(web3, account, { value })
);
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
setInput(e.target.value);
}
async function onSubmit(_e: React.FormEvent<HTMLFormElement>) {
if (pending) {
return;
}
if (!web3) {
alert("No web3");
return;
}
const value = Web3.utils.toBN(input);
const zero = Web3.utils.toBN(0);
if (value.gt(zero)) {
const { error } = await call({
web3,
account,
value,
});
if (error) {
alert(`Error: ${error.message}`);
} else {
setInput("");
}
}
}
return (
<Form onSubmit={onSubmit}>
<Form.Field>
<Form.Input
placeholder="Amount to deposit wei"
type="number"
min={0}
value={input}
onChange={onChange}
/>
</Form.Field>
<Button color="green" disabled={pending} loading={pending}>
Deposit
</Button>
</Form>
);
}
Example #7
Source File: topmenu.tsx From website with MIT License | 5 votes |
render() {
const { user, password, loginDialogOpen, loggingIn, errorMessage, messageModalOpen } = this.state;
const { narrowLayout, children } = this.props;
const windowGlobal = typeof window !== 'undefined' && window;
let isLoggedIn = windowGlobal && window.localStorage && window.localStorage.getItem('token') && window.localStorage.getItem('username');
const userName = isLoggedIn ? window.localStorage.getItem('username') : '';
return (
<React.Fragment>
<NavBar narrowLayout={narrowLayout} onMessageClicked={() => this.setState({ messageModalOpen: true })}>
{children}
</NavBar>
<Modal open={loginDialogOpen} onClose={() => this._closeLoginDialog()} size='tiny'>
<Modal.Header>Log-in to your account</Modal.Header>
<Modal.Content>
<Grid textAlign='center' verticalAlign='middle'>
<Grid.Column style={{ maxWidth: 450 }}>
<Form size='large' loading={loggingIn}>
<Segment>
<Form.Input
fluid
icon='user'
iconPosition='left'
placeholder='Username'
value={user}
onChange={(e, { value }) => this.setState({ user: value })}
/>
<Form.Input
fluid
icon='lock'
iconPosition='left'
placeholder='Password'
type='password'
value={password}
onChange={(e, { value }) => this.setState({ password: value })}
/>
</Segment>
</Form>
{errorMessage && <Message error>{errorMessage}</Message>}
{!errorMessage && (
<Message>If you are an approved book editor, log in here to submit changes directly from the site.</Message>
)}
</Grid.Column>
</Grid>
</Modal.Content>
<Modal.Actions>
<Button content='Cancel' onClick={() => this._closeLoginDialog()} />
<Button positive content='Login' onClick={() => this._doLogin()} />
</Modal.Actions>
</Modal>
<Modal open={messageModalOpen} closeOnEscape={false} closeOnDimmerClick={false} onClose={() => this._closeMessageDialog()}>
<Modal.Header>The DataCore website and bot are in need of software engineers!</Modal.Header>
<Modal.Content>
<p>
We need your help! The project is <a href='https://github.com/stt-datacore'>open source</a> so we're open for contributions
from software engineers, designers, devops, testers and so on. Reach out on our{' '}
<a href='https://discord.gg/2SY8W7Aeme'>development Discord</a> if you're not sure where to start.
</p>
<p>
If you've always wanted a feature on DataCore, here's your chance to hack on the project and implement it yourself! Most of
the project is written in TypeScript, with node.js on the backend and React with Gatsby on the frontend.
</p>
</Modal.Content>
<Modal.Actions>
<Button icon='checkmark' onClick={() => this._closeMessageDialog()} content='Ok' />
</Modal.Actions>
</Modal>
</React.Fragment>
);
}
Example #8
Source File: voyagecalculator.tsx From website with MIT License | 5 votes |
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 #9
Source File: behold.tsx From website with MIT License | 5 votes |
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 #10
Source File: bridgecrew.tsx From website with MIT License | 5 votes |
render() {
if (this.state.allcrew.length === 0) {
return (
<Layout title='Bridge crew assembly'>
<div className='ui medium centered text active inline loader'>Loading data...</div>
</Layout>
);
}
let peopleToShow = [...this.state.peopleList];
return (
<Layout title='Bridge crew assembly'>
<Header as='h4'>Bridge crew assembly</Header>
<p>Assemble your bridge crew.</p>
<Form>
<Form.Group>
<Dropdown
clearable
fluid
multiple
search
selection
closeOnChange
options={peopleToShow}
placeholder='Select or search for crew'
label='Bridge crew'
value={this.state.currentSelectedItems}
onChange={(e, { value }) => this._selectionChanged(value)}
/>
</Form.Group>
</Form>
<Divider horizontal hidden />
<div style={{ height: '500px', overflow: 'hidden', textAlign: 'center', padding: '25px', backgroundColor: '#203147', border: '2px solid lightblue' }}>
<Header as='h3'>Bridge Crew</Header>
{this.state.entries.map((entry, idx) => (
<img src={`${process.env.GATSBY_ASSETS_URL}${entry.crew.imageUrlFullBody}`} style={{ height: '725px', margin: '0 -6.5%' }} />
))}
</div>
</Layout>
);
}
Example #11
Source File: Story.tsx From communitymap-ui with Apache License 2.0 | 5 votes |
AddNewStoryObject: React.FC<{
type: ObjectItemInput['type'];
onPost: (data: ObjectItemInput) => void;
}> = ({ type, onPost }) => {
const [state, setState] = useState({ valid_until: 12 * 60 } as any);
const onChange = (e: any) => {
const { name, value } = e.target;
console.debug(e.target.name, e.target.value);
setState({ ...state, [name]: value });
};
return (
<div className="add-new-story">
<h4>
<Icon name="edit outline" /> Create story
</h4>
<Form
onSubmit={(e) => {
e.preventDefault();
console.debug('submit', state);
const { topic, message, valid_until } = state;
onPost({
type,
title: topic || message,
description: message,
valid_until: dayjs().add(valid_until, 'minute').toISOString(),
});
}}
>
<Form.Input
autoComplete="off"
label="Subject"
name="topic"
onChange={onChange}
/>
<Form.TextArea
autoFocus
label="Story"
name="message"
onChange={onChange}
/>
<Form.Button primary>Post</Form.Button>
</Form>
</div>
);
}
Example #12
Source File: Chat.tsx From communitymap-ui with Apache License 2.0 | 5 votes |
AddNewChatObject: React.FC<{
type: ObjectItemInput['type'];
onPost: (data: ObjectItemInput) => void;
}> = ({ type, onPost }) => {
const [state, setState] = useState({ valid_until: 12 * 60 } as any);
const onChange = (e: any) => {
const { name, value } = e.target;
console.debug(e.target.name, e.target.value);
setState({ ...state, [name]: value });
};
return (
<div className="add-new-chat">
<h4>
<Icon name={type2icon(type)} /> {type2title(type)}
</h4>
<Form
onSubmit={(e) => {
e.preventDefault();
console.debug('submit', state);
const { topic, message, valid_until } = state;
onPost({
type,
title: topic || message,
description: message,
valid_until: dayjs().add(valid_until, 'minute').toISOString(),
});
}}
>
<Form.Input
autoComplete="off"
label="Topic (optional)"
name="topic"
onChange={onChange}
/>
<Form.TextArea
autoFocus
label="Message"
name="message"
onChange={onChange}
/>
<Form.Dropdown
options={validUntilOptions}
label="Valid for next"
selection
defaultValue={state.valid_until}
onChange={(e, { value }) =>
setState({ ...state, valid_until: value })
}
/>
<Form.Button primary>Post</Form.Button>
</Form>
</div>
);
}
Example #13
Source File: LoginModal.tsx From watchparty with MIT License | 5 votes |
render() {
const { closeLogin } = this.props;
return (
<Modal open={true} onClose={closeLogin as any}>
<Modal.Header>
{this.state.isCreateMode ? 'Create an account' : 'Login'}
</Modal.Header>
<Modal.Content>
{this.state.error && (
<Message color="red" header="Error" content={this.state.error} />
)}
<Form>
<Form.Field>
<label>Email</label>
<input
placeholder="Email"
value={this.state.email}
onChange={(e) => this.setState({ email: e.target.value })}
/>
</Form.Field>
<Form.Field>
<label>Password</label>
<input
type="password"
placeholder="Password"
value={this.state.password}
onChange={(e) => this.setState({ password: e.target.value })}
/>
</Form.Field>
{!this.state.isCreateMode && (
<div>
Don't have an account?{' '}
<button
type="button"
className="linkButton"
onClick={() => this.setState({ isCreateMode: true })}
>
Create one.
</button>{' '}
Forgot your password? Enter your email and{' '}
<button
type="button"
className="linkButton"
onClick={this.resetPassword}
>
reset it.
</button>
</div>
)}
{this.state.isCreateMode ? (
<Button
type="submit"
onClick={() =>
this.createAccount(this.state.email, this.state.password)
}
>
Create
</Button>
) : (
<Button
type="submit"
onClick={() =>
this.emailSignIn(this.state.email, this.state.password)
}
>
Login
</Button>
)}
</Form>
</Modal.Content>
</Modal>
);
}
Example #14
Source File: index.tsx From frontegg-react with MIT License | 5 votes |
Input = forwardRef<HTMLInputElement, InputProps>(
({ children, labelButton, multiline, label, ...restProps }, forwardRef) => {
const inputLabel = useMemo(
() =>
labelButton ? (
<label className='fe-label__with-button'>
{label}
<Button {...labelButton} />
</label>
) : (
label
),
[labelButton, label]
);
const iconContent = useMemo(() => restProps.prefixIcon ?? restProps.suffixIcon, [restProps]);
const inputProps = { ...mapper(restProps), label: inputLabel };
return multiline ? (
<Form.TextArea {...(inputProps as StrictTextAreaProps)} ref={forwardRef}></Form.TextArea>
) : restProps.iconAction ? (
<Form.Input
{...(inputProps as StrictInputProps | StrictFormInputProps)}
icon
action={{
icon: iconContent,
onClick: restProps.iconAction,
}}
ref={forwardRef}
>
{children}
</Form.Input>
) : (
<Form.Input {...(inputProps as StrictInputProps | StrictFormInputProps)} icon>
<input ref={forwardRef} />
{iconContent}
{children}
</Form.Input>
);
}
)
Example #15
Source File: index.tsx From frontegg-react with MIT License | 5 votes |
Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
if (props.fullWidth) {
return <Form.Checkbox ref={ref as any} {...mapper(props)} />;
}
return <SemanticCheckbox label ref={ref as any} {...mapper(props)} />;
})
Example #16
Source File: ExampleList.tsx From plugin-vscode with Apache License 2.0 | 5 votes |
public render() {
return (
<Grid className="welcome-page" divided>
<Grid.Row className="welcome-navbar" columns={1}>
<Grid.Column>
<Header as="h3" dividing>
Ballerina Examples
</Header>
{this.state && this.state.noSearchReults ?
(<>No search results found!</>) : null
}
{this.state && this.state.samples && this.state.samples.length > 0 ?
(
<Form>
<Form.Field inline>
<Input
ref={(ref) => {
this.searchInput = ref as Input;
}}
loading={!this.state || !this.state.samples}
placeholder="Search"
onChange={(event: React.SyntheticEvent<HTMLInputElement>) => {
this.setState({
searchQuery: event.currentTarget.value,
});
this.onSearchQueryEdit();
}}
className="search-control"
/>
</Form.Field>
</Form>
) : (
<>No Samples found in [BALLERINA_HOME]/examples folder.</>
)
}
</Grid.Column>
</Grid.Row>
<Grid.Row className="welcome-content-wrapper">
<Grid.Column mobile={16} tablet={16} computer={16} className="rightContainer">
<Grid>
{this.state && this.state.samples &&
<Grid.Row columns={4} className="sample-wrapper">
{
this.getColumnContents().map((column, index) => {
return (
<Grid.Column key={index} mobile={16} tablet={8} computer={4}>
{column.map((columnItem) => this.renderColumnItem(columnItem))}
</Grid.Column>
);
})
}
</Grid.Row>
}
</Grid>
</Grid.Column>
</Grid.Row>
</Grid>);
}
Example #17
Source File: CreateTxModal.tsx From multi-sig-wallet with MIT License | 4 votes |
CreateTxModal: React.FC<Props> = ({ open, onClose }) => {
const {
state: { web3, account },
} = useWeb3Context();
const { pending, error, call } = useAsync<SubmitTxParams, any>(
async (params) => {
if (!web3) {
throw new Error("No web3");
}
await submitTx(web3, account, params);
}
);
const [inputs, setInputs] = useState({
to: "",
value: 0,
data: "",
});
function onChange(name: string, e: React.ChangeEvent<HTMLInputElement>) {
setInputs({
...inputs,
[name]: e.target.value,
});
}
async function onSubmit() {
if (pending) {
return;
}
const { error } = await call({
...inputs,
value: inputs.value.toString(),
});
if (!error) {
onClose();
}
}
return (
<Modal open={open} onClose={onClose}>
<Modal.Header>Create Transaction</Modal.Header>
<Modal.Content>
{error && <Message error>{error.message}</Message>}
<Form onSubmit={onSubmit}>
<Form.Field>
<label>To</label>
<Form.Input
type="text"
value={inputs.to}
onChange={(e) => onChange("to", e)}
/>
</Form.Field>
<Form.Field>
<label>Value</label>
<Form.Input
type="number"
min={0}
value={inputs.value}
onChange={(e) => onChange("value", e)}
/>
</Form.Field>
<Form.Field>
<label>Data</label>
<Form.Input
value={inputs.data}
onChange={(e) => onChange("data", e)}
/>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button onClick={onClose} disabled={pending}>
Cancel
</Button>
<Button
color="green"
onClick={onSubmit}
disabled={pending}
loading={pending}
>
Create
</Button>
</Modal.Actions>
</Modal>
);
}
Example #18
Source File: playertools.tsx From website with MIT License | 4 votes |
PlayerToolsForm = (props: PlayerToolsFormProps) => {
const PLAYERLINK = 'https://stt.disruptorbeam.com/player?client_api=17';
const { setValidInput } = props;
const [inputPlayerData, setInputPlayerData] = React.useState(undefined);
const [fullInput, setFullInput] = React.useState('');
const [displayedInput, setDisplayedInput] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState(undefined);
let inputUploadFile = null;
if (fullInput != "")
parseInput();
React.useEffect(() => {
if (inputPlayerData) {
setValidInput(inputPlayerData);
setInputPlayerData(undefined);
}
}, [inputPlayerData]);
return (
<Layout title='Player tools'>
<Header as='h2'>Player tools</Header>
<p>You can access some of your player data from the game's website and import it here to calculate optimal voyage lineups, identify unnecessary items, export your crew list as a CSV, or share your profile with other players, among other tools. This website cannot make direct requests to the game's servers due to security configurations and unclear terms of service interpretations, so there are a few manual steps required to import your data.</p>
<p>If you have multiple accounts, we recommend using your browser in InPrivate mode (Edge) or Incognito mode (Firefox / Chrome) to avoid caching your account credentials, making it easier to change accounts.</p>
<ul>
<li>
Open this page in your browser:{' '}
<a href={PLAYERLINK} target='_blank'>
https://stt.disruptorbeam.com/player
</a>
</li>
<li>
Log in if asked, then wait for the page to finish loading. It should start with:{' '}
<span style={{ fontFamily: 'monospace' }}>{'{"action":"update","player":'}</span> ...
</li>
<li>Select everything in the page (Ctrl+A) and copy it (Ctrl+C)</li>
<li>Paste it (Ctrl+V) in the text box below. Note that DataCore will intentionally display less data here to speed up the process</li>
<li>Click the 'Import data' button</li>
</ul>
<Form>
<TextArea
placeholder='Paste your player data here'
value={displayedInput}
onChange={(e, { value }) => setDisplayedInput(value)}
onPaste={(e) => { return onInputPaste(e) }}
/>
<input
type='file'
onChange={(e) => { handleFileUpload(e) }}
style={{ display: 'none' }}
ref={e => inputUploadFile = e}
/>
</Form>
<Button
onClick={() => parseInput()}
style={{ marginTop: '1em' }}
content='Import data'
icon='paste'
labelPosition='right'
/>
{errorMessage && (
<Message negative>
<Message.Header>Error</Message.Header>
<p>{errorMessage}</p>
</Message>
)}
<p style={{ marginTop: '2em' }}>To circumvent the long text copy limitations on mobile devices, download{' '}
<a href={PLAYERLINK} target='_blank'>
your player data
</a>
{' '}to your device, then click the 'Upload data file' button.
</p>
<p>
<Modal
trigger={<a href="#">Click here for detailed instructions for Apple iOS devices.</a>}
header='Player data upload on iOS'
content={<ul>
<li>Go to your player data using the link provided, logging in if asked.</li>
<li>Wait for the page to finish loading. It should start with:{' '}
<span style={{ fontFamily: 'monospace' }}>{'{"action":"update","player":'}</span> ...
</li>
<li>Press the share icon while viewing the page.</li>
<li>Tap 'options' and choose 'Web Archive', tap 'save to files', choose a location and save.</li>
<li>Come back to this page (DataCore.app player tools).</li>
<li>Tap the 'Upload data file' button.</li>
<li>Choose the file starting with 'player?client_api...' from where you saved it.</li>
</ul>}
/>
</p>
<Button
onClick={() => inputUploadFile.click()}
content='Upload data file'
icon='file'
labelPosition='right'
/>
</Layout>
);
function parseInput() {
let testInput = fullInput;
// Use inputted text if no pasted text detected
if (testInput == '') testInput = displayedInput;
try {
let testData = JSON.parse(testInput as string);
if (testData) {
// Test for playerData array glitch
if (Array.isArray(testData)) {
testData = {...testData[0]};
}
if (testData.player && testData.player.display_name) {
if (testData.player.character && testData.player.character.crew && (testData.player.character.crew.length > 0)) {
setInputPlayerData(testData);
setDisplayedInput('');
setErrorMessage(undefined);
} else {
setErrorMessage('Failed to parse player data from the text you pasted. Make sure you are logged in with the correct account.');
}
}
else {
setErrorMessage('Failed to parse player data from the text you pasted. Make sure the page is loaded correctly and you copied the entire contents!');
}
} else {
setErrorMessage('Failed to parse player data from the text you pasted. Make sure the page is loaded correctly and you copied the entire contents!');
}
} catch (err) {
if ((/Log in to CS Tools/).test(testInput)) {
setErrorMessage('You are not logged in! Open the player data link above and log in to the game as instructed. Then return to this DataCore page and repeat all the steps to import your data.');
}
else {
setErrorMessage(`Failed to read the data. Make sure the page is loaded correctly and you copied the entire contents! (${err})`);
}
}
setFullInput('');
}
function onInputPaste(event) {
let paste = event.clipboardData || window.clipboardData;
if (paste) {
let fullPaste = paste.getData('text');
setFullInput(fullPaste);
setDisplayedInput(`${fullPaste.substr(0, 300)} [ ... ] ${fullPaste.substr(-100)}\n/* Note that DataCore is intentionally displaying less data here to speed up the process */`);
event.preventDefault();
return false;
}
return true;
}
function handleFileUpload(event) {
// use FileReader to read file content in browser
const fReader = new FileReader();
fReader.onload = (e) => {
let data = e.target.result.toString();
// Handle Apple webarchive wrapping
if (data.match(/^bplist00/)) {
// Find where the JSON begins and ends, and extract just that from the larger string.
data = data.substring(data.indexOf('>{') + 1, data.lastIndexOf('}}') + 2);
}
setFullInput(data);
};
fReader.readAsText(event.target.files[0]);
}
}
Example #19
Source File: playertools.tsx From website with MIT License | 4 votes |
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 #20
Source File: voyagecalculator.tsx From website with MIT License | 4 votes |
InputCrewExcluder = (props: InputCrewExcluderProps) => {
const { updateExclusions } = props;
const [eventData, setEventData] = useStateWithStorage('tools/eventData', undefined);
const [activeEvent, setActiveEvent] = React.useState(undefined);
const [options, setOptions] = React.useState(undefined);
React.useEffect(() => {
if (props.excludedCrew)
if (options && !options.initialized) populatePlaceholders();
}, [props.excludedCrew]);
React.useEffect(() => {
if (!options) return;
if (!options.initialized)
populatePlaceholders();
else
populateOptions();
}, [props.showFrozen]);
React.useEffect(() => {
if (activeEvent && activeEvent.seconds_to_end > 0 && activeEvent.seconds_to_start < 86400) {
if (activeEvent.content_types.includes('shuttles') || activeEvent.content_types.includes('gather')) {
const crewIds = props.myCrew.filter(c => activeEvent.bonus.includes(c.symbol)).map(c => c.id);
updateExclusions([...crewIds]);
}
}
}, [activeEvent]);
if (!activeEvent) {
identifyActiveEvent();
return (<></>);
}
if (!options) {
populatePlaceholders();
return (<></>);
}
const label = (
<React.Fragment>
<label>Crew to exclude from voyage</label>
{activeEvent && activeEvent.bonus.length > 0 &&
(<div style={{ margin: '-.5em 0 .5em' }}>Preselected crew give bonuses for the event <b>{activeEvent.name}</b></div>)
}
</React.Fragment>
);
return (
<React.Fragment>
<Form.Field
label={label}
placeholder='Search for crew to exclude'
control={Dropdown}
clearable
fluid
multiple
search
selection
options={options.list}
value={props.excludedCrew}
onFocus={() => { if (!options.initialized) populateOptions(); }}
onChange={(e, { value }) => updateExclusions(value) }
/>
</React.Fragment>
);
function identifyActiveEvent(): void {
// Get event data from recently uploaded playerData
if (eventData && eventData.length > 0) {
const currentEvent = getEventData(eventData.sort((a, b) => (a.seconds_to_start - b.seconds_to_start))[0]);
setActiveEvent({...currentEvent});
}
// Otherwise guess event from autosynced events
else {
guessCurrentEvent().then(currentEvent => {
setActiveEvent({...currentEvent});
});
}
}
function populatePlaceholders(): void {
const options = { initialized: false, list: [] };
if (props.excludedCrew.length > 0) {
let crewList = [...props.myCrew];
if (!props.showFrozen) crewList = crewList.filter(c => c.immortal == 0);
options.list = crewList.filter(c => props.excludedCrew.includes(c.id)).map(c => {
return { key: c.id, value: c.id, text: c.name, image: { avatar: true, src: `${process.env.GATSBY_ASSETS_URL}${c.imageUrlPortrait}` }};
});
}
else {
options.list = [{ key: 0, value: 0, text: 'Loading...' }];
}
setOptions({...options});
}
function populateOptions(): void {
let crewList = [...props.myCrew];
if (!props.showFrozen) crewList = crewList.filter(c => c.immortal == 0);
options.list = crewList.sort((a, b) => a.name.localeCompare(b.name)).map(c => {
return { key: c.id, value: c.id, text: c.name, image: { avatar: true, src: `${process.env.GATSBY_ASSETS_URL}${c.imageUrlPortrait}` }};
});
options.initialized = true;
setOptions({...options});
}
}
Example #21
Source File: voyagecalculator.tsx From website with MIT License | 4 votes |
VoyageInput = (props: VoyageInputProps) => {
const { voyageConfig, myCrew, allShips, useInVoyage } = props;
const [bestShip, setBestShip] = React.useState(undefined);
const [consideredCrew, setConsideredCrew] = React.useState([]);
const [calculator, setCalculator] = React.useState(isMobile ? 'ussjohnjay' : 'iampicard');
const [calcOptions, setCalcOptions] = React.useState({});
const [telemetryOptOut, setTelemetryOptOut] = useStateWithStorage('telemetryOptOut', false, { rememberForever: true });
const [requests, setRequests] = React.useState([]);
const [results, setResults] = React.useState([]);
React.useEffect(() => {
// Note that allShips is missing the default ship for some reason (1* Constellation Class)
// This WILL break voyagecalculator if that's the only ship a player owns
const consideredShips = [];
allShips.filter(ship => ship.owned).forEach(ship => {
const traited = ship.traits.find(trait => trait === voyageConfig.ship_trait);
let entry = {
ship: ship,
score: ship.antimatter + (traited ? 150 : 0),
traited: traited,
bestIndex: Math.min(ship.index.left, ship.index.right)
};
consideredShips.push(entry);
});
consideredShips.sort((a, b) => {
if (a.score === b.score) return a.bestIndex - b.bestIndex;
return b.score - a.score;
});
setBestShip(consideredShips[0]);
setRequests([]);
setResults([]);
}, [voyageConfig]);
React.useEffect(() => {
return function cleanup() {
// Cancel active calculations when leaving page
requests.forEach(request => {
if (request.calcState == CalculatorState.InProgress)
request.abort();
});
}
}, []);
// Scroll here when calculator started, finished
const topAnchor = React.useRef(null);
const calculators = CALCULATORS.helpers.map(helper => {
return { key: helper.id, value: helper.id, text: helper.name };
});
calculators.push({ key: 'all', value: 'all', text: 'All calculators (slower)' });
return (
<React.Fragment>
<div ref={topAnchor} />
{renderBestShip()}
{renderResults()}
{requests.length > 0 && <Header as='h3'>Options</Header>}
<Form>
<InputCrewOptions myCrew={myCrew} updateConsideredCrew={setConsideredCrew} />
<Form.Group inline>
<Form.Field
control={Select}
label='Calculator'
options={calculators}
value={calculator}
onChange={(e, { value }) => setCalculator(value)}
placeholder='Select calculator'
/>
{CALCULATORS.fields.filter(field => field.calculators.includes(calculator) || calculator == 'all').map(field => (
<Form.Field
key={field.id}
control={Select} /* Only control allowed at the moment */
label={field.name}
options={field.options}
value={calcOptions[field.id] ?? field.default}
placeholder={field.description}
onChange={(e, { value }) => setCalcOptions(prevOptions =>
{
const newValue = { [field.id]: value };
return {...prevOptions, ...newValue};
}
)}
/>
))}
</Form.Group>
<Form.Group>
<Form.Button primary onClick={() => startCalculation()}>
Calculate best crew selection
</Form.Button>
{voyageConfig.state &&
<Form.Button onClick={()=> useInVoyage()}>
Return to in voyage calculator
</Form.Button>
}
</Form.Group>
</Form>
<Message style={{ marginTop: '2em' }}>
<Message.Content>
<Message.Header>Privacy Notice</Message.Header>
<p>We use anonymous statistics aggregated from voyage calculations to improve DataCore and power our <b><Link to='/hall_of_fame'>Voyage Hall of Fame</Link></b>.</p>
<Form>
<Form.Field
control={Checkbox}
label={<label>Permit DataCore to collect anonymous voyage stats</label>}
checked={!telemetryOptOut}
onChange={(e, { checked }) => setTelemetryOptOut(!checked) }
/>
</Form>
</Message.Content>
</Message>
</React.Fragment>
);
function renderBestShip(): JSX.Element {
if (!bestShip) return (<></>);
const direction = bestShip.ship.index.right < bestShip.ship.index.left ? 'right' : 'left';
const index = bestShip.ship.index[direction] ?? 0;
return (
<Card fluid>
<Card.Content>
<Image floated='left' src={`${process.env.GATSBY_ASSETS_URL}${bestShip.ship.icon.file.substr(1).replace('/', '_')}.png`} style={{ height: '4em' }} />
<Card.Header>{bestShip.ship.name}</Card.Header>
<p>best ship{bestShip.traited && (<span style={{ marginLeft: '1em' }}>{` +`}{allTraits.ship_trait_names[voyageConfig.ship_trait]}</span>)}</p>
<p style={{ marginTop: '.5em' }}>Tap <Icon name={`arrow ${direction}`} />{index} time{index != 1 ? 's' : ''} on your voyage ship selection screen to select {bestShip.ship.name}.</p>
</Card.Content>
</Card>
);
}
function renderResults(): JSX.Element {
if (results.length == 0)
return (<></>);
const showPopup = (result) => <Popup basic content={<p>{result.result.postscript}</p>} trigger={<p>{result.name}</p>} />
const panes = results.map(result => ({
menuItem: { key: result.id, content: result.result ? showPopup(result) : result.name },
render: () => (
<VoyageResultPane result={result.result}
requests={requests} requestId={result.requestId}
calcState={result.calcState} abortCalculation={abortCalculation}
/>
)
}));
return (
<React.Fragment>
<Header as='h3'>Recommended Lineups</Header>
<Tab menu={{ pointing: true }}
panes={panes}
/>
</React.Fragment>
);
}
function scrollToAnchor(): void {
if (!topAnchor.current) return;
topAnchor.current.scrollIntoView({
behavior: 'smooth'
}, 500);
}
function startCalculation(): void {
const helperConfig = {
voyageConfig, bestShip, consideredCrew, calcOptions,
resultsCallback: handleResults,
isMobile
};
CALCULATORS.helpers.forEach(helper => {
if (helper.id == calculator || calculator == 'all') {
const request = helper.helper(helperConfig);
requests.push(request);
results.push({
id: request.id,
requestId: request.id,
name: 'Calculating...',
calcState: CalculatorState.InProgress
});
request.start();
}
});
setRequests([...requests]);
setResults([...results]);
scrollToAnchor();
}
function handleResults(requestId: string, reqResults: any[], calcState: number): void {
reqResults.forEach((reqResult, idx) => {
// Update existing pane with results
if (idx == 0) {
setResults(prevResults => {
const result = prevResults.find(r => r.id == requestId);
if (calcState == CalculatorState.Done) {
result.name = formatTime(reqResult.estimate.refills[0].result);
result.calcState = CalculatorState.Done;
sendTelemetry(requestId, reqResult);
}
result.result = reqResult;
return [...prevResults];
});
}
// Add new panes if multiple results generated by this request
else {
setResults(prevResults => [...prevResults, {
id: requestId+'-'+idx,
requestId,
name: formatTime(reqResult.estimate.refills[0].result),
calcState: CalculatorState.Done,
result: reqResult
}]);
}
});
if (calcState == CalculatorState.Done) scrollToAnchor();
}
function abortCalculation(requestId: string): void {
const request = requests.find(r => r.id == requestId);
if (request) {
request.abort();
setResults(prevResults => {
const result = prevResults.find(prev => prev.id == requestId);
if (result.result) {
result.name = formatTime(result.result.estimate.refills[0].result);
result.calcState = CalculatorState.Done;
}
else {
const index = prevResults.findIndex(prev => prev.id == requestId);
prevResults.splice(index, 1);
}
return [...prevResults];
});
}
}
function sendTelemetry(requestId: string, result: any): void {
if (telemetryOptOut) return;
const request = requests.find(r => r.id == requestId);
const estimatedDuration = result.estimate.refills[0].result*60*60;
try {
fetch(`${process.env.GATSBY_DATACORE_URL}api/telemetry`, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'voyageCalc',
data: {
voyagers: result.entries.map((entry) => entry.choice.symbol),
estimatedDuration,
calculator: request ? request.calculator : ''
}
})
});
}
catch (err) {
console.log('An error occurred while sending telemetry', err);
}
}
}
Example #22
Source File: voyagecalculator.tsx From website with MIT License | 4 votes |
VoyageEditConfigModal = (props: VoyageEditConfigModalProps) => {
const { updateConfig } = props;
const [voyageConfig, setVoyageConfig] = React.useState(props.voyageConfig);
const [modalIsOpen, setModalIsOpen] = React.useState(false);
const [updateOnClose, setUpdateOnClose] = React.useState(false);
const [options, setOptions] = React.useState(undefined);
React.useEffect(() => {
if (!modalIsOpen && updateOnClose) {
updateConfig(voyageConfig);
setUpdateOnClose(false);
}
}, [modalIsOpen]);
const defaultSlots = [
{ symbol: 'captain_slot', name: 'First Officer', skill: 'command_skill', trait: '' },
{ symbol: 'first_officer', name: 'Helm Officer', skill: 'command_skill', trait: '' },
{ symbol: 'chief_communications_officer', name: 'Communications Officer', skill: 'diplomacy_skill', trait: '' },
{ symbol: 'communications_officer', name: 'Diplomat', skill: 'diplomacy_skill', trait: '' },
{ symbol: 'chief_security_officer', name: 'Chief Security Officer', skill: 'security_skill', trait: '' },
{ symbol: 'security_officer', name: 'Tactical Officer', skill: 'security_skill', trait: '' },
{ symbol: 'chief_engineering_officer', name: 'Chief Engineer', skill: 'engineering_skill', trait: '' },
{ symbol: 'engineering_officer', name: 'Engineer', skill: 'engineering_skill', trait: '' },
{ symbol: 'chief_science_officer', name: 'Chief Science Officer', skill: 'science_skill', trait: '' },
{ symbol: 'science_officer', name: 'Deputy Science Officer', skill: 'science_skill', trait: '' },
{ symbol: 'chief_medical_officer', name: 'Chief Medical Officer', skill: 'medicine_skill', trait: '' },
{ symbol: 'medical_officer', name: 'Ship\'s Counselor', skill: 'medicine_skill', trait: '' }
];
const crewSlots = voyageConfig.crew_slots ?? defaultSlots;
crewSlots.sort((s1, s2) => CONFIG.VOYAGE_CREW_SLOTS.indexOf(s1.symbol) - CONFIG.VOYAGE_CREW_SLOTS.indexOf(s2.symbol));
return (
<Modal
open={modalIsOpen}
onClose={() => setModalIsOpen(false)}
onOpen={() => setModalIsOpen(true)}
trigger={<Button size='small'><Icon name='edit' />Edit</Button>}
>
<Modal.Header>Edit Voyage</Modal.Header>
<Modal.Content scrolling>
{renderContent()}
</Modal.Content>
<Modal.Actions>
<Button positive onClick={() => setModalIsOpen(false)}>
Close
</Button>
</Modal.Actions>
</Modal>
);
function renderContent(): JSX.Element {
if (!modalIsOpen) return (<></>);
if (!options) {
// Renders a lot faster by using known voyage traits rather than calculate list from all possible traits
const knownShipTraits = ['andorian','battle_cruiser','borg','breen','cardassian','cloaking_device',
'dominion','emp','explorer','federation','ferengi','freighter','historic','hologram',
'klingon','malon','maquis','orion_syndicate','pioneer','reman','romulan','ruthless',
'scout','spore_drive','terran','tholian','transwarp','vulcan','warship','war_veteran','xindi'];
const knownCrewTraits = ['android','astrophysicist','bajoran','borg','brutal',
'cardassian','civilian','communicator','costumed','crafty','cultural_figure','cyberneticist',
'desperate','diplomat','doctor','duelist','exobiology','explorer','federation','ferengi',
'gambler','hero','hologram','human','hunter','innovator','inspiring','jury_rigger','klingon',
'marksman','maverick','pilot','prodigy','resourceful','romantic','romulan',
'saboteur','scoundrel','starfleet','survivalist','tactician','telepath','undercover_operative',
'veteran','villain','vulcan'];
const skillsList = [];
for (let skill in CONFIG.SKILLS) {
skillsList.push({
key: skill,
value: skill,
text: CONFIG.SKILLS[skill]
});
}
const shipTraitsList = knownShipTraits.map(trait => {
return {
key: trait,
value: trait,
text: allTraits.ship_trait_names[trait]
};
});
shipTraitsList.sort((a, b) => a.text.localeCompare(b.text));
const crewTraitsList = knownCrewTraits.map(trait => {
return {
key: trait,
value: trait,
text: allTraits.trait_names[trait]
};
});
crewTraitsList.sort((a, b) => a.text.localeCompare(b.text));
setOptions({ skills: skillsList, ships: shipTraitsList, traits: crewTraitsList });
return (<></>);
}
return (
<React.Fragment>
<Message>Editing this voyage will reset all existing recommendations and estimates.</Message>
<Form>
<Form.Group>
<Form.Select
label='Primary skill'
options={options.skills}
value={voyageConfig.skills.primary_skill ?? 'command_skill'}
onChange={(e, { value }) => setSkill('primary_skill', value)}
placeholder='Primary'
/>
<Form.Select
label='Secondary skill'
options={options.skills}
value={voyageConfig.skills.secondary_skill ?? 'science_skill'}
onChange={(e, { value }) => setSkill('secondary_skill', value)}
placeholder='Secondary'
/>
<Form.Select
search clearable
label='Ship trait'
options={options.ships}
value={voyageConfig.ship_trait}
onChange={(e, { value }) => setShipTrait(value)}
placeholder='Ship trait'
/>
</Form.Group>
</Form>
<Table compact striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell textAlign='center'>Skill</Table.HeaderCell>
<Table.HeaderCell>Seat</Table.HeaderCell>
<Table.HeaderCell>Trait</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{crewSlots.map((seat, idx) => (
<Table.Row key={seat.symbol}>
{ idx % 2 == 0 ?
(
<Table.Cell rowSpan='2' textAlign='center'>
<img alt="{seat.skill}" src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${seat.skill}.png`} style={{ height: '2em' }} />
</Table.Cell>
)
: (<></>)
}
<Table.Cell>{seat.name}</Table.Cell>
<Table.Cell>
<Dropdown search selection clearable
options={options.traits}
value={seat.trait}
onChange={(e, { value }) => setSeatTrait(seat.symbol, value)}
placeholder='Trait'
/>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</React.Fragment>
);
}
function setSkill(prime: string, value: string): void {
// Flip skill values if changing to value that's currently set as the other prime
if (prime == 'primary_skill' && value == voyageConfig.skills.secondary_skill)
voyageConfig.skills.secondary_skill = voyageConfig.skills.primary_skill;
else if (prime == 'secondary_skill' && value == voyageConfig.skills.primary_skill)
voyageConfig.skills.primary_skill = voyageConfig.skills.secondary_skill;
voyageConfig.skills[prime] = value;
setVoyageConfig({...voyageConfig});
setUpdateOnClose(true);
}
function setShipTrait(value: string): void {
voyageConfig.ship_trait = value;
setVoyageConfig({...voyageConfig});
setUpdateOnClose(true);
}
function setSeatTrait(seat: symbol, value: string): void {
voyageConfig.crew_slots.find(s => s.symbol === seat).trait = value;
setVoyageConfig({...voyageConfig});
setUpdateOnClose(true);
}
}
Example #23
Source File: collectionstool.tsx From website with MIT License | 4 votes |
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 #24
Source File: shuttlehelper.tsx From website with MIT License | 4 votes |
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 #25
Source File: profile_crew.tsx From website with MIT License | 4 votes |
ProfileCrewTable = (props: ProfileCrewTableProps) => {
const pageId = props.pageId ?? 'crew';
const [showFrozen, setShowFrozen] = useStateWithStorage(pageId+'/showFrozen', true);
const [findDupes, setFindDupes] = useStateWithStorage(pageId+'/findDupes', false);
const myCrew = JSON.parse(JSON.stringify(props.crew));
const tableConfig: ITableConfigRow[] = [
{ width: 3, column: 'name', title: 'Crew', pseudocolumns: ['name', 'events', 'collections.length'] },
{ width: 1, column: 'max_rarity', title: 'Rarity', reverse: true, tiebreakers: ['rarity'] },
{ width: 1, column: 'bigbook_tier', title: 'Tier' },
{ width: 1, column: 'cab_ov', title: <span>CAB <CABExplanation /></span>, reverse: true, tiebreakers: ['cab_ov_rank'] },
{ width: 1, column: 'ranks.voyRank', title: 'Voyage' }
];
CONFIG.SKILLS_SHORT.forEach((skill) => {
tableConfig.push({
width: 1,
column: `${skill.name}.core`,
title: <img alt={CONFIG.SKILLS[skill.name]} src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${skill.name}.png`} style={{ height: '1.1em' }} />,
reverse: true
});
});
function showThisCrew(crew: any, filters: [], filterType: string): boolean {
if (!showFrozen && crew.immortal > 0) {
return false;
}
if (findDupes) {
if (myCrew.filter((c) => c.symbol === crew.symbol).length === 1)
return false;
}
return crewMatchesSearchFilter(crew, filters, filterType);
}
function renderTableRow(crew: any, idx: number, highlighted: boolean): JSX.Element {
const attributes = {
positive: highlighted
};
return (
<Table.Row key={idx} style={{ cursor: 'zoom-in' }} onClick={() => navigate(`/crew/${crew.symbol}/`)} {...attributes}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}><Link to={`/crew/${crew.symbol}/`}>{crew.name}</Link></span>
</div>
<div style={{ gridArea: 'description' }}>{descriptionLabel(crew)}</div>
</div>
</Table.Cell>
<Table.Cell>
<Rating icon='star' rating={crew.rarity} maxRating={crew.max_rarity} size="large" disabled />
</Table.Cell>
<Table.Cell textAlign="center">
<b>{formatTierLabel(crew.bigbook_tier)}</b>
</Table.Cell>
<Table.Cell style={{ textAlign: 'center' }}>
<b>{crew.cab_ov}</b><br />
<small>{rarityLabels[parseInt(crew.max_rarity)-1]} #{crew.cab_ov_rank}</small>
</Table.Cell>
<Table.Cell style={{ textAlign: 'center' }}>
<b>#{crew.ranks.voyRank}</b><br />
{crew.ranks.voyTriplet && <small>Triplet #{crew.ranks.voyTriplet.rank}</small>}
</Table.Cell>
{CONFIG.SKILLS_SHORT.map(skill =>
crew[skill.name].core > 0 ? (
<Table.Cell key={skill.name} textAlign='center'>
<b>{crew[skill.name].core}</b>
<br />
+({crew[skill.name].min}-{crew[skill.name].max})
</Table.Cell>
) : (
<Table.Cell key={skill.name} />
)
)}
</Table.Row>
);
}
function descriptionLabel(crew: any): JSX.Element {
if (crew.immortal) {
return (
<div>
<Icon name="snowflake" /> <span>{crew.immortal} frozen</span>
</div>
);
} else {
const counts = [
{ name: 'event', count: crew.events },
{ name: 'collection', count: crew.collections.length }
];
const formattedCounts = counts.map((count, idx) => (
<span key={idx} style={{ whiteSpace: 'nowrap' }}>
{count.count} {count.name}{count.count != 1 ? 's' : ''}{idx < counts.length-1 ? ',' : ''}
</span>
)).reduce((prev, curr) => [prev, ' ', curr]);
return (
<div>
{crew.favorite && <Icon name="heart" />}
{crew.prospect && <Icon name="add user" />}
<span>Level {crew.level}, </span>
{formattedCounts}
</div>
);
}
}
return (
<React.Fragment>
<div style={{ margin: '.5em 0' }}>
<Form.Group grouped>
<Form.Field
control={Checkbox}
label='Show frozen (vaulted) crew'
checked={showFrozen}
onChange={(e, { checked }) => setShowFrozen(checked)}
/>
<Form.Field
control={Checkbox}
label='Only show duplicate crew'
checked={findDupes}
onChange={(e, { checked }) => setFindDupes(checked)}
/>
</Form.Group>
</div>
<SearchableTable
id={`${pageId}/table_`}
data={myCrew}
config={tableConfig}
renderTableRow={(crew, idx, highlighted) => renderTableRow(crew, idx, highlighted)}
filterRow={(crew, filters, filterType) => showThisCrew(crew, filters, filterType)}
showFilterOptions="true"
initOptions={props.initOptions}
lockable={props.lockable}
/>
</React.Fragment>
);
}
Example #26
Source File: eventplanner.tsx From website with MIT License | 4 votes |
EventCrewTable = (props: EventCrewTableProps) => {
const { eventData, phaseIndex, buffConfig } = props;
const [showBonus, setShowBonus] = useStateWithStorage('eventplanner/showBonus', true);
const [applyBonus, setApplyBonus] = useStateWithStorage('eventplanner/applyBonus', true);
const [showPotential, setShowPotential] = useStateWithStorage('eventplanner/showPotential', false);
const [showFrozen, setShowFrozen] = useStateWithStorage('eventplanner/showFrozen', true);
const [initOptions, setInitOptions] = React.useState({});
const crewAnchor = React.useRef(null);
React.useEffect(() => {
setInitOptions({});
}, [eventData, phaseIndex]);
if (eventData.bonus.length == 0)
return (
<div style={{ marginTop: '1em' }}>
Featured crew not yet identified for this event.
</div>
);
const tableConfig: ITableConfigRow[] = [
{ width: 3, column: 'name', title: 'Crew', pseudocolumns: ['name', 'max_rarity', 'level'] },
{ width: 1, column: 'bonus', title: 'Bonus', reverse: true },
{ width: 1, column: 'bestSkill.score', title: 'Best', reverse: true },
{ width: 1, column: 'bestPair.score', title: 'Pair', reverse: true }
];
CONFIG.SKILLS_SHORT.forEach((skill) => {
tableConfig.push({
width: 1,
column: `${skill.name}.core`,
title: <img alt={CONFIG.SKILLS[skill.name]} src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${skill.name}.png`} style={{ height: '1.1em' }} />,
reverse: true
});
});
// Check for custom column (i.e. combo from crew matrix click)
let customColumn = '';
if (initOptions?.column && tableConfig.findIndex(col => col.column === initOptions.column) == -1) {
customColumn = initOptions.column;
const customSkills = customColumn.replace('combos.', '').split(',');
tableConfig.push({
width: 1,
column: customColumn,
title:
<span>
<img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${customSkills[0]}.png`} style={{ height: '1.1em' }} />
+<img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${customSkills[1]}.png`} style={{ height: '1.1em' }} />
</span>,
reverse: true
});
}
const phaseType = phaseIndex < eventData.content_types.length ? eventData.content_types[phaseIndex] : eventData.content_types[0];
let bestCombos = {};
const zeroCombos = {};
for (let first = 0; first < CONFIG.SKILLS_SHORT.length; first++) {
let firstSkill = CONFIG.SKILLS_SHORT[first];
zeroCombos[firstSkill.name] = 0;
for (let second = first+1; second < CONFIG.SKILLS_SHORT.length; second++) {
let secondSkill = CONFIG.SKILLS_SHORT[second];
zeroCombos[firstSkill.name+','+secondSkill.name] = 0;
}
}
let myCrew = JSON.parse(JSON.stringify(props.crew));
// Filter crew by bonus, frozen here instead of searchabletable callback so matrix can use filtered crew list
if (showBonus) myCrew = myCrew.filter((c) => eventData.bonus.indexOf(c.symbol) >= 0);
if (!showFrozen) myCrew = myCrew.filter((c) => c.immortal == 0);
const getPairScore = (crew: any, primary: string, secondary: string) => {
if (phaseType === 'shuttles') {
if (secondary) return crew[primary].core+(crew[secondary].core/4);
return crew[primary].core;
}
if (secondary) return (crew[primary].core+crew[secondary].core)/2;
return crew[primary].core/2;
};
myCrew.forEach(crew => {
// First adjust skill scores as necessary
if (applyBonus || showPotential) {
crew.bonus = 1;
if (applyBonus && eventData.featured.indexOf(crew.symbol) >= 0) {
if (phaseType == 'gather') crew.bonus = 10;
else if (phaseType == 'shuttles') crew.bonus = 3;
}
else if (applyBonus && eventData.bonus.indexOf(crew.symbol) >= 0) {
if (phaseType == 'gather') crew.bonus = 5;
else if (phaseType == 'shuttles') crew.bonus = 2;
}
if (crew.bonus > 1 || showPotential) {
CONFIG.SKILLS_SHORT.forEach(skill => {
if (crew[skill.name].core > 0) {
if (showPotential && crew.immortal === 0 && !crew.prospect) {
crew[skill.name].current = crew[skill.name].core*crew.bonus;
crew[skill.name] = applySkillBuff(buffConfig, skill.name, crew.skill_data[crew.rarity-1].base_skills[skill.name]);
}
crew[skill.name].core = crew[skill.name].core*crew.bonus;
}
});
}
}
// Then calculate skill combination scores
let combos = {...zeroCombos};
let bestPair = { score: 0 };
let bestSkill = { score: 0 };
for (let first = 0; first < CONFIG.SKILLS_SHORT.length; first++) {
const firstSkill = CONFIG.SKILLS_SHORT[first];
const single = {
score: crew[firstSkill.name].core,
skillA: firstSkill.name
};
combos[firstSkill.name] = single.score;
if (!bestCombos[firstSkill.name] || single.score > bestCombos[firstSkill.name].score)
bestCombos[firstSkill.name] = { id: crew.id, score: single.score };
if (single.score > bestSkill.score) bestSkill = { score: single.score, skill: single.skillA };
for (let second = first+1; second < CONFIG.SKILLS_SHORT.length; second++) {
const secondSkill = CONFIG.SKILLS_SHORT[second];
let pair = {
score: getPairScore(crew, firstSkill.name, secondSkill.name),
skillA: firstSkill.name,
skillB: secondSkill.name
}
if (crew[secondSkill.name].core > crew[firstSkill.name].core) {
pair = {
score: getPairScore(crew, secondSkill.name, firstSkill.name),
skillA: secondSkill.name,
skillB: firstSkill.name
}
}
combos[firstSkill.name+','+secondSkill.name] = pair.score;
if (pair.score > bestPair.score) bestPair = pair;
const pairId = firstSkill.name+secondSkill.name;
if (!bestCombos[pairId] || pair.score > bestCombos[pairId].score)
bestCombos[pairId] = { id: crew.id, score: pair.score };
}
}
crew.combos = combos;
crew.bestPair = bestPair;
crew.bestSkill = bestSkill;
});
return (
<React.Fragment>
<div ref={crewAnchor} />
<Header as='h4'>Your Crew</Header>
<div style={{ margin: '.5em 0' }}>
<Form.Group grouped>
<Form.Field
control={Checkbox}
label={`Only show event crew (${eventData.bonus_text.replace('Crew Bonus: ', '')})`}
checked={showBonus}
onChange={(e, { checked }) => setShowBonus(checked)}
/>
<Form.Field
control={Checkbox}
label='Apply event bonus to skills'
checked={applyBonus}
onChange={(e, { checked }) => setApplyBonus(checked)}
/>
<Form.Field
control={Checkbox}
label='Show potential skills of unleveled crew'
checked={showPotential}
onChange={(e, { checked }) => setShowPotential(checked)}
/>
<Form.Field
control={Checkbox}
label='Show frozen (vaulted) crew'
checked={showFrozen}
onChange={(e, { checked }) => setShowFrozen(checked)}
/>
</Form.Group>
</div>
<SearchableTable
id='eventplanner'
data={myCrew}
config={tableConfig}
renderTableRow={(crew, idx, highlighted) => renderTableRow(crew, idx, highlighted)}
filterRow={(crew, filters, filterType) => showThisCrew(crew, filters, filterType)}
initOptions={initOptions}
showFilterOptions={true}
lockable={props.lockable}
/>
{phaseType !== 'skirmish' && (<EventCrewMatrix crew={myCrew} bestCombos={bestCombos} phaseType={phaseType} handleClick={sortByCombo} />)}
</React.Fragment>
);
function renderTableRow(crew: any, idx: number, highlighted: boolean): JSX.Element {
const attributes = {
positive: highlighted
};
return (
<Table.Row key={idx} style={{ cursor: 'zoom-in' }} onClick={() => navigate(`/crew/${crew.symbol}/`)} {...attributes}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img width={48} src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}><Link to={`/crew/${crew.symbol}/`}>{crew.name}</Link></span>
</div>
<div style={{ gridArea: 'description' }}>{descriptionLabel(crew)}</div>
</div>
</Table.Cell>
<Table.Cell textAlign='center'>
{crew.bonus > 1 ? `x${crew.bonus}` : ''}
</Table.Cell>
<Table.Cell textAlign='center'>
<b>{scoreLabel(crew.bestSkill.score)}</b>
<br /><img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${crew.bestSkill.skill}.png`} style={{ height: '1em' }} />
</Table.Cell>
<Table.Cell textAlign='center'>
<b>{scoreLabel(crew.bestPair.score)}</b>
<br /><img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${crew.bestPair.skillA}.png`} style={{ height: '1em' }} />
{crew.bestPair.skillB != '' && (<span>+<img alt='Skill' src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${crew.bestPair.skillB}.png`} style={{ height: '1em' }} /></span>)}
</Table.Cell>
{CONFIG.SKILLS_SHORT.map(skill =>
crew.base_skills[skill.name] ? (
<Table.Cell key={skill.name} textAlign='center'>
<b>{scoreLabel(crew[skill.name].core)}</b>
{phaseType != 'gather' && (<span><br /><small>+({crew[skill.name].min}-{crew[skill.name].max})</small></span>)}
</Table.Cell>
) : (
<Table.Cell key={skill.name} />
)
)}
{customColumn != '' && (
<Table.Cell key='custom' textAlign='center'>
<b>{scoreLabel(customColumn.split('.').reduce((prev, curr) => prev.hasOwnProperty(curr) ? prev[curr] : undefined, crew))}</b>
</Table.Cell>
)
}
</Table.Row>
);
}
function descriptionLabel(crew: any): JSX.Element {
return (
<div>
<div><Rating icon='star' rating={crew.rarity} maxRating={crew.max_rarity} size='large' disabled /></div>
<div>
{crew.favorite && <Icon name='heart' />}
{crew.immortal > 0 && <Icon name='snowflake' />}
{crew.prospect && <Icon name='add user' />}
<span>{crew.immortal ? (`${crew.immortal} frozen`) : (`Level ${crew.level}`)}</span>
</div>
</div>
);
}
function scoreLabel(score: number): JSX.Element {
if (!score || score === 0) return (<></>);
if (phaseType === 'gather') return (<>{`${calculateGalaxyChance(score)}%`}</>);
return (<>{Math.floor(score)}</>);
}
function showThisCrew(crew: any, filters: [], filterType: string): boolean {
// Bonus, frozen crew filtering now handled before rendering entire table instead of each row
return crewMatchesSearchFilter(crew, filters, filterType);
}
function sortByCombo(skillA: string, skillB: string): void {
if (skillA === skillB) {
setInitOptions({
column: `${skillA}.core`,
direction: 'descending'
});
}
else {
// Order of combo match order of skills in CONFIG
const customSkills = [];
CONFIG.SKILLS_SHORT.forEach((skill) => {
if (skillA === skill.name || skillB === skill.name)
customSkills.push(skill.name);
});
setInitOptions({
column: `combos.${customSkills[0]},${customSkills[1]}`,
direction: 'descending'
});
}
if (!crewAnchor.current) return;
crewAnchor.current.scrollIntoView({
behavior: 'smooth'
}, 500);
}
}
Example #27
Source File: eventplanner.tsx From website with MIT License | 4 votes |
EventPicker = (props: EventPickerProps) => {
const { playerData, events, crew, buffConfig, allCrew } = props;
const [eventIndex, setEventIndex] = useStateWithStorage('eventplanner/eventIndex', 0);
const [phaseIndex, setPhaseIndex] = useStateWithStorage('eventplanner/phaseIndex', 0);
const [prospects, setProspects] = useStateWithStorage('eventplanner/prospects', []);
const eventsList = [];
events.forEach((activeEvent, eventId) => {
eventsList.push(
{
key: activeEvent.symbol,
value: eventId,
text: activeEvent.name
}
);
});
const eventData = events[eventIndex];
const EVENT_TYPES = {
'shuttles': 'Faction',
'gather': 'Galaxy',
'skirmish': 'Skirmish'
};
const phaseList = [];
eventData.content_types.forEach((contentType, phaseId) => {
if (!phaseList.find((phase) => phase.key == contentType)) {
phaseList.push(
{
key: contentType,
value: phaseId,
text: EVENT_TYPES[contentType]
}
);
}
});
const allBonusCrew = allCrew.filter((c) => eventData.bonus.indexOf(c.symbol) >= 0);
allBonusCrew.sort((a, b)=>a.name.localeCompare(b.name));
const myCrew = JSON.parse(JSON.stringify(crew));
const lockable = [];
prospects.forEach((p) => {
let prospect = allCrew.find((c) => c.symbol == p.symbol);
if (prospect) {
prospect = JSON.parse(JSON.stringify(prospect));
prospect.id = myCrew.length+1;
prospect.prospect = true;
prospect.have = false;
prospect.rarity = p.rarity;
prospect.level = 100;
prospect.immortal = 0;
CONFIG.SKILLS_SHORT.forEach(skill => {
let score = { core: 0, min: 0, max: 0 };
if (prospect.base_skills[skill.name]) {
if (prospect.rarity == prospect.max_rarity)
score = applySkillBuff(buffConfig, skill.name, prospect.base_skills[skill.name]);
else
score = applySkillBuff(buffConfig, skill.name, prospect.skill_data[prospect.rarity-1].base_skills[skill.name]);
}
prospect[skill.name] = score;
});
myCrew.push(prospect);
lockable.push({
symbol: prospect.symbol,
name: prospect.name,
rarity: prospect.rarity,
level: prospect.level,
prospect: prospect.prospect
});
}
});
return (
<React.Fragment>
<Form>
<Form.Field
control={Dropdown}
selection
options={eventsList}
value={eventIndex}
onChange={(e, { value }) => setEventIndex(value) }
/>
<Image size='large' src={`${process.env.GATSBY_ASSETS_URL}${eventData.image}`} />
<div>{eventData.description}</div>
{phaseList.length > 1 && (<div style={{ margin: '1em 0' }}>Select a phase: <Dropdown selection options={phaseList} value={phaseIndex} onChange={(e, { value }) => setPhaseIndex(value) } /></div>)}
</Form>
<EventCrewTable crew={myCrew} eventData={eventData} phaseIndex={phaseIndex} buffConfig={buffConfig} lockable={lockable} />
<EventProspects pool={allBonusCrew} prospects={prospects} setProspects={setProspects} />
{eventData.content_types[phaseIndex] == 'shuttles' && (<EventShuttles playerData={playerData} crew={myCrew} eventData={eventData} />)}
</React.Fragment>
);
}
Example #28
Source File: crewretrieval.tsx From website with MIT License | 4 votes |
RetrievalForm = (props: RetrievalFormProps) => {
const { ownedPolestars, allCrew, allKeystones, myCrew } = props;
const [disabledPolestars, setDisabledPolestars] = useStateWithStorage('crewretrieval/disabledPolestars', []);
const [addedPolestars, setAddedPolestars] = useStateWithStorage('crewretrieval/addedPolestars', []);
const [ownedFilter, setOwnedFilter] = useStateWithStorage('crewretrieval/ownedFilter', ownedFilterOptions[0].value);
const [minRarity, setMinRarity] = useStateWithStorage('crewretrieval/minRarity', null);
const [collection, setCollection] = useStateWithStorage('crewretrieval/collection', null);
const [polestars, setPolestars] = React.useState([]);
const [data, setData] = React.useState(null);
// Update polestar list on filter, prospect change
React.useEffect(() => {
let filtered = JSON.parse(JSON.stringify(ownedPolestars));
filtered = filtered.filter((p) => disabledPolestars.indexOf(p.id) === -1);
addedPolestars.forEach((prospect) => {
let existing = filtered.find(polestar => polestar.symbol === prospect);
if (existing) {
existing.loaned = existing.loaned ? existing.loaned + 1 : 1;
existing.quantity++;
}
else {
let keystone = allKeystones.find(k => k.symbol === prospect);
filtered.push({...keystone, quantity: 1, loaned: 1});
}
});
setPolestars([...filtered]);
}, [disabledPolestars, addedPolestars]);
// Update dataset on any form change
React.useEffect(() => {
let retrievable = allCrew.filter(
(crew) => crew.unique_polestar_combos?.some(
(upc) => upc.every(
(trait) => polestars.some(op => filterTraits(op, trait))
)
)
);
retrievable = retrievable.filter(ownedFilters[ownedFilter](myCrew));
if (ownedFilterOptions[2].value === ownedFilter) {
retrievable = retrievable.filter(crew => crew.max_rarity > crew.highest_owned_rarity);
}
if (minRarity) {
retrievable = retrievable.filter((crew) => crew.max_rarity >= minRarity);
}
if (collection) {
retrievable = retrievable.filter((crew) => crew.collections.indexOf(collection) !== -1);
}
setData([...retrievable]);
}, [polestars, ownedFilter, minRarity, collection]);
return (
<React.Fragment>
<p>Here are all the crew who you can perform a 100% guaranteed crew retrieval for, using the polestars in your inventory:</p>
<Form>
<Form.Group inline>
<PolestarFilterModal ownedPolestars={ownedPolestars} disabledPolestars={disabledPolestars} updateDisableds={setDisabledPolestars} />
<PolestarProspectModal ownedPolestars={ownedPolestars} addedPolestars={addedPolestars} allKeystones={allKeystones} allCrew={allCrew} updateProspects={setAddedPolestars} />
<Form.Field
control={Dropdown}
selection
options={ownedFilterOptions}
value={ownedFilter}
onChange={(e, { value }) => setOwnedFilter(value)}
/>
<Form.Field
control={Dropdown}
placeholder="Minimum rarity"
selection
options={rarityOptions}
value={minRarity}
onChange={(e, { value }) => setMinRarity(value)}
/>
<Form.Field
control={Dropdown}
placeholder="Collections"
selection
options={collectionsOptions}
value={collection}
onChange={(e, { value }) => setCollection(value)}
/>
</Form.Group>
</Form>
<CrewTable data={data} polestars={polestars} />
</React.Fragment>
);
}
Example #29
Source File: collectionstool.tsx From website with MIT License | 4 votes |
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>
);
}
}