@mui/icons-material#ExpandMore TypeScript Examples

The following examples show how to use @mui/icons-material#ExpandMore. 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: GroupFilter.tsx    From Tachidesk-WebUI with Mozilla Public License 2.0 6 votes vote down vote up
export default function GroupFilter(props: Props) {
    const {
        state,
        name,
        position,
        updateFilterValue,
        update,
    } = props;

    const [open, setOpen] = React.useState(false);

    const handleClick = () => {
        setOpen(!open);
    };

    return (
        <>
            <ListItemButton onClick={handleClick}>
                <ListItemText primary={name} />
                {open ? <ExpandLess /> : <ExpandMore />}
            </ListItemButton>
            <Collapse in={open}>
                <List disablePadding>
                    <Options
                        sourceFilter={state}
                        group={position}
                        updateFilterValue={updateFilterValue}
                        update={update}
                    />
                </List>
            </Collapse>
        </>
    );
}
Example #2
Source File: index.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function FormulaCalcCard() {
  const { t } = useTranslation("page_character")
  const [expanded, setexpanded] = useState(false)
  const toggle = useCallback(() => setexpanded(!expanded), [setexpanded, expanded])
  return <CardLight>
    <CardContent sx={{ display: "flex", gap: 1 }}>
      <Grid container spacing={1}>
        <Grid item><HitModeToggle size="small" /></Grid>
        <Grid item><InfusionAuraDropdown /></Grid>
        <Grid item><ReactionToggle size="small" /></Grid>
      </Grid>
      <Box display="flex" gap={1} >
        <Box>
          <Typography variant='subtitle2' >{t("formulas")} {"&"}</Typography>
          <Typography variant='subtitle2' >{t("calculations")}</Typography>
        </Box>
        <ExpandButton
          expand={expanded}
          onClick={toggle}
          aria-expanded={expanded}
          aria-label="show more"
          size="small"
          sx={{ p: 0 }}
        >
          <ExpandMore />
        </ExpandButton>
      </Box>
    </CardContent>
    <Collapse in={expanded} timeout="auto" unmountOnExit>
      <CardContent sx={{ pt: 0 }}>
        <CalculationDisplay />
      </CardContent>
    </Collapse>
  </CardLight>
}
Example #3
Source File: index.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function FormulaCalc({ sectionKey, displayNs }: { displayNs: DisplaySub<NodeDisplay>, sectionKey: string }) {
  const { data } = useContext(DataContext)
  const header = usePromise(getDisplayHeader(data, sectionKey), [data, sectionKey])
  if (!header) return null
  if (Object.entries(displayNs).every(([_, node]) => node.isEmpty)) return null
  const { title, icon, action } = header
  return <CardDark sx={{ mb: 1 }}>
    <CardHeader avatar={icon && <ImgIcon size={2} sx={{ m: -1 }} src={icon} />} title={title} action={action} titleTypographyProps={{ variant: "subtitle1" }} />
    <Divider />
    <CardContent>
      {Object.entries(displayNs).map(([key, node]) =>
        !node.isEmpty && <Accordion sx={{ bgcolor: "contentLight.main" }} key={key}>
          <AccordionSummary expandIcon={<ExpandMore />}>
            <Typography><ColorText color={node.info.variant}>{KeyMap.get(node.info.key ?? "")}</ColorText> <strong>{valueString(node.value, node.unit)}</strong></Typography>
          </AccordionSummary>
          <AccordionDetails>
            {node.formulas.map((subform, i) => <Typography key={i}>{subform}</Typography>)}
          </AccordionDetails>
        </Accordion>)}
    </CardContent>
  </CardDark>
}
Example #4
Source File: index.tsx    From genshin-optimizer with MIT License 6 votes vote down vote up
function FeatureCard({ image, title, content, t }) {
  const [expanded, setExpanded] = useState(false);

  return <CardLight >
    <CardContent sx={{ p: 1, pb: 0 }}>
      <Box component="img" src={image} alt="test" sx={{ width: "100%", height: "auto" }} />
    </CardContent>
    <CardHeader
      action={
        <ExpandButton
          expand={expanded}
          onClick={() => setExpanded(!expanded)}
          aria-expanded={expanded}
          aria-label="show more"
        >
          <ExpandMore />
        </ExpandButton>
      }
      titleTypographyProps={{ variant: "subtitle1" }}
      title={title(t)}
    />
    <Collapse in={expanded} timeout="auto" unmountOnExit>
      <CardContent sx={{ pt: 0 }}>
        {content(t)}
      </CardContent>
    </Collapse>
  </CardLight >
}
Example #5
Source File: ExpandableListItem.tsx    From frontend with MIT License 6 votes vote down vote up
ExpandableListItem = ({ header, content }: Props) => {
  const [open, setOpen] = useState(false)

  return (
    <List
      sx={{
        my: 0,
        mx: { xs: 0, md: 3 },
        cursor: 'pointer',
      }}>
      <Paper elevation={1} sx={{ borderRadius: 2 }}>
        <Box
          sx={{ display: 'flex', alignItems: 'center', px: 3, py: 1 }}
          onClick={() => setOpen(!open)}>
          <ListItemText
            primary={header}
            primaryTypographyProps={{
              variant: 'subtitle1',
              color: `${withAccentColor(open)}`,
            }}
          />
          {open ? (
            <ExpandLess sx={{ color: `${withAccentColor(open)}` }} />
          ) : (
            <ExpandMore sx={{ color: `${withAccentColor(open)}` }} />
          )}
        </Box>
        <Collapse in={open}>
          <List>
            <Box sx={{ pl: { xs: 3, md: 6 }, pb: 2, pr: 2 }}>{content}</Box>
          </List>
        </Collapse>
      </Paper>
    </List>
  )
}
Example #6
Source File: SortFilter.tsx    From Tachidesk-WebUI with Mozilla Public License 2.0 5 votes vote down vote up
export default function SortFilter(props: Props) {
    const {
        values,
        name,
        state,
        position,
        group,
        updateFilterValue,
        update,
    } = props;
    const [val, setval] = React.useState(state);

    const [open, setOpen] = React.useState(false);

    const handleClick = () => {
        setOpen(!open);
    };

    if (values) {
        const handleChange = (event:
        React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
            const tmp = val;
            if (tmp.index === index) {
                tmp.ascending = !tmp.ascending;
            } else {
                tmp.ascending = true;
            }
            tmp.index = index;
            setval(tmp);
            const upd = update.filter((e: {
                position: number; group: number | undefined;
            }) => !(position === e.position && group === e.group));
            updateFilterValue([...upd, { position, state: JSON.stringify(tmp), group }]);
        };

        const ret = (
            <FormControl fullWidth>
                <ListItemButton onClick={handleClick}>
                    <ListItemText primary={name} />
                    {open ? <ExpandLess /> : <ExpandMore />}
                </ListItemButton>
                <Collapse in={open}>
                    <List>
                        {values.map((value: string, index: number) => {
                            let icon;
                            if (val.index === index) {
                                icon = val.ascending ? (<ArrowUpwardIcon color="primary" />)
                                    : (<ArrowDownwardIcon color="primary" />);
                            }
                            return (
                                <ListItem disablePadding key={`${name} ${value}`}>
                                    <ListItemButton
                                        onClick={(event) => handleChange(event, index)}
                                    >
                                        <ListItemIcon>
                                            {icon}
                                        </ListItemIcon>
                                        <ListItemText primary={value} />
                                    </ListItemButton>
                                </ListItem>
                            );
                        })}
                    </List>
                </Collapse>
            </FormControl>
        );
        return (
            <Box key={name} sx={{ display: 'flex', flexDirection: 'column', minWidth: 120 }}>
                {ret}
            </Box>
        );
    }
    return (<></>);
}
Example #7
Source File: itemAccordion.tsx    From Search-Next with GNU General Public License v3.0 5 votes vote down vote up
ItemAccordion: React.FC<ItemAccordionProps> = ({
  title,
  desc,
  action,
  expanded,
  onChange,
  children,
  disableDetailPadding = false,
}) => {
  return (
    <Accordion
      expanded={expanded}
      onChange={onChange}
      className={classNames(
        'rounded border shadow-none bg-white my-0',
        css`
          &::before {
            background-color: transparent !important;
          }
        `,
      )}
    >
      <AccordionSummary
        className=" transition hover:bg-gray-100"
        expandIcon={<ExpandMore />}
      >
        <div className="flex items-center justify-between w-full mr-2">
          <div>
            {title && <p className="mb-0 text-sm">{title}</p>}
            {desc && <p className="mb-0 text-xs text-gray-700">{desc}</p>}
          </div>
          <div className="flex items-center">{action}</div>
        </div>
      </AccordionSummary>
      <AccordionDetails className={classNames({ 'p-0': disableDetailPadding })}>
        {children}
      </AccordionDetails>
    </Accordion>
  );
}
Example #8
Source File: AnonymousForm.tsx    From frontend with MIT License 5 votes vote down vote up
export default function AnonymousForm() {
  const [field] = useField('anonymousDonation')
  const { t } = useTranslation('one-time-donation')
  return (
    <>
      <CircleCheckboxField
        label={
          <Typography
            fontSize={16}
            display="inline-flex"
            alignItems="center"
            component="span"
            color="#343434"
            fontWeight="bold">
            {t('anonymous-menu.checkbox-label')}
            {field.value ? <ExpandLess /> : <ExpandMore />}
          </Typography>
        }
        name="anonymousDonation"
      />
      <Collapse in={field.value} timeout="auto" unmountOnExit>
        <Grid container columnSpacing={3} rowSpacing={3}>
          <Grid item xs={12} color="#343434" sx={{ opacity: 0.9 }}>
            <Typography>{t('anonymous-menu.info-start')}</Typography>
          </Grid>
          <Grid item xs={12} md={6}>
            <FormTextField
              name="personsFirstName"
              type="text"
              label={t('anonymous-menu.firstName')}
              fullWidth
            />
          </Grid>
          <Grid item xs={12} md={6}>
            <FormTextField
              name="personsLastName"
              type="text"
              label={t('anonymous-menu.lastName')}
              fullWidth
            />
          </Grid>
          <Grid item xs={12} md={6}>
            <FormTextField name="personsEmail" type="text" label="Email" fullWidth />
          </Grid>
          <Grid item xs={12} md={6}>
            <FormTextField
              name="personsPhone"
              type="text"
              label={t('anonymous-menu.phone')}
              fullWidth
            />
          </Grid>
          <Grid item xs={12} color="GrayText">
            <Typography>* {t('anonymous-menu.info-end')}</Typography>
          </Grid>
        </Grid>
      </Collapse>
    </>
  )
}
Example #9
Source File: Profiler.tsx    From NekoMaid with MIT License 5 votes vote down vote up
Plugins: React.FC = React.memo(() => {
  const plugin = usePlugin()
  const [data, setData] = useState<[JSX.Element[], any[][]] | undefined>()
  useEffect(() => {
    const off = plugin.emit('profiler:fetchPlugins').on('profiler:plugins', (data: Record<string, [Record<string | number, [number, number]>]>) => {
      const pluginsTimes: any[][] = [[], [], []]
      const tree: [number, JSX.Element][] = []
      for (const name in data) {
        let totalTypesTime = 0
        let totalTypesCount = 0
        const subTrees: JSX.Element[] = []
        ;['events', 'tasks', 'commands'].forEach((type, i) => {
          const curKey = name + '/' + type
          const subTree: [number, JSX.Element][] = []
          const cur = data[name][i]
          let totalTime = 0
          let totalCount = 0
          for (const key in cur) {
            const [count, time] = cur[key]
            totalCount += count
            totalTypesCount += count
            totalTime += time
            totalTypesTime += time
            const key2 = `${curKey}/${key}`
            subTree.push([time, <TreeItem nodeId={key2} key={key2} label={getLabel(key, time, count)} />])
          }
          if (totalTime) pluginsTimes[i].push({ name, value: totalTime })
          if (subTree.length) {
            subTrees.push(<TreeItem nodeId={curKey} key={curKey} label={getLabel((lang.profiler as any)[type], totalTime, totalCount)}>
              {subTree.sort((a, b) => b[0] - a[0]).map(it => it[1])}
            </TreeItem>)
          }
        })
        if (totalTypesTime) {
          tree.push([totalTypesTime, <TreeItem
            nodeId={name}
            label={getLabel(name, totalTypesTime, totalTypesCount)}
            key={name}
          >{subTrees}</TreeItem>])
        }
      }
      setData([
        tree.sort((a, b) => b[0] - a[0]).map(it => it[1]),
        pluginsTimes.map(it => it.sort((a, b) => b.value - a.value))
      ])
    })
    return () => { off() }
  }, [])
  return <Container maxWidth={false} sx={{ py: 3, position: 'relative', height: data ? undefined : '80vh' }}>
    <CircularLoading loading={!data} background={false} />
    {data && <Grid container spacing={3}>
      <Grid item xs={12}>
        <Card>
          <CardHeader title={lang.profiler.pluginsTitle} sx={{ position: 'relative' }} />
          <Divider />
          {data[0].length
            ? <TreeView defaultCollapseIcon={<ExpandMore />} defaultExpandIcon={<ChevronRight />}>{data[0]}</TreeView>
            : <CardContent><Empty /></CardContent>}
        </Card>
      </Grid>
      <Pie title={lang.profiler.pluginsEventsTime} data={data[1][0]} formatter={nanoSecondFormatter} />
      <Pie title={lang.profiler.pluginsTasksTime} data={data[1][1]} formatter={nanoSecondFormatter} />
      <Pie title={lang.profiler.pluginsCommandsTime} data={data[1][2]} formatter={nanoSecondFormatter} />
    </Grid>}
  </Container>
})
Example #10
Source File: EnemyEditor.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
export function EnemyExpandCard() {
  const { data } = useContext(DataContext)
  const [expanded, setexpanded] = useState(false)
  const toggle = useCallback(() => setexpanded(!expanded), [setexpanded, expanded])
  const eLvlNode = data.get(input.enemy.level)
  const eDefRed = data.get(input.enemy.defRed)
  const eDefIgn = data.get(input.enemy.defIgn)
  return <CardLight>
    <CardContent>
      <Grid container>
        <Grid item flexGrow={1} alignItems="center">
          <Grid container spacing={1}>
            <Grid item>
              <Chip size="small" color="success" label={<span>{KeyMap.get(eLvlNode.info.key)} <strong>{eLvlNode.value}</strong></span>} />
            </Grid>
            {allElementsWithPhy.map(element => <Grid item key={element}>
              <Typography key={element} ><EnemyResText element={element} /></Typography>
            </Grid>)}
            <Grid item>
              <Typography>DEF Reduction {valueString(eDefRed.value, eDefRed.unit)}</Typography>
            </Grid>
            <Grid item>
              <Typography>DEF Ignore {valueString(eDefIgn.value, eDefIgn.unit)}</Typography>
            </Grid>
          </Grid>
        </Grid>
        <Grid item>
          <ExpandButton
            expand={expanded}
            onClick={toggle}
            aria-expanded={expanded}
            aria-label="show more"
            size="small"
            sx={{ p: 0 }}
          >
            <ExpandMore />
          </ExpandButton>
        </Grid>
      </Grid>
    </CardContent>
    <Collapse in={expanded} timeout="auto" unmountOnExit>
      <CardContent sx={{ pt: 0 }}>
        <EnemyEditor />
      </CardContent>
    </Collapse>
  </CardLight>
}
Example #11
Source File: Dashboard.tsx    From NekoMaid with MIT License 4 votes vote down vote up
Dashboard: React.FC = () => {
  const plugin = usePlugin()
  const { version, hasGeoIP } = useGlobalData()
  const [status, setStatus] = useState<Status[]>([])
  const [current, setCurrent] = useState<CurrentStatus | undefined>()

  useEffect(() => {
    const offSetStatus = plugin.once('dashboard:info', setStatus)
    const offCurrent = plugin.on('dashboard:current', (data: CurrentStatus) => setCurrent(old => {
      if (old && isEqual(old.players, data.players)) data.players = old.players
      return data
    }))
    plugin.switchPage('dashboard')
    return () => {
      offSetStatus()
      offCurrent()
    }
  }, [])

  const playerCount = current?.players?.length || 0
  const prev = status[status.length - 1]?.players || 0
  const percent = (prev ? playerCount / prev - 1 : playerCount) * 100
  const tpsColor = !current || current.tps >= 18 ? green : current.tps >= 15 ? yellow : red
  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3}>
        <Grid item lg={3} sm={6} xl={3} xs={12}>
          <TopCard
            title={lang.dashboard.version}
            content={current ? version : <Skeleton animation='wave' width={150} />}
            icon={<Handyman />}
            color={orange[600]}
          >
            <Box sx={{ pt: 2, display: 'flex', alignItems: 'flex-end' }}>
              {!current || current.behinds < 0
                ? <Refresh htmlColor={blue[900]} />
                : current?.behinds === 0
                  ? <Check htmlColor={green[900]} />
                  : <Update htmlColor={yellow[900]} />}
              <Typography color='textSecondary' variant='caption'>&nbsp;{!current || current.behinds === -3
                ? lang.dashboard.updateChecking
                : current.behinds < 0
                  ? <Link underline='hover' color='inherit' sx={{ cursor: 'pointer' }} onClick={() => {
                    toast(lang.dashboard.updateChecking)
                    plugin.emit('dashboard:checkUpdate')
                  }}>{lang.dashboard.updateFailed}</Link>
                  : current.behinds === 0 ? lang.dashboard.updated : lang.dashboard.behinds(current.behinds)}</Typography>
            </Box>
          </TopCard>
        </Grid>
        <Grid item lg={3} sm={6} xl={3} xs={12}>
          <TopCard
            title={lang.dashboard.onlinePlayers}
            content={current ? playerCount : <Skeleton animation='wave' width={150} />}
            icon={<People />}
            color={deepPurple[600]}
          >
            <Box sx={{ pt: 2, display: 'flex', alignItems: 'flex-end' }}>
              {percent === 0 ? <Remove color='primary' /> : percent < 0 ? <ArrowDownward color='error' /> : <ArrowUpward color='success' />}
              <Typography
                sx={{ color: (percent === 0 ? blue : percent < 0 ? red : green)[900], mr: 1 }}
                variant='body2'
              >{Math.abs(percent).toFixed(0)}%</Typography>
              <Typography color='textSecondary' variant='caption'>{lang.dashboard.lastHour}</Typography>
            </Box>
          </TopCard>
        </Grid>
        <Grid item lg={3} sm={6} xl={3} xs={12}>
          <TopCard
            title='TPS'
            content={current ? (current.tps === -1 ? '?' : current.tps.toFixed(2)) : <Skeleton animation='wave' width={150} />}
            icon={!current || current.tps >= 18 || current.tps === -1
              ? <SentimentVerySatisfied />
              : current.tps >= 15 ? <SentimentSatisfied /> : <SentimentDissatisfied />}
            color={tpsColor[600]}
          >
            <Box sx={{ pt: 2.1, display: 'flex', alignItems: 'flex-end' }}>
              <Typography
                sx={{ color: tpsColor[900], mr: 1 }}
                variant='body2'
              >{!current || current.mspt === -1 ? '?' : current.mspt.toFixed(2) + 'ms'}</Typography>
              <Typography color='textSecondary' variant='caption'>{lang.dashboard.mspt}</Typography>
            </Box>
          </TopCard>
        </Grid>
        <Grid item lg={3} sm={6} xl={3} xs={12}>
          <TopCard
            title={lang.dashboard.uptime}
            content={current ? <Uptime time={current.time} /> : <Skeleton animation='wave' width={150} />}
            icon={<AccessTime />}
            color={blue[600]}
          >
            <Box sx={{ pt: 2.7, display: 'flex', alignItems: 'center' }}>
              <Typography color='textSecondary' variant='caption' sx={{ marginRight: 1 }}>{lang.dashboard.memory}</Typography>
              <Tooltip title={current?.totalMemory ? prettyBytes(current.memory) + ' / ' + prettyBytes(current.totalMemory) : ''}>
                <LinearProgress
                  variant='determinate'
                  value={current?.totalMemory ? current.memory / current.totalMemory * 100 : 0}
                  sx={{ flex: '1' }}
                />
              </Tooltip>
            </Box>
          </TopCard>
        </Grid>
        <Grid item lg={8} md={12} xl={9} xs={12}>{useMemo(() => <Charts data={status} />, [status])}</Grid>
        <Grid item lg={4} md={12} xl={3} xs={12}><Players players={current?.players} /></Grid>
        {hasGeoIP && current?.players && typeof current.players[0] !== 'string' && <Grid item xs={12}>
          <Accordion TransitionProps={{ unmountOnExit: true }} disableGutters>
            <AccordionSummary expandIcon={<ExpandMore />}>
              <Typography>{lang.dashboard.playersDistribution}</Typography>
            </AccordionSummary>
            <Divider />
            <WorldMap players={current.players as Player[]} />
          </Accordion>
        </Grid>}
      </Grid>
    </Container>
  </Box>
}
Example #12
Source File: EntityEditor.tsx    From NekoMaid with MIT License 4 votes vote down vote up
EntityEditor: React.FC = () => {
  const theme = useTheme()
  const plugin = usePlugin()
  const his = useHistory()
  const loc = useLocation()
  const globalData = useGlobalData()
  const drawerWidth = useDrawerWidth()
  const [customName, setCustomName] = useState('')
  const [entity, setEntity] = useState<Entity>()
  let id: string | null = null
  if (loc.pathname.startsWith('/NekoMaid/entity/')) {
    const arr = loc.pathname.split('/')
    if (arr.length > 3) id = arr[3]
  }
  useEffect(() => {
    const off = plugin.on('entity:select', id => his.push('/NekoMaid/entity/' + id))
    return () => void off()
  }, [])
  const update = () => {
    if (id) {
      plugin.emit('entity:fetch', (entity: Entity) => {
        if (!entity) {
          failed()
          his.push('/NekoMaid/entity')
          return
        }
        if (globalData.hasNBTAPI && entity.nbt) entity.nbt = stringify(parse(entity.nbt), { pretty: true })
        setCustomName(entity.customName || '')
        setEntity(entity)
      }, id)
    }
  }
  const updateWithAction = (res: boolean) => {
    action(res)
    update()
  }
  useEffect(update, [id])
  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3} sx={{ width: { sm: `calc(100vw - ${drawerWidth}px - ${theme.spacing(3)})` } }}>
        <Grid item lg={6} md={12} xl={6} xs={12}>
          <Card>
            <CardHeader
              title={(entity && minecraft['entity.minecraft.' + entity.type.toLowerCase()]) || lang.entityEditor.title}
              sx={{ position: 'relative' }}
              action={<Box sx={cardActionStyles}>
                <IconButton
                  size='small'
                  disabled={!entity}
                  onClick={() => entity && plugin.emit('entity:save', (res: boolean) => {
                    action(res)
                    update()
                  }, id, entity.nbt || null, customName || null)}
                ><Save /></IconButton>
                <IconButton
                  size='small'
                  disabled={!entity}
                  onClick={() => {
                    update()
                    success()
                  }}
                ><Refresh /></IconButton>
              </Box>}
            />
            <Divider />
            {entity
              ? <>
                <CardContent>
                  <Grid container>
                    <Grid item lg={6} md={6} xl={6} xs={12}>
                      <TextField
                        size='small'
                        label={lang.entityEditor.customName}
                        value={customName}
                        sx={{ width: '90%' }}
                        onChange={e => setCustomName(e.target.value)}
                      />
                    </Grid>
                    {values.map(it => <Grid item lg={6} md={6} xl={6} xs={12} key={it}>
                      <FormControlLabel
                        control={<Switch checked={(entity as any)[it]} />}
                        label={(lang.entityEditor as any)[it]}
                        onChange={(e: any) => plugin.emit('entity:set', (res: boolean) => {
                          action(res)
                          update()
                        }, id, it, e.target.checked)}
                      />
                    </Grid>)}
                  </Grid>
                </CardContent>
                {entity.nbt != null && <Accordion sx={{ '&::before': { opacity: '1!important' } }} disableGutters>
                  <AccordionSummary expandIcon={<ExpandMore />}><Typography>NBT</Typography></AccordionSummary>
                  <AccordionDetails sx={{
                    padding: 0,
                    '& .CodeMirror': { width: '100%', height: 350 },
                    '& .CodeMirror-dialog, .CodeMirror-scrollbar-filler': { backgroundColor: theme.palette.background.paper + '!important' }
                  }}>
                    <UnControlled
                      value={entity.nbt}
                      options={{
                        mode: 'javascript',
                        phrases: lang.codeMirrorPhrases,
                        theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
                      }}
                      onChange={(_: any, __: any, data: string) => (entity.nbt = data)}
                    />
                  </AccordionDetails>
                </Accordion>}
              </>
              : <CardContent><EntitySelector /></CardContent>}
          </Card>
        </Grid>
        {entity?.inventory?.length
          ? <Grid item lg={6} md={12} xl={6} xs={12}>
            <Card>
              <CardHeader
                title={lang.entityEditor.container}
                sx={{ position: 'relative' }}
              />
              <Divider />
              <CardContent sx={{ whiteSpace: 'nowrap', overflowX: 'auto', textAlign: 'center' }}>
                {entity.inventory.map((it, i) => <React.Fragment key={i}><ItemViewer
                  item={it}
                  data={{ type: InvType.ENTITY, solt: i, id }}
                  onDrag={() => plugin.emit('entity:setItem', update, id, i, null, -1)}
                  onDrop={(item, obj) => plugin.emit('entity:setItem', update, id, i, JSON.stringify(item),
                    obj?.type === InvType.ENTITY && obj.id === id ? obj.solt : -1)}
                  onEdit={item => item !== false && plugin.emit('entity:setItem', updateWithAction, id, i, item && JSON.stringify(item), -1)}
                />{!((i + 1) % 9) && <br />}</React.Fragment>)}
              </CardContent>
            </Card>
          </Grid>
          : undefined}
      </Grid>
    </Container>
  </Box>
}
Example #13
Source File: PlayerList.tsx    From NekoMaid with MIT License 4 votes vote down vote up
PlayerInfo: React.FC<{ name?: string }> = React.memo(({ name }) => {
  const plugin = usePlugin()
  const globalData = useGlobalData()
  const [open, setOpen] = useState(false)
  const [info, setInfo] = useState<IPlayerInfo | undefined>()
  const refresh = () => plugin.emit('playerList:query', setInfo, name)
  useEffect(() => {
    setInfo(undefined)
    if (name) refresh()
  }, [name])

  return name && info
    ? <>
      <Divider />
      <List
        sx={{ width: '100%' }}
        component='nav'
        subheader={<ListSubheader component='div' sx={{ backgroundColor: 'inherit' }}>{lang.playerList.details}</ListSubheader>}
      >
        <ListItem>
          <ListItemIcon><AssignmentInd /></ListItemIcon>
          <ListItemText primary={globalData.onlineMode
            ? <Link underline='hover' rel='noopener' target='_blank' href={'https://namemc.com/profile/' + info.id}>{info.id}</Link>
            : info.id
          } />
        </ListItem>
        {!info.hasPlayedBefore && <ListItem>
          <ListItemIcon><ErrorOutline color='error' /></ListItemIcon>
          <ListItemText primary={lang.playerList.hasNotPlayed} />
        </ListItem>}
        {info.ban != null && <ListItem>
          <ListItemIcon><Block color='error' /></ListItemIcon>
          <ListItemText primary={lang.playerList.banned + (info.ban ? ': ' + info.ban : '')} />
        </ListItem>}
        {info.whitelisted && <ListItem>
          <ListItemIcon><Star color='warning' /></ListItemIcon>
          <ListItemText primary={lang.playerList.whitelisted} />
        </ListItem>}
        {info.isOP && <ListItem>
          <ListItemIcon><Security color='primary' /></ListItemIcon>
          <ListItemText primary={lang.playerList.op} />
          </ListItem>}
        {info.hasPlayedBefore && <>
            <ListItemButton onClick={() => setOpen(!open)}>
            <ListItemIcon><Equalizer /></ListItemIcon>
            <ListItemText primary={minecraft['gui.stats']} />
            {open ? <ExpandLess /> : <ExpandMore />}
          </ListItemButton>
          <Collapse in={open} timeout="auto" unmountOnExit>
            <List component='div' dense disablePadding>
              {[
                minecraft['stat.minecraft.play_time'] + ': ' + dayjs.duration(info.playTime / 20, 'seconds').humanize(),
                lang.playerList.firstPlay + ': ' + dayjs(info.firstPlay).fromNow(),
                lang.playerList.lastPlay + ': ' + dayjs(info.lastOnline).fromNow(),
                minecraft['stat.minecraft.leave_game'] + ': ' + info.quit,
                minecraft['stat.minecraft.deaths'] + ': ' + info.death,
                minecraft['stat.minecraft.player_kills'] + ': ' + info.playerKill,
                minecraft['stat.minecraft.mob_kills'] + ': ' + info.entityKill,
                lang.playerList.tnt + ': ' + info.tnt
              ].map((it, i) => <ListItem key={i} sx={{ pl: 4 }}>
                <ListItemIcon>{icons[i]}</ListItemIcon>
                <ListItemText primary={it} />
              </ListItem>)}
            </List>
          </Collapse>
        </>}
      </List>
      <CardActions disableSpacing sx={{ justifyContent: 'flex-end' }}>
        <Tooltip title={lang.playerList[info.whitelisted ? 'clickToRemoveWhitelist' : 'clickToAddWhitelist']}>
          <IconButton onClick={() => whitelist(name, plugin, refresh, !info.whitelisted)}>
            {info.whitelisted ? <Star color='warning' /> : <StarBorder />}
          </IconButton>
        </Tooltip>
        <Tooltip title={lang.playerList[info.ban == null ? 'clickToBan' : 'clickToPardon']}>
          <IconButton onClick={() => banPlayer(name, plugin, refresh, info.ban == null)}>
            <Block color={info.ban == null ? undefined : 'error'} />
          </IconButton>
        </Tooltip>
      </CardActions>
    </>
    : <></>
})
Example #14
Source File: Profiler.tsx    From NekoMaid with MIT License 4 votes vote down vote up
Timings: React.FC = React.memo(() => {
  const plugin = usePlugin()
  const theme = useTheme()
  const { isTimingsV1 } = useGlobalData()
  const [status, setStatus] = useState(false)
  const [data, setData] = useState<TimingsData | null>(null)
  useEffect(() => {
    const off = plugin.emit('profiler:timingsStatus', setStatus).on('profiler:timings', setData)
    return () => { off() }
  }, [])

  const [tree, entitiesTick, tilesTick] = useMemo(() => {
    if (!data) return []
    const entitiesTickMap: Record<string, { value: number, name: string, count: number }> = {}
    const tilesTickMap: Record<string, { value: number, name: string, count: number }> = {}
    const map: Record<number, [number, number, number, [number, number, number][] | undefined] | undefined> = { }
    data.data.forEach(it => (map[it[0]] = it))
    const createNode = (id: number, percent: number) => {
      const cur = map[id]
      if (!cur) return
      map[id] = undefined
      const [, count, time] = cur
      const handler = data.handlers[id] || [0, lang.unknown]
      const handlerName = data.groups[handler[0]] || lang.unknown
      const name = handler[1]
      const children = cur[cur.length - 1]

      if (isTimingsV1) {
        if (name.startsWith('tickEntity - ')) {
          const came = name.slice(13).replace(/^Entity(Mob)?/, '')
          const entity = decamelize(came)
          const node = entitiesTickMap[entity]
          if (node) {
            node.count += count
            node.value += time
          } else entitiesTickMap[entity] = { count, value: time, name: minecraft['entity.minecraft.' + entity] || came }
        } else if (name.startsWith('tickTileEntity - ')) {
          const came = name.slice(17).replace(/^TileEntity(Mob)?/, '')
          const entity = decamelize(came)
          const node = tilesTickMap[entity]
          if (node) {
            node.count += count
            node.value += time
          } else tilesTickMap[entity] = { count, value: time, name: minecraft['block.minecraft.' + entity] || came }
        }
      } else {
        if (name.startsWith('tickEntity - ') && name.endsWith('ick')) {
          const res = ENTITY_TYPE.exec(name)
          if (res) {
            const node = entitiesTickMap[res[1]]
            if (node) {
              node.count += count
              node.value += time
            } else entitiesTickMap[res[1]] = { count, value: time, name: minecraft['entity.minecraft.' + res[1]] || res[1] }
          }
        } else if (name.startsWith('tickTileEntity - ')) {
          const arr = name.split('.')
          const came = arr[arr.length - 1].replace(/^TileEntity(Mob)?/, '')
          const tile = decamelize(came)
          const node = tilesTickMap[tile]
          if (node) {
            node.count += count
            node.value += time
          } else tilesTickMap[tile] = { count, value: time, name: minecraft['block.minecraft.' + tile] || came }
        }
      }

      return <TreeItem
        key={id}
        nodeId={id.toString()}
        label={<Box sx={{
          '& .info, .count': { color: 'transparent' },
          '&:hover .count': { color: 'inherit' },
          '&:hover .info': {
            color: theme.palette.primary.contrastText,
            textShadow: theme.palette.mode === 'light'
              ? '#000 1px 0 0, #000 0 1px 0, #000 -1px 0 0, #000 0 -1px 0'
              : '#fff 1px 0 0, #fff 0 1px 0, #fff -1px 0 0, #fff 0 -1px 0'
          }
        }}>
          <Box sx={{
            position: 'relative',
            zIndex: 2,
            display: 'flex',
            alignItems: 'center'
          }}>
            {handlerName !== 'Minecraft' && <><Typography color='primary' component='span'>
              {isTimingsV1 ? 'Bukkit' : lang.plugin + ':' + handlerName}</Typography>::</>}
            {name}&nbsp;
            <Typography variant='caption' className='count'>({lang.profiler.timingsCount}: {count})</Typography>
          </Box>
          <Box className='info' sx={{
            position: 'absolute',
            height: 10,
            right: 0,
            top: '50%',
            marginTop: '-5px',
            minWidth: 40,
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center'
          }}>
            <Typography variant='caption' sx={{ position: 'absolute' }}>({Math.round(100 * percent)}%)</Typography>
            <div style={{ width: 100 * percent + 'px' }} className='bar' />
          </Box>
        </Box>}
      >{Array.isArray(children) && children.sort((a, b) => b[2] - a[2]).map(it => createNode(it[0], percent * (it[2] / time)))}</TreeItem>
    }
    // eslint-disable-next-line react/jsx-key
    return [<TreeView defaultCollapseIcon={<ExpandMore />} defaultExpandIcon={<ChevronRight />} defaultExpanded={['1']}>
      {createNode(1, 1)}
    </TreeView>, Object.values(entitiesTickMap), Object.values(tilesTickMap)]
  }, [data])

  return <Container maxWidth={false} sx={{ py: 3 }}>
    <Grid container spacing={3}>
      <Grid item xs={12}>
        <Card>
          <CardHeader title='Timings' sx={{ position: 'relative' }} action={<FormControlLabel
            control={<Switch checked={status} onChange={e => plugin.emit('profiler:timingsStatus', setStatus, e.target.checked)} />}
            label={minecraft['addServer.resourcePack.enabled']}
            sx={cardActionStyles}
          />} />
          <Divider />
          {status
            ? <Box sx={{
              position: 'relative',
              minHeight: data ? undefined : 300,
              '& .bar': { backgroundColor: theme.palette.primary.main, height: 10, marginLeft: 'auto', borderRadius: 2 }
            }}>
              <CircularLoading loading={!data} />
              {tree}
            </Box>
            : <CardContent><Empty title={lang.profiler.timingsNotStarted} /></CardContent>}
        </Card>
      </Grid>
      {data && <Pie title={lang.profiler.entitiesTick} data={entitiesTick!} formatter={countFormatter} />}
      {data && <Pie title={lang.profiler.tilesTick} data={tilesTick!} formatter={countFormatter} />}
    </Grid>
  </Container>
})
Example #15
Source File: Config.tsx    From NekoMaid with MIT License 4 votes vote down vote up
configs.push({
  title: lang.config.serverConfig,
  component () {
    const plugin = usePlugin()
    const globalData = useGlobalData()
    const [flag, update] = useState(0)
    const [info, setInfo] = useState<Record<string, string>>({ })
    const [open, setOpen] = useState(false)
    const [canGetData, setCanGetData] = useState(true)
    const [loading, setLoading] = useState(false)
    const setValue = (field: string, value: any, isGlobal = true) => {
      plugin.emit('server:set', field, value)
      success()
      if (isGlobal) {
        (globalData as any)[field] = value
        update(flag + 1)
        location.reload()
      }
    }
    const createEditButtom = (field: string, isGlobal?: boolean, isInt = true) => <IconButton
      onClick={() => dialog(
        {
          content: lang.inputValue,
          input: isInt
            ? {
                error: true,
                type: 'number',
                helperText: lang.invalidValue,
                validator: (it: string) => /^\d+$/.test(it) && +it >= 0
              }
            : { }
        }).then(res => res != null && setValue(field, isInt ? parseInt(res as any) : (res || null), isGlobal))}
    ><Edit /></IconButton>

    const infoElm: JSX.Element[] = []
    for (const key in info) {
      const name = (lang.config as any)[key]
      infoElm.push(<ListItem key={key} sx={{ pl: 4 }}>
        <ListItemText
          primary={key === 'isAikarFlags' ? <Link href='https://mcflags.emc.gs' target='_blank' rel='noopener'>{name}</Link> : name}
          secondary={info[key].toString()}
        />
      </ListItem>)
    }

    return <List>
      <CircularLoading loading={loading} />
      <ListItem secondaryAction={globalData.canSetMaxPlayers
        ? createEditButtom('maxPlayers')
        : undefined}>
        <ListItemText primary={lang.config.maxPlayers + ': ' + globalData.maxPlayers} />
      </ListItem>
      <ListItem secondaryAction={createEditButtom('spawnRadius')}>
        <ListItemText primary={lang.config.spawnRadius + ': ' + globalData.spawnRadius} />
      </ListItem>
      <ListItem secondaryAction={createEditButtom('motd', false, false)}>
        <ListItemText primary={lang.config.motd} />
      </ListItem>
      <ListItem secondaryAction={<Switch checked={globalData.hasWhitelist} onChange={e => setValue('hasWhitelist', e.target.checked)} />}>
        <ListItemText primary={lang.config.whitelist} />
      </ListItem>
      {canGetData && <>
        <ListItemButton onClick={() => {
          if (infoElm.length) setOpen(!open)
          else {
            setLoading(true)
            plugin.emit('server:fetchInfo', (data: any) => {
              setLoading(false)
              if (!data) {
                failed(lang.unsupported)
                setCanGetData(false)
                return
              }
              setInfo(data)
              setOpen(true)
            })
          }
        }}>
        <ListItemIcon><Equalizer /></ListItemIcon>
          <ListItemText primary={lang.info} />
          {open ? <ExpandLess /> : <ExpandMore />}
        </ListItemButton>
        <Collapse in={open} timeout='auto' unmountOnExit>
          <List component='div' dense disablePadding>{infoElm}</List>
        </Collapse>
      </>}
    </List>
  }
},
{
  title: lang.history,
  component () {
    const [cur, update] = useState(0)
    const list: ServerRecord[] = JSON.parse(localStorage.getItem('NekoMaid:servers') || '[]')
    return <List>
      {list.sort((a, b) => b.time - a.time).map(it => {
        const i = it.address.indexOf('?')
        return <ListItem
          disablePadding
          key={it.address}
          secondaryAction={<IconButton edge='end' size='small' onClick={() => {
            localStorage.setItem('NekoMaid:servers', JSON.stringify(list.filter(s => s.address !== it.address)))
            success()
            update(cur + 1)
          }}><Delete /></IconButton>}
        >
          <ListItemButton onClick={() => {
            location.hash = ''
            location.search = it.address
          }} dense>
            <ListItemAvatar><Avatar src={it.icon} variant='rounded'><HelpOutline /></Avatar></ListItemAvatar>
            <ListItemText primary={<Tooltip title={it.address.slice(i + 1)}>
              <span>{it.address.slice(0, i)}</span></Tooltip>} secondary={dayjs(it.time).fromNow()} />
          </ListItemButton>
        </ListItem>
      })}
    </List>
  }
},
{
  title: lang.config.theme,
  component () {
    const color = localStorage.getItem('NekoMaid:color') || 'blue'
    return <CardContent sx={{ textAlign: 'center' }}>
      <Box>
        <ToggleButtonGroup exclusive value={localStorage.getItem('NekoMaid:colorMode') || ''} onChange={(_, it) => {
          localStorage.setItem('NekoMaid:colorMode', it)
          location.reload()
        }}>
          <ToggleButton value='light'><Brightness7 /> {lang.config.light}</ToggleButton>
          <ToggleButton value=''><SettingsBrightness /> {lang.config.system}</ToggleButton>
          <ToggleButton value='dark'><Brightness4 /> {lang.config.dark}</ToggleButton>
        </ToggleButtonGroup>
      </Box>
      <Paper sx={{ marginTop: 2, width: '176px', overflow: 'hidden', display: 'inline-block' }}>
        {Object.keys(colors).slice(1, 17).map((key, i) => {
          const checked = color === key
          const elm = <Box
            key={key}
            onClick={() => {
              localStorage.setItem('NekoMaid:color', key)
              location.reload()
            }}
            sx={{
              backgroundColor: (colors as any)[key][600],
              width: '44px',
              height: '44px',
              display: 'inline-block',
              cursor: 'pointer'
            }}
          ><Check htmlColor='white' sx={{ top: '10px', position: 'relative', opacity: checked ? 1 : 0 }} /></Box>
          return (i + 1) % 4 === 0 ? <React.Fragment key={key}>{elm}<br /></React.Fragment> : elm
        })}
      </Paper>
    </CardContent>
  }
})
Example #16
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 #17
Source File: BlockEditor.tsx    From NekoMaid with MIT License 4 votes vote down vote up
BlockEditor: React.FC = () => {
  const theme = useTheme()
  const plugin = usePlugin()
  const his = useHistory()
  const loc = useLocation()
  const globalData = useGlobalData()
  const drawerWidth = useDrawerWidth()
  const [block, setBlock] = useState<Block>()
  const [types, setTypes] = useState<string[]>([])
  const [worlds, setWorlds] = useState<string[]>([])
  const params = { world: '', x: 0, y: 0, z: 0 }
  if (loc.pathname.startsWith('/NekoMaid/block/')) {
    const arr = loc.pathname.split('/')
    if (arr.length > 6) {
      params.world = arr[3]
      params.x = +arr[4]
      params.y = +arr[5]
      params.z = +arr[6]
    } else his.push('/NekoMaid/block')
  }
  useEffect(() => {
    const off = plugin.emit('item:blocks', (types: string[], worlds: string[]) => {
      setTypes(types)
      setWorlds(worlds)
    })
      .on('block:select', (world, x, y, z) => his.push(`/NekoMaid/block/${world}/${x}/${y}/${z}`))
    return () => void off()
  }, [])
  const update = () => {
    if (params.world) {
      plugin.emit('block:fetch', (block: Block) => {
        if (!block) {
          failed()
          his.push('/NekoMaid/block')
          return
        }
        if (globalData.hasNBTAPI && block.nbt) block.nbt = stringify(parse(block.nbt), { pretty: true })
        setBlock(block)
      }, params.world, params.x, params.y, params.z)
    }
  }
  const updateWithAction = (res: boolean) => {
    action(res)
    update()
  }
  useEffect(update, [params.world, params.x, params.y, params.z])
  return <Box sx={{ minHeight: '100%', py: 3 }}>
    <Toolbar />
    <Container maxWidth={false}>
      <Grid container spacing={3} sx={{ width: { sm: `calc(100vw - ${drawerWidth}px - ${theme.spacing(3)})` } }}>
        <Grid item lg={6} md={12} xl={6} xs={12}>
          <Card sx={{ '& .CodeMirror-dialog, .CodeMirror-scrollbar-filler': { backgroundColor: theme.palette.background.paper + '!important' } }}>
            <CardHeader
              title={lang.blockEditor.title}
              sx={{ position: 'relative' }}
              action={<Box sx={cardActionStyles}>
                <IconButton
                  size='small'
                  disabled={!block || (!block.data && !block.nbt)}
                  onClick={() => block && plugin.emit('block:save', (res: boolean) => {
                    action(res)
                    update()
                  }, params.world, params.x, params.y, params.z, block.nbt || null, block.data || null)}
                ><Save /></IconButton>
                <IconButton
                  size='small'
                  disabled={!block}
                  onClick={() => {
                    update()
                    success()
                  }}
                ><Refresh /></IconButton>
              </Box>}
            />
            <Divider />
            {block
              ? <>
                <CardContent sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}>
                  <ItemViewer item={{ type: block.type }} />
                  <Autocomplete
                    options={types}
                    sx={{ maxWidth: 300, marginLeft: 1, flexGrow: 1 }}
                    value={block.type}
                    onChange={(_, it) => it && plugin.emit('block:type', (res: boolean) => {
                      action(res)
                      update()
                    }, params.world, params.x, params.y, params.z, (block.type = it))}
                    getOptionLabel={it => {
                      const locatedName = getName(it.toLowerCase())
                      return (locatedName ? locatedName + ' ' : '') + it
                    }}
                    renderInput={(params) => <TextField {...params} label={lang.itemEditor.itemType} size='small' variant='standard' />}
                  />
                </CardContent>
                {block.data != null && <Accordion sx={{ '&::before': { opacity: '1!important' } }} disableGutters>
                  <AccordionSummary expandIcon={<ExpandMore />}><Typography>{lang.data}</Typography></AccordionSummary>
                  <AccordionDetails sx={{ padding: 0, '& .CodeMirror': { width: '100%', height: 350 } }}>
                    <UnControlled
                      value={block.data}
                      options={{
                        mode: 'javascript',
                        phrases: lang.codeMirrorPhrases,
                        theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
                      }}
                      onChange={(_: any, __: any, data: string) => (block.data = data)}
                    />
                  </AccordionDetails>
                </Accordion>}
                {block.nbt != null && <Accordion sx={{ '&::before': { opacity: '1!important', display: '!important' } }} disableGutters>
                  <AccordionSummary expandIcon={<ExpandMore />}><Typography>NBT</Typography></AccordionSummary>
                  <AccordionDetails sx={{ padding: 0, '& .CodeMirror': { width: '100%', height: 350 } }}>
                    <UnControlled
                      value={block.nbt}
                      options={{
                        mode: 'javascript',
                        phrases: lang.codeMirrorPhrases,
                        theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
                      }}
                      onChange={(_: any, __: any, data: string) => (block.nbt = data)}
                    />
                  </AccordionDetails>
                </Accordion>}
              </>
              : <CardContent>{worlds.length ? <BlockSelector worlds={worlds} /> : <Empty />}</CardContent>}
          </Card>
        </Grid>
        {block?.inventory?.length
          ? <Grid item lg={6} md={12} xl={6} xs={12}>
            <Card>
              <CardHeader
                title={minecraft[('container.' + block.inventoryType || '').toLowerCase()] || lang.blockEditor.container}
                sx={{ position: 'relative' }}
              />
              <Divider />
              <CardContent sx={{ whiteSpace: 'nowrap', overflowX: 'auto', textAlign: 'center' }}>
                {block.inventory.map((it, i) => <React.Fragment key={i}><ItemViewer
                  item={it}
                  data={{ type: InvType.BLOCK, solt: i, ...params }}
                  onDrag={() => plugin.emit('block:setItem', update, params.world, params.x, params.y, params.z, i, null, -1)}
                  onDrop={(item, obj) => plugin.emit('block:setItem', update, params.world, params.x, params.y, params.z, i,
                    JSON.stringify(item), compare(obj, params) ? obj.solt : -1)}
                  onEdit={item => item !== false && plugin.emit('block:setItem', updateWithAction, params.world, params.x, params.y,
                    params.z, i, item && JSON.stringify(item), -1)}
                />{!((i + 1) % 9) && <br />}</React.Fragment>)}
              </CardContent>
            </Card>
          </Grid>
          : undefined}
      </Grid>
    </Container>
  </Box>
}
Example #18
Source File: WidgetsSidebar.tsx    From fluttertemplates.dev with MIT License 4 votes vote down vote up
function SingleSubGroupRenderer(props: SingleSubGroupRendererProps) {
  const theme = useTheme();

  const [expanded, setExpanded] = useState(false);
  const [isSelected, setIsSelected] = useState(false);

  const handleChange = () => {
    setExpanded(!expanded);
  };
  useEffect(() => {
    setExpanded(props.selectedSubGroup?.id === props.subgroup.id);
    setIsSelected(props.selectedSubGroup?.id === props.subgroup.id);
  }, [props.selectedSubGroup]);

  return (
    <>
      <Grid
        container
        style={{
          borderRadius: `0 1rem 1rem 0`,
          backgroundColor: isSelected
            ? `${theme.palette.secondary.main}10`
            : "",
        }}
      >
        <Box sx={{ flexGrow: 1 }}>
          <Link
            href={`/widgets/${props.subgroup.id}`}
            replace
            key={`sub_group_${props.subgroup.id}`}
          >
            <a>
              <ListItem
                button
                style={{
                  borderRadius: "0 1rem 1rem 0",
                }}
              >
                <Typography
                  variant="body2"
                  style={{
                    color: isSelected ? theme.palette.secondary.main : "",
                  }}
                >
                  {props.subgroup.title}
                </Typography>
              </ListItem>
            </a>
          </Link>
        </Box>
        <Grid item>
          <IconButton onClick={() => handleChange()}>
            {expanded ? <ExpandLess /> : <ExpandMore />}
          </IconButton>
        </Grid>
      </Grid>

      <AnimatePresence initial={false}>
        {expanded && (
          <motion.section
            key="content"
            initial="collapsed"
            animate="open"
            exit="collapsed"
            variants={{
              open: { opacity: 1, height: "auto" },
              collapsed: { opacity: 0, height: 0 },
            }}
          >
            <div
              style={{
                marginLeft: "1rem",
                borderRadius: "0 0 1rem 1rem",
              }}
            >
              {props.subgroup.widgets.map((widget, index) => (
                <Link
                  href={
                    isSelected
                      ? `#${widget.id.split("/").slice(-1)[0]}`
                      : `/widgets/${props.subgroup.id}#${
                          widget.id.split("/").slice(-1)[0]
                        }`
                  }
                  replace={!isSelected}
                  scroll={true}
                  key={`sub_group_${props.subgroup.id}_widget_${index}`}
                >
                  <a>
                    <ListItem
                      button
                      style={{
                        borderRadius: "1rem",
                      }}
                    >
                      <Typography variant="body2">
                        {index + 1}. {widget.title}
                      </Typography>
                    </ListItem>
                  </a>
                </Link>
              ))}
            </div>
          </motion.section>
        )}
      </AnimatePresence>
    </>
  );
}