semantic-ui-react#Modal TypeScript Examples

The following examples show how to use semantic-ui-react#Modal. 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: SettingControl.tsx    From FLECT_Amazon_Chime_Meeting with Apache License 2.0 6 votes vote down vote up
generateSettingModal = () => {
        return (
            <Modal onClose={this.settingClose} open={this.state.open}>
            <Modal.Header>Setting</Modal.Header>
            <Modal.Content>
                <Grid>
                <Grid.Row>
                    Virtual background
                    </Grid.Row>
                <Grid.Row>
                    {this.generateVGSettingPanal()}
                </Grid.Row>
                </Grid>
            </Modal.Content>
            <Button content='Close' negative onClick={this.settingClose} />
            </Modal>
        )
    }
Example #2
Source File: index.tsx    From frontegg-react with MIT License 6 votes vote down vote up
Dialog: FC<DialogProps> = (props) => {
  const modalProps = dialogPropsMapper(props);
  return (
    <Modal {...modalProps}>
      {props.header && <Modal.Header className='fe-dialog__header'>{props.header}</Modal.Header>}
      <Modal.Content className='fe-dialog__content'>{props.children}</Modal.Content>
    </Modal>
  );
}
Example #3
Source File: ErrorModal.tsx    From watchparty with MIT License 6 votes vote down vote up
ErrorModal = ({ error }: { error: string }) => {
  return (
    <Modal inverted basic open>
      <Header as="h1" style={{ textAlign: 'center' }}>
        {error}
      </Header>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Button
          primary
          size="huge"
          onClick={() => {
            window.location.href = '/';
          }}
          icon
          labelPosition="left"
        >
          <Icon name="home" />
          Go to home page
        </Button>
      </div>
    </Modal>
  );
}
Example #4
Source File: DirectMessage.tsx    From communitymap-ui with Apache License 2.0 6 votes vote down vote up
DirectMessageModal: React.FC<{ onClose: () => void }> = ({
  onClose,
}) => {
  const { header, content, action } = useDirectMessageView();
  return (
    <Modal
      open
      closeIcon
      size="tiny"
      className="modal-direct-message"
      onClose={onClose}
    >
      <Modal.Header>{header}</Modal.Header>
      <Modal.Content scrolling>{content}</Modal.Content>
      <Modal.Actions>{action}</Modal.Actions>
    </Modal>
  );
}
Example #5
Source File: MultiStreamModal.tsx    From watchparty with MIT License 6 votes vote down vote up
MultiStreamModal = ({
  streams,
  setMedia,
  resetMultiSelect,
}: {
  streams: any[];
  setMedia: Function;
  resetMultiSelect: Function;
}) => (
  <Modal inverted basic open closeIcon onClose={resetMultiSelect as any}>
    <Modal.Header>Select a file</Modal.Header>
    <Modal.Content>
      {streams.length === 0 && <Loader />}
      {streams && (
        <List inverted>
          {streams.map((file: any) => (
            <List.Item>
              <List.Icon name="file" />
              <List.Content>
                <List.Header
                  as="a"
                  onClick={() => {
                    setMedia(null, { value: file.url });
                    resetMultiSelect();
                  }}
                >
                  {file.name}
                </List.Header>
                <List.Description>
                  {file.length.toLocaleString()} bytes
                </List.Description>
              </List.Content>
            </List.Item>
          ))}
        </List>
      )}
    </Modal.Content>
  </Modal>
)
Example #6
Source File: PasswordModal.tsx    From watchparty with MIT License 6 votes vote down vote up
PasswordModal = ({
  savedPasswords,
  roomId,
}: {
  savedPasswords: StringDict;
  roomId: string;
}) => {
  const setPassword = useCallback(() => {
    window.localStorage.setItem(
      'watchparty-passwords',
      JSON.stringify({
        ...savedPasswords,
        [roomId]: (document.getElementById('roomPassword') as HTMLInputElement)
          ?.value,
      })
    );
    window.location.reload();
  }, [savedPasswords, roomId]);
  return (
    <Modal inverted basic open>
      <Header as="h1" style={{ textAlign: 'center' }}>
        This room requires a password.
      </Header>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Input
          id="roomPassword"
          type="password"
          size="large"
          onKeyPress={(e: any) => e.key === 'Enter' && setPassword()}
          icon={
            <Icon onClick={setPassword} name="key" inverted circular link />
          }
        />
      </div>
    </Modal>
  );
}
Example #7
Source File: LocalSettings.tsx    From watchparty with MIT License 6 votes vote down vote up
SettingsModal = ({ trigger }: any) => (
  <Modal trigger={trigger} basic closeIcon size="small">
    <Header icon="setting" content="Settings" />
    <Modal.Content>
      <Form>
        <TextArea rows={10} id="settings_textarea">
          {window.localStorage.getItem('watchparty-setting') ||
            JSON.stringify(getDefaultSettings(), null, 2)}
        </TextArea>
      </Form>
    </Modal.Content>
    <Modal.Actions>
      <Button
        color="green"
        inverted
        onClick={() => {
          const newSetting = (document.getElementById(
            'settings_textarea'
          ) as HTMLTextAreaElement)!.value;
          try {
            validateSettingsString(newSetting);
            updateSettings(newSetting);
            window.location.reload();
          } catch (e) {
            alert(e);
          }
        }}
      >
        <Icon name="checkmark" />
        Save
      </Button>
    </Modal.Actions>
  </Modal>
)
Example #8
Source File: ScreenShareModal.tsx    From watchparty with MIT License 6 votes vote down vote up
render() {
    const { closeModal } = this.props;
    return (
      <Modal open={true} onClose={closeModal as any}>
        <Modal.Header>Share Your Screen</Modal.Header>
        <Modal.Content image>
          <Modal.Description>
            <div>You're about to share your screen.</div>
            <ul>
              <li>This feature is only supported on Chrome and Edge.</li>
              <li>
                To share audio, you need to check the "Share audio" checkbox in
                the screen selection dialog.
              </li>
              <li>
                Audio sharing is only supported if sharing your entire screen or
                a browser tab, not an application.
              </li>
            </ul>
            <Button
              onClick={() => {
                this.props.startScreenShare();
                this.props.closeModal();
              }}
            >
              Start Screenshare
            </Button>
          </Modal.Description>
        </Modal.Content>
      </Modal>
    );
  }
Example #9
Source File: App.tsx    From communitymap-ui with Apache License 2.0 5 votes vote down vote up
Home: React.FC = () => {
  const user = useAuth() || null;
  const [currentLocation, setCurrentLocation] = useState(defaultCenter);
  const router = useHistory();

  const objectRouteMatch = useRouteMatch<{ objectId: string }>(
    '/object/:objectId'
  );

  return (
    <div id="home">
      <CommunityMap
        defaultCenter={defaultCenter}
        onChange={(center) => setCurrentLocation(center)}
        mapApiKey={GOOGLE_API_KEY}
        showObjectId={objectRouteMatch?.params?.objectId}
        onClickObject={(obj) => {
          router.push(`/object/${obj.id}`);
          return true;
        }}
        onObjectModalClose={() => router.push('/')}
        centerPin={<Pin color="#2185d0" />}
        autolocate={initialParams?.autolocate}
        filterOrigin={initialParams?.filterOrigin}
        profileWidget={<ProfileWidgetPlus />}
        renderAuthor={renderAuthor}
        // renderObject={({ item }) => (item.type === 'story' ? true : null)}
      ></CommunityMap>
      {initialParams?.canAdd !== false && (
        <NewContentWidget
          authenticated={!!user}
          onAdd={(item) =>
            postObject(user, currentLocation, item, embedParams?.appId)
          }
        />
      )}

      <Switch>
        <Route path="/direct-messages/:dmKey">
          <DirectMessageModal onClose={() => router.push('/')} />
        </Route>
        <Route path="/users/:userId">
          <Modal open closeIcon size="tiny" onClose={() => router.push('/')}>
            <Modal.Content scrolling>
              <UserPage />
            </Modal.Content>
          </Modal>
        </Route>
        <Route path="/my-messages">
          <Modal open closeIcon size="tiny" onClose={() => router.push('/')}>
            <Modal.Header>Messages</Modal.Header>
            <Modal.Content scrolling>
              <DirectMessageDialogs />
            </Modal.Content>
          </Modal>
        </Route>
        <Route path="/terms">
          <Modal open closeIcon size="large" onClose={() => router.push('/')}>
            <Modal.Header>Terms of Service</Modal.Header>
            <Modal.Content scrolling>
              <TermsOfService />
            </Modal.Content>
          </Modal>
        </Route>
        <Route path="/privacy">
          <Modal open closeIcon size="large" onClose={() => router.push('/')}>
            <Modal.Header>Privacy and Cookie Policy</Modal.Header>
            <Modal.Content scrolling>
              <PrivacyPolicy />
            </Modal.Content>
          </Modal>
        </Route>
      </Switch>
    </div>
  );
}
Example #10
Source File: topmenu.tsx    From website with MIT License 5 votes vote down vote up
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: Login.tsx    From communitymap-ui with Apache License 2.0 5 votes vote down vote up
Login: React.FC<{ title?: string; onClose?: () => void }> = ({
  title = 'You need to sign in to continue',
  onClose,
}) => {
  useEffect(() => {
    const uiConfig = {
      signInFlow: 'popup',
      // signInSuccessUrl: window.location.origin,
      signInSuccessUrl: window.location.href,
      signInOptions: [
        firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        // firebase.auth.FacebookAuthProvider.PROVIDER_ID,
        // firebase.auth.PhoneAuthProvider.PROVIDER_ID,
      ],
      // tosUrl and privacyPolicyUrl accept either url string or a callback
      // function.
      // Terms of service url/callback.
      // tosUrl: '<your-tos-url>',
      // Privacy policy url/callback.
      // privacyPolicyUrl: function() {
      //   window.location.assign('<your-privacy-policy-url>');
      // }
    };

    // Initialize the FirebaseUI Widget using Firebase.
    const ui = new firebaseui.auth.AuthUI(
      getFirebaseApp().auth(),
      getFirebaseApp().name
    );
    // The start method will wait until the DOM is loaded.
    ui.start('#firebaseui-auth-container', uiConfig);

    return () => {
      ui.delete();
    };
  }, []);

  return (
    <Modal closeIcon size="mini" open onClose={onClose}>
      <Modal.Header>{title}</Modal.Header>
      <Modal.Content>
        <div style={{ margin: '3em 1.5em' }}>
          <div id="firebaseui-auth-container"></div>
        </div>
      </Modal.Content>
    </Modal>
  );
}
Example #12
Source File: Login.tsx    From communitymap-ui with Apache License 2.0 5 votes vote down vote up
Login: React.FC<{ title?: string; onClose?: () => void }> = ({
  title = 'You need to sign in to continue',
  onClose,
}) => {
  useEffect(() => {
    const uiConfig = {
      signInFlow: 'popup',
      // signInSuccessUrl: window.location.origin,
      signInSuccessUrl: window.location.href,
      signInOptions: [
        firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        // firebase.auth.FacebookAuthProvider.PROVIDER_ID,
        // firebase.auth.PhoneAuthProvider.PROVIDER_ID,
      ],
      // tosUrl and privacyPolicyUrl accept either url string or a callback
      // function.
      // Terms of service url/callback.
      tosUrl: window.location.origin + '/terms',
      // Privacy policy url/callback.
      privacyPolicyUrl: window.location.origin + '/privacy',
      // privacyPolicyUrl: function() {
      //   window.location.assign('<your-privacy-policy-url>');
      // }
    };

    // Initialize the FirebaseUI Widget using Firebase.
    const ui = new firebaseui.auth.AuthUI(
      getFirebaseApp().auth(),
      getFirebaseApp().name
    );
    // The start method will wait until the DOM is loaded.
    ui.start('#firebaseui-auth-container', uiConfig);

    return () => {
      ui.delete();
    };
  }, []);

  return (
    <Modal closeIcon size="mini" open onClose={onClose}>
      <Modal.Header>{title}</Modal.Header>
      <Modal.Content>
        <div style={{ margin: '3em 1.5em' }}>
          <div id="firebaseui-auth-container"></div>
        </div>
      </Modal.Content>
    </Modal>
  );
}
Example #13
Source File: NewContentWidget.tsx    From communitymap-ui with Apache License 2.0 5 votes vote down vote up
NewContentWidget: React.FC<{
  authenticated: boolean;
  onAdd: (item: ObjectItemInput) => Promise<any>;
}> = ({ authenticated, onAdd }) => {
  const [addType, setAddType] = useState<ObjectItemInput['type'] | null>(null);

  const showLogin = !authenticated && !!addType;

  return (
    <Segment id="new-content-widget">
      {showLogin && <Login onClose={() => setAddType(null)} />}
      {authenticated && (
        <>
          {!!addType && (
            <Modal open size="tiny" closeIcon onClose={() => setAddType(null)}>
              <Modal.Content>
                <AddNewObjectRender
                  type={addType}
                  onAdd={(it) =>
                    onAdd(it)
                      .then(() => setAddType(null))
                      .catch(reportError)
                  }
                />
              </Modal.Content>
            </Modal>
          )}
        </>
      )}
      <h5>I want to post</h5>
      {([
        'chat',
        'request',
        'offer',
        // 'donation',
      ] as ObjectItemInput['type'][]).map((type) => (
        <Button
          key={type}
          icon={type2icon(type)}
          // basic
          primary
          content={type2title(type)}
          onClick={() => setAddType(type)}
        />
      ))}
      <hr />
      <Button
        key="place"
        icon="building"
        primary
        content="Place"
        onClick={() => setAddType('place')}
      />
      <hr />
      <Button
        key="story"
        icon="edit outline"
        primary
        content="Story"
        onClick={() => setAddType('story')}
      />
    </Segment>
  );
}
Example #14
Source File: crewfullequiptree.tsx    From website with MIT License 5 votes vote down vote up
render() {
		const { crew, items } = this.props;

		if (!crew || !this.props.visible) {
			return <span />;
		}

		let { craftCost, demands, factionOnlyTotal, totalChronCost } = calculateCrewDemands(crew, items);

		return (
			<Modal open={this.props.visible} onClose={() => this.props.onClosed()}>
				<Modal.Header>{crew.name}'s expanded equipment recipe trees</Modal.Header>
				<Modal.Content scrolling>
					<p>
						Faction-only items required <b>{factionOnlyTotal}</b>
					</p>
					<p>
						Estimated chroniton cost{' '}
						<span style={{ display: 'inline-block' }}>
							<img src={`${process.env.GATSBY_ASSETS_URL}atlas/energy_icon.png`} height={14} />
						</span>{' '}
						<b>{totalChronCost}</b>
						<Popup
							wide
							trigger={<Icon fitted name='help' />}
							header={'How is this calculated?'}
							content={
								<div>
									<p>This sums the estimated chroniton cost of each equipment and component in the tree.</p>
									<p>It estimates an item's cost by running the formula below for each mission and choosing the cheapest:</p>
									<p>
										<code>
											(6 - PIPS) * 1.8 * <i>mission cost</i>
										</code>
									</p>
									<p>See code for details. Feedback is welcome!</p>
								</div>
							}
						/>
					</p>
					<p>
						Build cost{' '}
						<span style={{ display: 'inline-block' }}>
							<img src={`${process.env.GATSBY_ASSETS_URL}currency_sc_currency_0.png`} height={16} />
						</span>{' '}
						<b>{craftCost}</b>
					</p>
					<Grid columns={3} centered padded>
						{demands.map((entry, idx) => (
							<Grid.Column key={idx}>
								<Popup
									trigger={
										<Header
											style={{ display: 'flex', cursor: 'zoom-in' }}
											icon={
												<ItemDisplay
													src={`${process.env.GATSBY_ASSETS_URL}${entry.equipment.imageUrl}`}
													size={48}
													maxRarity={entry.equipment.rarity}
													rarity={entry.equipment.rarity}
												/>
											}
											content={entry.equipment.name}
											subheader={`Need ${entry.count} ${entry.factionOnly ? ' (FACTION)' : ''}`}
										/>
									}
									header={CONFIG.RARITIES[entry.equipment.rarity].name + ' ' + entry.equipment.name}
									content={<ItemSources item_sources={entry.equipment.item_sources} />}
									on='click'
									wide
								/>
							</Grid.Column>
						))}
					</Grid>
				</Modal.Content>
			</Modal>
		);
	}
Example #15
Source File: PermanentRoomModal.tsx    From watchparty with MIT License 5 votes vote down vote up
render() {
    const { closeModal } = this.props;
    return (
      <Modal open={true} onClose={closeModal as any} closeIcon>
        <Modal.Header>Permanent Rooms</Modal.Header>
        <Modal.Content image>
          <Modal.Description>
            <div>
              Registered users have the ability to make their rooms permanent.
              Subscribed users can create multiple permanent rooms.
            </div>
            <Table definition unstackable striped celled>
              <Table.Header>
                <Table.Row>
                  <Table.HeaderCell />
                  <Table.HeaderCell>Temporary</Table.HeaderCell>
                  <Table.HeaderCell>Permanent</Table.HeaderCell>
                </Table.Row>
              </Table.Header>

              <Table.Body>
                <Table.Row>
                  <Table.Cell>Expiry</Table.Cell>
                  <Table.Cell>After 24 hours of inactivity</Table.Cell>
                  <Table.Cell>Never</Table.Cell>
                </Table.Row>
                <Table.Row>
                  <Table.Cell>Room Passwords</Table.Cell>
                  <Table.Cell></Table.Cell>
                  <Table.Cell>
                    <Icon name="check" />
                  </Table.Cell>
                </Table.Row>
                <Table.Row>
                  <Table.Cell>Disable Chat</Table.Cell>
                  <Table.Cell></Table.Cell>
                  <Table.Cell>
                    <Icon name="check" />
                  </Table.Cell>
                </Table.Row>
                <Table.Row>
                  <Table.Cell>Kick Users</Table.Cell>
                  <Table.Cell></Table.Cell>
                  <Table.Cell>
                    <Icon name="check" />
                  </Table.Cell>
                </Table.Row>
                <Table.Row>
                  <Table.Cell>Custom Room URLs (subscribers)</Table.Cell>
                  <Table.Cell></Table.Cell>
                  <Table.Cell>
                    <Icon name="check" />
                  </Table.Cell>
                </Table.Row>
                {/* <Table.Row>
                  <Table.Cell>Max Room Capacity (subscribers)</Table.Cell>
                    <Table.Cell>20</Table.Cell>
                    <Table.Cell>100</Table.Cell>
                  </Table.Row> */}
              </Table.Body>
            </Table>
          </Modal.Description>
        </Modal.Content>
      </Modal>
    );
  }
Example #16
Source File: LoginModal.tsx    From watchparty with MIT License 5 votes vote down vote up
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 #17
Source File: FileShareModal.tsx    From watchparty with MIT License 5 votes vote down vote up
render() {
    const { closeModal } = this.props;
    return (
      <Modal open={true} onClose={closeModal as any}>
        <Modal.Header>Share A File</Modal.Header>
        <Modal.Content image>
          <Modal.Description>
            <div>You're about to share a file from your device.</div>
            <ul>
              <li>This feature is only supported on Chrome and Edge.</li>
              <li>
                To test whether the file can be shared, you can try opening it
                locally with your browser to see if it will play properly.
              </li>
              <li>
                Certain codecs, such as HEVC, AC3, and H265 will not play in
                Chrome (they may work in Edge)
              </li>
              <li>
                There is a Chrome issue where the sharer needs to{' '}
                <a
                  target="_blank"
                  rel="noreferrer"
                  href="https://www.howtogeek.com/412738/how-to-turn-hardware-acceleration-on-and-off-in-chrome/"
                >
                  disable hardware acceleration
                </a>{' '}
                in order for others to receive video.
              </li>
            </ul>
            <Button
              onClick={() => {
                this.props.startFileShare();
                this.props.closeModal();
              }}
            >
              Start Fileshare
            </Button>
          </Modal.Description>
        </Modal.Content>
      </Modal>
    );
  }
Example #18
Source File: missionslist.tsx    From website with MIT License 4 votes vote down vote up
MissionsList = (props: MissionsListProps) => {
	const { groupId, shuttlers, setShuttlers, activeShuttles } = props;

	const [editMission, setEditMission] = React.useState(undefined);
	const [state, dispatch] = React.useReducer(reducer, {
		data: shuttlers.shuttles.filter(shuttle => shuttle.groupId === groupId),
		column: null,
		direction: null
	});
	const { data, column, direction } = state;

	React.useEffect(() => {
		dispatch({ type: 'UPDATE_DATA', data: shuttlers.shuttles.filter(shuttle => shuttle.groupId === groupId), column, direction });
	}, [shuttlers]);

	const CheckDropdown = () => {
		if (data.length === 0) return (<></>);

		const checkOptions = [];

		const threeSeaters = [], fourSeaters = [];
		data.forEach(shuttle => {
			if (shuttle.seats.length <= 4)
				fourSeaters.push(shuttle.id);
			if (shuttle.seats.length === 3)
				threeSeaters.push(shuttle.id);
		});
		if (threeSeaters.length > 0)
			checkOptions.push({ key: 'three-seaters', text: `Select only 3-seaters (${threeSeaters.length})`, ids: threeSeaters });
		if (fourSeaters.length > 0)
			checkOptions.push({ key: 'four-seaters', text: `Select only 3- and 4- seaters (${fourSeaters.length})`, ids: fourSeaters });

		if (activeShuttles?.length > 0) {
			const openIds = activeShuttles.map(adventure => adventure.symbol);
			checkOptions.push({ key: `open-adventures`, text: `Select only open in-game (${openIds.length})`, ids: openIds });
		}

		const factions = [];
		data.forEach(shuttle => {
			if (shuttle.faction > 0 && !factions.includes(shuttle.faction)) factions.push(shuttle.faction);
		});
		if (factions.length > 1) {
			factions.forEach(factionId => {
				const ids = data.filter(shuttle => shuttle.faction === factionId).map(shuttle => shuttle.id);
				const faction = allFactions.find(af => af.id === factionId);
				checkOptions.push({ key: `faction-${factionId}`, text: `Select only ${faction.name} (${ids.length})`, ids });
			});
		}

		return (
			<Dropdown
				icon='check'
				floating
			>
				<Dropdown.Menu>
					<Dropdown.Item icon='check' text={`Select all (${data.length})`} onClick={() => checkMissions([])} />
					{missionsSelected > 0 && (
						<Dropdown.Item icon='x' text='Unselect all' onClick={() => checkMissions([], false)} />
					)}
					{checkOptions.length > 0 && <Dropdown.Divider />}
					{checkOptions.map(option => (
						<Dropdown.Item key={option.key} text={option.text} onClick={() => checkMissions(option.ids)} />
					))}
				</Dropdown.Menu>
			</Dropdown>
		);
	};

	const tableConfig = [
		{ title: <CheckDropdown />, align: 'center' },
		{ column: 'name', title: 'Mission' },
		{ column: 'faction', title: 'Faction', align: 'center' },
		{ column: 'seats.length', title: 'Seats', align: 'center' },
		{ column: 'skills', title: 'Skills', span: 5 },
		{ title: '' }
	];

	const MissionEditor = (props: { shuttle: Shuttle }) => {
		const [shuttle, setShuttle] = React.useState(JSON.parse(JSON.stringify(props.shuttle)));

		const factionOptions = allFactions.sort((a, b) => a.name.localeCompare(b.name)).map(faction => {
			return { key: faction.id, value: faction.id, text: (<span style={{ whiteSpace: 'nowrap' }}>{faction.name}</span>) };
		});

		const EditorSeat = (props: { seat: ShuttleSeat, seatNum: number }) => {
			const { seatNum, seat } = props;

			const skillOptions = [
				{ key: 'CMD', text: 'CMD', value: 'command_skill' },
				{ key: 'DIP', text: 'DIP', value: 'diplomacy_skill' },
				{ key: 'ENG', text: 'ENG', value: 'engineering_skill' },
				{ key: 'MED', text: 'MED', value: 'medicine_skill' },
				{ key: 'SCI', text: 'SCI', value: 'science_skill' },
				{ key: 'SEC', text: 'SEC', value: 'security_skill' }
			];

			return (
				<Grid textAlign='center' columns={3}>
					<Grid.Column>
						<Dropdown
							direction='right'
							compact
							selection
							options={skillOptions}
							value={seat.skillA}
							onChange={(e, { value }) => updateMissionSeat(seatNum, 'skillA', value)}
						/>
					</Grid.Column>
					<Grid.Column>
						<Button circular
							disabled={seat.skillB == '' ? true : false}
							onClick={() => updateMissionSeat(seatNum, 'operand', seat.operand == 'AND' ? 'OR' : 'AND')}
						>
							{seat.skillB == '' ? '' : seat.operand}
						</Button>
					</Grid.Column>
					<Grid.Column>
						<Dropdown
							compact
							selection
							clearable
							options={skillOptions}
							value={seat.skillB}
							onChange={(e, { value }) => updateMissionSeat(seatNum, 'skillB', value)}
						/>
					</Grid.Column>
				</Grid>
			);
		};

		return (
			<Modal
				open={true}
				onClose={() => applyEdits()}
			>
				<Modal.Header>Edit Mission</Modal.Header>
				<Modal.Content scrolling>
					{renderContent()}
				</Modal.Content>
				<Modal.Actions>
					<Button positive onClick={() => applyEdits()}>
						Close
					</Button>
				</Modal.Actions>
			</Modal>
		);

		function renderContent(): void {
			return (
				<React.Fragment>
					<Grid columns={2} divided stackable>
						<Grid.Column>
							<div>
								<Header as='h4'>Mission name</Header>
								<Input style={{ marginTop: '-1em' }}
									placeholder='Mission name...'
									value={shuttle.name}
									onChange={(e, { value }) => updateMissionName(value)}>
										<input />
										<Button icon onClick={() => updateMissionName('')} >
											<Icon name='delete' />
										</Button>
								</Input>
							</div>
							<div style={{ marginTop: '1em' }}>
								<Header as='h4'>Faction</Header>
								<Dropdown
									style={{ marginTop: '-1em' }}
									selection
									options={factionOptions}
									value={shuttle.faction}
									onChange={(e, { value }) => updateFaction(value)}
								/>
							</div>
						</Grid.Column>
						<Grid.Column>
							<Header as='h4'>Seats</Header>
							<p style={{ marginTop: '-1em' }}>Set each seat to the skills required. Add seats as necessary.</p>
							<Table collapsing unstackable compact='very' size='small'>
								<Table.Body>
									{shuttle.seats.map((seat, seatNum) => (
										<Table.Row key={seatNum}>
											<Table.Cell textAlign='right'>{seatNum+1}</Table.Cell>
											<Table.Cell textAlign='center'>
												<EditorSeat seatNum={seatNum} seat={seat} />
											</Table.Cell>
											<Table.Cell textAlign='right'>
												{shuttle.seats.length > 1 && <Button compact icon='trash' color='red' onClick={() => deleteMissionSeat(seatNum)} />}
											</Table.Cell>
										</Table.Row>
									))}
								</Table.Body>
							</Table>
							<Button compact icon='plus square outline' content='Add Seat' onClick={() => addMissionSeat()} />
						</Grid.Column>
					</Grid>
					<div style={{ marginTop: '1em' }}>
						<Divider />
						<p>If you no longer need this mission, you can delete it here. Note: missions will be automatically deleted after the event has concluded.</p>
						<p><Button icon='trash' color='red' content='Delete Mission' onClick={() => deleteMission(shuttle.id)} /></p>
					</div>
				</React.Fragment>
			);
		}

		function updateMissionName(newName: string): void {
			shuttle.name = newName;
			setShuttle({...shuttle});
		}

		function updateFaction(newFaction: number): void {
			shuttle.faction = newFaction;
			setShuttle({...shuttle});
		}

		function updateMissionSeat(seatNum: number, key: string, value: string): void {
			shuttle.seats[seatNum][key] = value;
			setShuttle({...shuttle});
		}

		function addMissionSeat(): void {
			shuttle.seats.push(new ShuttleSeat());
			setShuttle({...shuttle});
		}

		function deleteMissionSeat(seatNum: number): void {
			shuttle.seats.splice(seatNum, 1);
			setShuttle({...shuttle});
		}

		function applyEdits(): void {
			if (shuttle.priority === 0) shuttle.priority = missionsSelected + 1;
			const shuttleNum = shuttlers.shuttles.findIndex(s => s.id === shuttle.id);
			shuttlers.shuttles[shuttleNum] = shuttle;
			updateShuttlers();
			setEditMission(undefined);
		}
	};

	const missionsSelected = data.filter(shuttle => shuttle.priority > 0).length;

	return (
		<React.Fragment>
			<div>Click all the missions that you want to run, then click 'Recommend Crew' to see the best seats for your crew.</div>
			<Table celled striped selectable sortable singleLine>
				<Table.Header>
					<Table.Row>
						{tableConfig.map((cell, idx) => (
							<Table.HeaderCell key={idx}
								sorted={column === cell.column ? direction : null}
								onClick={() => dispatch({ type: 'CHANGE_SORT', column: cell.column, reverse: cell.reverse })}
								colSpan={cell.span ?? 1}
								textAlign={cell.align ?? 'left'}
							>
								{cell.title}
							</Table.HeaderCell>
						))}
					</Table.Row>
				</Table.Header>
				<Table.Body>
					{data.length === 0 && (
						<Table.Row>
							<Table.Cell colSpan={10} textAlign='center'>
								No missions available.
							</Table.Cell>
						</Table.Row>
					)}
					{data.map(shuttle => (
						<Table.Row key={shuttle.id} style={{ cursor: 'pointer' }}
							onClick={() => toggleMissionStatus(shuttle.id)}
							onDoubleClick={() => { toggleMissionStatus(shuttle.id); props.recommendShuttlers(); }}
						>
							<Table.Cell textAlign='center'>
								{shuttle.priority > 0 && (<Icon color='green' name='check' />)}
							</Table.Cell>
							<Table.Cell>
								<span style={{ fontSize: '1.1em' }}><b>{shuttle.name}</b></span>
							</Table.Cell>
							<Table.Cell textAlign='center'>
								<ShuttleFactionView factionId={shuttle.faction} size={1.5} />
							</Table.Cell>
							<Table.Cell textAlign='center'>{shuttle.seats.length}</Table.Cell>
							{[0, 1, 2, 3, 4].map(seatNum => (
								<Table.Cell key={seatNum} textAlign='center'>
									{shuttle.seats.length > seatNum && (
										<SeatSkillView seat={shuttle.seats[seatNum]} />
									)}
								</Table.Cell>
							))}
							<Table.Cell textAlign='right'>
								{!shuttle.readonly && (
									<Button icon='edit' content='Edit' onClick={(e) => { setEditMission(shuttle); e.stopPropagation(); }}/>
								)}
							</Table.Cell>
						</Table.Row>
					))}
				</Table.Body>
				<Table.Footer>
					<Table.Row>
						<Table.HeaderCell colSpan={10} textAlign='right'>
							{missionsSelected > 0 && (<Button compact icon='rocket' color='green' content='Recommend Crew' onClick={() => props.recommendShuttlers()} />)}
							{missionsSelected === 0 && (<Button compact icon='rocket' content='Recommend Crew' />)}
						</Table.HeaderCell>
					</Table.Row>
				</Table.Footer>
			</Table>
			{editMission && <MissionEditor shuttle={editMission} />}
			<p>If the mission you want isn't listed here, click 'Create Mission' to input the mission parameters manually. Tip: open shuttle missions in-game before uploading your player data to DataCore so that this tool can import the missions automatically.</p>
			<Button icon='plus square' content='Create Mission' onClick={() => createMission() } />
		</React.Fragment>
	);

	function reducer(state, action): any {
		switch (action.type) {
			case 'UPDATE_DATA':
				//const defaultColumn = action.data.filter(shuttle => shuttle.priority > 0).length ? 'priority' : 'name';
				const updatedData = action.data.slice();
				firstSort(updatedData, action.column ?? 'name', action.direction ?? 'ascending');
				return {
					column: action.column ?? 'name',
					data: updatedData,
					direction: action.direction ?? 'ascending'
				};
			case 'CHANGE_SORT':
				if (!action.column) {
					return {
						column: state.column,
						data: state.data,
						direction: state.direction
					};
				}
				if (state.column === action.column && action.column !== 'priority') {
					return {
						...state,
						data: state.data.slice().reverse(),
						direction: state.direction === 'ascending' ? 'descending' : 'ascending'
					};
				}
				else {
					const data = state.data.slice();
					firstSort(data, action.column, action.reverse);
					return {
						column: action.column,
						data: data,
						direction: action.reverse ? 'descending' : 'ascending'
					};
				}
			default:
				throw new Error();
		}
	}

	function firstSort(data: any[], column: string, reverse: boolean = false): any[] {
		data.sort((a, b) => {
			if (column === 'name') return a.name.localeCompare(b.name);
			let aValue = column.split('.').reduce((prev, curr) => prev.hasOwnProperty(curr) ? prev[curr] : undefined, a);
			let bValue = column.split('.').reduce((prev, curr) => prev.hasOwnProperty(curr) ? prev[curr] : undefined, b);
			// Always show selected missions at the top when sorting by priority
			if (column === 'priority') {
				if (aValue === 0) aValue = 100;
				if (bValue === 0) bValue = 100;
			}
			if (column === 'skills') {
				aValue = a.seats.length;
				bValue = b.seats.length;
			}
			// Tiebreaker goes to name ascending
			if (aValue === bValue) return a.name.localeCompare(b.name);
			if (reverse) bValue - aValue;
			return aValue - bValue;
		});
	}

	function checkMissions(shuttleIds: string[], checkState: boolean = true): void {
		let priority = 0;
		shuttlers.shuttles.forEach(shuttle => {
			if (shuttleIds.length === 0)
				shuttle.priority = checkState ? ++priority : 0;
			else
				shuttle.priority = checkState && shuttleIds.includes(shuttle.id) ? ++priority : 0;
		});
		updateShuttlers();
		if (shuttleIds.length !== 0)
			dispatch({ type: 'CHANGE_SORT', column: 'priority' });
	}

	function createMission(): void {
		const shuttle = new Shuttle(groupId);
		shuttle.seats.push(new ShuttleSeat());
		shuttlers.shuttles.push(shuttle);
		updateShuttlers();
		setEditMission(shuttle);
	}

	function deleteMission(shuttleId: string): void {
		const shuttleNum = shuttlers.shuttles.findIndex(shuttle => shuttle.id === shuttleId);
		shuttlers.shuttles.splice(shuttleNum, 1);
		updateShuttlers();
		setEditMission(undefined);
	}

	function toggleMissionStatus(shuttleId: string): void {
		const shuttle = shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId);
		shuttle.priority = shuttle.priority === 0 ? missionsSelected+1 : 0;
		updateShuttlers();
	}

	function updateShuttlers(): void {
		setShuttlers({...shuttlers});
	}
}
Example #19
Source File: CreateTxModal.tsx    From multi-sig-wallet with MIT License 4 votes vote down vote up
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 #20
Source File: assignmentslist.tsx    From website with MIT License 4 votes vote down vote up
AssignmentsList = (props: AssignmentsList) => {
	const { shuttlers, setShuttlers, assigned, setAssigned, crewScores, updateCrewScores } = props;

	const [shuttleScores, setShuttleScores] = React.useState([]);
	const [editAssignment, setEditAssignment] = React.useState(undefined);
	const [scoreLoadQueue, setScoreLoadQueue] = React.useState('');

	React.useEffect(() => {
		updateShuttleScores();
	}, [assigned]);

	const myCrew = props.crew;
	const SeatAssignmentRow = (props: { shuttleId: string, seatNum: number, seat: ShuttleSeat }) => {
		const { shuttleId, seatNum, seat } = props;

		let assignedCrew;
		const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum === seatNum);
		if (seated) {
			assignedCrew = myCrew.find(crew => crew.id === seated.assignedId && crew.symbol === seated.assignedSymbol);
			if (!assignedCrew) assignedCrew = myCrew.find(crew => crew.symbol === seated.assignedSymbol);
		}

		const lockAttributes = {};
		if (seated?.locked) lockAttributes.color = 'yellow';

		return (
			<Table.Row key={seatNum} style={{ cursor: 'pointer' }}
				onClick={() => { if (seat.skillA) setEditAssignment({shuttleId, seatNum}); }}
			>
				<Table.Cell textAlign='center'>
					<SeatSkillView seat={seat} />
				</Table.Cell>
				<Table.Cell textAlign={assignedCrew ? 'left' : 'right'}>
					{assignedCrew && (<SeatCrewView crew={assignedCrew} />)}
					{!assignedCrew && (<span style={{ color: 'gray' }}>(Open seat)</span>)}
				</Table.Cell>
				<Table.Cell>
					{assignedCrew?.immortal > 0 && (<Icon name='snowflake' />)}
					{assignedCrew?.prospect && (<Icon name='add user' />)}
				</Table.Cell>
				<Table.Cell textAlign='center'>
					{assignedCrew && (
						<Button.Group>
							<Button compact icon='lock' {... lockAttributes}
								onClick={(e) => { toggleAssignmentLock(shuttleId, seatNum); e.stopPropagation(); }} />
							<Button compact icon='x'
								onClick={(e) => { updateAssignment(shuttleId, seatNum); e.stopPropagation(); }} />
						</Button.Group>
					)}
				</Table.Cell>
			</Table.Row>
		);
	};

	const SeatAssignmentPicker = () => {
		const { shuttleId, seatNum } = editAssignment;
		const [paginationPage, setPaginationPage] = React.useState(1);

		const seat = shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId).seats[seatNum];
		const ssId = getSkillSetId(seat);
		const scores = crewScores.skillsets[ssId];
		if (!scores) {
			if (scoreLoadQueue === '') {
				setScoreLoadQueue(ssId);
				updateCrewScores([seat], () => setScoreLoadQueue(''));
			}
			return (<></>);
		}

		const shuttle = shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId);
		return (
			<Modal
				open={true}
				onClose={() => setEditAssignment(undefined)}
			>
				<Modal.Header>
					{shuttle.name}
					{shuttleScores[shuttleId] ?
						<span style={{ fontSize: '.95em', fontWeight: 'normal', paddingLeft: '1em' }}>
							({(shuttleScores[shuttleId].chance*100).toFixed(1)}% Chance)
						</span>
						: ''}
				</Modal.Header>
				<Modal.Content scrolling>
					{scores && renderTable()}
				</Modal.Content>
				<Modal.Actions>
					<Button icon='forward' content='Next Seat' onClick={() => cycleShuttleSeat()} />
					<Button positive onClick={() => setEditAssignment(undefined)}>
						Close
					</Button>
				</Modal.Actions>
			</Modal>
		);

		function renderTable(): JSX.Element {
			let assignedCrew;
			const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum == seatNum);
			if (seated) {
				assignedCrew = myCrew.find(crew => crew.id === seated.assignedId && crew.symbol === seated.assignedSymbol);
				if (!assignedCrew) assignedCrew = myCrew.find(crew => crew.symbol === seated.assignedSymbol);
			}

			// Pagination
			const rowsPerPage = 10;
			const totalPages = Math.ceil(scores.length / rowsPerPage);
			const data = scores.slice(rowsPerPage * (paginationPage - 1), rowsPerPage * paginationPage).map(score => {
				const scoreCrew = myCrew.find(crew => crew.id === score.id);
				return {...scoreCrew, ...score}
			});

			return (
				<React.Fragment>
					<Table striped selectable singleLine compact='very'>
						<Table.Header>
							<Table.Row>
								<Table.HeaderCell />
								<Table.HeaderCell colSpan={2}>Best <span style={{ padding: '0 .5em' }}><SeatSkillView seat={seat} /></span> Crew</Table.HeaderCell>
								<Table.HeaderCell textAlign='center'>Here<Popup trigger={<Icon name='help' />} content='Using this crew here will result in this net change to the success chance of this shuttle' /></Table.HeaderCell>
								<Table.HeaderCell>Current Assignment</Table.HeaderCell>
								<Table.HeaderCell textAlign='center'>There<Popup trigger={<Icon name='help' />} content='Removing this crew from their current assignment will leave an open seat on that shuttle, resulting in this success chance' /></Table.HeaderCell>
							</Table.Row>
						</Table.Header>
						<Table.Body>
							{data.map((crew, idx) => renderRow(crew, idx, assignedCrew))}
						</Table.Body>
						<Table.Footer>
							<Table.Row>
								<Table.HeaderCell colSpan={6}>
									<Pagination
										totalPages={totalPages}
										activePage={paginationPage}
										onPageChange={(e, { activePage }) => setPaginationPage(activePage)}
									/>
								</Table.HeaderCell>
							</Table.Row>
						</Table.Footer>
					</Table>
				</React.Fragment>
			);
		}

		function renderRow(crew: any, idx: number, assignedCrew: any): JSX.Element {
			const currentSeat = assigned.find(seat => seat.assignedId === crew.id && seat.assignedSymbol === crew.symbol);
			const currentShuttle = currentSeat ? shuttlers.shuttles.find(shuttle => shuttle.id === currentSeat.shuttleId) : undefined;
			return (
				<Table.Row key={idx} style={{ cursor: 'pointer' }}
					onClick={() => {
						if (!assignedCrew || crew.id !== assignedCrew.id)
							updateAssignment(shuttleId, seatNum, crew, true);
						setEditAssignment(undefined);
					}}
				>
					<Table.Cell textAlign='center'>
						{assignedCrew?.id === crew.id && (<Icon color='green' name='check' />)}
					</Table.Cell>
					<Table.Cell><SeatCrewView crew={crew} /></Table.Cell>
					<Table.Cell>
						{crew.immortal > 0 && (<Icon name='snowflake' />)}
						{crew.prospect && (<Icon name='add user' />)}
					</Table.Cell>
					<Table.Cell textAlign='center'>
						{renderScoreChange(shuttleId, seatNum, crew.score)}
					</Table.Cell>
					<Table.Cell>
						{currentShuttle?.name}
						{currentShuttle?.id === shuttleId && <span style={{ paddingLeft: '1em' }}><i>(This Shuttle)</i></span>}
						{currentSeat?.locked && <span style={{ paddingLeft: '1em' }}><Icon name='lock' /></span>}
					</Table.Cell>
					<Table.Cell textAlign='center'>
						{currentSeat && renderScoreChange(currentSeat.shuttleId, currentSeat.seatNum, 0)}
					</Table.Cell>
				</Table.Row>
			);
		}

		function renderScoreChange(shuttleId: string, seatNum: number, replacementScore: number = 0): JSX.Element {
			if (!shuttleScores[shuttleId]) return (<></>);
			const newScores = [...shuttleScores[shuttleId].scores];
			newScores[seatNum] = replacementScore;
			const DIFFICULTY = 2000;
			const dAvgSkill = newScores.reduce((a, b) => (a + b), 0)/newScores.length;
			const dChance = 1/(1+Math.pow(Math.E, 3.5*(0.5-dAvgSkill/DIFFICULTY)));
			const attributes = {};
			if (replacementScore === 0) {
				if (dChance*100 >= 90) attributes.style = { color: 'green', fontWeight: 'bold' };
				return (<span {...attributes}>{Math.floor(dChance*100)}%</span>);
			}
			const dDelta = dChance - shuttleScores[shuttleId].chance;
			if (dDelta > 0 && dChance*100 >= 90)
				attributes.style = { color: 'green', fontWeight: 'bold' };
			return (<span {...attributes}>{dDelta > 0 ? '+' : ''}{(dDelta*100).toFixed(1)}%</span>);
		}

		function cycleShuttleSeat(): void {
			const nextAssignment = {
				shuttleId: shuttleId,
				seatNum: seatNum + 1 >= shuttle.seats.length ? 0 : seatNum + 1
			};
			setEditAssignment(nextAssignment);
		}
	};

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

	return (
		<React.Fragment>
			<p>You can rearrange crew to balance shuttle chances as you see fit. Click a seat to change the crew assigned to it. Lock an assignment to keep that crew in that seat when requesting new recommendations.</p>
			<Table celled striped compact='very'>
				<Table.Header>
					<Table.Row>
						<Table.HeaderCell>Mission</Table.HeaderCell>
						<Table.HeaderCell textAlign='center'>Faction</Table.HeaderCell>
						<Table.HeaderCell textAlign='center'>Seat Assignments</Table.HeaderCell>
						<Table.HeaderCell textAlign='center'>Success Chance</Table.HeaderCell>
						<Table.HeaderCell />
					</Table.Row>
				</Table.Header>
				<Table.Body>
					{data.length === 0 && (
						<Table.Row>
							<Table.Cell colSpan={6} textAlign='center'>
								No missions selected.
							</Table.Cell>
						</Table.Row>
					)}
					{data.map(shuttle => (
						<Table.Row key={shuttle.id}>
							<Table.Cell><b>{shuttle.name}</b></Table.Cell>
							<Table.Cell textAlign='center'>
								<ShuttleFactionView factionId={shuttle.faction} size={3} />
							</Table.Cell>
							<Table.Cell>
								<Table striped selectable singleLine compact='very' style={{ margin: '0 auto' }}>
									<Table.Body>
										{shuttle.seats.map((seat, seatNum) =>
											<SeatAssignmentRow key={seatNum} shuttleId={shuttle.id} seatNum={seatNum} seat={seat} />
										)}
									</Table.Body>
								</Table>
							</Table.Cell>
							<Table.Cell textAlign='center'>
								{shuttleScores[shuttle.id]?.chance > 0 ? <b>{Math.floor(shuttleScores[shuttle.id].chance*100)}%</b> : <></>}
							</Table.Cell>
							<Table.Cell textAlign='right'>
								<Button compact icon='ban' content='Dismiss' onClick={() => dismissShuttle(shuttle.id)} />
							</Table.Cell>
						</Table.Row>
					))}
				</Table.Body>
				<Table.Footer>
					<Table.Row>
						<Table.HeaderCell colSpan={3}>
							<Button compact icon='backward' content='Change Missions' onClick={() => props.setActiveStep('missions')} />
						</Table.HeaderCell>
						<Table.HeaderCell colSpan={3} textAlign='right'>
							{data.length > 0 && (<Button compact icon='rocket' color='green' content='Recommend Crew' onClick={() => props.recommendShuttlers()} />)}
						</Table.HeaderCell>
					</Table.Row>
				</Table.Footer>
			</Table>
			{editAssignment && (<SeatAssignmentPicker />)}
		</React.Fragment>
	);

	function dismissShuttle(shuttleId: string): void {
		shuttlers.shuttles.find(shuttle => shuttle.id === shuttleId).priority = 0;
		setShuttlers({...shuttlers});
	}

	function updateAssignment(shuttleId: string, seatNum: number, assignedCrew: any, locked: boolean): void {
		// Unassign crew from previously assigned seat, if necessary
		if (assignedCrew) {
			const current = assigned.find(seat => seat.assignedId === assignedCrew.id);
			if (current) {
				current.assignedId = -1;
				current.assignedSymbol = '';
				current.seatScore = 0;
				current.locked = false;
			}
		}

		const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum === seatNum);
		if (assignedCrew && !seated) {
			assigned.push({
				shuttleId,
				seatNum,
				ssId: assignedCrew.ssId,
				assignedId: assignedCrew.id,
				assignedSymbol: assignedCrew.symbol,
				seatScore: assignedCrew.score,
				locked
			});
		}
		else if (assignedCrew) {
			seated.assignedId = assignedCrew.id;
			seated.assignedSymbol = assignedCrew.symbol;
			seated.seatScore = assignedCrew.score;
			seated.locked = locked;
		}
		else {
			seated.assignedId = -1;
			seated.assignedSymbol = '';
			seated.seatScore = 0;
			seated.locked = false;
		}
		setAssigned([...assigned]);
	}

	function toggleAssignmentLock(shuttleId: string, seatNum: number): void {
		const seated = assigned.find(seat => seat.shuttleId === shuttleId && seat.seatNum === seatNum);
		seated.locked = !seated.locked;
		setAssigned([...assigned]);
	}

	function updateShuttleScores(): void {
		const DIFFICULTY = 2000;
		const newScores = [];
		assigned.forEach(seated => {
			if (!newScores[seated.shuttleId]) {
				const seatCount = shuttlers.shuttles.find(shuttle => shuttle.id === seated.shuttleId).seats.length;
				newScores[seated.shuttleId] = { chance: 0, scores: Array(seatCount).fill(0) };
			}
			newScores[seated.shuttleId].scores[seated.seatNum] = seated.seatScore;
			const dAvgSkill = newScores[seated.shuttleId].scores.reduce((a, b) => (a + b), 0)/newScores[seated.shuttleId].scores.length;
			const dChance = 1/(1+Math.pow(Math.E, 3.5*(0.5-dAvgSkill/DIFFICULTY)));
			newScores[seated.shuttleId].chance = dAvgSkill > 0 ? dChance : 0;
		});
		setShuttleScores(newScores);
	}
}
Example #21
Source File: voyagecalculator.tsx    From website with MIT License 4 votes vote down vote up
VoyageEditConfigModal = (props: VoyageEditConfigModalProps) => {
	const { updateConfig } = props;

	const [voyageConfig, setVoyageConfig] = React.useState(props.voyageConfig);

	const [modalIsOpen, setModalIsOpen] = React.useState(false);
	const [updateOnClose, setUpdateOnClose] = React.useState(false);
	const [options, setOptions] = React.useState(undefined);

	React.useEffect(() => {
		if (!modalIsOpen && updateOnClose) {
			updateConfig(voyageConfig);
			setUpdateOnClose(false);
		}

	}, [modalIsOpen]);

	const defaultSlots = [
		{ symbol: 'captain_slot', name: 'First Officer', skill: 'command_skill', trait: '' },
		{ symbol: 'first_officer', name: 'Helm Officer', skill: 'command_skill', trait: '' },
		{ symbol: 'chief_communications_officer', name: 'Communications Officer', skill: 'diplomacy_skill', trait: '' },
		{ symbol: 'communications_officer', name: 'Diplomat', skill: 'diplomacy_skill', trait: '' },
		{ symbol: 'chief_security_officer', name: 'Chief Security Officer', skill: 'security_skill', trait: '' },
		{ symbol: 'security_officer', name: 'Tactical Officer', skill: 'security_skill', trait: '' },
		{ symbol: 'chief_engineering_officer', name: 'Chief Engineer', skill: 'engineering_skill', trait: '' },
		{ symbol: 'engineering_officer', name: 'Engineer', skill: 'engineering_skill', trait: '' },
		{ symbol: 'chief_science_officer', name: 'Chief Science Officer', skill: 'science_skill', trait: '' },
		{ symbol: 'science_officer', name: 'Deputy Science Officer', skill: 'science_skill', trait: '' },
		{ symbol: 'chief_medical_officer', name: 'Chief Medical Officer', skill: 'medicine_skill', trait: '' },
		{ symbol: 'medical_officer', name: 'Ship\'s Counselor', skill: 'medicine_skill', trait: '' }
	];
	const crewSlots = voyageConfig.crew_slots ?? defaultSlots;
	crewSlots.sort((s1, s2) => CONFIG.VOYAGE_CREW_SLOTS.indexOf(s1.symbol) - CONFIG.VOYAGE_CREW_SLOTS.indexOf(s2.symbol));

	return (
		<Modal
			open={modalIsOpen}
			onClose={() => setModalIsOpen(false)}
			onOpen={() => setModalIsOpen(true)}
			trigger={<Button size='small'><Icon name='edit' />Edit</Button>}
		>
			<Modal.Header>Edit Voyage</Modal.Header>
			<Modal.Content scrolling>
				{renderContent()}
			</Modal.Content>
			<Modal.Actions>
				<Button positive onClick={() => setModalIsOpen(false)}>
					Close
				</Button>
			</Modal.Actions>
		</Modal>
	);

	function renderContent(): JSX.Element {
		if (!modalIsOpen) return (<></>);

		if (!options) {
			// Renders a lot faster by using known voyage traits rather than calculate list from all possible traits
			const knownShipTraits = ['andorian','battle_cruiser','borg','breen','cardassian','cloaking_device',
				'dominion','emp','explorer','federation','ferengi','freighter','historic','hologram',
				'klingon','malon','maquis','orion_syndicate','pioneer','reman','romulan','ruthless',
				'scout','spore_drive','terran','tholian','transwarp','vulcan','warship','war_veteran','xindi'];
			const knownCrewTraits = ['android','astrophysicist','bajoran','borg','brutal',
				'cardassian','civilian','communicator','costumed','crafty','cultural_figure','cyberneticist',
				'desperate','diplomat','doctor','duelist','exobiology','explorer','federation','ferengi',
				'gambler','hero','hologram','human','hunter','innovator','inspiring','jury_rigger','klingon',
				'marksman','maverick','pilot','prodigy','resourceful','romantic','romulan',
				'saboteur','scoundrel','starfleet','survivalist','tactician','telepath','undercover_operative',
				'veteran','villain','vulcan'];

			const skillsList = [];
			for (let skill in CONFIG.SKILLS) {
				skillsList.push({
					key: skill,
					value: skill,
					text: CONFIG.SKILLS[skill]
				});
			}

			const shipTraitsList = knownShipTraits.map(trait => {
				return {
					key: trait,
					value: trait,
					text: allTraits.ship_trait_names[trait]
				};
			});
			shipTraitsList.sort((a, b) => a.text.localeCompare(b.text));

			const crewTraitsList = knownCrewTraits.map(trait => {
				return {
					key: trait,
					value: trait,
					text: allTraits.trait_names[trait]
				};
			});
			crewTraitsList.sort((a, b) => a.text.localeCompare(b.text));

			setOptions({ skills: skillsList, ships: shipTraitsList, traits: crewTraitsList });
			return (<></>);
		}

		return (
			<React.Fragment>
				<Message>Editing this voyage will reset all existing recommendations and estimates.</Message>
				<Form>
					<Form.Group>
						<Form.Select
							label='Primary skill'
							options={options.skills}
							value={voyageConfig.skills.primary_skill ?? 'command_skill'}
							onChange={(e, { value }) => setSkill('primary_skill', value)}
							placeholder='Primary'
						/>
						<Form.Select
							label='Secondary skill'
							options={options.skills}
							value={voyageConfig.skills.secondary_skill ?? 'science_skill'}
							onChange={(e, { value }) => setSkill('secondary_skill', value)}
							placeholder='Secondary'
						/>
						<Form.Select
							search clearable
							label='Ship trait'
							options={options.ships}
							value={voyageConfig.ship_trait}
							onChange={(e, { value }) => setShipTrait(value)}
							placeholder='Ship trait'
						/>
					</Form.Group>
				</Form>
				<Table compact striped>
					<Table.Header>
						<Table.Row>
							<Table.HeaderCell textAlign='center'>Skill</Table.HeaderCell>
							<Table.HeaderCell>Seat</Table.HeaderCell>
							<Table.HeaderCell>Trait</Table.HeaderCell>
						</Table.Row>
					</Table.Header>
					<Table.Body>
						{crewSlots.map((seat, idx) => (
							<Table.Row key={seat.symbol}>
								{ idx % 2 == 0 ?
									(
										<Table.Cell rowSpan='2' textAlign='center'>
											<img alt="{seat.skill}" src={`${process.env.GATSBY_ASSETS_URL}atlas/icon_${seat.skill}.png`} style={{ height: '2em' }} />
										</Table.Cell>
									)
									: (<></>)
								}
								<Table.Cell>{seat.name}</Table.Cell>
								<Table.Cell>
									<Dropdown search selection clearable
										options={options.traits}
										value={seat.trait}
										onChange={(e, { value }) => setSeatTrait(seat.symbol, value)}
										placeholder='Trait'
									/>
								</Table.Cell>
							</Table.Row>
						))}
					</Table.Body>
				</Table>
			</React.Fragment>
		);
	}

	function setSkill(prime: string, value: string): void {
		// Flip skill values if changing to value that's currently set as the other prime
		if (prime == 'primary_skill' && value == voyageConfig.skills.secondary_skill)
			voyageConfig.skills.secondary_skill = voyageConfig.skills.primary_skill;
		else if (prime == 'secondary_skill' && value == voyageConfig.skills.primary_skill)
			voyageConfig.skills.primary_skill = voyageConfig.skills.secondary_skill;
		voyageConfig.skills[prime] = value;
		setVoyageConfig({...voyageConfig});
		setUpdateOnClose(true);
	}

	function setShipTrait(value: string): void {
		voyageConfig.ship_trait = value;
		setVoyageConfig({...voyageConfig});
		setUpdateOnClose(true);
	}

	function setSeatTrait(seat: symbol, value: string): void {
		voyageConfig.crew_slots.find(s => s.symbol === seat).trait = value;
		setVoyageConfig({...voyageConfig});
		setUpdateOnClose(true);
	}
}
Example #22
Source File: crewchallenge.tsx    From website with MIT License 4 votes vote down vote up
CustomRules = (props: CustomRulesProps) => {
	const portalCrew = React.useContext(PortalCrewContext);
	const [modalIsOpen, setModalIsOpen] = React.useState(false);
	const [guesses, setGuesses] = React.useState(props.rules.guesses);
	const [series, setSeries] = React.useState(props.rules.series);
	const [rarities, setRarities] = React.useState(props.rules.rarities);
	const [excludedCrew, setExcludedCrew] = React.useState([]);

	React.useEffect(() => {
		const excludes = portalCrew.filter(crew => !series.includes(crew.series) || !rarities.includes(crew.max_rarity)).map(crew => crew.symbol);
		setExcludedCrew([...excludes]);
	}, [series, rarities]);

	const guessOptions = [];
	for (let i = 1; i <= 20; i++) {
		guessOptions.push(
			{ key: i, value: i, text: i }
		);
	}

	const seriesOptions = [
		{ key: 'tos', value: 'tos', text: 'The Original Series' },
		{ key: 'tas', value: 'tas', text: 'The Animated Series' },
		{ key: 'tng', value: 'tng', text: 'The Next Generation' },
		{ key: 'ds9', value: 'ds9', text: 'Deep Space Nine' },
		{ key: 'voy', value: 'voy', text: 'Voyager' },
		{ key: 'ent', value: 'ent', text: 'Enterprise' },
		{ key: 'dsc', value: 'dsc', text: 'Discovery' },
		{ key: 'pic', value: 'pic', text: 'Picard' },
		{ key: 'low', value: 'low', text: 'Lower Decks' },
		{ key: 'snw', value: 'snw', text: 'Strange New Worlds' },
		{ key: 'original', value: 'original', text: 'Timelines Originals' }
	];

	const rarityOptions = [
		{ key: '1*', value: 1, text: '1* Common' },
		{ key: '2*', value: 2, text: '2* Uncommon' },
		{ key: '3*', value: 3, text: '3* Rare' },
		{ key: '4*', value: 4, text: '4* Super Rare' },
		{ key: '5*', value: 5, text: '5* Legendary' }
	];

	const isDefault = guesses === DEFAULT_GUESSES && series.length === DEFAULT_SERIES.length && rarities.length === DEFAULT_RARITIES.length;
	const isDirty = guesses !== props.rules.guesses || series.length !== props.rules.series.length || rarities.length !== props.rules.rarities.length;
	const isValid = portalCrew.length - excludedCrew.length > 0;

	return (
		<Modal
			open={modalIsOpen}
			onClose={() => { revertRules(); setModalIsOpen(false); }}
			onOpen={() => setModalIsOpen(true)}
			trigger={renderTrigger()}
			size='tiny'
		>
			<Modal.Header>
				Custom rules
				<span style={{ paddingLeft: '1em', fontSize: '.9em', fontWeight: 'normal' }}>
					(Possible solutions: {portalCrew.length - excludedCrew.length})
				</span>
			</Modal.Header>
			<Modal.Content>
				<div>
					Max guesses:{' '}
					<Dropdown selection
						options={guessOptions}
						value={guesses}
						onChange={(e, { value }) => setGuesses(value)}
					/>
				</div>
				<div style={{ marginTop: '1em' }}>
					Include crew by series:
					<Dropdown selection multiple fluid clearable closeOnChange
						placeholder='Select at least 1 series'
						options={seriesOptions}
						value={series}
						onChange={(e, { value }) => setSeries(value)}
					/>
				</div>
				<div style={{ marginTop: '1em' }}>
					Include crew by rarity:
					<Dropdown selection multiple fluid clearable closeOnChange
						placeholder='Select at least 1 rarity'
						options={rarityOptions}
						value={rarities}
						onChange={(e, { value }) => setRarities(value)}
					/>
				</div>
			</Modal.Content>
			<Modal.Actions>
				{!isDefault && <Button content='Reset' onClick={() => resetRules()} />}
				{isDirty && <Button positive={isValid ? true : undefined} content='New Practice Game' onClick={() => applyRules()} />}
				{!isDirty && <Button content='Close' onClick={() => setModalIsOpen(false)} />}
			</Modal.Actions>
		</Modal>
	);

	function renderTrigger(): JSX.Element {
		return (
			<span style={{ paddingLeft: '1em' }}>
				<Button compact>
					{!isDefault && <span><Icon name='check' color='green' /> Use custom rules</span>}
					{isDefault && <span>Use custom rules...</span>}
				</Button>
			</span>
		);
	}

	function revertRules(): void {
		setGuesses(props.rules.guesses);
		setSeries(props.rules.series);
		setRarities(props.rules.rarities);
	}

	function resetRules(): void {
		setGuesses(DEFAULT_GUESSES);
		setSeries(DEFAULT_SERIES);
		setRarities(DEFAULT_RARITIES);
	}

	function applyRules(): void {
		if (!isValid) return;
		const newRules = new GameRules();
		newRules.guesses = guesses;
		newRules.excludedCrew = excludedCrew;
		newRules.series = series;
		newRules.rarities = rarities;
		props.changeRules(newRules);
		setModalIsOpen(false);
	}
}
Example #23
Source File: crewchallenge.tsx    From website with MIT License 4 votes vote down vote up
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 #24
Source File: events.tsx    From website with MIT License 4 votes vote down vote up
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 #25
Source File: playertools.tsx    From website with MIT License 4 votes vote down vote up
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 #26
Source File: LobbyRoomList.tsx    From FLECT_Amazon_Chime_Meeting with Apache License 2.0 4 votes vote down vote up
render() {
        const gs = this.props as GlobalState
        const props = this.props as any
        const appState = props.appState as AppState

        const joinedMeetingIds = Object.keys(appState.joinedMeetings)


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

            const currentMeetingId = meeting.meetingId

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

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

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


        return (
            <div>
                <div>

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

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

                        </List>

                        <Divider hidden />


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

                            </Item.Group>                            

                        </div>
                    </Segment>
                </div>
            </div>
        )
    }
Example #27
Source File: crewretrieval.tsx    From website with MIT License 4 votes vote down vote up
PolestarProspectModal = (props: PolestarProspectModalProps) => {
	const { ownedPolestars, allCrew, updateProspects } = props;

	const [addedPolestars, setAddedPolestars] = React.useState(props.addedPolestars);

	const [modalIsOpen, setModalIsOpen] = React.useState(false);
	const [activeCrew, setActiveCrew] = React.useState('');
	const [activeConstellation, setActiveConstellation] = React.useState('');
	const [activePolestar, setActivePolestar] = React.useState('');

	const [allKeystones, setAllKeystones] = React.useState(undefined);
	const [control, setControl] = React.useState([]);
	const [crewCrates, setCrewCrates] = React.useState(0);
	const [ownedConstellations, setOwnedConstellations] = React.useState([]);

	React.useEffect(() => {
		if (allKeystones) {
			// Chances assume you can't get rarity, skill constellations from scans
			setCrewCrates(allKeystones.filter(k => k.type == 'crew_keystone_crate').length);
			const owned = allKeystones.filter(k => (k.type == 'crew_keystone_crate' || k.type == 'keystone_crate') && k.quantity > 0)
				.sort((a, b) => a.name.localeCompare(b.name));
			setOwnedConstellations([...owned]);
		}
	}, [allKeystones]);

	// Recalculate combos only when modal gets closed
	React.useEffect(() => {
		if (!modalIsOpen) {
			updateProspects([...addedPolestars]);
		}
	}, [modalIsOpen]);

	return (
		<Modal
			open={modalIsOpen}
			onClose={() => setModalIsOpen(false)}
			onOpen={() => setModalIsOpen(true)}
			trigger={<Button><Icon name='add' />{addedPolestars.length}</Button>}
			size='large'
		>
			<Modal.Header>Add Prospective Polestars</Modal.Header>
			<Modal.Content scrolling>
				{renderContent()}
			</Modal.Content>
			<Modal.Actions>
				{activePolestar != '' && (<Button icon='backward' content='Return to polestars' onClick={() => setActivePolestar('')} />)}
				<Button positive onClick={() => setModalIsOpen(false)}>Close</Button>
			</Modal.Actions>
		</Modal>
	);

	function renderContent(): JSX.Element {
		if (!modalIsOpen) return (<></>);

		if (!allKeystones) {
			calculateKeystoneOdds();
			calculateControl();
			return (<></>);
		}

		if (activePolestar != '')
			return renderPolestarDetail();

		return renderPolestarFinder();
	}

	function calculateKeystoneOdds(): void {
		const allkeystones = JSON.parse(JSON.stringify(props.allKeystones));
		let totalCrates = 0, totalDrops = 0;
		allkeystones.forEach(keystone => {
			if (keystone.type == 'crew_keystone_crate') {
				totalCrates++;
				totalDrops += keystone.keystones.length;
			}
		});
		allkeystones.filter(k => k.type == 'keystone').forEach(polestar => {
			const crates = allkeystones.filter(k => (k.type == 'crew_keystone_crate' || k.type == 'keystone_crate') && k.keystones.includes(polestar.id));
			const nochance = polestar.filter.type == 'rarity' || polestar.filter.type == 'skill' || crates.length == 0;
			polestar.crate_count = nochance ? 0 : crates.length;
			//polestar.scan_odds = nochance ? 0 : crates.length/totalDrops; // equal chance of dropping
			polestar.scan_odds = nochance ? 0 : crates.reduce((prev, curr) => prev + (1/curr.keystones.length), 0)/totalCrates; // conditional probability
			const owned = crates.filter(k => k.quantity > 0);
			polestar.owned_crate_count = owned.reduce((prev, curr) => prev + curr.quantity, 0);
			polestar.owned_best_odds = owned.length == 0 ? 0 : 1/owned.reduce((prev, curr) => Math.min(prev, curr.keystones.length), 100);
			polestar.owned_total_odds = owned.length == 0 ? 0 : 1-owned.reduce((prev, curr) => prev*(((curr.keystones.length-1)/curr.keystones.length)**curr.quantity), 1);
			if (polestar.filter.type === 'rarity')
				polestar.crew_count = allCrew.filter(c => c.in_portal && c.max_rarity == polestar.filter.rarity).length;
			else if (polestar.filter.type === 'skill')
				polestar.crew_count = allCrew.filter(c => c.in_portal && c.base_skills[polestar.filter.skill]).length;
			else if (polestar.filter.type === 'trait')
				polestar.crew_count = allCrew.filter(c => c.in_portal && c.traits.some(trait => trait === polestar.filter.trait)).length;
		});
		setAllKeystones([...allkeystones]);
	}

	function calculateControl(): void {
		// Control is a list of crew that you can't retrieve, which includes crew not in portal
		const retrievable = getRetrievable(allCrew, ownedPolestars);
		const unretrievable = allCrew.filter(pc => !retrievable.some(cc => cc === pc));
		setControl([...unretrievable]);
	}

	function renderPolestarFinder(): JSX.Element {
		const polestarTable: ITableConfigRow[] = [
			{ width: 2, column: 'name', title: 'Polestar' },
			{ width: 1, column: 'crew_count', title: 'Crew in Portal', reverse: true },
			{ width: 1, column: 'crate_count', title: 'Constellation Chance', reverse: true },
			{ width: 1, column: 'scan_odds', title: 'Scan Chance', reverse: true },
			{ width: 1, column: 'owned_best_odds', title: 'Best Chance', reverse: true },
			{ width: 1, column: 'quantity', title: 'Owned', reverse: true },
			{ width: 1, column: 'loaned', title: 'Added', reverse: true }
		];

		const constellationList = ownedConstellations.map(c => {
				return { key: c.symbol, value: c.symbol, text: c.name };
			});

		// !! Always filter polestars by crew_count to hide deprecated polestars !!
		let data = allKeystones.filter(k => k.type == 'keystone' && k.crew_count > 0);
		if (activeCrew != '') {
			const crew = allCrew.find(c => c.symbol === activeCrew);
			data = data.filter(k => (k.filter.type == 'trait' && crew.traits.includes(k.filter.trait))
				|| (k.filter.type == 'rarity' && k.filter.rarity == crew.max_rarity)
				|| (k.filter.type == 'skill' && k.filter.skill in crew.base_skills));
		}
		if (activeConstellation != '') {
			const crewKeystones = allKeystones.find(k => k.symbol === activeConstellation).keystones;
			data = data.filter(k => crewKeystones.includes(k.id));
		}
		data.forEach(p => {
			p.loaned = addedPolestars.filter(added => added === p.symbol).length;
		});

		return (
			<React.Fragment>
				<CrewPicker crew={control} value={activeCrew} updateCrew={updateCrew} />
				{activeCrew == '' && constellationList.length > 0 && (
					<React.Fragment>
						<span style={{ margin: '0 1em' }}>or</span>
						<Dropdown
							placeholder='Filter polestars by owned constellation'
							style={{ minWidth: '20em' }}
							selection
							clearable
							options={constellationList}
							value={activeConstellation}
							onChange={(e, { value }) => setAsActive('constellation', value) }
						/>
					</React.Fragment>
				)}
				{renderCrewMessage(data)}
				{renderConstellationMessage(data)}
				<div style={{ marginTop: '1em' }}>
					<SearchableTable
						data={data}
						config={polestarTable}
						renderTableRow={(polestar, idx) => renderPolestarRow(polestar, idx)}
						filterRow={(polestar, filter) => filterText(polestar, filter)}
						explanation={
							<div>
								<p>Search for polestars by name.</p>
							</div>
						}
					/>
					<p>
						<i>Constellation Chance</i>: your chance of acquiring any constellation with the polestar from a successful scan.
						<br /><i>Scan Chance</i>: your overall chance of acquiring the polestar from a successful scan.
						<br /><i>Best Chance</i>: your best chance of acquiring the polestar from a constellation in your inventory.
					</p>
				</div>
			</React.Fragment>
		);
	}

	function filterText(polestar: any, filters: []): boolean {
		if (filters.length == 0) return true;

		const matchesFilter = (input: string, searchString: string) =>
			input.toLowerCase().indexOf(searchString.toLowerCase()) >= 0;

		let meetsAnyCondition = false;

		for (let filter of filters) {
			let meetsAllConditions = true;
			if (filter.conditionArray.length === 0) {
				// text search only
				for (let segment of filter.textSegments) {
					let segmentResult = matchesFilter(polestar.name, segment.text);
					meetsAllConditions = meetsAllConditions && (segment.negated ? !segmentResult : segmentResult);
				}
			}
			if (meetsAllConditions) {
				meetsAnyCondition = true;
				break;
			}
		}

		return meetsAnyCondition;
	}

	function renderPolestarRow(polestar: any, idx: number): JSX.Element {
		return (
			<Table.Row key={polestar.symbol}
				style={{ cursor: activePolestar != polestar.symbol ? 'zoom-in' : 'zoom-out' }}
				onClick={() => setActivePolestar(activePolestar != polestar.symbol ? polestar.symbol : '')}
			>
				<Table.Cell>
					<div
						style={{
							display: 'grid',
							gridTemplateColumns: '30px auto',
							gridTemplateAreas: `'icon stats'`,
							gridGap: '1px'
						}}
					>
						<div style={{ gridArea: 'icon' }}>
							<img width={24} src={`${process.env.GATSBY_ASSETS_URL}${polestar.icon.file.substr(1).replace(/\//g, '_')}`} />
						</div>
						<div style={{ gridArea: 'stats' }}>
							<span style={{ fontWeight: 'bolder', fontSize: '1.1em' }}>{polestar.short_name}</span>
						</div>
					</div>
				</Table.Cell>
				<Table.Cell textAlign='center'>{polestar.crew_count}</Table.Cell>
				<Table.Cell textAlign='center'>{(polestar.crate_count/crewCrates*100).toFixed(1)}%</Table.Cell>
				<Table.Cell textAlign='center'>{(polestar.scan_odds*100).toFixed(2)}%</Table.Cell>
				<Table.Cell textAlign='center'>{(polestar.owned_best_odds*100).toFixed(1)}%</Table.Cell>
				<Table.Cell textAlign='center'>{polestar.quantity}</Table.Cell>
				<Table.Cell textAlign='center'>
					<ProspectInventory polestar={polestar.symbol} loaned={polestar.loaned} updateProspect={updateProspect} />
				</Table.Cell>
			</Table.Row>
		);
	}

	function renderCrewMessage(data: any[]): JSX.Element {
		if (activeCrew == '') return (<></>);

		const crew = allCrew.find(c => c.symbol === activeCrew);

		if (!crew.in_portal)
			return (<Message>{crew.name} is not available by crew retrieval.</Message>);

		if (crew.unique_polestar_combos?.length == 0)
			return (<Message>{crew.name} has no guaranteed retrieval options.</Message>);

		const unownedPolestars = data.filter(p => p.quantity === 0);
		if (unownedPolestars.length == 0)
			return (<Message>You can already retrieve {crew.name} with the polestars in your inventory.</Message>);

		crew.unique_polestar_combos.forEach(upc => {
			const needs = unownedPolestars.filter(p => upc.some(trait => filterTraits(p, trait)));
			needs.forEach(p => {
				p.useful = p.useful ? p.useful + 1: 1;
				if (needs.length == 1) p.useful_alone = true;
			});
		});

		// "Useful" polestars are all unowned polestars that unlock retrievals by themselves (i.e. `useful_alone`)
		//	or other unowned polestars that together unlock retrievals WITHOUT also relying on a `useful_alone` polestar
		const usefulPolestars = unownedPolestars.filter(p => p.useful_alone ||
			crew.unique_polestar_combos.filter(upc => !upc.some(trait =>
				unownedPolestars.filter(p => p.useful_alone).some(p => filterTraits(p, trait))
			)).some(upc => upc.some(trait => filterTraits(p, trait))))
			.sort((a, b) => b.useful - a.useful);

		const showUsefulPolestars = () => {
			if (usefulPolestars.length == 0)
				return (<p>No unowned polestars will help you retrieve {crew.name}.</p>); // What case is this?

			const usefulAlone = usefulPolestars.filter(p => p.useful_alone);
			const usefulWithOthers = usefulPolestars.filter(p => !p.useful_alone); // Should either be 0 or 2+

			return (
				<p>
					{usefulAlone.length > 0 && (<span>You need exactly one of the following polestars to retrieve {crew.name}: {renderPolestarsInline(usefulAlone)}</span>)}
					{usefulAlone.length > 0 && usefulWithOthers.length > 0 && (<span><br />Or some combination of the following polestars: {renderPolestarsInline(usefulWithOthers)}</span>)}
					{usefulAlone.length == 0 && (<span>You need some combination of the following polestars to retrieve {crew.name}: {renderPolestarsInline(usefulWithOthers)}</span>)}
				</p>
			);
		};

		// "Usable" constellations are owned constellations that include useful polestars
		const showUsableConstellations = () => {
			const usablePolestars = usefulPolestars.filter(p => p.owned_crate_count > 0);
			if (usablePolestars.length == 0) return (<></>);

			const constellations = ownedConstellations.filter(k => k.keystones.some(kId => usablePolestars.find(p => p.id === kId)));
			if (constellations.length == 1)
				return constellations.map(k => renderPolestarsFromConstellation(k, usablePolestars.filter(p => k.keystones.some(kId => kId === p.id))));

			return usablePolestars.sort((a, b) => {
					if (b.owned_total_odds == a.owned_total_odds)
						return b.owned_best_odds - a.owned_best_odds;
					return b.owned_total_odds - a.owned_total_odds;
				}).map(p =>
					renderConstellationsWithPolestar(p)
				);
		};

		return (
			<Message>
				{showUsefulPolestars()}
				{showUsableConstellations()}
			</Message>
		);
	}

	function renderPolestarsInline(polestars: any[]): JSX.Element[] {
		return polestars.map((p, pdx) => (
				<span key={pdx} onClick={() => setActivePolestar(p.symbol) }>
					<b>{p.short_name}</b>{pdx < polestars.length-1 ? ',' : ''}
				</span>
				))
			.reduce((prev, curr) => [prev, ' ', curr]);
	}

	function renderConstellationMessage(data: any[]): JSX.Element {
		if (activeConstellation == '') return (<></>);

		const constellation = allKeystones.find(k => k.symbol === activeConstellation);

		const unownedPolestars = data.filter(p => p.quantity === 0);

		if (unownedPolestars.length == 0)
			return (<Message>You already own all polestars in the {constellation.name}.</Message>);

		return (
			<Message>
				{renderPolestarsFromConstellation(constellation, unownedPolestars)}
			</Message>
		);
	}

	function renderPolestarDetail(): JSX.Element {
		const polestar = allKeystones.find(k => k.symbol === activePolestar);
		polestar.loaned = addedPolestars.filter(added => added === polestar.symbol).length;

		return (
			<div style={{ marginTop: '1em' }}>
				<Table celled striped unstackable compact="very">
					<Table.Header>
						<Table.Row>
							<Table.HeaderCell>Polestar</Table.HeaderCell>
							<Table.HeaderCell textAlign='center'>Crew in Portal</Table.HeaderCell>
							<Table.HeaderCell textAlign='center'>Constellation Chance</Table.HeaderCell>
							<Table.HeaderCell textAlign='center'>Scan Chance</Table.HeaderCell>
							<Table.HeaderCell textAlign='center'>Best Chance</Table.HeaderCell>
							<Table.HeaderCell textAlign='center'>Owned</Table.HeaderCell>
							<Table.HeaderCell textAlign='center'>Added</Table.HeaderCell>
						</Table.Row>
					</Table.Header>
					<Table.Body>
						{renderPolestarRow(polestar, 1)}
					</Table.Body>
				</Table>
				{polestar.owned_crate_count > 0 && (<Message>{renderConstellationsWithPolestar(polestar)}</Message>)}
				{renderNewRetrievals(polestar)}
			</div>
		);
	}

	function renderPolestarsFromConstellation(constellation: any, polestars: any[]): JSX.Element {
		const clarify = activeCrew != '' ? 'a needed' : 'an unowned';

		return (
			<div key={constellation.symbol}>
				Open the <b><span onClick={() => setAsActive('constellation', constellation.symbol) }>{constellation.name}</span></b>{` `}
				for a <b>{(polestars.length/constellation.keystones.length*100).toFixed(1)}%</b> chance of acquiring {clarify} polestar:{` `}
				<Grid centered padded stackable>
				{
					polestars.map((p, pdx) => (
						<Grid.Column key={pdx} width={2} textAlign='center' onClick={() => setActivePolestar(p.symbol)}>
							<img width={32} src={`${process.env.GATSBY_ASSETS_URL}${p.icon.file.substr(1).replace(/\//g, '_')}`} />
							<br /><b>{p.short_name}</b><br /><small>({(1/constellation.keystones.length*100).toFixed(1)}%)</small>
						</Grid.Column>
					))
				}
				</Grid>
				{activeCrew == '' && constellation.quantity > 1 && (<p>You own {constellation.quantity} of this constellation.</p>)}
			</div>
		);
	}

	function renderConstellationsWithPolestar(polestar: any): JSX.Element {
		const constellations = [];
		ownedConstellations.filter(k => k.keystones.includes(polestar.id))
			.forEach(k => {
				for (let i = 0; i < k.quantity; i++) {
					const newName = k.quantity > 1 ? k.name + " #"+(i+1) : k.name;
					constellations.push({...k, name: newName});
				}
			});

		return (
			<p key={polestar.symbol}>
				Open{` `}
				{
					constellations.sort((a, b) => 1/b.keystones.length - 1/a.keystones.length).map((k, kdx) => (
						<span key={kdx} onClick={() => setAsActive('constellation', k.symbol) }>
							<b>{k.name}</b> ({(1/k.keystones.length*100).toFixed(1)}%){kdx < constellations.length-1 ? ' or ' : ''}
						</span>
					)).reduce((prev, curr) => [prev, ' ', curr])
				}{` `}
				for a chance of acquiring the <b><span onClick={() => setActivePolestar(polestar.symbol)}>{polestar.name}</span></b>
				{constellations.length > 1 && (<span>; open all for a <b>{(polestar.owned_total_odds*100).toFixed(1)}%</b> chance</span>)}
			</p>
		);
	}

	function renderNewRetrievals(polestar: any): JSX.Element {
		const ownedPlus = JSON.parse(JSON.stringify(ownedPolestars));
		ownedPlus.push({...polestar, quantity: 1});
		const newRetrievables = getRetrievable(control, ownedPlus).filter(c => c.in_portal);

		if (newRetrievables.length == 0)
			return (
				<p>
					{polestar.quantity > 0 ? `You own ${polestar.quantity} of the ${polestar.name}. ` : ''}
					Acquiring{polestar.quantity > 0 ? ` more of ` : ` `}this polestar will not unlock guaranteed retrievals for any new crew.
				</p>
			);

		return (
			<React.Fragment>
				<p>Acquire the <b>{polestar.name}</b> to unlock guaranteed retrievals for the following crew:</p>
				<Grid centered padded stackable>
					{newRetrievables.sort((a, b) => a.name.localeCompare(b.name)).map((crew, cdx) => (
						<Grid.Column key={crew.symbol} width={2} textAlign='center' onClick={() => setAsActive('crew', crew.symbol) }>
							<ItemDisplay
								src={`${process.env.GATSBY_ASSETS_URL}${crew.imageUrlPortrait}`}
								size={64}
								maxRarity={crew.max_rarity}
								rarity={crew.highest_owned_rarity}
							/>
							<div>{crew.name}</div>
						</Grid.Column>
					))}
				</Grid>
			</React.Fragment>
		);
	}

	function getRetrievable(crewpool: any[], polestars: any[]): any[] {
		return crewpool.filter(crew =>
			crew.unique_polestar_combos?.some(upc =>
				upc.every(trait => polestars.some(op => filterTraits(op, trait)))
			));
	}

	function setAsActive(activeType: string, activeValue: string): void {
		setActiveCrew(activeType == 'crew' ? activeValue : '');
		setActiveConstellation(activeType == 'constellation' ? activeValue : '');
		setActivePolestar('');
	}

	function updateCrew(symbol: string): void {
		setAsActive('crew', symbol);
	}

	function updateProspect(polestar: string, increase: boolean): void {
		if (polestar == '') return;
		if (increase) {
			addedPolestars.push(polestar);
		}
		else {
			const prospectNum = addedPolestars.indexOf(polestar);
			if (prospectNum >= 0) addedPolestars.splice(prospectNum, 1);
		}
	}
}
Example #28
Source File: crewretrieval.tsx    From website with MIT License 4 votes vote down vote up
PolestarFilterModal = (props: PolestarFilterModalProps) => {
	const { ownedPolestars, updateDisableds } = props;

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

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

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

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

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

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

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

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

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

	function handleFilterChange(id: number, checked: boolean): void {
		if(checked === true && disabledPolestars.indexOf(id) !== -1) {
			disabledPolestars.splice(disabledPolestars.indexOf(id), 1);
		}
		if(checked === false && disabledPolestars.indexOf(id) === -1) {
			disabledPolestars.push(id);
		}
	}
}
Example #29
Source File: ProfileWidgetPlus.tsx    From communitymap-ui with Apache License 2.0 4 votes vote down vote up
ProfileWidgetPlus: React.FC = () => {
  const user = useAuth();
  const { dialogs } = useMyDirectMessages();
  const unreadDialogs =
    dialogs?.filter((dlg) => dlg.lastMsgId !== dlg.lastReadBy[user?.uid || ''])
      .length || 0;

  const [login, setLogin] = useState(false);
  useEffect(() => {
    if (user && login) {
      setLogin(false);
    }
  }, [user, login]);

  const [showProfile, setShowProfile] = useState(false);

  const signOut = () => getFirebaseApp().auth().signOut();

  return (
    <div id="profile-widget">
      {login && <Login title="" onClose={() => setLogin(false)} />}
      {showProfile && !!user && (
        <Modal open closeIcon onClose={() => setShowProfile(false)}>
          <Modal.Header>Your profile</Modal.Header>
          <Modal.Content>
            <EditUserProfile user={user} />
          </Modal.Content>
        </Modal>
      )}
      {user ? (
        <Dropdown
          trigger={
            <Button className="profile-button" icon size="large">
              <Icon.Group>
                <Icon name="user outline" />
                {unreadDialogs > 0 && (
                  <Icon
                    corner="top right"
                    className="has-unread-messages"
                    name="mail"
                    color="red"
                  />
                )}
              </Icon.Group>
            </Button>
          }
          pointing="top right"
          icon={null}
        >
          <Dropdown.Menu>
            <Dropdown.Item disabled>{user.email}</Dropdown.Item>
            <Dropdown.Divider />
            <Dropdown.Item onClick={() => setShowProfile(true)}>
              <Icon name="user" />
              Profile
            </Dropdown.Item>
            <Dropdown.Item as={Link} to="/my-messages">
              <Icon name="mail" />
              Messages
              {unreadDialogs > 0 && (
                <Label className="user-menu-label" color="blue">
                  {unreadDialogs}
                </Label>
              )}
            </Dropdown.Item>
            <Dropdown.Divider />
            <Dropdown.Item as={Link} to="/terms">
              Terms of Service
            </Dropdown.Item>
            <Dropdown.Item as={Link} to="/privacy">
              Privacy Policy
            </Dropdown.Item>
            <Dropdown.Divider />
            <Dropdown.Item onClick={signOut}>
              <Icon name="log out" />
              Log out
            </Dropdown.Item>
          </Dropdown.Menu>
        </Dropdown>
      ) : (
        // <Button primary size="large" onClick={() => setLogin(true)}>
        //   Sign in
        // </Button>
        <Dropdown
          trigger={
            <Button className="profile-button" icon size="large">
              <Icon name="bars" />
            </Button>
          }
          pointing="top right"
          icon={null}
        >
          <Dropdown.Menu>
            <Dropdown.Item as={Link} to="/terms">
              Terms of Service
            </Dropdown.Item>
            <Dropdown.Item as={Link} to="/privacy">
              Privacy Policy
            </Dropdown.Item>
            <Dropdown.Divider />
            <Dropdown.Item onClick={() => setLogin(true)}>
              <Icon name="sign in" />
              Sign in
            </Dropdown.Item>
          </Dropdown.Menu>
        </Dropdown>
      )}
    </div>
  );
}