react-bootstrap#Alert TypeScript Examples

The following examples show how to use react-bootstrap#Alert. 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: common.tsx    From remote-office-hours-queue with Apache License 2.0 6 votes vote down vote up
ErrorDisplay: React.FC<ErrorDisplayProps> = (props) => {
    const messages = props.formErrors.map(
        (a: FormError, index: number) => (
            <Alert variant='danger' key={index}><b>{a.source}:</b> {a.error.message}</Alert>
        )
    );
    if (messages.length === 0) return null;
    return <div>{messages}</div>;
}
Example #2
Source File: notification-page.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 6 votes vote down vote up
NotificationPage = (props: any) => {

    const { t } = useTranslation();
    const [show, setShow] = React.useState(true);

    React.useEffect(() => {
        if (props)
            setShow(props.show);
    }, [props, props.show])

    return (
        <>
            <Modal
                dialogClassName='align-items-end'
                contentClassName='border-0 bg-transparent'
                show={show}
                backdrop={false}
                keyboard={false}
                centered
                onEnter={() => setTimeout(props.setNotificationShow, 3000, false)}

            >
                <Alert className='qt-notification' variant='success' onClose={() => props.setNotificationShow(false)}>
                    <Image src={successIcon} className='mr-3 my-3' />
                    <p className='qt-notification-text my-auto mx-1'>
                        {t('translation:successfull-transferred')}
                    </p>
                </Alert>
            </Modal>
        </>
    )
}
Example #3
Source File: CommandCodeAsset.tsx    From apps with MIT License 6 votes vote down vote up
CommnadCodeAsset = ({ commandCode, region }: { commandCode: CommandCode.CommandCode; region: Region }) => {
    const flattenAssets = (assetMap: Entity.EntityAssetMap | undefined): string[] => {
        if (!assetMap) return [];

        const assets = [];

        if (assetMap.cc) assets.push(...Object.values(assetMap.cc));

        return assets;
    };

    const displayAssets = (assetMap: Entity.EntityAssetMap | undefined, altName: string) => {
        const assets = flattenAssets(assetMap);

        return mergeElements(
            assets.map((asset) => <Image url={asset} alt={commandCode.name + " " + altName} />),
            ""
        );
    };
    return (
        <>
            <Alert variant="success">
                <IllustratorDescriptor region={region} illustrator={commandCode.illustrator} />
            </Alert>

            <h3>Potraits</h3>
            <div>{displayAssets(commandCode.extraAssets.charaGraph, "Potrait")}</div>

            <hr />

            <h3>Faces</h3>
            <div>{displayAssets(commandCode.extraAssets.faces, "Face")}</div>
        </>
    );
}
Example #4
Source File: CraftEssenceAssets.tsx    From apps with MIT License 6 votes vote down vote up
render() {
        return (
            <div>
                <Alert variant="success">
                    <IllustratorDescriptor
                        region={this.props.region}
                        illustrator={this.props.craftEssence.profile?.illustrator}
                    />
                </Alert>
                <h3>Portraits</h3>
                <div>{this.displayAssets(this.props.craftEssence.extraAssets.charaGraph)}</div>

                <hr />

                <h3>Formation</h3>
                <div>{this.displayAssets(this.props.craftEssence.extraAssets.equipFace)}</div>

                <hr />

                <h3>Thumbnail</h3>
                <div>{this.displayAssets(this.props.craftEssence.extraAssets.faces)}</div>
            </div>
        );
    }
Example #5
Source File: queue.tsx    From remote-office-hours-queue with Apache License 2.0 6 votes vote down vote up
JoinedClosedAlert = (props: JoinedClosedAlertProps) => {
    const statusClause = props.meetingStatus === MeetingStatus.STARTED ? 'your meeting is still in progress' : 'you are still in line';
    return (
        <Alert variant="dark">
            This queue has been closed by the host, but {statusClause}.
            If you are unsure if your meeting will still happen, please contact the host.
        </Alert>
    );
}
Example #6
Source File: queue.tsx    From remote-office-hours-queue with Apache License 2.0 6 votes vote down vote up
WaitingTurnAlert = (props: WaitingTurnAlertProps) => {
    const inPersonEnding = '-- we will tell you when the host is ready. Make sure you are nearby the in-person meeting location.';
    const videoMessageEnding = 'so you can join the meeting once it is created.';

    const typeEnding = props.meetingType === 'inperson' ? inPersonEnding : videoMessageEnding;

    const placeBeginning = props.placeInLine > 0
        ? "It's not your turn yet, but the host may be ready for you at any time."
        : "You're up next, but the host isn't quite ready for you.";
    return (
        <Alert variant="warning">
            {placeBeginning} Pay attention to this page {typeEnding}
        </Alert>
    );
}
Example #7
Source File: ServantLimitImage.tsx    From apps with MIT License 6 votes vote down vote up
ServantLimitImage = ({ region, servant }: { region: Region; servant: Servant.Servant }) => {
    if (servant.ascensionImage.length === 0) return <></>;
    return (
        <Alert variant="success">
            Locked ascension image{servant.ascensionImage.length > 1 ? "s" : ""}:
            <ul className="mb-0">
                {servant.ascensionImage.map((limitImage) => (
                    <li key={limitImage.limitCount}>
                        {ordinalNumeral(limitImage.limitCount)} Ascension:{" "}
                        {ordinalNumeral(limitImage.defaultLimitCount)} Ascension is used unless{" "}
                        <CondTargetValueDescriptor
                            region={region}
                            cond={limitImage.condType}
                            target={limitImage.condTargetId}
                            value={limitImage.condNum}
                        />
                    </li>
                ))}
            </ul>
        </Alert>
    );
}
Example #8
Source File: ServantModelViewer.tsx    From apps with MIT License 6 votes vote down vote up
ServantModelViewer = ({ servant }: { servant: Servant.Servant }) => {
    const spriteModel = servant.extraAssets.spriteModel;
    if (spriteModel.ascension === undefined && spriteModel.costume === undefined) {
        return null;
    }

    return (
        <Alert variant="success">
            Sprite Model:
            <ul className="mb-0">
                {spriteModel.ascension !== undefined ? (
                    <li>
                        Base Model:
                        <AscensionModelViewer assetMap={spriteModel.ascension} />
                    </li>
                ) : null}
                {spriteModel.costume !== undefined && servant.profile?.costume !== undefined ? (
                    <li>
                        Costume Model:
                        <CostumeModelViewer assetMap={spriteModel.costume} costumeDetails={servant.profile?.costume} />
                    </li>
                ) : null}
            </ul>
        </Alert>
    );
}
Example #9
Source File: queue.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
MeetingReadyAlert = (props: TurnAlertProps) => {
    const typeEnding = props.meetingType === 'inperson'
        ? 'go to the in-person meeting location to meet with the host'
        : 'follow the directions to join the meeting now';

    return <Alert variant="success">The host is ready for you! If you haven't already, {typeEnding}.</Alert>;
}
Example #10
Source File: common.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
userLoggedOnWarning = (
    <Alert variant='primary'>
        <strong>Note:</strong> The person you want to add needs to have logged on to Remote Office Hours Queue
        at least once in order to be added.
    </Alert>
)
Example #11
Source File: index.tsx    From bada-frame with GNU General Public License v3.0 5 votes vote down vote up
function ChangeEmailPage() {
    const [email, setEmail] = useState('');
    const [waiting, setWaiting] = useState(true);
    const [showMessage, setShowMessage] = useState(false);
    const [showBigDialog, setShowBigDialog] = useState(false);

    useEffect(() => {
        const token = getToken();
        if (!token) {
            router.push(PAGES.ROOT);
            return;
        }
        setWaiting(false);
    }, []);

    return (
        <Container>
            {waiting ? (
                <EnteSpinner>
                    <span className="sr-only">Loading...</span>
                </EnteSpinner>
            ) : (
                <EnteCard size={showBigDialog ? 'md' : 'sm'}>
                    <Card.Body style={{ padding: '40px 30px' }}>
                        <Card.Title style={{ marginBottom: '32px' }}>
                            <LogoImg src="/icon.svg" />
                            {constants.UPDATE_EMAIL}
                        </Card.Title>
                        <Alert
                            variant="success"
                            show={showMessage}
                            style={{ paddingBottom: 0 }}
                            transition
                            dismissible
                            onClose={() => setShowMessage(false)}>
                            {constants.EMAIL_SENT({ email })}
                        </Alert>
                        <ChangeEmailForm
                            showMessage={(value) => {
                                setShowMessage(value);
                                setShowBigDialog(value);
                            }}
                            setEmail={setEmail}
                        />
                    </Card.Body>
                </EnteCard>
            )}
        </Container>
    );
}
Example #12
Source File: queue.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
function QueueAttendingNotJoined(props: QueueAttendingProps) {
    const joinedOther = props.joinedQueue && props.joinedQueue.id !== props.queue.id;
    
    const notJoinedInpersonMeetingText = (
        props.queue.inperson_location === ''
            ? (
                'The host(s) have not specified an in-person meeting location.'
            )
            : <>In-person meetings will take place at: <strong>{props.queue.inperson_location}</strong></>
    );

    const controls = props.queue.status !== "closed" && (
        joinedOther && props.joinedQueue
            ? (
                <>
                <div className="row">
                    <div className="col-lg">
                        <JoinedQueueAlert joinedQueue={props.joinedQueue}/>
                    </div>
                </div>
                <JoinQueue queue={props.queue} backends={props.backends} onJoinQueue={props.onLeaveAndJoinQueue} disabled={props.disabled}
                    selectedBackend={props.selectedBackend} onChangeSelectedBackend={props.onChangeBackend}/>
                </>
            )
            : (
                <JoinQueue queue={props.queue} backends={props.backends} onJoinQueue={props.onJoinQueue} disabled={props.disabled}
                    selectedBackend={props.selectedBackend} onChangeSelectedBackend={props.onChangeBackend}/>
            )
    );
    const closedAlert = props.queue.status === "closed"
        && <Alert variant="dark"><strong>This queue is currently closed.</strong> Please return at a later time or message the queue host to find out when the queue will be open.</Alert>
    return (
        <>
        {closedAlert}
        <div className="row">
            <ul>
                <li>Number of people currently in line: <strong>{props.queue.line_length}</strong></li>
                <li>You are not in the meeting queue yet</li>
                {
                    props.queue.allowed_backends.includes('inperson') && 
                    <li>{notJoinedInpersonMeetingText}</li>
                }
            </ul>
        </div>
        {controls}
        </>
    );
}
Example #13
Source File: queueEditors.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
export function GeneralEditor(props: GeneralEditorProps) {
    const correctMessage = 'Please correct the invalid entries below in order to proceed.';
    const successMessage = 'Your changes were saved successfully!';

    const allowedFeedbackMessages = props.allowedValidationResult?.isInvalid
        ? props.allowedValidationResult.messages.map((m, key) => <Alert key={key} variant='danger'>{m}</Alert>)
        : undefined;

    return (
        <div>
            <h2>General</h2>
            {props.showSuccessMessage ? <Alert variant='success'>{successMessage}</Alert> : undefined}
            {props.showCorrectGeneralMessage ? <Alert variant='danger'>{correctMessage}</Alert> : undefined}
            <p>{requiredSymbol} indicates a required field.</p>
            <h3>Name {requiredSymbol}</h3>
            <StatelessInputGroupForm
                id='name'
                value={props.name}
                formLabel='Queue Name'
                placeholder='Queue name...'
                disabled={props.disabled}
                validationResult={props.nameValidationResult}
                onChangeValue={props.onChangeName}
            />
            <h3>Description</h3>
            <StatelessTextAreaForm
                id='description'
                value={props.description}
                formLabel='Queue Description'
                placeholder='Queue description...'
                disabled={props.disabled}
                validationResult={props.descriptValidationResult}
                onChangeValue={props.onChangeDescription}
            />
            <h3>Meeting Types {requiredSymbol}</h3>
            <p>Allow the following meeting types (select at least one):</p>
            <div>{allowedFeedbackMessages}</div>
            <AllowedBackendsForm
                allowed={props.allowedMeetingTypes}
                backends={props.backends}
                onChange={props.onChangeAllowed}
                disabled={props.disabled}
            />
            {props.allowedMeetingTypes.has('inperson') && 
                <>
                    <h3>In-Person Meeting Location</h3>
                    <p>
                        Attendees who select to meet in-person will be instructed to meet at this location. 
                        Enter all information an attendee would need to know, such as a street address, building name, and/or room number.
                    </p>
                    <StatelessInputGroupForm 
                        id='inpersonLocation'
                        value={props.inpersonLocation}
                        formLabel='In-Person Meeting Location'
                        placeholder='In-person meeting location...'
                        disabled={props.disabled}
                        validationResult={props.locationValidationResult}
                        onChangeValue={props.onChangeLocation}
                    />
                </>
            }
        </div>
    );
}
Example #14
Source File: QuestEnemy.tsx    From apps with MIT License 5 votes vote down vote up
QuestDropDescriptor = ({ region, drops }: { region: Region; drops: QuestEnemy.EnemyDrop[] }) => {
    return (
        <Alert variant="success">
            <ul style={{ marginBottom: 0 }}>
                {drops.map((drop) => {
                    const dummyGift = {
                        ...drop,
                        id: 0,
                        priority: 0,
                        giftAdds: [],
                    };
                    let ciText = <></>;
                    if (drop.runs > 1) {
                        const c = quantile(0.975, drop.runs - 1);
                        const stdDevOverRuns = Math.sqrt(drop.dropVariance / drop.runs);
                        const lower = drop.dropExpected - c * stdDevOverRuns;
                        const upper = drop.dropExpected + c * stdDevOverRuns;
                        ciText = (
                            <>
                                <br />
                                95% CI: {numToPct(lower)} – {numToPct(upper)}
                            </>
                        );
                    }
                    const tooltip = (
                        <Tooltip id={`drop-detail-tooltip`} style={{ fontSize: "1em" }}>
                            {drop.dropCount.toLocaleString()} drops / {drop.runs.toLocaleString()} runs
                            {ciText}
                        </Tooltip>
                    );
                    return (
                        <li key={`${drop.type}-${drop.objectId}-${drop.num}`}>
                            <GiftDescriptor region={region} gift={dummyGift} />:{" "}
                            <span>{numToPct(drop.dropExpected)}</span>{" "}
                            <OverlayTrigger overlay={tooltip}>
                                <FontAwesomeIcon icon={faInfoCircle} />
                            </OverlayTrigger>
                        </li>
                    );
                })}
            </ul>
        </Alert>
    );
}
Example #15
Source File: SkillBreakdown.tsx    From apps with MIT License 5 votes vote down vote up
render() {
        const skill = this.props.skill;
        const skillAdd =
            this.props.skill.skillAdd.length > 0 ? (
                <Tooltip id="skillAdd-tooltip" style={{ fontSize: "1em" }} lang={Manager.lang()}>
                    {getRubyText(this.props.region, skill.skillAdd[0].name, skill.skillAdd[0].ruby, true)}
                </Tooltip>
            ) : null;

        return (
            <div>
                <h3>
                    <SkillDescriptor region={this.props.region} skill={skill} iconHeight={33} />
                    {skillAdd !== null ? (
                        <>
                            {" "}
                            <OverlayTrigger overlay={skillAdd}>
                                <FontAwesomeIcon icon={faInfoCircle} style={{ fontSize: "0.75em" }} />
                            </OverlayTrigger>
                        </>
                    ) : null}
                </h3>

                {this.props.rankUp !== undefined ? (
                    <Alert variant={"primary"}>Rank Up +{this.props.rankUp}</Alert>
                ) : null}

                {skill.condQuestId && skill.condQuestPhase ? (
                    <Alert variant={"primary"}>
                        Available after{" "}
                        <QuestDescriptor
                            region={this.props.region}
                            questId={skill.condQuestId}
                            questPhase={
                                ["91", "94"].includes(skill.condQuestId.toString().slice(0, 2))
                                    ? 1
                                    : skill.condQuestPhase
                            }
                        />
                    </Alert>
                ) : null}

                <p className="newline">{skill.detail}</p>

                {this.props.extraPassiveCond && skill.extraPassive.length > 0 ? (
                    <>
                        <ExtraPassiveCondition region={this.props.region} extraPassive={skill.extraPassive[0]} />
                    </>
                ) : null}

                <EffectBreakdown
                    region={this.props.region}
                    cooldowns={this.props.cooldowns ? skill.coolDown : undefined}
                    funcs={skill.functions}
                    triggerSkillIdStack={[skill.id]}
                    levels={this.props.levels}
                    scripts={skill.script}
                    additionalSkillId={skill.script.additionalSkillId}
                />
            </div>
        );
    }
Example #16
Source File: queue.tsx    From remote-office-hours-queue with Apache License 2.0 4 votes vote down vote up
function QueueAttendingJoined(props: QueueAttendingProps) {
    const meeting = props.queue.my_meeting!;
    const meetingBackend = getBackendByName(meeting.backend_type, props.backends);
    const isVideoMeeting = VideoBackendNames.includes(meetingBackend.name);
    const inProgress = meeting.status === MeetingStatus.STARTED;

    // Alerts and head
    const closedAlert = props.queue.status === 'closed' && <JoinedClosedAlert meetingStatus={meeting.status} />;

    const turnAlert = meeting.line_place !== null
        ? <WaitingTurnAlert meetingType={meetingBackend.name} placeInLine={meeting.line_place} />
        : <MeetingReadyAlert meetingType={meetingBackend.name} />;

    const headText = inProgress ? 'Your meeting is in progress.' : 'You are currently in line.';

    // Card content
    const changeMeetingType = props.queue.my_meeting?.assignee
        ? <small className="ml-2">(A Host has been assigned to this meeting. Meeting Type can no longer be changed.)</small>
        : <Button variant='link' onClick={props.onShowDialog} aria-label='Change Meeting Type' disabled={props.disabled}>Change</Button>;

    const notificationBlurb = !inProgress
        && (
            <Alert variant="info">
                <small>
                    Did you know? You can receive an SMS (text) message when
                    it's your turn by adding your cell phone number and
                    enabling attendee notifications in
                    your <Link to="/preferences">User Preferences</Link>.
                </small>
            </Alert>
        );

    const agendaBlock = !inProgress
        ? (
            <>
            <Card.Text><strong>Meeting Agenda</strong> (Optional):</Card.Text>
            <Card.Text><small>Let the host(s) know the topic you wish to discuss.</small></Card.Text>
            <EditToggleField
                id='agenda'
                value={meeting.agenda}
                formLabel='Meeting Agenda'
                placeholder=''
                buttonOptions={{ onSubmit: props.onChangeAgenda, buttonType: 'success' }}
                disabled={props.disabled}
                fieldComponent={StatelessInputGroupForm}
                fieldSchema={meetingAgendaSchema}
                showRemaining={true}
                initialState={!meeting.agenda}
            >
                Update
            </EditToggleField>
            </>
        )
        : <Card.Text><strong>Meeting Agenda</strong>: {meeting.agenda ? meeting.agenda : 'None'}</Card.Text>;

    // Meeting actions and info
    const leaveButtonText = inProgress ? 'Cancel My Meeting' : 'Leave the Line';
    const leave = (
        <Button
            variant='link'
            type='button'
            onClick={() => props.onLeaveQueue(meeting)}
            disabled={props.disabled}
            aria-label={leaveButtonText}
        >
            {leaveButtonText}
            {props.disabled && DisabledMessage}
        </Button>
    );

    const joinText = isVideoMeeting && (
        !inProgress
            ? (
                'The host has not created the meeting yet. You will be able to join the meeting once it is created. ' +
                "We'll show a message in this window when it is created -- pay attention to the window so you don't miss it."
            )
            : 'The host has created the meeting. Join it now! The host will join when they are ready for you.'
    );

    const joinedInpersonMeetingText = (
        props.queue.inperson_location === '' 
            ? (
                'The host(s) have not specified an in-person meeting location.'
            )
            : props.queue.inperson_location
    );

    const joinLink = isVideoMeeting && (
        meeting.backend_metadata!.meeting_url
            ? (
                <Button
                    href={meeting.backend_metadata!.meeting_url}
                    target='_blank'
                    variant='warning'
                    className='mr-3'
                    aria-label='Join Meeting'
                    disabled={props.disabled}
                >
                    Join Meeting
                </Button>
            )
            : <span><strong>Please wait. A Join Meeting button will appear here.</strong></span>
    );

    const meetingInfo = isVideoMeeting && <VideoMeetingInfo metadata={meeting.backend_metadata!} backend={meetingBackend} />;

    return (
        <>
        {closedAlert}
        {turnAlert}
        <h3>{headText}</h3>
        <Card className='card-middle card-width center-align'>
            <Card.Body>
                {meeting.line_place !== null && <Card.Text><strong>Your Number in Line</strong>: {meeting.line_place + 1}</Card.Text>}
                {notificationBlurb}
                <Card.Text><strong>Time Joined</strong>: <DateTimeDisplay dateTime={props.queue.my_meeting!.created_at}/></Card.Text>
                <Card.Text>
                    <strong>Meeting Via</strong>: {meetingBackend.friendly_name} {!inProgress && changeMeetingType}
                </Card.Text>
                {
                    meetingBackend.name === 'inperson' &&
                    <Card.Text>
                        <strong>Meet At</strong>: {joinedInpersonMeetingText}
                    </Card.Text>
                }
                {agendaBlock}
            </Card.Body>
        </Card>
        {joinText && <p>{joinText}</p>}
        <Row className='mb-3'>
            <Col>
                {joinLink}
                {leave}
            </Col>
        </Row>
        {meetingInfo}
        </>
    );
}
Example #17
Source File: queueManager.tsx    From remote-office-hours-queue with Apache License 2.0 4 votes vote down vote up
function QueueManager(props: QueueManagerProps) {
    const spacingClass = 'mt-4';

    let startedMeetings = [];
    let unstartedMeetings = [];
    for (const meeting of props.queue.meeting_set) {
        if (meeting.status ===  MeetingStatus.STARTED) {
            startedMeetings.push(meeting);
        } else {
            unstartedMeetings.push(meeting);
        }
    }

    const currentStatus = props.queue.status === 'open';
    const absoluteUrl = `${location.origin}/queue/${props.queue.id}`;

    const cannotReassignHostWarning = (
        <Alert variant='primary'>
            Once you start a video conferencing meeting or indicate you are ready for an attendee in person,
            you cannot re-assign the meeting to another host.
        </Alert>
    );

    return (
        <div>
            <div className="float-right">
                <Link to={`/manage/${props.queue.id}/settings`}>
                    <Button variant='primary' aria-label='Settings'>
                        <FontAwesomeIcon icon={faCog} />
                        <span className='ml-2'>Settings</span>
                    </Button>
                </Link>
            </div>
            <h1>Manage: {props.queue.name}</h1>
            <p><Link to={"/queue/" + props.queue.id}>View as visitor</Link></p>
            <Row noGutters className={spacingClass}>
                <Col md={2}><Form.Label htmlFor='queue-url'>Queue URL</Form.Label></Col>
                <Col md={6}><CopyField text={absoluteUrl} id="queue-url"/></Col>
            </Row>
            <Row noGutters className={spacingClass}>
                <Col md={2}><Form.Label htmlFor='queue-status'>Queue Status</Form.Label></Col>
                <Col md={6}>
                    <Form.Check
                        className='switch'
                        id='queue-status'
                        type='switch'
                        label={currentStatus ? 'Open' : 'Closed'}
                        checked={props.queue.status === 'open'}
                        onChange={(e: ChangeEvent<HTMLInputElement>) => props.onSetStatus(!currentStatus)}
                    />
                </Col>
            </Row>
            <Row noGutters className={spacingClass}>
                <Col md={2}><div id='created'>Created</div></Col>
                <Col md={6}><div aria-labelledby='created'><DateDisplay date={props.queue.created_at} /></div></Col>
            </Row>
            <h2 className={spacingClass}>Meetings in Progress</h2>
            <Row noGutters className={spacingClass}><Col md={8}>{cannotReassignHostWarning}</Col></Row>
            <Row noGutters className={spacingClass}>
                <Col md={12}><MeetingsInProgressTable meetings={startedMeetings} {...props} /></Col>
            </Row>
            <h2 className={spacingClass}>Meetings in Queue</h2>
            <Row noGutters className={spacingClass}>
                <Col md={8}>
                    {userLoggedOnWarning}
                    {props.addMeetingError && <ErrorDisplay formErrors={[props.addMeetingError]} />}
                    <AddAttendeeForm
                        allowedBackends={new Set(props.queue.allowed_backends)}
                        backends={props.backends}
                        defaultBackend={props.defaultBackend}
                        disabled={props.disabled}
                        onSubmit={props.onAddMeeting}
                    />
                </Col>
            </Row>
            <Row noGutters className={spacingClass}>
                <Col md={12}><MeetingsInQueueTable meetings={unstartedMeetings} {...props} /></Col>
            </Row>
        </div>
    );
}
Example #18
Source File: preferences.tsx    From remote-office-hours-queue with Apache License 2.0 4 votes vote down vote up
// null = no changes, [] = valid

function PreferencesEditor(props: PreferencesEditorProps) {
    const [phoneField, setPhoneField] = useState(props.user.phone_number);
    const [countryDialCode, setCountryDialCode] = useState("");
    const [notifyMeAttendee, setNotifyMeAttendee] = useState(props.user.notify_me_attendee);
    const [notifyMeHost, setNotifyMeHost] = useState(props.user.notify_me_host);
    const [validationStatus, setValidationStatus] = useState(undefined as undefined | ValidationStatus);

    const phoneNumberToSubmit = (phoneField.length <= countryDialCode.length) ? "" : phoneField;
    const changed = props.user.phone_number !== phoneNumberToSubmit
        || props.user.notify_me_attendee !== notifyMeAttendee
        || props.user.notify_me_host !== notifyMeHost;

    const phoneInput = (
        <PhoneInput
            country={'us'}
            onlyCountries={['us', 'ca']}
            countryCodeEditable={false}
            value={props.user.phone_number}
            onChange={(value: any, data: any) => {
                setPhoneField(value);
                if ('dialCode' in data) setCountryDialCode(data.dialCode);
            }}
            disabled={props.disabled}
            inputProps={{id: 'phone'}}
            placeholder=""
        />
    );
    const notifyMeAttendeeInput = (
        <Form.Check 
            type="checkbox"
            id="notify-me-attendee"
            className="mt-3"
            disabled={props.disabled}
            checked={notifyMeAttendee}
            onChange={() => setNotifyMeAttendee(!notifyMeAttendee)}
            label="As an attendee, I want to be notified via SMS when it becomes my turn." />
    );
    const notifyMeHostInput = (
        <Form.Check 
            type="checkbox"
            id="notify-me-host"
            className="mt-2"
            disabled={props.disabled}
            checked={notifyMeHost}
            onChange={() => setNotifyMeHost(!notifyMeHost)}
            label="As a host, I want to be notified via SMS when someone joins my empty queue." />
    );

    const validateAndSubmit = (e: React.SyntheticEvent) => {
        e.preventDefault() // Prevent page reload
        if (!changed) {
            setValidationStatus(null);
            return;
        }
        const phoneValidationErrors = phoneNumberToSubmit
            ? validatePhoneNumber(phoneField, countryDialCode)
            : [];
        const optInValidationErrors = [
            (notifyMeAttendee && !phoneNumberToSubmit)
                && new Error("You must enter a phone number to opt in to attendee SMS notifications."),
            (notifyMeHost && !phoneNumberToSubmit)
                && new Error("You must enter a phone number to opt in to host SMS notifications."),
        ].filter(e => e) as Error[];
        const validationErrors = phoneValidationErrors.concat(optInValidationErrors);
        setValidationStatus(validationErrors);
        if (!validationErrors.length)
            props.onUpdateInfo(phoneNumberToSubmit, notifyMeAttendee, notifyMeHost);
    }

    const alertBlock =
        validationStatus === undefined // not yet validated
            ? undefined
        : validationStatus === null
            ? <Alert variant='primary'>Your preferences were not changed.</Alert>
        : validationStatus.length
            ? <Alert variant='danger'><ul className="mb-0">{validationStatus.map(e => <li>{e.message}</li>)}</ul></Alert>
        : props.errorOccurred
            ? <Alert variant='danger'>An error occurred while trying to update your preferences; please try again later.</Alert>
        : <Alert variant='success'>Your preferences were successfully updated.</Alert>

    return (
        <div>
            <h1>View/Update Preferences</h1>
            {alertBlock}
            <Form onSubmit={validateAndSubmit}>
                <p>Enter a phone number in order to opt in to SMS notifications.</p>
                <Form.Group controlId='phone'>
                    <Form.Label>Phone Number</Form.Label>
                    {phoneInput}
                    {notifyMeAttendeeInput}
                    {notifyMeHostInput}
                </Form.Group>
                <Button variant="primary" type="submit" disabled={props.disabled}>Save</Button>
            </Form>
        </div>
    );
}
Example #19
Source File: VieraConfigUI.tsx    From homebridge-vieramatic with Apache License 2.0 4 votes vote down vote up
Body = () => {
  useSingleton(() => void (async (): Promise<void> => await updateGlobalConfig())())
  const state = useState(globalState)

  useEffect(
    () => (state.loading.value ? homebridge.showSpinner() : homebridge.hideSpinner()),
    [state.loading.value]
  )

  const request = async (path: string, body?: unknown) => {
    state.loading.set(true)
    return await homebridge.request(path, body).finally(() => state.loading.set(false))
  }

  const previousConfig = (ip: string): UserConfig | undefined =>
    state.pluginConfig.tvs.value.find((o) => o.ipAddress === ip)

  const backToMain = (form?: IHomebridgeUiFormHelper) => {
    if (form) form.end()
    state.merge({ frontPage: true, selected: none })
  }

  const onEdition = async (raw?: string): Promise<void> => {
    const pair = (challenge: string) => {
      const pinForm = homebridge.createForm(pinRequestSchema, undefined, 'Next', 'Cancel')
      pinForm.onCancel(pinForm.end)
      pinForm.onSubmit(
        async (data) =>
          await request('/pair', { challenge, ip: tv.ipAddress, pin: data.pin })
            .then(async (auth: VieraAuth) => {
              const body = JSON.stringify({ ...tv, appId: auth.appId, encKey: auth.key })
              const specs: VieraSpecs = await request('/specs', body)
              return { auth, specs }
            })
            // eslint-disable-next-line promise/always-return
            .then((payload) => {
              const config = { ...tv, appId: payload.auth.appId, encKey: payload.auth.key }
              state.selected.merge({ config, onHold: false, reachable: true, specs: payload.specs })
            })
            .catch(() => {
              homebridge.toast.error('Wrong PIN...', tv.ipAddress)
              backToMain()
            })
            .finally(pinForm.end)
      )
    }

    if (!raw) {
      state.frontPage.set(false)
      const tvForm = homebridge.createForm(tvAddressSchema, undefined, 'Next', 'Cancel')
      tvForm.onCancel(() => backToMain(tvForm))

      tvForm.onSubmit(async (data) => {
        if (isValidIPv4(data.ipAddress)) {
          if (previousConfig(data.ipAddress))
            homebridge.toast.error('Trying to add an already configured TV set!', data.ipAddress)
          else {
            tvForm.end()
            const config = { hdmiInputs: [], ipAddress: data.ipAddress }
            state.selected.merge({ config, onHold: true })
          }
        } else homebridge.toast.error('Please insert a valid IP address...', data.ipAddress)
      })
    } else
      state.batch((s) => {
        s.selected.merge({ config: JSON.parse(raw), onHold: true }), s.frontPage.set(false)
      })

    while (!state.selected.value?.config) await sleep(250)
    const tv = state.selected.value.config
    await request('/ping', tv.ipAddress).then(async (reachable: boolean) => {
      /* eslint-disable promise/no-nesting*/
      if (!reachable) return state.selected.merge({ onHold: false, reachable })
      return await request('/specs', JSON.stringify(tv))
        .then((specs) => state.selected.merge({ onHold: false, reachable, specs }))
        .catch(async () => await request('/pin', tv.ipAddress).then((challenge) => pair(challenge)))
    })
  }

  const onDeletion = (raw: string) =>
    state.batch((s) => {
      s.frontPage.set(false), s.selected.merge({ config: JSON.parse(raw), onHold: false })
    })

  const FrontPage = () => {
    const flip = () => !state.abnormal.value && state.killSwitch.set((k) => !k)
    const label = `${state.killSwitch.value ? 'deletion' : 'edition'} mode`
    const doIt = (tv: string) => (state.killSwitch.value ? onDeletion(tv) : onEdition(tv))
    const KillBox = () =>
      state.pluginConfig.value.tvs.length === 0 ? (
        <></>
      ) : state.abnormal.value ? (
        <Alert variant="warning" className="d-flex justify-content-center mt-3 mb-5">
          <b>more than one TV with same IP address found: please delete the bogus ones!</b>
        </Alert>
      ) : (
        <Form className="d-flex justify-content-end mt-3 mb-5">
          <Form.Switch onChange={flip} id="kS" label={label} checked={state.killSwitch.value} />
        </Form>
      )
    const style = { height: '4em', width: '10em' }
    const AddNew = () =>
      state.killSwitch.value ? (
        <></>
      ) : (
        <div className="d-flex justify-content-center mt-3 mb-5">
          <Button
            className="my-4"
            variant="primary"
            onClick={async () => await onEdition()}
            style={style}
          >
            <Icon fixedWidth size="sm" icon={faTv} /> <br />
            <Icon fixedWidth size="lg" icon={faCartPlus} />
          </Button>
        </div>
      )
    const Available = () => {
      const variant = state.killSwitch.value ? 'danger' : 'info'
      const onClick = (tv: UserConfig) => doIt(JSON.stringify(tv))
      const tvs = state.pluginConfig.value.tvs.map((tv, index) => (
        <Button variant={variant} style={style} key={index} onClick={() => onClick(tv)}>
          <Icon fixedWidth size="lg" icon={state.killSwitch.value ? faTrash : faTv} />
          <br /> {tv.ipAddress}
        </Button>
      ))
      return <>{tvs}</>
    }

    return (
      <section className="mh-100">
        <KillBox /> <Available /> <AddNew />
      </section>
    )
  }

  const Results = (props: { selected: State<Selected> | undefined }) => {
    if (!props.selected || props.selected.onHold.value) return <></>

    const Offline = (props: { selected: State<Selected> }) => (
      <Alert variant="danger" className="mt-3">
        <Alert.Heading>
          The Viera TV at <b>{props.selected.config.ipAddress.value}</b> could not be edited.
        </Alert.Heading>
        <hr />
        <p className="mb-2">
          Please, do make sure that it is <b>turned on</b> and <b>connected to the network</b>, and
          then try again.
        </p>
        <div className="mt-4 w-75 mx-auto">
          <p className="text-left ">
            Also, <b>if you haven't done it already</b>...
          </p>
          <p className="text-left">
            ...on your TV go to <b>Menu / Network / TV Remote App Settings</b> and make sure that
            the following settings are <b>all</b> turned <b>ON</b>:
            <ul className="mt-2">
              <li>
                <b>TV Remote</b>
              </li>
              <li>
                <b>Powered On by Apps</b>
              </li>
              <li>
                <b>Networked Standby</b>
              </li>
            </ul>
          </p>
        </div>
        <div className="d-flex justify-content-end mt-5">
          <Button onClick={() => backToMain()} variant="primary">
            OK
          </Button>
        </div>
      </Alert>
    )

    const ConfirmDeletion = (props: { selected: State<Selected> }) => {
      const { ipAddress } = props.selected.config.value
      const nxt = rawClone(state.pluginConfig.value.tvs.filter((o) => o.ipAddress !== ipAddress))
      const dropIt = async () =>
        await updateHomebridgeConfig(ipAddress, nxt, actionType.delete).then(() => backToMain())

      return (
        <Alert variant="danger" className="mt-3">
          <Alert.Heading>
            The Viera TV at <b>{ipAddress}</b> is about to be deleted from this Homebridge.
          </Alert.Heading>
          <hr />
          <div className="d-flex justify-content-center">
            <div className="w-75">
              <p className="mb-2">Please, make sure you know what you are doing...</p>
              <hr />
              <pre class="text-monospace text-left bg-light p-2">
                {prettyPrint(props.selected.config.value)}
              </pre>
              <hr />
            </div>
          </div>
          <div className="d-flex justify-content-end mt-1">
            <Button onClick={() => backToMain()} variant="primary">
              Cancel
            </Button>
            <Button onClick={() => dropIt()} variant="danger">
              Delete
            </Button>
          </div>
        </Alert>
      )
    }

    const Editor = (props: { selected: State<Selected> }) => {
      if (props.selected.specs.ornull?.requiresEncryption.value)
        commonFormLayout.splice(1, 0, authLayout)

      const schema = { layout: commonFormLayout, schema: commonSchema }
      const data = rawClone(props.selected.config.value)
      const tvform = homebridge.createForm(schema, data, 'Submit', 'Cancel')
      tvform.onCancel(() => backToMain(tvform))
      tvform.onSubmit(async (submited) => {
        const queued = submited as UserConfig
        state.loading.set(true)
        backToMain(tvform)
        const before = previousConfig(queued.ipAddress)
        let [others, type] = [[] as UserConfig[], actionType.none]

        if (!isSame(before, queued)) {
          const modded = before !== undefined
          const { tvs } = state.pluginConfig.value
          others = modded ? rawClone(tvs.filter((v) => v.ipAddress != queued.ipAddress)) : []
          type = modded ? actionType.update : actionType.create
        }
        await updateHomebridgeConfig(queued.ipAddress, [...others, queued], type).finally(() =>
          state.loading.set(false)
        )
      })
      return <></>
    }

    if (state.killSwitch.value) return <ConfirmDeletion selected={props.selected} />
    if (props.selected.reachable.value) return <Editor selected={props.selected} />
    return <Offline selected={props.selected} />
  }

  return state.frontPage.value ? <FrontPage /> : <Results selected={state.selected.ornull} />
}
Example #20
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
CreateProposalPage = () => {
  const { account } = useEthers();
  const latestProposalId = useProposalCount();
  const latestProposal = useProposal(latestProposalId ?? 0);
  const availableVotes = useUserVotes();
  const proposalThreshold = useProposalThreshold();

  const { propose, proposeState } = usePropose();

  const [proposalTransactions, setProposalTransactions] = useState<ProposalTransaction[]>([]);
  const [titleValue, setTitleValue] = useState('');
  const [bodyValue, setBodyValue] = useState('');

  const handleAddProposalAction = useCallback(
    (transaction: ProposalTransaction) => {
      if (!transaction.address.startsWith('0x')) {
        transaction.address = `0x${transaction.address}`;
      }
      if (!transaction.calldata.startsWith('0x')) {
        transaction.calldata = `0x${transaction.calldata}`;
      }
      setProposalTransactions([...proposalTransactions, transaction]);
      setShowTransactionFormModal(false);
    },
    [proposalTransactions],
  );

  const handleRemoveProposalAction = useCallback(
    (index: number) => {
      setProposalTransactions(proposalTransactions.filter((_, i) => i !== index));
    },
    [proposalTransactions],
  );

  const handleTitleInput = useCallback(
    (title: string) => {
      setTitleValue(title);
    },
    [setTitleValue],
  );

  const handleBodyInput = useCallback(
    (body: string) => {
      setBodyValue(body);
    },
    [setBodyValue],
  );

  const isFormInvalid = useMemo(
    () => !proposalTransactions.length || titleValue === '' || bodyValue === '',
    [proposalTransactions, titleValue, bodyValue],
  );

  const hasEnoughVote = Boolean(
    availableVotes && proposalThreshold !== undefined && availableVotes > proposalThreshold,
  );

  const handleCreateProposal = async () => {
    if (!proposalTransactions?.length) return;

    await propose(
      proposalTransactions.map(({ address }) => address), // Targets
      proposalTransactions.map(({ value }) => value ?? '0'), // Values
      proposalTransactions.map(({ signature }) => signature), // Signatures
      proposalTransactions.map(({ calldata }) => calldata), // Calldatas
      `# ${titleValue}\n\n${bodyValue}`, // Description
    );
  };

  const [showTransactionFormModal, setShowTransactionFormModal] = useState(false);
  const [isProposePending, setProposePending] = useState(false);

  const dispatch = useAppDispatch();
  const setModal = useCallback((modal: AlertModal) => dispatch(setAlertModal(modal)), [dispatch]);

  useEffect(() => {
    switch (proposeState.status) {
      case 'None':
        setProposePending(false);
        break;
      case 'Mining':
        setProposePending(true);
        break;
      case 'Success':
        setModal({
          title: <Trans>Success</Trans>,
          message: <Trans>Proposal Created!</Trans>,
          show: true,
        });
        setProposePending(false);
        break;
      case 'Fail':
        setModal({
          title: <Trans>Transaction Failed</Trans>,
          message: proposeState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setProposePending(false);
        break;
      case 'Exception':
        setModal({
          title: <Trans>Error</Trans>,
          message: proposeState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setProposePending(false);
        break;
    }
  }, [proposeState, setModal]);

  return (
    <Section fullWidth={false} className={classes.createProposalPage}>
      <ProposalTransactionFormModal
        show={showTransactionFormModal}
        onHide={() => setShowTransactionFormModal(false)}
        onProposalTransactionAdded={handleAddProposalAction}
      />
      <Col lg={{ span: 8, offset: 2 }}>
        <Link to="/vote">
          ← <Trans>All Proposals</Trans>
        </Link>
      </Col>
      <Col lg={{ span: 8, offset: 2 }} className={classes.createProposalForm}>
        <h3 className={classes.heading}>
          <Trans>Create Proposal</Trans>
        </h3>
        <Alert variant="secondary" className={classes.voterIneligibleAlert}>
          <b>
            <Trans>Tip:</Trans>
          </b>
          :
          <Trans>
            Add one or more transactions and describe your proposal for the community. The proposal
            cannot modified after submission, so please verify all information before submitting.
            The voting period will begin after 2 1/3 days and last for 3 days.
          </Trans>
        </Alert>
        <div className="d-grid">
          <Button
            className={classes.addTransactionButton}
            variant="dark"
            onClick={() => setShowTransactionFormModal(true)}
          >
            <Trans>Add Transaction</Trans>
          </Button>
        </div>
        <ProposalTransactions
          proposalTransactions={proposalTransactions}
          onRemoveProposalTransaction={handleRemoveProposalAction}
        />
        <ProposalEditor
          title={titleValue}
          body={bodyValue}
          onTitleInput={handleTitleInput}
          onBodyInput={handleBodyInput}
        />
        <CreateProposalButton
          className={classes.createProposalButton}
          isLoading={isProposePending}
          proposalThreshold={proposalThreshold}
          hasActiveOrPendingProposal={
            (latestProposal?.status === ProposalState.ACTIVE ||
              latestProposal?.status === ProposalState.PENDING) &&
            latestProposal.proposer === account
          }
          hasEnoughVote={hasEnoughVote}
          isFormInvalid={isFormInvalid}
          handleCreateProposal={handleCreateProposal}
        />
      </Col>
    </Section>
  );
}
Example #21
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
Proposals = ({ proposals }: { proposals: Proposal[] }) => {
  const history = useHistory();

  const { account } = useEthers();
  const connectedAccountNounVotes = useUserVotes() || 0;
  const currentBlock = useBlockNumber();
  const isMobile = isMobileScreen();
  const activeLocale = useActiveLocale();

  const nullStateCopy = () => {
    if (account !== null) {
      return <Trans>You have no Votes.</Trans>;
    }
    return <Trans>Connect wallet to make a proposal.</Trans>;
  };

  return (
    <div className={classes.proposals}>
      <div>
        <h3 className={classes.heading}>
          <Trans>Proposals</Trans>
        </h3>
        {account !== undefined && connectedAccountNounVotes > 0 ? (
          <div className={classes.submitProposalButtonWrapper}>
            <Button className={classes.generateBtn} onClick={() => history.push('create-proposal')}>
              <Trans>Submit Proposal</Trans>
            </Button>
          </div>
        ) : (
          <div className={clsx('d-flex', classes.submitProposalButtonWrapper)}>
            {!isMobile && <div className={classes.nullStateCopy}>{nullStateCopy()}</div>}
            <div className={classes.nullBtnWrapper}>
              <Button className={classes.generateBtnDisabled}>
                <Trans>Submit Proposal</Trans>
              </Button>
            </div>
          </div>
        )}
      </div>
      {isMobile && <div className={classes.nullStateCopy}>{nullStateCopy()}</div>}
      {proposals?.length ? (
        proposals
          .slice(0)
          .reverse()
          .map((p, i) => {
            const isPropInStateToHaveCountDown =
              p.status === ProposalState.PENDING ||
              p.status === ProposalState.ACTIVE ||
              p.status === ProposalState.QUEUED;

            const infoPills = (
              <>
                {isPropInStateToHaveCountDown && (
                  <div className={classes.proposalStatusWrapper}>
                    <div
                      className={clsx(proposalStatusClasses.proposalStatus, classes.countdownPill)}
                    >
                      <div className={classes.countdownPillContentWrapper}>
                        <span className={classes.countdownPillClock}>
                          <ClockIcon height={16} width={16} />
                        </span>{' '}
                        <span className={classes.countdownPillText}>
                          {getCountdownCopy(p, currentBlock || 0, activeLocale)}
                        </span>
                      </div>
                    </div>
                  </div>
                )}

                <div className={classes.proposalStatusWrapper}>
                  <ProposalStatus status={p.status}></ProposalStatus>
                </div>
              </>
            );

            return (
              <div
                className={clsx(classes.proposalLink, classes.proposalLinkWithCountdown)}
                onClick={() => history.push(`/vote/${p.id}`)}
                key={i}
              >
                <span className={classes.proposalTitle}>
                  <span className={classes.proposalId}>{p.id}</span> <span>{p.title}</span>
                  <div className={classes.proposalInfoPillsWrapperMobile}>{infoPills}</div>
                </span>

                <div className={classes.proposalInfoPillsWrapper}>{infoPills}</div>
              </div>
            );
          })
      ) : (
        <Alert variant="secondary">
          <Alert.Heading>
            <Trans>No proposals found</Trans>
          </Alert.Heading>
          <p>
            <Trans>Proposals submitted by community members will appear here.</Trans>
          </p>
        </Alert>
      )}
    </div>
  );
}
Example #22
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
ProposalHeader: React.FC<ProposalHeaderProps> = props => {
  const { proposal, isActiveForVoting, isWalletConnected, submitButtonClickHandler } = props;

  const isMobile = isMobileScreen();
  const availableVotes = useUserVotesAsOfBlock(proposal?.createdBlock) ?? 0;
  const hasVoted = useHasVotedOnProposal(proposal?.id);
  const proposalVote = useProposalVote(proposal?.id);
  const proposalCreationTimestamp = useBlockTimestamp(proposal?.createdBlock);
  const disableVoteButton = !isWalletConnected || !availableVotes || hasVoted;
  const activeLocale = useActiveLocale();

  const voteButton = (
    <>
      {isWalletConnected ? (
        <>
          {!availableVotes && (
            <div className={classes.noVotesText}>
              <Trans>You have no votes.</Trans>
            </div>
          )}
        </>
      ) : (
        <div className={classes.connectWalletText}>
          <Trans>Connect a wallet to vote.</Trans>
        </div>
      )}
      <Button
        className={disableVoteButton ? classes.submitBtnDisabled : classes.submitBtn}
        disabled={disableVoteButton}
        onClick={submitButtonClickHandler}
      >
        <Trans>Submit vote</Trans>
      </Button>
    </>
  );

  const proposer = (
    <a
      href={buildEtherscanAddressLink(proposal.proposer || '')}
      target="_blank"
      rel="noreferrer"
      className={classes.proposerLinkJp}
    >
      <ShortAddress address={proposal.proposer || ''} avatar={false} />
    </a>
  );

  const proposedAtTransactionHash = (
    <Trans>
      at{' '}
      <span className={classes.propTransactionHash}>
        {transactionLink(proposal.transactionHash)}
      </span>
    </Trans>
  );

  return (
    <>
      <div className="d-flex justify-content-between align-items-center">
        <div className="d-flex justify-content-start align-items-start">
          <Link to={'/vote'}>
            <button className={clsx(classes.backButton, navBarButtonClasses.whiteInfo)}>←</button>
          </Link>
          <div className={classes.headerRow}>
            <span>
              <div className="d-flex">
                <div>
                  <Trans>Proposal {i18n.number(parseInt(proposal.id || '0'))}</Trans>
                </div>
                <div>
                  <ProposalStatus status={proposal?.status} className={classes.proposalStatus} />
                </div>
              </div>
            </span>
            <div className={classes.proposalTitleWrapper}>
              <div className={classes.proposalTitle}>
                <h1>{proposal.title} </h1>
              </div>
            </div>
          </div>
        </div>
        {!isMobile && (
          <div className="d-flex justify-content-end align-items-end">
            {isActiveForVoting && voteButton}
          </div>
        )}
      </div>

      <div className={classes.byLineWrapper}>
        {activeLocale === Locales.ja_JP ? (
          <div className={classes.proposalByLineWrapperJp}>
            <Trans>
              <span className={classes.proposedByJp}>Proposed by: </span>
              <span className={classes.proposerJp}>
              {proposer}
              </span>
              <span className={classes.propTransactionWrapperJp}>{proposedAtTransactionHash}</span>
            </Trans>
          </div>
        ) : (
          <>
            <h3>Proposed by</h3>

            <div className={classes.byLineContentWrapper}>
              <h3>
                {proposer}
                <span className={classes.propTransactionWrapper}>{proposedAtTransactionHash}</span>
              </h3>
            </div>
          </>
        )}
      </div>

      {isMobile && (
        <div className={classes.mobileSubmitProposalButton}>{isActiveForVoting && voteButton}</div>
      )}

      {proposal && isActiveForVoting && hasVoted && (
        <Alert variant="success" className={classes.voterIneligibleAlert}>
          {getTranslatedVoteCopyFromString(proposalVote)}
        </Alert>
      )}

      {proposal && isActiveForVoting && proposalCreationTimestamp && !!availableVotes && !hasVoted && (
        <Alert variant="success" className={classes.voterIneligibleAlert}>
          <Trans>
            Only Nouns you owned or were delegated to you before{' '}
            {i18n.date(new Date(proposalCreationTimestamp * 1000), {
              dateStyle: 'long',
              timeStyle: 'long',
            })}{' '}
            are eligible to vote.
          </Trans>
        </Alert>
      )}
    </>
  );
}
Example #23
Source File: ServantPage.tsx    From apps with MIT License 4 votes vote down vote up
render() {
        if (this.state.error) return <ErrorStatus error={this.state.error} />;

        if (this.state.loading || !this.state.servant) return <Loading />;

        const servant = this.state.servant;
        document.title = `[${this.props.region}] Servant - ${this.getOverwriteName()} - Atlas Academy DB`;

        let remappedCostumeMaterials: Entity.EntityLevelUpMaterialProgression = {};
        if (servant.profile) {
            for (const [costumeId, costume] of Object.entries(servant.costumeMaterials)) {
                if (servant.profile?.costume[costumeId] !== undefined) {
                    remappedCostumeMaterials[servant.profile?.costume[costumeId].name] = costume;
                }
            }
        }

        const rawUrl = `${Host}/raw/${this.props.region}/servant/${servant.id}?expand=true&lore=true`;
        return (
            <div id={"servant"}>
                <ServantPicker region={this.props.region} servants={this.state.servants} id={servant.collectionNo} />
                <hr style={{ marginBottom: "1rem" }} />

                <div style={{ display: "flex", flexDirection: "row", marginBottom: 3 }}>
                    <h1 style={{ marginBottom: "1rem" }}>
                        <ClassIcon className={servant.className} rarity={servant.rarity} height={50} />
                        &nbsp;
                        {this.getOverwriteName()}
                    </h1>
                    <span style={{ flexGrow: 1 }} />
                </div>
                <Row
                    style={{
                        marginBottom: "3%",
                    }}
                >
                    <Col xs={{ span: 12, order: 2 }} lg={{ span: 6, order: 1 }}>
                        <ServantMainData
                            region={this.props.region}
                            servant={this.state.servant}
                            servantName={this.getOverwriteName()}
                            originalServantName={this.getOverwriteName(true)}
                            assetType={this.state.assetType}
                            assetId={this.state.assetId}
                        />
                        <Row>
                            <Col>
                                <RawDataViewer text="Nice" data={servant} />
                            </Col>
                            <Col>
                                <RawDataViewer text="Raw" data={rawUrl} />
                            </Col>
                        </Row>
                    </Col>
                    <Col xs={{ span: 12, order: 1 }} lg={{ span: 6, order: 2 }}>
                        <ServantPortrait
                            servant={this.state.servant}
                            assetType={this.state.assetType}
                            assetId={this.state.assetId}
                            assetExpand={this.state.assetExpand}
                            updatePortraitCallback={(assetType, assetId, assetExpand) => {
                                this.updatePortrait(assetType, assetId, assetExpand);
                            }}
                        />
                    </Col>
                </Row>

                <Tabs
                    id={"servant-tabs"}
                    defaultActiveKey={this.props.tab ?? "skill-1"}
                    mountOnEnter={true}
                    onSelect={(key: string | null) => {
                        this.props.history.replace(`/${this.props.region}/servant/${this.props.id}/${key}`);
                    }}
                >
                    {[1, 2, 3].map((i) => (
                        <Tab key={`skill-${i}`} eventKey={`skill-${i}`} title={`Skill ${i}`}>
                            {servant.skills
                                .filter((skill) => skill.num === i)
                                .sort((a, b) => b.id - a.id)
                                .map((skill, i2) => {
                                    return (
                                        <div key={i2}>
                                            <SkillBreakdown
                                                region={this.props.region}
                                                key={skill.id}
                                                skill={skill}
                                                cooldowns={true}
                                                levels={10}
                                            />
                                            {this.skillRankUps(skill.id).map((rankUpSkill, rankUp) => {
                                                return (
                                                    <SkillReferenceBreakdown
                                                        key={rankUpSkill}
                                                        region={this.props.region}
                                                        id={rankUpSkill}
                                                        cooldowns={true}
                                                        levels={10}
                                                        rankUp={rankUp + 1}
                                                    />
                                                );
                                            })}
                                        </div>
                                    );
                                })}
                        </Tab>
                    ))}
                    <Tab eventKey={"noble-phantasms"} title={"NPs"}>
                        {servant.noblePhantasms
                            .filter((noblePhantasm) => noblePhantasm.functions.length > 0)
                            // Card change NPs have 0 priority.
                            // Card change NPs are sorted by ascending ID number so the main NP is on top.
                            .sort(
                                (a, b) =>
                                    b.strengthStatus - a.strengthStatus ||
                                    (a.priority === 0 || b.priority === 0 ? a.id - b.id : b.id - a.id)
                            )
                            .map((noblePhantasm) => {
                                return (
                                    <NoblePhantasmBreakdown
                                        key={noblePhantasm.id}
                                        region={this.props.region}
                                        servant={servant}
                                        noblePhantasm={noblePhantasm}
                                        assetType={this.state.assetType}
                                        assetId={this.state.assetId}
                                    />
                                );
                            })}
                    </Tab>
                    <Tab eventKey={"passives"} title={"Passives"}>
                        <ServantPassive region={this.props.region} servant={servant} />
                    </Tab>
                    <Tab eventKey={"traits"} title={"Traits"}>
                        <ServantTraits region={this.props.region} servant={this.state.servant} />
                    </Tab>
                    <Tab eventKey={"materials"} title={"Materials"}>
                        <Row>
                            <Col xs={12} lg={6}>
                                <ServantMaterialBreakdown
                                    region={this.props.region}
                                    materials={servant.ascensionMaterials}
                                    title={"Ascension Materials"}
                                    showNextLevelInDescription={true}
                                />
                            </Col>
                            <Col xs={12} lg={6}>
                                <ServantMaterialBreakdown
                                    region={this.props.region}
                                    materials={servant.skillMaterials}
                                    title={"Skill Materials"}
                                    showNextLevelInDescription={true}
                                />
                            </Col>
                        </Row>
                        {servant.appendPassive.length > 0 && servant.coin !== undefined ? (
                            <Row>
                                <Col xs={12} lg={6}>
                                    <ServantMaterialBreakdown
                                        region={this.props.region}
                                        materials={{
                                            "Summon Get": {
                                                items: [
                                                    {
                                                        item: servant.coin.item,
                                                        amount: servant.coin.summonNum,
                                                    },
                                                ],
                                                qp: 0,
                                            },
                                            "Append Skill Unlock Cost": {
                                                items: servant.appendPassive[0].unlockMaterials,
                                                qp: 0,
                                            },
                                        }}
                                        title="Servant Coin"
                                    />
                                </Col>
                                <Col xs={12} lg={6}>
                                    <ServantMaterialBreakdown
                                        region={this.props.region}
                                        materials={servant.appendSkillMaterials}
                                        title="Append Skill Level Up Materials"
                                        showNextLevelInDescription={true}
                                    />
                                </Col>
                            </Row>
                        ) : null}
                        {Object.keys(servant.costumeMaterials).length ? (
                            <ServantMaterialBreakdown
                                region={this.props.region}
                                materials={remappedCostumeMaterials}
                                title={"Costume Materials"}
                                idMinWidth="10em"
                            />
                        ) : null}
                    </Tab>
                    <Tab eventKey={"stat-growth"} title={"Growth"}>
                        <ServantStatGrowth region={this.props.region} servant={servant} />
                    </Tab>
                    <Tab eventKey={"lore"} title={"Profile"}>
                        <Alert variant="success" style={{ lineHeight: "2em" }}>
                            <IllustratorDescriptor
                                region={this.props.region}
                                illustrator={servant.profile?.illustrator}
                            />
                            <br />
                            <VoiceActorDescriptor region={this.props.region} cv={servant.profile?.cv} />
                        </Alert>
                        <ServantBondGrowth bondGrowth={servant.bondGrowth} />
                        <ServantProfileStats region={this.props.region} profile={servant.profile} />
                        <ServantRelatedQuests region={this.props.region} questIds={servant.relateQuestIds} />
                        <ServantRelatedQuests
                            region={this.props.region}
                            questIds={servant.trialQuestIds}
                            title="Trial Quest"
                        />
                        <ServantValentine region={this.props.region} servant={servant} />
                        <ServantCostumeDetails costumes={servant.profile?.costume} />
                        <ServantProfileComments region={this.props.region} comments={servant.profile?.comments ?? []} />
                    </Tab>
                    <Tab eventKey={"assets"} title={"Assets"}>
                        <ServantAssets region={this.props.region} servant={servant} />
                    </Tab>
                    <Tab eventKey={"voices"} title={"Voices"}>
                        <ServantVoiceLines
                            region={this.props.region}
                            servants={new Map(this.state.servants.map((servant) => [servant.id, servant]))}
                            servant={servant}
                            servantName={this.getOverwriteName()}
                        />
                    </Tab>
                </Tabs>
            </div>
        );
    }
Example #24
Source File: ServantVoiceLines.tsx    From apps with MIT License 4 votes vote down vote up
VoiceLinesTable = ({
    region,
    voice,
    mergedDownloadNamePrefix,
    servants,
    costumes,
}: {
    region: Region;
    voice: Profile.VoiceGroup;
    mergedDownloadNamePrefix: string;
    servants: Map<number, Servant.ServantBasic>;
    costumes?: {
        [key: string]: Profile.CostumeDetail;
    };
}) => {
    const voiceLines = voice.voiceLines.sort((a, b) => (b.priority || 0) - (a.priority || 0));
    const voiceLineNames: string[] = [];
    const voiceNameCount: Record<string, number> = {};
    for (const line of voiceLines) {
        line.conds = line.conds.filter(
            (cond) => !(cond.condType === Profile.VoiceCondType.EVENT_END && cond.value === 0)
        );
        let lineName = line.overwriteName || line.name || "";
        if (lineName in voiceNameCount) {
            voiceNameCount[lineName]++;
        } else {
            voiceNameCount[lineName] = 1;
        }
        voiceLineNames.push(lineName.replace("{0}", voiceNameCount[lineName].toString()));
    }

    return (
        <Table bordered className="mb-0">
            <tbody>
                {voiceLines.map((line, index) => (
                    <tr key={`line_${index}`}>
                        <td style={{ verticalAlign: "middle" }}>
                            <b className="newline">{voiceLineNames[index]}</b>
                            <br />
                            <div className="newline">
                                {voiceTextField(region, voice.type) ? (
                                    line.text.map((line, i) => (
                                        <VoiceSubtitleFormat key={i} region={region} inputString={line} />
                                    ))
                                ) : (
                                    <VoiceSubtitleFormat region={region} inputString={line.subtitle} />
                                )}
                            </div>
                            {line.conds.length || line.playConds.length || line.summonScript ? (
                                <>
                                    <Alert variant="info" style={{ marginBottom: 0, marginTop: "1em" }}>
                                        {line.summonScript === undefined ? null : (
                                            <>
                                                Summoning Script:{" "}
                                                <ScriptDescriptor
                                                    region={region}
                                                    scriptId={line.summonScript.scriptId}
                                                    scriptType=""
                                                />
                                            </>
                                        )}
                                        {line.conds.length > 1 && (
                                            <>
                                                <b>Unlock Requirements (all of the following):</b>
                                                <br />
                                                <ul style={{ marginBottom: 0 }}>
                                                    {line.conds.map((cond, index) => (
                                                        <li key={index}>
                                                            <VoiceCondTypeDescriptor
                                                                region={region}
                                                                servants={servants}
                                                                costumes={costumes}
                                                                cond={cond}
                                                            />
                                                        </li>
                                                    ))}
                                                </ul>
                                            </>
                                        )}
                                        {line.conds.length === 1 && (
                                            <>
                                                <b>Unlock Requirement:</b>
                                                <br />
                                                <VoiceCondTypeDescriptor
                                                    region={region}
                                                    servants={servants}
                                                    costumes={costumes}
                                                    cond={line.conds[0]}
                                                />
                                                <br />
                                            </>
                                        )}
                                        <VoicePlayCondDescriptor
                                            region={region}
                                            playConds={line.playConds}
                                            servants={servants}
                                        />
                                    </Alert>
                                </>
                            ) : (
                                ""
                            )}
                        </td>
                        <td style={{ verticalAlign: "middle", width: "1px" }}>
                            <ButtonGroup>
                                <VoiceLinePlayer
                                    audioAssetUrls={line.audioAssets}
                                    delay={line.delay}
                                    title={voiceLineNames[index]}
                                />
                                <Dropdown as={ButtonGroup}>
                                    <Dropdown.Toggle variant={"info"} title={`Download ${voiceLineNames[index]}`}>
                                        <FontAwesomeIcon icon={faFileAudio} />
                                        &nbsp;
                                    </Dropdown.Toggle>

                                    <Dropdown.Menu title={`Download ${voiceLineNames[index]}`}>
                                        <Dropdown.Item
                                            title={`Download ${voiceLineNames[index]} merged file`}
                                            onClick={() => {
                                                const fileName = `${mergedDownloadNamePrefix} - ${voiceLineNames[index]}`;
                                                mergeVoiceLine(line.audioAssets, line.delay, fileName);
                                            }}
                                        >
                                            Merged
                                        </Dropdown.Item>
                                        {line.audioAssets.map((asset, i) => (
                                            <Dropdown.Item
                                                key={i}
                                                href={asset}
                                                target="_blank"
                                                title={`Download ${voiceLineNames[index]} part ${i + 1}`}
                                            >
                                                Part {i + 1}
                                            </Dropdown.Item>
                                        ))}
                                    </Dropdown.Menu>
                                </Dropdown>
                            </ButtonGroup>
                        </td>
                    </tr>
                ))}
            </tbody>
        </Table>
    );
}
Example #25
Source File: ServantVoiceLines.tsx    From apps with MIT License 4 votes vote down vote up
export default function ServantVoiceLines(props: {
    region: Region;
    servants: Map<number, Servant.ServantBasic>;
    servant: Servant.Servant | CraftEssence.CraftEssence;
    servantName?: string;
}) {
    const [relatedVoiceSvts, setRelatedVoiceSvts] = useState<Entity.EntityBasic[] | null>(null);
    useEffect(() => {
        Api.searchEntityVoiceCondSvt([props.servant.collectionNo]).then((s) => setRelatedVoiceSvts(s));
    }, [props.servant]);

    const { profile, ascensionAdd } = props.servant;
    const voices = profile?.voices;
    const voicePrefixes = new Set([...(voices?.entries() || [])].map((entry) => entry[1].voicePrefix));
    const voicePrefixConditionPresent = voicePrefixes.size > 1;

    // sorting into prefixes
    const sortedVoice: [number, Profile.VoiceGroup[] | undefined][] = [...voicePrefixes].map((prefix) => [
        prefix,
        voices?.filter((voice) => voice.voicePrefix === prefix),
    ]);

    const voiceGroupTable = sortedVoice.map(([prefix, voices]) => {
        const outputTable = (
            <Table responsive>
                <thead>
                    <tr>
                        <td>Type</td>
                        <td>Lines</td>
                    </tr>
                </thead>
                <tbody>
                    {voices?.map((voice) => (
                        <tr key={`${voice.svtId}-${voice.type}-${voice.voicePrefix}`}>
                            <td>{voice.type === ProfileVoiceType.GROETH ? "Growth" : toTitleCase(voice.type)}</td>
                            <td>
                                <VoiceLinesTable
                                    region={props.region}
                                    voice={voice}
                                    mergedDownloadNamePrefix={`${props.servant.collectionNo} - ${props.servant.name}`}
                                    servants={props.servants}
                                    costumes={props.servant.profile?.costume}
                                />
                            </td>
                        </tr>
                    ))}
                </tbody>
            </Table>
        );
        if (voicePrefixConditionPresent) {
            const title = (
                <VoicePrefixDescriptor
                    currentVoicePrefix={prefix}
                    ascensionAdd={ascensionAdd}
                    costumes={profile?.costume}
                />
            );
            return (
                <React.Fragment key={prefix}>
                    {renderCollapsibleContent({ title: title, content: outputTable, subheader: false })}
                </React.Fragment>
            );
        } else {
            return <React.Fragment key={prefix}>{outputTable}</React.Fragment>;
        }
    });

    return (
        <>
            <Alert variant="success">
                <VoiceActorDescriptor region={props.region} cv={props.servant.profile?.cv} />
            </Alert>
            {props.servant.type !== Entity.EntityType.SERVANT_EQUIP && (
                <Alert variant="success">
                    {relatedVoiceSvts !== null
                        ? relatedVoiceSvts.length > 0
                            ? `Servants with voice lines about ${props.servantName ?? props.servant.name}: `
                            : `There is no voice line about ${
                                  props.servantName ?? props.servant.name
                              } from other servants.`
                        : "Fetching related voice line data ..."}
                    {relatedVoiceSvts !== null && relatedVoiceSvts.length > 0
                        ? mergeElements(
                              relatedVoiceSvts.map((svt) => (
                                  <EntityDescriptor key={svt.id} region={props.region} entity={svt} tab={"voices"} />
                              )),
                              ", "
                          )
                        : ""}
                </Alert>
            )}
            {voiceGroupTable}
        </>
    );
}
Example #26
Source File: ServantAssets.tsx    From apps with MIT License 4 votes vote down vote up
render() {
        const charaFigure = (
            <>
                {this.displayAssets(this.props.servant.extraAssets.charaFigure)}
                <br />
                {Object.entries(this.props.servant.extraAssets.charaFigureForm).map(([form, assetMap]) => (
                    <div key={form}>
                        {renderCollapsibleContent({
                            title: `Form ${form}`,
                            content: this.displayAssets(assetMap),
                            subheader: true,
                        })}
                    </div>
                ))}
                <br />
                {Object.entries(this.props.servant.extraAssets.charaFigureMulti).map(([idx, assetMap]) => (
                    <div key={idx}>
                        {renderCollapsibleContent({
                            title: `Character ${idx}`,
                            content: this.displayAssets(assetMap),
                            subheader: true,
                        })}
                    </div>
                ))}
            </>
        );

        const charaGraphSize = { width: 512, height: 724 };
        const narrowFigureSize = { width: 148, height: 375 };
        const faceSize = { width: 128, height: 128 };

        const content = [
            {
                title: "Portraits",
                content: (
                    <>
                        {this.displayAssets(this.props.servant.extraAssets.charaGraph, charaGraphSize)}
                        {this.displayAssets(this.props.servant.extraAssets.charaGraphEx, charaGraphSize)}
                        {this.displayAssets(this.props.servant.extraAssets.charaGraphChange, charaGraphSize)}
                    </>
                ),
            },
            {
                title: "Status",
                content: this.displayAssets(this.props.servant.extraAssets.status, { width: 256, height: 256 }),
            },
            {
                title: "Command",
                content: this.displayAssets(this.props.servant.extraAssets.commands, { width: 256, height: 256 }),
            },
            {
                title: "Formation",
                content: (
                    <>
                        {this.displayAssets(this.props.servant.extraAssets.narrowFigure, narrowFigureSize)}
                        {this.displayAssets(this.props.servant.extraAssets.narrowFigureChange, narrowFigureSize)}
                    </>
                ),
            },
            {
                title: "Thumbnail",
                content: (
                    <>
                        {this.displayAssets(this.props.servant.extraAssets.faces, faceSize)}
                        {this.displayAssets(this.props.servant.extraAssets.facesChange, faceSize)}
                    </>
                ),
            },
            { title: "Figure", content: charaFigure },
        ].map((a) => Object.assign({}, a, { subheader: false }));
        return (
            <div>
                <Alert variant="success">
                    <IllustratorDescriptor
                        region={this.props.region}
                        illustrator={this.props.servant.profile?.illustrator}
                    />
                </Alert>
                <ServantModelViewer servant={this.props.servant} />
                <ServantLimitImage region={this.props.region} servant={this.props.servant} />
                {content.map((content) => (
                    <div key={content.title}>{renderCollapsibleContent(content)}</div>
                ))}
                {this.props.servant.extraAssets.charaFigure.story
                    ? renderCollapsibleContent({
                          title: "Story Figure (May contain spoilers)",
                          content: (
                              <>
                                  {this.displayAssets(this.props.servant.extraAssets.charaFigure, undefined, true)}
                                  {this.displayAssets(this.props.servant.extraAssets.image, undefined, true)}
                              </>
                          ),
                          subheader: false,
                          initialOpen: false,
                      })
                    : ""}
                <br />
                {Object.entries(this.props.servant.extraAssets.charaFigureForm).map(([form, assetMap]) => (
                    <div key={form}>
                        {assetMap.story
                            ? renderCollapsibleContent({
                                  title: `Story Figure Form ${form}`,
                                  content: this.displayAssets(assetMap, undefined, true),
                                  subheader: true,
                                  initialOpen: false,
                              })
                            : null}
                    </div>
                ))}
            </div>
        );
    }
Example #27
Source File: QuestPage.tsx    From apps with MIT License 4 votes vote down vote up
render() {
        if (this.state.error) return <ErrorStatus error={this.state.error} />;

        if (this.state.loading || !this.state.quest) return <Loading />;

        const quest = this.state.quest;

        return (
            <div>
                <h1>{quest.name}</h1>

                <br />
                <Row style={{ marginBottom: "3%" }}>
                    <Col xs={{ span: 12 }} lg={{ span: 6 }}>
                        <QuestMainData
                            region={this.props.region}
                            quest={quest}
                            phase={this.state.phase}
                            setPhase={(phase) => {
                                this.setState({ phase });
                            }}
                        />
                    </Col>
                    <Col xs={{ span: 12 }} lg={{ span: 6 }}>
                        <QuestSubData region={this.props.region} quest={quest} />
                    </Col>
                </Row>
                {quest.messages.length > 0 ? (
                    <Alert variant="success" className="newline">
                        {quest.messages.length > 1 ? (
                            <ul className="mb-0">
                                {quest.messages.map((message) => (
                                    <li key={message.idx}>{colorString(message.message)}</li>
                                ))}
                            </ul>
                        ) : (
                            colorString(quest.messages[0].message)
                        )}
                    </Alert>
                ) : null}
                {quest.scripts.length > 0 ? (
                    <Alert variant="success">
                        {quest.scripts.length > 1 ? (
                            <ul className="mb-0">
                                {sortScript(quest.scripts.map((script) => script.scriptId)).map((scriptId) => (
                                    <li key={scriptId}>
                                        <ScriptDescriptor region={this.props.region} scriptId={scriptId} />
                                    </li>
                                ))}
                            </ul>
                        ) : (
                            <ScriptDescriptor region={this.props.region} scriptId={quest.scripts[0].scriptId} />
                        )}
                    </Alert>
                ) : null}
                {quest.extraDetail.questSelect !== undefined &&
                quest.extraDetail.questSelect.filter((questId) => questId !== this.props.id).length > 0 ? (
                    <Alert variant="success">
                        {quest.extraDetail.questSelect.filter((questId) => questId !== this.props.id).length > 1
                            ? "Other versions"
                            : "Another version"}{" "}
                        this quest:
                        <ul className="mb-0">
                            {quest.extraDetail.questSelect
                                .filter((questId) => questId !== this.props.id)
                                .map((questId) => (
                                    <li key={questId}>
                                        {questId}:{" "}
                                        <QuestDescriptorId
                                            region={this.props.region}
                                            questId={questId}
                                            questPhase={this.state.phase}
                                        />
                                    </li>
                                ))}
                        </ul>
                    </Alert>
                ) : null}
                <QuestDrops region={this.props.region} drops={quest.drops} />
                {quest.supportServants.length > 0 ? (
                    <>
                        {renderCollapsibleContent({
                            title: `${quest.isNpcOnly ? "Forced " : ""}Support Servant${
                                quest.supportServants.length > 1 ? "s" : ""
                            }`,
                            content: (
                                <SupportServantTables
                                    region={this.props.region}
                                    supportServants={quest.supportServants}
                                />
                            ),
                            subheader: false,
                            initialOpen: false,
                        })}
                    </>
                ) : null}
                {quest.stages.length > 0 ? (
                    <Tabs
                        defaultActiveKey={this.props.stage !== undefined ? this.props.stage : 1}
                        onSelect={(key: string | null) => {
                            this.props.history.replace(
                                `/${this.props.region}/quest/${this.props.id}/${this.state.phase}` +
                                    (key ? `/stage-${key}` : "")
                            );
                        }}
                    >
                        {quest.stages.map((stage) => (
                            <Tab key={stage.wave} eventKey={stage.wave} title={`Stage ${stage.wave}`}>
                                <QuestStage region={this.props.region} stage={stage} />
                            </Tab>
                        ))}
                    </Tabs>
                ) : null}
            </div>
        );
    }
Example #28
Source File: Shop.tsx    From apps with MIT License 4 votes vote down vote up
ShopTab = ({ region, shops, filters, onChange, itemCache, questCache }: IProps) => {
    let [forceEnablePlanner, setForceEnablePlanner] = useState<boolean | undefined>(undefined);
    let [itemFilters, setItemFilters] = useState(new Set<number>());

    const allItems = new Map(shops.map((shop) => [shop.cost.item.id, shop.cost.item]));

    let shopEnabled = forceEnablePlanner === undefined ? Manager.shopPlannerEnabled() : forceEnablePlanner;
    let counted = shops
        .filter((shop) => (shopEnabled ? filters.has(shop.id) : true))
        .map(
            (shop) =>
                [shop.cost.item, (shopEnabled ? filters.get(shop.id)! : shop.limitNum) * shop.cost.amount] as const
        );

    let items = new Map(counted.map((tuple) => [tuple[0].id, tuple[0]]));

    let amounts = new Map<number, number>();
    for (let [item, amount] of counted)
        if (amounts.has(item.id)) amounts.set(item.id, (amounts.get(item.id) ?? 0) + amount);
        else amounts.set(item.id, amount);

    // reset filter if nothing is chosen
    if (!amounts.size && itemFilters.size) setItemFilters(new Set());

    const excludeItemIds = (itemIds: number[]) => {
        return new Map(
            shops
                .filter((shop) => shop.payType !== Shop.PayType.FREE)
                .filter((shop) => shop.limitNum !== 0)
                .filter((shop) => !itemIds.includes(shop.targetIds[0]) || shop.purchaseType !== Shop.PurchaseType.ITEM)
                .map((shop) => [shop.id, shop.limitNum])
        );
    };

    return (
        <>
            <Alert variant="success" style={{ margin: "1em 0", display: "flex" }}>
                <div style={{ flexGrow: 1 }}>
                    {shopEnabled
                        ? amounts.size > 0
                            ? "Total amount for chosen items: "
                            : "No item was chosen. Choose at least one to get calculations."
                        : "Total currency amount needed to clear the shop: "}
                    {[...amounts]
                        .filter(([_, amount]) => amount > 0)
                        .map(([itemId, amount]) => (
                            <span style={{ whiteSpace: "nowrap", paddingRight: "1ch" }} key={itemId}>
                                <IconLink region={region} item={items.get(itemId)!} />
                                <b>×{amount.toLocaleString()}</b>
                            </span>
                        ))}
                </div>
                <div style={{ flexBasis: "auto", paddingLeft: "10px" }}>
                    <Button
                        variant={shopEnabled ? "dark" : "success"}
                        onClick={() => setForceEnablePlanner(!forceEnablePlanner)}
                    >
                        <FontAwesomeIcon icon={faEdit} title={shopEnabled ? "Disable planner" : "Enable planner"} />
                    </Button>
                </div>
            </Alert>
            {shopEnabled && (
                <>
                    <ButtonGroup>
                        <Button disabled variant="outline-dark">
                            Quick toggle
                        </Button>
                        <Button variant="outline-success" onClick={() => onChange?.(excludeItemIds([]))}>
                            All
                        </Button>
                        <Button variant="outline-success" onClick={() => onChange?.(new Map())}>
                            None
                        </Button>
                        <Dropdown as={ButtonGroup}>
                            <Dropdown.Toggle variant="outline-success">Exclude</Dropdown.Toggle>
                            <Dropdown.Menu>
                                <Dropdown.Item
                                    as={Button}
                                    onClick={() =>
                                        onChange?.(excludeItemIds([...gemIds, ...magicGemIds, ...secretGemIds]))
                                    }
                                >
                                    Gems
                                </Dropdown.Item>
                                <Dropdown.Item
                                    as={Button}
                                    onClick={() => onChange?.(excludeItemIds([...monumentIds, ...pieceIds]))}
                                >
                                    Monuments & Pieces
                                </Dropdown.Item>
                                <Dropdown.Item as={Button} onClick={() => onChange?.(excludeItemIds(monumentIds))}>
                                    Monuments
                                </Dropdown.Item>
                                <Dropdown.Item as={Button} onClick={() => onChange?.(excludeItemIds(pieceIds))}>
                                    Pieces
                                </Dropdown.Item>
                            </Dropdown.Menu>
                        </Dropdown>
                    </ButtonGroup>
                    <div>&nbsp;</div>
                </>
            )}
            <Table hover responsive className="shopTable">
                <thead>
                    <tr>
                        <th style={{ textAlign: "left" }}>Detail</th>
                        <th style={{ whiteSpace: "nowrap" }}>
                            Currency&nbsp;
                            <Dropdown as={ButtonGroup}>
                                <Dropdown.Toggle size="sm">
                                    <FontAwesomeIcon style={{ display: "inline" }} icon={faFilter} />
                                </Dropdown.Toggle>
                                <Dropdown.Menu>
                                    {/* Actually a checkbox is the best here */}
                                    <Dropdown.Item
                                        as={Button}
                                        onClick={() => {
                                            setItemFilters(new Set());
                                        }}
                                    >
                                        Reset
                                    </Dropdown.Item>
                                    {[...allItems].map(([itemId, item]) => (
                                        <Dropdown.Item
                                            key={item.id}
                                            as={Button}
                                            onClick={() => {
                                                setItemFilters(new Set([itemId]));
                                            }}
                                        >
                                            <ItemIcon region={region} item={item} height={40} />
                                            {item.name}
                                        </Dropdown.Item>
                                    ))}
                                </Dropdown.Menu>
                            </Dropdown>
                        </th>
                        <th>Cost</th>
                        <th>Item</th>
                        <th>Set</th>
                        <th>Limit</th>
                        {shopEnabled && <th>Target</th>}
                    </tr>
                </thead>
                <tbody>
                    {shops
                        .filter((shop) =>
                            itemFilters.size && amounts.size ? itemFilters.has(shop.cost.item.id) : true
                        )
                        .sort((a, b) => a.priority - b.priority)
                        .map((shop) => {
                            let limitNumIndicator = shopEnabled ? (
                                <Button
                                    variant="light"
                                    onClick={() => {
                                        filters.set(shop.id, shop.limitNum);
                                        onChange?.(filters);
                                    }}
                                >
                                    {shop.limitNum.toLocaleString()}
                                </Button>
                            ) : (
                                <>{shop.limitNum.toLocaleString()}</>
                            );

                            return (
                                <tr key={shop.id}>
                                    <td style={{ minWidth: "10em" }}>
                                        <b>{shop.name}</b>
                                        <div style={{ fontSize: "0.75rem" }} className="newline">
                                            {colorString(shop.detail)}
                                            <ScriptLink region={region} shop={shop} />
                                            <br />
                                            <div>
                                                {shop.releaseConditions.length ? (
                                                    <ul className="condition-list">
                                                        {shop.releaseConditions.map((cond, index) => (
                                                            <li key={index} style={{ fontSize: "0.75rem" }}>
                                                                {cond.closedMessage && `${cond.closedMessage} — `}
                                                                <CondTargetNumDescriptor
                                                                    region={region}
                                                                    cond={cond.condType}
                                                                    targets={cond.condValues}
                                                                    num={cond.condNum}
                                                                    quests={questCache}
                                                                    items={itemCache}
                                                                />
                                                            </li>
                                                        ))}
                                                    </ul>
                                                ) : (
                                                    ""
                                                )}
                                            </div>
                                        </div>
                                    </td>
                                    <td style={{ textAlign: "center" }}>
                                        {shop.payType !== Shop.PayType.FREE ? (
                                            <IconLink region={region} item={shop.cost.item} />
                                        ) : null}
                                    </td>
                                    <td style={{ textAlign: "center" }}>
                                        {shop.payType !== Shop.PayType.FREE ? shop.cost.amount.toLocaleString() : null}
                                    </td>
                                    <td>
                                        <ShopPurchaseDescriptor region={region} shop={shop} itemMap={itemCache} />
                                    </td>
                                    <td style={{ textAlign: "center" }}>{shop.setNum.toLocaleString()}</td>
                                    <td style={{ textAlign: "center" }}>
                                        {shop.limitNum === 0 ? <>Unlimited</> : limitNumIndicator}
                                    </td>
                                    {shopEnabled && (
                                        <>
                                            <td style={{ textAlign: "center", maxWidth: "5em" }}>
                                                <InputGroup size="sm">
                                                    <Form.Control
                                                        type="number"
                                                        value={filters.get(shop.id) ?? 0}
                                                        min={0}
                                                        max={shop.limitNum || undefined}
                                                        onChange={(event) => {
                                                            let value = +event.target.value;
                                                            if (value) filters.set(shop.id, value);
                                                            else filters.delete(shop.id);
                                                            onChange?.(filters);
                                                        }}
                                                    />
                                                </InputGroup>
                                            </td>
                                        </>
                                    )}
                                </tr>
                            );
                        })}
                </tbody>
            </Table>
        </>
    );
}
Example #29
Source File: BuffPage.tsx    From apps with MIT License 4 votes vote down vote up
render() {
        if (this.state.error) return <ErrorStatus error={this.state.error} />;

        if (this.state.loading || !this.state.buff) return <Loading />;

        const buff = this.state.buff;

        return (
            <div>
                <h1>
                    {buff.icon ? <BuffIcon location={buff.icon} height={48} /> : undefined}
                    {buff.icon ? " " : undefined}
                    {buff.name}
                </h1>

                <br />

                <DataTable
                    data={{
                        ID: buff.id,
                        Name: buff.name,
                        Detail: <span className="newline">{buff.detail}</span>,
                        Type: <Link to={`/${this.props.region}/buffs?type=${buff.type}`}>{buff.type}</Link>,
                        "Buff Group": buff.buffGroup,
                        "Buff Traits": (
                            <div>
                                {mergeElements(
                                    buff.vals.map((trait) => (
                                        <TraitDescription
                                            region={this.props.region}
                                            trait={trait}
                                            owner="buffs"
                                            ownerParameter="vals"
                                        />
                                    )),
                                    " "
                                )}
                            </div>
                        ),
                        "Target Traits": (
                            <div>
                                {mergeElements(
                                    buff.tvals.map((trait) => (
                                        <TraitDescription
                                            region={this.props.region}
                                            trait={trait}
                                            owner="buffs"
                                            ownerParameter="tvals"
                                        />
                                    )),
                                    " "
                                )}
                            </div>
                        ),
                        "Required Self Traits": (
                            <div>
                                {mergeElements(
                                    buff.ckSelfIndv.map((trait) => (
                                        <TraitDescription
                                            region={this.props.region}
                                            trait={trait}
                                            owner="buffs"
                                            ownerParameter="vals"
                                        />
                                    )),
                                    " "
                                )}
                            </div>
                        ),
                        "Required Opponent Traits": (
                            <div>
                                {mergeElements(
                                    buff.ckOpIndv.map((trait) => (
                                        <TraitDescription region={this.props.region} trait={trait} />
                                    )),
                                    " "
                                )}
                            </div>
                        ),
                    }}
                />
                <div style={{ marginBottom: "3%" }}>
                    <RawDataViewer text="Nice" data={buff} />
                    <RawDataViewer text="Raw" data={`${Host}/raw/${this.props.region}/buff/${buff.id}`} />
                </div>

                {buff.script.relationId !== undefined ? (
                    <Alert variant="success">
                        <BuffClassRelationOverwrite relations={buff.script.relationId} />
                    </Alert>
                ) : undefined}

                <h3>Related Functions</h3>
                <Table>
                    <thead>
                        <tr>
                            <th>Function</th>
                            <th>Usage Count</th>
                        </tr>
                    </thead>
                    <tbody>
                        {buff.reverse?.basic?.function
                            ? buff.reverse.basic.function.map((func) => {
                                  return (
                                      <tr key={func.funcId}>
                                          <td>
                                              <FuncDescriptor region={this.props.region} func={func} />
                                          </td>
                                          <td>
                                              {(func.reverse?.basic?.NP ?? []).length +
                                                  (func.reverse?.basic?.skill ?? []).length}
                                          </td>
                                      </tr>
                                  );
                              })
                            : undefined}
                    </tbody>
                </Table>
            </div>
        );
    }