@mui/material#Card TypeScript Examples

The following examples show how to use @mui/material#Card. 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: SingleVaultCard.tsx    From yearn-watch-legacy with GNU Affero General Public License v3.0 6 votes vote down vote up
SingleVaultCard = ({ vault, network }: Props) => {
    const classes = useStyles();
    const theme = useTheme();
    return (
        <Card className={classes.card}>
            <CardHeader
                style={{
                    borderColor:
                        vault?.configOK == false
                            ? theme.palette.error.main
                            : '',
                }}
                classes={{
                    action: classes.action,
                    title: classes.title,
                    subheader: classes.subheader,
                }}
                avatar={
                    <Avatar
                        src={vault ? vault.icon : ''}
                        aria-label="vault"
                        sizes={classes.avatar}
                    />
                }
                title={vault ? vault.name : ''}
                subheader={vault ? vault.strategies.length + ' strats' : ''}
            />
            <div className={classes.action}>
                {vault && (
                    <EtherScanLink address={vault.address} network={network} />
                )}
            </div>
        </Card>
    );
}
Example #2
Source File: Home.tsx    From mui-toolpad with MIT License 6 votes vote down vote up
function AppCard({ app, onDelete }: AppCardProps) {
  return (
    <Card sx={{ gridColumn: 'span 1' }}>
      <CardActionArea component="a" href={app ? `/deploy/${app.id}` : ''} disabled={!app}>
        <CardContent>
          <Typography gutterBottom variant="h5" component="div">
            {app ? app.name : <Skeleton />}
          </Typography>
          <Typography variant="body2" color="text.secondary">
            {app ? `Created: ${app.createdAt.toLocaleDateString('short')}` : <Skeleton />}
          </Typography>
        </CardContent>
      </CardActionArea>
      <CardActions>
        <Button component="a" href={app ? `/_toolpad/app/${app.id}/editor` : ''} disabled={!app}>
          Edit
        </Button>
        <Button disabled={!app} onClick={onDelete}>
          delete
        </Button>
      </CardActions>
    </Card>
  );
}
Example #3
Source File: Config.tsx    From NekoMaid with MIT License 6 votes vote down vote up
Config: React.FC = () => {
  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3}>
        {configs.map((it, i) => <Grid key={i} item lg={4} md={12} xl={6} xs={12}>
          <Card>
            <CardHeader title={it.title} />
            <Divider />
            <it.component />
          </Card>
        </Grid>)}
      </Grid>
    </Container>
  </Box>
}
Example #4
Source File: PrincipleCard.tsx    From frontend with MIT License 6 votes vote down vote up
StyledCard = styled(Card)(({ theme }) => ({
  [`& .${classes.contentContainer}`]: {
    padding: theme.spacing(0),
    margin: theme.spacing(2, 0),
    '&:last-child': {
      paddingBottom: theme.spacing(0),
    },
  },

  [`& .${classes.heading}`]: {
    color: theme.palette.primary.main,
    display: 'block',
    fontSize: theme.typography.pxToRem(18),
  },

  [`& .${classes.content}`]: {
    marginBottom: theme.spacing(0),
    paddingRight: theme.spacing(6),
  },

  [`& .${classes.cardHeader}`]: {
    padding: theme.spacing(0),
  },

  [`& .${classes.cardHeaderAction}`]: {
    margin: theme.spacing(0),
    alignSelf: 'center',
  },

  [`& .${classes.cardHeaderTitleRoot}`]: {
    flexGrow: 1,
    flex: 0,
  },

  [`& .${classes.icon}`]: {
    fontSize: theme.spacing(5),
  },
}))
Example #5
Source File: Profiler.tsx    From NekoMaid with MIT License 6 votes vote down vote up
Pie: React.FC<{ title: string, data: any[], formatter?: any }> = React.memo(({ title, data, formatter }) => <Grid item sm={6} xs={12}>
  <Card>
    <CardHeader title={title} />
    <Divider />
    {data.length
      ? <ReactECharts style={{ height: 300 }} theme={useTheme().palette.mode === 'dark' ? 'dark' : undefined} option={{
        backgroundColor: 'rgba(0, 0, 0, 0)',
        itemStyle: {
          borderRadius: 5,
          borderColor: 'rgba(0, 0, 0, 0)',
          borderWidth: 4
        },
        tooltip: {
          trigger: 'item',
          formatter
        },
        series: [{
          type: 'pie',
          radius: '50%',
          data,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }]
      }} />
      : <CardContent><Empty /></CardContent>}
  </Card>
</Grid>)
Example #6
Source File: CampaignCard.tsx    From frontend with MIT License 6 votes vote down vote up
StyledCard = styled(Card)(({ theme }) => ({
  [`& .${classes.media}`]: {
    backgroundSize: 'contain',
    height: 250,
    margin: theme.spacing(0, 4),
    transition: 'filter 0.3s, opacity 0.8s',
  },

  [`& .${classes.cardActions}`]: {
    padding: '0',
  },

  [`&.${classes.cardWrapper}`]: {
    position: 'relative',
    minHeight: theme.spacing(87),
    backgroundColor: theme.palette.secondary.light,
    border: 'none',
    borderRadius: 0,
  },

  [`& .${classes.campaignTitle}`]: {
    textTransform: 'capitalize',
  },

  [`& .${classes.progressBar}`]: {
    margin: theme.spacing(2.5),
    textAlign: 'left',
  },

  [`& .${classes.cardContent}`]: {
    minHeight: theme.spacing(25),
  },
}))
Example #7
Source File: ConnectionConfigDialog.tsx    From airmessage-web with Apache License 2.0 6 votes vote down vote up
function ConnectionCard(props: {connected?: boolean, children?: React.ReactNode}) {
	return (
		<Card variant="outlined">
			<div className={`${styles.connectionCard} ${props.connected ? styles.connectionCardOK : ""}`}>
				{props.connected ? <CheckCircleOutline className={styles.connectionIcon} /> : <CloudOff className={styles.connectionIcon} />}
				<Typography variant="body1">{props.children}</Typography>
			</div>
		</Card>
	);
}
Example #8
Source File: DetailsModal.tsx    From frontend with MIT License 6 votes vote down vote up
export default function DetailsModal({ campaign, onClose }: Props) {
  const { t } = useTranslation()

  return (
    <Dialog open scroll="body" onClose={onClose}>
      <DialogTitle>{t('Детайли на кампанията')}</DialogTitle>
      <Card>
        <Divider />
        <CardContent sx={{ p: 3 }}>
          <Typography variant="body1">
            {t('Заглавие')}: {campaign.title}
          </Typography>
          <Typography variant="body1">Слъг: {campaign.slug}</Typography>
          <Typography variant="body1">Целева сума: {campaign.targetAmount}</Typography>
          <Typography variant="body1">Стартова дата: {campaign.startDate}</Typography>
          <Typography variant="body1">Крайна Дата: {campaign.endDate}</Typography>
          <Typography variant="body1">Същество: {campaign.essence}</Typography>
          <Typography variant="body1">Тип на кампанията: {campaign.campaignTypeId}</Typography>
          <Typography variant="body1">Бенефициент: {campaign.beneficiaryId}</Typography>
          <Typography variant="body1">Кординатор: {campaign.coordinatorId}</Typography>
          <Typography variant="body1">Валута: {campaign.currency}</Typography>
          <Typography variant="body1">
            Описание: {campaign.description}
            {campaign.description}
            {campaign.description}
            {campaign.description}
          </Typography>
        </CardContent>
      </Card>
      <DialogActions>
        <Button variant="contained" onClick={onClose}>
          Затвори
        </Button>
      </DialogActions>
    </Dialog>
  )
}
Example #9
Source File: index.tsx    From ExpressLRS-Configurator with GNU General Public License v3.0 6 votes vote down vote up
SettingsView: FunctionComponent = () => {
  return (
    <MainLayout>
      <Card>
        <CardTitle icon={<SettingsIcon />} title="Settings" />
        <Divider />
        <CardContent>
          <Typography variant="h6">Todo:</Typography>
          <ul>
            <li>Display configurator version</li>
            <li>Language selector</li>
          </ul>
        </CardContent>
      </Card>
    </MainLayout>
  );
}
Example #10
Source File: item-card.tsx    From sdk with MIT License 6 votes vote down vote up
export function ItemCard({ item }: IItemCardProps) {
	return (
		<Card sx={{ width: 200 }}>
			<CardHeader
				sx={{
					display: "flex",
					overflow: "hidden",
					"& .MuiCardHeader-content": {
						overflow: "hidden"
					}
				}}
				title={<Typography noWrap gutterBottom variant="h6" component="h4">
					{item.meta ? item.meta.name : "No metadata"}
				</Typography>}
			/>
			<ItemMedia url={getMetaImageUrl(item.meta?.content)}/>
			<CardContent>
				<Typography variant="body2" color="text.secondary" sx={{ textAlign: "right" }}>
					<strong>Supply: {item.supply}</strong>
				</Typography>
			</CardContent>
			<CardActions>
				<Button
					size="small"
					component={Link}
					to={`/sell/${item.id}`}
				>
					Sell
				</Button>
				{/*<Button size="small" color={"warning"}>Burn</Button>*/}
			</CardActions>
		</Card>
	)
}
Example #11
Source File: index.tsx    From ExpressLRS-Configurator with GNU General Public License v3.0 6 votes vote down vote up
LogsView: FunctionComponent = () => {
  const onLogs = () => {
    ipcRenderer.send(IpcRequest.OpenLogsFolder);
  };
  return (
    <MainLayout>
      <Card>
        <CardTitle icon={<ListIcon />} title="Logs" />
        <Divider />
        <CardContent>
          <Button
            color="primary"
            size="large"
            variant="contained"
            onClick={onLogs}
          >
            Open logs folder
          </Button>
        </CardContent>
      </Card>
    </MainLayout>
  );
}
Example #12
Source File: BorderColorCard.tsx    From Search-Next with GNU General Public License v3.0 6 votes vote down vote up
BorderColorCard: React.FC<BorderColorCardProps> = ({
  color,
  children,
  ...props
}) => {
  return (
    <Card {...props} className={classNames(BorderColorCardStyle(color))}>
      {children}
    </Card>
  );
}
Example #13
Source File: PlayerList.tsx    From NekoMaid with MIT License 5 votes vote down vote up
PlayerActions: React.FC = () => {
  const theme = useTheme()
  const ref = useRef<HTMLCanvasElement | null>(null)
  const globalData = useGlobalData()
  const { name } = useParams<{ name: string }>()
  useEffect(() => {
    if (!ref.current || !name) return
    const viewer = new FXAASkinViewer({
      canvas: ref.current!,
      height: 350,
      width: ref.current.clientWidth,
      skin: getSkin(globalData, name)
    })
    viewer.renderer.setClearColor(theme.palette.mode === 'dark' ? 0x2c2c2c : 0xffffff)

    const resize = () => ref.current && (viewer.width = ref.current.clientWidth)
    window.addEventListener('resize', resize)
    const control = createOrbitControls(viewer)
    control.enableRotate = true
    control.enablePan = control.enableZoom = false

    viewer.animations.add(WalkingAnimation)
    viewer.animations.add(RotatingAnimation)
    return () => {
      window.removeEventListener('resize', resize)
      viewer.dispose()
    }
  }, [ref.current, name, theme.palette.mode, globalData])

  return <Card>
    <CardHeader title={name ? lang.playerList.whosDetails(name) : lang.openInv.notSelected} />
    <Divider />
    <Box sx={{ position: 'relative', '& canvas': { width: '100%!important' } }}>
      {name
        ? <canvas
          ref={ref}
          height='400'
          style={{ cursor: 'grab', display: name ? undefined : 'none' }}
        />
        : <CardContent><Empty title={null} /></CardContent>}
    </Box>
    <PlayerInfo name={name} />
  </Card>
}
Example #14
Source File: AdminPage.tsx    From frontend with MIT License 5 votes vote down vote up
export default function AdminPage() {
  const { t } = useTranslation()
  const router = useRouter()
  const { data: session, status } = useSession()
  if (status !== 'authenticated') {
    return (
      <AdminLayout>
        <AdminContainer title={t('nav.admin.index')}>
          <Box p={3}>
            <Typography variant="h6">Not authenticated</Typography>
          </Box>
        </AdminContainer>
      </AdminLayout>
    )
  }

  if (!isAdmin(session)) {
    return (
      <AdminLayout>
        <AdminContainer title={t('nav.admin.index')}>
          <Box p={3}>
            <Typography variant="h6">Not authorized</Typography>
          </Box>
        </AdminContainer>
      </AdminLayout>
    )
  }

  return (
    <AdminLayout>
      <AdminContainer title={t('nav.admin.index')}>
        <Box p={4}>
          <Typography variant="h6">{'Добре дошли!'}</Typography>
        </Box>
        <Grid container spacing={2} rowSpacing={4} px={4} pb={4}>
          {menuItems.map(({ label, href, icon: Icon }, index) => (
            <Grid xs={12} sm={6} md={4} lg={2} item key={index}>
              <Card sx={{ maxWidth: 345 }}>
                <CardActionArea onClick={() => router.push(href)}>
                  <CardContent>
                    <Box textAlign="center">
                      <Icon fontSize="medium" />
                    </Box>
                    <Typography gutterBottom variant="h6" component="div" textAlign="center">
                      {label}
                    </Typography>
                  </CardContent>
                </CardActionArea>
              </Card>
            </Grid>
          ))}
        </Grid>
      </AdminContainer>
    </AdminLayout>
  )
}
Example #15
Source File: VehCard.tsx    From mojito_pdm with Creative Commons Attribution Share Alike 4.0 International 5 votes vote down vote up
VehCard: React.FC<Car> = ({
    name,
    brand,
    description,
    brandLogo,
    image,
    price,
    category,
    spawncode,
    trunkspace,
    performance
}) => {
    const theme = useTheme();
    const [open, setOpen] = useState(false)
    const testDrive = async () => {
        await fetchNui("test_drive", {vehicle: spawncode})
    }

    // Functions
    const handleOpen = () => setOpen(true)
    const handleClose = () => setOpen(false)

    return (
        <Card sx={{
            boxShadow: theme.shadows[3],
            margin: theme.spacing(2)
        }} variant="outlined">
            <CardHeader
                avatar={<img height={40} style={{maxWidth: 100, maxHeight: 40}} src={brandLogo} alt={brand}/>}
                title={name}
                subheader={category}
            />
            <CardMedia style={{height: "150px"}} image={image}/>
            <CardActions>
                <Tooltip TransitionComponent={Zoom} sx={{maxWidth: 120}} arrow title="Test drive this vehicle">
                    <Button
                        size="small"
                        variant={theme.palette.mode === "dark" ? "contained" : "outlined"}
                        color="primary"
                        startIcon={<DirectionsCarIcon/>}
                        onClick={testDrive}
                        disableElevation
                    >
                        TEST DRIVE
                    </Button>
                </Tooltip>
                <Tooltip TransitionComponent={Zoom} arrow sx={{maxWidth: 120}}
                         title="View more information about this vehicle">
                    <Button
                        size="small"
                        variant={theme.palette.mode === "dark" ? "contained" : "outlined"}
                        color="primary"
                        startIcon={<AssignmentIcon/>}
                        onClick={handleOpen}
                        disableElevation
                    >
                        MORE INFO
                    </Button>
                </Tooltip>
                <Modal
                    open={open}
                    onClose={handleClose}
                >
                    {<ModalBody
                        name={name}
                        brand={brand}
                        description={description}
                        price={price}
                        trunkspace={trunkspace}
                        setOpen={setOpen}
                        performance={performance}
                        spawncode={spawncode}
                    />}
                </Modal>
            </CardActions>
        </Card>
    )
}
Example #16
Source File: FeaturesSection.tsx    From fluttertemplates.dev with MIT License 5 votes vote down vote up
function SingleFeature(props: SingleFeatureProps) {
  const theme = useTheme();

  return (
    <Grid item xs={12} md={4} lg={3}>
      <Grid
        container
        direction="column"
        justifyContent="center"
        alignItems="center"
        spacing={1}
      >
        <Grid item>
          <Card
            elevation={0}
            style={{
              padding: "2rem",
              marginBottom: "0.5rem",
              background: `${theme.palette.secondary.main}20`,
              borderRadius: "1.5rem",
            }}
          >
            {props.icon}
          </Card>
        </Grid>
        <Grid item>
          <Typography
            variant="h4"
            style={{
              fontSize: "1.2rem",
              fontWeight: "bold",
            }}
          >
            {props.title}
          </Typography>
        </Grid>
        <Grid item>
          <Typography
            variant="caption"
            style={{
              fontSize: "1rem",
              display: "flex",
              flexGrow: 1,
              textAlign: "center",
            }}
          >
            {props.description}
          </Typography>
        </Grid>
      </Grid>
    </Grid>
  );
}
Example #17
Source File: ArtifactLevelSlider.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
export default function ArtifactLevelSlider({ levelLow, levelHigh, setLow, setHigh, setBoth, dark = false, disabled = false, showLevelText = false }: {
  levelLow: number,
  levelHigh: number,
  setLow: (low: number) => void,
  setHigh: (high: number) => void,
  setBoth: (low: number, high: number) => void,
  dark?: boolean,
  disabled?: boolean,
  showLevelText?: boolean,
}) {
  const [sliderLow, setsliderLow] = useState(levelLow)
  const [sliderHigh, setsliderHigh] = useState(levelHigh)
  const setSlider = useCallback(
    (e: unknown, value: number | number[]) => {
      if (typeof value == "number") throw new TypeError()
      const [l, h] = value
      setsliderLow(l)
      setsliderHigh(h)
    },
    [setsliderLow, setsliderHigh])
  useEffect(() => setsliderLow(levelLow), [setsliderLow, levelLow])

  useEffect(() => setsliderHigh(levelHigh), [setsliderHigh, levelHigh])
  return <Card sx={{ width: "100%", display: "flex", alignItems: "center", bgcolor: dark ? "contentDark.main" : "contentLight.main" }}>
    <CustomNumberInput
      value={sliderLow}
      onChange={val => setLow(clamp(val, 0, levelHigh))}
      sx={{ px: 1, pl: showLevelText ? 2 : undefined, width: showLevelText ? 100 : 50, }}
      inputProps={{ sx: { textAlign: "center" } }}
      startAdornment={showLevelText ? "Level: " : undefined}
      disabled={disabled}
    />
    <Slider sx={{ width: 100, flexGrow: 1, mx: 2 }}
      getAriaLabel={() => 'Arifact Level Range'}
      value={[sliderLow, sliderHigh]}
      onChange={setSlider}
      onChangeCommitted={(e, value) => setBoth(value[0], value[1])}
      valueLabelDisplay="auto"
      min={0} max={20} step={1} marks
      disabled={disabled}
    />
    <CustomNumberInput
      value={sliderHigh}
      onChange={val => setHigh(clamp(val, levelLow, 20))}
      sx={{ px: 1, width: 50, }}
      inputProps={{ sx: { textAlign: "center" } }}
      disabled={disabled}
    />
  </Card>
}
Example #18
Source File: TemplateCard.tsx    From fluttertemplates.dev with MIT License 5 votes vote down vote up
export default function TemplateCard(props: TemplateCardProps) {
  return (
    <Grid item md={3} xs={12} sm={6}>
      <Link href={`/templates/${props.id}`} passHref>
        <a>
          <Card elevation={0}>
            <CardMedia
              image={props.frontmatter.image}
              title={props.frontmatter.title.toString()}
              style={{
                height: "9rem",
              }}
            />

            <Typography
              gutterBottom
              variant="h6"
              style={{
                fontSize: "1rem",
                margin: "10px",
              }}
            >
              {props.frontmatter.title}
            </Typography>

            {/* <div
                className="categories-list"
                style={{
                  marginLeft: "8px",
                }}
              >
                {props.frontmatter.categories.map((category) => (
                  <Chip
                    label={category}
                    style={{
                      marginRight: "4px",
                    }}
                  />
                ))}
              </div> */}
          </Card>
        </a>
      </Link>
    </Grid>
  );
}
Example #19
Source File: index.tsx    From ExpressLRS-Configurator with GNU General Public License v3.0 5 votes vote down vote up
SupportView: FunctionComponent = () => {
  return (
    <MainLayout>
      <Card>
        <CardTitle icon={<SettingsIcon />} title="Support" />
        <Divider />
        <CardContent sx={styles.listContainer}>
          <p>Need help? Confused? Join the Community!</p>
          <ul className="linksList">
            <li>
              <Button
                target="_blank"
                variant="contained"
                rel="noreferrer noreferrer"
                href="https://www.expresslrs.org/"
              >
                ExpressLRS Documentation
              </Button>
            </li>
            <li>
              <Button
                target="_blank"
                variant="contained"
                rel="noreferrer noreferrer"
                href="https://discord.gg/dS6ReFY"
              >
                Discord Chat
              </Button>
            </li>
            <li>
              <Button
                target="_blank"
                variant="contained"
                rel="noreferrer noreferrer"
                href="https://www.facebook.com/groups/636441730280366"
              >
                Facebook Group
              </Button>
            </li>
          </ul>
        </CardContent>
        <Divider />
        <CardTitle icon={<SettingsIcon />} title="Troubleshooting" />
        <Divider />
        <CardContent>
          <ClearPlatformioDependencies />
          <ClearFirmwareFiles />
        </CardContent>
        <Divider />
        <CardTitle icon={<SettingsIcon />} title="Legal disclaimer" />
        <Divider />
        <CardContent>
          <p>
            The use and operation of this type of device may require a license,
            and some countries may forbid its use. It is entirely up to the end
            user to ensure compliance with local regulations. This is
            experimental software / hardware and there is no guarantee of
            stability or reliability. USE AT YOUR OWN RISK.
          </p>
        </CardContent>
      </Card>
    </MainLayout>
  );
}
Example #20
Source File: Dashboard.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Players: React.FC<{ players?: CurrentStatus['players'] }> = React.memo(({ players }) => {
  const his = useHistory()
  const plugin = usePlugin()
  const globalData = useGlobalData()
  const [page, setPage] = useState(1)
  const [id, update] = useState(0)
  return <Card>
    <CardHeader title={lang.dashboard.onlinePlayers} />
    <Divider />
    <CardContent>
      {players?.length === 0
        ? <Empty />
        : <>
        <List sx={{ paddingTop: 0 }}>
          {players
            ? players.slice((page - 1) * 8, page * 8).map(p => {
              const name = typeof p === 'string' ? p : p.name
              return <Tooltip key={name} title={'IP: ' + ((p as any).ip || lang.unknown)}>
                <ListItem
                  secondaryAction={<>
                    <IconButton
                      edge='end'
                      size='small'
                      onClick={() => dialog(lang.dashboard.confirmKick(<span className='bold'>{name}</span>), lang.reason)
                        .then(it => it != null && plugin.emit('dashboard:kick', (res: boolean) => {
                          action(res)
                          if (!players) return
                          players.splice(players.indexOf(it!), 1)
                          update(id + 1)
                        }, name, it || null))
                      }
                    ><ExitToApp /></IconButton>
                    <IconButton edge='end' onClick={() => his.push('/NekoMaid/playerList/' + name)} size='small'><MoreHoriz /></IconButton>
                  </>
                  }
                >
                  <ListItemAvatar>
                    <Avatar
                      src={getSkin(globalData, name, true)}
                      imgProps={{ crossOrigin: 'anonymous', onClick () { his.push('/NekoMaid/playerList/' + name) }, style: { width: 40, height: 40 } }}
                      sx={{ cursor: 'pointer' }}
                      variant='rounded'
                    />
                  </ListItemAvatar>
                  <ListItemText primary={name} />
                </ListItem>
              </Tooltip>
            })
            : <LoadingList />
          }
        </List>
        {players && <Pagination
          page={page}
          onChange={(_, it) => setPage(it)}
          count={Math.max(Math.ceil(players.length / 8), 1)}
          sx={{ display: 'flex', justifyContent: 'flex-end' }}
        />}
      </>}
    </CardContent>
  </Card>
})
Example #21
Source File: Filter.tsx    From Cromwell with MIT License 5 votes vote down vote up
private getFilterItem = (props: {
    title: string;
    key: string;
    content: JSX.Element;
  }) => {
    const { collapsedItems } = this.state;
    if (collapsedItems[props.key] === undefined) {
      collapsedItems[props.key] = this.collapsedByDefault;
    }
    const isExpanded = !collapsedItems[props.key];

    return (
      <Card className="productFilter_card">
        <div className="productFilter_headerWrapper"
          onClick={() => this.setState(prev => ({
            collapsedItems: {
              ...prev.collapsedItems,
              [props.key]: !prev.collapsedItems[props.key]
            }
          }))}
        >
          <Typography gutterBottom style={{
            fontSize: '14px',
            margin: '0 0 0 15px'
          }}>{props.title}</Typography>
          <IconButton
            className={clsx('productFilter_expand', {
              'productFilter_expandOpen': isExpanded,
            })}
            aria-expanded={isExpanded}
            aria-label={`Toggle ${props.title} filter visibility`}
          >
            <ExpandMoreIcon />
          </IconButton>
        </div>
        <Collapse in={isExpanded} timeout="auto" unmountOnExit>
          {props.content}
        </Collapse>
      </Card >
    )
  }
Example #22
Source File: index.tsx    From ExpressLRS-Configurator with GNU General Public License v3.0 4 votes vote down vote up
SerialMonitorView: FunctionComponent = () => {
  const [viewState, setViewState] = useState<ViewState>(
    ViewState.ConnectionConfig
  );

  /*
  We batch log events in order to save React.js state updates and rendering performance.
 */
  const [logs, setLogs] = useState<string>('');
  const logsRef = useRef<string[]>([]);
  const eventsBatcherRef = useRef<EventsBatcher<string> | null>(null);
  useEffect(() => {
    eventsBatcherRef.current = new EventsBatcher<string>(200);
    eventsBatcherRef.current.onBatch((newLogs) => {
      const newLogsList = [...logsRef.current, ...newLogs];
      logsRef.current = newLogsList;
      setLogs(newLogsList.join(''));
    });
  }, []);
  useSerialMonitorLogsSubscription({
    fetchPolicy: 'network-only',
    onSubscriptionData: (options) => {
      const args = options.subscriptionData.data?.serialMonitorLogs.data;
      if (args !== undefined && eventsBatcherRef.current !== null) {
        eventsBatcherRef.current.enqueue(args);
      }
    },
  });

  useSerialMonitorEventsSubscription({
    onSubscriptionData: (options) => {
      const args = options.subscriptionData.data?.serialMonitorEvents;
      if (args !== undefined) {
        if (args.type === SerialMonitorEventType.Disconnected) {
          setViewState(ViewState.ConnectionConfig);
        }
      }
    },
  });

  const [serialDevice, setSerialDevice] = useState<string | null>(null);
  const [baudRate, setBaudRate] = useState<number>(420000);
  const [
    connectToSerialDeviceMutation,
    { loading: connectInProgress, data: response, error: connectError },
  ] = useConnectToSerialDeviceMutation();
  const onConnect = (newSerialDevice: string | null, newBaudRate: number) => {
    setSerialDevice(newSerialDevice);
    setBaudRate(newBaudRate);
    setLogs('');
    logsRef.current = [];
    connectToSerialDeviceMutation({
      variables: {
        input: {
          port: newSerialDevice,
          baudRate: newBaudRate,
        },
      },
    })
      .then((resp) => {
        if (resp.data?.connectToSerialDevice.success) {
          setViewState(ViewState.LogsStream);
        }
      })
      .catch(() => {
        setViewState(ViewState.ConnectionConfig);
      });
  };

  const [
    disconnectFromSerialDeviceMutation,
    { loading: disconnectInProgress, error: disconnectError },
  ] = useDisconnectFromSerialDeviceMutation();
  const onDisconnect = () => {
    disconnectFromSerialDeviceMutation()
      .then((data) => {
        if (data.data?.disconnectFromSerialDevice.success) {
          setViewState(ViewState.ConnectionConfig);
        }
      })
      .catch(() => {});
  };
  return (
    <MainLayout>
      <Card>
        <CardTitle icon={<DvrIcon />} title="Serial Monitor" />
        <Divider />
        <CardContent>
          {viewState === ViewState.ConnectionConfig && (
            <>
              <SerialConnectionForm
                serialDevice={serialDevice}
                baudRate={baudRate}
                onConnect={onConnect}
              />
              <Loader loading={connectInProgress} />
              {response && !response.connectToSerialDevice.success && (
                <ShowAlerts
                  severity="error"
                  messages={response.connectToSerialDevice.message}
                />
              )}
              <ShowAlerts severity="error" messages={connectError} />
            </>
          )}
          <ShowAlerts severity="error" messages={disconnectError} />
          {viewState === ViewState.LogsStream && (
            <>
              <Button
                onClick={onDisconnect}
                color="secondary"
                size="large"
                variant="contained"
                sx={styles.disconnectButton}
              >
                Disconnect
              </Button>
              <Loader loading={disconnectInProgress} />
              <Logs data={logs} />
            </>
          )}
        </CardContent>
      </Card>
    </MainLayout>
  );
}
Example #23
Source File: Worlds.tsx    From NekoMaid with MIT License 4 votes vote down vote up
Worlds: React.FC = () => {
  const plugin = usePlugin()
  const globalData = useGlobalData()
  const [worlds, setWorlds] = useState<World[]>([])
  const [selected, setSelected] = useState('')
  const [open, setOpen] = useState(false)
  const update = () => plugin.emit('worlds:fetch', (data: World[]) => {
    setWorlds(data)
    if (data.length) setSelected(old => data.some(it => it.id === old) ? old : '')
  })
  useEffect(() => {
    const offUpdate = plugin.on('worlds:update', update)
    update()
    return () => { offUpdate() }
  }, [])
  const sw = worlds.find(it => it.id === selected)
  const getSwitch = (name: string, configId = name) => sw
    ? <ListItem
      secondaryAction={<Switch disabled={!globalData.hasMultiverse} checked={(sw as any)[name]}
      onChange={e => {
        plugin.emit('worlds:set', sw.id, configId, e.target.checked.toString())
        success()
      }}
    />}><ListItemText primary={(lang.worlds as any)[name]} /></ListItem>
    : null

  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3}>
        <Grid item lg={8} md={12} xl={9} xs={12}>
        <Card>
          <CardHeader title={lang.worlds.title} />
          <Divider />
          <Box sx={{ position: 'relative' }}>
            <TableContainer>
              <Table>
                <TableHead>
                  <TableRow>
                    <TableCell padding='checkbox' />
                    <TableCell>{lang.worlds.name}</TableCell>
                    {globalData.hasMultiverse && <TableCell>{lang.worlds.alias}</TableCell>}
                    <TableCell>{lang.worlds.players}</TableCell>
                    <TableCell>{lang.worlds.chunks}</TableCell>
                    <TableCell>{lang.worlds.entities}</TableCell>
                    <TableCell>{lang.worlds.tiles}</TableCell>
                    <TableCell>{lang.worlds.time}</TableCell>
                    <TableCell>{lang.worlds.weather}</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  {worlds.map(it => <TableRow key={it.id}>
                    <TableCell padding='checkbox'><Checkbox checked={selected === it.id} onClick={() => setSelected(it.id)} /></TableCell>
                    <TableCell><Tooltip title={it.id}><span>{it.name}</span></Tooltip></TableCell>
                    {globalData.hasMultiverse && <TableCell>{it.alias}
                      <IconButton size='small' onClick={() => dialog(lang.inputValue, lang.worlds.alias).then(res => {
                        if (res == null) return
                        plugin.emit('worlds:set', it.id, 'alias', res)
                        success()
                      })}><Edit fontSize='small' /></IconButton>
                      </TableCell>}
                    <TableCell>{it.players}</TableCell>
                    <TableCell>{it.chunks}</TableCell>
                    <TableCell>{it.entities}</TableCell>
                    <TableCell>{it.tiles}</TableCell>
                    <TableCell><Countdown time={it.time} max={24000} interval={50} /></TableCell>
                    <TableCell><IconButton size='small' onClick={() => {
                      plugin.emit('worlds:weather', it.id)
                      success()
                    }}>
                      {React.createElement((it.weather === 1 ? WeatherRainy : it.weather === 2 ? WeatherLightningRainy : WbSunny) as any)}
                    </IconButton></TableCell>
                  </TableRow>)}
                </TableBody>
              </Table>
            </TableContainer>
          </Box>
        </Card>
        </Grid>
        <Grid item lg={4} md={6} xl={3} xs={12}>
          <Card>
            <CardHeader
              title={lang.operations}
              sx={{ position: 'relative' }}
              action={<Tooltip title={lang.worlds.save} placement='left'>
                <IconButton
                  size='small'
                  onClick={() => {
                    if (!sw) return
                    plugin.emit('worlds:save', sw.id)
                    success()
                  }}
                  sx={cardActionStyles}
                ><Save /></IconButton>
              </Tooltip>}
            />
            <Divider />
            <Box sx={{ position: 'relative' }}>
              {sw
                ? <List sx={{ width: '100%' }} component='nav'>
                  <ListItem secondaryAction={<ToggleButtonGroup
                    exclusive
                    color='primary'
                    size='small'
                    value={sw.difficulty}
                    onChange={(_, value) => {
                      plugin.emit('worlds:difficulty', sw.id, value)
                      success()
                    }}
                  >
                    {difficulties.map(it => <ToggleButton value={it.toUpperCase()} key={it}>{minecraft['options.difficulty.' + it]}</ToggleButton>)}
                  </ToggleButtonGroup>}><ListItemText primary={minecraft['options.difficulty']} /></ListItem>
                  <ListItem secondaryAction={<Switch checked={sw.pvp} onChange={e => {
                    plugin.emit('worlds:pvp', sw.id, e.target.checked)
                    success()
                  }} />}><ListItemText primary='PVP' /></ListItem>
                  {getSwitch('allowAnimals', 'spawning.animals.spawn')}
                  {getSwitch('allowMonsters', 'spawning.monsters.spawn')}
                  {globalData.hasMultiverse && <>
                    {getSwitch('allowFlight')}
                    {getSwitch('autoHeal')}
                    {getSwitch('hunger')}
                  </>}
                  <ListItem secondaryAction={globalData.canSetViewDistance
                    ? <IconButton
                      onClick={() => dialog({
                        content: lang.inputValue,
                        input: {
                          error: true,
                          type: 'number',
                          helperText: lang.invalidValue,
                          validator: (it: string) => /^\d+$/.test(it) && +it > 1 && +it < 33
                        }
                      }).then(res => {
                        if (!res) return
                        plugin.emit('worlds:viewDistance', sw.id, parseInt(res as any))
                        success()
                      })}
                    ><Edit /></IconButton>
                    : undefined}>
                    <ListItemText primary={lang.worlds.viewDistance + ': ' + sw.viewDistance} />
                  </ListItem>
                  <ListItem><ListItemText primary={minecraft['selectWorld.enterSeed']} secondary={sw.seed} /></ListItem>
                  <ListItemButton onClick={() => setOpen(!open)}>
                    <ListItemText primary={minecraft['selectWorld.gameRules']} />
                    {open ? <ExpandLess /> : <ExpandMore />}
                  </ListItemButton>
                  <Collapse in={open} timeout="auto" unmountOnExit>
                    <List component='div' dense disablePadding>
                      {sw.rules.map(([key, value]) => {
                        const isTrue = value === 'true'
                        const isBoolean = isTrue || value === 'false'
                        const isNumber = /^\d+$/.test(value)
                        return <ListItem
                          key={key}
                          sx={{ pl: 4 }}
                          secondaryAction={isBoolean
                            ? <Switch
                              checked={isTrue}
                              onChange={e => {
                                plugin.emit('worlds:rule', sw.id, key, e.target.checked.toString())
                                success()
                              }}
                            />
                            : <IconButton
                              onClick={() => dialog({
                                content: lang.inputValue,
                                input: isNumber
                                  ? {
                                      error: true,
                                      type: 'number',
                                      helperText: lang.invalidValue,
                                      validator: (it: string) => /^\d+$/.test(it)
                                    }
                                  : { }
                              }).then(res => {
                                if (res == null) return
                                plugin.emit('worlds:rule', sw.id, key, res)
                                success()
                              })}
                            ><Edit /></IconButton>}
                        >
                          <ListItemText primary={(minecraft['gamerule.' + key] || key) + (isBoolean ? '' : ': ' + value)} />
                        </ListItem>
                      })}
                    </List>
                  </Collapse>
                </List>
                : <CardContent><Empty /></CardContent>
              }
            </Box>
          </Card>
        </Grid>
      </Grid>
    </Container>
  </Box>
}
Example #24
Source File: SearchAll.tsx    From Tachidesk-WebUI with Mozilla Public License 2.0 4 votes vote down vote up
export default function SearchAll() {
    const [query] = useQueryParam('query', StringParam);
    const { setTitle, setAction } = useContext(NavbarContext);
    const [triggerUpdate, setTriggerUpdate] = useState<number>(2);
    const [mangas, setMangas] = useState<any>({});

    const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
    const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);

    const [sources, setSources] = useState<ISource[]>([]);
    const [fetched, setFetched] = useState<any>({});
    const [FetchedSources, setFetchedSources] = useState<any>({});

    const [lastPageNum, setLastPageNum] = useState<number>(1);

    const [ResetUI, setResetUI] = useState<number>(0);

    const limit = new PQueue({ concurrency: 5 });

    useEffect(() => {
        setTitle('Global Search');
        setAction(
            <>
                <AppbarSearch />
            </>
            ,
        );
    }, []);

    useEffect(() => {
        client.get('/api/v1/source/list')
            .then((response) => response.data)
            .then((data) => {
                setSources(data.sort((a: { displayName: string; }, b: { displayName: string; }) => {
                    if (a.displayName < b.displayName) { return -1; }
                    if (a.displayName > b.displayName) { return 1; }
                    return 0;
                })); setFetchedSources(true);
            });
    }, []);

    async function doIT(elem: any[]) {
        elem.map((ele) => limit.add(async () => {
            const response = await client.get(`/api/v1/source/${ele.id}/search?searchTerm=${query || ''}&pageNum=1`);
            const data = await response.data;
            const tmp = mangas;
            tmp[ele.id] = data.mangaList;
            setMangas(tmp);
            const tmp2 = fetched;
            tmp2[ele.id] = true;
            setFetched(tmp2);
            setResetUI(1);
        }));
    }

    useEffect(() => {
        if (triggerUpdate === 2) {
            return;
        }
        if (triggerUpdate === 0) {
            setTriggerUpdate(1);
            return;
        }
        setFetched({});
        setMangas({});
        // eslint-disable-next-line max-len
        doIT(sources.filter(({ lang }) => shownLangs.indexOf(lang) !== -1).filter((source) => showNsfw || !source.isNsfw));
    }, [triggerUpdate]);

    useEffect(() => {
        if (ResetUI === 1) {
            setResetUI(0);
        }
    }, [ResetUI]);

    useEffect(() => {
        if (query && FetchedSources) {
            const delayDebounceFn = setTimeout(() => {
                setTriggerUpdate(0);
            }, 1000);
            return () => clearTimeout(delayDebounceFn);
        }
        return () => {};
    }, [query, shownLangs, sources]);

    useEffect(() => {
        // make sure all of forcedDefaultLangs() exists in shownLangs
        sourceForcedDefaultLangs().forEach((forcedLang) => {
            let hasLang = false;
            shownLangs.forEach((lang) => {
                if (lang === forcedLang) hasLang = true;
            });
            if (!hasLang) {
                setShownLangs((shownLangsCopy) => {
                    shownLangsCopy.push(forcedLang);
                    return shownLangsCopy;
                });
            }
        });
    }, []);

    useEffect(() => {
        setTitle('Sources');
        setAction(
            <>
                <AppbarSearch
                    autoOpen
                />
                <LangSelect
                    shownLangs={shownLangs}
                    setShownLangs={setShownLangs}
                    allLangs={sourceToLangList(sources)}
                    forcedLangs={sourceForcedDefaultLangs()}
                />
            </>,
        );
    }, [shownLangs, sources]);

    const history = useHistory();

    const redirectTo = (e: any, to: string) => {
        history.push(to);

        // prevent parent tags from getting the event
        e.stopPropagation();
    };
    if (query) {
        return (
            <>
                {/* eslint-disable-next-line max-len */}
                {sources.filter(({ lang }) => shownLangs.indexOf(lang) !== -1).filter((source) => showNsfw || !source.isNsfw).sort((a, b) => {
                    const af = fetched[a.id];
                    const bf = fetched[b.id];
                    if (af && !bf) { return -1; }
                    if (!af && bf) { return 1; }
                    if (!af && !bf) { return 0; }

                    const al = mangas[a.id].length === 0;
                    const bl = mangas[b.id].length === 0;
                    if (al && !bl) { return 1; }
                    if (bl && !al) { return -1; }
                    return 0;
                }).map(({ lang, id, displayName }) => (
                    (
                        <>
                            <Card
                                sx={{
                                    margin: '10px',
                                    '&:hover': {
                                        backgroundColor: 'action.hover',
                                        transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
                                    },
                                    '&:active': {
                                        backgroundColor: 'action.selected',
                                        transition: 'background-color 100ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
                                    },
                                }}
                                onClick={(e) => redirectTo(e, `/sources/${id}/popular/?R&query=${query}`)}
                            >
                                <h1
                                    key={lang}
                                    style={{ margin: '25px 0px 0px 25px' }}
                                >
                                    {displayName}
                                </h1>
                                <p
                                    style={{ margin: '0px 0px 25px 25px' }}
                                >
                                    {langCodeToName(lang)}
                                </p>
                            </Card>
                            <MangaGrid
                                mangas={mangas[id] || []}
                                isLoading={!fetched[id]}
                                hasNextPage={false}
                                lastPageNum={lastPageNum}
                                setLastPageNum={setLastPageNum}
                                horisontal
                                noFaces
                                message={fetched[id] ? 'No manga was found!' : undefined}
                            />
                        </>
                    )
                ))}

            </>
        );
    }
    return (<></>);
}
Example #25
Source File: index.tsx    From ExpressLRS-Configurator with GNU General Public License v3.0 4 votes vote down vote up
ConfiguratorView: FunctionComponent<ConfiguratorViewProps> = (props) => {
  const {
    gitRepository,
    selectedDevice,
    networkDevices,
    onDeviceChange,
    deviceType,
  } = props;

  const [viewState, setViewState] = useState<ViewState>(
    ViewState.Configuration
  );

  const { setAppStatus } = useAppState();

  const [progressNotifications, setProgressNotifications] = useState<
    BuildProgressNotification[]
  >([]);
  const progressNotificationsRef = useRef<BuildProgressNotification[]>([]);
  const [
    lastProgressNotification,
    setLastProgressNotification,
  ] = useState<BuildProgressNotification | null>(null);

  useBuildProgressNotificationsSubscription({
    onSubscriptionData: (options) => {
      const args = options.subscriptionData.data?.buildProgressNotifications;
      if (args !== undefined) {
        const newNotificationsList = [
          ...progressNotificationsRef.current,
          args,
        ];
        progressNotificationsRef.current = newNotificationsList;
        setProgressNotifications(newNotificationsList);
        setLastProgressNotification(args);
      }
    },
  });

  /*
    We batch log events in order to save React.js state updates and rendering performance.
   */
  const [logs, setLogs] = useState<string>('');
  const logsRef = useRef<string[]>([]);
  const eventsBatcherRef = useRef<EventsBatcher<string> | null>(null);
  useEffect(() => {
    eventsBatcherRef.current = new EventsBatcher<string>(200);
    eventsBatcherRef.current.onBatch((newLogs) => {
      const newLogsList = [...logsRef.current, ...newLogs];
      logsRef.current = newLogsList;
      setLogs(newLogsList.join(''));
    });
  }, []);
  useBuildLogUpdatesSubscription({
    fetchPolicy: 'network-only',
    onSubscriptionData: (options) => {
      const args = options.subscriptionData.data?.buildLogUpdates.data;
      if (args !== undefined && eventsBatcherRef.current !== null) {
        eventsBatcherRef.current.enqueue(args);
      }
    },
  });

  const [
    firmwareVersionData,
    setFirmwareVersionData,
  ] = useState<FirmwareVersionDataInput | null>(null);
  const [firmwareVersionErrors, setFirmwareVersionErrors] = useState<Error[]>(
    []
  );
  const onFirmwareVersionData = useCallback(
    (data: FirmwareVersionDataInput) => {
      setFirmwareVersionErrors([]);
      setFirmwareVersionData(data);
    },
    []
  );

  const [deviceTarget, setDeviceTarget] = useState<Target | null>(null);
  const [deviceTargetErrors, setDeviceTargetErrors] = useState<Error[]>([]);

  const onDeviceTarget = useCallback(
    (data: Target | null) => {
      setDeviceTargetErrors([]);
      setDeviceTarget(data);
      // if target was manually changed, set selected device to null
      onDeviceChange(null);
    },
    [onDeviceChange]
  );

  const [deviceTargets, setDeviceTargets] = useState<Device[] | null>(null);

  const [
    fetchDeviceTargets,
    {
      loading: loadingTargets,
      data: targetsResponse,
      error: targetsResponseError,
    },
  ] = useAvailableFirmwareTargetsLazyQuery({
    fetchPolicy: 'network-only',
  });

  const [
    fetchLuaScript,
    { data: luaScriptResponse, error: luaScriptResponseError },
  ] = useLuaScriptLazyQuery();

  const device = useMemo(() => {
    return deviceTargets?.find((d) => {
      return d.targets.find((target) => target.id === deviceTarget?.id);
    });
  }, [deviceTarget, deviceTargets]);

  useEffect(() => {
    if (
      firmwareVersionData === null ||
      validateFirmwareVersionData(firmwareVersionData).length > 0
    ) {
      setDeviceTargets(null);
    } else {
      fetchDeviceTargets({
        variables: {
          source: firmwareVersionData.source as FirmwareSource,
          gitBranch: firmwareVersionData.gitBranch!,
          gitTag: firmwareVersionData.gitTag!,
          gitCommit: firmwareVersionData.gitCommit!,
          localPath: firmwareVersionData.localPath!,
          gitPullRequest: firmwareVersionData.gitPullRequest,
          gitRepository: {
            url: gitRepository.url,
            owner: gitRepository.owner,
            repositoryName: gitRepository.repositoryName,
            rawRepoUrl: gitRepository.rawRepoUrl,
            srcFolder: gitRepository.srcFolder,
          },
        },
      });
    }
  }, [gitRepository, firmwareVersionData, fetchDeviceTargets]);

  useEffect(() => {
    if (targetsResponse?.availableFirmwareTargets) {
      setDeviceTargets([...targetsResponse.availableFirmwareTargets]);
    } else {
      setDeviceTargets(null);
    }
  }, [targetsResponse]);

  const [
    deviceOptionsFormData,
    setDeviceOptionsFormData,
  ] = useState<DeviceOptionsFormData>({
    userDefinesTxt: '',
    userDefinesMode: UserDefinesMode.UserInterface,
    userDefineOptions: [],
  });

  const handleDeviceOptionsResponse = async (
    deviceOptionsResponse: TargetDeviceOptionsQuery
  ) => {
    const storage = new ApplicationStorage();
    const deviceName = device?.name || null;
    const userDefineOptions = await mergeWithDeviceOptionsFromStorage(
      storage,
      deviceName,
      {
        ...deviceOptionsFormData,
        userDefineOptions: [...deviceOptionsResponse.targetDeviceOptions],
      }
    );

    // if a network device is selected, merge in its options
    if (selectedDevice && networkDevices.has(selectedDevice)) {
      const networkDevice = networkDevices.get(selectedDevice);
      userDefineOptions.userDefineOptions = userDefineOptions.userDefineOptions.map(
        (userDefineOption) => {
          const networkDeviceOption = networkDevice?.options.find(
            (item) => item.key === userDefineOption.key
          );

          const newUserDefineOption = { ...userDefineOption };
          if (networkDeviceOption) {
            newUserDefineOption.enabled = networkDeviceOption.enabled;
            newUserDefineOption.value = networkDeviceOption.value;
          }
          return newUserDefineOption;
        }
      );
    }

    setDeviceOptionsFormData(userDefineOptions);
  };
  const [
    fetchOptions,
    {
      loading: loadingOptions,
      data: deviceOptionsResponse,
      error: deviceOptionsResponseError,
    },
  ] = useTargetDeviceOptionsLazyQuery({
    fetchPolicy: 'network-only',
    onCompleted: (data) => {
      handleDeviceOptionsResponse(data).catch((err) => {
        console.error('failed to handle device options response', err);
      });
    },
  });

  useEffect(() => {
    if (
      deviceTarget === null ||
      firmwareVersionData === null ||
      validateFirmwareVersionData(firmwareVersionData).length > 0
    ) {
      setDeviceOptionsFormData({
        userDefinesTxt: '',
        userDefinesMode: UserDefinesMode.UserInterface,
        userDefineOptions: [],
      });
    } else {
      fetchOptions({
        variables: {
          target: deviceTarget.name,
          source: firmwareVersionData.source as FirmwareSource,
          gitBranch: firmwareVersionData.gitBranch!,
          gitTag: firmwareVersionData.gitTag!,
          gitCommit: firmwareVersionData.gitCommit!,
          localPath: firmwareVersionData.localPath!,
          gitPullRequest: firmwareVersionData.gitPullRequest,
          gitRepository: {
            url: gitRepository.url,
            owner: gitRepository.owner,
            repositoryName: gitRepository.repositoryName,
            rawRepoUrl: gitRepository.rawRepoUrl,
            srcFolder: gitRepository.srcFolder,
          },
        },
      });
    }
  }, [deviceTarget, firmwareVersionData, gitRepository, fetchOptions]);

  const onResetToDefaults = () => {
    const handleReset = async () => {
      if (deviceOptionsResponse === undefined || deviceTarget === null) {
        // eslint-disable-next-line no-alert
        alert(`deviceOptionsResponse is undefined`);
        return;
      }
      const deviceName = device?.name || null;
      if (deviceName) {
        const storage = new ApplicationStorage();
        await storage.removeDeviceOptions(deviceName);

        const userDefineOptions = await mergeWithDeviceOptionsFromStorage(
          storage,
          deviceName,
          {
            ...deviceOptionsFormData,
            userDefineOptions: [...deviceOptionsResponse.targetDeviceOptions],
          }
        );
        setDeviceOptionsFormData(userDefineOptions);
      }
    };
    handleReset().catch((err) => {
      console.error(`failed to reset device options form data: ${err}`);
    });
  };

  const onUserDefines = useCallback(
    (data: DeviceOptionsFormData) => {
      setDeviceOptionsFormData(data);
      if (deviceTarget !== null) {
        const storage = new ApplicationStorage();
        const deviceName = device?.name;
        if (deviceName) {
          persistDeviceOptions(storage, deviceName, data).catch((err) => {
            console.error(`failed to persist user defines: ${err}`);
          });
        }
      }
    },
    [deviceTarget, deviceTargets]
  );

  const [
    buildFlashFirmwareMutation,
    {
      loading: buildInProgress,
      data: response,
      error: buildFlashErrorResponse,
    },
  ] = useBuildFlashFirmwareMutation();

  useEffect(() => {
    const arg = response?.buildFlashFirmware?.firmwareBinPath;
    if (arg !== undefined && arg !== null && arg?.length > 0) {
      const body: OpenFileLocationRequestBody = {
        path: arg,
      };
      ipcRenderer.send(IpcRequest.OpenFileLocation, body);
    }
  }, [response]);

  const isTX = useMemo(() => {
    if (deviceTarget) {
      return deviceTarget.name?.indexOf('_TX_') > -1;
    }
    return false;
  }, [deviceTarget]);

  const hasLuaScript = useMemo(() => {
    return deviceType === DeviceType.ExpressLRS && isTX;
  }, [deviceType, isTX]);

  useEffect(() => {
    if (firmwareVersionData && isTX && hasLuaScript) {
      fetchLuaScript({
        variables: {
          source: firmwareVersionData.source as FirmwareSource,
          gitBranch: firmwareVersionData.gitBranch!,
          gitTag: firmwareVersionData.gitTag!,
          gitCommit: firmwareVersionData.gitCommit!,
          localPath: firmwareVersionData.localPath!,
          gitPullRequest: firmwareVersionData.gitPullRequest,
          gitRepository: {
            url: gitRepository.url,
            owner: gitRepository.owner,
            repositoryName: gitRepository.repositoryName,
            rawRepoUrl: gitRepository.rawRepoUrl,
            srcFolder: gitRepository.srcFolder,
          },
        },
      });
    }
  }, [gitRepository, firmwareVersionData, fetchLuaScript, isTX, hasLuaScript]);

  /*
    Display Electron.js confirmation dialog if user wants to shutdown the app
    when build is in progress.
   */
  useEffect(() => {
    const body: UpdateBuildStatusRequestBody = {
      buildInProgress,
    };
    ipcRenderer.send(IpcRequest.UpdateBuildStatus, body);
  }, [buildInProgress]);

  const [serialDevice, setSerialDevice] = useState<string | null>(null);
  const onSerialDevice = (newSerialDevice: string | null) => {
    setSerialDevice(newSerialDevice);
  };

  const [wifiDevice, setWifiDevice] = useState<string | null>(null);
  const onWifiDevice = useCallback((newWifiDevice: string | null) => {
    setWifiDevice(newWifiDevice);
  }, []);

  const [serialPortRequired, setSerialPortRequired] = useState<boolean>(false);
  const [wifiDeviceRequired, setWifiDeviceRequired] = useState<boolean>(false);

  useEffect(() => {
    if (
      deviceTarget &&
      (deviceTarget.flashingMethod === FlashingMethod.BetaflightPassthrough ||
        deviceTarget.flashingMethod === FlashingMethod.UART)
    ) {
      setSerialPortRequired(true);
    } else {
      setSerialPortRequired(false);
    }

    if (deviceTarget && deviceTarget.flashingMethod === FlashingMethod.WIFI) {
      setWifiDeviceRequired(true);
    } else {
      setWifiDeviceRequired(false);
    }
  }, [deviceTarget, deviceTarget, deviceTargets]);

  const [
    deviceOptionsValidationErrors,
    setDeviceOptionsValidationErrors,
  ] = useState<Error[] | null>(null);

  const reset = () => {
    logsRef.current = [];
    progressNotificationsRef.current = [];
    setLogs('');
    setFirmwareVersionErrors([]);
    setDeviceTargetErrors([]);
    setDeviceOptionsValidationErrors([]);

    setProgressNotifications([]);
    setLastProgressNotification(null);
  };

  const onBack = () => {
    reset();
    setViewState(ViewState.Configuration);
    setAppStatus(AppStatus.Interactive);
  };

  const getAbbreviatedDeviceName = (item: Device) => {
    return item.abbreviatedName?.slice(0, 16) ?? item.name?.slice(0, 16);
  };

  const [currentJobType, setCurrentJobType] = useState<BuildJobType>(
    BuildJobType.Build
  );
  const sendJob = (type: BuildJobType) => {
    reset();
    setCurrentJobType(type);

    // Validate firmware source
    if (firmwareVersionData === null) {
      setFirmwareVersionErrors([new Error('Please select firmware source')]);
      return;
    }
    const sourceErrors = validateFirmwareVersionData(firmwareVersionData);
    if (sourceErrors.length > 0) {
      setFirmwareVersionErrors(sourceErrors);
      return;
    }

    // Validate device target
    if (deviceTarget === null) {
      setDeviceTargetErrors([new Error('Please select a device target')]);
      return;
    }

    // Validate device options
    if (deviceOptionsFormData === null) {
      setDeviceTargetErrors([
        new Error('Please configure your device options'),
      ]);
      return;
    }

    switch (deviceOptionsFormData.userDefinesMode) {
      case UserDefinesMode.Manual:
        break;
      case UserDefinesMode.UserInterface:
        const errs = new UserDefinesValidator().validate(
          deviceOptionsFormData.userDefineOptions
        );
        if (errs.length > 0) {
          setDeviceOptionsValidationErrors(errs);
          return;
        }
        break;
      default:
        break;
    }

    let uploadPort: string | undefined;

    if (serialPortRequired && serialDevice != null) {
      uploadPort = serialDevice;
    } else if (wifiDeviceRequired && wifiDevice !== null) {
      uploadPort = wifiDevice;
    }

    const userDefines = deviceOptionsFormData.userDefineOptions.map((item) => ({
      key: item.key,
      value: item.value,
      enabled: item.enabled,
      enumValues: item.enumValues,
      type: item.type,
    }));

    if (device?.parent && device?.name) {
      const deviceName = getAbbreviatedDeviceName(device);
      // add the user define for the device name
      userDefines.push({
        key: UserDefineKey.DEVICE_NAME,
        value: deviceName,
        enabled: true,
        enumValues: null,
        type: UserDefineKind.Text,
      });
    }

    const input: BuildFlashFirmwareInput = {
      type,
      firmware: firmwareVersionData,
      target: deviceTarget.name,
      userDefinesTxt: deviceOptionsFormData.userDefinesTxt,
      userDefinesMode: deviceOptionsFormData.userDefinesMode,
      userDefines,
      serialDevice: uploadPort,
    };
    buildFlashFirmwareMutation({
      variables: {
        input,
        gitRepository: {
          url: gitRepository.url,
          owner: gitRepository.owner,
          repositoryName: gitRepository.repositoryName,
          rawRepoUrl: gitRepository.rawRepoUrl,
          srcFolder: gitRepository.srcFolder,
        },
      },
    });
    setViewState(ViewState.Compiling);
    setAppStatus(AppStatus.Busy);
  };

  useEffect(() => {
    if (
      !buildInProgress &&
      response?.buildFlashFirmware?.success !== undefined
    ) {
      window.scrollTo(0, document.body.scrollHeight);
    }
  }, [buildInProgress, response]);

  const onBuild = () => sendJob(BuildJobType.Build);
  const onBuildAndFlash = () => sendJob(BuildJobType.BuildAndFlash);
  const onForceFlash = () => sendJob(BuildJobType.ForceFlash);

  const deviceTargetRef = useRef<HTMLDivElement | null>(null);
  const deviceOptionsRef = useRef<HTMLDivElement | null>(null);

  const [
    deviceSelectErrorDialogOpen,
    setDeviceSelectErrorDialogOpen,
  ] = useState<boolean>(false);

  const handleSelectedDeviceChange = useCallback(
    (deviceName: string) => {
      const dnsDevice = networkDevices.get(deviceName);
      if (dnsDevice) {
        const dnsDeviceName = dnsDevice.deviceName?.toUpperCase();
        const dnsDeviceTarget = dnsDevice.target.toUpperCase();

        let deviceMatches: Device[] | undefined = [];

        // try to find the device by the deviceName
        deviceMatches = deviceTargets?.filter((item) => {
          return getAbbreviatedDeviceName(item).toUpperCase() === dnsDeviceName;
        });

        // if no matches found by deviceName, then use the target
        if (
          deviceMatches?.length === 0 &&
          dnsDeviceTarget.trim().length !== 0
        ) {
          deviceMatches = deviceTargets?.filter((item) => {
            // only match on a device that doesn't have a parent, which means it
            // is not an alias of another device
            return (
              !item.parent &&
              item.targets.find((target) => {
                const baseTargetName = target.name.split('_via_')[0];
                return baseTargetName.toUpperCase() === dnsDeviceTarget;
              })
            );
          });
        }

        // if no device is found that matches the target
        if (!deviceMatches || deviceMatches.length === 0) {
          console.error(
            `no device matches found for target ${dnsDeviceTarget}!`
          );
          setDeviceSelectErrorDialogOpen(true);
          return;
        }

        // if multiple device matches are found, then don't select any of them
        // we do not know which one is correct and do not want to pick the wrong device.
        if (deviceMatches.length > 1) {
          console.error(
            `multiple device matches found for target ${dnsDeviceTarget}!`
          );
          setDeviceSelectErrorDialogOpen(true);
          return;
        }

        const deviceMatch = deviceMatches[0];

        const dTarget =
          deviceMatch?.targets.find((target) => {
            return target.flashingMethod === FlashingMethod.WIFI;
          }) ||
          deviceMatch?.targets[0] ||
          null;

        if (dTarget !== deviceTarget) {
          setDeviceTarget(dTarget);
          deviceTargetRef?.current?.scrollIntoView({ behavior: 'smooth' });
        }

        setWifiDevice(dnsDevice.ip);
      }
    },
    [deviceTarget, deviceTargets, networkDevices]
  );

  useEffect(() => {
    if (selectedDevice) {
      handleSelectedDeviceChange(selectedDevice);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedDevice]);

  const luaDownloadButton = () => {
    if (
      hasLuaScript &&
      luaScriptResponse &&
      luaScriptResponse.luaScript.fileLocation &&
      luaScriptResponse.luaScript.fileLocation.length > 0
    ) {
      return (
        <Button
          sx={styles.button}
          color="primary"
          size="large"
          variant="contained"
          href={luaScriptResponse?.luaScript.fileLocation ?? ''}
          download
        >
          Download LUA script
        </Button>
      );
    }
    return null;
  };

  const handleDeviceSelectErrorDialogClose = useCallback(() => {
    setDeviceSelectErrorDialogOpen(false);
  }, []);

  const saveBuildLogToFile = useCallback(async () => {
    const saveFileRequestBody: SaveFileRequestBody = {
      data: logs,
      defaultPath: `ExpressLRSBuildLog_${new Date()
        .toISOString()
        .replace(/[^0-9]/gi, '')}.txt`,
    };

    const result: SaveFileResponseBody = await ipcRenderer.invoke(
      IpcRequest.SaveFile,
      saveFileRequestBody
    );

    if (result.success) {
      const openFileLocationRequestBody: OpenFileLocationRequestBody = {
        path: result.path,
      };
      ipcRenderer.send(
        IpcRequest.OpenFileLocation,
        openFileLocationRequestBody
      );
    }
  }, [logs]);

  return (
    <MainLayout>
      {viewState === ViewState.Configuration && (
        <>
          <Card>
            <CardTitle icon={<SettingsIcon />} title="Firmware version" />
            <Divider />
            <CardContent>
              <FirmwareVersionForm
                onChange={onFirmwareVersionData}
                data={firmwareVersionData}
                gitRepository={gitRepository}
              />
              <ShowAlerts severity="error" messages={firmwareVersionErrors} />
            </CardContent>
            <Divider />

            <CardTitle icon={<SettingsIcon />} title="Target" />
            <Divider />
            <CardContent ref={deviceTargetRef}>
              {firmwareVersionData === null ||
                (validateFirmwareVersionData(firmwareVersionData).length >
                  0 && (
                  <Alert severity="info">
                    <AlertTitle>Notice</AlertTitle>
                    Please select a firmware version first
                  </Alert>
                ))}
              {!loadingTargets && !targetsResponseError && (
                <DeviceTargetForm
                  currentTarget={deviceTarget}
                  onChange={onDeviceTarget}
                  firmwareVersionData={firmwareVersionData}
                  deviceOptions={deviceTargets}
                />
              )}
              <Loader loading={loadingTargets} />
              {luaDownloadButton()}
              {hasLuaScript && (
                <ShowAlerts
                  severity="error"
                  messages={luaScriptResponseError}
                />
              )}
              <ShowAlerts severity="error" messages={targetsResponseError} />
              <ShowAlerts severity="error" messages={deviceTargetErrors} />
            </CardContent>
            <Divider />

            <CardTitle
              icon={<SettingsIcon />}
              title={
                <div ref={deviceOptionsRef}>
                  Device options{' '}
                  {deviceOptionsFormData.userDefinesMode ===
                    UserDefinesMode.UserInterface &&
                    deviceTarget !== null &&
                    !loadingOptions && (
                      <Tooltip
                        placement="top"
                        arrow
                        title={
                          <div>
                            Reset device options to the recommended defaults on
                            this device target. Except for your custom binding
                            phrase.
                          </div>
                        }
                      >
                        <Button onClick={onResetToDefaults} size="small">
                          Reset
                        </Button>
                      </Tooltip>
                    )}
                </div>
              }
            />
            <Divider />
            <CardContent>
              {!loadingOptions && (
                <DeviceOptionsForm
                  target={deviceTarget?.name ?? null}
                  deviceOptions={deviceOptionsFormData}
                  firmwareVersionData={firmwareVersionData}
                  onChange={onUserDefines}
                />
              )}
              {deviceOptionsFormData.userDefinesMode ===
                UserDefinesMode.UserInterface &&
                (firmwareVersionData === null ||
                  validateFirmwareVersionData(firmwareVersionData).length > 0 ||
                  deviceTarget === null) && (
                  <Alert severity="info">
                    <AlertTitle>Notice</AlertTitle>
                    Please select a firmware version and device target first
                  </Alert>
                )}
              <ShowAlerts
                severity="error"
                messages={deviceOptionsResponseError}
              />
              <ShowAlerts
                severity="error"
                messages={deviceOptionsValidationErrors}
              />
              <Loader loading={loadingOptions} />
            </CardContent>
            <Divider />

            <CardTitle icon={<SettingsIcon />} title="Actions" />
            <Divider />
            <CardContent>
              <UserDefinesAdvisor
                deviceOptionsFormData={deviceOptionsFormData}
              />

              <div>
                {serialPortRequired && (
                  <SerialDeviceSelect
                    serialDevice={serialDevice}
                    onChange={onSerialDevice}
                  />
                )}
                {wifiDeviceRequired && (
                  <WifiDeviceSelect
                    wifiDevice={wifiDevice}
                    wifiDevices={Array.from(networkDevices.values()).filter(
                      (item) => {
                        return deviceTarget?.name
                          ?.toUpperCase()
                          .startsWith(item.target.toUpperCase());
                      }
                    )}
                    onChange={onWifiDevice}
                  />
                )}
                <Button
                  sx={styles.button}
                  size="large"
                  variant="contained"
                  onClick={onBuild}
                >
                  Build
                </Button>
                {deviceTarget?.flashingMethod !== FlashingMethod.Radio && (
                  <SplitButton
                    sx={styles.button}
                    size="large"
                    variant="contained"
                    options={[
                      {
                        label: 'Build & Flash',
                        value: BuildJobType.BuildAndFlash,
                      },
                      {
                        label: 'Force Flash',
                        value: BuildJobType.ForceFlash,
                      },
                    ]}
                    onButtonClick={(value: string | null) => {
                      if (value === BuildJobType.BuildAndFlash) {
                        onBuildAndFlash();
                      } else if (value === BuildJobType.ForceFlash) {
                        onForceFlash();
                      }
                    }}
                  />
                )}
              </div>
            </CardContent>
          </Card>
          <Card>
            {networkDevices.size > 0 && (
              <Box>
                <Divider />
                <CardTitle icon={<NetworkWifi />} title="Network Devices" />
                <Divider />
                <CardContent>
                  <div>
                    <WifiDeviceList
                      wifiDevices={Array.from(networkDevices.values())}
                      onChange={(dnsDevice: MulticastDnsInformation) => {
                        onDeviceChange(dnsDevice);
                        handleSelectedDeviceChange(dnsDevice.name);
                      }}
                    />
                  </div>
                </CardContent>
              </Box>
            )}
          </Card>
          <Dialog
            open={deviceSelectErrorDialogOpen}
            onClose={handleDeviceSelectErrorDialogClose}
            aria-labelledby="alert-dialog-title"
            aria-describedby="alert-dialog-description"
          >
            <DialogTitle id="alert-dialog-title">
              Device Select Error
            </DialogTitle>
            <DialogContent>
              <DialogContentText id="alert-dialog-description">
                The target device could not be automatically selected, it must
                be done manually.
              </DialogContentText>
            </DialogContent>
            <DialogActions>
              <Button onClick={handleDeviceSelectErrorDialogClose}>
                Close
              </Button>
            </DialogActions>
          </Dialog>
        </>
      )}

      {viewState === ViewState.Compiling && (
        <Card>
          <CardTitle icon={<SettingsIcon />} title="Build" />
          <Divider />
          <CardContent>
            <BuildProgressBar
              inProgress={buildInProgress}
              jobType={currentJobType}
              progressNotification={lastProgressNotification}
            />
            <BuildNotificationsList notifications={progressNotifications} />

            <ShowAlerts severity="error" messages={buildFlashErrorResponse} />
          </CardContent>

          {logs.length > 0 && (
            <>
              <CardTitle
                icon={<SettingsIcon />}
                title={
                  <Box display="flex" justifyContent="space-between">
                    <Box>Logs</Box>
                    <Box>
                      <IconButton
                        aria-label="Copy log to clipboard"
                        title="Copy log to clipboard"
                        onClick={async () => {
                          await navigator.clipboard.writeText(logs);
                        }}
                      >
                        <ContentCopy />
                      </IconButton>
                      <IconButton
                        aria-label="Save log to file"
                        title="Save log to file"
                        onClick={saveBuildLogToFile}
                      >
                        <Save />
                      </IconButton>
                    </Box>
                  </Box>
                }
              />
              <Divider />
              <CardContent>
                <Box sx={styles.longBuildDurationWarning}>
                  <ShowTimeoutAlerts
                    severity="warning"
                    messages="Sometimes builds take at least a few minutes. It is normal, especially for the first time builds."
                    active={buildInProgress}
                    timeout={14 * 1000}
                  />
                </Box>
                <Logs data={logs} />
              </CardContent>
              <Divider />
            </>
          )}
          {response !== undefined && (
            <>
              <CardTitle icon={<SettingsIcon />} title="Result" />
              <Divider />
              <CardContent>
                {response?.buildFlashFirmware?.success &&
                  currentJobType === BuildJobType.BuildAndFlash &&
                  deviceTarget?.flashingMethod === FlashingMethod.WIFI && (
                    <>
                      <Alert sx={styles.buildNotification} severity="warning">
                        <AlertTitle>Warning</AlertTitle>
                        Please wait for LED to resume blinking before
                        disconnecting power
                      </Alert>
                    </>
                  )}
                <ShowAfterTimeout
                  timeout={
                    response?.buildFlashFirmware?.success &&
                    currentJobType === BuildJobType.BuildAndFlash &&
                    deviceTarget?.flashingMethod === FlashingMethod.WIFI
                      ? 15000
                      : 1000
                  }
                  active={!buildInProgress}
                >
                  <Box sx={styles.buildNotification}>
                    <BuildResponse
                      response={response?.buildFlashFirmware}
                      firmwareVersionData={firmwareVersionData}
                    />
                  </Box>
                  {response?.buildFlashFirmware?.success && hasLuaScript && (
                    <>
                      <Alert sx={styles.buildNotification} severity="info">
                        <AlertTitle>Update Lua Script</AlertTitle>
                        Make sure to update the Lua script on your radio
                      </Alert>
                    </>
                  )}
                </ShowAfterTimeout>
                {response?.buildFlashFirmware?.success &&
                  currentJobType === BuildJobType.Build && (
                    <>
                      <Alert sx={styles.buildNotification} severity="info">
                        <AlertTitle>Build notice</AlertTitle>
                        {deviceTarget?.flashingMethod !== FlashingMethod.Radio
                          ? 'Firmware binary file was opened in the file explorer'
                          : "Firmware binary file was opened in the file explorer, copy the firmware file to your radios's SD card and flash it to the transmitter using EdgeTX/OpenTX"}
                      </Alert>
                    </>
                  )}
              </CardContent>
              <Divider />
            </>
          )}
          {!buildInProgress && (
            <>
              <CardTitle icon={<SettingsIcon />} title="Actions" />
              <Divider />
              <CardContent>
                <Button
                  sx={styles.button}
                  color="primary"
                  size="large"
                  variant="contained"
                  onClick={onBack}
                >
                  Back
                </Button>

                {!response?.buildFlashFirmware.success && (
                  <Button
                    sx={styles.button}
                    size="large"
                    variant="contained"
                    onClick={() => {
                      sendJob(currentJobType);
                    }}
                  >
                    Retry
                  </Button>
                )}

                {!response?.buildFlashFirmware.success &&
                  response?.buildFlashFirmware.errorType ===
                    BuildFirmwareErrorType.TargetMismatch && (
                    <Button
                      sx={styles.button}
                      size="large"
                      variant="contained"
                      onClick={onForceFlash}
                    >
                      Force Flash
                    </Button>
                  )}

                {response?.buildFlashFirmware.success && luaDownloadButton()}
              </CardContent>
            </>
          )}
        </Card>
      )}
    </MainLayout>
  );
}
Example #26
Source File: engineSelectPopper.tsx    From Search-Next with GNU General Public License v3.0 4 votes vote down vote up
EngineSelectPopper: FC<EngineSelectPopperProps> = (props) => {
  const {
    width = 300,
    anchorEl,
    open = false,
    onBtnClick,
    onEngineSelect,
    engine,
  } = props;
  const [classifyEngineList, setClassifyEngineList] = useState<
    SearchEngineClassifyWithChildren[]
  >([]);
  const [selected, setSelected] = useState<string>('');
  const [engineList, setEngineList] = React.useState([] as SearchEngine[]);

  const getClassifyEngine = () => {
    getClassifyEngineListApi().then((res) => {
      const current = {
        ...engine.engine,
        classifyId: engine.engine?.classify?._id,
      } as SearchEngine;
      let filterEngines = res
        .map((i) => i.children)
        .flat()
        .filter((u) => u._id !== engine.engine?._id)
        .slice(0, engine.indexCount - 1);
      if (engine.sortType === 'count') {
        filterEngines = filterEngines.sort((r, t) => t.count - r.count);
      }
      setEngineList([current, ...filterEngines]);
      setClassifyEngineList(res);
      res.length > 0 && setSelected(res[0]._id);
    });
  };

  useEffect(() => {
    if (engine?.engine?.classify?._id) {
      setSelected(engine?.engine?.classify?._id);
    }
    getClassifyEngine();
  }, [engine]);

  return (
    <div className="mb-1">
      <Popper open={open} anchorEl={anchorEl} placement="top">
        {({ TransitionProps }) => (
          <Card
            {...TransitionProps}
            style={{ width: `${anchorEl?.clientWidth}px` }}
            className="mb-1"
          >
            <div className="p-2 flex gap-2 items-start">
              <div className="max-h-20 overflow-y-auto pr-1">
                {classifyEngineList.map((item) => (
                  <div
                    key={item._id}
                    onClick={() => {
                      setSelected(item._id);
                    }}
                    className={classnames(
                      'px-1.5 py-0.5 cursor-pointer rounded text-sm',
                      selected === item._id
                        ? 'bg-primary text-white'
                        : 'bg-white',
                    )}
                  >
                    {item.name}
                  </div>
                ))}
              </div>
              <div className="flex gap-1 items-start justify-start flex-grow">
                {classifyEngineList
                  .filter((i) => i._id === selected)?.[0]
                  ?.children.map((i) => (
                    <div
                      className={classnames(
                        'px-1.5 py-0.5 cursor-pointer rounded text-sm',
                        engine?.engine?._id === i._id
                          ? 'bg-primary text-white'
                          : 'bg-white border',
                      )}
                      onClick={() => {
                        onEngineSelect(i);
                      }}
                    >
                      {i.name}
                    </div>
                  ))}
              </div>
              <IconButton
                onClick={() => {
                  onBtnClick(false);
                }}
                size="small"
              >
                <Close />
              </IconButton>
            </div>
          </Card>
        )}
      </Popper>
      <div className="w-full text-left mb-1 flex justify-start items-center overflow-x-auto">
        {engineList.map((i, j) => (
          <Chip
            key={j}
            className={classnames(
              'mx-1',
              i._id === engine?.engine?._id
                ? 'bg-primary text-white'
                : 'bg-gray-100',
            )}
            size="small"
            label={i.name}
            onClick={(e) => onEngineSelect(i)}
          ></Chip>
        ))}
        {engine.mode === 'custom' && (
          <Chip
            onClick={(e: any) => {
              onBtnClick(!open);
            }}
            className={classnames('mx-1', 'bg-gray-100')}
            size="small"
            label={
              <div className="text-sm flex gap-1 items-center">
                <Settings className="text-base" />
              </div>
            }
          />
        )}
      </div>
    </div>
  );
}
Example #27
Source File: TemplateDetailPage.tsx    From fluttertemplates.dev with MIT License 4 votes vote down vote up
function RenderBody(props: TemplateCardProps) {
  const router = useRouter();

  const [selectedTab, setSelectedTab] = useState(0);
  const handleTabChange = (event: any, newValue: any) => {
    setSelectedTab(newValue);
  };

  const _frontmatter = props.frontmatter;
  const _hasMdContent = props.content.toString().length != 0;

  function renderTabs(selectedTab: number) {
    if (_hasMdContent && selectedTab == 0) {
      return (
        <div
          style={{
            width: "100%",
            height: "80%",
            overflowX: "hidden",
            overflowY: "auto",
          }}
        >
          <ReactMarkdown
            children={props.content}
            remarkPlugins={[remarkGfm]}
            rehypePlugins={[rehypeRaw]}
            linkTarget="_blank"
            components={{
              img: ({ node, ...props }) => (
                <img {...props} width="100%" height="100%" />
              ),

              tr: ({ node, ...props }) => (
                <Grid container spacing={1}>
                  {props.children}
                </Grid>
              ),
              td: ({ node, ...props }) => (
                <Grid item xs={4}>
                  {props.children}
                </Grid>
              ),
            }}
          />
        </div>
      );
    } else if (
      (!_hasMdContent && selectedTab == 0) ||
      (_hasMdContent && selectedTab == 1)
    ) {
      return (
        <Code
          codeGistUrl={_frontmatter.codeGistUrl}
          fullCodeUrl={_frontmatter.codeUrl}
        />
      );
    } else if (
      (!_hasMdContent && selectedTab == 1) ||
      (_hasMdContent && selectedTab == 2)
    ) {
      return <PackagesUsed packages={_frontmatter.packages ?? []} />;
    } else {
      return <PageNotFoundPage />;
    }
  }

  return (
    <Grid
      container
      style={{
        marginTop: "2rem",
      }}
      spacing={2}
      justifyContent="center"
    >
      {!(_frontmatter.isProtected ?? false) && (
        <Grid
          item
          md={6}
          style={{
            height: "90vh",
            width: "100%",
          }}
        >
          <Card elevation={0}>
            <Typography
              component="h5"
              variant="h5"
              style={{
                fontWeight: "bold",
                marginLeft: "1rem",
                marginTop: "1rem",
              }}
            >
              {_frontmatter.title}
            </Typography>
            {_frontmatter.categories && _frontmatter.categories.length > 0 && (
              <div
                style={{
                  marginLeft: "1rem",
                  marginBottom: "-10px",
                }}
              >
                <CategoriesList
                  categories={_frontmatter.categories}
                  selected={""}
                  showAll={false}
                />
              </div>
            )}

            <Tabs
              value={selectedTab}
              onChange={handleTabChange}
              indicatorColor="secondary"
              textColor="inherit"
              centered
            >
              {_hasMdContent && (
                <Tab
                  label="About"
                  icon={
                    <InfoOutlined
                      style={{
                        marginTop: "8px",
                        marginRight: "8px",
                      }}
                    />
                  }
                />
              )}
              <Tab
                label="Code"
                icon={
                  <CodeOutlined
                    style={{
                      marginTop: "8px",
                      marginRight: "8px",
                    }}
                  />
                }
              />
              <Tab
                label="Packages Used"
                icon={
                  <AttachmentOutlined
                    style={{
                      marginTop: "8px",
                      marginRight: "8px",
                    }}
                  />
                }
              />
            </Tabs>
          </Card>
          {renderTabs(selectedTab)}
        </Grid>
      )}
      <Grid
        item
        md={6}
        style={{
          display: "flex",
          justifyContent: "center",
        }}
      >
        <div
          style={{
            height: "80vh",
            width: "calc(80vh/17 * 9)",
            margin: "8px",
          }}
        >
          <CustomIframe
            url={_frontmatter.demoUrl}
            showLoadingIndicator={true}
            style={{
              borderRadius: "16px",
              border: "4px solid grey",
            }}
          />
        </div>
      </Grid>
    </Grid>
  );
}
Example #28
Source File: DonationTable.tsx    From frontend with MIT License 4 votes vote down vote up
function DonationTable({ donations }: DonationTableProps) {
  const { t, i18n } = useTranslation()
  const [fromDate, setFromDate] = React.useState<Date | null>(null)
  const [toDate, setToDate] = React.useState<Date | null>(null)
  const [monthly, setMonthly] = React.useState(true)
  const [oneTime, setOneTime] = React.useState(true)
  const filteredByTypeDonations = useMemo(() => {
    if (monthly && oneTime) {
      return donations
    }
    if (!monthly && !oneTime) {
      return []
    }
    if (monthly) {
      return donations?.filter((d) => d.type !== 'donation')
    }
    if (oneTime) {
      return donations?.filter((d) => d.type === 'donation')
    }
    return donations
  }, [donations, monthly, oneTime])
  const filteredDonations = useMemo(() => {
    if (!fromDate && !toDate) {
      return filteredByTypeDonations
    }
    if (fromDate && toDate) {
      return filteredByTypeDonations?.filter((d) => {
        const createdAtDate = parseISO(d.createdAt)
        return isAfter(createdAtDate, fromDate) && isBefore(createdAtDate, toDate)
      })
    }
    if (fromDate) {
      return filteredByTypeDonations?.filter((d) => {
        const createdAtDate = parseISO(d.createdAt)
        return isAfter(createdAtDate, fromDate)
      })
    }
    if (toDate) {
      return filteredByTypeDonations?.filter((d) => {
        const createdAtDate = parseISO(d.createdAt)
        return isBefore(createdAtDate, toDate)
      })
    }
  }, [filteredByTypeDonations, fromDate, toDate])
  return (
    <Card sx={{ padding: theme.spacing(2) }}>
      <Grid container alignItems={'flex-start'} spacing={theme.spacing(2)}>
        <Grid item xs={6} sm={3}>
          <CheckboxLabel>{t('profile:donations.oneTime')}</CheckboxLabel>
          <Checkbox
            onChange={(e, checked) => setOneTime(checked)}
            checked={oneTime}
            name="oneTime"
          />
        </Grid>
        <Grid item xs={6} sm={3}>
          <CheckboxLabel>{t('profile:donations.monthly')}</CheckboxLabel>
          <Checkbox
            onChange={(e, checked) => setMonthly(checked)}
            checked={monthly}
            name="monthly"
          />
        </Grid>
        <LocalizationProvider
          locale={i18n.language === 'bg' ? bg : enUS}
          dateAdapter={AdapterDateFns}>
          <Grid item xs={12} sm={3}>
            <DatePicker
              label={t('profile:donations.fromDate')}
              value={fromDate}
              onChange={setFromDate}
              renderInput={(params) => <TextField size="small" {...params} />}
            />
          </Grid>
          <Grid item xs={12} sm={3}>
            <DatePicker
              label={t('profile:donations.toDate')}
              value={toDate}
              onChange={setToDate}
              renderInput={(params) => <TextField size="small" {...params} />}
            />
          </Grid>
        </LocalizationProvider>
      </Grid>
      {filteredDonations?.length ? (
        <TableContainer>
          <Table sx={{ minWidth: 650, backgroundColor: 'white' }} aria-label="simple table">
            <TableHead>
              <TableRow>
                <TableCell>№</TableCell>
                <TableCell>{t('profile:donations.date')}</TableCell>
                <TableCell>{t('profile:donations.type')}</TableCell>
                <TableCell>{t('profile:donations.cause')}</TableCell>
                <TableCell>{t('profile:donations.amount')}</TableCell>
                <TableCell>{t('profile:donations.certificate')}</TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {filteredDonations.map((donation, index) => (
                <TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
                  <TableCell component="th" scope="row">
                    {index + 1}
                  </TableCell>
                  <TableCell>
                    {format(parseISO(donation.createdAt), 'd.LL.yyyy', {
                      locale: i18n.language === 'bg' ? bg : enUS,
                    })}
                  </TableCell>
                  <TableCell>
                    <Avatar sx={{ background: darken(theme.palette.secondary.main, 0.175) }}>
                      <StarIcon />
                    </Avatar>
                  </TableCell>
                  <TableCell>{donation.targetVault.campaign.title}</TableCell>
                  <TableCell>{money(donation.amount)}</TableCell>
                  <TableCell>
                    <Button variant="outlined" disabled={donation.status != 'succeeded'}>
                      <Link target="_blank" href={routes.donation.viewCertificate(donation.id)}>
                        {t('profile:donations.download')} <ArrowForwardIcon />
                      </Link>
                    </Button>
                  </TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>
      ) : (
        <Box sx={{ fontSize: 20, mt: 4 }}>Към момента няма направени дарения</Box>
      )}
    </Card>
  )
}
Example #29
Source File: SettingsView.tsx    From react-flight-tracker with MIT License 4 votes vote down vote up
SettingsView: React.FC<Props> = (props) => {

  // Fields
  const contextName: string = ViewKeys.SettingsView;

  // Contexts
  const systemContext = useContext(SystemContext);
  const appContext = useContext(AppContext);

  const getSetting = (key: string, type: string) => {

    const value = systemContext.getSetting(key)
    if (typeof (value) === type)
      return value;

    return false;
  };

  const handleChange = (e: SelectChangeEvent) => {
    appContext.changeTheme(e.target.value);
  };

  const handleSettingsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    systemContext.storeSetting(e.target.name, e.target.checked);
  };

  const renderAppSettings = () => {

    return (
      <Card>

        <CardContent>

          <Typography
            variant={'h6'}
            gutterBottom={true}>
            {'App settings'}
          </Typography>

          <FormGroup>
            <FormControl
              color='secondary'
              variant="filled"
              sx={{ m: 1, minWidth: 120 }}>

              <InputLabel
                id="demo-simple-select-filled-label">
                Theme change
              </InputLabel>
              <Select
                labelId="demo-simple-select-filled-label"
                id="demo-simple-select-filled"
                value={appContext.activeThemeName}
                onChange={handleChange}>

                <MenuItem
                  value={ThemeKeys.DarkTheme}>
                  {ThemeKeys.DarkTheme}
                </MenuItem>
                <MenuItem
                  value={ThemeKeys.LightTheme}>
                  {ThemeKeys.LightTheme}
                </MenuItem>
                <MenuItem
                  value={ThemeKeys.PineappleTheme}>
                  {ThemeKeys.PineappleTheme}
                </MenuItem>
              </Select>
            </FormControl>

          </FormGroup>
        </CardContent>
      </Card>
    );
  };

  const renderMapSettings = () => {

    return (
      <Card>

        <CardContent>

          <Typography
            variant={'h6'}
            gutterBottom={true}>
            {'Map settings'}
          </Typography>

          <FormGroup>
            <FormControlLabel
              control={
                <Switch
                  color='secondary'
                  name={SettingKeys.ShowDataOverlayOnMap}
                  checked={getSetting(SettingKeys.ShowDataOverlayOnMap, 'boolean')}
                  onChange={handleSettingsChange} />
              }
              label="Show data overlay on map"
            />
            <FormControlLabel
              control={
                <Switch
                  color='secondary'
                  name={SettingKeys.ShowLogOverlayOnMap}
                  checked={getSetting(SettingKeys.ShowLogOverlayOnMap, 'boolean')}
                  onChange={handleSettingsChange} />
              }
              label="Show log overlay on map"
            />
          </FormGroup>
        </CardContent>
      </Card>
    );
  };

  return (

    <ViewContainer
      isScrollLocked={true}>

      {renderAppSettings()}

      <Box sx={{ height: (theme) => theme.spacing(1) }} />

      {renderMapSettings()}
    </ViewContainer>
  );
}