@mui/material#Autocomplete TypeScript Examples

The following examples show how to use @mui/material#Autocomplete. 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: ServerSwitch.tsx    From NekoMaid with MIT License 5 votes vote down vote up
ServerSwitch: React.FC<DialogProps> = props => {
  const [value, setValue] = useState<string>('')
  let error = false
  // eslint-disable-next-line no-new
  try { if (value) new URL(value.startsWith('http://') ? value : 'http://' + value) } catch { error = true }
  return <Dialog fullWidth maxWidth='xs' {...props}>
    <DialogTitle>{lang.serverSwitch.title}</DialogTitle>
    <DialogContent sx={{ overflow: 'hidden' }}>
      <Autocomplete
        freeSolo
        inputValue={value}
        clearOnBlur={false}
        onInputChange={(_: any, v: string) => setValue(v)}
        noOptionsText={lang.serverSwitch.noServer}
        style={{ width: '100%', maxWidth: 500, marginTop: 10 }}
        options={JSON.parse(localStorage.getItem('NekoMaid:servers') || '[]')}
        getOptionLabel={(option: any) => option.address}
        renderInput={(props: any) => <TextField
          {...props}
          error={error}
          label={minecraft['addServer.enterIp']}
          helperText={error ? lang.serverSwitch.wrongHostname : undefined}
        />}
      />
      <DialogContentText>{lang.serverSwitch.content}</DialogContentText>
    </DialogContent>
    <DialogActions>
      <Button
        color='primary'
        disabled={error}
        onClick={() => (location.search = '?' + encodeURIComponent(value))}
      >{lang.serverSwitch.connect}</Button>
    </DialogActions>
  </Dialog>
}
Example #2
Source File: PluginBlock.tsx    From Cromwell with MIT License 5 votes vote down vote up
export function PluginBlockSidebar(props: TBlockMenuProps) {
    const data = props.block?.getData();
    // const pluginInfo = props.plugins?.find(p => p.name === data?.plugin?.pluginName);
    const forceUpdate = useForceUpdate();
    const pluginBlocks = getPluginBlocks();
    const pluginProps: any = props.block.getContentInstance().props;

    const currentId = getPluginBlockId(
        data?.plugin?.pluginName ?? pluginProps.pluginName ?? '',
        data?.plugin?.blockName ?? pluginProps.blockName ?? '',
    );
    const currentBlock = pluginBlocks.find(b => getPluginBlockId(b.pluginName, b.blockName) === currentId);

    useEffect(() => {
        onPluginBlockRegister(() => {
            forceUpdate();
        });
    }, []);

    const handleChange = (event: any, newValue: TPluginBlockOptions | null) => {
        if (newValue?.pluginName && newValue?.blockName) {
            const data = props.block?.getData();
            if (!data.plugin) data.plugin = {};
            data.plugin.pluginName = newValue.pluginName;
            data.plugin.blockName = newValue.blockName;
            props.modifyData?.(data);
            forceUpdate();
        }
    }

    const handleChangeInstanceSettings = (settings: any) => {
        if (!data.plugin) data.plugin = {};
        data.plugin.instanceSettings = settings;
        props?.modifyData?.(data);
        forceUpdate?.();
    }

    return (
        <div>
            <div className={styles.settingsHeader}>
                <PluginIcon className={styles.customIcon} />
                {props.isGlobalElem(props.getBlockElementById(data?.id)) && (
                    <div className={styles.headerIcon}>
                        <Tooltip title="Global block">
                            <PublicIcon />
                        </Tooltip>
                    </div>
                )}
                <h3 className={styles.settingsTitle}>Plugin settings</h3>
            </div>
            <Autocomplete
                onChange={handleChange}
                options={pluginBlocks ?? []}
                value={(pluginBlocks ?? []).find(p => getPluginBlockId(p.pluginName, p.blockName) === currentId)}
                getOptionLabel={(option) => option.blockName}
                renderInput={(params) => <TextField {...params}
                    placeholder="Plugin"
                />}
            />
            {currentBlock?.component && (
                <currentBlock.component
                    {...{
                        ...props,
                        forceUpdate,
                        instanceSettings: data?.plugin?.instanceSettings ?? {},
                        changeInstanceSettings: handleChangeInstanceSettings,
                    }}
                />
            )}
            <StylesEditor
                forceUpdate={forceUpdate}
                blockProps={props}
            />
        </div>
    );
}
Example #3
Source File: NativeSelect.tsx    From GTAV-NativeDB with MIT License 5 votes vote down vote up
function NativeSelect({ value, onChange, sx }: NativeSelectProps) {
  const natives = useNatives()
  const nativesArray = useMemo(() => Object.values(natives), [natives])

  return (
    <Autocomplete
      options={nativesArray}
      value={natives[value]}
      onChange={(e, native) => native && onChange(native.hash)}
      renderInput={(params) => (
        <TextField 
          {...params} 
          label="Preview Native" 
        />
      )}
      renderOption={(props, option) => (
        <li {...props}>
          <Typography noWrap>{option.name}</Typography>
        </li>
      )}
      ListboxComponent={
        ListboxComponent as React.ComponentType<React.HTMLAttributes<HTMLElement>>
      }
      getOptionLabel={(native) => native.name}
      sx={sx}
      disableClearable
      disableListWrap
      fullWidth
    />
  )
}
Example #4
Source File: ArtifactAutocomplete.tsx    From genshin-optimizer with MIT License 5 votes vote down vote up
function ArtifactSingleAutocomplete<T extends ArtifactSingleAutocompleteKey>({ allArtifactKeys, selectedArtifactKey, setArtifactKey, getName, getImage, label, disable= () => false, showDefault = false, defaultText = "", defaultIcon = "", flattenCorners = false, ...props }:
  ArtifactSingleAutocompleteProps<T>) {
  const theme = useTheme();

  const options = useMemo(() =>
    (showDefault
      ? [{ key: "" as T, label: defaultText }]
      : []
    ).concat(allArtifactKeys.map(key => (
      { key: key, label: getName(key) }
    ))), [allArtifactKeys, getName, defaultText, showDefault])
  return <Autocomplete
    autoHighlight
    options={options}
    value={{ key: selectedArtifactKey, label: getName(selectedArtifactKey) }}
    onChange={(_, newValue) => setArtifactKey(newValue ? newValue.key : "")}
    getOptionLabel={(option) => option.label ? option.label : defaultText}
    isOptionEqualToValue={(option, value) => option.key === value.key}
    getOptionDisabled={option => option.key ? disable(option.key) : false}
    renderInput={(props) => <SolidColoredTextField
      {...props}
      label={label}
      startAdornment={getImage(selectedArtifactKey)}
      hasValue={selectedArtifactKey ? true : false}
      flattenCorners={flattenCorners}
    />}
    renderOption={(props, option) => (
      <MenuItemWithImage
        key={option.key}
        value={option.key}
        image={getImage(option.key)}
        text={option.label}
        theme={theme}
        isSelected={selectedArtifactKey === option.key}
        props={props}
      />
    )}
    {...props}
  />
}
Example #5
Source File: GroupMemberSelect.tsx    From abrechnung with GNU Affero General Public License v3.0 5 votes vote down vote up
export default function GroupMemberSelect({
    group,
    onChange,
    value = null,
    disabled = false,
    noDisabledStyling = false,
    className = null,
    ...props
}) {
    const members = useRecoilValue(groupMembers(group.id));
    const memberIDToUsername = useRecoilValue(groupMemberIDsToUsername(group.id));

    return (
        <Autocomplete
            options={members.map((m) => m.user_id)}
            getOptionLabel={(option) => memberIDToUsername[option]}
            value={value}
            disabled={disabled}
            openOnFocus
            fullWidth
            PopperComponent={StyledAutocompletePopper}
            className={className}
            onChange={(event, newValue) => onChange(newValue)}
            renderOption={(props, user_id) => (
                <Box component="li" {...props}>
                    <Typography variant="body2" component="span" sx={{ ml: 1 }}>
                        {memberIDToUsername[user_id]}
                    </Typography>
                </Box>
            )}
            renderInput={
                noDisabledStyling
                    ? (params) => <DisabledTextField variant="standard" {...params} {...props} />
                    : (params) => <TextField variant="standard" {...params} {...props} />
            }
        />
    );
}
Example #6
Source File: AccountSelect.tsx    From abrechnung with GNU Affero General Public License v3.0 5 votes vote down vote up
export default function AccountSelect({
    group,
    onChange,
    value = null,
    exclude = null,
    disabled = false,
    noDisabledStyling = false,
    className = null,
    ...props
}) {
    const accounts = useRecoilValue(accountsSeenByUser(group.id));
    const filteredAccounts =
        exclude !== null ? accounts.filter((account) => exclude.indexOf(account.id) < 0) : accounts;

    return (
        <Autocomplete
            options={filteredAccounts}
            getOptionLabel={(option) => option.name}
            value={value}
            disabled={disabled}
            openOnFocus
            fullWidth
            PopperComponent={StyledAutocompletePopper}
            disableClearable
            className={className}
            onChange={(event, newValue) => onChange(newValue)}
            renderOption={(props, option) => (
                <Box component="li" {...props}>
                    {option.type === "personal" ? <Person /> : <CompareArrows />}
                    <Typography variant="body2" component="span" sx={{ ml: 1 }}>
                        {option.name}
                    </Typography>
                </Box>
            )}
            renderInput={
                noDisabledStyling
                    ? (params) => <DisabledTextField variant="standard" {...params} {...props} />
                    : (params) => <TextField variant="standard" {...params} {...props} />
            }
        />
    );
}
Example #7
Source File: Order.tsx    From Cromwell with MIT License 4 votes vote down vote up
OrderPage = () => {
    const { id: orderId } = useParams<{ id: string }>();
    const client = getGraphQLClient();
    const [data, setData] = useState<TOrder | null>(null);
    const [notFound, setNotFound] = useState(false);
    const [isCartUpdated, setIsCartUpdated] = useState(false);
    const [orderLoading, setOrderLoading] = useState<boolean>(false);
    const forceUpdate = useForceUpdate();
    const cstoreRef = useRef(getCStore({ local: true }));
    const cstore = cstoreRef.current;
    const cart = cstore.getCart();

    const cartInfo = cstore.getCartTotal();
    const updatedCartTotal = cartInfo.total ?? 0;
    const updatedOrderTotalPrice = parseFloat((updatedCartTotal +
        (data?.shippingPrice ?? 0)).toFixed(2));

    // Support old and new address format
    const { addressString, addressJson } = parseAddress(data?.customerAddress);

    useEffect(() => {
        getOrderData();
    }, []);

    const getOrderData = async () => {
        let orderData: TOrder;
        setOrderLoading(true);
        try {
            orderData = await client.getOrderById(parseInt(orderId), gql`
            fragment AdminOrderFragment on Order {
                ...OrderFragment
                coupons {
                    ...CouponFragment
                }
            }
            ${client.CouponFragment}
            ${client.OrderFragment}
        `, 'AdminOrderFragment');
            if (orderData) {
                setData(orderData);
                updateCart(orderData);
            }
        } catch (e) {
            console.error(e)
        }
        setOrderLoading(false);

        if (!orderData) {
            setNotFound(true);
        }
    }

    const updateCart = async (order: TOrder) => {
        let oldCart = order.cart;
        if (typeof oldCart === 'string') {
            try {
                oldCart = JSON.parse(oldCart);
            } catch (e) { console.error(e); }
        }
        if (!Array.isArray(oldCart) || oldCart.length === 0) {
            oldCart = [];
        }

        oldCart.forEach(product => cstore.addToCart(product));

        if (order?.coupons?.length) {
            cstore.setCoupons(order.coupons);
        }

        const cart = cstore.getCart();
        return cart;
    }

    const handleDeleteFromCart = (item: TStoreListItem) => {
        cstore.removeFromCart(item);
        setIsCartUpdated(true);
        forceUpdate();
    }

    const handleSave = async () => {
        if (data) {
            const inputData: TOrderInput = {
                status: data.status,
                cart: JSON.stringify(cart),
                orderTotalPrice: isCartUpdated ? updatedOrderTotalPrice : data.orderTotalPrice,
                cartTotalPrice: isCartUpdated ? updatedCartTotal : data.cartTotalPrice,
                cartOldTotalPrice: data.cartOldTotalPrice,
                shippingPrice: data.shippingPrice,
                totalQnt: data.totalQnt,
                userId: data.userId,
                customerName: data.customerName,
                customerPhone: data.customerPhone,
                customerEmail: data.customerEmail,
                customerAddress: data.customerAddress,
                customerComment: data.customerComment,
                shippingMethod: data.shippingMethod,
                paymentMethod: data.paymentMethod,
                currency: data.currency,
                couponCodes: cstore.getCoupons()?.map(c => c.code) ?? [],
            }
            try {
                await client?.updateOrder(data.id, inputData);
                await getOrderData();
                toast.success('Saved!');
            } catch (e) {
                toast.error('Failed to save');
                console.error(e)
            }
        }
    }

    const handleInputChange = (prop: keyof TOrder, val: any) => {
        if (data) {
            setData((prevData) => {
                const newData = Object.assign({}, prevData);
                (newData[prop] as any) = val;
                return newData;
            });
        }
    }

    if (notFound) {
        return (
            <div className={styles.OrderPage}>
                <div className={styles.notFoundPage}>
                    <p className={styles.notFoundText}>Order not found</p>
                </div>
            </div>
        )
    }

    return (
        <div className={styles.OrderPage}>
            <div className={styles.header}>
                <div className={styles.headerLeft}>
                    <IconButton
                        onClick={() => window.history.back()}
                    >
                        <ArrowBackIcon style={{ fontSize: '18px' }} />
                    </IconButton>
                    <p className={commonStyles.pageTitle}>order</p>
                </div>
                <div className={styles.headerActions}>
                    <Button variant="contained" color="primary"
                        className={styles.saveBtn}
                        size="small"
                        onClick={handleSave}>Update</Button>
                </div>
            </div>
            <div className={styles.content}>

                <div className={styles.fields}>
                    {orderLoading && (
                        Array(8).fill(1).map((it, index) => (
                            <Skeleton style={{ marginBottom: '10px' }} key={index} height={"50px"} />
                        ))
                    )}
                    {!orderLoading && (
                        <Grid container spacing={3}>
                            <Grid item xs={12} sm={6}>
                                <Autocomplete
                                    value={data?.status ?? orderStatuses[0]}
                                    onChange={(event: any, newValue: string | null) => {
                                        handleInputChange('status', newValue);
                                    }}
                                    classes={{ paper: styles.popper }}
                                    options={orderStatuses}
                                    getOptionLabel={(option) => option}
                                    className={styles.textField}
                                    fullWidth
                                    renderInput={(params) => <TextField {...params}
                                        label="Status"
                                        variant="standard"
                                        fullWidth
                                    />}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Name"
                                    value={data?.customerName || ''}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                    onChange={(e) => { handleInputChange('customerName', e.target.value) }}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Phone"
                                    value={data?.customerPhone || ''}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                    onChange={(e) => { handleInputChange('customerPhone', e.target.value) }}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Email"
                                    value={data?.customerEmail || ''}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                    onChange={(e) => { handleInputChange('customerEmail', e.target.value) }}
                                />
                            </Grid>
                            {!addressJson && (
                                <Grid item xs={12} sm={12}>
                                    <TextField label="Address"
                                        value={addressString || ''}
                                        fullWidth
                                        variant="standard"
                                        className={styles.textField}
                                        onChange={(e) => { handleInputChange('customerAddress', e.target.value) }}
                                    />
                                </Grid>
                            )}
                            {addressJson && (
                                Object.entries<any>(addressJson).map(([fieldKey, value]) => {
                                    return (
                                        <Grid item xs={12} sm={6} key={fieldKey}>
                                            <TextField label={fieldKey}
                                                value={value || ''}
                                                fullWidth
                                                variant="standard"
                                                className={styles.textField}
                                                onChange={(e) => {
                                                    const newVal = e.target.value;
                                                    handleInputChange('customerAddress', JSON.stringify({
                                                        ...addressJson,
                                                        [fieldKey]: newVal,
                                                    }))
                                                }}
                                            />
                                        </Grid>
                                    )
                                }))}
                            <Grid item xs={12} sm={12}>
                                <TextField label="Comment"
                                    value={data?.customerComment || ''}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                    onChange={(e) => { handleInputChange('customerComment', e.target.value) }}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Payment method"
                                    disabled
                                    value={data?.paymentMethod}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Shipping method"
                                    disabled
                                    value={data?.shippingMethod}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Shipping price"
                                    value={data?.shippingPrice ?? 0}
                                    className={styles.textField}
                                    variant="standard"
                                    onChange={(e) => {
                                        let newPrice = Number(e.target.value);
                                        if (!e.target.value) newPrice = 0;
                                        if (!isNaN(newPrice)) handleInputChange('shippingPrice',
                                            newPrice);
                                        setIsCartUpdated(true);
                                    }}
                                    fullWidth
                                    InputProps={{
                                        inputComponent: NumberFormatCustom as any,
                                    }}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Created"
                                    value={toLocaleDateTimeString(data?.createDate)}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                />
                            </Grid>
                            <Grid item xs={12} sm={6}>
                                <TextField label="Last updated"
                                    value={toLocaleDateTimeString(data?.updateDate)}
                                    fullWidth
                                    variant="standard"
                                    className={styles.textField}
                                />
                            </Grid>
                        </Grid>
                    )}
                </div>
                {!!cstore.getCoupons()?.length && (
                    <div className={styles.fields}>
                        <p>Applied coupons</p>
                        {cstore.getCoupons().map(coupon => (
                            <Box key={coupon.id}
                                sx={{ display: 'flex', alignItems: 'center', mt: 2, }}
                            >
                                <Link to={`${couponPageInfo.baseRoute}/${coupon.id}`} >
                                    <Box sx={{ mr: 2, border: '1px dashed #222', py: '5px', px: '10px', borderRadius: '6px', cursor: 'pointer' }}
                                    >{coupon.code}</Box>
                                </Link>
                                {coupon.discountType === 'fixed' && (
                                    <Box>{cstore.getPriceWithCurrency(coupon.value)}</Box>
                                )}
                                {coupon.discountType === 'percentage' && (
                                    <Box>{coupon.value}%</Box>
                                )}
                                <IconButton onClick={() => {
                                    cstore.setCoupons(cstore.getCoupons()
                                        .filter(c => c.id !== coupon.id));
                                    setIsCartUpdated(true);
                                    forceUpdate();
                                }}
                                    sx={{ ml: 2 }}
                                >
                                    <CloseIcon />
                                </IconButton>
                            </Box>
                        ))}
                    </div>
                )}
                <div className={styles.sums}>
                    {!orderLoading && data && (
                        <>
                            <p>Cart total: <b>{cstore.getPriceWithCurrency(isCartUpdated ?
                                updatedCartTotal : data.cartTotalPrice)}</b></p>
                            <p>Shipping: <b>{cstore.getPriceWithCurrency(data.shippingPrice ?? 0)}</b></p>
                            <p>Order total: <b>{cstore.getPriceWithCurrency(isCartUpdated ?
                                updatedOrderTotalPrice : data.orderTotalPrice)}</b></p>
                        </>
                    )}
                    {orderLoading && (
                        Array(4).fill(1).map((it, index) => (
                            <Skeleton style={{ marginBottom: '3px' }} key={index} height={"20px"} />
                        ))
                    )}
                </div>
                <div className={styles.cart}>
                    {!orderLoading && cart.map((it, i) => {
                        const product = it.product;
                        const checkedAttrKeys = Object.keys(it.pickedAttributes || {});
                        if (product) {
                            const productLink = `${productPageInfo.baseRoute}/${product.id}`;
                            return (
                                <Grid key={i} className={styles.cartItem} container>
                                    <Grid item xs={2} className={styles.itemBlock}>
                                        <Link to={productLink}>
                                            <img src={product.mainImage} className={styles.mainImage} />
                                        </Link>
                                    </Grid>
                                    <Grid item xs={4} className={styles.itemBlock}>
                                        <Link to={productLink} className={styles.productName}>{product.name}</Link>
                                        <div className={styles.priceBlock}>
                                            {(product?.oldPrice !== undefined && product?.oldPrice !== null) && (
                                                <p className={styles.oldPrice}>{cstore.getPriceWithCurrency(product.oldPrice)}</p>
                                            )}
                                            <p className={styles.price}>{cstore.getPriceWithCurrency(product?.price)}</p>
                                        </div>
                                    </Grid>
                                    <Grid item xs={3} className={styles.itemBlock}>
                                        {checkedAttrKeys.map(key => {
                                            const vals = it.pickedAttributes ? it.pickedAttributes[key] : [];
                                            const valsStr = vals.join(', ');
                                            return <p key={key}>{key}: {valsStr}</p>
                                        })}
                                    </Grid>
                                    <Grid item xs={2} className={styles.itemBlock}>
                                        <p>Qty: {it.amount}</p>
                                    </Grid>
                                    <Grid item xs={1} className={styles.itemBlock} style={{ marginLeft: 'auto', paddingRight: '0px' }}>
                                        <div>
                                            <IconButton
                                                aria-label="Delete"
                                                onClick={() => { handleDeleteFromCart(it); }}
                                            >
                                                <DeleteForeverIcon />
                                            </IconButton>
                                        </div>
                                    </Grid>
                                </Grid>
                            )
                        }
                    })}
                    {orderLoading && (
                        Array(2).fill(1).map((it, index) => (
                            <Skeleton style={{ margin: '0 20px 5px 20px' }} key={index} height={"60px"} />
                        ))
                    )}
                </div>
            </div>
        </div >
    )
}
Example #8
Source File: Terminal.tsx    From NekoMaid with MIT License 4 votes vote down vote up
Terminal: React.FC = () => {
  const logs = useMemo<JSX.Element[]>(() => [], [])
  const ref = useRef<HTMLDivElement | null>(null)
  const plugin = usePlugin()
  const [, update] = useState(0)
  const [open, setOpen] = useState(false)
  const [command, setCommand] = useState('')
  const [suggestions, setSuggestions] = useState<Array<[string, boolean] | [string]>>([])
  const getSuggestions = useMemo(() => throttle(
    (it: string) => {
      let cmd = it.substr(0, it.lastIndexOf(' '))
      if (cmd) cmd += ' '
      return plugin.emit('console:complete', (data: string[] = []) => {
        setSuggestions(JSON.parse(localStorage.getItem(`NekoMaid:${address}:commandHistory`) || '[]').concat(data.map(c => [cmd + c] as [string])))
      }, it)
    },
    500
  ), [])
  const scrollToEnd = useMemo(() => throttle(
    (elm: HTMLDivElement) => {
      const select = (window.getSelection || document.getSelection)()
      if (select && !select.isCollapsed) {
        let node = select?.anchorNode
        while (node && node !== document) {
          if (node === elm) return
          node = node.parentNode
        }
      }
      if (elm) elm.lastElementChild?.scrollIntoView()
    },
    300
  ), [])
  const execCommand = () => {
    if (!command) return
    plugin.emit('console:run', action, command)
    const arr = JSON.parse(localStorage.getItem(`NekoMaid:${address}:commandHistory`) || '[]').filter((it: [string]) => it[0] !== command)
    if (arr.length === 5) arr.pop()
    arr.unshift([command, true])
    localStorage.setItem(`NekoMaid:${address}:commandHistory`, JSON.stringify(arr))
    setCommand('')
  }

  useEffect(() => {
    const runCommand = (it: string) => plugin.emit('console:run', action, it)
    let lastLog: Log = {} as any
    const onLog = (data: Log) => {
      if (lastLog.logger === data.logger && (lastLog.time / 100 | 0) === (data.time / 100 | 0) &&
        lastLog.level === data.level && (!lastLog.components === !data.components)) {
        logs.pop()
        if (data.components) {
          lastLog.components!.push(null as any)
          lastLog.components = lastLog.components!.concat(data.components)
        } else lastLog.msg += '\n' + data.msg
        data = lastLog
      } else lastLog = data
      logs.push(parseLog(data, runCommand, setCommand))
      update(++i)
    }
    const offLogs = plugin.on('console:logs', (it: Log[]) => {
      logs.length = 0
      it.forEach(onLog)
    })
    const offLog = plugin.on('console:log', onLog)
    plugin.switchPage('console')
    return () => {
      offLogs()
      offLog()
    }
  }, [])

  useEffect(() => { ref.current && scrollToEnd(ref.current) }, [logs[logs.length - 1]])

  return <Box sx={{
    width: '100%',
    height: '100vh',
    overflow: 'hidden',
    display: 'flex',
    fontSize: '0.91rem',
    flexDirection: 'column',
    fontFamily: '"Roboto Mono","Helvetica","Arial",sans-serif',
    '& p': {
      margin: 0,
      whiteSpace: 'pre-wrap',
      wordBreak: 'break-word',
      display: 'flex',
      '& .msg': {
        flex: '1'
      },
      '& .logger': {
        color: theme => theme.palette.secondary.main,
        fontStyle: 'italic'
      },
      '& .level': {
        userSelect: 'none',
        height: 'fit-content',
        fontWeight: 'bolder',
        cursor: 'pointer',
        color: theme => theme.palette.primary.main
      },
      '& .white': {
        textShadow: theme => theme.palette.mode === 'light' ? '#000 1px 0 0, #000 0 1px 0, #000 -1px 0 0, #000 0 -1px 0' : undefined
      },
      '& .black': {
        textShadow: theme => theme.palette.mode === 'dark' ? '#fff 1px 0 0, #fff 0 1px 0, #fff -1px 0 0, #fff 0 -1px 0' : undefined
      },
      '& .more': {
        color: theme => theme.palette.secondary.main,
        marginRight: '4px',
        cursor: 'pointer',
        textDecoration: 'underline'
      }
    },
    '& .warn, & .warn .level': {
      color: theme => theme.palette.warning.main
    },
    '& .error, & .error .level': {
      color: theme => theme.palette.error.main
    }
  }}>
    <Toolbar />
    <Box
      ref={ref}
      sx={{
        height: '100%',
        overflow: 'hidden scroll',
        backgroundColor: theme => theme.palette.background.default,
        padding: theme => theme.spacing(1)
      }}>
      {logs}
    </Box>
    <Paper sx={{
      display: 'flex',
      borderRadius: '4px 4px 0 0',
      padding: theme => theme.spacing(1),
      zIndex: 2
    }}>
      <Autocomplete
        freeSolo
        open={open}
        inputValue={command}
        options={suggestions}
        onOpen={() => setOpen(true)}
        onClose={() => setOpen(false)}
        onFocus={() => getSuggestions(command)}
        onKeyUp={(e: any) => e.key === 'Enter' && (!open || !suggestions.length) && execCommand()}
        sx={{ flex: '1' }}
        classes={{ popper: 'command-popper' }}
        renderInput={params => <TextField {...params as any} label={lang.terminal.command} />}
        getOptionLabel={it => typeof it === 'string' ? it : it[0]}
        groupBy={it => it[1] ? lang.history : lang.terminal.command}
        onInputChange={(_, it) => {
          getSuggestions(it)
          setCommand(it)
        }}
      />
      <IconButton
        color='primary'
        disabled={!command}
        onClick={execCommand}
        sx={{ margin: theme => theme.spacing('auto', 0, 'auto', 1) }}
      ><Send /></IconButton>
    </Paper>
  </Box>
}
Example #9
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 #10
Source File: ItemViewer.tsx    From NekoMaid with MIT License 4 votes vote down vote up
ItemEditor: React.FC = () => {
  const plugin = usePlugin()
  const theme = useTheme()
  const [item, setItem] = useState<Item | undefined>()
  const [types, setTypes] = useState<string[]>([])
  const [tab, setTab] = useState(0)
  const [level, setLevel] = useState(1)
  const [enchantment, setEnchantment] = useState<string | undefined>()
  const [nbtText, setNBTText] = useState('')
  const nbt: NBT = item?.nbt ? parse(item.nbt) : { id: 'minecraft:' + (item?.type || 'air').toLowerCase(), Count: new Byte(1) } as any
  useEffect(() => {
    if (!item || types.length) return
    plugin.emit('item:fetch', (a: string[], b: string[]) => {
      setTypes(a)
      enchantments = b
    })
  }, [item])
  useEffect(() => {
    _setItem = (it: any) => {
      setItem(it)
      setNBTText(it.nbt ? stringify(parse(it.nbt), { pretty: true }) : '')
    }
    return () => { _setItem = null }
  }, [])
  const cancel = () => {
    setItem(undefined)
    if (_resolve) {
      _resolve(false)
      _resolve = null
    }
  }
  const update = () => {
    const newItem: any = { ...item }
    if (nbt) {
      newItem.nbt = stringify(nbt as any)
      setNBTText(stringify(nbt, { pretty: true }))
    }
    setItem(newItem)
  }
  const isAir = item?.type === 'AIR'
  const name = nbt?.tag?.display?.Name
  const enchantmentMap: Record<string, true> = { }
  return <Dialog open={!!item} onClose={cancel}>
    <DialogTitle>{lang.itemEditor.title}</DialogTitle>
    <DialogContent sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
      {item && <Box sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}>
        <ItemViewer item={item} />
        <Autocomplete
          options={types}
          sx={{ maxWidth: 300, marginLeft: 1, flexGrow: 1 }}
          value={item?.type}
          onChange={(_, it) => {
            item.type = it || 'AIR'
            if (nbt) nbt.id = 'minecraft:' + (it ? it.toLowerCase() : 'air')
            update()
          }}
          getOptionLabel={it => {
            const locatedName = getName(it.toLowerCase())
            return (locatedName ? locatedName + ' ' : '') + it
          }}
          renderInput={(params) => <TextField {...params} label={lang.itemEditor.itemType} size='small' variant='standard' />}
        />
      </Box>}
      <Tabs centered value={tab} onChange={(_, it) => setTab(it)} sx={{ marginBottom: 2 }}>
        <Tab label={lang.itemEditor.baseAttribute} disabled={isAir} />
        <Tab label={minecraft['container.enchant']} disabled={isAir} />
        <Tab label='NBT' disabled={isAir} />
      </Tabs>
      {nbt && tab === 0 && <Grid container spacing={1} rowSpacing={1}>
        <Grid item xs={12} md={6}><TextField
          fullWidth
          label={lang.itemEditor.count}
          type='number'
          variant='standard'
          value={nbt.Count}
          disabled={isAir}
          onChange={e => {
            nbt.Count = new Byte(item!.amount = parseInt(e.target.value))
            update()
          }}
        /></Grid>
        <Grid item xs={12} md={6}><TextField
          fullWidth
          label={lang.itemEditor.damage}
          type='number'
          variant='standard'
          value={nbt.tag?.Damage}
          disabled={isAir}
          onChange={e => {
            set(nbt, 'tag.Damage', parseInt(e.target.value))
            update()
          }}
        /></Grid>
        <Grid item xs={12} md={6}>
          <TextField
            fullWidth
            label={lang.itemEditor.displayName}
            variant='standard'
            disabled={isAir}
            value={name ? stringifyTextComponent(JSON.parse(name)) : ''}
            onChange={e => {
              set(nbt, 'tag.display.Name', JSON.stringify(item!.name = e.target.value))
              update()
            }}
          />
          <FormControlLabel
            label={minecraft['item.unbreakable']}
            disabled={isAir}
            checked={nbt.tag?.Unbreakable?.value === 1}
            control={<Checkbox
              checked={nbt.tag?.Unbreakable?.value === 1}
              onChange={e => {
                set(nbt, 'tag.Unbreakable', new Byte(+e.target.checked))
                update()
              }} />
            }
          />
        </Grid>
        <Grid item xs={12} md={6}><TextField
          fullWidth
          multiline
          label={lang.itemEditor.lore}
          variant='standard'
          maxRows={5}
          disabled={isAir}
          value={nbt.tag?.display?.Lore?.map(l => stringifyTextComponent(JSON.parse(l)))?.join('\n') || ''}
          onChange={e => {
            set(nbt, 'tag.display.Lore', e.target.value.split('\n').map(text => JSON.stringify(text)))
            update()
          }}
        /></Grid>
      </Grid>}
      {nbt && tab === 1 && <Grid container spacing={1} sx={{ width: '100%' }}>
        {nbt.tag?.Enchantments?.map((it, i) => {
          enchantmentMap[it.id] = true
          return <Grid item key={i}><Chip label={getEnchantmentName(it)} onDelete={() => {
            nbt?.tag?.Enchantments?.splice(i, 1)
            update()
          }} /></Grid>
        })}
        <Grid item><Chip label={lang.itemEditor.newEnchantment} color='primary' onClick={() => {
          setEnchantment('')
          setLevel(1)
        }} /></Grid>
        <Dialog onClose={() => setEnchantment(undefined)} open={enchantment != null}>
          <DialogTitle>{lang.itemEditor.newEnchantmentTitle}</DialogTitle>
          <DialogContent>
            <Box component='form' sx={{ display: 'flex', flexWrap: 'wrap' }}>
              <FormControl variant='standard' sx={{ m: 1, minWidth: 120 }}>
                <InputLabel htmlFor='item-editor-enchantment-selector'>{minecraft['container.enchant']}</InputLabel>
                <Select
                  id='item-editor-enchantment-selector'
                  label={minecraft['container.enchant']}
                  value={enchantment || ''}
                  onChange={e => setEnchantment(e.target.value)}
                >{enchantments
                  .filter(e => !(e in enchantmentMap))
                  .map(it => <MenuItem key={it} value={it}>{getEnchantmentName(it)}</MenuItem>)}
                </Select>
              </FormControl>
              <FormControl sx={{ m: 1, minWidth: 120 }}>
                <TextField
                  label={lang.itemEditor.level}
                  type='number'
                  variant='standard'
                  value={level}
                  onChange={e => setLevel(parseInt(e.target.value))}
                />
              </FormControl>
            </Box>
          </DialogContent>
          <DialogActions>
            <Button onClick={() => setEnchantment(undefined)}>{minecraft['gui.cancel']}</Button>
            <Button disabled={!enchantment || isNaN(level)} onClick={() => {
              if (nbt) {
                if (!nbt.tag) nbt.tag = { Damage: new Int(0) }
                ;(nbt.tag.Enchantments || (nbt.tag.Enchantments = [])).push({ id: enchantment!, lvl: new Short(level) })
              }
              setEnchantment(undefined)
              update()
            }}>{minecraft['gui.ok']}</Button>
          </DialogActions>
        </Dialog>
      </Grid>}
    </DialogContent>
    {nbt && tab === 2 && <Box sx={{
      '& .CodeMirror': { width: '100%' },
      '& .CodeMirror-dialog, .CodeMirror-scrollbar-filler': { backgroundColor: theme.palette.background.paper + '!important' }
    }}>
      <UnControlled
        value={nbtText}
        options={{
          mode: 'javascript',
          phrases: lang.codeMirrorPhrases,
          theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
        }}
        onChange={(_: any, __: any, nbt: string) => {
          const n = parse(nbt) as any as NBT
          const newItem: any = { ...item, nbt }
          if (n.Count?.value != null) newItem.amount = n.Count.value
          setItem(newItem)
        }}
      />
    </Box>}
    <DialogActions>
      <Button onClick={cancel}>{minecraft['gui.cancel']}</Button>
      <Button onClick={() => {
        setItem(undefined)
        if (_resolve) {
          _resolve(!item || item.type === 'AIR' ? null : item)
          _resolve = null
        }
      }}>{minecraft['gui.ok']}</Button>
    </DialogActions>
  </Dialog>
}
Example #11
Source File: client.tsx    From mui-toolpad with MIT License 4 votes vote down vote up
function QueryEditor({
  appId,
  connectionId,
  value,
  onChange,
}: QueryEditorProps<GoogleSheetsApiQuery>) {
  const [spreadsheetQuery, setSpreadsheetQuery] = React.useState<string | null>(null);

  const debouncedSpreadsheetQuery = useDebounced(spreadsheetQuery, 300);

  const fetchedFiles: UseQueryResult<GoogleDriveFiles> = client.useQuery('dataSourceFetchPrivate', [
    appId,
    connectionId,
    {
      type: GoogleSheetsPrivateQueryType.FILES_LIST,
      spreadsheetQuery: debouncedSpreadsheetQuery,
    },
  ]);

  const fetchedFile: UseQueryResult<GoogleDriveFile> = client.useQuery(
    'dataSourceFetchPrivate',
    value.spreadsheetId
      ? [
          appId,
          connectionId,
          {
            type: GoogleSheetsPrivateQueryType.FILE_GET,
            spreadsheetId: value.spreadsheetId,
          },
        ]
      : null,
  );

  const fetchedSpreadsheet: UseQueryResult<GoogleSpreadsheet> = client.useQuery(
    'dataSourceFetchPrivate',
    value.spreadsheetId
      ? [
          appId,
          connectionId,
          {
            type: GoogleSheetsPrivateQueryType.FETCH_SPREADSHEET,
            spreadsheetId: value.spreadsheetId,
          },
        ]
      : null,
  );

  const selectedSheet = React.useMemo(
    () =>
      fetchedSpreadsheet.data?.sheets?.find(
        (sheet) => sheet.properties?.title === value.sheetName,
      ) ?? null,
    [fetchedSpreadsheet, value],
  );

  const handleSpreadsheetChange = React.useCallback(
    (event, newValue: GoogleDriveFile | null) => {
      onChange({
        ...value,
        sheetName: null,
        spreadsheetId: newValue?.id ?? null,
      });
    },
    [onChange, value],
  );

  const handleSheetChange = React.useCallback(
    (event, newValue: GoogleSheet | null) => {
      onChange({
        ...value,
        sheetName: newValue?.properties?.title ?? null,
      });
    },
    [onChange, value],
  );

  const handleRangeChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      onChange({
        ...value,
        ranges: event.target.value,
      });
    },
    [onChange, value],
  );

  const handleTransformChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      onChange({
        ...value,
        headerRow: event.target.checked,
      });
    },
    [onChange, value],
  );

  const handleSpreadsheetInput = React.useCallback(
    (event: React.SyntheticEvent, input: string, reason: string) => {
      if (reason === 'input') {
        setSpreadsheetQuery(input);
      }
    },
    [],
  );

  return (
    <Stack direction="column" gap={2}>
      <Autocomplete
        fullWidth
        value={fetchedFile.data ?? null}
        loading={fetchedFiles.isLoading}
        loadingText={'Loading...'}
        options={fetchedFiles.data?.files ?? []}
        getOptionLabel={(option: GoogleDriveFile) => option.name ?? ''}
        onInputChange={handleSpreadsheetInput}
        onChange={handleSpreadsheetChange}
        isOptionEqualToValue={(option: GoogleDriveFile, val: GoogleDriveFile) =>
          option.id === val.id
        }
        renderInput={(params) => <TextField {...params} label="Select spreadsheet" />}
        renderOption={(props, option) => {
          return (
            <li {...props} key={option.id}>
              {option.name}
            </li>
          );
        }}
      />
      <Autocomplete
        fullWidth
        loading={fetchedSpreadsheet.isLoading}
        value={selectedSheet}
        loadingText={'Loading...'}
        options={fetchedSpreadsheet.data?.sheets ?? []}
        getOptionLabel={(option: GoogleSheet) => option.properties?.title ?? ''}
        onChange={handleSheetChange}
        renderInput={(params) => <TextField {...params} label="Select sheet" />}
        renderOption={(props, option) => {
          return (
            <li {...props} key={option?.properties?.sheetId}>
              {option?.properties?.title}
            </li>
          );
        }}
      />
      <TextField
        label="Range"
        helperText={`In the form of A1:Z999`}
        value={value.ranges}
        disabled={!value.sheetName}
        onChange={handleRangeChange}
      />
      <FormControlLabel
        label="First row contains column headers"
        control={
          <Checkbox
            checked={value.headerRow}
            onChange={handleTransformChange}
            inputProps={{ 'aria-label': 'controlled' }}
          />
        }
      />
    </Stack>
  );
}
Example #12
Source File: RegionSelect.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
RegionSelect = ({
  type,
  onChange,
  inputProps,
}: {
  type: "minio" | "s3" | "gcs" | "azure" | "unsupported";
  onChange: (obj: any) => void;
  inputProps?: any;
}) => {
  const regionList = getRegions(type);
  const [value, setValue] = React.useState("");

  return (
    <Autocomplete
      sx={{
        "& .MuiOutlinedInput-root": {
          padding: 0,
          paddingLeft: "10px",
          fontSize: 13,
          fontWeight: 600,
        },
        "& .MuiAutocomplete-inputRoot": {
          "& .MuiOutlinedInput-notchedOutline": {
            borderColor: "#e5e5e5",
            borderWidth: 1,
          },
          "&:hover .MuiOutlinedInput-notchedOutline": {
            borderColor: "#07193E",
            borderWidth: 1,
          },
          "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
            borderColor: "#07193E",
            borderWidth: 1,
          },
        },
      }}
      freeSolo
      selectOnFocus
      handleHomeEndKeys
      onChange={(event, newValue) => {
        let newVal: any = newValue;

        if (typeof newValue === "string") {
          newVal = {
            label: newValue,
          };
        } else if (newValue && newValue.inputValue) {
          // Create a new value from the user input
          newVal = {
            label: newValue.inputValue,
          };
        } else {
          newVal = newValue;
        }
        setValue(newVal);
        onChange(newVal?.value);
      }}
      value={value}
      onInputChange={(e: any) => {
        const { target: { value = "" } = {} } = e || {};
        onChange(value);
      }}
      getOptionLabel={(option) => {
        // Value selected with enter, right from the input
        if (typeof option === "string") {
          return option;
        }
        // Add "xxx" option created dynamically
        if (option.inputValue) {
          return option.inputValue;
        }
        // Regular option
        return option.value;
      }}
      options={regionList}
      filterOptions={(opts: any[], state: any) => {
        const filterText = state.inputValue.toLowerCase();

        return opts.filter((opt) =>
          `${opt.label.toLowerCase()}${opt.value.toLowerCase()}`.includes(
            filterText
          )
        );
      }}
      renderOption={(props: any, opt: any) => {
        return (
          <li {...props}>
            <Box
              sx={{
                display: "flex",
                flexFlow: "column",
                alignItems: "baseline",
                padding: "4px",
                borderBottom: "1px solid #eaeaea",
                cursor: "pointer",
                width: "100%",

                "& .label": {
                  fontSize: "13px",
                  fontWeight: 500,
                },
                "& .value": {
                  fontSize: "11px",
                  fontWeight: 400,
                },
              }}
            >
              <span className="label">{opt.value}</span>
              <span className="value">{opt.label}</span>
            </Box>
          </li>
        );
      }}
      renderInput={(params) => (
        <TextField {...params} {...inputProps} fullWidth />
      )}
    />
  );
}
Example #13
Source File: CharacterAutocomplete.tsx    From genshin-optimizer with MIT License 4 votes vote down vote up
export default function CharacterAutocomplete({ value, onChange, defaultText = "", defaultIcon = "", placeholderText = "", labelText = "", showDefault = false, showInventory = false, showEquipped = false, filter = () => true, disable = () => false, ...props }: CharacterAutocompleteProps) {
  // TODO: #412 We shouldn't be loading all the character translation files. Should have a separate lookup file for character name.
  const { t } = useTranslation(["ui", "artifact", ...allCharacterKeys.map(k => `char_${k}_gen`)])
  const theme = useTheme()
  const { database } = useContext(DatabaseContext)
  const characterSheets = usePromise(CharacterSheet.getAll, [])
  const filterConfigs = useMemo(() => characterSheets && characterFilterConfigs(database, characterSheets), [database, characterSheets])
  const characterKeys = database._getCharKeys().filter(ck => characterSheets?.[ck] && filter(characterSheets[ck], ck)).sort()

  const textForValue = useCallback((value: CharacterAutocompleteValue): string => {
    switch (value) {
      case "Equipped":
        return t("artifact:filterLocation.currentlyEquipped")
      case "Inventory":
        return t("artifact:filterLocation.inventory")
      case "":
        return defaultText
      default:
        return t(`char_${value}_gen:name`)
    }
  }, [defaultText, t])

  const imageForValue = useCallback((value: CharacterAutocompleteValue): Displayable => {
    switch (value) {
      case "Equipped":
        return <FontAwesomeIcon icon={faUserShield} />
      case "Inventory":
        return <BusinessCenter />
      case "":
        return defaultIcon
      default:
        return <ThumbSide src={characterSheets![value]?.thumbImgSide} sx={{ pr: 1 }} />
    }
  }, [defaultIcon, characterSheets])

  const characterOptions = useMemo(() => filterConfigs && charOptions(characterKeys, filterConfigs, textForValue, showDefault, showInventory, showEquipped),
    [filterConfigs, characterKeys, showDefault, showInventory, showEquipped, textForValue])



  if (!characterSheets || !characterOptions) return null

  return <Autocomplete
    autoHighlight
    options={characterOptions}
    getOptionLabel={(option) => option.label}
    onChange={(_, newValue) => onChange(newValue ? newValue.value : "")}
    isOptionEqualToValue={(option, value) => option.value === value.value}
    getOptionDisabled={option => option.value ? disable(option.value) : false}
    value={{ value, label: textForValue(value) }}
    renderInput={(props) => <SolidColoredTextField
      {...props}
      label={labelText}
      placeholder={placeholderText}
      startAdornment={imageForValue(value)}
      hasValue={value ? true : false}
    />}
    renderOption={(props, option) => {
      const favorite = option.value !== "Equipped" && option.value !== "Inventory"
        && option.value !== "" && database._getChar(option.value)?.favorite
      return <MenuItemWithImage
        key={option.value ? option.value : "default"}
        value={option.value ? option.value : "default"}
        image={imageForValue(option.value)}
        text={
          <Suspense fallback={<Skeleton variant="text" width={100} />}>
            <Typography variant="inherit" noWrap>
              {textForValue(option.value)}
            </Typography>
          </Suspense>
        }
        theme={theme}
        isSelected={value === option.value}
        addlElement={<>
          {favorite && <Box display="flex" flexGrow={1} />}
          {favorite && <Favorite sx={{ ml: 1, mr: -0.5 }} />}
        </>}
        props={props}
      />
    }}
    {...props}
  />
}
Example #14
Source File: SettingsModal.tsx    From rewind with MIT License 4 votes vote down vote up
function SkinsSettings() {
  // TODO: Button for synchronizing skin list again

  const theater = useCommonManagers();

  const { preferredSkinId } = useObservable(() => theater.skinSettingsStore.settings$, DEFAULT_SKIN_SETTINGS);
  const chosenSkinId = stringToSkinId(preferredSkinId);
  const skins = useObservable(() => theater.skinManager.skinList$, []);

  const skinOptions: SkinId[] = useMemo(
    () => [DEFAULT_OSU_SKIN_ID, DEFAULT_REWIND_SKIN_ID].concat(skins.map((name) => ({ source: "osu", name }))),
    [skins],
  );

  useEffect(() => {
    theater.skinManager.loadSkinList();
  }, [theater]);

  // TODO:

  const handleSkinChange = useCallback(
    (skinId: SkinId) => {
      (async function () {
        try {
          await theater.skinManager.loadSkin(skinId);
        } catch (e) {
          // Show some error dialog
          console.error(`Could not load skin ${skinId}`);
        }
      })();
      // TODO: Error handling
    },
    [theater],
  );

  return (
    <Box sx={{ p: 2 }}>
      <Autocomplete
        id="skin-selection-demo"
        // TODO: Make sure skinIds are sorted
        options={skinOptions}
        groupBy={(option: SkinId) => sourceName[option.source]}
        value={chosenSkinId}
        onChange={(event, newValue) => {
          if (newValue) {
            handleSkinChange(newValue as SkinId);
          }
        }}
        getOptionLabel={(option: SkinId) => option.name}
        sx={{ width: 300 }}
        renderInput={(params) => <TextField {...params} label="Skin" />}
        isOptionEqualToValue={(option, value) => option.name === value.name && option.source === value.source}
      />
    </Box>
  );
}
Example #15
Source File: index.tsx    From ExpressLRS-Configurator with GNU General Public License v3.0 4 votes vote down vote up
Omnibox: FunctionComponent<OmniboxProps> = ({
  options,
  currentValue,
  onChange,
  title,
  disabled = false,
  loading = false,
  groupBy,
}) => {
  const onInputChange = (_event: any, opt: Option | null) => {
    if (opt && opt.value) {
      onChange(opt.value);
    } else {
      onChange(null);
    }
  };
  const filterOptions = (
    values: Option[],
    { inputValue }: FilterOptionsState<Option>
  ): OptionWithMatches[] => {
    if (inputValue) {
      const searchResults = new QuickScore(values, ['label']).search(
        inputValue
      );
      return searchResults.map(
        (result: { item: Option; matches: { label: number[][] } }) => {
          return {
            ...result.item,
            matches: result.matches.label,
          };
        }
      );
    }
    // if no inputValue, then maintain original item order
    return values.map((item) => {
      return {
        ...item,
        matches: [[0, 0]],
      };
    });
  };
  return (
    <Autocomplete
      id={`omnibox-${title}`}
      options={options}
      disablePortal
      fullWidth
      loading={loading}
      disabled={disabled}
      getOptionLabel={(option) => option.label}
      isOptionEqualToValue={(option, otherOption) =>
        option.value === otherOption.value
      }
      openOnFocus
      clearIcon={false}
      renderInput={(params) => (
        <TextField {...params} label={title} margin="none" />
      )}
      filterOptions={filterOptions}
      groupBy={groupBy}
      value={currentValue}
      onChange={onInputChange}
      renderOption={(props, option) => {
        const opt: OptionWithMatches = option as OptionWithMatches;
        const parts = parse(option.label, opt.matches);
        return (
          <li {...props}>
            <div>
              {parts.map(
                (
                  part: { highlight: any; text: React.ReactNode },
                  index: string | number | null | undefined
                ) => (
                  <span
                    key={index}
                    style={{
                      fontWeight: part.highlight ? 800 : 400,
                    }}
                  >
                    {part.text}
                  </span>
                )
              )}
            </div>
          </li>
        );
      }}
    />
  );
}
Example #16
Source File: latest-frontend-dependencies.tsx    From Cromwell with MIT License 4 votes vote down vote up
export default function FrontendDependencies() {

    const [expanded, setExpanded] = React.useState(false);
    const [versionsLoading, setVersionsLoading] = React.useState(false);
    const [cmsVersions, setCmsVersions] = React.useState<string[]>([]);
    const [pickedVersion, setPickedVersion] = React.useState<string | undefined>();
    const [dependencies, setDependencies] = useState<{
        name: string;
        version: string;
    }[]>([]);

    const handleExpandClick = () => {
        setExpanded(!expanded);
    };

    useEffect(() => {
        (async () => {
            setVersionsLoading(true);
            const versions = (await apiClient.getFrontendDependenciesBindings())
                .filter(compareVersions.validate)
                .sort(compareVersions).reverse();

            setCmsVersions(versions);
            const latest = versions[0];
            setPickedVersion(latest);

            await getDependencies(latest);
        })();
    }, []);


    const getDependencies = async (version: string) => {
        setVersionsLoading(true);
        const deps = await apiClient.getFrontendDependenciesList(version);
        setDependencies(Object.keys(deps?.latestVersions ?? {}).map(pckg => ({
            name: pckg,
            version: (deps?.latestVersions ?? {})[pckg],
        })));
        setVersionsLoading(false);
    }

    const getDepName = (option) => `${option.name}: "${option.version}"`;

    const changeCmsVersion = async (version: string) => {
        setPickedVersion(version);
        setDependencies([]);
        await getDependencies(version);
    }

    return (
        <Layout
            title='Hello from'
            description=""
        >
            <div className={styles.content}>
                <h1 className={styles.title}>Latest Frontend dependencies</h1>
                <Link
                    style={{ marginBottom: '25px' }}
                    href="/docs/development/frontend-dependencies">Documentation</Link>
                <div className={styles.searchBox} >
                    <Autocomplete
                        id="cms-versions"
                        options={cmsVersions ?? ['']}
                        value={pickedVersion ?? ''}
                        onChange={(event, value) => changeCmsVersion(value)}
                        getOptionLabel={ver => ver}
                        style={{ width: 300 }}
                        renderInput={(params) =>
                            <TextField {...params}
                                label="Pick CMS version"
                                variant="outlined"
                            />}
                    />
                </div>
                <div className={styles.searchBox}>
                    {versionsLoading ? (
                        <CircularProgress />
                    ) : (
                        <Autocomplete
                            id="dependencies-versions"
                            options={dependencies}
                            getOptionLabel={getDepName}
                            style={{ width: 300 }}
                            renderInput={(params) =>
                                <TextField {...params}
                                    label="Search dependencies..."
                                    variant="outlined"
                                />}
                        />
                    )}
                </div>
                <div className={styles.listHeader} onClick={handleExpandClick}>
                    <h3 className={styles.expandTitle}>Expand all</h3>
                    <IconButton >
                        <ExpandMoreIcon
                            style={{ transform: expanded ? 'rotate(180deg)' : '' }}
                        />
                    </IconButton>
                </div>
                <Collapse in={expanded} timeout="auto" unmountOnExit>
                    {dependencies.map(dep => {
                        return (
                            <div key={dep.name}>{getDepName(dep)}</div>
                        )
                    })}
                </Collapse>

            </div>
        </Layout>
    )
}
Example #17
Source File: blog.tsx    From Cromwell with MIT License 4 votes vote down vote up
BlogPage: TPageWithLayout<BlogProps> = (props) => {
  const filterInput = useRef<TPostFilter>({});
  const listId = 'Blog_list_01';
  const publishSort = useRef<"ASC" | "DESC">('DESC');
  const forceUpdate = useForceUpdate();

  const resetList = () => {
    const list = getBlockInstance<TCList>(listId)?.getContentInstance();
    list?.updateData();
  }

  useEffect(() => {
    // updateList();
  }, []);

  const handleChangeTags = (event: any, newValue?: (TTag | undefined | string)[]) => {
    filterInput.current.tagIds = newValue?.map(tag => (tag as TTag)?.id);
    forceUpdate();
    resetList();
  }

  const handleGetPosts = (params: TPagedParams<TPost>) => {
    params.orderBy = 'publishDate';
    params.order = publishSort.current;
    return handleGetFilteredPosts(params, filterInput.current);
  }

  const handleChangeSort = (event: SelectChangeEvent<unknown>) => {
    if (event.target.value === 'Newest') publishSort.current = 'DESC';
    if (event.target.value === 'Oldest') publishSort.current = 'ASC';
    resetList();
  }

  const handleTagClick = (tag?: TTag) => {
    if (!tag) return;
    if (filterInput.current.tagIds?.length === 1 &&
      filterInput.current.tagIds[0] === tag.id) return;
    handleChangeTags(null, [tag]);
    forceUpdate();
  }

  return (
    <CContainer className={commonStyles.content} id="blog-1">
      <CContainer className={styles.filter} id="blog-2">
        <Autocomplete
          multiple
          freeSolo
          value={filterInput.current.tagIds?.map(id => props.tags?.find(tag => tag.id === id)) ?? []}
          className={styles.filterItem}
          options={props.tags ?? []}
          getOptionLabel={(option: any) => option?.name ?? ''}
          style={{ width: 300 }}
          onChange={handleChangeTags}
          renderInput={(params) => (
            <TextField
              {...params}
              variant="standard"
              placeholder="Tags"
            />
          )}
        />
        <FormControl className={styles.filterItem}>
          <InputLabel className={styles.sortLabel}>Sort</InputLabel>
          <Select
            onChange={handleChangeSort}
            variant="standard"
            defaultValue='Newest'
          >
            {['Newest', 'Oldest'].map(sort => (
              <MenuItem value={sort} key={sort}>{sort}</MenuItem>
            ))}
          </Select>
        </FormControl>
      </CContainer>
      <CContainer style={{ marginBottom: '20px' }} id="blog-3">
        <CList<TPost>
          id={listId}
          editorHidden
          ListItem={(props) => (
            <div className={styles.postWrapper}>
              <PostCard onTagClick={handleTagClick} post={props.data} key={props.data?.id} />
            </div>
          )}
          usePagination
          useShowMoreButton
          useQueryPagination
          disableCaching
          pageSize={20}
          scrollContainerSelector={`.${layoutStyles.Layout}`}
          firstBatch={props.posts}
          loader={handleGetPosts}
          cssClasses={{
            page: styles.postList
          }}
          elements={{
            pagination: MuiPagination
          }}
        />
      </CContainer>
    </CContainer>
  );
}
Example #18
Source File: search.tsx    From Cromwell with MIT License 4 votes vote down vote up
SearchPage: TPageWithLayout<SearchPageProps> = (props) => {
    const filterInput = useRef<TPostFilter>({});
    const listId = 'Blog_list_01';
    const publishSort = useRef<"ASC" | "DESC">('DESC');
    const forceUpdate = useForceUpdate();
    const titleSearchId = "post-filter-search";

    const updateList = () => {
        const list = getBlockInstance<TCList>(listId)?.getContentInstance();
        list?.clearState();
        list?.init();
        list?.updateData();
    }

    const handleChangeTags = (event: any, newValue?: (TTag | undefined | string)[]) => {
        filterInput.current.tagIds = newValue?.map(tag => (tag as TTag)?.id);
        forceUpdate();
        updateList();
    }

    const handleGetPosts = (params: TPagedParams<TPost>) => {
        params.orderBy = 'publishDate';
        params.order = publishSort.current;
        return handleGetFilteredPosts(params, filterInput.current);
    }

    const handleChangeSort = (event: SelectChangeEvent<unknown>) => {
        if (event.target.value === 'Newest') publishSort.current = 'DESC';
        if (event.target.value === 'Oldest') publishSort.current = 'ASC';
        updateList();
    }

    const handleTagClick = (tag?: TTag) => {
        if (!tag) return;
        if (filterInput.current.tagIds?.length === 1 &&
            filterInput.current.tagIds[0] === tag.id) return;
        handleChangeTags(null, [tag]);
        forceUpdate();
    }

    const handleTitleInput = debounce(400, () => {
        filterInput.current.titleSearch = (document.getElementById(titleSearchId) as HTMLInputElement)?.value ?? undefined;
        updateList();
    });

    return (
        <CContainer className={commonStyles.content} id="search_01">
            <CContainer className={styles.filter} id="search_02">
                <div className={styles.filterLeft}>
                    <TextField
                        className={styles.filterItem}
                        placeholder="Search by title"
                        id={titleSearchId}
                        variant="standard"
                        onChange={handleTitleInput}
                    />
                    <Autocomplete
                        multiple
                        freeSolo
                        value={filterInput.current.tagIds?.map(id => props.tags?.find(tag => tag.id === id)) ?? []}
                        className={styles.filterItem}
                        options={props.tags ?? []}
                        getOptionLabel={(option: any) => option?.name ?? ''}
                        style={{ width: 300 }}
                        onChange={handleChangeTags}
                        renderInput={(params) => (
                            <TextField
                                {...params}
                                variant="standard"
                                placeholder="Tags"
                            />
                        )}
                    />
                </div>
                <FormControl className={styles.filterItem}>
                    <InputLabel className={styles.sortLabel}>Sort</InputLabel>
                    <Select
                        style={{ width: '100px' }}
                        onChange={handleChangeSort}
                        variant="standard"
                        defaultValue='Newest'
                    >
                        {['Newest', 'Oldest'].map(sort => (
                            <MenuItem value={sort} key={sort}>{sort}</MenuItem>
                        ))}
                    </Select>
                </FormControl>
            </CContainer>
            <CContainer style={{ marginBottom: '20px' }} id="search_03">
                <CList<TPost>
                    id={listId}
                    ListItem={(props) => (
                        <div className={styles.postWrapper}>
                            <PostCard onTagClick={handleTagClick} data={props.data} key={props.data?.id} />
                        </div>
                    )}
                    usePagination
                    useShowMoreButton
                    useQueryPagination
                    disableCaching
                    pageSize={20}
                    scrollContainerSelector={`.${layoutStyles.Layout}`}
                    firstBatch={props.posts}
                    loader={handleGetPosts}
                    cssClasses={{
                        page: styles.postList
                    }}
                    elements={{
                        pagination: Pagination
                    }}
                />
            </CContainer>
        </CContainer>
    );
}
Example #19
Source File: PageSettings.tsx    From Cromwell with MIT License 4 votes vote down vote up
PageSettings = (props: {
    pageConfig: TPageConfig;
    handlePageInfoChange: (page: TPageConfig) => void;
    themeConfig?: TThemeConfig;
}) => {
    const { pageConfig, themeConfig } = props;
    const genericPages = themeConfig?.genericPages ?? [{ route: "pages/[slug]", name: "default" }];
    const pageLayout = useRef(pageConfig?.layoutRoute ?? genericPages[0].route);

    const handlePageSettingsChange = (prop: keyof TPageConfig, val: any) => {
        if (pageConfig?.isVirtual && prop === 'route') {
            if (!val) val = '';
            const prefix = pageLayout.current.replace('[slug]', '');
            val = val.replace(prefix, '');
            val = val.replace(/\W/g, '-');
            val = prefix + val;
        }
        const next = Object.assign({}, pageConfig, { [prop]: val, layoutRoute: pageLayout.current });
        props.handlePageInfoChange(next);
    }

    const changeLayout = (route: string) => {
        pageLayout.current = route;
        handlePageSettingsChange('route', pageConfig.route);
    }

    return (
        <div className={styles.pageSettings}>
            {pageConfig?.isVirtual && !!genericPages?.length && genericPages.length > 1 && (
                <Select
                    label="Layout name"
                    value={pageLayout.current}
                    options={genericPages.map(p => ({ label: p.name, value: p.route }))}
                    onChange={(event) => changeLayout(event.target.value as string)}
                    className={styles.textField}
                />
            )}
            <TextField label="Route" variant="outlined"
                disabled={!pageConfig?.isVirtual}
                value={pageConfig.route ?? ''}
                className={styles.textField}
                onChange={(e) => { handlePageSettingsChange('route', e.target.value) }}
            />
            <TextField label="Name" variant="outlined"
                value={pageConfig.name ?? ''}
                className={styles.textField}
                onChange={(e) => { handlePageSettingsChange('name', e.target.value) }}
            />
            <TextField label="Meta title" variant="outlined"
                value={pageConfig.title ?? ''}
                className={styles.textField}
                onChange={(e) => { handlePageSettingsChange('title', e.target.value) }}
            />
            <TextField label="Meta description" variant="outlined"
                value={pageConfig.description ?? ''}
                className={styles.textField}
                onChange={(e) => { handlePageSettingsChange('description', e.target.value) }}
            />
            <Autocomplete
                multiple
                freeSolo
                options={[]}
                className={styles.textField}
                value={pageConfig?.keywords ?? []}
                getOptionLabel={(option) => option as any}
                onChange={(e, newVal) => {
                    handlePageSettingsChange('keywords', newVal);
                }}
                renderInput={(params) => (
                    <Tooltip title="Press ENTER to add">
                        <TextField
                            {...params}
                            variant="outlined"
                            label="Meta keywords"
                        />
                    </Tooltip>
                )}
            />
            <TextField label="Head HTML" variant="outlined"
                value={pageConfig.headHtml ?? ''}
                className={styles.textField}
                multiline
                onChange={(e) => { handlePageSettingsChange('headHtml', e.target.value) }}
            />
            <TextField label="Footer HTML" variant="outlined"
                value={pageConfig.footerHtml ?? ''}
                className={styles.textField}
                multiline
                onChange={(e) => { handlePageSettingsChange('footerHtml', e.target.value) }}
            />
        </div>
    )
}
Example #20
Source File: MainInfoCard.tsx    From Cromwell with MIT License 4 votes vote down vote up
MainInfoCard = (props: {
    product: TProduct | TProductVariant,
    setProdData: (data: TProduct | TProductVariant) => void;
    isProductVariant?: boolean;
    canValidate?: boolean;
}) => {
    const productPrevRef = React.useRef<TProductVariant | TProduct | null>(props.product);
    const cardIdRef = React.useRef<string>(getRandStr(10));
    const productRef = React.useRef<TProductVariant | TProduct | null>(props.product);
    if (props.product !== productPrevRef.current) {
        productPrevRef.current = props.product;
        productRef.current = props.product;
    }
    const forceUpdate = useForceUpdate();
    const editorId = "prod-text-editor_" + cardIdRef.current;
    const product = productRef.current;

    const setProdData = (data: TProduct) => {
        Object.keys(data).forEach(key => { productRef.current[key] = data[key] })
        props.setProdData(data);
        forceUpdate();
    }

    const fullSave = async () => {
        setProdData({
            ...(product as TProduct),
            description: await getEditorHtml(editorId),
            descriptionDelta: JSON.stringify(await getEditorData(editorId)),
        });
    }

    useEffect(() => {
        init();
    }, [])

    const init = async () => {
        let descriptionDelta;
        if (product?.descriptionDelta) {
            try {
                descriptionDelta = JSON.parse(product.descriptionDelta);
            } catch (e) { console.error(e) }
        }

        const updateText = debounce(600, () => {
            fullSave();
        })

        await initTextEditor({
            htmlId: editorId,
            data: descriptionDelta,
            placeholder: 'Product description...',
            onChange: updateText,
        });
    }

    const handleChange = (prop: keyof TProduct, val: any) => {
        if (product) {
            const prod = Object.assign({}, product);
            (prod[prop] as any) = val;

            if (prop === 'images') {
                prod.mainImage = val?.[0];
            }
            setProdData(prod as TProduct);
        }
    }

    if (!product) return null;

    let pageFullUrl;
    if ((product as TProduct)?.slug) {
        pageFullUrl = serviceLocator.getFrontendUrl() + resolvePageRoute('product', {
            slug: (product as TProduct).slug ?? (product as TProduct).id + '',
        });
    }

    return (
        <Grid container spacing={3}>
            <Grid item xs={12} sm={12}>
                <TextField label="Name" variant="standard"
                    value={product.name ?? ''}
                    className={styles.textField}
                    onChange={(e) => { handleChange('name', e.target.value) }}
                    error={props.canValidate && !product?.name}
                />
            </Grid>
            <Grid item xs={12} sm={6}>
                <TextField label="SKU" variant="standard"
                    value={product.sku ?? ''}
                    className={styles.textField}
                    onChange={(e) => { handleChange('sku', e.target.value) }}
                />
            </Grid>
            <Grid item xs={12} sm={6}></Grid>
            <Grid item xs={12} sm={6}>
                <Select
                    fullWidth
                    label="Stock status"
                    variant="standard"
                    value={product.stockStatus}
                    onChange={(e) => { handleChange('stockStatus', e.target.value) }}
                    options={['In stock', 'Out of stock', 'On backorder'] as TStockStatus[]}
                />
            </Grid>
            <Grid item xs={12} sm={6}>
                <TextField label="Stock amount" variant="standard"
                    value={product.stockAmount ?? ''}
                    className={styles.textField}
                    type="number"
                    onChange={(e) => {
                        let val = parseInt(e.target.value);
                        if (isNaN(val)) val = null;
                        if (val && val < 0) val = 0;
                        handleChange('stockAmount', val);
                    }}
                />
            </Grid>
            <Grid item xs={12} sm={12} style={{ display: 'flex', alignItems: 'center' }}>
                <FormGroup>
                    <FormControlLabel control={<Checkbox defaultChecked />}
                        label="Manage stock"
                        checked={!!product?.manageStock}
                        onChange={() => handleChange('manageStock', !product?.manageStock)}
                    />
                </FormGroup>
                <Tooltip title="Automatically manage stock amount when new orders placed">
                    <InfoOutlinedIcon />
                </Tooltip>
            </Grid>
            <Grid item xs={12} sm={6}>
                <TextField label="Price" variant="standard"
                    value={product.price ?? ''}
                    className={styles.textField}
                    onChange={(e) => { handleChange('price', e.target.value) }}
                    InputProps={{
                        inputComponent: NumberFormatCustom as any,
                    }}
                />
            </Grid>
            <Grid item xs={12} sm={6}>
                <TextField label="Old price" variant="standard"
                    value={product.oldPrice ?? ''}
                    className={styles.textField}
                    onChange={(e) => {
                        const val = e.target.value;
                        handleChange('oldPrice', (val && val !== '') ? val : null);
                    }}
                    InputProps={{
                        inputComponent: NumberFormatCustom as any,
                    }}
                />
            </Grid>
            <Grid item xs={12} sm={12}>
                <div className={styles.imageBlock}>
                    <GalleryPicker
                        classes={{
                            imagePicker: {
                                root: styles.imageItem
                            }
                        }}
                        label="Gallery"
                        images={((product as TProduct)?.images ?? []).map(src => ({ src }))}
                        onChange={(val) => handleChange('images', val.map(s => s.src))}
                    />
                </div>
            </Grid>
            <Grid item xs={12} sm={12}>
                <div className={styles.descriptionEditor}>
                    <div style={{ minHeight: '300px' }} id={editorId}></div>
                </div>
            </Grid>
            <Grid item xs={12} sm={12}>
                {props.isProductVariant !== true && (
                    <TextField label="Page URL" variant="standard"
                        value={(product as TProduct).slug ?? ''}
                        className={styles.textField}
                        onChange={(e) => { handleChange('slug', e.target.value) }}
                        helperText={pageFullUrl}
                    />
                )}
            </Grid>
            <Grid item xs={12} sm={12}>
                {props.isProductVariant !== true && (
                    <TextField label="Meta title" variant="standard"
                        value={(product as TProduct).pageTitle ?? ''}
                        className={styles.textField}
                        onChange={(e) => { handleChange('pageTitle', e.target.value) }}
                    />
                )}
            </Grid>
            <Grid item xs={12} sm={12}>
                {props.isProductVariant !== true && (
                    <TextField label="Meta description" variant="standard"
                        multiline
                        value={(product as TProduct).pageDescription ?? ''}
                        className={styles.textField}
                        onChange={(e) => { handleChange('pageDescription', e.target.value) }}
                    />
                )}
            </Grid>
            <Grid item xs={12} sm={12}>
                {props.isProductVariant !== true && (
                    <Autocomplete
                        multiple
                        freeSolo
                        options={[]}
                        className={styles.textField}
                        value={((product as TProduct).meta?.keywords ?? []) as any}
                        getOptionLabel={(option) => option}
                        onChange={(e, newVal) => {
                            handleChange('meta', {
                                ...((product as TProduct).meta ?? {}),
                                keywords: newVal
                            })
                        }}
                        renderInput={(params) => (
                            <Tooltip title="Press ENTER to add">
                                <TextField
                                    {...params}
                                    variant="standard"
                                    label="Meta keywords"
                                />
                            </Tooltip>
                        )}
                    />
                )}
            </Grid>
        </Grid>
    )
}
Example #21
Source File: PostSettings.tsx    From Cromwell with MIT License 4 votes vote down vote up
PostSettings = (props: {
    postData?: TPost;
    isSettingsOpen: boolean;
    anchorEl: Element;
    allTags?: TTag[] | null;
    onClose: (newData: Partial<TPost>) => void;
    isSaving?: boolean;
    handleUnpublish: () => void;
    refetchMeta: () => Promise<Record<string, string> | undefined>;
}) => {
    const { postData, refetchMeta } = props;
    const [title, setTitle] = useState<string | undefined>(postData?.title ?? null);
    const [mainImage, setMainImage] = useState<string | undefined>(postData?.mainImage ?? null);
    const [pageDescription, setPageDescription] = useState<string | undefined>(postData?.pageDescription ?? null);
    const [pageKeywords, setPageKeywords] = useState<string[] | undefined>(postData?.meta?.keywords ?? null);
    const [pageTitle, setPageTitle] = useState<string | undefined>(postData?.pageTitle ?? null);
    const [slug, setSlug] = useState<string | undefined>(postData?.slug ?? null);
    const [tags, setTags] = useState<TTag[] | undefined>(postData?.tags ?? []);
    const [publishDate, setPublishDate] = useState<Date | undefined | null>(postData?.publishDate ?? null);
    const [featured, setFeatured] = useState<boolean | undefined | null>(postData?.featured ?? null);

    const handleChangeTags = (event: any, newValue: TTag[]) => {
        setTags(newValue);
    }
    const handleChangeKeywords = (event: any, newValue: string[]) => {
        setPageKeywords(newValue);
    }

    const handleClose = async () => {
        const newData = Object.assign({}, postData);
        newData.title = title;
        newData.mainImage = mainImage;
        newData.pageDescription = pageDescription;
        newData.pageTitle = pageTitle;
        newData.slug = slug;
        newData.tags = tags;
        newData.publishDate = publishDate;
        newData.featured = featured;
        if (pageKeywords) {
            if (!newData.meta) newData.meta = {};
            newData.meta.keywords = pageKeywords;
        }
        newData.customMeta = Object.assign({}, postData.customMeta, await getCustomMetaFor(EDBEntity.Post));
        props.onClose(newData);
    }

    let pageFullUrl;
    if (slug) {
        pageFullUrl = serviceLocator.getFrontendUrl() + resolvePageRoute('post', { slug: slug ?? postData.id + '' });
    }

    return (
        <Popover
            disableEnforceFocus
            open={props.isSettingsOpen}
            elevation={0}
            anchorEl={props.anchorEl}
            onClose={handleClose}
            anchorOrigin={{
                vertical: 'bottom',
                horizontal: 'center',
            }}
            classes={{ paper: styles.popover }}
            transformOrigin={{
                vertical: 'top',
                horizontal: 'center',
            }}
        >
            <div className={styles.PostSettings}>
                <p className={styles.headerText}>Post meta</p>
                <IconButton className={styles.closeBtn}
                    id="post-settings-close-btn"
                    onClick={handleClose}>
                    <CloseIcon />
                </IconButton>
                <TextField
                    label="Title"
                    value={title ?? ''}
                    fullWidth
                    className={styles.settingItem}
                    variant="standard"
                    onChange={e => setTitle(e.target.value)}
                />
                <TextField
                    label="Page URL"
                    className={styles.settingItem}
                    fullWidth
                    value={slug ?? ''}
                    onChange={e => setSlug(e.target.value)}
                    variant="standard"
                    helperText={pageFullUrl}
                />
                <ImagePicker
                    label="Main image"
                    onChange={(val) => setMainImage(val)}
                    value={mainImage}
                    className={styles.imageBox}
                    backgroundSize='cover'
                    showRemove
                />
                <Autocomplete
                    multiple
                    options={props.allTags ?? []}
                    defaultValue={tags?.map(tag => (props.allTags ?? []).find(allTag => allTag.name === tag.name)) ?? []}
                    getOptionLabel={(option) => option.name}
                    onChange={handleChangeTags}
                    renderInput={(params) => (
                        <TextField
                            {...params}
                            className={styles.settingItem}
                            variant="standard"
                            label="Tags"
                        />
                    )}
                />
                <LocalizationProvider dateAdapter={AdapterDateFns}>
                    <DatePicker
                        label="Publish date"
                        value={publishDate}
                        onChange={(newValue) => {
                            if (!newValue) {
                                setPublishDate(null);
                                return;
                            }
                            const date = new Date(newValue);
                            if (isNaN(date.getTime())) {
                                setPublishDate(null);
                                return;
                            }
                            setPublishDate(date);
                        }}
                        renderInput={(params) => <TextField
                            variant="standard"
                            fullWidth
                            {...params} />}
                    />
                </LocalizationProvider>
                <FormControlLabel
                    control={
                        <Checkbox
                            checked={featured}
                            onChange={() => setFeatured(!featured)}
                            color="primary"
                        />
                    }
                    style={{ margin: '10px 0' }}
                    className={styles.settingItem}
                    label="Featured post"
                />
                <TextField
                    label="Meta title"
                    className={styles.settingItem}
                    fullWidth
                    variant="standard"
                    value={pageTitle ?? ''}
                    onChange={e => setPageTitle(e.target.value)}
                />
                <TextField
                    label="Meta description"
                    className={styles.settingItem}
                    fullWidth
                    variant="standard"
                    value={pageDescription ?? ''}
                    onChange={e => setPageDescription(e.target.value)}
                />
                <Autocomplete
                    multiple
                    freeSolo
                    options={[]}
                    value={(pageKeywords ?? []) as any}
                    getOptionLabel={(option) => option}
                    onChange={handleChangeKeywords}
                    renderInput={(params) => (
                        <Tooltip title="Press ENTER to add">
                            <TextField
                                {...params}
                                className={styles.settingItem}
                                variant="standard"
                                label="Meta keywords"
                            />
                        </Tooltip>
                    )}
                />
                {postData?.published && (
                    <Tooltip title="Remove post from publication">
                        <Button variant="contained" color="primary"
                            className={styles.publishBtn}
                            size="small"
                            disabled={props.isSaving}
                            onClick={props.handleUnpublish}
                        >Unpublish</Button>
                    </Tooltip>
                )}
                <div style={{ marginBottom: '15px' }}></div>
                {postData && (
                    <RenderCustomFields
                        entityType={EDBEntity.Post}
                        entityData={postData}
                        refetchMeta={refetchMeta}
                    />
                )}
            </div>
        </Popover>
    )
}
Example #22
Source File: EntityTable.tsx    From Cromwell with MIT License 4 votes vote down vote up
render() {
        const tableColumns = this.getColumns();
        return (
            <div className={styles.EntityTable}>
                <div className={styles.header}>
                    <div style={{ display: 'flex', alignItems: 'center' }}>
                        <h1 className={styles.pageTitle}>{this.props.listLabel}</h1>
                        {this.props.customElements?.listLeftActions}
                    </div>
                    <div style={{ display: 'flex', alignItems: 'center' }}>
                        {this.props.customElements?.listRightActions}
                        {!!(this.filters?.length || this.sortBy?.column
                            || this.props.isFilterActive?.()) && (
                                <Tooltip title="Clear filters">
                                    <IconButton className={styles.iconButton}
                                        onClick={this.clearAllFilters}>
                                        <ClearAllIcon />
                                    </IconButton>
                                </Tooltip>
                            )}
                        <DeleteSelectedButton
                            style={{ marginRight: '10px' }}
                            onClick={this.handleDeleteSelected}
                            totalElements={this.totalElements}
                        />
                        {this.props.entityBaseRoute && !this.props.hideAddNew && (
                            <Button variant="contained"
                                size="small"
                                onClick={this.handleCreate}
                            >Add new</Button>
                        )}
                    </div>
                </div>
                <div className={styles.tableHeader}>
                    <div className={commonStyles.center}>
                        <Tooltip title="Select all">
                            <Checkbox
                                checked={this.props.allSelected ?? false}
                                onChange={this.handleToggleSelectAll}
                            />
                        </Tooltip>
                    </div>
                    <div className={styles.tableColumnNames}>
                        {tableColumns.map(col => {
                            if (!col.visible) return null;
                            const columnFilter = this.filters?.find(filter => filter.key === col.name);
                            let searchQuery = columnFilter?.value !== null && columnFilter?.value !== undefined &&
                                columnFilter.value !== '' ? columnFilter.value : null;

                            if (col.searchOptions) {
                                const autocompleteVal = this.getAutocompleteValueFromSearch(searchQuery, col);
                                if (Array.isArray(autocompleteVal)) {
                                    searchQuery = autocompleteVal.map(val => val.label).join(', ');
                                } else if (autocompleteVal) {
                                    searchQuery = autocompleteVal.label;
                                }
                            }

                            return (
                                <div key={col.name}
                                    id={`column_${col.name}`}
                                    className={clsx(styles.columnName)}
                                    style={this.getColumnStyles(col, tableColumns)}
                                >
                                    <div style={{ display: 'flex', alignItems: 'center' }}>
                                        <Tooltip title="Open search" placement="top" enterDelay={500}>
                                            <p onClick={() => this.openColumnSearch(col)}
                                                className={clsx(styles.ellipsis, styles.columnNameText)}>{col.label}</p>
                                        </Tooltip>
                                        {!col.disableSort && (
                                            <Tooltip title="Toggle sort" placement="top" enterDelay={500}>
                                                <div onClick={() => this.toggleOrderBy(col)}
                                                    className={styles.orderButton}>
                                                    <ArrowDropDownIcon
                                                        style={{
                                                            transform: this.sortBy?.column?.name === col.name
                                                                && this.sortBy?.sort === 'ASC' ? 'rotate(180deg)' : undefined,
                                                            transition: '0.3s',
                                                            color: this.sortBy?.column?.name !== col.name ? '#aaa' : '#9747d3',
                                                            fill: this.sortBy?.column?.name !== col.name ? '#aaa' : '#9747d3',
                                                            fontSize: this.sortBy?.column?.name === col.name ? '26px' : undefined,
                                                        }}
                                                    />
                                                </div>
                                            </Tooltip>
                                        )}
                                    </div>
                                    {searchQuery && (
                                        <div style={{ display: 'flex', alignItems: 'center' }}>
                                            <p className={clsx(styles.searchQuery, styles.ellipsis)}>{searchQuery}</p>
                                            <Tooltip title="Clear search" enterDelay={500}>
                                                <div onClick={() => this.clearColumnSearch(col)}
                                                    style={{ cursor: 'pointer' }}>
                                                    <CloseIcon style={{
                                                        fontSize: '14px',
                                                        color: '#555'
                                                    }} />
                                                </div>
                                            </Tooltip>
                                        </div>
                                    )}
                                </div>
                            )
                        })}
                    </div>
                    <div className={clsx(commonStyles.center, styles.headerSettings)}>
                        <Tooltip title="Configure columns">
                            <IconButton
                                onClick={this.toggleConfigureColumns}
                                ref={this.configureColumnsButtonRef}
                                disabled={!tableColumns?.length}
                            >
                                <SettingsIcon />
                            </IconButton>
                        </Tooltip>
                        <Popover
                            open={!!this.state?.configureColumnsOpen}
                            anchorEl={this.configureColumnsButtonRef.current}
                            onClose={this.toggleConfigureColumns}
                            classes={{ paper: styles.popoverPaper }}
                            anchorOrigin={{
                                vertical: 'bottom',
                                horizontal: 'right',
                            }}
                            transformOrigin={{
                                vertical: 'top',
                                horizontal: 'right',
                            }}
                            elevation={0}
                        >
                            <div className={styles.columnsConfigure}>
                                <Button size="small" variant="outlined"
                                    onClick={this.resetConfiguredColumns}
                                    style={{ margin: '10px 0 0 10px' }}
                                >Reset</Button>
                                <DraggableList<TColumnConfigureItemData>
                                    data={tableColumns.map(col => ({
                                        id: col.name,
                                        column: col,
                                        sortedColumns: this.sortedColumns,
                                    }))}
                                    onChange={this.changeColumnsOrder}
                                    component={ColumnConfigureItem}
                                />
                            </div>
                        </Popover>
                        <Popover
                            open={!!this.state?.columnSearch}
                            anchorEl={() => document.getElementById(`column_${this.state?.columnSearch?.name}`)}
                            onClose={this.closeColumnSearch}
                            classes={{ paper: styles.popoverPaper }}
                            anchorOrigin={{
                                vertical: 'bottom',
                                horizontal: 'left',
                            }}
                            transformOrigin={{
                                vertical: 'top',
                                horizontal: 'left',
                            }}
                            elevation={0}
                        >
                            <div className={styles.columnsConfigure}
                                style={{ padding: '10px 15px' }}>
                                {this.state?.columnSearch?.searchOptions ? (
                                    <Autocomplete
                                        multiple={this.state.columnSearch.multipleOptions}
                                        options={this.state.columnSearch.searchOptions}
                                        getOptionLabel={(option: any) => option?.label ?? ''}
                                        defaultValue={this.getAutocompleteValueFromSearch(this.currentSearch,
                                            this.state.columnSearch)}
                                        className={styles.filterItem}
                                        onChange={(event, newVal) => {
                                            if (Array.isArray(newVal)) newVal = JSON.stringify(newVal.map(val => typeof val === 'object' ? val?.value : val));
                                            this.currentSearch = typeof newVal === 'object' ? newVal?.value : newVal
                                        }}
                                        classes={{ popper: styles.autocompletePopper }}
                                        renderInput={(params) => <TextField
                                            {...params}
                                            variant="standard"
                                            fullWidth
                                            label={`Search ${this.state?.columnSearch?.label ?? ''}`}
                                        />}
                                    />
                                ) : (
                                    <TextField
                                        fullWidth
                                        onChange={(event) => this.currentSearch = event.target.value}
                                        variant="standard"
                                        label={`Search ${this.state?.columnSearch?.label ?? ''}`}
                                        defaultValue={this.currentSearch}
                                    />
                                )}
                            </div>
                        </Popover>
                    </div>
                </div>
                <CList<TEntityType, TListItemProps<TEntityType, TFilterType>>
                    className={styles.listWrapper}
                    id={this.listId}
                    ListItem={EntityTableItem}
                    useAutoLoading
                    usePagination
                    listItemProps={{
                        handleDeleteBtnClick: this.handleDeleteItem,
                        toggleSelection: this.handleToggleItemSelection,
                        tableProps: this.props,
                        getColumns: this.getColumns,
                        getColumnStyles: this.getColumnStyles,
                    }}
                    useQueryPagination
                    loader={this.getManyFilteredItems}
                    cssClasses={{
                        scrollBox: styles.list,
                        contentWrapper: styles.listContent,
                    }}
                    elements={{
                        pagination: Pagination,
                        preloader: listPreloader
                    }}
                />
            </div >
        )
    }
Example #23
Source File: ArtifactAutocomplete.tsx    From genshin-optimizer with MIT License 3 votes vote down vote up
function ArtifactMultiAutocomplete<T extends ArtifactMultiAutocompleteKey>({ allArtifactKeys, selectedArtifactKeys, setArtifactKeys, getName, getImage, label, ...props }:
  ArtifactMultiAutocompleteProps<T>) {
  const theme = useTheme();

  const handleChange = (_, value: ArtifactMultiAutocompleteOption<T>[]) => {
    setArtifactKeys(value.map(v => v.key))
  };
  const options = useMemo(() => allArtifactKeys.map(key => ({ key: key, label: getName(key) })), [allArtifactKeys, getName])
  return <Autocomplete
    autoHighlight
    multiple
    options={options}
    value={selectedArtifactKeys.map(key => ({ key: key, label: getName(key) }))}
    onChange={handleChange}
    getOptionLabel={(option) => option.label}
    isOptionEqualToValue={(option, value) => option.key === value.key}
    renderInput={(params) => <TextField
      {...params}
      label={label}
      variant="filled"
      InputLabelProps={{ style: { color: theme.palette.text.primary } }}
      color={selectedArtifactKeys.length ? "success" : "primary"}
      type="search"
    />}
    renderOption={(props, option) => (
      <MenuItemWithImage
        key={option.key}
        value={option.key}
        image={getImage(option.key)}
        text={option.label}
        theme={theme}
        isSelected={selectedArtifactKeys.includes(option.key)}
        props={props}
      />
    )}
    renderTags={(selected, getTagProps) => selected.map((value, index) => {
      const element = allElementsWithPhy.find(ele => value.key === `${ele}_dmg_`)
      const color = element ? element : undefined
      return <Chip {...getTagProps({ index })} key={value.key} icon={getImage(value.key)} label={value.label} color={color} />
    })}
    {...props}
  />
}
Example #24
Source File: PersonAutocomplete.tsx    From frontend with MIT License 3 votes vote down vote up
export default function PersonAutocomplete({
  onSelect,
  showId,
  autocompleteProps,
}: PersonAutocompleteProps) {
  const { t } = useTranslation('person')
  const {
    data: personList,
    isLoading,
    refetch,
  } = usePersonList({
    enabled: false,
    refetchOnWindowFocus: false,
  })
  return (
    <Autocomplete
      isOptionEqualToValue={(option, value) => option.firstName === value.firstName}
      options={personList || []}
      getOptionLabel={(person) =>
        showId
          ? `${person.firstName} ${person.lastName} (${person.id})`
          : person.firstName + ' ' + person.lastName
      }
      onChange={(e, person) => {
        onSelect(person)
      }}
      onOpen={() => {
        refetch()
      }}
      loading={isLoading}
      renderInput={(params) => (
        <TextField
          {...params}
          type="text"
          fullWidth
          defaultValue=""
          label={t('person:autocomplete.personSearch')}
        />
      )}
      {...autocompleteProps}
    />
  )
}
Example #25
Source File: Vault.tsx    From NekoMaid with MIT License 1 votes vote down vote up
PermissionDialog: React.FC<{ plugin: Plugin, id: string | undefined, isGroup: boolean, onClose: () => void }> = ({ plugin, id, onClose, isGroup }) => {
  const [value, setValue] = useState('')
  const [status, setStatus] = useState<boolean | undefined>(false)
  const [options, setOptions] = useState<string[]>([])
  useEffect(() => {
    if (!id) return
    setValue('')
    setStatus(false)
    plugin.emit('vault:getAllPermissions', (it: any) => setOptions(it.sort()))
  }, [id])
  const queryStatus = useMemo(() => throttle((value: string) => plugin.emit('vault:permission', setStatus, id, value, 0, isGroup), 500), [id, isGroup])
  return <Dialog open={!!id} onClose={onClose}>
    <DialogTitle>{lang.vault.editorTitle}</DialogTitle>
    <DialogContent sx={{ overflow: 'hidden' }}>
      <DialogContentText>{lang.vault.permissionInput}: <span className='bold' style={{ }}>
        ({isGroup ? lang.vault.permissionGroup : minecraft['entity.minecraft.player']}: {id})</span></DialogContentText>
      <Autocomplete
        freeSolo
        options={options}
        sx={{ marginTop: 1 }}
        inputValue={value}
        renderInput={params => <TextField {...params as any} label={lang.vault.permission} />}
        onInputChange={(_, it) => {
          setValue(it)
          setStatus(undefined)
          queryStatus(it)
        }}
      />
      <Box sx={{ display: 'flex', alignItems: 'center', marginTop: 1 }}>
        {lang.status}:{status == null
          ? <CircularProgress size={20} sx={{ margin: '5px' }}/>
          : status ? <Check color='success' /> : <Close color='error' />}
        &nbsp;{status != null && <Button
          disabled={!value}
          variant='outlined'
          size='small'
          onClick={() => plugin.emit('vault:permission', (res: boolean) => {
            action(res)
            setStatus(undefined)
            queryStatus(value)
          }, id, value, status ? 2 : 1, isGroup)}
        >{lang.vault[status ? 'removePermission' : 'addPermission']}</Button>}
      </Box>
    </DialogContent>
    <DialogActions><Button onClick={onClose}>{minecraft['gui.back']}</Button></DialogActions>
  </Dialog>
}