react-bootstrap#Tab TypeScript Examples

The following examples show how to use react-bootstrap#Tab. 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: InfoTabs.tsx    From devex with GNU General Public License v3.0 6 votes vote down vote up
InfoTabs: React.FC<IProps> = ({ tabs }) => {

  const { tabHeaders, tabTitles, tabContents } = tabs
  const [currTab, setCurrTab] = useState(tabHeaders[0])

  return <>
    {tabHeaders.length > 0
      ? <Card className='tabs-card'>
        <Card.Header className='tabs-card-header'>
          <Tabs id="info-tabs" activeKey={currTab} onSelect={(k: string) => setCurrTab(k)}>
            {tabHeaders.map((tabHeader: string, index: number) => (
              <Tab key={index} eventKey={tabHeader} title={[tabTitles[index]]} />
            ))}
          </Tabs>
        </Card.Header>
        <Card.Body>
          <Container>
            {tabContents[tabHeaders.indexOf(currTab)]}
          </Container>
        </Card.Body>
      </Card>
      : null}
  </>
}
Example #2
Source File: RcsbTabs.tsx    From rcsb-saguaro-app with MIT License 6 votes vote down vote up
render(): JSX.Element {
        return (<div className={classes.bootstrapGroupSequenceComponentScope}>
            <Tabs
                id={this.props.id}
                defaultActiveKey={this.props.default}
                onSelect={(eventKey: string)=>{
                    this.props.onSelect(eventKey as T);
                }}
            >
                {
                    this.props.tabList.map((tab,n)=>(
                        <Tab eventKey={tab.key} title={tab.title}>
                            {
                               tab.additionalComponent
                            }
                            <div id={tab.key+RcsbTabs.UI_SUFFIX} style={{height:20}}/>
                            <div id={tab.key}/>
                        </Tab>
                    ))
                }
            </Tabs>
        </div>);
    }
Example #3
Source File: ItemPage.tsx    From apps with MIT License 6 votes vote down vote up
private renderBreakdownTab(className: ClassName, usageData: MaterialUsageData[]) {
        const region = this.props.region;
        return (
            <Tab
                key={className.toLowerCase()}
                eventKey={className.toLowerCase()}
                title={className.toLowerCase().replace(/^\w/, (c) => c.toUpperCase())}
            >
                <br />
                <MaterialListingTable
                    region={region}
                    usageData={usageData}
                    blacklistedColumnIndexes={this.state.blacklistedColumnIndexes}
                />
            </Tab>
        );
    }
Example #4
Source File: ItemsPage.tsx    From apps with MIT License 5 votes vote down vote up
private renderTab(index: number): JSX.Element {
        const tab = this.state.tabs[index],
            items = this.applySearch(tab.items, this.state.search ?? ""),
            results = items.slice(this.state.perPage * this.state.page, this.state.perPage * (this.state.page + 1));

        return (
            <Tab key={tab.key} eventKey={tab.key} title={tab.title}>
                <br />
                <Table striped bordered hover responsive>
                    <thead>
                        <tr>
                            <th style={{ textAlign: "center", width: "1px" }}>#</th>
                            <th style={{ textAlign: "center", width: "1px" }}>Thumbnail</th>
                            <th>Name</th>
                        </tr>
                    </thead>
                    <tbody>
                        {results.map((item, i) => {
                            const route = `/${this.props.region}/item/${item.id}`;

                            return (
                                <tr key={i}>
                                    <td className="col-center">
                                        <Link to={route}>{item.id}</Link>
                                    </td>
                                    <td className="col-center">
                                        <Link to={route}>
                                            <ItemIcon region={this.props.region} item={item} height={50} />
                                        </Link>
                                    </td>
                                    <td>
                                        <Link to={route}>{item.name}</Link>
                                    </td>
                                </tr>
                            );
                        })}
                    </tbody>
                </Table>
            </Tab>
        );
    }
Example #5
Source File: queueSettings.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
// The 'tab-custom' role is used to override a default 'tab' role that resulted in tab links not being keyboard accessible.
function QueueSettingsEditor(props: QueueSettingsProps) {
    return (
        <Tab.Container id='add-queue-editor' defaultActiveKey='general'>
            <Row>
                <Col md={3} sm={3}>
                    <div className='mt-4'>
                        <Link to={`/manage/${props.queue.id}/`}>
                            <FontAwesomeIcon icon={faChevronLeft} /> Back to Queue
                        </Link>
                    </div>
                    <Nav variant='pills' className='flex-column mt-4'>
                        <Nav.Item>
                            <Nav.Link eventKey='general' role='tab-custom' tabIndex={0} aria-label='General Tab'>
                                General
                            </Nav.Link>
                        </Nav.Item>
                        <Nav.Item>
                            <Nav.Link eventKey='hosts' role='tab-custom' tabIndex={0} aria-label='Manage Hosts Tab'>
                                Manage Hosts
                            </Nav.Link>
                        </Nav.Item>
                        <Nav.Item>
                            <Nav.Link eventKey='delete' role='tab-custom' tabIndex={0} aria-label='Delete Queue Tab'>
                                Delete Queue
                            </Nav.Link>
                        </Nav.Item>
                    </Nav>
                </Col>
                <Col md={6} sm={9}>
                    <h1>Settings</h1>
                    <Tab.Content aria-live='polite'>
                        <Tab.Pane eventKey='general'>
                            <GeneralEditor {...props} />
                            <div className='mt-4'>
                                <Button
                                    variant='primary'
                                    className={buttonSpacing}
                                    disabled={props.disabled}
                                    aria-label='Save Changes'
                                    onClick={props.onSaveGeneralClick}
                                >
                                    Save Changes
                                </Button>
                                <Button
                                    variant='link'
                                    className={'text-danger ' + buttonSpacing}
                                    disabled={props.disabled}
                                    aria-label='Discard Changes'
                                    onClick={props.onDiscardGeneralClick}
                                >
                                    Discard Changes
                                </Button>
                            </div>
                        </Tab.Pane>
                        <Tab.Pane eventKey='hosts'>
                            <ManageHostsEditor {...props} />
                        </Tab.Pane>
                        <Tab.Pane eventKey='delete'>
                            <h2>Delete Queue</h2>
                            <p>Delete the entire queue, including all hosts and current meetings in queue. <strong>This cannot be undone.</strong></p>
                            <div className='mt-4'>
                                <Button variant='danger' disabled={props.disabled} aria-label='Delete Queue' onClick={props.onDeleteClick}>
                                    Delete Queue
                                </Button>
                            </div>
                        </Tab.Pane>
                    </Tab.Content>
                </Col>
            </Row>
        </Tab.Container>
    );
}
Example #6
Source File: addQueue.tsx    From remote-office-hours-queue with Apache License 2.0 5 votes vote down vote up
// The 'tab-custom' role is used to override a default 'tab' role that resulted in tab links not being keyboard accessible.
function AddQueueEditor(props: AddQueueEditorProps) {
    return (
        <Tab.Container id='add-queue-editor' defaultActiveKey='general' activeKey={props.activeKey} onSelect={props.onTabSelect}>
            <Row>
                <Col md={3} sm={3}>
                    <Nav variant='pills' className='flex-column mt-5'>
                        <Nav.Item>
                            <Nav.Link eventKey='general' role='tab-custom' tabIndex={0} aria-label='General Tab'>
                                General
                            </Nav.Link>
                        </Nav.Item>
                        <Nav.Item>
                            <Nav.Link eventKey='hosts' role='tab-custom' tabIndex={0} aria-label='Manage Hosts Tab'>
                                Manage Hosts
                            </Nav.Link>
                        </Nav.Item>
                    </Nav>
                </Col>
                <Col md={6} sm={9}>
                    <h1>Add Queue</h1>
                    <Tab.Content aria-live='polite'>
                        <Tab.Pane eventKey='general'>
                            <GeneralEditor {...props} />
                            <div className='mt-4'>
                                <Button
                                    variant='primary'
                                    className={buttonSpacing}
                                    aria-label='Next'
                                    disabled={props.disabled}
                                    onClick={props.onGeneralNextClick}
                                >
                                    Next
                                </Button>
                                <CancelAddButton disabled={props.disabled} />
                            </div>
                        </Tab.Pane>
                        <Tab.Pane eventKey='hosts'>
                            <ManageHostsEditor {...props} />
                            <div className='mt-4'>
                                <Button
                                    variant='primary'
                                    className={buttonSpacing}
                                    aria-label='Back'
                                    disabled={props.disabled}
                                    onClick={props.onBackClick}
                                >
                                    Back
                                </Button>
                                <Button
                                    variant='primary'
                                    className={buttonSpacing}
                                    aria-label='Finish Adding Queue'
                                    disabled={props.disabled}
                                    onClick={props.onFinishClick}
                                >
                                    Finish Adding Queue
                                </Button>
                                <CancelAddButton disabled={props.disabled} />
                            </div>
                        </Tab.Pane>
                    </Tab.Content>
                </Col>
            </Row>
        </Tab.Container>
    );
}
Example #7
Source File: index.tsx    From polkabtc-ui with Apache License 2.0 5 votes vote down vote up
InterlayTab = (props: TabProps): JSX.Element => (
  <Tab {...props} />
)
Example #8
Source File: WarPage.tsx    From apps with MIT License 5 votes vote down vote up
WarMapList = (props: {
    region: Region;
    title: string;
    maps: War.Map[];
    spots: War.Spot[];
    warName: string;
    warId: number;
    last?: boolean;
}) => {
    const groupBy = <T,>(array: T[], property: (x: T) => string): { [key: string]: Array<T> } =>
        array.reduce((acc: { [key: string]: Array<T> }, cur: T) => {
            if (!acc[property(cur)]) {
                acc[property(cur)] = [];
            }
            acc[property(cur)].push(cur);
            return acc;
        }, {});

    const mapsById = groupBy(props.maps, (map) => `${map.id}`);
    let last = false;
    const warMaps = (
        <Tabs id="war-maps-tabs" className="mb-3">
            {Object.keys(mapsById).map((mapId, index, array) => {
                let mapSpots = props.spots
                    .filter((spot) => spot.mapId === +mapId)
                    .filter((spot) => spot.quests.some((quest) => quest.afterClear === "repeatLast"))
                    .filter((spot) => spot.x || spot.y);
                last = index === array.length - 1;
                return mapSpots.length > 0 ? (
                    <Tab eventKey={`${mapId}`} key={mapId} title={`#${mapId}`}>
                        <WarMap
                            region={props.region}
                            key={index}
                            map={mapsById[mapId][0]}
                            spots={mapSpots}
                            warName={props.warName}
                            warId={props.warId}
                        />
                    </Tab>
                ) : null;
            })}
        </Tabs>
    );
    return renderCollapsibleContent(
        {
            title: props.title,
            content: warMaps,
            subheader: false,
        },
        !last
    );
}
Example #9
Source File: CommunityView.tsx    From 3Speak-app with GNU General Public License v3.0 5 votes vote down vote up
export function CommunityView(props: any) {
  const [communityInfo, setCommunityInfo] = useState({} as any)
  const [newVideos, setNewVideos] = useState([])
  const [trendingVideos, setTrendingVideos] = useState([])

  const [backgroundUrl, setBackgroundUrl] = useState(null)

  const reflink = useMemo(() => {
    return RefLink.parse(props.match.params.reflink)
  }, [props.match])

  const generate = async () => {
    const commInfo = await client.call('bridge', 'get_community', {
      name: reflink.root,
      observer: 'alice',
    })
    setCommunityInfo(commInfo)

    const trendingVideosRes = (
      await axios.get(`https://3speak.tv/apiv2/feeds/community/${reflink.root}/trending`)
    ).data
    const newVideosRes = (
      await axios.get(`https://3speak.tv/apiv2/feeds/community/${reflink.root}/new`)
    ).data

    setTrendingVideos(trendingVideosRes)
    setNewVideos(newVideosRes)

    const bgUrlRes = await AccountService.getProfileBackgroundImageUrl(props.match.params.reflink)
    setBackgroundUrl(bgUrlRes)
  }

  useEffect(() => {
    void generate()
  }, [reflink])

  return (
    <div>
      <div
        style={{
          position: 'relative',
          display: 'inline-block',
          width: '100%',
          minHeight: '400px',
          backgroundAttachment: 'fixed',
          backgroundSize: 'cover',
          backgroundRepeat: 'no-repeat',
          background: `url(${backgroundUrl})`,
        }}
      >
        <img
          className="channel-profile-img"
          style={{ position: 'absolute', bottom: '10px', left: '10px' }}
          alt=""
          src={`https://images.hive.blog/u/${reflink.root}/avatar`}
        />

        <h1 style={{ position: 'absolute', bottom: '10px', left: '150px' }}>
          <b
            style={{
              color: 'white',
              textShadow: '-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000',
            }}
          >
            {communityInfo.title}
          </b>
        </h1>
      </div>
      <h4 className="mt-3">
        <ReactMarkdown source={communityInfo.about} />
      </h4>
      <p>{communityInfo.description}</p>

      <hr />
      <Tabs>
        <Tab eventKey="videos" title="Videos" variant="secondary">
          <hr />
          <Tabs>
            <Tab eventKey="trending" title="Show Trending">
              <h3 id="videoSectionHeading">Trending Videos</h3>
              <hr />
              <div>
                {trendingVideos !== null ? (
                  <GridFeedView key="community-trends" type={`#${reflink.root}/trending`} />
                ) : null}
              </div>
            </Tab>
            <Tab eventKey="new" title="Show New">
              <h3 id="videoSectionHeading">New Videos</h3>
              <hr />
              <div>
                {newVideos !== null ? (
                  <GridFeedView key="community-new" type={`#${reflink.root}/new`} />
                ) : null}
              </div>
            </Tab>
          </Tabs>
        </Tab>
        <Tab eventKey="polls" title="Polls"></Tab>
        <Tab eventKey="stats" title="Stats">
          <div className="row">
            <div className="card col-lg-6 col-md-11 col-sm-12 col-xs-11 col-xl-5 ml-2 mt-2">
              <div className="card-header">
                <h3>More Info</h3>
              </div>
              <div className="card-body">
                <h5>
                  Language: {by639_1[communityInfo.lang] ? by639_1[communityInfo.lang].name : null}
                </h5>
                <h5>Community:</h5>
                <b className="text-success">{communityInfo.num_pending}</b> posts waiting to cash
                out
                <br />
                <b className="text-success">${communityInfo.sum_pending}</b> pending rewards
                <br />
                <b className="text-success">{communityInfo.subscribers}</b> subscribers
                <br />
                <p>
                  <b className="text-success">{communityInfo.num_authors}</b> active authors
                </p>
                <br />
                {communityInfo.is_nsfw === true ? (
                  <h5 className="text-danger">NSFW</h5>
                ) : (
                  <h5 className="text-success">Not NSFW</h5>
                )}
              </div>
            </div>
            <div className="card col-lg-6 col-md-8 col-sm-10 col-xs-11 col-xl-5 ml-2 mt-2">
              <div className="card-header">
                <h3>The team</h3>
              </div>
              <div className="card-body">
                <table className="table">
                  <thead>
                    <tr>
                      <td>User</td>
                      <td>Role</td>
                      <td>Nickname</td>
                    </tr>
                  </thead>
                  <tbody>
                    {communityInfo.team
                      ? communityInfo.team.map((value) => (
                          <tr key={value[0]}>
                            <td>
                              <a href={`#/user/hive:${value[0]}`}>{value[0]}</a>
                            </td>
                            <td>{value[1]}</td>
                            <td>{value[2]}</td>
                          </tr>
                        ))
                      : null}
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </Tab>
      </Tabs>
    </div>
  )
}
Example #10
Source File: CommandCodePage.tsx    From apps with MIT License 5 votes vote down vote up
render() {
        if (this.state.error) return <ErrorStatus error={this.state.error} />;

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

        const commandCode = this.state.commandCode;

        return (
            <div>
                <CommandCodePicker
                    region={this.props.region}
                    commandCodes={this.state.commandCodes}
                    id={commandCode.id}
                />
                <hr />

                <Row
                    style={{
                        marginBottom: "3%",
                    }}
                >
                    <Col xs={{ span: 12, order: 2 }} lg={{ span: 6, order: 1 }}>
                        <CommandCodeMainData region={this.props.region} commandCode={commandCode} />
                    </Col>
                    <Col xs={{ span: 12, order: 1 }} lg={{ span: 6, order: 2 }}>
                        <CommandCodePortrait commandCode={commandCode} />
                    </Col>
                </Row>

                <Tabs
                    id={"cc-tabs"}
                    defaultActiveKey={this.props.tab ?? "effects"}
                    mountOnEnter={false}
                    onSelect={(key: string | null) => {
                        this.props.history.replace(`/${this.props.region}/command-code/${this.props.id}/${key}`);
                    }}
                >
                    <Tab eventKey={"effects"} title={"Effects"}>
                        <br />
                        {commandCode.skills.map((skill) => {
                            return (
                                <SkillBreakdown
                                    key={skill.id}
                                    region={this.props.region}
                                    skill={skill}
                                    cooldowns={true}
                                />
                            );
                        })}
                    </Tab>

                    <Tab eventKey={"assets"} title={"Assets"}>
                        <br />
                        <CommandCodeAsset region={this.props.region} commandCode={commandCode} />
                    </Tab>
                </Tabs>
            </div>
        );
    }
Example #11
Source File: EventPage.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.event) return <Loading />;

        const event = this.state.event;

        let tabs: TabInfo[] = [];

        if (event.missions.length > 0) {
            tabs.push({
                type: "mission",
                id: 0,
                title: "Missions",
                tabKey: "missions",
            });
        }

        const lotteries = new Set(event.lotteries.map((lottery) => lottery.id));

        tabs = tabs.concat(
            event.lotteries
                .sort((a, b) => a.id - b.id)
                .map((lottery) => {
                    return {
                        type: "lottery",
                        id: lottery.id,
                        title: lotteries.size === 1 ? "Lottery" : `Lottery ${lottery.id}`,
                        tabKey: `lottery-${lottery.id}`,
                    };
                })
        );

        const towers = new Set(event.towers.map((tower) => tower.towerId));

        tabs = tabs.concat(
            event.towers
                .sort((a, b) => a.towerId - b.towerId)
                .map((tower) => {
                    return {
                        type: "tower",
                        id: tower.towerId,
                        title: towers.size === 1 ? "Tower" : tower.name,
                        tabKey: `tower-${tower.towerId}`,
                    };
                })
        );

        const pointGroupMap = new Map(event.pointGroups.map((pointGroup) => [pointGroup.groupId, pointGroup]));

        tabs = tabs.concat(
            Array.from(new Set(event.rewards.map((reward) => reward.groupId)))
                .sort((a, b) => a - b)
                .map((groupId) => {
                    let title: string | React.ReactNode = `Ladder ${groupId}`;
                    const pointGroupInfo = pointGroupMap.get(groupId);
                    if (groupId === 0) {
                        title = "Ladder";
                    } else if (pointGroupInfo !== undefined) {
                        title = (
                            <>
                                <img
                                    style={{ height: "1.75em" }}
                                    src={pointGroupInfo.icon}
                                    alt={`${pointGroupInfo.name} Icon`}
                                />
                                {pointGroupInfo.name}
                            </>
                        );
                    }
                    return {
                        type: "ladder",
                        id: groupId,
                        title: title,
                        tabKey: `ladder-${groupId}`,
                    };
                })
        );

        const treasureBoxSlots = Array.from(new Set(event.treasureBoxes.map((tb) => tb.slot)));

        tabs = tabs.concat(
            treasureBoxSlots
                .sort((a, b) => a - b)
                .map((slot) => {
                    return {
                        type: "treasureBox",
                        id: slot,
                        title: treasureBoxSlots.length === 1 ? "Treasure Box" : `Treasure Box ${slot}`,
                        tabKey: `treasure-box-${slot}`,
                    };
                })
        );

        const shopSlots = Array.from(new Set(event.shop.map((shop) => shop.slot)));

        tabs = tabs.concat(
            shopSlots
                .sort((a, b) => a - b)
                .map((shopSlot) => {
                    return {
                        type: "shop",
                        id: shopSlot,
                        title: shopSlots.length === 1 ? "Shop" : `Shop ${shopSlot}`,
                        tabKey: `shop-${shopSlot}`,
                    };
                })
        );

        const wars =
            this.state.wars.length === 1 ? (
                <WarDescriptor region={this.props.region} war={this.state.wars[0]} />
            ) : (
                <ul className="mb-0">
                    {this.state.wars.map((war) => (
                        <li key={war.id}>
                            <WarDescriptor region={this.props.region} war={war} />
                        </li>
                    ))}
                </ul>
            );

        return (
            <div>
                <h1>{replacePUACodePoints(event.name)}</h1>

                <br />
                <div style={{ marginBottom: "3%" }}>
                    <DataTable
                        data={{
                            ID: event.id,
                            Name: replacePUACodePoints(event.name),
                            ...(event.name !== event.originalName && { "Original Name": event.originalName }),
                            Wars: wars,
                            Status: getEventStatus(event.startedAt, event.endedAt),
                            Start: new Date(event.startedAt * 1000).toLocaleString(),
                            End: new Date(event.endedAt * 1000).toLocaleString(),
                            Raw: (
                                <Row>
                                    <Col>
                                        <RawDataViewer text="Nice" data={event} />
                                    </Col>
                                    <Col>
                                        <RawDataViewer
                                            text="Raw"
                                            data={`${Host}/raw/${this.props.region}/event/${event.id}`}
                                        />
                                    </Col>
                                </Row>
                            ),
                        }}
                    />
                </div>

                <Tabs
                    id={"event-reward-tabs"}
                    defaultActiveKey={this.props.tab ?? (tabs.length > 0 ? tabs[0].tabKey : undefined)}
                    mountOnEnter={true}
                    onSelect={(key: string | null) => {
                        this.props.history.replace(`/${this.props.region}/event/${this.props.eventId}/${key}`);
                    }}
                >
                    {tabs.map((tab) => {
                        return (
                            <Tab key={tab.tabKey} eventKey={tab.tabKey} title={tab.title}>
                                {this.renderTab(
                                    this.props.region,
                                    event,
                                    tab,
                                    this.state.servantCache,
                                    this.state.itemCache,
                                    this.state.questCache,
                                    this.state.enums
                                )}
                            </Tab>
                        );
                    })}
                    {event.voices.length > 0 && (
                        <Tab eventKey="voices" title="Voices">
                            <EventVoices
                                region={this.props.region}
                                voiceGroups={event.voices}
                                servants={this.state.servantCache}
                                eventRewardScenes={this.state.event.rewardScenes}
                            />
                        </Tab>
                    )}
                </Tabs>
            </div>
        );
    }
Example #12
Source File: MysticCodePage.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.mysticCode) return <Loading />;

        return (
            <div>
                <MysticCodePicker
                    region={this.props.region}
                    mysticCodes={this.state.mysticCodes}
                    id={this.state.mysticCode.id}
                />

                <hr />

                <Row
                    style={{
                        marginBottom: "3%",
                    }}
                >
                    <Col xs={{ span: 12, order: 2 }} lg={{ span: 6, order: 1 }}>
                        <MysticCodeMainData region={this.props.region} mysticCode={this.state.mysticCode} />
                    </Col>
                    <Col xs={{ span: 12, order: 1 }} lg={{ span: 6, order: 2 }}>
                        <MysticCodePortrait mysticCode={this.state.mysticCode} />
                    </Col>
                </Row>

                <Tabs
                    id={"mystic-code-tabs"}
                    defaultActiveKey={this.props.tab ?? "skill-1"}
                    mountOnEnter={false}
                    onSelect={(key: string | null) => {
                        this.props.history.replace(`/${this.props.region}/mystic-code/${this.props.id}/${key}`);
                    }}
                >
                    <Tab eventKey={"skill-1"} title={"Skill 1"}>
                        <br />
                        {this.state.mysticCode.skills[0] ? (
                            <SkillBreakdown
                                region={this.props.region}
                                skill={this.state.mysticCode.skills[0]}
                                cooldowns={true}
                                levels={10}
                            />
                        ) : null}
                    </Tab>

                    <Tab eventKey={"skill-2"} title={"Skill 2"}>
                        <br />
                        {this.state.mysticCode.skills[1] ? (
                            <SkillBreakdown
                                region={this.props.region}
                                skill={this.state.mysticCode.skills[1]}
                                cooldowns={true}
                                levels={10}
                            />
                        ) : null}
                    </Tab>

                    <Tab eventKey={"skill-3"} title={"Skill 3"}>
                        <br />
                        {this.state.mysticCode.skills[2] ? (
                            <SkillBreakdown
                                region={this.props.region}
                                skill={this.state.mysticCode.skills[2]}
                                cooldowns={true}
                                levels={10}
                            />
                        ) : null}
                    </Tab>

                    <Tab eventKey={"exp"} title={"EXP"}>
                        <br />
                        <MysticCodeExp mysticCode={this.state.mysticCode} />
                    </Tab>

                    <Tab eventKey={"assets"} title={"Assets"}>
                        <br />
                        <MysticCodeAssets mysticCode={this.state.mysticCode} />
                    </Tab>
                </Tabs>
            </div>
        );
    }
Example #13
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 #14
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 #15
Source File: EnemyPage.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.enemy) return <Loading />;

        const enemy = this.state.enemy;

        return (
            <div id={"enemy"}>
                <h1 style={{ marginBottom: "1rem" }}>
                    <ClassIcon className={enemy.className} rarity={enemy.rarity} height={50} />
                    &nbsp;
                    {enemy.name}
                </h1>

                <Row
                    style={{
                        marginBottom: "3%",
                    }}
                >
                    <Col xs={{ span: 12 }} lg={{ span: 6 }}>
                        <EnemyMainData region={this.props.region} enemy={enemy} />
                    </Col>
                    <Col xs={{ span: 12 }} lg={{ span: 6 }}>
                        <EnemySubData region={this.props.region} enemy={enemy} />
                    </Col>
                </Row>

                <Tabs
                    id={"enemy-tabs"}
                    defaultActiveKey={this.props.tab ?? "noble-phantasms"}
                    mountOnEnter={false}
                    onSelect={(key: string | null) => {
                        this.props.history.replace(`/${this.props.region}/enemy/${this.props.id}/${key}`);
                    }}
                >
                    {enemy.skills.length > 0 ? (
                        <Tab eventKey={"skills"} title={"Skills"}>
                            <br />
                            {enemy.skills.map((skill) => {
                                return (
                                    <SkillBreakdown
                                        region={this.props.region}
                                        key={skill.id}
                                        skill={skill}
                                        cooldowns={true}
                                        levels={10}
                                    />
                                );
                            })}
                        </Tab>
                    ) : null}
                    <Tab eventKey={"noble-phantasms"} title={"Noble Phantasms"}>
                        <br />
                        {enemy.noblePhantasms.map((noblePhantasm) => {
                            return (
                                <NoblePhantasmBreakdown
                                    key={noblePhantasm.id}
                                    region={this.props.region}
                                    servant={enemy as unknown as Servant.Servant}
                                    noblePhantasm={noblePhantasm}
                                    hideCard={true}
                                />
                            );
                        })}
                    </Tab>
                    {enemy.classPassive.length > 0 ? (
                        <Tab eventKey={"passives"} title={"Passives"}>
                            <br />
                            <Row>
                                {enemy.classPassive.map((skill) => {
                                    return (
                                        <Col xs={12} lg={(enemy.classPassive.length ?? 1) > 1 ? 6 : 12} key={skill.id}>
                                            <SkillBreakdown
                                                region={this.props.region}
                                                skill={skill}
                                                cooldowns={false}
                                            />
                                        </Col>
                                    );
                                })}
                            </Row>
                        </Tab>
                    ) : null}
                    <Tab eventKey={"stat-growth"} title={"Stat Growth"}>
                        <br />
                        <ServantStatGrowth region={this.props.region} servant={enemy as unknown as Servant.Servant} />
                    </Tab>
                    <Tab eventKey={"profile"} title={"Profile"}>
                        <br />
                        <ServantProfileStat
                            region={this.props.region}
                            profile={(enemy as unknown as Servant.Servant).profile}
                        />
                    </Tab>
                    <Tab eventKey={"assets"} title={"Assets"}>
                        <br />
                        <ServantAssets region={this.props.region} servant={enemy as unknown as Servant.Servant} />
                    </Tab>
                </Tabs>
            </div>
        );
    }
Example #16
Source File: CraftEssencePage.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.craftEssence) return <Loading />;

        const craftEssence = this.state.craftEssence;

        return (
            <div>
                <CraftEssencePicker
                    region={this.props.region}
                    craftEssences={this.state.craftEssences}
                    id={this.state.craftEssence.collectionNo}
                />
                <hr />

                <Row
                    style={{
                        marginBottom: "3%",
                    }}
                >
                    <Col xs={{ span: 12, order: 2 }} lg={{ span: 6, order: 1 }}>
                        <CraftEssenceMainData region={this.props.region} craftEssence={this.state.craftEssence} />
                    </Col>
                    <Col xs={{ span: 12, order: 1 }} lg={{ span: 6, order: 2 }}>
                        <CraftEssencePortrait craftEssence={this.state.craftEssence} />
                    </Col>
                </Row>

                <Tabs
                    id={"ce-tabs"}
                    defaultActiveKey={this.props.tab ?? "effects"}
                    mountOnEnter={false}
                    onSelect={(key: string | null) => {
                        this.props.history.replace(`/${this.props.region}/craft-essence/${this.props.id}/${key}`);
                    }}
                >
                    <Tab eventKey={"effects"} title={"Effects"}>
                        <br />
                        <Row>
                            {this.state.craftEssence.skills
                                .sort((a, b) => (a.num || 0) - (b.num || 0) || (a.priority || 0) - (b.priority || 0))
                                .map((skill) => {
                                    return (
                                        <Col key={skill.id} xs={12} lg={craftEssence.skills.length > 1 ? 6 : 12}>
                                            <SkillBreakdown
                                                region={this.props.region}
                                                skill={skill}
                                                cooldowns={false}
                                            />
                                        </Col>
                                    );
                                })}
                        </Row>
                    </Tab>
                    <Tab eventKey={"stat-growth"} title={"Stat Growth"}>
                        <br />
                        <CraftEssenceStatGrowth region={this.props.region} craftEssence={craftEssence} />
                    </Tab>
                    <Tab eventKey={"profile"} title={"Profile"}>
                        <br />
                        <CraftEssenceProfileComments
                            region={this.props.region}
                            comments={craftEssence.profile?.comments ?? []}
                        />
                    </Tab>
                    <Tab eventKey={"assets"} title={"Assets"}>
                        <br />
                        <CraftEssenceAssets region={this.props.region} craftEssence={craftEssence} />
                    </Tab>
                    {(craftEssence.profile?.voices.length ?? 0) > 0 && (
                        <Tab eventKey={"voices"} title={"Voices"}>
                            <br />
                            <ServantVoiceLines
                                region={this.props.region}
                                servants={new Map()}
                                servant={craftEssence}
                                servantName={craftEssence.name}
                            />
                        </Tab>
                    )}
                </Tabs>
            </div>
        );
    }
Example #17
Source File: WatchView.tsx    From 3Speak-app with GNU General Public License v3.0 4 votes vote down vote up
export function WatchView(props: any) {
  const player = useRef<any>()
  const [videoInfo, setVideoInfo] = useState<any>({})
  const [postInfo, setPostInfo] = useState<any>({})
  const [profilePictureURL, setProfilePictureUrl] = useState(EmptyProfile)
  const [commentGraph, setCommentGraph] = useState()
  const [videoLink, setVideoLink] = useState('')
  const [recommendedVideos, setRecommendedVideos] = useState([])
  const [loaded, setLoaded] = useState(false)
  const [loadingMessage, setLoadingMessage] = useState('')
  const [rootCid, setRootCid] = useState()

  const reflink = useMemo(() => {
    return props.match.params.reflink
  }, [])

  const reflinkParsed = useMemo(() => {
    return RefLink.parse(reflink) as any
  }, [reflink])

  const generalFetch = async () => {
    const info = await AccountService.permalinkToVideoInfo(reflink, { type: 'video' })
    setVideoInfo(info)
    setPostInfo(await AccountService.permalinkToPostInfo(reflink))
    try {
      //Leave profileURL default if error is thrown when attempting to retrieve profile picture
      setProfilePictureUrl(await AccountService.getProfilePictureURL(reflink))
    } catch (ex) {
      console.error(ex)
      throw ex
    }
    document.title = `3Speak - ${info.title}`
    const cids = []
    for (const source of info.sources) {
      const url = new URL(source.url)
      try {
        new CID(url.host)
        cids.push(url.host)
      } catch {}
    }
    setRootCid(cids[0])
  }

  const mountPlayer = async () => {
    try {
      const playerType = 'standard'
      switch (playerType) {
        case 'standard': {
          setVideoLink(await VideoService.getVideoSourceURL(reflink))
        }
      }
      recordView()
    } catch (ex) {
      console.error(ex)
    }
  }

  const recordView = async () => {
    return
    /*let cids = [];
        for(const source of videoInfo.sources) {
            const url = new (require('url').URL)(source.url)
            try {
                new CID(url.host)
                cids.push(url.host)
            } catch  {

            }
        }
        console.log(`CIDs to cache ${JSON.stringify(cids)}`)

        if(cids.length !== 0) {
            await PromiseIpc.send("pins.add", {
                _id: reflink,
                source: "Watch Page",
                cids,
                expire: (new Date().getTime()) + convert("1").from("d").to("ms"),
                meta: {
                    title: videoInfo.title
                }
            })
        }*/
  }

  const gearSelect = async (eventKey) => {
    switch (eventKey) {
      case 'mute_post': {
        await PromiseIpc.send('blocklist.add', reflinkParsed.toString())
        break
      }
      case 'mute_user': {
        await PromiseIpc.send(
          'blocklist.add',
          `${reflinkParsed.source.value}:${reflinkParsed.root}` as any,
        )
        break
      }
    }
  }

  const retrieveRecommended = async () => {
    const query = knex.raw(
      `SELECT TOP 25 x.* FROM DBHive.dbo.Comments x WHERE CONTAINS(json_metadata , '3speak/video') AND category LIKE '${postInfo.category}' ORDER BY NEWID()`,
    )
    const blob = []
    query.stream().on('data', async (val) => {
      if (await PromiseIpc.send('blocklist.has', `hive:${val.author}:${val.permlink}` as any)) {
        console.log(`${val.author} is blocked`)
        return
      }
      val.json_metadata = JSON.parse(val.json_metadata)
      //console.log(val)
      if (!val.json_metadata.video) {
        val.json_metadata.video = {
          info: {},
        }
      }
      let thumbnail
      if (val.json_metadata.sourceMap) {
        thumbnail = Finder.one.in(val.json_metadata.sourceMap).with({ type: 'thumbnail' }).url
        console.log(thumbnail)
      }
      blob.push({
        reflink: `hive:${val.author}:${val.permlink}`,
        created: val.created,
        author: val.author,
        permlink: val.permlink,
        tags: val.json_metadata.tags,
        title: val.title,
        duration: val.json_metadata.video.info.duration || val.json_metadata.video.duration,
        isIpfs: val.json_metadata.video.info.ipfs || thumbnail ? true : false,
        ipfs: val.json_metadata.video.info.ipfs,
        images: {
          ipfs_thumbnail: thumbnail
            ? `/ipfs/${thumbnail.slice(7)}`
            : `/ipfs/${val.json_metadata.video.info.ipfsThumbnail}`,
          thumbnail: `https://threespeakvideo.b-cdn.net/${val.permlink}/thumbnails/default.png`,
          poster: `https://threespeakvideo.b-cdn.net/${val.permlink}/poster.png`,
          post: `https://threespeakvideo.b-cdn.net/${val.permlink}/post.png`,
        },
        views: val.total_vote_weight ? Math.log(val.total_vote_weight / 1000).toFixed(2) : 0,
      })

      setRecommendedVideos(blob)
    })
    query.on('query-response', (ret, det, aet) => {
      console.log(ret, det, aet)
    })
    query.on('end', (err) => {
      console.log(err)
    })
    /*
        let ref = RefLink.parse(reflink)
        let data = (await axios.get(`https://3speak.tv/apiv2/recommended?v=${ref.root}/${ref.permlink}`)).data
        data.forEach((value => {
            let link = value.link.split("=")[1].split("/")
            value.reflink = `hive:${link[0]}:${link[1]}`
        }))*/
  }

  const PinLocally = async () => {
    const cids = []
    for (const source of videoInfo.sources) {
      const url = new URL(source.url)
      try {
        new CID(url.host)
        cids.push(url.host)
      } catch {}
    }

    debug(`CIDs to store ${JSON.stringify(cids)}`)
    if (cids.length !== 0) {
      NotificationManager.info('Pinning in progress')
      await PromiseIpc.send('pins.add', {
        _id: reflink,
        source: 'Watch Page',
        cids,
        expire: null,
        meta: {
          title: videoInfo.title,
        },
      } as any)
      NotificationManager.success(
        `Video with reflink of ${reflink} has been successfully pinned! Thank you for contributing!`,
        'Pin Successful',
      )
    } else {
      NotificationManager.warning('This video is not available on IPFS')
    }
  }
  const showDebug = () => {
    const metadata = videoInfo
    Popup.registerPlugin('watch_debug', async function () {
      this.create({
        content: (
          <div>
            <Tabs defaultActiveKey="meta" id="uncontrolled-tab-example">
              <Tab eventKey="meta" title="Metadata">
                <Editor value={metadata} ace={ace} theme="ace/theme/github"></Editor>
              </Tab>
            </Tabs>
          </div>
        ),
        buttons: {
          right: [
            {
              text: 'Close',
              className: 'success',
              action: function () {
                Popup.close()
              },
            },
          ],
        },
      })
    })
    Popup.plugins().watch_debug()
  }

  useEffect(() => {
    const load = async () => {
      try {
        await generalFetch()
        setLoadingMessage('Loading: Mounting player...')
        await mountPlayer()
      } catch (ex) {
        setLoadingMessage('Loading resulted in error')
        throw ex
      }
      setLoaded(true)
      await retrieveRecommended()
    }

    void load()
  }, [])

  useEffect(() => {
    window.scrollTo(0, 0)

    const update = async () => {
      await generalFetch()
      await mountPlayer()
      await retrieveRecommended()
      player.current?.ExecUpdate()
    }

    void update()
  }, [reflink])

  return (
    <div>
      {loaded ? (
        <Container fluid>
          {/* <Container fluid pb={0}> */}
          {/* <Row fluid="md"> */}
          <Row>
            <Col md={8}>
              <div>
                <Player reflink={reflink}></Player>
              </div>
              <div className="single-video-title box mb-3 clearfix">
                <div className="float-left">
                  <h2 style={{ fontSize: '18px' }}>
                    <a>{videoInfo.title}</a>
                  </h2>
                  <DHTProviders rootCid={rootCid} />
                </div>
                <div
                  className="float-right"
                  style={
                    {
                      textAlign: 'right !important',
                      float: 'right !important',
                      display: 'inline-block !important',
                    } as any
                  }
                >
                  <span>
                    <VoteWidget reflink={reflink} />
                  </span>
                  <Dropdown onSelect={gearSelect} style={{ paddingTop: '10px' }}>
                    <Dropdown.Toggle
                      as={CustomToggle}
                      id="dropdown-custom-components"
                    ></Dropdown.Toggle>
                    <Dropdown.Menu>
                      <Dropdown.Item eventKey="mute_post">
                        <p style={{ color: 'red' }}>Mute Post</p>
                      </Dropdown.Item>
                      <Dropdown.Item eventKey="mute_user">
                        <p style={{ color: 'red' }}>Mute User</p>
                      </Dropdown.Item>
                    </Dropdown.Menu>
                  </Dropdown>
                </div>
              </div>
              <div className="single-video-author box mb-3">
                <div className="float-right">
                  <Row>
                    <FollowWidget reflink={reflink} />
                    <a
                      target="_blank"
                      style={{ marginRight: '5px', marginLeft: '5px' }}
                      className="btn btn-light btn-sm"
                      onClick={PinLocally}
                    >
                      <FaDownload /> Download to IPFS node
                    </a>
                    <a
                      target="_blank"
                      style={{ marginRight: '5px' }}
                      className="btn btn-light btn-sm"
                      href={(() => {
                        const videoSource = Finder.one.in(videoInfo.sources).with({
                          format: 'mp4',
                        })
                        if (videoSource) {
                          return videoSource.url
                        }
                      })()}
                    >
                      <FaDownload /> Download
                    </a>
                  </Row>
                </div>
                <img className="img-fluid" src={profilePictureURL} alt="" />
                <p>
                  <a href={`#/user/${reflinkParsed.source.value}:${reflinkParsed.root}`}>
                    <strong>{postInfo.author}</strong>
                  </a>
                </p>
                <small>
                  Published on{' '}
                  {(() => {
                    const pattern = DateTime.compile('MMMM D, YYYY')
                    return DateTime.format(new Date(videoInfo.creation), pattern)
                  })()}
                </small>
              </div>
              <div className="single-video-info-content box mb-3">
                <h6>About :</h6>
                <CollapsibleText>
                  <ReactMarkdown
                    escapeHtml={false}
                    source={DOMPurify.sanitize(videoInfo.description)}
                  ></ReactMarkdown>
                  <hr />
                  <Container style={{ marginBottom: '10px', textAlign: 'center' }}>
                    <a
                      target="_blank"
                      style={{ marginRight: '5px' }}
                      className="btn btn-light btn-sm"
                      onClick={() => showDebug()}
                    >
                      <BsInfoSquare /> Debug Info
                    </a>
                  </Container>
                </CollapsibleText>
                <h6>Tags: </h6>
                <p className="tags mb-0">
                  {(() => {
                    const out = []
                    if (videoInfo.tags) {
                      for (const tag of videoInfo.tags) {
                        out.push(
                          <span style={{ paddingLeft: '3px' }} key={tag}>
                            <a>{tag}</a>
                          </span>,
                        )
                      }
                    }
                    return out
                  })()}
                </p>
              </div>
              <CommentSection reflink={reflink.toString()} />
            </Col>
            <Col md={4}>
              <Row>
                <Col md={12}>
                  {recommendedVideos.map((value) => (
                    <VideoTeaser key={value.reflink} reflink={value.reflink} />
                  ))}
                </Col>
              </Row>
            </Col>
          </Row>
        </Container>
      ) : (
        <div>
          <LoopCircleLoading />
          <div
            style={{
              textAlign: 'center',
              margin: 'auto',
              position: 'absolute',
              left: '0px',
              right: '0px',
              top: '60%',
              bottom: '0px',
            }}
          >
            <h1 style={{ top: '60%', fontSize: '20px' }}>{loadingMessage}</h1>
          </div>
        </div>
      )}
    </div>
  )
}
Example #18
Source File: UploaderView.tsx    From 3Speak-app with GNU General Public License v3.0 4 votes vote down vote up
export function UploaderView() {
  const videoUpload = useRef<any>()
  const thumbnailUpload = useRef<any>()
  const thumbnailPreview = useRef('')
  const publishForm = useRef()
  const hwaccelOption = useRef()
  const ipfs = useRef<any>()

  const [logData, setLogData] = useState([])
  const [videoSourceFile, setVideoSourceFile] = useState()
  const [encodingInProgress, setEncodingInProgress] = useState(false)
  const [progress, setProgress] = useState<any>({})
  const [statusInfo, setStatusInfo] = useState<any>({ progress: {} })
  const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState('')
  const [videoInfo, setVideoInfo] = useState<any>({
    path: null,
    size: 0,
    cid: null,
    language: '',
    duration: null,
  })
  const [thumbnailInfo, setThumbnailInfo] = useState({
    path: null,
    size: 0,
    cid: null,
  })
  const [startTime, setStartTime] = useState<number>()
  const [endTime, setEndTime] = useState<number>(0)
  const [publishReady, setPublishReady] = useState(false)
  const [blockedGlobalMessage, setBlockedGlobalMessage] = useState('')

  useEffect(() => {
    ipfs.current = IPFSHTTPClient.create({ host: IPFS_HOST })
  }, [])

  const caluclatePercentage = () => {
    return progress.percent / statusInfo.nstages + statusInfo.stage * (100 / statusInfo.nstages)
  }

  const normalizeSize = () => {
    const size = videoInfo.size + thumbnailInfo.size
    return bytesAsString(size)
  }

  const compileVideoCid = async () => {
    const videoCid = videoInfo.cid
    if (thumbnailInfo.cid) {
      const obj = await ipfs.current.object.stat(thumbnailInfo.cid)
      console.log(obj)
      console.log(thumbnailInfo)
      const output = await ipfs.current.object.patch.addLink(videoCid, {
        name: thumbnailInfo.path,
        size: thumbnailInfo.size,
        cid: thumbnailInfo.cid,
      })
      console.log(output)
      return output.toString()
    }
    return videoCid
  }

  /**
   * Note: example metadata https://hiveblocks.com/hive-181335/@taskmaster4450/tqxwimhy
   */
  const publish = async () => {
    const videoCid = await compileVideoCid()
    const formData = FormUtils.formToObj(new FormData(publishForm.current))
    let tags: string[] = []
    if (formData.tags) {
      tags = formData.tags.replace(/\s/g, '').split(',')
    }

    console.log(`thumbnail info`, thumbnailInfo)

    const sourceMap = []
    if (thumbnailInfo.path) {
      sourceMap.push({
        type: 'thumbnail',
        url: `ipfs://${videoCid}/${thumbnailInfo.path}`,
      })
    }

    if (videoInfo) {
      sourceMap.push({
        type: 'video',
        url: `ipfs://${videoCid}/${videoInfo.path}`,
        format: 'm3u8',
      })
    }
    const permlink = `speak-${randomstring
      .generate({
        length: 8,
        charset: 'alphabetic',
      })
      .toLowerCase()}`
    //     console.log(permlink)
    console.log(`source map`)
    console.log(sourceMap)
    //     console.log(videoInfo)
    //     console.log(typeof formData.description)
    //     console.log(videoCid)
    setBlockedGlobalMessage('Publishing')

    const filesize = videoInfo.size + thumbnailInfo.size

    console.log(`formdata is `, formData)

    formData.title = formData.title || 'no form data'
    formData.description = formData.description || 'no form data'

    console.log(`publish form is `, publishForm.current)

    try {
      const [reflink] = await AccountService.postComment({
        accountType: 'hive',
        title: formData.title,
        body: formData.description,
        permlink,
        tags,
        json_metadata: {
          title: formData.title,
          description: formData.description,
          tags,
          sourceMap,
          filesize,
          created: new Date(),
          lang: videoInfo.language,
          video: {
            duration: videoInfo.duration,
          },
          app: '3speak/app-beta',
          type: '3speak/video',
        },
      })

      setTimeout(() => {
        location.hash = `#/watch/${reflink}`
        setBlockedGlobalMessage('done')
      }, 15000)
    } catch (error) {
      console.error(`Error in postComment operation ${error.message}`)
      throw error
    }
  }

  const handleVideoSelect = async (e) => {
    const file = e.target.files[0]
    setVideoSourceFile(file.path)
    setLogData([...logData, `Selected: ${videoInfo.path}`])
  }

  const handleThumbnailSelect = async (e) => {
    console.log(`handling thumbnail selectr`)

    const file = e.target.files[0]
    const imgblob = URL.createObjectURL(file)
    const size = file.size

    console.log(`uploading file with size ${size}`)

    thumbnailPreview.current = imgblob

    const fileDetails = {
      path: e.target.files[0].name,
      content: e.target.files[0],
    }

    const ipfsOut = await ipfs.current.add(fileDetails, { pin: false })
    console.log(`setting thumbnail info to cid`, ipfsOut.cid.toString())

    setThumbnailInfo({
      size,
      path: `thumbnail.${file.type.split('/')[1]}`,
      cid: ipfsOut.cid.toString(),
    })
  }

  const handleStartEncode = async (event) => {
    event.preventDefault()
    if (videoSourceFile === null) {
      NotificationManager.error('No video source file selected')
      return
    }
    if (!Fs.existsSync(videoSourceFile)) {
      NotificationManager.error('Source file does not exist')
      return
    }
    setEncodingInProgress(true)
    setStartTime(new Date().getTime())
    setEndTime(null)

    const jobInfo = (await PromiseIpc.send('encoder.createJob', {
      sourceUrl: videoSourceFile,
      profiles: [
        {
          name: '1080p',
          size: '1920x1080',
        },
        {
          name: '720p',
          size: '1080x720',
        },
        {
          name: '480p',
          size: '720x480',
        },
      ],
      options: {
        hwaccel:
          hwaccelOption.current !== '' && hwaccelOption.current !== 'none'
            ? hwaccelOption.current
            : undefined,
      },
    } as any)) as any
    NotificationManager.success('Encoding Started.')

    let savePct = 0
    const progressTrack = async () => {
      const status = (await PromiseIpc.send('encoder.status', jobInfo.id)) as any

      console.log(`Encoder status: `, status)

      setProgress(status.progress || {})
      setStatusInfo(status)

      const val = caluclatePercentage()
      const diffPct = val - savePct
      savePct = val
      const pctPerSec = diffPct / 3
      const totalTimeRemaining = (100 - val) / pctPerSec

      setEstimatedTimeRemaining(secondsAsString(totalTimeRemaining))
      setEndTime(new Date().getTime())
    }

    const pid = setInterval(progressTrack, 3000)
    void progressTrack()

    const encodeOutput = (await PromiseIpc.send('encoder.getjoboutput', jobInfo.id)) as any
    console.log(`got encode output`)
    console.log(encodeOutput)

    setVideoInfo({
      size: encodeOutput.size,
      cid: encodeOutput.ipfsHash,
      path: encodeOutput.path,
      duration: Number(DateTime.parse(encodeOutput.duration, 'hh:mm:ss.SS', true)) / 1000,
    })

    clearInterval(pid)

    setEncodingInProgress(false)
    setEstimatedTimeRemaining(null)
    setEndTime(new Date().getTime())
    setPublishReady(true)

    NotificationManager.success('Encoding complete.')
  }

  if (blockedGlobalMessage) {
    return (
      <LoadingMessage
        loadingMessage={blockedGlobalMessage}
        subtitle="Note: you will need to keep the app open for your video to play for other users. A process called 'shunting' will be released in the future to relieve this issue."
      />
    )
  }

  return (
    <div style={{ width: '95%', marginRight: 'auto', marginLeft: 'auto' }}>
      <Row style={{ marginTop: '1.45rem' }}>
        <div>
          <div
            className="d-table-cell align-middle card dz-clickable"
            onClick={() => videoUpload.current.click()}
            style={{
              width: '4000px',
              textAlign: 'center',
              height: '150px',
              fontSize: '16px',
              fontWeight: 'bold',
              cursor: 'pointer',
            }}
          >
            Drop a file or click to start the upload <br />
            <p>
              Selected: <kbd>{videoSourceFile}</kbd>
            </p>
            <input
              accept="video/*"
              type="file"
              id="video"
              className="d-none"
              ref={videoUpload}
              onChange={handleVideoSelect}
            />
          </div>
        </div>
      </Row>
      <Row style={{ marginTop: '15px' }}>
        <Col xl={6} sm={12} style={{ paddingLeft: '0px' }}>
          <div className="card" style={{ padding: '10px' }}>
            <Form ref={publishForm.current}>
              <Form.Group>
                <Form.Label>Title</Form.Label>
                <Form.Control type="text" name="title"></Form.Control>
              </Form.Group>
              <Form.Group>
                <Form.Label>Description</Form.Label>
                <textarea className="form-control" name="description"></textarea>
              </Form.Group>
              <Form.Group>
                <Form.Label>Tags</Form.Label>
                <Form.Control type="text" name="tags"></Form.Control>
                <small className="text-muted">
                  Separate multiple tags with a <kbd>,</kbd>{' '}
                </small>
              </Form.Group>
              <Form.Group>
                <Form.Label>Language</Form.Label>
                <select disabled={false} name="language" className="form-control mb-4">
                  <option selected={false} value="en">
                    English
                  </option>
                  <option value="de">Deutsch</option>
                  <option value="fr">Français</option>
                  <option value="es">Español</option>
                  <option value="nl">Nederlands</option>
                  <option value="ko">한국어</option>
                  <option value="ru">русский</option>
                  <option value="hu">Magyar</option>
                  <option value="ro">Română</option>
                  <option value="cs">čeština</option>
                  <option value="pl">Polskie</option>
                  <option value="in">bahasa Indonesia</option>
                  <option value="bn">Bangla</option>
                  <option value="it">Italian</option>
                </select>
              </Form.Group>
              <span className="form-check mb-3">
                <input
                  type="checkbox"
                  className="form-check-input"
                  id="nsfwContent"
                  name="nsfwContent"
                />
                <label className="form-check-label" htmlFor="nsfwContent">
                  Content is graphic and/or NSFW
                  <span className="text-danger">
                    &nbsp;Warning: you should check this option if your content is&nbsp;
                    <a href="https://en.wikipedia.org/wiki/Not_safe_for_work">NSFW</a>.
                  </span>
                </label>
              </span>
              <Form.Group>
                <Form.Label>Thumbnail</Form.Label>
                <div></div>
                <img
                  src={thumbnailPreview.current || DefaultThumbnail}
                  style={{
                    width: '720px',
                    aspectRatio: '16/9',
                    cursor: 'pointer',
                  }}
                  alt=""
                  onClick={() => thumbnailUpload.current.click()}
                />
                <input
                  accept="image/*"
                  type="file"
                  id="thumbnail_input"
                  className="d-none"
                  ref={thumbnailUpload}
                  onChange={handleThumbnailSelect}
                />
                <p>Click the thumbnail to change it</p>
                <p>Recommended 5MB. Ideally 1280px×720px.</p>
              </Form.Group>
              <Button onClick={handleStartEncode}>Start Encode</Button>
              <Button
                style={{ marginLeft: '5px' }}
                onClick={publish}
                disabled={encodingInProgress || !publishReady}
              >
                Publish
              </Button>
            </Form>
          </div>
        </Col>
        <Col style={{ paddingRight: '0px', paddingLeft: '0px' }}>
          <Card>
            <Card.Header>Encoder status</Card.Header>
            <Card.Body>
              <Card.Text>This area will show live encoding statistics</Card.Text>
              <Button style={{ marginBottom: '5px' }} variant="primary">
                FPS: {progress.currentFps}
              </Button>{' '}
              <br />
              <Button variant="primary">Video Size: {normalizeSize()}</Button>
              <ProgressBar
                style={{
                  display: encodingInProgress ? '' : 'none',
                }}
                striped
                variant="success"
                now={caluclatePercentage()}
                label={progress.percent ? `${Math.round(caluclatePercentage())}%` : null}
              />
              <div
                style={{
                  display: encodingInProgress ? '' : 'none',
                }}
              >
                Time Remaining:{' '}
                {estimatedTimeRemaining !== 'NaNns' ? estimatedTimeRemaining : 'Calculating'}
              </div>
              <div style={{ display: endTime ? '' : 'none' }}>
                Total Time (so far):{' '}
                {endTime !== 0 ? millisecondsAsString(endTime - startTime) : 'Calculating'}
              </div>
            </Card.Body>
          </Card>
          <div className="card" style={{ marginTop: '15px' }}>
            <div className="card-header">
              <h5>Control Panel</h5>
            </div>
            <Tabs style={{ background: 'white' }} defaultActiveKey="encode">
              <Tab
                style={{ background: 'white', padding: '10px' }}
                eventKey="encode"
                title="Encode Settings"
              >
                <Form.Group>
                  <Form.Label>
                    <strong>Format</strong>
                  </Form.Label>
                  <select
                    style={{ width: '6em' }}
                    disabled={encodingInProgress}
                    id="language"
                    className="form-control mb-4"
                  >
                    <option selected={false} value="hls">
                      HLS
                    </option>
                  </select>
                </Form.Group>
                <Form.Group>
                  <Form.Label>
                    <strong>Hardware Accel</strong>
                  </Form.Label>
                  <Form.Text>
                    Use hardware acceleration to speed up video encode. Not available on all
                    systems, results may vary.
                  </Form.Text>
                  <select
                    style={{ width: '6em' }}
                    ref={hwaccelOption}
                    disabled={encodingInProgress}
                    id="language"
                    className="form-control mb-4"
                  >
                    <option selected={false} value="none">
                      none
                    </option>
                    <option value="qsv">QSV</option>
                    <option value="nvenc">nvenc</option>
                  </select>
                </Form.Group>
              </Tab>
              <Tab eventKey="info" title="Info" style={{ padding: '10px' }}>
                <Form.Group>
                  <Form.Label>Video IpfsPath</Form.Label>
                  <Form.Control
                    type="text"
                    name="vidoeHash"
                    disabled
                    value={videoInfo.cid}
                  ></Form.Control>
                </Form.Group>
                <Form.Group>
                  <Form.Label>Thumbnail IpfsPath</Form.Label>
                  <Form.Control
                    type="text"
                    name="thumbnailHash"
                    disabled
                    value={thumbnailInfo.cid}
                  ></Form.Control>
                </Form.Group>
                <Form.Group>
                  <Form.Label>Total Size</Form.Label>
                  <Form.Control
                    style={{ width: '15%' }}
                    type="text"
                    name="videoSize"
                    value={normalizeSize()}
                    disabled
                  ></Form.Control>
                </Form.Group>
              </Tab>
              {/*<Tab eventKey="networks" title="Networks">
                                <Table striped bordered hover size="sm">
                                    <thead>
                                        <tr>
                                            <th>Enabled</th>
                                            <th>ID</th>
                                            <th>Username</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <tr>
                                            <td><Form.Check/></td>
                                            <td>Hive</td>
                                            <td>vaultec</td>
                                        </tr>
                                    </tbody>
                                </Table>
                                
                            </Tab>*/}

              <Tab eventKey="log" title="Log" style={{ padding: '10px' }}>
                <textarea
                  disabled
                  className="form-control"
                  value={(() => logData.join('\n'))()}
                ></textarea>
              </Tab>
            </Tabs>
          </div>
        </Col>
      </Row>
    </div>
  )
}