semantic-ui-react#Message TypeScript Examples
The following examples show how to use
semantic-ui-react#Message.
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: profile.tsx From website with MIT License | 6 votes |
render() {
const { dbid, errorMessage, playerData, mobile } = this.state;
if (playerData === undefined || dbid === undefined || errorMessage !== undefined) {
return (
<Layout title='Player profile'>
<Header as='h4'>Player profile</Header>
{errorMessage && (
<Message negative>
<Message.Header>Unable to load profile</Message.Header>
<p>
Failed to find the player profile you were searching. Make sure you have the right URL, or contact the player and ask them
to reupload their profile.
</p>
<pre>{errorMessage.toString()}</pre>
</Message>
)}
<p>
Are you looking to share your player profile? Go to the <Link to={`/playertools`}>Player Tools page</Link> to upload your
player.json and access other useful player tools.
</p>
{!errorMessage && (
<div>
<Icon loading name='spinner' /> Loading...
</div>
)}
</Layout>
);
}
if (mobile) {
return this.renderMobile();
} else {
return this.renderDesktop();
}
}
Example #2
Source File: ErrorBoundary.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
render = (): ReactNode => {
const { children, statusMessages = {} } = this.props;
const { hasError, error } = this.state;
const messages = { ...DEFAULT_MESSAGES, ...statusMessages };
if (hasError) {
const statusCode = (error as HTTPError)?.response?.status;
if (statusCode && Object.keys(messages).includes(String(statusCode))) {
return <Message warning>{messages[statusCode]}</Message>;
}
return <Message error>{messages[0]}</Message>;
}
return children;
};
Example #3
Source File: ErrorBoundary.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 6 votes |
render = (): ReactNode => {
const { children, statusMessages = {} } = this.props;
const { hasError, error } = this.state;
const messages = { ...DEFAULT_MESSAGES, ...statusMessages };
if (hasError) {
const statusCode = (error as HTTPError)?.response?.status;
if (statusCode && Object.keys(messages).includes(String(statusCode))) {
return <Message warning>{messages[statusCode]}</Message>;
}
return <Message error>{messages[0]}</Message>;
}
return children;
};
Example #4
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 #5
Source File: index.tsx From multi-sig-wallet with MIT License | 5 votes |
function App() {
const {
state: { account, netId },
updateAccount,
} = useWeb3Context();
const { pending, error, call } = useAsync(unlockAccount);
async function onClickConnect() {
const { error, data } = await call(null);
if (error) {
console.error(error);
}
if (data) {
updateAccount(data);
}
}
return (
<div className="App">
<div className="App-main">
<h1>Multi Sig Wallet</h1>
{account ? (
<>
{netId !== 0 && <Network netId={netId} />}
<div>Account: {account}</div>
<MultiSigWallet />
</>
) : (
<>
{error ? (
<Message error>{error.message}</Message>
) : (
<Message warning>Metamask is not connected</Message>
)}
<Button
color="green"
onClick={() => onClickConnect()}
disabled={pending}
loading={pending}
>
Connect to Metamask
</Button>
</>
)}
</div>
<Footer />
</div>
);
}
Example #6
Source File: voyagecalculator.tsx From website with MIT License | 5 votes |
VoyageResultPane = (props: VoyageResultPaneProps) => {
const { result, requests, requestId, calcState, abortCalculation } = props;
const request = requests.find(r => r.id == requestId);
if (!request) return (<></>);
if (!result) {
return (
<Tab.Pane>
<div style={{ textAlign: 'center' }}>
<Image centered src='/media/voyage-wait-icon.gif' />
<Button onClick={() => abortCalculation(request.id)}>Abort</Button>
</div>
</Tab.Pane>
);
}
// resultToVoyageData
let data = {...request.voyageConfig};
if (result.entries) {
result.entries.forEach((entry, idx) => {
let acrew = request.consideredCrew.find(c => c.symbol === entry.choice.symbol);
data.crew_slots[entry.slotId].crew = acrew;
});
}
data.skill_aggregates = result.aggregates;
data.max_hp = result.startAM;
data.state = 'pending';
const renderCalculatorMessage = () => {
if (calcState != CalculatorState.Done) {
return (
<>
<Image inline size='mini' src='/media/voyage-wait-icon.gif' />
Calculation in progress. Please wait...{` `}
<Button size='mini' onClick={() => abortCalculation(request.id)}>Abort</Button>
</>
);
}
let inputs = Object.entries(request.calcOptions).map(entry => entry[0]+': '+entry[1]);
inputs.unshift('considered crew: '+request.consideredCrew.length);
return (
<>
Calculated by <b>{request.calcName}</b> calculator ({inputs.join(', ')}){` `}
in {((request.perf.end-request.perf.start)/1000).toFixed(2)} seconds!
{result.postscript && (<div>{result.postscript}</div>)}
</>
);
};
return (
<React.Fragment>
{calcState == CalculatorState.Done && (
<Message attached>
Estimate: <b>{formatTime(result.estimate.refills[0].result)}</b>{` `}
(expected range: {formatTime(result.estimate.refills[0].saferResult)} to{` `}
{formatTime(result.estimate.refills[0].moonshotResult)})
</Message>
)}
<Tab.Pane>
<VoyageStats
voyageData={data}
estimate={result.estimate}
ships={[request.bestShip]}
showPanels={['crew']}
/>
<div style={{ marginTop: '1em' }}>
{renderCalculatorMessage()}
</div>
</Tab.Pane>
</React.Fragment>
);
}
Example #7
Source File: voyagecalculator.tsx From website with MIT License | 5 votes |
VoyageExisting = (props: VoyageExistingProps) => {
const { voyageConfig, allShips, useCalc } = props;
const [CIVASExportFailed, setCIVASExportFailed] = React.useState(false);
const [doingCIVASExport, setDoingCIVASExport] = React.useState(false);
const hoursToTime = hours => {
let wholeHours = Math.floor(hours);
return `${wholeHours}:${Math.floor((hours-wholeHours)*60).toString().padStart(2, '0')}`
}
const exportData = () => new Promise((resolve, reject) => {
setDoingCIVASExport(true);
let estimate = getEstimate({
startAm: voyageConfig.max_hp,
ps: voyageConfig.skill_aggregates[voyageConfig.skills['primary_skill']],
ss: voyageConfig.skill_aggregates[voyageConfig.skills['secondary_skill']],
others: Object.values(voyageConfig.skill_aggregates).filter(s => !Object.values(voyageConfig.skills).includes(s.skill)),
}, () => true).refills[0].result;
let values = [
new Date(voyageConfig.created_at).toLocaleDateString(),
hoursToTime(estimate),
hoursToTime(voyageConfig.log_index/180),
voyageConfig.hp
];
values = values.concat(voyageConfig
.crew_slots
.sort((s1, s2) => CONFIG.VOYAGE_CREW_SLOTS.indexOf(s1.symbol) - CONFIG.VOYAGE_CREW_SLOTS.indexOf(s2.symbol))
.map(s => s.crew.name)
);
navigator.clipboard.writeText(values.join('\n')).then(resolve, reject);
});
return (
<div style={{ marginTop: '1em' }}>
<VoyageStats
voyageData={voyageConfig}
ships={allShips}
showPanels={voyageConfig.state == 'started' ? ['estimate'] : ['rewards']}
/>
<Button onClick={() => useCalc()}>Return to crew calculator</Button>
{(voyageConfig.state == 'recalled' || voyageConfig.state == 'failed') && navigator.clipboard &&
<React.Fragment>
<Button loading={doingCIVASExport} onClick={() => exportData().then(
() => setDoingCIVASExport(false),
() => {
setDoingCIVASExport(false);
setCIVASExportFailed(true);
let timeout = setTimeout(() => {
setCIVASExportFailed(false);
clearTimeout(timeout);
}, 5000);
})}>
Export to CIVAS
</Button>
<Popup
trigger={<Icon name='help' />}
content={
<>
Copies details of the voyage to the clipboard so that it can be pasted into <a href='https://docs.google.com/spreadsheets/d/1nbnD2WvDXAT9cxEWep0f78bv6_hOaP51tmRjmY0oT1k' target='_blank'>Captain Idol's Voyage Analysis Sheet</a>
</>
}
mouseLeaveDelay={1000}
/>
{CIVASExportFailed &&
<Message negative>Export to clipboard failed</Message>
}
</React.Fragment>
}
</div>
)
}
Example #8
Source File: voyagecalculator.tsx From website with MIT License | 5 votes |
VoyageCalculator = (props: VoyageCalculatorProps) => {
const { playerData } = props;
const [activeCrew, setActiveCrew] = useStateWithStorage('tools/activeCrew', undefined);
const [allShips, setAllShips] = React.useState(undefined);
if (!allShips) {
fetchAllShips();
return (<><Icon loading name='spinner' /> Loading...</>);
}
// Create fake ids for shuttle crew based on level and equipped status
const shuttleCrew = activeCrew.map(ac => {
return {
id: ac.symbol+','+ac.level+','+ac.equipment.join(''),
active_status: ac.active_status
};
});
const myCrew = JSON.parse(JSON.stringify(playerData.player.character.crew));
myCrew.forEach((crew, crewId) => {
crew.id = crewId+1;
// Voyage calculator looks for skills, range_min, range_max properties
let skills = {};
for (let skill in CONFIG.SKILLS) {
if (crew[skill].core > 0)
skills[skill] = {
'core': crew[skill].core,
'range_min': crew[skill].min,
'range_max': crew[skill].max
};
}
crew.skills = skills;
// Voyage roster generation looks for active_status property
crew.active_status = 0;
if (crew.immortal === 0) {
let shuttleCrewId = crew.symbol+','+crew.level+','+crew.equipment.join('');
let onShuttle = shuttleCrew.find(sc => sc.id == shuttleCrewId);
if (onShuttle) {
crew.active_status = onShuttle.active_status;
onShuttle.id = ''; // Clear this id so that dupes are counted properly
}
}
});
// There may be other ways we should check for voyage eligibility
if (playerData.player.character.level < 8 || myCrew.length < 12)
return (<Message>Sorry, but you can't voyage just yet!</Message>);
return (
<VoyageMain myCrew={myCrew} allShips={allShips} />
);
async function fetchAllShips() {
const [shipsResponse] = await Promise.all([
fetch('/structured/ship_schematics.json')
]);
const allships = await shipsResponse.json();
const ships = mergeShips(allships, playerData.player.character.ships);
const ownedCount = playerData.player.character.ships.length;
playerData.player.character.ships.sort((a, b) => a.archetype_id - b.archetype_id).forEach((ship, idx) => {
const myShip = ships.find(s => s.symbol === ship.symbol);
if (myShip) {
myShip.id = ship.id; // VoyageStats needs ship id to identify ship on existing voyage
myShip.index = { left: (ownedCount-idx+1), right: idx-1 };
if (idx == 0)
myShip.index = { left: 1, right: ownedCount-1 };
else if (idx == 1)
myShip.index = { left: 0, right: 0 };
}
});
setAllShips(ships);
}
}
Example #9
Source File: unneededitems.tsx From website with MIT License | 5 votes |
render() {
const { playerData } = this.props;
let itemCount = playerData.player.character.items.length;
let itemLimit = 1000, itemWarning = .9*itemLimit;
// Hardcoded limit works now, but if the game increases limit, we'll have to update
// We should get this from playerData.player.character.item_limit, but it's not in preparedProfileData
return (
<div>
{itemCount > itemWarning && (
<Message warning>
<Message.Header>Items approaching limit</Message.Header>
<p>
You have {itemCount} items in your inventory. At {itemLimit} the game starts randomly losing
items; go and replicate away some unnecessary stuff.
</p>
</Message>
)}
{this.state.fuelschematics.length > 0 && (
<React.Fragment>
<Header as='h4'>Here are {this.state.fuelschematics.length} Ship Schematics that you don't need (used to upgrade ships you already maxed):</Header>
<Grid columns={5} centered padded>
{this.state.fuelschematics.map((item, idx) => (
<Grid.Column key={idx} rel={item.archetype_id} textAlign='center'>
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${item.imageUrl}`}
size={64}
maxRarity={item.rarity}
rarity={item.rarity}
/>
<p>{item.name}</p>
</Grid.Column>
))}
</Grid>
</React.Fragment>
)}
{this.state.fuelspecific.length > 0 && (
<React.Fragment>
<Header as='h4'>Here are {this.state.fuelspecific.length} Equipment items that you don't need (used to equip specific crew you already equipped):</Header>
<Grid columns={5} centered padded>
{this.state.fuelspecific.map((item, idx) => (
<Grid.Column key={idx} rel={item.archetype_id} textAlign='center'>
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${item.imageUrl}`}
size={64}
maxRarity={item.rarity}
rarity={item.rarity}
/>
<p>{item.name}</p>
</Grid.Column>
))}
</Grid>
</React.Fragment>
)}
{this.state.fuelgeneric.length > 0 && (
<React.Fragment>
<Header as='h4'>Here are {this.state.fuelgeneric.length} Equipment items that you don't need now, but might be useful in the future:</Header>
<Grid columns={5} centered padded>
{this.state.fuelgeneric.map((item, idx) => (
<Grid.Column key={idx} rel={item.archetype_id} textAlign='center'>
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${item.imageUrl}`}
size={64}
maxRarity={item.rarity}
rarity={item.rarity}
/>
<p>{item.name}</p>
</Grid.Column>
))}
</Grid>
</React.Fragment>
)}
</div>
);
}
Example #10
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 #11
Source File: announcement.tsx From website with MIT License | 5 votes |
Announcement = () => {
const [readyToAnnounce, setReadyToAnnounce] = React.useState(false);
const [dateNow, setDateNow] = React.useState(new Date());
const [dismissAnnouncement, setDismissAnnouncement] = useStateWithStorage(
'dismissAnnouncement', /* cookie name */
undefined, /* initial value */
{
rememberForever: true,
onInitialize: () => setReadyToAnnounce(true)
} /* storage options */
);
// To avoid rendering and then hiding an announcement that was previously dismissed,
// wait for cookie retrieval before rendering the message in the first place
if (!readyToAnnounce) return (<></>);
const query = graphql`
query AnnouncementQuery {
allMarkdownRemark(
limit: 1
sort: {fields: [frontmatter___date], order: [DESC]}
filter: {fields: {source: {eq: "announcements"}}}
) {
edges {
node {
html
frontmatter {
title
class
icon
date
}
excerpt(format:HTML)
}
}
}
}
`;
const render = ({ allMarkdownRemark }) => {
const announcements = allMarkdownRemark.edges;
if (announcements.length === 0) return (<></>);
const announcement = announcements[0].node;
const datePosted = new Date(announcement.frontmatter.date);
if (dismissAnnouncement) {
const dateDismissed = new Date(dismissAnnouncement);
if (dateDismissed > datePosted) return (<></>);
}
const dateExpires = new Date(datePosted);
dateExpires.setDate(datePosted.getDate()+DAYS_TO_EXPIRE);
if (dateExpires < dateNow) return (<></>);
const isExcerpt = announcement.html !== announcement.excerpt;
return (
<Message icon className={announcement.frontmatter.class ?? ''} onDismiss={() => setDismissAnnouncement(new Date())}>
<Icon name={announcement.frontmatter.icon ?? 'info'} />
<Message.Content>
<Message.Header>{announcement.frontmatter.title ?? 'Message from the DataCore Team'}</Message.Header>
<div dangerouslySetInnerHTML={{ __html: announcement.excerpt }} />
{isExcerpt && (<Link to='/announcements/'>See details...</Link>)}
</Message.Content>
</Message>
);
};
return (
<StaticQuery query={query} render={render} />
);
}
Example #12
Source File: Members2.tsx From Riakuto-StartingReact-ja3.1 with Apache License 2.0 | 5 votes |
Members: VFC<Props> = ({ orgCodeList, prefetch = () => undefined }) => {
const [orgCode, setOrgCode] = useState('');
const [input, setInput] = useState('');
const [isPending, startTransition] = useTransition();
const { reset } = useQueryErrorResetBoundary();
const menuItems = orgCodeList.map((code) => ({
key: code,
name: capitalize(code),
onClick: () => {
setInput('');
if (orgCode) {
startTransition(() => setOrgCode(code));
} else {
setOrgCode(code);
}
},
onMouseOver: () => prefetch(code),
active: code === orgCode,
}));
const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
event.preventDefault();
setOrgCode(input.toLowerCase().trim());
};
return (
<>
<header className="app-header">
<h1>組織メンバーリスト</h1>
</header>
<form className="search-form" onSubmit={handleSubmit}>
<Input
placeholder="組織コードを入力..."
type="text"
value={input}
onChange={(_, data) => setInput(data.value)}
/>
<Button type="submit" disabled={isPending} primary>
検索
</Button>
</form>
<Menu items={menuItems} text />
<Divider />
<div className={isPending ? 'loading' : ''}>
<ErrorBoundary
fallbackRender={({ resetErrorBoundary }) => (
<>
<Message warning>
{orgCode} というコードの組織は見つかりません
</Message>
<Button color="olive" onClick={() => resetErrorBoundary()}>
エラーをリセット
</Button>
</>
)}
onReset={() => reset()}
>
<SuspenseList revealOrder="forwards">
<Suspense fallback={<Spinner size="small" />}>
<OrgInfo orgCode={orgCode} />
</Suspense>
<Suspense fallback={<Spinner size="large" />}>
<MemberList orgCode={orgCode} />
</Suspense>
</SuspenseList>
</ErrorBoundary>
</div>
</>
);
}
Example #13
Source File: leaderboard.tsx From website with MIT License | 5 votes |
function LeaderboardTab({leaderboard}) {
return (
<>
<Message>
If this event is currently active, the leaderboard below might be out of date.
(Data is updated only a couple of times a week)
</Message>
<Table celled striped compact='very'>
<Table.Body>
{leaderboard.map(member => (
<Table.Row key={member.dbid}>
<Table.Cell style={{ fontSize: '1.25em' }}>
Rank: {member.rank}
</Table.Cell>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}>
<div style={{ gridArea: 'icon' }}>
<img
width={48}
src={member.avatar ? getIconPath(member.avatar) : `${process.env.GATSBY_ASSETS_URL}crew_portraits_cm_empty_sm.png`}
/>
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{member.display_name}
</span>
</div>
<div style={{ gridArea: 'description' }}>
Level {member.level}
</div>
</div>
</Table.Cell>
<Table.Cell style={{ fontSize: '1.25em' }}>
Score: {member.score}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</>
);
}
Example #14
Source File: crewchallenge.tsx From website with MIT License | 4 votes |
CrewPicker = (props: CrewPickerProps) => {
const { rules, guesses, handleSelect } = props;
const portalCrew = React.useContext(PortalCrewContext);
const [modalIsOpen, setModalIsOpen] = React.useState(false);
const [searchFilter, setSearchFilter] = React.useState('');
const [paginationPage, setPaginationPage] = React.useState(1);
const [selectedCrew, setSelectedCrew] = React.useState(undefined);
const [showHints, setShowHints] = React.useState(true);
const guessesLeft = rules.guesses - guesses.length;
const inputRef = React.createRef();
React.useEffect(() => {
if (modalIsOpen) inputRef.current.focus();
}, [modalIsOpen]);
return (
<Modal
open={modalIsOpen}
onClose={() => setModalIsOpen(false)}
onOpen={() => setModalIsOpen(true)}
trigger={renderButton()}
size='tiny'
centered={false}
closeIcon
>
<Modal.Header>
<Input ref={inputRef}
size='mini' fluid
iconPosition='left'
placeholder='Search for crew by name'
value={searchFilter}
onChange={(e, { value }) => { setSearchFilter(value); setPaginationPage(1); setSelectedCrew(undefined); }}>
<input />
<Icon name='search' />
<Button icon onClick={() => { setSearchFilter(''); setPaginationPage(1); setSelectedCrew(undefined); inputRef.current.focus(); }} >
<Icon name='delete' />
</Button>
</Input>
</Modal.Header>
<Modal.Content scrolling>
{renderGrid()}
</Modal.Content>
<Modal.Actions>
<Button content='Show hints' onClick={() => setShowHints(!showHints) } />
{selectedCrew && (
<Button color='blue'
content={`Guess ${selectedCrew.name}`}
onClick={() => confirmGuess(selectedCrew.symbol)} />
)}
{!selectedCrew && (
<Button content='Close' onClick={() => setModalIsOpen(false)} />
)}
</Modal.Actions>
</Modal>
);
function renderButton(): JSX.Element {
return (
<Button fluid size='big' color='blue'>
<Icon name='zoom-in' />
Guess Crew
<span style={{ fontSize: '.95em', fontWeight: 'normal', paddingLeft: '1em' }}>
({guessesLeft} guess{guessesLeft !== 1 ? 'es' : ''} remaining)
</span>
</Button>
);
}
function renderGrid(): JSX.Element {
if (!modalIsOpen) return (<></>);
let data = portalCrew.slice();
if (rules.excludedCrew.length > 0)
data = data.filter(crew => !rules.excludedCrew.includes(crew.symbol));
// Filtering
if (searchFilter !== '') {
const filter = (input: string) => input.toLowerCase().indexOf(searchFilter.toLowerCase()) >= 0;
data = data.filter(crew => filter(crew.name));
}
if (data.length === 0) return (
<Message>
<p>No crew names match your current search.</p>
<p>Only crew that are currently <b>available in the time portal</b> will be used as mystery crew and valid guesses.</p>
</Message>
);
// Pagination
const itemsPerPage = 24, itemsToShow = itemsPerPage*paginationPage;
return (
<div>
<Grid doubling columns={3} textAlign='center'>
{data.slice(0, itemsToShow).map(crew => (
<Grid.Column key={crew.symbol} style={{ cursor: 'pointer' }}
onClick={() => { if (!guesses.includes(crew.symbol)) setSelectedCrew(crew); }}
onDoubleClick={() => { if (!guesses.includes(crew.symbol)) confirmGuess(crew.symbol); }}
color={selectedCrew?.symbol === crew.symbol ? 'blue' : null}
>
<img width={48} height={48} src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`} />
<div>
{guesses.includes(crew.symbol) && (<Icon name='x' color='red' />)}
{crew.name}
</div>
{!showHints && (
<div>({[crew.series.toUpperCase(), `${crew.max_rarity}*`].join(', ')})</div>
)}
</Grid.Column>
))}
</Grid>
{itemsToShow < data.length && (
<InView as='div' style={{ margin: '2em 0', textAlign: 'center' }}
onChange={(inView, entry) => { if (inView) setPaginationPage(prevState => prevState + 1); }}
>
<Icon loading name='spinner' /> Loading...
</InView>
)}
{itemsToShow >= data.length && (
<Message>Tip: Double-tap a crew to make your guess more quickly.</Message>
)}
</div>
);
}
function confirmGuess(symbol: string): void {
handleSelect(symbol);
setModalIsOpen(false);
setSelectedCrew(undefined);
}
}
Example #15
Source File: App.tsx From watchparty with MIT License | 4 votes |
render() {
const sharer = this.state.participants.find((p) => p.isScreenShare);
const controls = (
<Controls
key={this.state.controlsTimestamp}
togglePlay={this.togglePlay}
onSeek={this.onSeek}
fullScreen={this.fullScreen}
toggleMute={this.toggleMute}
toggleSubtitle={this.toggleSubtitle}
setVolume={this.setVolume}
jumpToLeader={this.jumpToLeader}
paused={this.state.currentMediaPaused}
muted={this.isMuted()}
subtitled={this.isSubtitled()}
currentTime={this.getCurrentTime()}
duration={this.getDuration()}
disabled={!this.haveLock()}
leaderTime={this.isHttp() ? this.getLeaderTime() : undefined}
isPauseDisabled={this.isPauseDisabled()}
/>
);
const subscribeButton = (
<SubscribeButton
user={this.props.user}
isSubscriber={this.props.isSubscriber}
isCustomer={this.props.isCustomer}
/>
);
const displayRightContent =
this.state.showRightBar || this.state.fullScreen;
const rightBar = (
<Grid.Column
width={displayRightContent ? 4 : 1}
style={{ display: 'flex', flexDirection: 'column' }}
className={`${
this.state.fullScreen
? 'fullHeightColumnFullscreen'
: 'fullHeightColumn'
}`}
>
<Input
inverted
fluid
label={'My name is:'}
value={this.state.myName}
onChange={this.updateName}
style={{ visibility: displayRightContent ? '' : 'hidden' }}
icon={
<Icon
onClick={() => this.updateName(null, { value: generateName() })}
name="refresh"
inverted
circular
link
/>
}
/>
{
<Menu
inverted
widths={3}
style={{
marginTop: '4px',
marginBottom: '4px',
visibility: displayRightContent ? '' : 'hidden',
}}
>
<Menu.Item
name="chat"
active={this.state.currentTab === 'chat'}
onClick={() => {
this.setState({ currentTab: 'chat', unreadCount: 0 });
}}
as="a"
>
Chat
{this.state.unreadCount > 0 && (
<Label circular color="red">
{this.state.unreadCount}
</Label>
)}
</Menu.Item>
<Menu.Item
name="people"
active={this.state.currentTab === 'people'}
onClick={() => this.setState({ currentTab: 'people' })}
as="a"
>
People
<Label
circular
color={
getColorForString(
this.state.participants.length.toString()
) as SemanticCOLORS
}
>
{this.state.participants.length}
</Label>
</Menu.Item>
<Menu.Item
name="settings"
active={this.state.currentTab === 'settings'}
onClick={() => this.setState({ currentTab: 'settings' })}
as="a"
>
{/* <Icon name="setting" /> */}
Settings
</Menu.Item>
</Menu>
}
<Chat
chat={this.state.chat}
nameMap={this.state.nameMap}
pictureMap={this.state.pictureMap}
socket={this.socket}
scrollTimestamp={this.state.scrollTimestamp}
getMediaDisplayName={this.getMediaDisplayName}
hide={this.state.currentTab !== 'chat' || !displayRightContent}
isChatDisabled={this.state.isChatDisabled}
owner={this.state.owner}
user={this.props.user}
ref={this.chatRef}
/>
{this.state.state === 'connected' && (
<VideoChat
socket={this.socket}
participants={this.state.participants}
nameMap={this.state.nameMap}
pictureMap={this.state.pictureMap}
tsMap={this.state.tsMap}
rosterUpdateTS={this.state.rosterUpdateTS}
hide={this.state.currentTab !== 'people' || !displayRightContent}
owner={this.state.owner}
user={this.props.user}
/>
)}
<SettingsTab
hide={this.state.currentTab !== 'settings' || !displayRightContent}
user={this.props.user}
roomLock={this.state.roomLock}
setRoomLock={this.setRoomLock}
socket={this.socket}
isSubscriber={this.props.isSubscriber}
roomId={this.state.roomId}
isChatDisabled={this.state.isChatDisabled}
setIsChatDisabled={this.setIsChatDisabled}
owner={this.state.owner}
setOwner={this.setOwner}
vanity={this.state.vanity}
setVanity={this.setVanity}
roomLink={this.state.roomLink}
password={this.state.password}
setPassword={this.setPassword}
clearChat={this.clearChat}
roomTitle={this.state.roomTitle}
setRoomTitle={this.setRoomTitle}
roomDescription={this.state.roomDescription}
setRoomDescription={this.setRoomDescription}
roomTitleColor={this.state.roomTitleColor}
setRoomTitleColor={this.setRoomTitleColor}
mediaPath={this.state.mediaPath}
setMediaPath={this.setMediaPath}
/>
</Grid.Column>
);
return (
<React.Fragment>
{!this.state.isAutoPlayable && (
<Modal inverted basic open>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Button
primary
size="large"
onClick={() => {
this.setState({ isAutoPlayable: true });
this.setMute(false);
this.setVolume(1);
}}
icon
labelPosition="left"
>
<Icon name="volume up" />
Click to unmute
</Button>
</div>
</Modal>
)}
{this.state.multiStreamSelection && (
<MultiStreamModal
streams={this.state.multiStreamSelection}
setMedia={this.setMedia}
resetMultiSelect={this.resetMultiSelect}
/>
)}
{this.state.isVBrowserModalOpen && (
<VBrowserModal
isSubscriber={this.props.isSubscriber}
subscribeButton={subscribeButton}
closeModal={() => this.setState({ isVBrowserModalOpen: false })}
startVBrowser={this.startVBrowser}
user={this.props.user}
beta={this.props.beta}
/>
)}
{this.state.isScreenShareModalOpen && (
<ScreenShareModal
closeModal={() => this.setState({ isScreenShareModalOpen: false })}
startScreenShare={this.setupScreenShare}
/>
)}
{this.state.isFileShareModalOpen && (
<FileShareModal
closeModal={() => this.setState({ isFileShareModalOpen: false })}
startFileShare={this.setupFileShare}
/>
)}
{this.state.isSubtitleModalOpen && (
<SubtitleModal
closeModal={() => this.setState({ isSubtitleModalOpen: false })}
socket={this.socket}
currentSubtitle={this.state.currentSubtitle}
src={this.state.currentMedia}
haveLock={this.haveLock}
getMediaDisplayName={this.getMediaDisplayName}
beta={this.props.beta}
/>
)}
{this.state.error && <ErrorModal error={this.state.error} />}
{this.state.isErrorAuth && (
<PasswordModal
savedPasswords={this.state.savedPasswords}
roomId={this.state.roomId}
/>
)}
{this.state.errorMessage && (
<Message
negative
header="Error"
content={this.state.errorMessage}
style={{
position: 'fixed',
bottom: '10px',
right: '10px',
zIndex: 1000,
}}
></Message>
)}
{this.state.successMessage && (
<Message
positive
header="Success"
content={this.state.successMessage}
style={{
position: 'fixed',
bottom: '10px',
right: '10px',
zIndex: 1000,
}}
></Message>
)}
<TopBar
user={this.props.user}
isCustomer={this.props.isCustomer}
isSubscriber={this.props.isSubscriber}
roomTitle={this.state.roomTitle}
roomDescription={this.state.roomDescription}
roomTitleColor={this.state.roomTitleColor}
/>
{
<Grid stackable celled="internally">
<Grid.Row id="theaterContainer">
<Grid.Column
width={this.state.showRightBar ? 12 : 15}
className={
this.state.fullScreen
? 'fullHeightColumnFullscreen'
: 'fullHeightColumn'
}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
}}
>
{!this.state.fullScreen && (
<React.Fragment>
<ComboBox
setMedia={this.setMedia}
playlistAdd={this.playlistAdd}
playlistDelete={this.playlistDelete}
playlistMove={this.playlistMove}
currentMedia={this.state.currentMedia}
getMediaDisplayName={this.getMediaDisplayName}
launchMultiSelect={this.launchMultiSelect}
streamPath={this.props.streamPath}
mediaPath={this.state.mediaPath}
disabled={!this.haveLock()}
playlist={this.state.playlist}
/>
<Separator />
<div
className="mobileStack"
style={{ display: 'flex', gap: '4px' }}
>
{this.screenShareStream && (
<Button
fluid
className="toolButton"
icon
labelPosition="left"
color="red"
onClick={this.stopScreenShare}
disabled={sharer?.id !== this.socket?.id}
>
<Icon name="cancel" />
Stop Share
</Button>
)}
{!this.screenShareStream &&
!sharer &&
!this.isVBrowser() && (
<Popup
content={`Share a tab or an application. Make sure to check "Share audio" for best results.`}
trigger={
<Button
fluid
className="toolButton"
disabled={!this.haveLock()}
icon
labelPosition="left"
color={'instagram'}
onClick={() => {
this.setState({
isScreenShareModalOpen: true,
});
}}
>
<Icon name={'slideshare'} />
Screenshare
</Button>
}
/>
)}
{!this.screenShareStream &&
!sharer &&
!this.isVBrowser() && (
<Popup
content="Launch a shared virtual browser"
trigger={
<Button
fluid
className="toolButton"
disabled={!this.haveLock()}
icon
labelPosition="left"
color="green"
onClick={() => {
this.setState({
isVBrowserModalOpen: true,
});
}}
>
<Icon name="desktop" />
VBrowser
</Button>
}
/>
)}
{this.isVBrowser() && (
<Popup
content="Choose the person controlling the VBrowser"
trigger={
<Dropdown
icon="keyboard"
labeled
className="icon"
style={{ height: '36px' }}
button
value={this.state.controller}
placeholder="No controller"
clearable
onChange={this.changeController}
selection
disabled={!this.haveLock()}
options={this.state.participants.map((p) => ({
text: this.state.nameMap[p.id] || p.id,
value: p.id,
}))}
></Dropdown>
}
/>
)}
{this.isVBrowser() && (
<Dropdown
icon="desktop"
labeled
className="icon"
style={{ height: '36px' }}
button
disabled={!this.haveLock()}
value={this.state.vBrowserResolution}
onChange={(_e, data) =>
this.setState({
vBrowserResolution: data.value as string,
})
}
selection
options={[
{
text: '1080p (Plus only)',
value: '1920x1080@30',
disabled: !this.state.isVBrowserLarge,
},
{
text: '720p',
value: '1280x720@30',
},
{
text: '576p',
value: '1024x576@60',
},
{
text: '486p',
value: '864x486@60',
},
{
text: '360p',
value: '640x360@60',
},
]}
></Dropdown>
)}
{this.isVBrowser() && (
<Button
fluid
className="toolButton"
icon
labelPosition="left"
color="red"
disabled={!this.haveLock()}
onClick={this.stopVBrowser}
>
<Icon name="cancel" />
Stop VBrowser
</Button>
)}
{!this.screenShareStream &&
!sharer &&
!this.isVBrowser() && (
<Popup
content="Stream your own video file"
trigger={
<Button
fluid
className="toolButton"
disabled={!this.haveLock()}
icon
labelPosition="left"
onClick={() => {
this.setState({
isFileShareModalOpen: true,
});
}}
>
<Icon name="file" />
File
</Button>
}
/>
)}
{false && (
<SearchComponent
setMedia={this.setMedia}
playlistAdd={this.playlistAdd}
type={'youtube'}
streamPath={this.props.streamPath}
disabled={!this.haveLock()}
/>
)}
{Boolean(this.props.streamPath) && (
<SearchComponent
setMedia={this.setMedia}
playlistAdd={this.playlistAdd}
type={'stream'}
streamPath={this.props.streamPath}
launchMultiSelect={this.launchMultiSelect}
disabled={!this.haveLock()}
/>
)}
</div>
<Separator />
</React.Fragment>
)}
<div style={{ flexGrow: 1 }}>
<div id="playerContainer">
{(this.state.loading ||
!this.state.currentMedia ||
this.state.nonPlayableMedia) && (
<div
id="loader"
className="videoContent"
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{this.state.loading && (
<Dimmer active>
<Loader>
{this.isVBrowser()
? 'Launching virtual browser. This can take up to a minute.'
: ''}
</Loader>
</Dimmer>
)}
{!this.state.loading && !this.state.currentMedia && (
<Message
color="yellow"
icon="hand point up"
header="You're not watching anything!"
content="Pick something to watch above."
/>
)}
{!this.state.loading &&
this.state.nonPlayableMedia && (
<Message
color="red"
icon="frown"
header="It doesn't look like this is a media file!"
content="Maybe you meant to launch a VBrowser if you're trying to visit a web page?"
/>
)}
</div>
)}
<iframe
style={{
display:
this.isYouTube() && !this.state.loading
? 'block'
: 'none',
}}
title="YouTube"
id="leftYt"
className="videoContent"
allowFullScreen
frameBorder="0"
allow="autoplay"
src="https://www.youtube.com/embed/?enablejsapi=1&controls=0&rel=0"
/>
{this.isVBrowser() &&
this.getVBrowserPass() &&
this.getVBrowserHost() ? (
<VBrowser
username={this.socket.id}
password={this.getVBrowserPass()}
hostname={this.getVBrowserHost()}
controlling={this.state.controller === this.socket.id}
setLoadingFalse={this.setLoadingFalse}
resolution={this.state.vBrowserResolution}
doPlay={this.doPlay}
setResolution={(data: string) =>
this.setState({ vBrowserResolution: data })
}
/>
) : (
<video
style={{
display:
(this.isVideo() && !this.state.loading) ||
this.state.fullScreen
? 'block'
: 'none',
width: '100%',
maxHeight:
'calc(100vh - 62px - 36px - 36px - 8px - 41px - 16px)',
}}
id="leftVideo"
onEnded={this.onVideoEnded}
playsInline
></video>
)}
</div>
</div>
{this.state.currentMedia && controls}
{Boolean(this.state.total) && (
<div>
<Progress
size="tiny"
color="green"
inverted
value={this.state.downloaded}
total={this.state.total}
// indicating
label={
Math.min(
(this.state.downloaded / this.state.total) * 100,
100
).toFixed(2) +
'% - ' +
formatSpeed(this.state.speed) +
' - ' +
this.state.connections +
' connections'
}
></Progress>
</div>
)}
</div>
<Button
style={{
position: 'absolute',
top: '50%',
right: 'calc(0% - 18px)',
zIndex: 900,
}}
circular
size="mini"
icon={this.state.showRightBar ? 'angle right' : 'angle left'}
onClick={() =>
this.setState({ showRightBar: !this.state.showRightBar })
}
/>
</Grid.Column>
{rightBar}
</Grid.Row>
</Grid>
}
</React.Fragment>
);
}
Example #16
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 #17
Source File: VBrowserModal.tsx From watchparty with MIT License | 4 votes |
render() {
const { closeModal, startVBrowser } = this.props;
const LaunchButton = withGoogleReCaptcha(
({ googleReCaptchaProps, large }: any) => (
<Button
size="large"
color={large ? 'orange' : undefined}
onClick={async () => {
const rcToken = await (
googleReCaptchaProps as any
).executeRecaptcha('launchVBrowser');
startVBrowser(rcToken, {
size: large ? 'large' : '',
region: this.state.region,
provider: this.state.provider,
});
closeModal();
}}
>
{large ? 'Launch VBrowser+' : 'Continue with Free'}
</Button>
)
);
const vmPoolFullMessage = (
<Message
size="small"
color="red"
icon="hourglass two"
style={{ width: '380px' }}
header="No Free VBrowsers Available"
content={
<div>
<div>All of the free VBrowsers are currently being used.</div>
<div>
Please consider subscribing for anytime access to faster
VBrowsers, or try again later.
</div>
</div>
}
/>
);
return (
<GoogleReCaptchaProvider
reCaptchaKey={process.env.REACT_APP_RECAPTCHA_SITE_KEY as string}
useRecaptchaNet
>
<Modal open={true} onClose={closeModal as any}>
<Modal.Header>Launch a VBrowser</Modal.Header>
<Modal.Content image>
<Modal.Description>
<div>
You're about to launch a virtual browser to share in this room.
</div>
<Table definition unstackable striped celled>
<Table.Header>
<Table.Row>
<Table.HeaderCell />
<Table.HeaderCell>WatchParty Free</Table.HeaderCell>
<Table.HeaderCell>WatchParty Plus</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>VBrowser Resolution</Table.Cell>
<Table.Cell>720p</Table.Cell>
<Table.Cell>1080p</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>VBrowser CPU/RAM</Table.Cell>
<Table.Cell>Standard</Table.Cell>
<Table.Cell>Extra</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>VBrowser Session Length</Table.Cell>
<Table.Cell>3 hours</Table.Cell>
<Table.Cell>24 hours</Table.Cell>
</Table.Row>
{this.props.beta && (
<Table.Row>
<Table.Cell>Region</Table.Cell>
<Table.Cell colspan={2}>
<Dropdown
selection
onChange={(e, { value }) =>
this.setState({ region: value })
}
value={this.state.region}
options={regionOptions}
></Dropdown>
</Table.Cell>
</Table.Row>
)}
{this.props.beta && (
<Table.Row>
<Table.Cell>Provider</Table.Cell>
<Table.Cell colspan={2}>
<Dropdown
placeholder="Select provider"
selection
onChange={(e, { value }) =>
this.setState({ provider: value })
}
value={this.state.provider}
options={providerOptions}
></Dropdown>
</Table.Cell>
</Table.Row>
)}
<Table.Row>
<Table.Cell></Table.Cell>
<Table.Cell>
{this.props.user ? (
this.state.isVMPoolFull[
this.state.provider + '' + this.state.region
] ? (
vmPoolFullMessage
) : (
<LaunchButton />
)
) : (
<SignInButton fluid user={this.props.user} />
)}
</Table.Cell>
<Table.Cell>
{this.props.isSubscriber ? (
<LaunchButton large />
) : (
this.props.subscribeButton
)}
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</Modal.Description>
</Modal.Content>
</Modal>
</GoogleReCaptchaProvider>
);
}
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: item_info.tsx From website with MIT License | 4 votes |
render() {
const { errorMessage, item_data, items } = this.state;
if (item_data === undefined || errorMessage !== undefined) {
return (
<Layout title='Item information'>
<Header as="h4">Item information</Header>
{errorMessage && (
<Message negative>
<Message.Header>Unable to load item information</Message.Header>
<pre>{errorMessage.toString()}</pre>
</Message>
)}
{!errorMessage && (
<div>
<Icon loading name="spinner" /> Loading...
</div>
)}
</Layout>
);
}
console.log(item_data);
let bonusText = [];
if (item_data.item.bonuses) {
for (let [key, value] of Object.entries(item_data.item.bonuses)) {
let bonus = CONFIG.STATS_CONFIG[Number.parseInt(key)];
if (bonus) {
bonusText.push(`+${value} ${bonus.symbol}`);
} else {
// TODO: what kind of bonus is this?
}
}
}
// TODO: share this code with equipment.ts
let demands = [];
if (item_data.item.recipe) {
for (let iter of item_data.item.recipe.list) {
let recipeEquipment = items.find(item => item.symbol === iter.symbol);
demands.push({
count: iter.count,
symbol: iter.symbol,
equipment: recipeEquipment,
factionOnly: iter.factionOnly
});
}
}
return (
<Layout title={item_data.item.name}>
<Message icon warning>
<Icon name="exclamation triangle" />
<Message.Content>
<Message.Header>Work in progress!</Message.Header>
This section is under development and not fully functional yet.
</Message.Content>
</Message>
<Header as="h3">
{item_data.item.name}{' '}
<Rating icon='star' rating={item_data.item.rarity} maxRating={item_data.item.rarity} size="large" disabled />
</Header>
<Image size="small" src={`${process.env.GATSBY_ASSETS_URL}${item_data.item.imageUrl}`} />
<br />
{bonusText.length > 0 && (
<div>
<p>Bonuses: {bonusText.join(', ')}</p>
<br />
</div>
)}
{item_data.item.recipe && item_data.item.recipe.list && (
<div>
<Header as="h4">Craft it for {item_data.item.recipe.craftCost} chrons using this recipe:</Header>
<Grid columns={3} padded>
{demands.map((entry, idx) => (
<Grid.Column key={idx}>
<Popup
trigger={
<Header
style={{ display: 'flex', cursor: 'zoom-in' }}
icon={
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${entry.equipment.imageUrl}`}
size={48}
maxRarity={entry.equipment.rarity}
rarity={entry.equipment.rarity}
/>
}
content={entry.equipment.name}
subheader={`Need ${entry.count} ${entry.factionOnly ? ' (FACTION)' : ''}`}
/>
}
header={
<Link to={`/item_info?symbol=${entry.symbol}`}>
{CONFIG.RARITIES[entry.equipment.rarity].name + ' ' + entry.equipment.name}
</Link>
}
content={<ItemSources item_sources={entry.equipment.item_sources} />}
on="click"
wide
/>
</Grid.Column>
))}
</Grid>
</div>
)}
{item_data.item.item_sources.length > 0 && (
<div>
<Header as="h4">Item sources</Header>
<ItemSources item_sources={item_data.item.item_sources} />
<br />
</div>
)}
{item_data.crew_levels.length > 0 && (
<div>
<Header as="h4">Equippable by this crew:</Header>
<Grid columns={3} padded>
{item_data.crew_levels.map((entry, idx) => (
<Grid.Column key={idx}>
<Header
style={{ display: 'flex' }}
icon={
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${entry.crew.imageUrlPortrait}`}
size={60}
maxRarity={entry.crew.max_rarity}
rarity={entry.crew.max_rarity}
/>
}
content={<Link to={`/crew/${entry.crew.symbol}/`}>{entry.crew.name}</Link>}
subheader={`Level ${entry.level}`}
/>
</Grid.Column>
))}
</Grid>
</div>
)}
{item_data.builds.length > 0 && (
<div>
<Header as="h4">Is used to build these</Header>
<Grid columns={3} padded>
{item_data.builds.map((entry, idx) => (
<Grid.Column key={idx}>
<Header
style={{ display: 'flex', cursor: 'zoom-in' }}
icon={
<ItemDisplay
src={`${process.env.GATSBY_ASSETS_URL}${entry.imageUrl}`}
size={48}
maxRarity={entry.rarity}
rarity={entry.rarity}
/>
}
content={
<Link to={`/item_info?symbol=${entry.symbol}`}>
{CONFIG.RARITIES[entry.rarity].name + ' ' + entry.name}
</Link>
}
/>
</Grid.Column>
))}
</Grid>
</div>
)}
</Layout>
);
}
Example #21
Source File: fleet_info.tsx From website with MIT License | 4 votes |
render() {
const { fleet_id, errorMessage, fleet_data, factions, events } = this.state;
if (fleet_id === undefined || fleet_data === undefined || errorMessage !== undefined) {
return (
<Layout title='Fleet information'>
<Header as="h4">Fleet information</Header>
{errorMessage && (
<Message negative>
<Message.Header>Unable to load fleet profile</Message.Header>
<pre>{errorMessage.toString()}</pre>
</Message>
)}
{!errorMessage && (
<div>
<Icon loading name="spinner" /> Loading...
</div>
)}
<p>
Are you looking to share your player profile? Go to the <Link to={`/playertools`}>Player Tools page</Link> to
upload your player.json and access other useful player tools.
</p>
</Layout>
);
}
let imageUrl = 'icons_icon_faction_starfleet.png';
if (factions && factions[fleet_data.nicon_index]) {
imageUrl = factions[fleet_data.nicon_index].icon;
}
let event1;
let event2;
let event3;
if (events[0].event_name === fleet_data.leaderboard[0].event_name) {
event1 = events[0];
event2 = events[1];
event3 = events[2];
} else {
event1 = events.find(ev => ev.event_name === fleet_data.leaderboard[0].event_name);
event2 = events.find(ev => ev.event_name === fleet_data.leaderboard[1].event_name);
event3 = events.find(ev => ev.event_name === fleet_data.leaderboard[2].event_name);
}
return (
<Layout title={fleet_data.name}>
<Item.Group>
<Item>
<Item.Image size="tiny" src={`${process.env.GATSBY_ASSETS_URL}${imageUrl}`} />
<Item.Content>
<Item.Header>{fleet_data.name}</Item.Header>
<Item.Meta>
<Label>Starbase level: {fleet_data.nstarbase_level}</Label>
<Label>
Size: {fleet_data.cursize} / {fleet_data.maxsize}
</Label>
<Label>Created: {new Date(fleet_data.created).toLocaleDateString()}</Label>
<Label>
Enrollment {fleet_data.enrollment} (min level: {fleet_data.nmin_level})
</Label>
</Item.Meta>
</Item.Content>
</Item>
</Item.Group>
{event1 && <table>
<tbody>
<tr>
<th>
{' '}
<Link to={`/event_info?instance_id=${event1.instance_id}`}>
{fleet_data.leaderboard[0].event_name}
</Link>
</th>
<th>
{' '}
<Link to={`/event_info?instance_id=${event2.instance_id}`}>
{fleet_data.leaderboard[1].event_name}
</Link>
</th>
<th>
{' '}
<Link to={`/event_info?instance_id=${event3.instance_id}`}>
{fleet_data.leaderboard[2].event_name}
</Link>
</th>
</tr>
<tr>
<td><Image size="medium" src={`${process.env.GATSBY_ASSETS_URL}${event1.image}`} /></td>
<td><Image size="medium" src={`${process.env.GATSBY_ASSETS_URL}${event2.image}`} /></td>
<td><Image size="medium" src={`${process.env.GATSBY_ASSETS_URL}${event3.image}`} /></td>
</tr>
<tr>
<td align="center">Fleet rank: {fleet_data.leaderboard[0].fleet_rank}</td>
<td align="center">Fleet rank: {fleet_data.leaderboard[1].fleet_rank}</td>
<td align="center">Fleet rank: {fleet_data.leaderboard[2].fleet_rank}</td>
</tr>
</tbody>
</table>}
<Header as="h4">Members</Header>
<Table celled selectable striped collapsing unstackable compact="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell width={3}>Name</Table.HeaderCell>
<Table.HeaderCell width={1}>Rank</Table.HeaderCell>
<Table.HeaderCell width={1}>Profile</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{fleet_data.members.map((member, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}
>
<div style={{ gridArea: 'icon' }}>
<img
width={48}
src={`${process.env.GATSBY_ASSETS_URL}${member.crew_avatar || 'crew_portraits_cm_empty_sm.png'}`}
/>
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{member.last_update ? (
<Link to={`/profile?dbid=${member.dbid}`}>{member.display_name}</Link>
) : (
<span>{member.display_name}</span>
)}
</span>
</div>
<div style={{ gridArea: 'description' }}>
{member.last_update && (
<Label size="tiny">
Last profile upload: {new Date(Date.parse(member.last_update)).toLocaleDateString()}
</Label>
)}
</div>
</div>
</Table.Cell>
<Table.Cell>{member.rank}</Table.Cell>
<Table.Cell>
{member.last_update
? `Last profile upload: ${new Date(Date.parse(member.last_update)).toLocaleDateString()}`
: 'Never'}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</Layout>
);
}
Example #22
Source File: events.tsx From website with MIT License | 4 votes |
function EventsPage() {
const [eventsData, setEventsData] = React.useState<EventInstance[]>([]);
const [leaderboardData, setLeaderboardData] = React.useState(null);
const [loadingError, setLoadingError] = React.useState(null);
const [modalEventInstance, setModalEventInstance] = React.useState(null);
// load the events and leaderboard data once on component mount
React.useEffect(() => {
async function loadData() {
try {
const fetchEventResp = await fetch('/structured/event_instances.json')
const eventDataList = await fetchEventResp.json();
setEventsData(eventDataList.reverse());
const fetchLeaderboardResp = await fetch('/structured/event_leaderboards.json');
const leaderboardDataList = await fetchLeaderboardResp.json();
const keyedLeaderboard = {};
leaderboardDataList.forEach(entry => keyedLeaderboard[entry.instance_id] = entry);
setLeaderboardData(keyedLeaderboard);
}
catch (e) {
setLoadingError(e);
}
}
loadData();
}, []);
return (
<Layout>
<Container style={{ paddingTop: '4em', paddingBottom: '2em' }}>
<Header as='h2'>Events</Header>
{loadingError && (
<Message negative>
<Message.Header>Unable to load event information</Message.Header>
<pre>{loadingError.toString()}</pre>
</Message>
)}
<Grid stackable columns={3}>
{eventsData.map(eventInfo => (
<Grid.Column key={eventInfo.instance_id}>
<div
style={{ cursor: 'pointer' }}
onClick={() => setModalEventInstance(eventInfo)}
>
<Segment padded>
<Label attached="bottom">
{eventInfo.event_name}
</Label>
<LazyImage
src={`${process.env.GATSBY_ASSETS_URL}${eventInfo.image}`}
size="large"
onError={e => e.target.style.visibility = 'hidden'}
/>
</Segment>
</div>
</Grid.Column>
))}
</Grid>
{modalEventInstance !== null && (
<Modal
open
size="large"
onClose={() => setModalEventInstance(null)}
closeIcon
>
<Modal.Header>{modalEventInstance.event_name}</Modal.Header>
<Modal.Content scrolling>
<EventInfoModal
instanceId={modalEventInstance.instance_id}
image={modalEventInstance.image}
hasDetails={modalEventInstance.event_details}
leaderboard={leaderboardData[modalEventInstance.instance_id].leaderboard}
/>
</Modal.Content>
</Modal>
)}
</Container>
</Layout>
);
}
Example #23
Source File: event_info.tsx From website with MIT License | 4 votes |
render() {
const { event_instace, errorMessage, event_data } = this.state;
if (event_instace === undefined || event_data === undefined || errorMessage !== undefined) {
return (
<Layout title='Event information'>
<Header as='h4'>Event information</Header>
{errorMessage && (
<Message negative>
<Message.Header>Unable to load event information</Message.Header>
<pre>{errorMessage.toString()}</pre>
</Message>
)}
{!errorMessage && (
<div>
<Icon loading name='spinner' /> Loading...
</div>
)}
</Layout>
);
}
return (
<Layout title={event_data.ev_inst.event_name}>
<Header as='h3'>{event_data.ev_inst.event_name}</Header>
<Image size='large' src={`${process.env.GATSBY_ASSETS_URL}${event_data.ev_inst.image}`} />
{this.renderEventDetails()}
{this.renderEventLog()}
<Header as='h4'>Leaderboard</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={3}>Name</Table.HeaderCell>
<Table.HeaderCell width={1}>Rank</Table.HeaderCell>
<Table.HeaderCell width={1}>Score</Table.HeaderCell>
<Table.HeaderCell width={2}>Fleet</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event_data.ev_lead.leaderboard.map((member, idx) => (
<Table.Row key={idx}>
<Table.Cell>
<div
style={{
display: 'grid',
gridTemplateColumns: '60px auto',
gridTemplateAreas: `'icon stats' 'icon description'`,
gridGap: '1px'
}}>
<div style={{ gridArea: 'icon' }}>
<img
width={48}
src={`${process.env.GATSBY_ASSETS_URL}${member.avatar ? member.avatar.file.substr(1).replace(/\//g, '_') + '.png' : 'crew_portraits_cm_empty_sm.png'
}`}
/>
</div>
<div style={{ gridArea: 'stats' }}>
<span style={{ fontWeight: 'bolder', fontSize: '1.25em' }}>
{member.last_update ? (
<Link to={`/profile?dbid=${member.dbid}`}>{member.display_name}</Link>
) : (
<span>{member.display_name}</span>
)}
</span>
</div>
<div style={{ gridArea: 'description' }}>
Level {member.level}
{member.last_update && (
<Label size='tiny'>Last profile upload: {new Date(Date.parse(member.last_update)).toLocaleDateString()}</Label>
)}
</div>
</div>
</Table.Cell>
<Table.Cell>{member.rank}</Table.Cell>
<Table.Cell>{member.score}</Table.Cell>
<Table.Cell>
{member.fleetname ? <Link to={`/fleet_info?fleetid=${member.fleetid}`}>
<b>{member.fleetname}</b>
</Link> : <span>-</span>}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
{event_data.ev_flead &&
<div>
<Message>
<Message.Header>TODO: Fleet Leaderboard is experimental</Message.Header>
This data may be incomplete or out of date!
</Message>
<Header as='h4'>Fleet leaderboard</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={3}>Name</Table.HeaderCell>
<Table.HeaderCell width={1}>Rank</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event_data.ev_flead.fleet_ranks.map((fleet, idx) => (
<Table.Row key={idx}>
<Table.Cell>{fleet.fleet_rank}</Table.Cell>
<Table.Cell>
<Link to={`/fleet_info?fleetid=${fleet.id}`}>
<b>{fleet.name}</b>
</Link>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
</div>}
</Layout>
);
}
Example #24
Source File: event_info.tsx From website with MIT License | 4 votes |
renderEventDetails() {
const { event_data } = this.state;
if (event_data === undefined || event_data.event_details === undefined) {
return <span />;
}
let event = event_data.event_details;
return (
<div>
<Message icon warning>
<Icon name='exclamation triangle' />
<Message.Content>
<Message.Header>Work in progress!</Message.Header>
This section is under development and not fully functional yet.
</Message.Content>
</Message>
<p>{event.description}</p>
<p>{event.rules}</p>
<Label>{event.bonus_text}</Label>
{event.content.map((cnt, idx) => {
let crew_bonuses = undefined;
if (cnt.shuttles) {
crew_bonuses = cnt.shuttles[0].crew_bonuses;
} else if (cnt.crew_bonuses) {
crew_bonuses = cnt.crew_bonuses;
} else if (cnt.bonus_crew && cnt.bonus_traits) {
// Skirmishes
crew_bonuses = {};
cnt.bonus_crew.forEach(element => {
crew_bonuses[element] = 10;
});
// TODO: crew from traits
} else if (cnt.special_crew) {
// Expeditions
crew_bonuses = {};
cnt.special_crew.forEach(element => {
crew_bonuses[element] = 50;
});
}
return (
<div key={idx}>
<Header as='h5'>Phase {idx + 1}</Header>
<Label>Type: {cnt.content_type}</Label>
<Header as='h6'>Bonus crew</Header>
{crew_bonuses && (
<p>
{Object.entries(crew_bonuses).map(([bonus, val], idx) => (
<span key={idx}>
{bonus} ({val}),
</span>
))}
</p>
)}
</div>
);
})}
<Header as='h4'>Threshold rewards</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={2}>Points</Table.HeaderCell>
<Table.HeaderCell width={4}>Reward</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event.threshold_rewards.filter(reward => reward.rewards && reward.rewards.length > 0).map((reward, idx) => (
<Table.Row key={idx}>
<Table.Cell>{reward.points}</Table.Cell>
<Table.Cell>
{reward.rewards[0].quantity} {reward.rewards[0].full_name}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Header as='h4'>Ranked rewards</Header>
<Table celled selectable striped collapsing unstackable compact='very'>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={2}>Ranks</Table.HeaderCell>
<Table.HeaderCell width={4}>Rewards</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{event.ranked_brackets.map((bracket, idx) => (
<Table.Row key={idx}>
<Table.Cell>
{bracket.first} - {bracket.last}
</Table.Cell>
<Table.Cell>
{bracket.rewards.map((reward, idx) => (
<span key={idx}>
{reward.quantity} {reward.full_name},
</span>
))}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<Header as='h4'>Quest</Header>
{event.quest ? event.quest.map((quest, idx) => (
<div key={idx}>
{quest && quest.screens && quest.screens.map((screen, idx) => (
<p key={idx}>
<b>{screen.speaker_name}: </b>
{screen.text}
</p>
))}
</div>
)) : <span>Mini-events don't include quest information.</span>}
<Message>
<Message.Header>TODO: Leaderboard out of date</Message.Header>
If this event is currently active, the leaderboard below is out of date (updated only a couple of times a week).
</Message>
</div>
);
}
Example #25
Source File: voyagestats.tsx From website with MIT License | 4 votes |
/* Not yet in use
_renderReminder() {
return (
<div>
<p>Remind me :-</p>
<Form.Field
control={Checkbox}
label={<label>At the next dilemma.</label>}
checked={this.state.dilemmaAlarm}
onChange={(e, { checked }) => this.setState({ dilemmaAlarm: checked}) }
/>
<Form.Field
control={Checkbox}
label={<label>When the probably of voyage still running reaches {oddsControl}.</label>}
checked={this.state.failureAlarm}
onChange={(e, {checked}) => this.setState({failureAlarm : checked}) }
/>
</div>
);
}
*/
render() {
const { voyageData } = this.props;
if (!voyageData)
return (<Dimmer active>
<Loader>Calculating...</Loader>
</Dimmer>);
const { activePanels } = this.state;
const voyState = voyageData.state;
const rewards = {
'pending': () => [],
'started': () => voyageData.pending_rewards.loot,
'failed': () => voyageData.pending_rewards.loot,
'recalled': () => voyageData.pending_rewards.loot,
'completed': () => voyageData.granted_rewards.loot
}[voyState]();
// Adds/Removes panels from the active list
const flipItem = (items, item) => items.includes(item)
? items.filter(i => i != item)
: items.concat(item);
const handleClick = (e, {index}) =>
this.setState({
activePanels: flipItem(activePanels, index)
});
const accordionPanel = (title, content, key, ctitle = false) => {
const collapsedTitle = ctitle ? ctitle : title;
const isActive = activePanels.includes(key);
return (
<Accordion.Panel
active={isActive}
index={key}
onTitleClick={handleClick}
title={isActive ? {icon: 'caret down', content: title} : {icon: 'caret right', content: collapsedTitle}}
content={{content: <Segment>{content}</Segment>}}/>
);
};
if (voyState !== 'pending') {
return (
<div>
{Math.floor(voyageData.log_index/360) < Math.floor(voyageData.voyage_duration/7200) &&
<Message warning>
WARNING!!! A potential problem with the reported voyage duration has been detected.
The estimate is likely to be inaccurate.
Load the game then return to Datacore with a fresh copy of your player file to get an accurate estimate.
</Message>
}
<Message>Your voyage {voyState === 'failed' ? 'failed at ' : 'has been running for ' + this._formatTime(voyageData.voyage_duration/3600)}.</Message>
<Accordion fluid exclusive={false}>
{
voyState !== 'recalled' && voyState !== 'completed' &&
accordionPanel('Voyage estimate', this._renderEstimate(voyState === 'failed'), 'estimate', this._renderEstimateTitle())
}
{ accordionPanel('Voyage lineup', this._renderCrew(), 'crew') }
{
accordionPanel('Rewards', this._renderRewards(rewards), 'rewards', this._renderRewardsTitle(rewards))
}
</Accordion>
</div>
);
} else {
return (
<div>
<Accordion fluid exclusive={false}>
{ accordionPanel('Voyage estimate', this._renderEstimate(false), 'estimate', this._renderEstimateTitle()) }
{ accordionPanel('Voyage lineup', this._renderCrew(), 'crew') }
</Accordion>
</div>
);
}
}
Example #26
Source File: SettingsTab.tsx From watchparty with MIT License | 4 votes |
SettingsTab = ({
hide,
user,
roomLock,
setRoomLock,
socket,
isSubscriber,
owner,
vanity,
setVanity,
roomLink,
password,
setPassword,
isChatDisabled,
setIsChatDisabled,
clearChat,
roomTitle,
roomDescription,
roomTitleColor,
mediaPath,
setMediaPath,
}: SettingsTabProps) => {
const [updateTS, setUpdateTS] = useState(0);
const [permModalOpen, setPermModalOpen] = useState(false);
const [validVanity, setValidVanity] = useState(true);
const [validVanityLoading, setValidVanityLoading] = useState(false);
const [adminSettingsChanged, setAdminSettingsChanged] = useState(false);
const [roomTitleInput, setRoomTitleInput] = useState<string | undefined>(undefined);
const [roomDescriptionInput, setRoomDescriptionInput] = useState<
string | undefined
>(undefined);
const [roomTitleColorInput, setRoomTitleColorInput] = useState<
string | undefined
>('');
const setRoomState = useCallback(
async (data: any) => {
const token = await user?.getIdToken();
socket.emit('CMD:setRoomState', {
uid: user?.uid,
token,
...data,
});
},
[socket, user]
);
const setRoomOwner = useCallback(
async (data: any) => {
const token = await user?.getIdToken();
socket.emit('CMD:setRoomOwner', {
uid: user?.uid,
token,
...data,
});
},
[socket, user]
);
const checkValidVanity = useCallback(
async (input: string) => {
if (!input) {
setValidVanity(true);
return;
}
setValidVanity(false);
setValidVanityLoading(true);
const response = await axios.get(serverPath + '/resolveRoom/' + input);
const data = response.data;
setValidVanityLoading(false);
if (
data &&
data.vanity &&
data.vanity !== roomLink.split('/').slice(-1)[0]
) {
// Already exists and doesn't match current room
setValidVanity(false);
} else {
setValidVanity(true);
}
},
[setValidVanity, roomLink]
);
const disableLocking =
!Boolean(user) || Boolean(roomLock && roomLock !== user?.uid);
const disableOwning = !Boolean(user) || Boolean(owner && owner !== user?.uid);
return (
<div
style={{
display: hide ? 'none' : 'block',
color: 'white',
overflow: 'scroll',
padding: '8px',
}}
>
{permModalOpen && (
<PermanentRoomModal
closeModal={() => setPermModalOpen(false)}
></PermanentRoomModal>
)}
<div className="sectionHeader">Room Settings</div>
{!user && (
<Message color="yellow" size="tiny">
You need to be signed in to change these settings.
</Message>
)}
<SettingRow
icon={roomLock ? 'lock' : 'lock open'}
name={`Lock Room`}
description="Only the person who locked the room can control the video."
checked={Boolean(roomLock)}
disabled={disableLocking && disableOwning}
onChange={(_e, data) => setRoomLock(data.checked)}
/>
{
<SettingRow
icon={'clock'}
name={`Make Room Permanent`}
description={
'Prevent this room from expiring. This also unlocks additional room features.'
}
helpIcon={
<Icon
name="help circle"
onClick={() => setPermModalOpen(true)}
style={{ cursor: 'pointer' }}
></Icon>
}
checked={Boolean(owner)}
disabled={disableOwning}
onChange={(_e, data) => setRoomOwner({ undo: !data.checked })}
/>
}
{owner && owner === user?.uid && (
<div className="sectionHeader">Admin Settings</div>
)}
{owner && owner === user?.uid && (
<SettingRow
icon={'key'}
name={`Set Room Password`}
description="Users must know this password in order to join the room."
content={
<Input
value={password}
size="mini"
onChange={(e) => {
setAdminSettingsChanged(true);
setPassword(e.target.value);
}}
fluid
/>
}
disabled={false}
/>
)}
{owner && owner === user?.uid && (
<SettingRow
icon={'folder'}
name={`Set Room Media Source`}
description="Set a media source URL with files to replace the default examples. Supports S3 buckets and nginx file servers."
content={
<Input
value={mediaPath}
size="mini"
onChange={(e) => {
setAdminSettingsChanged(true);
setMediaPath(e.target.value);
}}
fluid
/>
}
disabled={false}
/>
)}
{owner && owner === user?.uid && (
<SettingRow
icon={'i cursor'}
name={`Disable Chat`}
description="Prevent users from sending messages in chat."
checked={Boolean(isChatDisabled)}
disabled={false}
onChange={(_e, data) => {
setAdminSettingsChanged(true);
setIsChatDisabled(Boolean(data.checked));
}}
/>
)}
{owner && owner === user?.uid && (
<SettingRow
icon={'i delete'}
name={`Clear Chat`}
description="Delete all existing chat messages"
disabled={false}
content={
<Button
color="red"
icon
labelPosition="left"
onClick={() => clearChat()}
>
<Icon name="delete" />
Delete Messages
</Button>
}
/>
)}
{owner && owner === user?.uid && (
<SettingRow
icon={'linkify'}
name={`Set Custom Room URL`}
description="Set a custom URL for this room. Inappropriate names may be revoked."
checked={Boolean(roomLock)}
disabled={!isSubscriber}
subOnly={true}
content={
<React.Fragment>
<Input
value={vanity}
disabled={!isSubscriber}
onChange={(e) => {
setAdminSettingsChanged(true);
checkValidVanity(e.target.value);
setVanity(e.target.value);
}}
label={<Label>{`${window.location.origin}/r/`}</Label>}
loading={validVanityLoading}
fluid
size="mini"
icon
action={
validVanity ? (
<Icon name="checkmark" color="green" />
) : (
<Icon name="close" color="red" />
)
}
></Input>
</React.Fragment>
}
/>
)}
{owner && owner === user?.uid && (
<SettingRow
icon={'pencil'}
name={`Set Room Title, Description & Color`}
description="Set the room title, description and title color to be displayed in the top bar."
disabled={!isSubscriber}
subOnly={true}
content={
<React.Fragment>
<div style={{ display: 'flex', marginBottom: 2 }}>
<Input
style={{ marginRight: 3, flexGrow: 1 }}
value={roomTitleInput ?? roomTitle}
disabled={!isSubscriber}
maxLength={roomTitleMaxCharLength}
onChange={(e) => {
setAdminSettingsChanged(true);
setRoomTitleInput(e.target.value);
}}
placeholder={`Title (max. ${roomTitleMaxCharLength} characters)`}
fluid
size="mini"
icon
></Input>
<Popup
content={
<React.Fragment>
<h5>Edit Title Color</h5>
<HexColorPicker
color={
roomTitleColorInput ||
roomTitleColor ||
defaultRoomTitleColor
}
onChange={(e) => {
setAdminSettingsChanged(true);
setRoomTitleColorInput(e);
}}
/>
<div
style={{
marginTop: 8,
paddingLeft: 4,
borderLeft: `24px solid ${roomTitleColorInput}`,
}}
>
{roomTitleColorInput?.toUpperCase()}
</div>
</React.Fragment>
}
on="click"
trigger={
<Button
icon
color="teal"
size="tiny"
style={{ margin: 0 }}
disabled={!isSubscriber}
>
<Icon name="paint brush" />
</Button>
}
/>
</div>
<Input
style={{ marginBottom: 2 }}
value={roomDescriptionInput ?? roomDescription}
disabled={!isSubscriber}
maxLength={roomDescriptionMaxCharLength}
onChange={(e) => {
setAdminSettingsChanged(true);
setRoomDescriptionInput(e.target.value);
}}
placeholder={`Description (max. ${roomDescriptionMaxCharLength} characters)`}
fluid
size="mini"
icon
></Input>
</React.Fragment>
}
/>
)}
<div
style={{
borderTop: '3px dashed white',
marginTop: 10,
marginBottom: 10,
}}
/>
{owner && owner === user?.uid && (
<Button
primary
disabled={!validVanity || !adminSettingsChanged}
labelPosition="left"
icon
fluid
onClick={() => {
setRoomState({
vanity: vanity,
password: password,
isChatDisabled: isChatDisabled,
roomTitle: roomTitleInput ?? roomTitle,
roomDescription: roomDescriptionInput ?? roomDescription,
roomTitleColor:
roomTitleColorInput || roomTitleColor || defaultRoomTitleColor,
mediaPath: mediaPath,
});
setAdminSettingsChanged(false);
}}
>
<Icon name="save" />
Save Admin Settings
</Button>
)}
<div className="sectionHeader">Local Settings</div>
<SettingRow
updateTS={updateTS}
icon="bell"
name="Disable chat notification sound"
description="Don't play a sound when a chat message is sent while you're on another tab"
checked={Boolean(getCurrentSettings().disableChatSound)}
disabled={false}
onChange={(_e, data) => {
updateSettings(
JSON.stringify({
...getCurrentSettings(),
disableChatSound: data.checked,
})
);
setUpdateTS(Number(new Date()));
}}
/>
</div>
);
}
Example #27
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 #28
Source File: ProfileWidgetPlus.tsx From communitymap-ui with Apache License 2.0 | 4 votes |
EditUserProfile: React.FC<{ user: firebase.User }> = ({ user }) => {
const [toggled, setToggle] = useState(false);
const info = useUserPublicInfo(user.uid, true);
const [changed, setChanged] = useState<{ name: string } | null>(null);
const { status, func: saveInfo } = useAsyncStatus(async () => {
return saveUserPublicInfo({
...(info || { id: user.uid }),
...(changed || { name: '' }),
});
});
if (info === undefined) return <Loader active />;
// if (info === null) return <div>User not found :(</div>;
const UserForm = () => (
<Form
onSubmit={() => saveInfo()}
loading={status.pending}
error={!!status.error}
success={status.success}
>
<Form.Input
label="Name/Nickname"
required
value={changed?.name || info?.name || ''}
onChange={(e, { value }) => setChanged({ ...changed, name: value })}
/>
{/* <Form.Input label="Gender"/> */}
<Message error>{status.error}</Message>
<Message success>Successfully saved</Message>
<Form.Group>
<Form.Button primary disabled={!changed}>
Save
</Form.Button>
<Form.Button
type="button"
disabled={!changed}
onClick={() => setChanged(null)}
>
Clear changes
</Form.Button>
</Form.Group>
</Form>
);
const BtnSettings = () => (
<Button
circular
icon="settings"
onClick={() => setToggle((toggled) => !toggled)}
/>
);
return (
<Card>
<Image
src="http://dwinery.com/ocm/wp-content/uploads/elementor/thumbs/avatar-ookvknm0adkt2o1cwyctxzbsfccgeo4fo00qr8xhao.png"
wrapped
ui={false}
/>
<Card.Content>
<Card.Header>
<Grid>
<Grid.Column floated="left" width={8}>
{info?.name}
</Grid.Column>
<Grid.Column floated="right" width={3}>
{BtnSettings()}
</Grid.Column>
</Grid>
</Card.Header>
<Card.Meta>
<span className="date">Joined in: {info?.created}</span>
</Card.Meta>
<Card.Description>{toggled && <>{UserForm()}</>}</Card.Description>
</Card.Content>
<Card.Content extra>
<Grid columns={2} divided>
<Grid.Row>
<Grid.Column>
<Icon name="handshake" /> 11 People
</Grid.Column>
<Grid.Column>
<Icon name="globe" /> 12 Places
</Grid.Column>
</Grid.Row>
</Grid>
</Card.Content>
</Card>
);
}
Example #29
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);
}
}