@mui/material#DialogActions TypeScript Examples

The following examples show how to use @mui/material#DialogActions. 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: UTXODetail.tsx    From sapio-studio with Mozilla Public License 2.0 6 votes vote down vote up
function ContinuationOption(props: { v: Continuation }) {
    const [is_open, setOpen] = React.useState(false);
    const name = props.v.path.substr(props.v.path.lastIndexOf('/') + 1);
    const dispatch = useDispatch();

    return (
        <div>
            <Button onClick={() => setOpen(true)} variant="contained">
                {name}
            </Button>
            <Dialog open={is_open} onClose={() => setOpen(false)}>
                <DialogTitle>
                    <Typography variant="h5">{name}</Typography>
                    <ASM
                        className="txhex"
                        value={props.v.path}
                        label="Full Path"
                    />
                </DialogTitle>
                <DialogContent>
                    <MemoizeContForm {...props} />
                </DialogContent>
                <DialogActions>
                    <Button onClick={() => dispatch(recreate_contract())}>
                        Recompile
                    </Button>
                    <Button onClick={() => setOpen(false)}>Close</Button>
                </DialogActions>
            </Dialog>
        </div>
    );
}
Example #2
Source File: Home.tsx    From mui-toolpad with MIT License 6 votes vote down vote up
function AppDeleteDialog({ app, onClose }: AppDeleteDialogProps) {
  const latestApp = useLatest(app);
  const deleteAppMutation = client.useMutation('deleteApp');

  const handleDeleteClick = React.useCallback(async () => {
    if (app) {
      await deleteAppMutation.mutateAsync([app.id]);
    }
    await client.refetchQueries('getApps');
    onClose();
  }, [app, deleteAppMutation, onClose]);

  return (
    <Dialog open={!!app} onClose={onClose}>
      <DialogForm>
        <DialogTitle>Confirm delete</DialogTitle>
        <DialogContent>
          Are you sure you want to delete application &quot;{latestApp?.name}&quot;
        </DialogContent>
        <DialogActions>
          <Button color="inherit" variant="text" onClick={onClose}>
            Cancel
          </Button>
          <LoadingButton
            type="submit"
            loading={deleteAppMutation.isLoading}
            onClick={handleDeleteClick}
            color="error"
          >
            Delete
          </LoadingButton>
        </DialogActions>
      </DialogForm>
    </Dialog>
  );
}
Example #3
Source File: Files.tsx    From NekoMaid with MIT License 6 votes vote down vote up
CompressDialog: React.FC<{ file: string | null, dirs: Record<string, boolean>, onClose: () => void, plugin: Plugin, path: string, refresh: () => void }> =
  ({ dirs, file, onClose, plugin, path, refresh }) => {
    const [value, setValue] = useState('')
    const [ext, setExt] = useState('zip')
    useEffect(() => {
      setValue(file || 'server')
    }, [file])
    let error: string | undefined
    if (!validFilename(value)) error = lang.files.wrongName
    else if (((path || '/') + value + '.' + ext) in dirs) error = lang.files.exists
    return <Dialog open={file != null} onClose={onClose}>
      <DialogTitle>{lang.files.compress}</DialogTitle>
      <DialogContent>
        <DialogContentText>{lang.files.compressName}</DialogContentText>
        <TextField value={value} variant='standard' error={!!error} helperText={error} onChange={e => setValue(e.target.value)} />
        <Select variant='standard' value={ext} onChange={e => setExt(e.target.value)}>
          {compressFileExt.map(it => <MenuItem key={it} value={it}>.{it}</MenuItem>)}
        </Select>
      </DialogContent>
      <DialogActions>
        <Button onClick={onClose}>{minecraft['gui.cancel']}</Button>
        <Button disabled={!!error} onClick={() => {
          onClose()
          plugin.emit('files:compress', (res: boolean) => {
            action(res)
            refresh()
          }, file, value, ext)
        }}>{minecraft['gui.ok']}</Button>
      </DialogActions>
    </Dialog>
  }
Example #4
Source File: DeleteSelectedModal.tsx    From frontend with MIT License 6 votes vote down vote up
function DeleteSelectedModal({
  isOpen,
  handleDeleteModalClose,
  handleDelete,
}: {
  isOpen: boolean
  handleDeleteModalClose: () => void
  handleDelete: () => void
}) {
  return (
    <Modal open={isOpen} onClose={handleDeleteModalClose}>
      <Dialog open={isOpen} onClose={handleDeleteModalClose}>
        <DialogTitle>Искате ли да изтриете избраните кампании?</DialogTitle>
        <DialogActions>
          <Button onClick={handleDelete} autoFocus>
            Да
          </Button>
          <Button onClick={handleDeleteModalClose}>Не</Button>
        </DialogActions>
      </Dialog>
    </Modal>
  )
}
Example #5
Source File: GroupDeleteModal.tsx    From abrechnung with GNU Affero General Public License v3.0 6 votes vote down vote up
export default function GroupDeleteModal({ show, onClose, groupToDelete }) {
    const confirmDeleteGroup = () => {
        deleteGroup({ groupID: groupToDelete.id })
            .then((res) => {
                onClose();
            })
            .catch((err) => {
                toast.error(err);
            });
    };

    return (
        <Dialog open={show} onClose={onClose}>
            <DialogTitle>Delete Group</DialogTitle>
            <DialogContent>
                <DialogContentText>
                    {groupToDelete ? <span>Are you sure you want to delete group {groupToDelete.name}</span> : null}
                </DialogContentText>
            </DialogContent>
            <DialogActions>
                <Button color="primary" onClick={onClose}>
                    No
                </Button>
                <Button color="error" onClick={confirmDeleteGroup}>
                    Yes pls
                </Button>
            </DialogActions>
        </Dialog>
    );
}
Example #6
Source File: FaceTimeLinkDialog.tsx    From airmessage-web with Apache License 2.0 6 votes vote down vote up
export default function FaceTimeLinkDialog(props: {
	isOpen: boolean,
	onDismiss: () => void,
	link: string
}) {
	const propsLink = props.link;
	const propsOnDismiss = props.onDismiss;
	const copyLink = useCallback(async () => {
		await navigator.clipboard.writeText(propsLink);
		propsOnDismiss();
	}, [propsLink, propsOnDismiss]);
	
	return (
		<Dialog
			open={props.isOpen}
			onClose={props.onDismiss}>
			<DialogTitle>FaceTime link</DialogTitle>
			<DialogContent>
				<DialogContentText>
					{props.link}
				</DialogContentText>
			</DialogContent>
			<DialogActions>
				<Button onClick={props.onDismiss} color="primary">
					Cancel
				</Button>
				<Button onClick={copyLink} color="primary" autoFocus>
					Copy
				</Button>
			</DialogActions>
		</Dialog>
	);
}
Example #7
Source File: cookies-dialog.tsx    From master-frontend-lemoncode with MIT License 6 votes vote down vote up
CookiesDialog: React.FunctionComponent<Props> = (props) => {
  const { onAgreeClick } = props;
  const [open, setOpen] = React.useState(false);

  const handleAgreeClick = () => {
    setOpen(false);
    onAgreeClick();
  };

  return (
    <>
      <Button variant="outlined" onClick={() => setOpen(true)}>
        Learn more about our cookies
      </Button>
      <Dialog open={open} onClose={() => setOpen(false)}>
        <DialogTitle>About cookies</DialogTitle>
        <DialogContent>
          <DialogContentText>
            Any information that you voluntarily provide to us, including your
            name and email address, will be used for the sole purpose for which
            the information was provided to us. In addition, communication
            exchanges on this website are public (not private) communications.
            Therefore, any message that you post on this website will be
            considered and treated as available for public use and distribution.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button color="primary" onClick={handleAgreeClick}>
            Agree
          </Button>
        </DialogActions>
      </Dialog>
    </>
  );
}
Example #8
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 #9
Source File: FeedbackDialog.tsx    From airmessage-web with Apache License 2.0 5 votes vote down vote up
/**
 * A dialog that presents help and feedback options
 */
export default function FeedbackDialog(props: {isOpen: boolean, onDismiss: () => void}) {
	const propsOnDismiss = props.onDismiss;
	
	const onClickEmail = useCallback(async () => {
		const body =
			`\n\n---------- DEVICE INFORMATION ----------` +
			Object.entries(await getPlatformUtils().getExtraEmailDetails())
				.map(([key, value]) => `\n${key}: ${value}`)
				.join("") +
			`\nUser agent: ${navigator.userAgent}` +
			`\nClient version: ${appVersion}` +
			`\nCommunications version: ${getActiveCommVer()?.join(".")} (target ${targetCommVerString})` +
			`\nProxy type: ${getActiveProxyType()}` +
			`\nServer system version: ${getServerSystemVersion()}` +
			`\nServer software version: ${getServerSoftwareVersion()}`;
		const url = `mailto:${supportEmail}?subject=${encodeURIComponent("AirMessage feedback")}&body=${encodeURIComponent(body)}`;
		window.open(url, "_blank");
		propsOnDismiss();
	}, [propsOnDismiss]);
	
	const onClickCommunity = useCallback(() => {
		window.open(communityPage, "_blank");
		propsOnDismiss();
	}, [propsOnDismiss]);
	
	return (
		<Dialog
			open={props.isOpen}
			onClose={props.onDismiss}>
			<DialogTitle>Help and feedback</DialogTitle>
			<DialogContent>
				<DialogContentText>
					Have a bug to report, a feature to suggest, or anything else to say? Contact us or discuss with others using the links below.
				</DialogContentText>
			</DialogContent>
			<DialogActions>
				<Button onClick={onClickEmail} color="primary">
					Send E-Mail
				</Button>
				<Button onClick={onClickCommunity} color="primary" autoFocus>
					Open community subreddit
				</Button>
			</DialogActions>
		</Dialog>
	);
}
Example #10
Source File: QueryStateEditor.tsx    From mui-toolpad with MIT License 5 votes vote down vote up
export default function QueryStateEditor() {
  const dom = useDom();
  const state = usePageEditorState();
  const domApi = useDomApi();

  const [editedState, setEditedState] = React.useState<NodeId | null>(null);
  const editedStateNode = editedState ? appDom.getNode(dom, editedState, 'queryState') : null;
  const handleEditStateDialogClose = React.useCallback(() => setEditedState(null), []);

  const page = appDom.getNode(dom, state.nodeId, 'page');
  const { queryStates = [] } = appDom.getChildNodes(dom, page) ?? [];

  // To keep dialog content around during closing animation
  const lastEditedStateNode = useLatest(editedStateNode);

  const handleRemove = React.useCallback(() => {
    if (editedStateNode) {
      domApi.removeNode(editedStateNode.id);
    }
    handleEditStateDialogClose();
  }, [editedStateNode, handleEditStateDialogClose, domApi]);

  return queryStates.length > 0 ? (
    <Stack spacing={1} alignItems="start">
      <List>
        {queryStates.map((stateNode) => {
          return (
            <ListItem key={stateNode.id} button onClick={() => setEditedState(stateNode.id)}>
              {stateNode.name}
            </ListItem>
          );
        })}
      </List>
      {lastEditedStateNode ? (
        <Dialog
          fullWidth
          maxWidth="lg"
          open={!!editedStateNode}
          onClose={handleEditStateDialogClose}
          scroll="body"
        >
          <DialogTitle>Edit Query State ({lastEditedStateNode.id})</DialogTitle>
          <DialogContent>
            <QueryStateNodeEditor node={lastEditedStateNode} />
          </DialogContent>
          <DialogActions>
            <Button color="inherit" variant="text" onClick={handleEditStateDialogClose}>
              Close
            </Button>
            <Button onClick={handleRemove}>Remove</Button>
          </DialogActions>
        </Dialog>
      ) : null}
    </Stack>
  ) : null;
}
Example #11
Source File: ConfirmationDialog.tsx    From console with GNU Affero General Public License v3.0 5 votes vote down vote up
ConfirmationDialog = ({
  classes,
  open,
  cancelLabel,
  okLabel,
  onClose,
  cancelOnClick,
  okOnClick,
  title,
  description,
}: IConfirmationDialog) => {
  const [isSending, setIsSending] = useState<boolean>(false);
  const onClick = () => {
    setIsSending(true);
    if (okOnClick !== null) {
      okOnClick();
    }
    setIsSending(false);
  };
  if (!open) return null;
  return (
    <Dialog
      open={open}
      onClose={onClose}
      aria-labelledby="alert-dialog-title"
      aria-describedby="alert-dialog-description"
    >
      <DialogTitle id="alert-dialog-title">{title}</DialogTitle>
      <DialogContent>
        {isSending && <LinearProgress />}
        <DialogContentText id="alert-dialog-description">
          {description}
        </DialogContentText>
      </DialogContent>
      <DialogActions>
        <Button onClick={cancelOnClick} color="primary" disabled={isSending}>
          {cancelLabel || "Cancel"}
        </Button>
        <Button onClick={onClick} color="secondary" autoFocus>
          {okLabel || "Ok"}
        </Button>
      </DialogActions>
    </Dialog>
  );
}
Example #12
Source File: CreatePageNodeDialog.tsx    From mui-toolpad with MIT License 5 votes vote down vote up
export default function CreatePageDialog({ appId, onClose, ...props }: CreatePageDialogProps) {
  const dom = useDom();
  const domApi = useDomApi();
  const [name, setName] = React.useState('');
  const navigate = useNavigate();

  return (
    <Dialog {...props} onClose={onClose}>
      <DialogForm
        autoComplete="off"
        onSubmit={(e) => {
          e.preventDefault();
          const newNode = appDom.createNode(dom, 'page', {
            name,
            attributes: {
              title: appDom.createConst(name),
              urlQuery: appDom.createConst({}),
            },
          });
          const appNode = appDom.getApp(dom);
          domApi.addNode(newNode, appNode, 'pages');

          onClose();
          navigate(`/app/${appId}/editor/pages/${newNode.id}`);
        }}
      >
        <DialogTitle>Create a new MUI Toolpad Page</DialogTitle>
        <DialogContent>
          <TextField
            sx={{ my: 1 }}
            autoFocus
            fullWidth
            label="name"
            value={name}
            onChange={(event) => setName(event.target.value)}
          />
        </DialogContent>
        <DialogActions>
          <Button color="inherit" variant="text" onClick={onClose}>
            Cancel
          </Button>
          <Button type="submit" disabled={!name}>
            Create
          </Button>
        </DialogActions>
      </DialogForm>
    </Dialog>
  );
}
Example #13
Source File: WalletSendDialog.tsx    From sapio-studio with Mozilla Public License 2.0 5 votes vote down vote up
export function WalletSendDialog(props: {
    show: boolean;
    amt: number;
    to: string;
    close: () => void;
    bitcoin_node_manager: BitcoinNodeManager;
}) {
    return (
        <Dialog
            open={props.show}
            onClose={() => {
                props.close();
            }}
        >
            <DialogTitle>Confirm Spend</DialogTitle>
            <DialogContent>
                <DialogContentText>
                    Confirm sending
                    {props.amt} BTC to {props.to}
                </DialogContentText>
            </DialogContent>
            <DialogActions>
                <Button
                    onClick={() => {
                        props.close();
                    }}
                >
                    Cancel
                </Button>
                <Button
                    onClick={async () => {
                        await props.bitcoin_node_manager.send_to_address(
                            props.amt,
                            props.to
                        );
                        props.close();
                    }}
                >
                    Confirm
                </Button>
            </DialogActions>
        </Dialog>
    );
}
Example #14
Source File: StatsDialog.tsx    From GTAV-NativeDB with MIT License 5 votes vote down vote up
export default function StatsDialog({ open, onClose }: Props) {
  const stats = useStats()

  return (
    <Dialog open={open} onClose={onClose} fullWidth maxWidth="xs">
      <DialogTitle>
        Stats
      </DialogTitle>
      <List dense>
        <ListItem sx={{ px: 3 }} >
          <ListItemText 
            primary="Namespaces"
            secondary={stats.namespaces}
          />
        </ListItem>
        <ListItem sx={{ px: 3 }} >
          <ListItemText 
            primary="Natives"
            secondary={stats.natives}
          />
        </ListItem>
        <ListItem sx={{ px: 3 }} >
          <ListItemText 
            primary="Comments"
            secondary={stats.comments}
          />
        </ListItem>
        <ListItem sx={{ px: 3 }} >
          <ListItemText 
            primary="Known names"
            secondary={`${stats.knownNames.confirmed} (${stats.knownNames.total})`}
          />
        </ListItem>
      </List>
      <DialogActions>
        <Button onClick={onClose}>Close</Button>
      </DialogActions>
    </Dialog>
  )
}
Example #15
Source File: dialog.tsx    From NekoMaid with MIT License 5 votes vote down vote up
DialogWrapper: React.FC = () => {
  const [canClick, setCanClick] = useState(false)
  const [open, setOpen] = useState(false)
  const [text, setText] = useState('')
  const [data, setDate] = useState<DialogOptionsWithPromise | undefined>()
  useEffect(() => {
    openFn = it => {
      setDate(it)
      setOpen(true)
    }
  }, [])
  if (!data) return <></>

  const input = (data as any).input
  const cancel = () => {
    setOpen(false)
    setDate(undefined)
    setText('')
    data.resolve(input ? null : false)
  }
  let inputElm: React.ReactNode
  if (input) {
    const props: any = {
      key: input.label || input,
      autoFocus: true,
      fullWidth: true,
      margin: 'dense',
      variant: 'standard',
      value: text,
      onStatusChange: setCanClick,
      onChange (it: any) { setText(it.target.value) }
    }
    if (typeof input === 'string') props.label = input
    else if (typeof input === 'object') Object.assign(props, input)
    inputElm = React.createElement(ValidInput, props)
  }

  return <Dialog open={!!open} onClose={cancel}>
    <DialogTitle>{data.title || lang.tip}</DialogTitle>
    <DialogContent>
      <DialogContentText>{data.content}</DialogContentText>
      {inputElm}
    </DialogContent>
    <DialogActions>
      {data.cancelButton !== false && <Button onClick={cancel}>{minecraft['gui.cancel']}</Button>}
      <Button {...data.okButton} disabled={canClick} onClick={() => {
        setOpen(false)
        setDate(undefined)
        setText('')
        data.resolve((data as any).input ? text : true)
      }}>{minecraft['gui.ok']}</Button>
    </DialogActions>
  </Dialog>
}
Example #16
Source File: DeleteDialog.tsx    From sapio-studio with Mozilla Public License 2.0 5 votes vote down vote up
export function DeleteDialog(props: {
    set_to_delete: () => void;
    to_delete: ['workspace', string] | ['contract', string] | null;
    reload: () => void;
}) {
    const workspace = useSelector(selectWorkspace);
    return (
        <Dialog onClose={props.set_to_delete} open={props.to_delete !== null}>
            <DialogTitle>Confirm Deletion</DialogTitle>
            <DialogContent>
                <DialogContentText>
                    Confirm deletion of &quot;{props.to_delete}&quot;? File will
                    be in your trash folder.
                </DialogContentText>
            </DialogContent>
            <DialogActions>
                <Button
                    color="warning"
                    onClick={(ev) => {
                        if (props.to_delete) {
                            switch (props.to_delete[0]) {
                                case 'workspace':
                                    window.electron.sapio.workspaces.trash(
                                        props.to_delete[1]
                                    );
                                    break;
                                case 'contract':
                                    window.electron.sapio.compiled_contracts.trash(
                                        workspace,
                                        props.to_delete[1]
                                    );
                                    break;
                            }
                        }
                        props.set_to_delete();
                        props.reload();
                    }}
                >
                    Delete
                </Button>
                <Button onClick={props.set_to_delete}>Cancel</Button>
            </DialogActions>
        </Dialog>
    );
}
Example #17
Source File: PersonSelectDialog.tsx    From frontend with MIT License 5 votes vote down vote up
function PersonSelectDialog({ onConfirm: confirmCallback, onClose: closeCallback, error }: Props) {
  const [person, setPerson] = useState<PersonResponse | null>(null)
  const { t } = useTranslation()
  const { open, confirmHandler, closeHandler, openHandler, loading } = useConfirm({
    onConfirm: async () => {
      confirmCallback ? confirmCallback(person) : null
    },
    onClose: async () => {
      closeCallback ? closeCallback(person) : null
    },
  })
  return (
    <>
      <FormFieldButton
        onClick={openHandler}
        placeholder={t('person:selectDialog.notSelected')}
        value={person ? `${person.firstName} ${person.lastName} (${person.id})` : undefined}
        button={{ label: t('person:selectDialog.select') }}
        error={error ? translateError(error, t) : undefined}
      />
      <Dialog fullWidth open={open} onClose={closeHandler}>
        <DialogTitle>{t('person:selectDialog.personSelect')}</DialogTitle>
        <DialogContent>
          <Box sx={{ marginTop: theme.spacing(2) }}>
            <PersonAutocomplete
              onSelect={(person) => {
                setPerson(person)
              }}
              showId
              autocompleteProps={{ defaultValue: person }}
            />
          </Box>

          <Box sx={{ marginTop: theme.spacing(2) }}>
            {person ? <PersonInfo person={person} /> : t('person:selectDialog.notSelected')}
          </Box>
        </DialogContent>
        <DialogActions>
          <CloseModalButton onClose={closeHandler} />
          <LoadingButton onClick={confirmHandler} loading={loading}>
            {t('person:selectDialog.confirm')}
          </LoadingButton>
        </DialogActions>
      </Dialog>
    </>
  )
}
Example #18
Source File: NewNickname.tsx    From sapio-studio with Mozilla Public License 2.0 5 votes vote down vote up
export function NewNickname(props: { show: boolean; hide: () => void }) {
    const [value, set_value] = React.useState<null | string>(null);
    const [key_value, set_key_value] = React.useState<null | string>(null);
    return (
        <Dialog onClose={props.hide} open={props.show}>
            <DialogTitle>Create a new User</DialogTitle>
            <DialogContent>
                <DialogContentText>
                    The key and nickname for the new person. Keys must be
                    unique.
                </DialogContentText>
                <TextField
                    onChange={(ev) => set_value(ev.currentTarget.value)}
                    value={value}
                    autoFocus
                    margin="dense"
                    label="Name"
                    name="name"
                    type="text"
                    fullWidth
                    variant="standard"
                />
                <TextField
                    onChange={(ev) => set_key_value(ev.currentTarget.value)}
                    value={key_value}
                    autoFocus
                    margin="dense"
                    label="Key Hash"
                    name="keyhash"
                    type="text"
                    fullWidth
                    variant="standard"
                />
            </DialogContent>
            <DialogActions>
                <Button onClick={props.hide}>Cancel</Button>
                <Button
                    color="success"
                    onClick={async (ev) => {
                        if (value !== null && key_value) {
                            await window.electron.chat.add_user(
                                value,
                                key_value
                            );
                        }
                        props.hide();
                    }}
                >
                    Create
                </Button>
            </DialogActions>
        </Dialog>
    );
}
Example #19
Source File: PurchaseDialogueBody.tsx    From mojito_pdm with Creative Commons Attribution Share Alike 4.0 International 5 votes vote down vote up
PurchaseDialogueBody: React.FC<IPurchaseDialogueBody> = ({spawncode, price, setDialogueOpen, setModalOpen}) => {
    const [colour, setColour] = useState<RgbColor>({r: 0, g: 0, b: 0})
    const handleClose = () => {
        setDialogueOpen(false)
    }
    const {setVisible} = useVisibility()
    const coloursEnabled = useRecoilValue(GlobalState.customColours)

    const handleAccept = async () => {
        setDialogueOpen(false)
        setModalOpen(false)

        try {
            await fetchNui("buy_vehicle", {
                vehicle: spawncode,
                colour: coloursEnabled ? colour : null
            })
            await fetchNui("exit")
            setVisible(false)
        } catch (e) {
            console.error(e)
        }
    }

    return (
        <>
            <DialogTitle>Are you sure?</DialogTitle>
            <DialogContent>
                <DialogContentText>
                    Do you want to purchase this vehicle for {price}?
                </DialogContentText>

                {coloursEnabled &&
                    <DialogContentText>
                        <br />
                        Pick a colour, any colour:
                        <RgbColorPicker color={colour} onChange={setColour} />
                    </DialogContentText>
                }

            </DialogContent>
            <DialogActions>
                <Button color="success" variant="outlined" onClick={handleAccept}>Yes</Button>
                <Button color="error" variant="outlined" onClick={handleClose}>Cancel</Button>
            </DialogActions>
        </>
    )
}
Example #20
Source File: Settings.tsx    From sapio-studio with Mozilla Public License 2.0 4 votes vote down vote up
export function SettingsInner() {
    const [idx, set_idx] = React.useState<number>(0);
    const [dialog_node, set_dialog_node] = React.useState<
        [string | null, string[]]
    >([null, []]);
    const handleChange = (_: any, idx: number) => {
        set_idx(idx);
    };

    const test_bitcoind = async () => {
        window.electron
            .bitcoin_command([{ method: 'getbestblockhash', parameters: [] }])
            .then((h) =>
                set_dialog_node(['Connection Seems OK:', [`Best Hash ${h[0]}`]])
            )
            .catch((e) => {
                console.log('GOT', JSON.stringify(e));
                const r = e.message;
                if (typeof e.message === 'string') {
                    const err = JSON.parse(r);
                    if (
                        err instanceof Object &&
                        'code' in err &&
                        'name' in err &&
                        'message' in err
                    ) {
                        set_dialog_node([
                            '¡Connection Not Working!',
                            [
                                `Name: ${err.name}`,
                                `Message: ${err.message}`,
                                `Error Code: ${err.code}`,
                            ],
                        ]);
                        return;
                    } else if (typeof err === 'string') {
                        set_dialog_node([
                            '¡Connection Not Working!',
                            [`${err}`],
                        ]);
                        return;
                    }
                }
                set_dialog_node(['¡Unknown Error!', [`${r.toString()}`]]);
            });
    };

    const test_sapio = async () => {
        window.electron.sapio
            .show_config()
            .then((conf) => {
                if ('ok' in conf)
                    set_dialog_node([
                        'Sapio-CLI is Working!\nUsing Configuration:',
                        [`${conf.ok}`],
                    ]);
                else
                    set_dialog_node(['¡Configuration Error!', [`${conf.err}`]]);
            })
            .catch((e) =>
                set_dialog_node(['¡Configuration Error!', [`${e.toString()}`]])
            );
    };
    const check_emulator = async () => {
        window.electron.emulator.read_log().then((log) => {
            if (log.length) {
                const json = JSON.parse(log);
                set_dialog_node([
                    'Emulator Status:',
                    [
                        `interface: ${json.interface}`,
                        `pk: ${json.pk}`,
                        `sync: ${json.sync}`,
                    ],
                ]);
            } else {
                set_dialog_node(['Emulator Status:', ['Not Running']]);
            }
        });
    };

    return (
        <div className="Settings">
            <Box className="SettingsNav">
                <Tabs
                    orientation="vertical"
                    value={idx}
                    onChange={handleChange}
                    aria-label="basic tabs example"
                >
                    <Tab label="Guide"></Tab>
                    <Tab label="Sapio CLI"></Tab>
                    <Tab label="Bitcoin"></Tab>
                    <Tab label="Emulator"></Tab>
                    <Tab label="Display"></Tab>
                </Tabs>
            </Box>
            <Box className="SettingsPanes">
                <Dialog
                    open={
                        Boolean(dialog_node[0]) ||
                        Boolean(dialog_node[1].length)
                    }
                    onClose={() => set_dialog_node([null, []])}
                    aria-labelledby="alert-dialog-title"
                    aria-describedby="alert-dialog-description"
                >
                    <DialogTitle id="alert-dialog-title">
                        {dialog_node[0]}
                    </DialogTitle>
                    <DialogContent>
                        <div id="alert-dialog-description">
                            {dialog_node[1].map((txt) => (
                                <DialogContentText key={txt}>
                                    {txt}
                                </DialogContentText>
                            ))}
                        </div>
                    </DialogContent>
                    <DialogActions>
                        <Button onClick={() => set_dialog_node([null, []])}>
                            Close
                        </Button>
                    </DialogActions>
                </Dialog>
                <Guide idx={idx} my_idx={0} />
                <SettingPane name={'sapio_cli'} value={idx} idx={1}>
                    <Button
                        onClick={test_sapio}
                        variant="contained"
                        color="info"
                        size="large"
                    >
                        Test Sapio-Cli
                    </Button>
                </SettingPane>
                <SettingPane name={'bitcoin'} value={idx} idx={2}>
                    <Button
                        onClick={test_bitcoind}
                        variant="contained"
                        color="info"
                        size="large"
                    >
                        Test Connection
                    </Button>
                </SettingPane>
                <SettingPane name={'local_oracle'} value={idx} idx={3}>
                    <Button
                        variant="contained"
                        color="success"
                        size="large"
                        onClick={window.electron.emulator.start}
                    >
                        Start
                    </Button>
                    <Button
                        sx={{ marginLeft: '20px' }}
                        variant="contained"
                        color="error"
                        size="large"
                        onClick={window.electron.emulator.kill}
                    >
                        Kill
                    </Button>
                    <Button
                        sx={{ marginLeft: '20px' }}
                        variant="contained"
                        color="info"
                        size="large"
                        onClick={check_emulator}
                    >
                        Check Status
                    </Button>
                </SettingPane>
                <SettingPane name={'display'} value={idx} idx={4} />
            </Box>
        </div>
    );
}
Example #21
Source File: index.tsx    From mui-toolpad with MIT License 4 votes vote down vote up
export default function HierarchyExplorer({ appId, className }: HierarchyExplorerProps) {
  const dom = useDom();
  const domApi = useDomApi();

  const app = appDom.getApp(dom);
  const {
    apis = [],
    codeComponents = [],
    pages = [],
    connections = [],
  } = appDom.getChildNodes(dom, app);

  const [expanded, setExpanded] = useLocalStorageState<string[]>(
    `editor/${app.id}/hierarchy-expansion`,
    [':connections', ':pages', ':codeComponents'],
  );

  const location = useLocation();
  const match =
    matchRoutes(
      [
        { path: `/app/:appId/editor/pages/:activeNodeId` },
        { path: `/app/:appId/editor/apis/:activeNodeId` },
        { path: `/app/:appId/editor/codeComponents/:activeNodeId` },
        { path: `/app/:appId/editor/connections/:activeNodeId` },
      ],
      location,
    ) || [];

  const selected: NodeId[] = match.map((route) => route.params.activeNodeId as NodeId);

  const handleToggle = (event: React.SyntheticEvent, nodeIds: string[]) => {
    setExpanded(nodeIds as NodeId[]);
  };

  const navigate = useNavigate();

  const handleSelect = (event: React.SyntheticEvent, nodeIds: string[]) => {
    if (nodeIds.length <= 0) {
      return;
    }

    const rawNodeId = nodeIds[0];
    if (rawNodeId.startsWith(':')) {
      return;
    }

    const selectedNodeId: NodeId = rawNodeId as NodeId;
    const node = appDom.getNode(dom, selectedNodeId);
    if (appDom.isElement(node)) {
      // TODO: sort out in-page selection
      const page = appDom.getPageAncestor(dom, node);
      if (page) {
        navigate(`/app/${appId}/editor/pages/${page.id}`);
      }
    }

    if (appDom.isPage(node)) {
      navigate(`/app/${appId}/editor/pages/${node.id}`);
    }

    if (appDom.isApi(node)) {
      navigate(`/app/${appId}/editor/apis/${node.id}`);
    }

    if (appDom.isCodeComponent(node)) {
      navigate(`/app/${appId}/editor/codeComponents/${node.id}`);
    }

    if (appDom.isConnection(node)) {
      navigate(`/app/${appId}/editor/connections/${node.id}`);
    }
  };

  const [createConnectionDialogOpen, setCreateConnectionDialogOpen] = React.useState(0);
  const handleCreateConnectionDialogOpen = React.useCallback((event: React.MouseEvent) => {
    event.stopPropagation();
    setCreateConnectionDialogOpen(Math.random());
  }, []);
  const handleCreateConnectionDialogClose = React.useCallback(
    () => setCreateConnectionDialogOpen(0),
    [],
  );

  const [createPageDialogOpen, setCreatePageDialogOpen] = React.useState(0);
  const handleCreatePageDialogOpen = React.useCallback((event: React.MouseEvent) => {
    event.stopPropagation();
    setCreatePageDialogOpen(Math.random());
  }, []);
  const handleCreatepageDialogClose = React.useCallback(() => setCreatePageDialogOpen(0), []);

  const [createCodeComponentDialogOpen, setCreateCodeComponentDialogOpen] = React.useState(0);
  const handleCreateCodeComponentDialogOpen = React.useCallback((event: React.MouseEvent) => {
    event.stopPropagation();
    setCreateCodeComponentDialogOpen(Math.random());
  }, []);
  const handleCreateCodeComponentDialogClose = React.useCallback(
    () => setCreateCodeComponentDialogOpen(0),
    [],
  );

  const [deletedNodeId, setDeletedNodeId] = React.useState<NodeId | null>(null);
  const handleDeleteNodeDialogOpen = React.useCallback(
    (nodeId: NodeId) => (event: React.MouseEvent) => {
      event.stopPropagation();
      setDeletedNodeId(nodeId);
    },
    [],
  );
  const handledeleteNodeDialogClose = React.useCallback(() => setDeletedNodeId(null), []);

  const handleDeleteNode = React.useCallback(() => {
    if (deletedNodeId) {
      domApi.removeNode(deletedNodeId);
      navigate(`/app/${appId}/editor/`);
      handledeleteNodeDialogClose();
    }
  }, [deletedNodeId, domApi, navigate, appId, handledeleteNodeDialogClose]);

  const deletedNode = deletedNodeId && appDom.getMaybeNode(dom, deletedNodeId);
  const latestDeletedNode = useLatest(deletedNode);

  return (
    <HierarchyExplorerRoot className={className}>
      <TreeView
        aria-label="hierarchy explorer"
        selected={selected}
        onNodeSelect={handleSelect}
        expanded={expanded}
        onNodeToggle={handleToggle}
        multiSelect
        defaultCollapseIcon={<ExpandMoreIcon />}
        defaultExpandIcon={<ChevronRightIcon />}
      >
        <HierarchyTreeItem
          nodeId=":connections"
          labelText="Connections"
          onCreate={handleCreateConnectionDialogOpen}
        >
          {connections.map((connectionNode) => (
            <HierarchyTreeItem
              key={connectionNode.id}
              nodeId={connectionNode.id}
              labelText={connectionNode.name}
              onDelete={handleDeleteNodeDialogOpen(connectionNode.id)}
            />
          ))}
        </HierarchyTreeItem>
        {apis.length > 0 ? (
          <HierarchyTreeItem nodeId=":apis" labelText="Apis">
            {apis.map((apiNode) => (
              <HierarchyTreeItem
                key={apiNode.id}
                nodeId={apiNode.id}
                labelText={apiNode.name}
                onDelete={handleDeleteNodeDialogOpen(apiNode.id)}
              />
            ))}
          </HierarchyTreeItem>
        ) : null}
        <HierarchyTreeItem
          nodeId=":codeComponents"
          labelText="Components"
          onCreate={handleCreateCodeComponentDialogOpen}
        >
          {codeComponents.map((codeComponent) => (
            <HierarchyTreeItem
              key={codeComponent.id}
              nodeId={codeComponent.id}
              labelText={codeComponent.name}
              onDelete={handleDeleteNodeDialogOpen(codeComponent.id)}
            />
          ))}
        </HierarchyTreeItem>
        <HierarchyTreeItem nodeId=":pages" labelText="Pages" onCreate={handleCreatePageDialogOpen}>
          {pages.map((page) => (
            <HierarchyTreeItem
              key={page.id}
              nodeId={page.id}
              labelText={page.name}
              onDelete={handleDeleteNodeDialogOpen(page.id)}
            />
          ))}
        </HierarchyTreeItem>
      </TreeView>

      <CreateConnectionNodeDialog
        key={createConnectionDialogOpen || undefined}
        appId={appId}
        open={!!createConnectionDialogOpen}
        onClose={handleCreateConnectionDialogClose}
      />
      <CreatePageNodeDialog
        key={createPageDialogOpen || undefined}
        appId={appId}
        open={!!createPageDialogOpen}
        onClose={handleCreatepageDialogClose}
      />
      <CreateCodeComponentNodeDialog
        key={createCodeComponentDialogOpen || undefined}
        appId={appId}
        open={!!createCodeComponentDialogOpen}
        onClose={handleCreateCodeComponentDialogClose}
      />
      <Dialog open={!!deletedNode} onClose={handledeleteNodeDialogClose}>
        <DialogTitle>
          Delete {latestDeletedNode?.type} &quot;{latestDeletedNode?.name}&quot;?
        </DialogTitle>
        <DialogActions>
          <Button
            type="submit"
            color="inherit"
            variant="text"
            onClick={handledeleteNodeDialogClose}
          >
            Cancel
          </Button>
          <Button type="submit" onClick={handleDeleteNode}>
            Delete
          </Button>
        </DialogActions>
      </Dialog>
    </HierarchyExplorerRoot>
  );
}
Example #22
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 #23
Source File: DetailsModal.tsx    From frontend with MIT License 4 votes vote down vote up
function DetailsModal() {
  const { getDialogs } = DialogStore
  const handleClose = () => DialogStore.hide()
  const { t } = useTranslation()

  return (
    <>
      {getDialogs.map(({ id, show, title, row }) => {
        return (
          <Dialog
            key={id}
            onClose={handleClose}
            open={show}
            maxWidth="md"
            PaperProps={{ elevation: 5 }}
            BackdropProps={{ style: { opacity: 0.3 } }}>
            {title && <DialogTitle>{title}</DialogTitle>}
            <DialogContent dividers>
              {/* TODO: Extract concrete implementation and use generic one */}
              <Grid item xs={12}>
                <List>
                  <ListItem>
                    <ListItemText
                      primary={`${row.getValue(row.id, 'name')}`}
                      secondary={row.row.person.company}
                    />
                  </ListItem>
                  <ListItem>
                    <ListItemText primary={row.row.person.email} secondary={row.row.person.phone} />
                  </ListItem>
                  <ListItem>{dateFormatter(row.row.createdAt)}</ListItem>
                  <ListItem>
                    <Typography variant="body2">{row.row.message || row.row.comment}</Typography>
                  </ListItem>
                  <ListItem>
                    <Typography variant="caption">{row.row.person.id}</Typography>
                  </ListItem>
                  {'associationMember' in row.row &&
                    [
                      'associationMember',
                      'benefactorCampaign',
                      'benefactorPlatform',
                      'companyOtherText',
                      'companySponsor',
                      'companyVolunteer',
                      'partnerBussiness',
                      'partnerNpo',
                      'partnerOtherText',
                      'roleAssociationMember',
                      'roleBenefactor',
                      'roleCompany',
                      'rolePartner',
                      'roleVolunteer',
                      'volunteerBackend',
                      'volunteerDesigner',
                      'volunteerDevOps',
                      'volunteerFinancesAndAccounts',
                      'volunteerFrontend',
                      'volunteerLawyer',
                      'volunteerMarketing',
                      'volunteerProjectManager',
                      'volunteerQa',
                      'volunteerSecurity',
                    ].map((k, i) => (
                      <ListItem key={i}>
                        <ListItemText
                          primary={k}
                          secondary={row.row[k] ? <Check color="primary" /> : <Clear />}
                        />
                      </ListItem>
                    ))}
                </List>
              </Grid>
              {/*  */}
            </DialogContent>
            <DialogActions>
              <Button autoFocus onClick={handleClose} color="primary">
                {t('common:close')}
              </Button>
            </DialogActions>
          </Dialog>
        )
      })}
    </>
  )
}
Example #24
Source File: ConfirmDialog.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
ConfirmDialog = ({
  isOpen = false,
  onClose,
  onCancel,
  onConfirm,
  classes = {},
  title = "",
  isLoading,
  confirmationContent,
  cancelText = "Cancel",
  confirmText = "Confirm",
  confirmButtonProps = {},
  cancelButtonProps = {},
  titleIcon = null,
}: ConfirmDialogProps) => {
  return (
    <Dialog
      open={isOpen}
      onClose={(event, reason) => {
        if (reason !== "backdropClick") {
          onClose(); // close on Esc but not on click outside
        }
      }}
      className={classes.root}
      sx={{
        "& .MuiPaper-root": {
          padding: "1rem 2rem 2rem 1rem",
        },
      }}
    >
      <DialogTitle className={classes.title}>
        <div className={classes.titleText}>
          {titleIcon} {title}
        </div>
        <div className={classes.closeContainer}>
          <IconButton
            aria-label="close"
            className={classes.closeButton}
            onClick={onClose}
            disableRipple
            size="small"
          >
            <CloseIcon />
          </IconButton>
        </div>
      </DialogTitle>

      <DialogContent className={classes.content}>
        {confirmationContent}
      </DialogContent>
      <DialogActions className={classes.actions}>
        <Button
          className={classes.cancelButton}
          onClick={onCancel || onClose}
          disabled={isLoading}
          type="button"
          {...cancelButtonProps}
          variant="outlined"
          color="primary"
          id={"confirm-cancel"}
        >
          {cancelText}
        </Button>

        <LoadingButton
          className={classes.confirmButton}
          type="button"
          onClick={onConfirm}
          loading={isLoading}
          disabled={isLoading}
          variant="outlined"
          color="secondary"
          loadingPosition="start"
          startIcon={<React.Fragment />}
          autoFocus
          id={"confirm-ok"}
          {...confirmButtonProps}
        >
          {confirmText}
        </LoadingButton>
      </DialogActions>
    </Dialog>
  );
}
Example #25
Source File: GridColumns.tsx    From mui-toolpad with MIT License 4 votes vote down vote up
function GridColumnsPropEditor({
  label,
  nodeId,
  value = [],
  onChange,
  disabled,
}: EditorProps<GridColumns>) {
  const { bindings } = usePageEditorState();
  const [editColumnsDialogOpen, setEditColumnsDialogOpen] = React.useState(false);
  const [editedIndex, setEditedIndex] = React.useState<number | null>(null);

  const editedColumn = typeof editedIndex === 'number' ? value[editedIndex] : null;
  React.useEffect(() => {
    if (editColumnsDialogOpen) {
      setEditedIndex(null);
    }
  }, [editColumnsDialogOpen]);

  const [menuAnchorEl, setMenuAnchorEl] = React.useState<null | HTMLElement>(null);
  const menuOpen = Boolean(menuAnchorEl);
  const handleMenuClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    setMenuAnchorEl(event.currentTarget);
  };
  const handleClose = () => {
    setMenuAnchorEl(null);
  };

  const rowsValue = nodeId && bindings[`${nodeId}.props.rows`];
  const definedRows: unknown = rowsValue?.value;

  const columnSuggestions = React.useMemo(() => {
    const inferred = inferColumns(Array.isArray(definedRows) ? definedRows : []);
    const existingFields = new Set(value.map(({ field }) => field));
    return inferred.filter((column) => !existingFields.has(column.field));
  }, [definedRows, value]);

  const handleCreateColumn = React.useCallback(
    (suggestion: GridColDef) => () => {
      const existingFields = new Set(value.map(({ field }) => field));
      const newFieldName = generateUniqueString(suggestion.field, existingFields);
      const newValue = [...value, { ...suggestion, field: newFieldName }];
      onChange(newValue);
      setEditedIndex(newValue.length - 1);
      handleClose();
    },
    [value, onChange],
  );

  const handleColumnItemClick = React.useCallback(
    (index: number) => () => {
      setEditedIndex(index);
    },
    [],
  );

  const handleColumnChange = React.useCallback(
    (newValue: GridColDef) => {
      onChange(value.map((column, i) => (i === editedIndex ? newValue : column)));
    },
    [editedIndex, onChange, value],
  );

  const handleColumnDelete = React.useCallback(
    (deletedIndex: number) => (event: React.MouseEvent) => {
      event.stopPropagation();
      onChange(value.filter((column, i) => i !== deletedIndex));
    },
    [onChange, value],
  );

  return (
    <React.Fragment>
      <Button onClick={() => setEditColumnsDialogOpen(true)}>{label}</Button>
      <Dialog
        fullWidth
        open={editColumnsDialogOpen}
        onClose={() => setEditColumnsDialogOpen(false)}
      >
        {editedColumn ? (
          <React.Fragment>
            <DialogTitle>
              <IconButton aria-label="Back" onClick={() => setEditedIndex(null)}>
                <ArrowBackIcon />
              </IconButton>
              Edit column {editedColumn.field}
            </DialogTitle>
            <DialogContent>
              <Stack gap={1} py={1}>
                <TextField
                  label="field"
                  value={editedColumn.field}
                  disabled={disabled}
                  onChange={(event) =>
                    handleColumnChange({ ...editedColumn, field: event.target.value })
                  }
                />
                <TextField
                  select
                  fullWidth
                  label="type"
                  value={editedColumn.type ?? ''}
                  disabled={disabled}
                  onChange={(event) =>
                    handleColumnChange({ ...editedColumn, type: event.target.value })
                  }
                >
                  {COLUMN_TYPES.map((type) => (
                    <MenuItem key={type} value={type}>
                      {type}
                    </MenuItem>
                  ))}
                </TextField>
                <TextField
                  select
                  fullWidth
                  label="align"
                  value={editedColumn.align ?? ''}
                  disabled={disabled}
                  onChange={(event) =>
                    handleColumnChange({
                      ...editedColumn,
                      align: (event.target.value as GridAlignment) || undefined,
                    })
                  }
                >
                  {ALIGNMENTS.map((alignment) => (
                    <MenuItem key={alignment} value={alignment}>
                      {alignment}
                    </MenuItem>
                  ))}
                </TextField>
                <TextField
                  label="width"
                  type="number"
                  value={editedColumn.width}
                  disabled={disabled}
                  onChange={(event) =>
                    handleColumnChange({ ...editedColumn, width: Number(event.target.value) })
                  }
                />
              </Stack>
            </DialogContent>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <DialogTitle>Edit columns</DialogTitle>
            <DialogContent>
              <IconButton aria-label="Add column" onClick={handleMenuClick} disabled={disabled}>
                <AddIcon />
              </IconButton>
              <Menu
                id="new-column-menu"
                anchorEl={menuAnchorEl}
                open={menuOpen}
                onClose={handleClose}
                MenuListProps={{
                  'aria-labelledby': 'basic-button',
                }}
              >
                {columnSuggestions.map((suggestion) => (
                  <MenuItem key={suggestion.field} onClick={handleCreateColumn(suggestion)}>
                    {suggestion.field}
                  </MenuItem>
                ))}
                <MenuItem onClick={handleCreateColumn({ field: 'new' })}>new column</MenuItem>
              </Menu>
              <List>
                {value.map((colDef, i) => {
                  return (
                    <ListItem
                      key={colDef.field}
                      disableGutters
                      onClick={handleColumnItemClick(i)}
                      secondaryAction={
                        <IconButton
                          aria-label="Remove column"
                          edge="end"
                          onClick={handleColumnDelete(i)}
                        >
                          <DeleteIcon />
                        </IconButton>
                      }
                    >
                      <ListItemButton>
                        <ListItemText primary={colDef.field} />
                      </ListItemButton>
                    </ListItem>
                  );
                })}
              </List>
            </DialogContent>
          </React.Fragment>
        )}
        <DialogActions>
          <Button color="inherit" variant="text" onClick={() => setEditColumnsDialogOpen(false)}>
            Close
          </Button>
        </DialogActions>
      </Dialog>
    </React.Fragment>
  );
}
Example #26
Source File: TransactionCreateModal.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function TransactionCreateModal({ group, show, onClose }) {
    const setTransactions = useSetRecoilState(groupTransactions(group.id));

    const handleSubmit = (values, { setSubmitting }) => {
        createTransaction({
            groupID: group.id,
            type: values.type,
            description: values.description,
            value: parseFloat(values.value),
            billedAt: values.billedAt.toISODate(),
            currencySymbol: "€",
            currencyConversionRate: 1.0,
        })
            .then((t) => {
                addTransactionInState(t, setTransactions);
                setSubmitting(false);
                onClose();
            })
            .catch((err) => {
                toast.error(err);
                setSubmitting(false);
            });
    };

    const validate = (values) => {
        let errors = { value: undefined, description: undefined, billedAt: undefined };
        const floatValue = parseFloat(values.value);
        if (isNaN(floatValue) || floatValue <= 0) {
            errors.value = "please input a valid decimal number";
        }
        if (values.description === null || values.description === undefined || values.description === "") {
            errors.description = "please input a description";
        }
        if (values.billedAt === null || values.billedAt === undefined || values.billedAt === "") {
            errors.billedAt = "please input valid billed at time";
        }
        return errors;
    };

    return (
        <Dialog open={show} onClose={onClose}>
            <DialogTitle>Create Transaction</DialogTitle>
            <DialogContent>
                <Formik
                    validate={validate}
                    initialValues={{
                        type: "purchase",
                        description: "",
                        value: "0.0",
                        billedAt: DateTime.now(),
                    }}
                    onSubmit={handleSubmit}
                >
                    {({ values, setFieldValue, handleChange, handleBlur, handleSubmit, isSubmitting }) => (
                        <Form>
                            <Select
                                required
                                value={values.type}
                                onChange={handleChange}
                                onBlur={handleBlur}
                                variant="standard"
                                name="type"
                            >
                                <MenuItem value="purchase">Purchase</MenuItem>
                                <MenuItem value="transfer">Transfer</MenuItem>
                                {/*<MenuItem value="mimo">MIMO</MenuItem>*/}
                            </Select>
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                variant="standard"
                                name="description"
                                label="Description"
                                autoFocus
                                value={values.description}
                                onChange={handleChange}
                                onBlur={handleBlur}
                            />
                            <DatePicker
                                inputFormat="yyyy-MM-dd"
                                renderInput={(params) => (
                                    <TextField
                                        name="billedAt"
                                        required
                                        variant="standard"
                                        fullWidth
                                        {...params}
                                        helperText={null}
                                    />
                                )}
                                label="Billed at"
                                value={values.billedAt}
                                onChange={(val) => setFieldValue("billedAt", val, true)}
                            />
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                type="number"
                                variant="standard"
                                name="value"
                                label="Value"
                                value={values.value}
                                onChange={handleChange}
                                onBlur={handleBlur}
                            />
                            {isSubmitting && <LinearProgress />}
                            <DialogActions>
                                <Button type="submit" color="primary" disabled={isSubmitting}>
                                    Save
                                </Button>
                                <Button color="error" onClick={onClose}>
                                    Cancel
                                </Button>
                            </DialogActions>
                        </Form>
                    )}
                </Formik>
            </DialogContent>
        </Dialog>
    );
}
Example #27
Source File: ExportButton.tsx    From firecms with MIT License 4 votes vote down vote up
export function ExportButton<M extends { [Key: string]: any }, UserType>({
                                                                             schema,
                                                                             schemaResolver,
                                                                             path,
                                                                             exportConfig
                                                                         }: ExportButtonProps<M, UserType>
) {

    const dataSource = useDataSource();
    const context = useFireCMSContext();

    const dataRef = useRef<Entity<M>[]>();
    const additionalDataRef = useRef<Record<string, any>[]>();

    const [dataLoading, setDataLoading] = React.useState<boolean>(false);
    const [dataLoadingError, setDataLoadingError] = React.useState<Error | undefined>();

    // If in the initial load, we get more than INITIAL_DOCUMENTS_LIMIT results
    const [hasLargeAmountOfData, setHasLargeAmountOfData] = React.useState<boolean>(false);

    // did the user agree to export a large amount of data
    const [fetchLargeDataAccepted, setFetchLargeDataAccepted] = React.useState<boolean>(false);

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

    const handleClickOpen = useCallback(() => {
        setOpen(true);
    }, [setOpen]);

    const handleClose = useCallback(() => {
        setOpen(false);
    }, [setOpen]);

    const doDownload = useCallback((data: Entity<M>[] | undefined,
                                    additionalData: Record<string, any>[] | undefined,
                                    schema: EntitySchema<M>,
                                    schemaResolver: EntitySchemaResolver<M>,
                                    path: string,
                                    exportConfig: ExportConfig | undefined) => {
        if (!data)
            throw Error("Trying to perform export without loading data first");

        const resolvedSchema = schemaResolver({});
        downloadCSV(data, additionalData, resolvedSchema, path, exportConfig);
    }, []);

    useEffect(() => {

        if (!open) return;

        setDataLoading(true);

        const updateEntities = async (entities: Entity<M>[]) => {
            if (entities.length >= INITIAL_DOCUMENTS_LIMIT) {
                setHasLargeAmountOfData(true);
            }

            const pendingDownload = dataRef.current && entities.length > dataRef.current.length && fetchLargeDataAccepted;

            dataRef.current = entities;
            const additionalColumnsData = await fetchAdditionalColumns(entities);
            additionalDataRef.current = additionalColumnsData;
            setDataLoading(false);
            setDataLoadingError(undefined);

            if (pendingDownload) {
                doDownload(entities, additionalColumnsData, schema, schemaResolver, path, exportConfig);
                handleClose();
            }
        };

        const fetchAdditionalColumns = async (entities: Entity<M>[]) => {

            if (!exportConfig?.additionalColumns) {
                return;
            }

            const additionalColumns = exportConfig.additionalColumns;

            const resolvedColumnsValues: Record<string, any>[] = await Promise.all(entities.map(async (entity) => {
                return (await Promise.all(additionalColumns.map(async (column) => {
                    return {
                        [column.key]: await column.builder({
                            entity,
                            context
                        })
                    };
                }))).reduce((a, b) => ({ ...a, ...b }), {});
            }));
            return resolvedColumnsValues;
        };

        const onFetchError = (error: Error) => {
            console.error("ERROR", error);
            setDataLoading(false);
            setDataLoadingError(error);
        };

        dataSource.fetchCollection<M>({
            path,
            schema: schemaResolver,
            limit: fetchLargeDataAccepted ? undefined : INITIAL_DOCUMENTS_LIMIT
        })
            .then(updateEntities)
            .catch(onFetchError);

    }, [path, fetchLargeDataAccepted, schema, open, dataSource, schemaResolver, doDownload, exportConfig, handleClose, context]);

    const needsToAcceptFetchAllData = hasLargeAmountOfData && !fetchLargeDataAccepted;

    const onOkClicked = useCallback(() => {
        if (needsToAcceptFetchAllData) {
            setFetchLargeDataAccepted(true);
        } else {
            doDownload(dataRef.current, additionalDataRef.current, schema, schemaResolver, path, exportConfig);
            handleClose();
        }
    }, [needsToAcceptFetchAllData, doDownload, schema, schemaResolver, path, exportConfig, handleClose]);

    return <>

        <Tooltip title={"Export"}>
            <IconButton color={"primary"} onClick={handleClickOpen}
                        size="large">
                <GetAppIcon/>
            </IconButton>
        </Tooltip>

        <Dialog
            open={open}
            onClose={handleClose}
        >
            <DialogTitle>Export data</DialogTitle>

            <DialogContent>
                <DialogContentText>

                    <div>Download the the content of this table as a CSV
                    </div>
                    <br/>

                    {needsToAcceptFetchAllData &&
                    <Alert elevation={1}
                              variant="filled"
                              severity={"warning"}>
                        <div>
                            This collections has a large number
                            of documents (more than {INITIAL_DOCUMENTS_LIMIT}).
                        </div>
                        <div>
                            Would you like to proceed?
                        </div>

                    </Alert>}

                </DialogContentText>
            </DialogContent>

            <DialogActions>

                {dataLoading && <CircularProgress size={16} thickness={8}/>}

                <Button color="primary" onClick={handleClose}>
                    Cancel
                </Button>

                <Button color="primary"
                        disabled={dataLoading}
                        onClick={onOkClicked}>
                    Download
                </Button>

            </DialogActions>

        </Dialog>

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

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

  const { setAppStatus } = useAppState();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    let uploadPort: string | undefined;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        const deviceMatch = deviceMatches[0];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                {response?.buildFlashFirmware.success && luaDownloadButton()}
              </CardContent>
            </>
          )}
        </Card>
      )}
    </MainLayout>
  );
}
Example #29
Source File: CreateAccountModal.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function CreateAccountModal({ show, onClose, group }) {
    const setAccounts = useSetRecoilState(groupAccounts(group.id));
    const userPermissions = useRecoilValue(currUserPermissions(group.id));
    const currentUser = useRecoilValue(userData);

    const handleSubmit = (values, { setSubmitting }) => {
        createAccount({
            groupID: group.id,
            name: values.name,
            owningUserID: values.owningUserID,
            description: values.description,
        })
            .then((account) => {
                toast.success(`Created account ${values.name}`);
                addAccount(account, setAccounts);
                setSubmitting(false);
                onClose();
            })
            .catch((err) => {
                toast.error(err);
                setSubmitting(false);
            });
    };
    return (
        <Dialog open={show} onClose={onClose}>
            <DialogTitle>Create Personal Account</DialogTitle>

            <DialogContent>
                <Formik
                    initialValues={{ name: "", description: "", owningUserID: null }}
                    onSubmit={handleSubmit}
                    validationSchema={validationSchema}
                >
                    {({
                        values,
                        touched,
                        errors,
                        handleChange,
                        handleBlur,
                        handleSubmit,
                        isSubmitting,
                        setFieldValue,
                    }) => (
                        <Form>
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                autoFocus
                                variant="standard"
                                name="name"
                                label="Account Name"
                                onBlur={handleBlur}
                                onChange={handleChange}
                                value={values.name}
                                error={touched.name && Boolean(errors.name)}
                                helperText={touched.name && (errors.name as ReactNode)}
                            />
                            <TextField
                                margin="normal"
                                name="description"
                                fullWidth
                                variant="standard"
                                label="Description"
                                onBlur={handleBlur}
                                onChange={handleChange}
                                value={values.description}
                                error={touched.description && Boolean(errors.description)}
                                helperText={touched.description && (errors.description as ReactNode)}
                            />
                            {userPermissions.is_owner ? (
                                <GroupMemberSelect
                                    margin="normal"
                                    group={group}
                                    label="Owning user"
                                    value={values.owningUserID}
                                    onChange={(user_id) => setFieldValue("owningUserID", user_id)}
                                />
                            ) : (
                                <FormControlLabel
                                    control={
                                        <Checkbox
                                            name="owningUserID"
                                            onChange={(e) =>
                                                setFieldValue("owningUserID", e.target.checked ? currentUser.id : null)
                                            }
                                            checked={values.owningUserID === currentUser.id}
                                        />
                                    }
                                    label="This is me"
                                />
                            )}

                            {isSubmitting && <LinearProgress />}
                            <DialogActions>
                                <Button type="submit" color="primary" disabled={isSubmitting}>
                                    Save
                                </Button>
                                <Button color="error" onClick={onClose}>
                                    Cancel
                                </Button>
                            </DialogActions>
                        </Form>
                    )}
                </Formik>
            </DialogContent>
        </Dialog>
    );
}