@material-ui/core#ButtonGroup TypeScript Examples

The following examples show how to use @material-ui/core#ButtonGroup. 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: QuerySqlInput.tsx    From SeeQR with MIT License 6 votes vote down vote up
QuerySqlInput = ({ sql, onChange, runQuery }: QuerySqlInputProps) => {
  const formatQuery = () => {
    const formatted = format(sql, { language: 'postgresql', uppercase: true });
    onChange(formatted);
  };

  // Codemirror module configuration options
  const options = {
    lineNumbers: true,
    mode: 'sql',
    theme: 'lesser-dark',
    extraKeys: {
      'Ctrl-Enter': runQuery,
      'Ctrl-F': formatQuery,
    },
  };
  return (
    <Container>
      <Toolbar>
        <ButtonGroup variant="contained">
          <Tooltip title="Auto-Format Query">
            <SquareBtn onClick={formatQuery}>
              <FormatPaintIcon />
            </SquareBtn>
          </Tooltip>
        </ButtonGroup>
      </Toolbar>
      <CodeMirror onChange={onChange} options={options} value={sql}/>
    </Container>
  );
}
Example #2
Source File: index.tsx    From prism-frontend with MIT License 6 votes vote down vote up
function LanguageSelector({ classes }: LanguageSelectorProps) {
  const { i18n } = useSafeTranslation();

  const handleChangeLanguage = (lng: string): void => {
    i18n.changeLanguage(lng);
  };
  // If there is only one language, hide the selector
  if (languages.length <= 1) {
    return null;
  }

  return (
    <ButtonGroup variant="text" className={classes.block}>
      {languages.map(lng => (
        <Button
          key={lng}
          type="submit"
          onClick={() => handleChangeLanguage(lng)}
        >
          <Typography
            variant="body2"
            style={{
              fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal',
            }}
          >
            {lng}
          </Typography>
        </Button>
      ))}
    </ButtonGroup>
  );
}
Example #3
Source File: PullRequestStatusButtonGroup.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
PullRequestStatusButtonGroup = ({
  status,
  setStatus,
}: {
  status: PullRequestStatus;
  setStatus: (pullRequestStatus: PullRequestStatus) => void;
}) => {
  return (
    <ButtonGroup aria-label="outlined button group">
      <Button
        color={status === PullRequestStatus.Active ? 'primary' : 'default'}
        onClick={() => {
          setStatus(PullRequestStatus.Active);
        }}
      >
        Active
      </Button>
      <Button
        color={status === PullRequestStatus.Completed ? 'primary' : 'default'}
        onClick={() => {
          setStatus(PullRequestStatus.Completed);
        }}
      >
        Completed
      </Button>
      <Button
        color={status === PullRequestStatus.Abandoned ? 'primary' : 'default'}
        onClick={() => {
          setStatus(PullRequestStatus.Abandoned);
        }}
      >
        Abandoned
      </Button>
    </ButtonGroup>
  );
}
Example #4
Source File: SchemaSqlInput.tsx    From SeeQR with MIT License 6 votes vote down vote up
SchemaSqlInput = ({ sql, onChange, runQuery }: SchemaSqlInputProps) => {
    const formatQuery = () => {
      const formatted = format(sql, { language: 'postgresql', uppercase: true });
      onChange(formatted);
    };
  
    // Codemirror module configuration options
    const options = {
      lineNumbers: true,
      mode: 'sql',
      theme: 'lesser-dark',
      extraKeys: {
        'Ctrl-Enter': runQuery,
        'Ctrl-F': formatQuery,
      },
    };
    return (
      <Container>
        <Toolbar>
          <ButtonGroup variant="contained">
            <Tooltip title="Auto-Format Query">
              <SquareBtn onClick={formatQuery}>
                <FormatPaintIcon />
              </SquareBtn>
            </Tooltip>
          </ButtonGroup>
        </Toolbar>
        <CodeMirror onChange={onChange} options={options} value={sql} />
      </Container>
    );
  }
Example #5
Source File: MainToolBar.tsx    From logo-generator with MIT License 6 votes vote down vote up
export default function MainToolBar(props:any) {
    const classes = useStyles();

    return (
        <Card className={classes.toolBarRoot}>
            <ButtonGroup variant="outlined" color="default" className={classes.toolBarButtonGroup}>
                <Tooltip title="Go Dark" aria-label="Go Dark" placement="top">
                    <Button
                        onClick={() => props.toDark(true)}
                        className={ props.darkMode === true ? classes.selected : ""}>
                        <Brightness3 />
                    </Button>
                </Tooltip>
                <Tooltip title="Light Up" aria-label="Light Up" placement="top">
                    <Button
                        onClick={() => props.toDark(false)}
                        className={ props.darkMode === false ? classes.selected : ""}>
                        <Brightness7 />
                    </Button>
                </Tooltip>
            </ButtonGroup>
        </Card>
    );
}
Example #6
Source File: TabSelector.tsx    From SeeQR with MIT License 5 votes vote down vote up
ViewBtnGroup = styled(ButtonGroup)`
  margin: ${defaultMargin} 0;
`
Example #7
Source File: SwapSection.tsx    From interface-v2 with GNU General Public License v3.0 5 votes vote down vote up
SwapSection: React.FC = () => {
  const classes = useStyles();
  const { palette, breakpoints } = useTheme();
  const mobileWindowSize = useMediaQuery(breakpoints.down('sm'));
  const [tabIndex, setTabIndex] = useState(SWAP_TAB);

  return (
    <>
      <Box className={classes.buttonGroup}>
        <ButtonGroup>
          <Button
            className={tabIndex === SWAP_TAB ? 'active' : ''}
            onClick={() => setTabIndex(SWAP_TAB)}
          >
            Swap
          </Button>
          <Button
            className={tabIndex === LIQUIDITY_TAB ? 'active' : ''}
            onClick={() => setTabIndex(LIQUIDITY_TAB)}
          >
            Liquidity
          </Button>
        </ButtonGroup>
      </Box>
      <Box className={classes.swapContainer}>
        <Grid container spacing={mobileWindowSize ? 0 : 8} alignItems='center'>
          <Grid item sm={12} md={6}>
            {tabIndex === SWAP_TAB ? (
              <Swap currencyBg={palette.background.paper} />
            ) : (
              <AddLiquidity currencyBg={palette.background.paper} />
            )}
          </Grid>
          <Grid item sm={12} md={6} className={classes.swapInfo}>
            <Typography variant='h4'>
              {tabIndex === SWAP_TAB
                ? 'Swap tokens at near-zero gas fees'
                : 'Let your crypto work for you'}
            </Typography>
            <Typography variant='body1' style={{ marginTop: '20px' }}>
              {tabIndex === SWAP_TAB
                ? 'Deposit your Liquidity Provider tokens to receive Rewards in $QUICK on top of LP Fees.'
                : 'Provide Liquidity and earn 0.25% fee on all trades proportional to your share of the pool. Earn additional rewards by depositing your LP Tokens in Rewards Pools.'}
            </Typography>
          </Grid>
        </Grid>
      </Box>
    </>
  );
}
Example #8
Source File: index.tsx    From aqualink-app with MIT License 5 votes vote down vote up
MenuDrawer = ({ classes, open, onClose }: MenuDrawerProps) => {
  return (
    <Drawer
      anchor="left"
      open={open}
      onClose={onClose}
      classes={{ paper: classes.paper }}
    >
      <IconButton
        onClick={onClose}
        style={{
          alignSelf: "flex-end",
          marginRight: 5,
          marginTop: 5,
          color: "white",
        }}
      >
        <Clear />
      </IconButton>
      {menuRoutes.map(({ text, to, href, gaActionLabel }) => (
        <Button
          className={classes.menuDrawerButton}
          key={text}
          component={href ? ExternalLink : Link}
          target={href ? "_blank" : undefined}
          href={href || undefined}
          to={to || ""}
          onClick={() =>
            trackButtonClick(
              GaCategory.BUTTON_CLICK,
              GaAction.SIDE_MENU_BUTTON_CLICK,
              gaActionLabel
            )
          }
        >
          <Typography variant="h6">{text}</Typography>
        </Button>
      ))}
      <Box marginTop="auto" padding="25px">
        <Typography variant="subtitle1">
          {/* eslint-disable-next-line react/no-unescaped-entities */}
          Aqualink is open-source (MIT). Join us and contribute!
        </Typography>
        <ButtonGroup variant="contained" color="default">
          <Button
            target="_blank"
            href="https://github.com/aqualinkorg/aqualink-app"
            startIcon={<GitHub />}
            className={classes.contributeButton}
          >
            GitHub
          </Button>

          <Button
            target="_blank"
            href="https://ovio.org/project/aqualinkorg/aqualink-app"
            className={classes.contributeButton}
          >
            <img src={ovioLogo} alt="Ovio Logo" />
          </Button>
        </ButtonGroup>
      </Box>
    </Drawer>
  );
}
Example #9
Source File: button-switch.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
export function ButtonSwitch<T extends string>(props: ButtonSwitchProps<T>) {
  const { values, vertical = false } = props;

  const onClick = useCallback(
    (ev: MouseEvent<HTMLSpanElement>) => {
      const btn = findParent('BUTTON', ev.target as HTMLElement);
      const index = [...btn.parentElement!.children].findIndex(
        child => child === btn,
      );
      const value = switchValue(values[index]);

      if (props.multi) {
        props.onChange(
          props.selection.includes(value as T)
            ? props.selection.filter(val => val !== value)
            : [...props.selection, value as T],
        );
      } else {
        props.onChange(value as T);
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [values, props.selection, props.multi, props.onChange],
  );

  const hasSelection = (value: T) => {
    if (props.multi) {
      return props.selection.includes(value);
    }
    return props.selection === value;
  };

  const tooltipify = (value: SwitchValue<T>, elem: JSX.Element) =>
    typeof value === 'object' && value.tooltip ? (
      <Tooltip
        key={value.value}
        TransitionComponent={Zoom}
        title={value.tooltip}
        arrow
      >
        {elem}
      </Tooltip>
    ) : (
      elem
    );

  return (
    <ButtonGroup
      disableElevation
      orientation={vertical ? 'vertical' : 'horizontal'}
      variant="outlined"
      size="small"
    >
      {values.map(value =>
        tooltipify(
          value,
          <Button
            key={switchValue(value)}
            color={hasSelection(switchValue(value)) ? 'primary' : 'default'}
            variant={
              hasSelection(switchValue(value)) ? 'contained' : 'outlined'
            }
            onClick={onClick}
          >
            {switchText(value)}
          </Button>,
        ),
      )}
    </ButtonGroup>
  );
}
Example #10
Source File: FlowControls.tsx    From SeeQR with MIT License 5 votes vote down vote up
FlowControls = ({
  toggleFullscreen,
  fullscreen,
  thresholds,
  setThresholds,
}: FlowControlsProps) => {
  const [showThresholdsDialog, setShowThresholdsDialog] = useState(false);
  const { fitView, zoomIn, zoomOut } = useReactFlow();
  const tooltipDelay = 1000;

  return (
    <>
      <Toolbar>
        <ButtonGroup orientation="vertical" variant="contained" size="small">
          <Tooltip title="Fit tree to view" enterDelay={tooltipDelay}>
            <SquareBtn onClick={() => fitView({ padding: 0.2 })}>
              <FilterCenterFocusIcon />
            </SquareBtn>
          </Tooltip>
          <Tooltip title="Fullscreen" enterDelay={tooltipDelay}>
            <SquareBtn onClick={toggleFullscreen}>
              {fullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}
            </SquareBtn>
          </Tooltip>
          <Tooltip title="Zoom Out" enterDelay={tooltipDelay}>
            <SquareBtn onClick={() => zoomOut()}>
              <ZoomOutIcon />
            </SquareBtn>
          </Tooltip>
          <Tooltip title="Zoom In" enterDelay={tooltipDelay}>
            <SquareBtn onClick={() => zoomIn()}>
              <ZoomInIcon />
            </SquareBtn>
          </Tooltip>
          <Tooltip title="Set Warning Thresholds" enterDelay={tooltipDelay}>
            <SquareBtn onClick={() => setShowThresholdsDialog(true)}>
              <ErrorOutlineIcon />
            </SquareBtn>
          </Tooltip>
        </ButtonGroup>
      </Toolbar>
      <ThresholdsDialog
        open={showThresholdsDialog}
        handleClose={() => setShowThresholdsDialog(false)}
        thresholds={thresholds}
        setThresholds={setThresholds}
      />
    </>
  );
}
Example #11
Source File: FooterBar.tsx    From shadowsocks-electron with GNU General Public License v3.0 5 votes vote down vote up
FooterBar: React.FC<StatusBarProps> =  (props) => {
  const styles = useStyles();
  const { t } = useTranslation();
  const dispatch = useDispatch();
  const settings = useTypedSelector(state => state.settings);
  const { mode, setDialogOpen } = props;

  const handleModeChange = ((value: string) => {
    if (value !== mode) {
      if (
        platform === "win32" &&
        value !== 'Manual' &&
        !settings.httpProxy.enable
      ) {
        MessageChannel.invoke('main', 'service:desktop', {
          action: 'openNotification',
          params: {
            title: t('warning'),
            body: t('use_pac_and_global_mode_to_turn_on_the_http_proxy_in_the_settings')
          }
        });
      }
      dispatch({
        type: SET_SETTING,
        key: "mode",
        value: value as Mode
      });
    }
  });

  const handleDialogOpen = () => {
    setDialogOpen(true);
  };

  return (
    <>
      <div className={styles.fabPlaceholder} />
      <div className={styles.fabs}>
        <Fab size="small" color="secondary" className={styles.noShadow} variant="circular" onClick={handleDialogOpen}>
          <AddIcon />
        </Fab>
        <span>

          <ButtonGroup size="small" aria-label="small outlined button group">
            {
              menuItems.map(value => (
                <Button
                  key={value}
                  variant="text"
                  className={mode === value ? styles.button : undefined}
                  onClick={() => handleModeChange(value)}
                >
                  {t(value.toLocaleLowerCase())}
                </Button>
              ))
            }
          </ButtonGroup>
        </span>
      </div>
    </>
  );
}
Example #12
Source File: ProjectDetailsPage.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
DetailsPage = () => {
  const api = useApi(gcpApiRef);
  const classes = useStyles();

  const [{ status, result: details, error }, { execute }] = useAsync(async () =>
    api.getProject(decodeURIComponent(location.search.split('projectId=')[1])),
  );

  useMountEffect(execute);

  if (status === 'loading') {
    return <LinearProgress />;
  } else if (error) {
    return (
      <WarningPanel title="Failed to load project">
        {error.toString()}
      </WarningPanel>
    );
  }

  const cloud_home_url = 'https://console.cloud.google.com';

  return (
    <Table component={Paper} className={classes.table}>
      <Table>
        <TableBody>
          <TableRow>
            <TableCell>
              <Typography noWrap>Name</Typography>
            </TableCell>
            <TableCell>{details?.name}</TableCell>
          </TableRow>
          <TableRow>
            <TableCell>
              <Typography noWrap>Project Number</Typography>
            </TableCell>
            <TableCell>{details?.projectNumber}</TableCell>
          </TableRow>
          <TableRow>
            <TableCell>
              <Typography noWrap>Project ID</Typography>
            </TableCell>
            <TableCell>{details?.projectId}</TableCell>
          </TableRow>
          <TableRow>
            <TableCell>
              <Typography noWrap>State</Typography>
            </TableCell>
            <TableCell>{details?.lifecycleState}</TableCell>
          </TableRow>
          <TableRow>
            <TableCell>
              <Typography noWrap>Creation Time</Typography>
            </TableCell>
            <TableCell>{details?.createTime}</TableCell>
          </TableRow>
          <TableRow>
            <TableCell>
              <Typography noWrap>Links</Typography>
            </TableCell>
            <TableCell>
              <ButtonGroup
                variant="text"
                color="primary"
                aria-label="text primary button group"
              >
                {details?.name && (
                  <Button>
                    <a
                      href={`${cloud_home_url}/home/dashboard?project=${details.name}&supportedpurview=project`}
                      target="_blank"
                      rel="noreferrer noopener"
                    >
                      GCP
                    </a>
                  </Button>
                )}
                {details?.name && (
                  <Button>
                    <a
                      href={`${cloud_home_url}/logs/query?project=${details.name}&supportedpurview=project`}
                      target="_blank"
                      rel="noreferrer noopener"
                    >
                      Logs
                    </a>
                  </Button>
                )}
              </ButtonGroup>
            </TableCell>
          </TableRow>
        </TableBody>
      </Table>
    </Table>
  );
}
Example #13
Source File: AdrHeader.tsx    From log4brains with Apache License 2.0 4 votes vote down vote up
export function AdrHeader({
  className,
  adr,
  locallyEditable = false
}: AdrHeaderProps) {
  const classes = useStyles();

  const [linkCopiedSnackIsOpened, linkCopiedSnackSetOpened] = React.useState(
    false
  );
  const linkCopiedSnackClose = () => {
    linkCopiedSnackSetOpened(false);
  };

  const decidersIcon =
    adr.deciders.length > 1 ? (
      <PeopleIcon className={classes.icon} fontSize="inherit" />
    ) : (
      <PersonIcon className={classes.icon} fontSize="inherit" />
    );

  return (
    <>
      <div className={clsx(className, classes.root)}>
        <div>
          <div className={classes.inlineInfo}>
            {adr.package ? (
              <Typography variant="body2" title="Package">
                <CropFreeIcon className={classes.icon} fontSize="inherit" />{" "}
                {adr.package}
              </Typography>
            ) : null}

            <Typography
              variant="body2"
              title={adr.publicationDate ? "Publication date" : "Creation date"}
            >
              <EventIcon className={classes.icon} fontSize="inherit" />{" "}
              {moment(adr.publicationDate || adr.creationDate).format("ll")}
            </Typography>

            <div title="Status">
              <AdrStatusChip status={adr.status} />
            </div>
          </div>

          <Typography
            variant="body2"
            title={
              adr.deciders.length > 0
                ? `Decider${adr.deciders.length > 1 ? "s" : ""}`
                : "Author"
            }
          >
            {decidersIcon}{" "}
            {adr.deciders.length > 0
              ? adr.deciders.join(", ")
              : adr.lastEditAuthor}
          </Typography>

          {adr.tags.length > 0 ? (
            <Typography variant="body2" title="Tags">
              <LabelIcon className={classes.icon} fontSize="inherit" />{" "}
              <span className={classes.tags}>
                {adr.tags.map((tag) => `#${tag}`).join(" ")}
              </span>
            </Typography>
          ) : null}
        </div>
        <div>
          <div>
            <ButtonGroup size="medium">
              <Tooltip title="Copy link">
                <Button
                  onClick={() => {
                    copyTextToClipboard(
                      window.location.href.replace(window.location.hash, "")
                    );
                    linkCopiedSnackSetOpened(true);
                  }}
                >
                  <SvgIcon>
                    <LinkRIcon />
                  </SvgIcon>
                </Button>
              </Tooltip>

              {adr.repository ? (
                <Tooltip
                  title={`View/edit on ${
                    adr.repository.provider === "generic"
                      ? "Git"
                      : capitalize(adr.repository.provider)
                  }`}
                >
                  <Button
                    href={adr.repository.viewUrl}
                    target="_blank"
                    rel="noopener"
                  >
                    {getRepositoryIcon(adr.repository.provider)}
                  </Button>
                </Tooltip>
              ) : null}

              {locallyEditable ? (
                <Tooltip title="Edit locally">
                  <Button
                    color="secondary"
                    onClick={() => editLocally(adr.slug)}
                  >
                    <EditIcon />
                  </Button>
                </Tooltip>
              ) : null}
            </ButtonGroup>
          </div>
        </div>
      </div>
      <Snackbar
        anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
        open={linkCopiedSnackIsOpened}
        onClose={linkCopiedSnackClose}
        autoHideDuration={6000}
        message="Link copied to clipboard"
        action={
          <IconButton
            size="small"
            aria-label="close"
            color="inherit"
            onClick={linkCopiedSnackClose}
            title="Close"
          >
            <CloseIcon fontSize="small" />
          </IconButton>
        }
      />
    </>
  );
}
Example #14
Source File: index.tsx    From back-home-safe with GNU General Public License v3.0 4 votes vote down vote up
QRGenerator = () => {
  const { t } = useTranslation("qr_generator");
  const imgRef = useRef<HTMLImageElement>(null);
  const fileFieldRef = React.useRef<HTMLInputElement>(null);

  const [showPreview, setShowPreview] = useState(false);
  const [qrCode, setQrCode] = useState<QrCodeWithLogo | null>(null);
  const [state, setState] = useSetState<EnhancedEncodeParam>({
    typeEn: "Stores/Shopping Malls",
    typeZh: "商店/商場",
    nameEn: "CityWalk",
    nameZh: "荃新天地",
    type: "IMPORT",
    venueCode: "0",
    venueID: "WHBvLDSa",
    addressEn: "1 & 18 Yeung Uk Rd, Tsuen Wan, Hong Kong",
    addressZh: "荃灣楊屋道1號",
    customImg: null,
  });

  const isVenueCodeValid = state.venueCode.length === 1;
  const isVenueIdValid = state.venueID.length === 8;

  const isValidData = isVenueCodeValid && isVenueIdValid;

  useEffect(() => {
    if (!imgRef.current || !isValidData) return;
    const encodedString = qrEncode(state);

    const qrCode = new QrCodeWithLogo({
      image: imgRef.current,
      content: encodedString,
      width: 380,
      logo: {
        src: state.customImg || baseIcon,
        logoRadius: 8,
        borderSize: 0,
      },
    });

    qrCode.toImage();
    setQrCode(qrCode);
  }, [state, isValidData]);

  const handleFileSelected = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files || []);
    const img = head(files);
    if (!img) {
      setState({ customImg: null });
    } else {
      const reader = new FileReader();
      reader.readAsDataURL(img);
      reader.onload = () => setState({ customImg: String(reader.result) });
    }
  };

  const handleDownload = () => {
    if (!qrCode) return;
    qrCode.downloadImage("QR Code");
  };

  return (
    <PageWrapper>
      <Header backPath="/" name={t("name")} />
      <ContentWrapper id="scroll">
        <StyledForm>
          <StyledTextField
            label={t("form.typeZh")}
            value={state.typeZh}
            onChange={(e) => {
              setState({ typeZh: e.target.value });
            }}
          />
          <StyledTextField
            label={t("form.typeEn")}
            value={state.typeEn}
            onChange={(e) => {
              setState({ typeEn: e.target.value });
            }}
          />
          <StyledTextField
            label={t("form.nameZh")}
            value={state.nameZh}
            onChange={(e) => {
              setState({ nameZh: e.target.value });
            }}
          />
          <StyledTextField
            label={t("form.nameEn")}
            value={state.nameEn}
            onChange={(e) => {
              setState({ nameEn: e.target.value });
            }}
          />
          <StyledTextField
            label={t("form.addressZh")}
            value={state.addressZh}
            onChange={(e) => {
              setState({ addressZh: e.target.value });
            }}
          />
          <StyledTextField
            label={t("form.addressEn")}
            value={state.addressEn}
            onChange={(e) => {
              setState({ addressEn: e.target.value });
            }}
          />
          <StyledTextField
            label={t("form.type")}
            value={state.type}
            onChange={(e) => {
              setState({ type: e.target.value });
            }}
          />
          <StyledTextField
            label={t("form.venue_code")}
            value={state.venueCode}
            onChange={(e) => {
              setState({ venueCode: e.target.value });
            }}
            error={!isVenueCodeValid}
            inputProps={{
              maxLength: 1,
            }}
          />
          <StyledTextField
            label={t("form.venue_id")}
            value={state.venueID}
            onChange={(e) => {
              setState({ venueID: e.target.value });
            }}
            error={!isVenueIdValid}
            inputProps={{
              maxLength: 8,
            }}
          />
          <StyledInputWrapper>
            <div>{t("form.custom_icon")}</div>
            <StyledFileInput
              type="file"
              name="avatar"
              accept="image/png, image/jpeg"
              ref={fileFieldRef}
              onChange={handleFileSelected}
            />
          </StyledInputWrapper>
        </StyledForm>
        <Divider />
        <Actions>
          <ButtonGroup aria-label="outlined primary button group">
            <Button
              variant="contained"
              size="small"
              startIcon={<SaveIcon />}
              onClick={handleDownload}
              disabled={!isValidData}
            >
              {t("global:button.save")}
            </Button>
            <Button
              variant="contained"
              size="small"
              startIcon={<SaveIcon />}
              onClick={() => {
                setShowPreview(true);
              }}
              disabled={!isValidData}
            >
              {t("global:button.preview")}
            </Button>
          </ButtonGroup>
        </Actions>
        <StyledQrCode ref={imgRef} alt="qrCode" />
      </ContentWrapper>
      {showPreview && (
        <QRPreview
          data={state}
          onLeave={() => {
            setShowPreview(false);
          }}
        />
      )}
    </PageWrapper>
  );
}
Example #15
Source File: EditorPanel.tsx    From vscode-crossnote with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function EditorPanel(props: Props) {
  const classes = useStyles(props);
  const { t } = useTranslation();
  const [textAreaElement, setTextAreaElement] = useState<HTMLTextAreaElement>(
    null
  );
  const [previewElement, setPreviewElement] = useState<HTMLElement>(null);
  const [editor, setEditor] = useState<CodeMirrorEditor>(null);
  const [cursorPosition, setCursorPosition] = useState<CursorPosition>({
    line: 0,
    ch: 0,
  });
  const [note, setNote] = useState<Note>(null);
  const [editorMode, setEditorMode] = useState<EditorMode>(EditorMode.Preview);
  const [deleteDialogOpen, setDeleteDialogOpen] = useState<boolean>(false);
  const [filePathDialogOpen, setFilePathDialogOpen] = useState<boolean>(false);
  const [toggleEncryptionDialogOpen, setToggleEncryptionDialogOpen] = useState<
    boolean
  >(false);
  const [toggleEncryptionPassword, setToggleEncryptionPassword] = useState<
    string
  >("");
  const [decryptionDialogOpen, setDecryptionDialogOpen] = useState<boolean>(
    false
  );
  const [decryptionPassword, setDecryptionPassword] = useState<string>("");
  const [isDecrypted, setIsDecrypted] = useState<boolean>(false);
  const [tagsMenuAnchorEl, setTagsMenuAnchorEl] = useState<HTMLElement>(null);
  const [tagNames, setTagNames] = useState<string[]>([]);
  const [editImageElement, setEditImageElement] = useState<HTMLImageElement>(
    null
  );
  const [editImageTextMarker, setEditImageTextMarker] = useState<TextMarker>(
    null
  );
  const [editImageDialogOpen, setEditImageDialogOpen] = useState<boolean>(
    false
  );
  const [previewIsPresentation, setPreviewIsPresentation] = useState<boolean>(
    false
  );
  const [notebookTagNode, setNotebookTagNode] = useState<TagNode>(null);
  const [forceUpdate, setForceUpdate] = useState<number>(Date.now());

  const updateNote = useCallback(
    (note: Note, markdown: string, password: string = "") => {
      vscode.postMessage({
        action: MessageAction.UpdateNote,
        data: {
          note,
          markdown,
          password,
        },
      });
    },
    []
  );

  const closeFilePathDialog = useCallback(() => {
    if (!note) {
      return;
    }
    setFilePathDialogOpen(false);
  }, [note]);

  const closeEncryptionDialog = useCallback(() => {
    setToggleEncryptionPassword("");
    setToggleEncryptionDialogOpen(false);
  }, []);

  const closeDecryptionDialog = useCallback(() => {
    setDecryptionPassword("");
    setDecryptionDialogOpen(false);
  }, []);

  const togglePin = useCallback(() => {
    if (note && editor && isDecrypted) {
      note.config.pinned = !note.config.pinned;
      if (!note.config.pinned) {
        delete note.config.pinned;
      }
      updateNote(
        note,
        editor.getValue(),
        note.config.encryption ? decryptionPassword : ""
      );
      setForceUpdate(Date.now());
    }
  }, [note, editor, decryptionPassword, isDecrypted]);

  const addTag = useCallback(
    (tagName: string) => {
      let tag = tagName.trim() || "";
      if (!note || !tag.length || !editor || !isDecrypted) {
        return;
      }
      tag = tag
        .replace(/\s+/g, " ")
        .replace(TagStopRegExp, "")
        .split("/")
        .map((t) => t.trim())
        .filter((x) => x.length > 0)
        .join("/");
      if (!tag.length) {
        return;
      }
      setTagNames((tagNames) => {
        const newTagNames =
          tagNames.indexOf(tag) >= 0 ? [...tagNames] : [tag, ...tagNames];
        note.config.tags = newTagNames.sort((x, y) => x.localeCompare(y));
        updateNote(
          note,
          editor.getValue(),
          note.config.encryption ? decryptionPassword : ""
        );
        // crossnoteContainer.updateNotebookTagNode();
        return newTagNames;
      });
    },
    [note, editor, decryptionPassword, isDecrypted]
  );

  const deleteTag = useCallback(
    (tagName: string) => {
      if (note && editor && isDecrypted) {
        setTagNames((tagNames) => {
          const newTagNames = tagNames.filter((t) => t !== tagName);
          note.config.tags = newTagNames.sort((x, y) => x.localeCompare(y));
          updateNote(
            note,
            editor.getValue(),
            note.config.encryption ? decryptionPassword : ""
          );
          // crossnoteContainer.updateNotebookTagNode();
          return newTagNames;
        });
      }
    },
    [note, editor, decryptionPassword, isDecrypted]
  );

  const toggleEncryption = useCallback(() => {
    if (!note || !editor) {
      return;
    }
    const markdown = editor.getValue();
    if (note.config.encryption) {
      // Disable encryption
      // Check if the password is correct
      try {
        const bytes = CryptoJS.AES.decrypt(
          note.markdown.trim(),
          toggleEncryptionPassword
        );
        const json = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
        // Disable encryption
        note.config.encryption = null;
        delete note.config.encryption;
        updateNote(note, json.markdown, "");
        setDecryptionPassword("");
        setIsDecrypted(true);
        closeEncryptionDialog();
        // editor.setValue(json.markdown);
        editor.setOption("readOnly", false);
      } catch (error) {
        new Noty({
          type: "error",
          text: t("error/failed-to-disable-encryption"),
          layout: "topRight",
          theme: "relax",
          timeout: 5000,
        }).show();
      }
    } else {
      // Enable encryption
      note.config.encryption = {
        title: getHeaderFromMarkdown(markdown),
      };
      updateNote(note, editor.getValue(), toggleEncryptionPassword);
      setDecryptionPassword(toggleEncryptionPassword);
      setIsDecrypted(true);
      closeEncryptionDialog();
    }
  }, [note, editor, closeEncryptionDialog, toggleEncryptionPassword]);

  const decryptNote = useCallback(() => {
    if (!note || !editor) {
      return;
    }

    // Decrypt
    try {
      const bytes = CryptoJS.AES.decrypt(
        note.markdown.trim(),
        decryptionPassword
      );
      const json = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
      editor.setOption("readOnly", false);
      editor.setValue(json.markdown);
      setIsDecrypted(true);
      setDecryptionDialogOpen(false); // Don't clear decryptionPassword

      if (json.markdown.length === 0) {
        setEditorMode(EditorMode.VickyMD);
      } else {
        setEditorMode(EditorMode.Preview);
      }
    } catch (error) {
      new Noty({
        type: "error",
        text: t("error/decryption-failed"),
        layout: "topRight",
        theme: "relax",
        timeout: 5000,
      }).show();
      setIsDecrypted(false);
    }
  }, [note, editor, decryptionPassword, closeDecryptionDialog]);

  const duplicateNote = useCallback(() => {
    if (!note) {
      return;
    }
    const message: Message = {
      action: MessageAction.DuplicateNote,
      data: note,
    };
    vscode.postMessage(message);
  }, [note]);

  const postprocessPreview = useCallback(
    (previewElement: HTMLElement) => {
      previewPostprocessPreview(previewElement, note, (flag) => {
        setPreviewIsPresentation(flag);
      });
    },
    [note]
  );
  useEffect(() => {
    const message: Message = {
      action: MessageAction.InitializedEditorPanelWebview,
      data: {},
    };
    vscode.postMessage(message);

    return () => {
      setEditor(null);
      setTextAreaElement(null);
      setPreviewElement(null);
    };
  }, []);

  useEffect(() => {
    const onMessage = (event) => {
      const message: Message = event.data;
      switch (message.action) {
        case MessageAction.SendNote:
          setNote(message.data);
          break;
        case MessageAction.UpdatedNote:
          setNote(message.data);
          break;
        case MessageAction.SendNotebookTagNode:
          setNotebookTagNode(message.data);
        default:
          break;
      }
    };
    window.addEventListener("message", onMessage);
    return () => {
      window.removeEventListener("message", onMessage);
    };
  }, []);

  useEffect(() => {
    if (textAreaElement && !editor) {
      // console.log("textarea element mounted");
      const editor: CodeMirrorEditor = VickyMD.fromTextArea(textAreaElement, {
        mode: {
          name: "hypermd",
          hashtag: true,
        },
        hmdFold: HMDFold,
        keyMap: crossnoteSettings.keyMap,
        showCursorWhenSelecting: true,
        inputStyle: "contenteditable",
        hmdClick: (info: any, cm: CodeMirrorEditor) => {
          let { text, url } = info;
          if (info.type === "link" || info.type === "url") {
            const footnoteRef = text.match(/\[[^[\]]+\](?:\[\])?$/); // bare link, footref or [foot][] . assume no escaping char inside
            if (!footnoteRef && (info.ctrlKey || info.altKey) && url) {
              // just open URL
              openURL(url, note);
              return false; // Prevent default click event
            }
          }
        },
      });
      editor.setOption("lineNumbers", false);
      editor.setOption("foldGutter", false);
      editor.setValue("");
      editor.on("cursorActivity", (instance) => {
        const cursor = instance.getCursor();
        if (cursor) {
          setCursorPosition({
            line: cursor.line,
            ch: cursor.ch,
          });
        }
      });
      setEditor(editor);
      initMathPreview(editor);
    }
  }, [textAreaElement, editor]);

  useEffect(() => {
    if (editor) {
      setTheme({
        editor,
        themeName: selectedTheme.name,
        baseUri:
          extensionPath +
          (extensionPath.endsWith("/") ? "" : "/") +
          "node_modules/vickymd/theme/",
      });
    }
  }, [editor]);

  // Initialize cursor color
  useEffect(() => {
    const styleID = "codemirror-cursor-style";
    let style = document.getElementById(styleID);
    if (!style) {
      style = document.createElement("style");
      style.id = styleID;
      document.body.appendChild(style);
    }
    style.innerText = `
    .CodeMirror-cursor.CodeMirror-cursor {
      border-left: 2px solid rgba(74, 144, 226, 1);
    }    
    `;
  }, []);

  /*
  useEffect(() => {
    if (note && editor) {
      editor.setValue(note.markdown);
      editor.refresh();
    }
  }, [note, editor]);
  */

  // Decryption
  useEffect(() => {
    if (!editor || !note) {
      return;
    }
    if (note.config.encryption) {
      setIsDecrypted(false);
      setDecryptionPassword("");
      editor.setOption("readOnly", true);
      editor.setValue(`? ${t("general/encrypted")}`);
      setDecryptionDialogOpen(true);
    } else {
      setIsDecrypted(true);
      setDecryptionPassword("");
      editor.setOption("readOnly", false);
      editor.setValue(note.markdown);
      setDecryptionDialogOpen(false);

      if (note.markdown.length === 0) {
        setEditorMode(EditorMode.VickyMD);
      } else {
        setEditorMode(EditorMode.Preview);
      }
    }
  }, [editor, note]);

  useEffect(() => {
    if (!editor || !note) {
      return;
    }
    setTagNames(note.config.tags || []);
    const changesHandler = () => {
      if (editor.getOption("readOnly") || !isDecrypted) {
        // This line is necessary for decryption...
        return;
      }
      const markdown = editor.getValue();

      if (!note.config.encryption && markdown === note.markdown) {
        return;
      }
      updateNote(
        note,
        markdown,
        note.config.encryption ? decryptionPassword : ""
      );
      setTagNames(note.config.tags || []); // After resolve conflicts
    };
    editor.on("changes", changesHandler);

    const keyupHandler = () => {
      if (!isDecrypted && note.config.encryption) {
        setDecryptionDialogOpen(true);
      }
    };
    editor.on("keyup", keyupHandler);

    const imageClickedHandler = (args: any) => {
      const marker: TextMarker = args.marker;
      const imageElement: HTMLImageElement = args.element;
      imageElement.setAttribute(
        "data-marker-position",
        JSON.stringify(marker.find())
      );
      setEditImageElement(imageElement);
      setEditImageTextMarker(marker);
      setEditImageDialogOpen(true);
    };
    editor.on("imageClicked", imageClickedHandler);

    const loadImage = (args: any) => {
      const element = args.element;
      const imageSrc = element.getAttribute("data-src");
      element.setAttribute("src", resolveNoteImageSrc(note, imageSrc));
    };
    editor.on("imageReadyToLoad", loadImage);

    return () => {
      editor.off("changes", changesHandler);
      editor.off("keyup", keyupHandler);
      editor.off("imageClicked", imageClickedHandler);
      editor.off("imageReadyToLoad", loadImage);
    };
  }, [editor, note, decryptionPassword, isDecrypted]);

  useEffect(() => {
    if (!editor || !note) {
      return;
    }
    if (editorMode === EditorMode.VickyMD) {
      VickyMD.switchToHyperMD(editor);
      // @ts-ignore
      editor.setOption("hmdFold", HMDFold);
      editor.getWrapperElement().style.display = "block";
      editor.refresh();
    } else if (editorMode === EditorMode.SourceCode) {
      VickyMD.switchToNormal(editor);
      editor.getWrapperElement().style.display = "block";
      editor.refresh();
    } else if (editorMode === EditorMode.Preview) {
      editor.getWrapperElement().style.display = "none";
    } else if (editorMode === EditorMode.SplitView) {
      VickyMD.switchToNormal(editor);
      editor.getWrapperElement().style.display = "block";
      editor.refresh();
    }
  }, [editorMode, editor, note, isDecrypted]);

  // Render Preview
  useEffect(() => {
    if (
      (editorMode === EditorMode.Preview ||
        editorMode === EditorMode.SplitView) &&
      editor &&
      note &&
      previewElement
    ) {
      if (isDecrypted) {
        try {
          renderPreview(previewElement, editor.getValue());
          postprocessPreview(previewElement);
          previewElement.scrollTop = 0;
        } catch (error) {
          previewElement.innerText = error;
        }
      } else {
        previewElement.innerHTML = `? ${t("general/encrypted")}`;
        const clickHandler = () => {
          setDecryptionDialogOpen(true);
        };
        previewElement.addEventListener("click", clickHandler);
        return () => {
          previewElement.removeEventListener("click", clickHandler);
        };
      }
    }
  }, [
    editorMode,
    editor,
    previewElement,
    note,
    isDecrypted,
    postprocessPreview,
    t,
  ]);

  // Command
  useEffect(() => {
    if (!editor || !note) {
      return;
    }

    const onChangeHandler = (
      instance: CodeMirrorEditor,
      changeObject: EditorChangeLinkedList
    ) => {
      // Check commands
      if (changeObject.text.length === 1 && changeObject.text[0] === "/") {
        const aheadStr = editor
          .getLine(changeObject.from.line)
          .slice(0, changeObject.from.ch + 1);
        if (!aheadStr.match(/#[^\s]+?\/$/)) {
          // Not `/` inside a tag
          // @ts-ignore
          editor.showHint({
            closeOnUnfocus: false,
            completeSingle: false,
            hint: () => {
              const cursor = editor.getCursor();
              const token = editor.getTokenAt(cursor);
              const line = cursor.line;
              const lineStr = editor.getLine(line);
              const end: number = cursor.ch;
              let start = token.start;
              if (lineStr[start] !== "/") {
                start = start - 1;
              }
              const currentWord: string = lineStr
                .slice(start, end)
                .replace(/^\//, "");

              const render = (
                element: HTMLElement,
                data: CommandHint[],
                cur: CommandHint
              ) => {
                const wrapper = document.createElement("div");
                wrapper.style.padding = "6px 0";
                wrapper.style.display = "flex";
                wrapper.style.flexDirection = "row";
                wrapper.style.alignItems = "flex-start";
                wrapper.style.maxWidth = "100%";
                wrapper.style.minWidth = "200px";

                const leftPanel = document.createElement("div");
                const iconWrapper = document.createElement("div");
                iconWrapper.style.padding = "0 6px";
                iconWrapper.style.marginRight = "6px";
                iconWrapper.style.fontSize = "1rem";

                const iconElement = document.createElement("span");
                iconElement.classList.add("mdi");
                iconElement.classList.add(
                  cur.icon || "mdi-help-circle-outline"
                );
                iconWrapper.appendChild(iconElement);
                leftPanel.appendChild(iconWrapper);

                const rightPanel = document.createElement("div");

                const descriptionElement = document.createElement("p");
                descriptionElement.innerText = cur.description;
                descriptionElement.style.margin = "2px 0";
                descriptionElement.style.padding = "0";

                const commandElement = document.createElement("p");
                commandElement.innerText = cur.command;
                commandElement.style.margin = "0";
                commandElement.style.padding = "0";
                commandElement.style.fontSize = "0.7rem";

                rightPanel.appendChild(descriptionElement);
                rightPanel.appendChild(commandElement);

                wrapper.appendChild(leftPanel);
                wrapper.appendChild(rightPanel);
                element.appendChild(wrapper);
              };

              const commands: CommandHint[] = [
                {
                  text: "# ",
                  command: "/h1",
                  description: t("editor/toolbar/insert-header-1"),
                  icon: "mdi-format-header-1",
                  render,
                },
                {
                  text: "## ",
                  command: "/h2",
                  description: t("editor/toolbar/insert-header-2"),
                  icon: "mdi-format-header-2",
                  render,
                },
                {
                  text: "### ",
                  command: "/h3",
                  description: t("editor/toolbar/insert-header-3"),
                  icon: "mdi-format-header-3",
                  render,
                },
                {
                  text: "#### ",
                  command: "/h4",
                  description: t("editor/toolbar/insert-header-4"),
                  icon: "mdi-format-header-4",
                  render,
                },
                {
                  text: "##### ",
                  command: "/h5",
                  description: t("editor/toolbar/insert-header-5"),
                  icon: "mdi-format-header-5",
                  render,
                },
                {
                  text: "###### ",
                  command: "/h6",
                  description: t("editor/toolbar/insert-header-6"),
                  icon: "mdi-format-header-6",
                  render,
                },
                {
                  text: "> ",
                  command: "/blockquote",
                  description: t("editor/toolbar/insert-blockquote"),
                  icon: "mdi-format-quote-open",
                  render,
                },
                {
                  text: "* ",
                  command: "/ul",
                  description: t("editor/toolbar/insert-unordered-list"),
                  icon: "mdi-format-list-bulleted",
                  render,
                },
                {
                  text: "1. ",
                  command: "/ol",
                  description: t("editor/toolbar/insert-ordered-list"),
                  icon: "mdi-format-list-numbered",
                  render,
                },
                {
                  text: "<!-- @crossnote.image -->\n",
                  command: "/image",
                  description: t("editor/toolbar/insert-image"),
                  icon: "mdi-image",
                  render,
                },
                {
                  text: `|   |   |
  |---|---|
  |   |   |
  `,
                  command: "/table",
                  description: t("editor/toolbar/insert-table"),
                  icon: "mdi-table",
                  render,
                },
                {
                  text:
                    "<!-- @timer " +
                    JSON.stringify({ date: new Date().toString() })
                      .replace(/^{/, "")
                      .replace(/}$/, "") +
                    " -->\n",
                  command: "/timer",
                  description: t("editor/toolbar/insert-clock"),
                  icon: "mdi-timer",
                  render,
                },
                {
                  text: "<!-- @crossnote.audio -->  \n",
                  command: "/audio",
                  description: t("editor/toolbar/insert-audio"),
                  icon: "mdi-music",
                  render,
                },
                /*
                  {
                    text: "<!-- @crossnote.netease_music -->  \n",
                    displayText: `/netease - ${t(
                      "editor/toolbar/netease-music",
                    )}`,
                  },
                  */
                {
                  text: "<!-- @crossnote.video -->  \n",
                  command: "/video",
                  description: t("editor/toolbar/insert-video"),
                  icon: "mdi-video",
                  render,
                },
                {
                  text: "<!-- @crossnote.youtube -->  \n",
                  command: "/youtube",
                  description: t("editor/toolbar/insert-youtube"),
                  icon: "mdi-youtube",
                  render,
                },
                {
                  text: "<!-- @crossnote.bilibili -->  \n",
                  command: "/bilibili",
                  description: t("editor/toolbar/insert-bilibili"),
                  icon: "mdi-television-classic",
                  render,
                },
                {
                  text: "<!-- slide -->  \n",
                  command: "/slide",
                  description: t("editor/toolbar/insert-slide"),
                  icon: "mdi-presentation",
                  render,
                },
                {
                  text: "<!-- @crossnote.ocr -->  \n",
                  command: "/ocr",
                  description: t("editor/toolbar/insert-ocr"),
                  icon: "mdi-ocr",
                  render,
                },
                {
                  text:
                    '<!-- @crossnote.kanban "v":2,"board":{"columns":[]} -->  \n',
                  command: "/kanban",
                  description: `${t("editor/toolbar/insert-kanban")} (beta)`,
                  icon: "mdi-developer-board",
                  render,
                },
                /*
                  {
                    text: "<!-- @crossnote.abc -->  \n",
                    displayText: `/abc - ${t(
                      "editor/toolbar/insert-abc-notation",
                    )}`,
                  },
                  */
                {
                  text: "<!-- @crossnote.github_gist -->  \n",
                  command: "/github_gist",
                  description: t("editor/toolbar/insert-github-gist"),
                  icon: "mdi-github",
                  render,
                },
                /*
                {
                  text: "<!-- @crossnote.comment -->  \n",
                  command: "/crossnote.comment",
                  description: t("editor/toolbar/insert-comment"),
                  icon: "mdi-comment-multiple",
                  render,
                },
                */
              ];
              const filtered = commands.filter(
                (item) =>
                  (item.command + item.description)
                    .toLocaleLowerCase()
                    .indexOf(currentWord.toLowerCase()) >= 0
              );
              return {
                list: filtered.length ? filtered : commands,
                from: { line, ch: start },
                to: { line, ch: end },
              };
            },
          });
        }
      }

      // Check emoji
      if (
        changeObject.text.length === 1 &&
        changeObject.text[0].length > 0 &&
        changeObject.text[0] !== " " &&
        changeObject.text[0] !== ":" &&
        changeObject.from.ch > 0 &&
        editor.getLine(changeObject.from.line)[changeObject.from.ch - 1] === ":"
      ) {
        // @ts-ignore
        editor.showHint({
          closeOnUnfocus: true,
          completeSingle: false,
          hint: () => {
            const cursor = editor.getCursor();
            const token = editor.getTokenAt(cursor);
            const line = cursor.line;
            const lineStr = editor.getLine(line);
            const end: number = cursor.ch;
            let start = token.start;
            let doubleSemiColon = false;
            if (lineStr[start] !== ":") {
              start = start - 1;
            }
            if (start > 0 && lineStr[start - 1] === ":") {
              start = start - 1;
              doubleSemiColon = true;
            }
            const currentWord: string = lineStr
              .slice(start, end)
              .replace(/^:+/, "");

            const commands: { text: string; displayText: string }[] = [];
            for (const def in EmojiDefinitions) {
              const emoji = EmojiDefinitions[def];
              commands.push({
                text: doubleSemiColon ? `:${def}: ` : `${emoji} `,
                displayText: `:${def}: ${emoji}`,
              });
            }
            const filtered = commands.filter(
              (item) =>
                item.displayText
                  .toLocaleLowerCase()
                  .indexOf(currentWord.toLowerCase()) >= 0
            );
            return {
              list: filtered.length ? filtered : commands,
              from: { line, ch: start },
              to: { line, ch: end },
            };
          },
        });
      }

      // Check tag
      if (
        changeObject.text.length === 1 &&
        changeObject.text[0] !== " " &&
        changeObject.from.ch > 0 &&
        editor.getLine(changeObject.from.line)[changeObject.from.ch - 1] === "#"
      ) {
        // @ts-ignore
        editor.showHint({
          closeOnUnfocus: true,
          completeSingle: false,
          hint: () => {
            const cursor = editor.getCursor();
            const token = editor.getTokenAt(cursor);
            const line = cursor.line;
            const lineStr = editor.getLine(line);
            const end: number = cursor.ch;
            let start = token.start;
            if (lineStr[start] !== "#") {
              start = start - 1;
            }
            const currentWord: string = lineStr
              .slice(start, end)
              .replace(TagStopRegExp, "");
            const commands: { text: string; displayText: string }[] = [];
            if (currentWord.trim().length > 0) {
              commands.push({
                text: `#${currentWord} `,
                displayText: `+ #${currentWord}`,
              });
            }
            const helper = (children: TagNode[]) => {
              if (!children || !children.length) {
                return;
              }
              for (let i = 0; i < children.length; i++) {
                const tag = children[i].path;
                commands.push({
                  text: `#${tag} `,
                  displayText: `+ #${tag}`,
                });
                helper(children[i].children);
              }
            };
            helper(notebookTagNode?.children || []);
            const filtered = commands.filter(
              (item) => item.text.toLocaleLowerCase().indexOf(currentWord) >= 0
            );

            return {
              list: filtered.length ? filtered : commands,
              from: { line, ch: start },
              to: { line, ch: end },
            };
          },
        });
      }

      // Timer
      if (
        changeObject.text.length > 0 &&
        changeObject.text[0].startsWith("<!-- @timer ") &&
        changeObject.removed.length > 0 &&
        changeObject.removed[0].startsWith("/")
      ) {
        // Calcuate date time
        const lines = editor.getValue().split("\n");
        const timerTexts: TimerText[] = [];
        for (let i = 0; i < lines.length; i++) {
          const match = lines[i].match(/^`@timer\s.+`/);
          if (match) {
            const text = match[0];
            const dataMatch = text.match(/^`@timer\s+(.+)`/);
            if (!dataMatch || !dataMatch.length) {
              continue;
            }
            const dataStr = dataMatch[1];
            try {
              const data = JSON.parse(`{${dataStr}}`);
              const datetime = data["date"];
              if (datetime) {
                timerTexts.push({
                  text: text,
                  line: i,
                  date: new Date(datetime),
                });
              }
            } catch (error) {
              continue;
            }
          }
        }
        for (let i = 1; i < timerTexts.length; i++) {
          const currentTimerText = timerTexts[i];
          const previousTimerText = timerTexts[i - 1];
          const duration = formatDistance(
            currentTimerText.date,
            previousTimerText.date,
            { includeSeconds: true }
          );
          const newText = `\`@timer ${JSON.stringify({
            date: currentTimerText.date.toString(),
            duration,
          })
            .replace(/^{/, "")
            .replace(/}$/, "")}\``;
          editor.replaceRange(
            newText,
            { line: currentTimerText.line, ch: 0 },
            { line: currentTimerText.line, ch: currentTimerText.text.length }
          );
        }
      }

      // Add Tag
      if (
        changeObject.origin === "complete" &&
        changeObject.removed[0] &&
        changeObject.removed[0].match(/^#[^\s]/) &&
        changeObject.text[0] &&
        changeObject.text[0].match(/^#[^\s]/)
      ) {
        addTag(changeObject.text[0].replace(/^#/, ""));
      }
    };
    editor.on("change", onChangeHandler);

    const onCursorActivityHandler = (instance: CodeMirrorEditor) => {
      // console.log("cursorActivity", editor.getCursor());
      // console.log("selection: ", editor.getSelection());
      return;
    };
    editor.on("cursorActivity", onCursorActivityHandler);

    return () => {
      editor.off("change", onChangeHandler);
      editor.off("cursorActivity", onCursorActivityHandler);
    };
  }, [editor, note, notebookTagNode, addTag /*t*/]);

  // Split view
  useEffect(() => {
    if (
      !editor ||
      !note ||
      !previewElement ||
      editorMode !== EditorMode.SplitView
    ) {
      return;
    }
    let onChangeCallback: any = null;
    let onCursorActivityCallback: any = null;
    let onScrollCallback: any = null;
    let onWindowResizeCallback: any = null;
    let scrollMap: any = null;
    let scrollTimeout: NodeJS.Timeout = null;
    let previewScrollDelay = Date.now();
    let editorScrollDelay = Date.now();
    let currentLine: number = -1;
    let editorClientWidth = editor.getScrollInfo().clientWidth;
    let editorClientHeight = editor.getScrollInfo().clientHeight;
    let lastCursorPosition: Position = null;

    const totalLineCount = editor.lineCount();
    const buildScrollMap = () => {
      if (!totalLineCount) {
        return null;
      }
      const scrollMap = [];
      const nonEmptyList = [];

      for (let i = 0; i < totalLineCount; i++) {
        scrollMap.push(-1);
      }

      nonEmptyList.push(0);
      scrollMap[0] = 0;

      // write down the offsetTop of element that has 'data-line' property to scrollMap
      const lineElements = previewElement.getElementsByClassName("sync-line");

      for (let i = 0; i < lineElements.length; i++) {
        let el = lineElements[i] as HTMLElement;
        let t: any = el.getAttribute("data-line");
        if (!t) {
          continue;
        }

        t = parseInt(t, 10);
        if (!t) {
          continue;
        }

        // this is for ignoring footnote scroll match
        if (t < nonEmptyList[nonEmptyList.length - 1]) {
          el.removeAttribute("data-line");
        } else {
          nonEmptyList.push(t);

          let offsetTop = 0;
          while (el && el !== previewElement) {
            offsetTop += el.offsetTop;
            el = el.offsetParent as HTMLElement;
          }

          scrollMap[t] = Math.round(offsetTop);
        }
      }

      nonEmptyList.push(totalLineCount);
      scrollMap.push(previewElement.scrollHeight);

      let pos = 0;
      for (let i = 0; i < totalLineCount; i++) {
        if (scrollMap[i] !== -1) {
          pos++;
          continue;
        }

        const a = nonEmptyList[pos - 1];
        const b = nonEmptyList[pos];
        scrollMap[i] = Math.round(
          (scrollMap[b] * (i - a) + scrollMap[a] * (b - i)) / (b - a)
        );
      }

      return scrollMap; // scrollMap's length == screenLineCount (vscode can't get screenLineCount... sad)
    };
    const scrollToPos = (scrollTop: number) => {
      if (scrollTimeout) {
        clearTimeout(scrollTimeout);
        scrollTimeout = null;
      }

      if (scrollTop < 0) {
        return;
      }

      const delay = 10;

      const helper = (duration = 0) => {
        scrollTimeout = setTimeout(() => {
          if (duration <= 0) {
            previewScrollDelay = Date.now() + 500;
            previewElement.scrollTop = scrollTop;
            return;
          }

          const difference = scrollTop - previewElement.scrollTop;

          const perTick = (difference / duration) * delay;

          // disable preview onscroll
          previewScrollDelay = Date.now() + 500;

          previewElement.scrollTop += perTick;
          if (previewElement.scrollTop === scrollTop) {
            return;
          }

          helper(duration - delay);
        }, delay);
      };

      const scrollDuration = 120;
      helper(scrollDuration);
    };
    const scrollToRevealSourceLine = (line: number, topRatio = 0.372) => {
      if (line === currentLine) {
        return;
      } else {
        currentLine = line;
      }

      // disable preview onscroll
      previewScrollDelay = Date.now() + 500;

      /*
        if (presentationMode) {
          scrollSyncToSlide(line);
        } else {
          scrollSyncToLine(line, topRatio);
        }
        */
      scrollSyncToLine(line, topRatio);
    };
    const scrollSyncToLine = (line: number, topRatio: number = 0.372) => {
      if (!scrollMap) {
        scrollMap = buildScrollMap();
      }
      if (!scrollMap || line >= scrollMap.length) {
        return;
      }

      if (line + 1 === totalLineCount) {
        // last line
        scrollToPos(previewElement.scrollHeight);
      } else {
        /**
         * Since I am not able to access the viewport of the editor
         * I used `golden section` (0.372) here for scrollTop.
         */
        scrollToPos(
          Math.max(scrollMap[line] - previewElement.offsetHeight * topRatio, 0)
        );
      }
    };
    const revealEditorLine = (line: number) => {
      const scrollInfo = editor.getScrollInfo();
      editor.scrollIntoView({ line: line, ch: 0 }, scrollInfo.clientHeight / 2);
      editorScrollDelay = Date.now() + 500;
      if (
        scrollInfo.clientHeight !== editorClientHeight ||
        scrollInfo.clientWidth !== editorClientWidth
      ) {
        editorClientHeight = scrollInfo.clientHeight;
        editorClientWidth = scrollInfo.clientWidth;
        scrollMap = null;
      }
    };
    const previewSyncSource = () => {
      let scrollToLine;

      if (previewElement.scrollTop === 0) {
        // editorScrollDelay = Date.now() + 100
        scrollToLine = 0;

        revealEditorLine(scrollToLine);
        return;
      }

      const top = previewElement.scrollTop + previewElement.offsetHeight / 2;

      // try to find corresponding screen buffer row
      if (!scrollMap) {
        scrollMap = buildScrollMap();
      }

      let i = 0;
      let j = scrollMap.length - 1;
      let count = 0;
      let screenRow = -1; // the screenRow is the bufferRow in vscode.
      let mid;

      while (count < 20) {
        if (Math.abs(top - scrollMap[i]) < 20) {
          screenRow = i;
          break;
        } else if (Math.abs(top - scrollMap[j]) < 20) {
          screenRow = j;
          break;
        } else {
          mid = Math.floor((i + j) / 2);
          if (top > scrollMap[mid]) {
            i = mid;
          } else {
            j = mid;
          }
        }
        count++;
      }

      if (screenRow === -1) {
        screenRow = mid;
      }

      scrollToLine = screenRow;
      revealEditorLine(scrollToLine);
      // @scrollToPos(screenRow * @editor.getLineHeightInPixels() - @previewElement.offsetHeight / 2, @editor.getElement())
      // # @editor.getElement().setScrollTop

      // track currnet time to disable onDidChangeScrollTop
      // editorScrollDelay = Date.now() + 100
    };

    onChangeCallback = () => {
      try {
        const markdown = editor.getValue();
        setTimeout(() => {
          const newMarkdown = editor.getValue();
          if (markdown === newMarkdown) {
            renderPreview(previewElement, newMarkdown);
            postprocessPreview(previewElement);
          }
        }, 300);
      } catch (error) {
        previewElement.innerText = error;
      }
      // Reset scrollMap
      scrollMap = null;
    };
    onCursorActivityCallback = () => {
      const cursor = editor.getCursor();
      const scrollInfo = editor.getScrollInfo();
      const firstLine = editor.lineAtHeight(scrollInfo.top, "local");
      const lastLine = editor.lineAtHeight(
        scrollInfo.top + scrollInfo.clientHeight,
        "local"
      );
      if (!lastCursorPosition || lastCursorPosition.line !== cursor.line) {
        scrollSyncToLine(
          cursor.line,
          (cursor.line - firstLine) / (lastLine - firstLine)
        );
      }
      lastCursorPosition = cursor;
    };
    onScrollCallback = () => {
      // console.log("scroll editor: ", editor.getScrollInfo());
      // console.log("viewport: ", editor.getViewport());
      const scrollInfo = editor.getScrollInfo();
      if (
        scrollInfo.clientHeight !== editorClientHeight ||
        scrollInfo.clientWidth !== editorClientWidth
      ) {
        editorClientHeight = scrollInfo.clientHeight;
        editorClientWidth = scrollInfo.clientWidth;
        scrollMap = null;
      }

      if (Date.now() < editorScrollDelay) {
        return;
      }
      const topLine = editor.lineAtHeight(scrollInfo.top, "local");
      const bottomLine = editor.lineAtHeight(
        scrollInfo.top + scrollInfo.clientHeight,
        "local"
      );
      let midLine;
      if (topLine === 0) {
        midLine = 0;
      } else if (bottomLine === totalLineCount - 1) {
        midLine = bottomLine;
      } else {
        midLine = Math.floor((topLine + bottomLine) / 2);
      }
      scrollSyncToLine(midLine);
    };
    onWindowResizeCallback = () => {
      const scrollInfo = editor.getScrollInfo();
      editorClientHeight = scrollInfo.clientHeight;
      editorClientWidth = scrollInfo.clientWidth;
      scrollMap = null;
    };

    editor.on("changes", onChangeCallback);
    onChangeCallback();

    editor.on("cursorActivity", onCursorActivityCallback);
    editor.on("scroll", onScrollCallback);
    previewElement.onscroll = () => {
      // console.log("scroll preview: ", previewElement.scrollTop);
      if (Date.now() < previewScrollDelay) {
        return;
      }
      previewSyncSource();
    };
    window.addEventListener("resize", onWindowResizeCallback);

    return () => {
      if (onChangeCallback) {
        editor.off("changes", onChangeCallback);
      }
      if (onCursorActivityCallback) {
        editor.off("cursorActivity", onCursorActivityCallback);
      }
      if (onScrollCallback) {
        editor.off("scroll", onScrollCallback);
      }
      if (onWindowResizeCallback) {
        window.removeEventListener("resize", onWindowResizeCallback);
      }
    };
  }, [editor, note, previewElement, editorMode, postprocessPreview]);

  if (!note) {
    return (
      <Box className={clsx(classes.editorPanel, "editor-panel")}>
        <Box
          style={{
            margin: "0 auto",
            top: "50%",
            position: "relative",
          }}
        >
          <Typography>{`? ${t("general/no-notes-found")}`}</Typography>
        </Box>
      </Box>
    );
  }

  return (
    <Box className={clsx(classes.editorPanel)}>
      <Box className={clsx(classes.topPanel)}>
        <Box className={clsx(classes.row)}>
          <ButtonGroup
            variant={"outlined"}
            color="default"
            aria-label="editor mode"
          >
            <Tooltip title={t("general/vickymd")}>
              <Button
                className={clsx(
                  classes.controlBtn,
                  editorMode === EditorMode.VickyMD &&
                    classes.controlBtnSelected
                )}
                color={
                  editorMode === EditorMode.VickyMD ? "primary" : "default"
                }
                onClick={() => setEditorMode(EditorMode.VickyMD)}
              >
                <Pencil></Pencil>
              </Button>
            </Tooltip>
            <Tooltip title={t("editor/note-control/source-code")}>
              <Button
                className={clsx(
                  classes.controlBtn,
                  editorMode === EditorMode.SourceCode &&
                    classes.controlBtnSelected
                )}
                color={
                  editorMode === EditorMode.SourceCode ? "primary" : "default"
                }
                onClick={() => setEditorMode(EditorMode.SourceCode)}
              >
                <CodeTags></CodeTags>
              </Button>
            </Tooltip>
            <Tooltip title={t("editor/note-control/split-view")}>
              <Button
                className={clsx(
                  classes.controlBtn,
                  editorMode === EditorMode.SplitView &&
                    classes.controlBtnSelected
                )}
                color={
                  editorMode === EditorMode.SplitView ? "primary" : "default"
                }
                onClick={() => setEditorMode(EditorMode.SplitView)}
              >
                <ViewSplitVertical></ViewSplitVertical>
              </Button>
            </Tooltip>
            <Tooltip title={t("editor/note-control/preview")}>
              <Button
                className={clsx(
                  classes.controlBtn,
                  editorMode === EditorMode.Preview &&
                    classes.controlBtnSelected
                )}
                color={
                  editorMode === EditorMode.Preview ? "primary" : "default"
                }
                onClick={() => setEditorMode(EditorMode.Preview)}
              >
                <FilePresentationBox></FilePresentationBox>
              </Button>
            </Tooltip>
          </ButtonGroup>
          <ButtonGroup style={{ marginLeft: "8px" }}>
            {isDecrypted && note && (
              <Tooltip title={t("general/tags")}>
                <Button
                  className={clsx(
                    classes.controlBtn,
                    note.config.tags &&
                      note.config.tags.length > 0 &&
                      classes.controlBtnSelectedSecondary
                  )}
                  onClick={(event) => setTagsMenuAnchorEl(event.currentTarget)}
                >
                  {note.config.tags && note.config.tags.length > 0 ? (
                    <Tag></Tag>
                  ) : (
                    <TagOutline></TagOutline>
                  )}
                </Button>
              </Tooltip>
            )}
            {isDecrypted && note && (
              <Tooltip title={t("general/Pin")}>
                <Button
                  className={clsx(
                    classes.controlBtn,
                    note.config.pinned && classes.controlBtnSelectedSecondary
                  )}
                  onClick={togglePin}
                >
                  {note.config.pinned ? <Pin></Pin> : <PinOutline></PinOutline>}
                </Button>
              </Tooltip>
            )}
            {note && (
              <Tooltip title={t("general/Encryption")}>
                <Button
                  className={clsx(
                    classes.controlBtn,
                    note.config.encryption &&
                      classes.controlBtnSelectedSecondary
                  )}
                  onClick={() => setToggleEncryptionDialogOpen(true)}
                >
                  {note.config.encryption ? (
                    <Lock></Lock>
                  ) : (
                    <LockOpenOutline></LockOpenOutline>
                  )}
                </Button>
              </Tooltip>
            )}
          </ButtonGroup>
          <ButtonGroup style={{ marginLeft: "8px" }}>
            <Tooltip title={t("general/change-file-path")}>
              <Button
                className={clsx(classes.controlBtn)}
                onClick={() => setFilePathDialogOpen(true)}
              >
                <RenameBox></RenameBox>
              </Button>
            </Tooltip>
            <Tooltip title={t("general/Delete")}>
              <Button
                className={clsx(classes.controlBtn)}
                onClick={() => setDeleteDialogOpen(true)}
              >
                <Delete></Delete>
              </Button>
            </Tooltip>
            {note && !note.config.encryption && (
              <Tooltip title={t("general/create-a-copy")}>
                <Button
                  className={clsx(classes.controlBtn)}
                  onClick={duplicateNote}
                >
                  <ContentDuplicate></ContentDuplicate>
                </Button>
              </Tooltip>
            )}
          </ButtonGroup>
          <TagsMenuPopover
            anchorElement={tagsMenuAnchorEl}
            onClose={() => setTagsMenuAnchorEl(null)}
            addTag={addTag}
            deleteTag={deleteTag}
            tagNames={tagNames}
            notebookTagNode={notebookTagNode}
          ></TagsMenuPopover>
        </Box>
      </Box>
      <Box
        className={clsx(
          classes.editorWrapper,
          editorMode === EditorMode.SplitView ? classes.splitView : null
        )}
      >
        <textarea
          className={clsx(classes.editor, "editor-textarea")}
          placeholder={t("editor/placeholder")}
          ref={(element: HTMLTextAreaElement) => {
            setTextAreaElement(element);
          }}
        ></textarea>
        {(editorMode === EditorMode.Preview ||
          editorMode === EditorMode.SplitView) &&
        editor ? (
          <div
            className={clsx(
              classes.preview,
              "preview",
              previewIsPresentation ? classes.presentation : null
            )}
            ref={(element: HTMLElement) => {
              setPreviewElement(element);
            }}
          ></div>
        ) : null}
      </Box>
      <Box className={clsx(classes.bottomPanel, "editor-bottom-panel")}>
        {note && (
          <Box className={clsx(classes.row)}>
            <Breadcrumbs aria-label={"File path"} maxItems={4}>
              {note.filePath.split("/").map((path, offset, arr) => {
                return (
                  <Typography
                    variant={"caption"}
                    style={{ cursor: "pointer" }}
                    color={"textPrimary"}
                    key={`${offset}-${path}`}
                    onClick={() => {
                      if (offset === arr.length - 1) {
                        setFilePathDialogOpen(true);
                      } else {
                        setSelectedSection({
                          type: CrossnoteSectionType.Directory,
                          path: arr.slice(0, offset + 1).join("/"),
                          notebook: {
                            name: "",
                            dir: note.notebookPath,
                          },
                        });
                      }
                    }}
                  >
                    {path}
                  </Typography>
                );
              })}
            </Breadcrumbs>
          </Box>
        )}
        <Box className={clsx(classes.cursorPositionInfo)}>
          <Typography variant={"caption"} color={"textPrimary"}>
            {`Ln ${cursorPosition.line + 1}, Col ${cursorPosition.ch}`}
          </Typography>
        </Box>
      </Box>

      <Card
        id="math-preview"
        className={clsx(classes.floatWin, "float-win", "float-win-hidden")}
      >
        <Box className={clsx(classes.floatWinTitle, "float-win-title")}>
          <IconButton
            className={clsx(classes.floatWinClose, "float-win-close")}
          >
            <Close></Close>
          </IconButton>
          <Typography>{t("general/math-preview")}</Typography>
        </Box>
        <Box
          className={clsx(classes.floatWinContent, "float-win-content")}
          id="math-preview-content"
        ></Box>
      </Card>

      <DeleteDialog
        open={deleteDialogOpen}
        onClose={() => setDeleteDialogOpen(false)}
        note={note}
      ></DeleteDialog>
      <ChangeFilePathDialog
        note={note}
        open={filePathDialogOpen}
        onClose={closeFilePathDialog}
      ></ChangeFilePathDialog>
      <EditImageDialog
        open={editImageDialogOpen}
        onClose={() => setEditImageDialogOpen(false)}
        editor={editor}
        imageElement={editImageElement}
        marker={editImageTextMarker}
        note={note}
      ></EditImageDialog>

      <Dialog open={toggleEncryptionDialogOpen} onClose={closeEncryptionDialog}>
        <DialogTitle>
          {note.config.encryption
            ? t("general/disable-the-encryption-on-this-note")
            : t("general/encrypt-this-note-with-password")}
        </DialogTitle>
        <DialogContent>
          <TextField
            value={toggleEncryptionPassword}
            autoFocus={true}
            onChange={(event) =>
              setToggleEncryptionPassword(event.target.value)
            }
            onKeyUp={(event) => {
              if (event.which === 13) {
                toggleEncryption();
              }
            }}
            placeholder={t("general/Password")}
            type={"password"}
          ></TextField>
        </DialogContent>
        <DialogActions>
          <Button
            variant={"contained"}
            color={"primary"}
            onClick={toggleEncryption}
          >
            {note.config.encryption ? <Lock></Lock> : <LockOpen></LockOpen>}
            {note.config.encryption
              ? t("general/disable-encryption")
              : t("general/encrypt")}
          </Button>
          <Button onClick={closeEncryptionDialog}>{t("general/cancel")}</Button>
        </DialogActions>
      </Dialog>

      <Dialog open={decryptionDialogOpen} onClose={closeDecryptionDialog}>
        <DialogTitle>{t("general/decrypt-this-note")}</DialogTitle>
        <DialogContent>
          <TextField
            value={decryptionPassword}
            autoFocus={true}
            onChange={(event) => setDecryptionPassword(event.target.value)}
            placeholder={t("general/Password")}
            type={"password"}
            onKeyUp={(event) => {
              if (event.which === 13) {
                decryptNote();
              }
            }}
          ></TextField>
        </DialogContent>
        <DialogActions>
          <Button variant={"contained"} color={"primary"} onClick={decryptNote}>
            {t("general/decrypt")}
          </Button>
          <Button onClick={closeDecryptionDialog}>{t("general/cancel")}</Button>
        </DialogActions>
      </Dialog>
    </Box>
  );
}
Example #16
Source File: OverclockingCard.tsx    From Pi-Tool with GNU General Public License v3.0 4 votes vote down vote up
OverclockingCard: React.FC = () => {
    const classes = useStyles();
    const dispatch = useDispatch();
    const overclockLevel = useSelector((state: RootState) => state.overclock.level);
    const { enabled } = useSelector((state: RootState) => state.overclock);

    const [rebootRequired, setRebootRequired] = React.useState(false);
    const [enableDialogOpen, setEnableDialogOpen] = React.useState(false);
    const [infoDialogOpen, setInfoDialogOpen] = React.useState(false);

    const handleLevelSelect = (_event: React.MouseEvent<HTMLElement>, level: OverclockLevel | null) => {
        if (level !== null) {
            dispatch(setOverclockLevel(level));
            flushStore();

            if (level !== overclockLevel) {
                setTimeout(() => {
                    setRebootRequired(true);
                }, 1000);
            }
        }
    }

    const openInfoDialog = () => {
        setInfoDialogOpen(true);
    };

    const openEnableDialog = () => {
        setEnableDialogOpen(true);
    }

    const closeEnableDialog = (confirmed: boolean) => {
        if (confirmed) { dispatch(enableOverclocking()); }
        setEnableDialogOpen(false);
    }

    const closeInfoDialog = () => {
        setInfoDialogOpen(false);
    }

    const triggerReboot = () => {
        const msg: CommandMessage = {
            command: Command.Reboot
        };

        daemon.next(msg);
    }


    const cardActions = (
        <Tooltip title="About overclocking" placement="left">
            <IconButton aria-label="delete" color="default" onClick={openInfoDialog}>
                <HelpOutlineIcon />
            </IconButton>
        </Tooltip>
    );

    const rebootButton = (
        <ButtonGroup className={classes.rebootButtonGroup}>
            <Box flexShrink={1}>
                <Tooltip title="Go back" placement="bottom">
                    <Button
                        className={classes.rebootButton}
                        variant="contained"
                        onClick={(_e) => setRebootRequired(false)}
                        size="large">
                        <ArrowBackIcon />
                    </Button>
                </Tooltip>
            </Box>
            <Button
                className={classes.rebootButton}
                variant="contained"
                startIcon={<PowerSettingsNewIcon />}
                onClick={triggerReboot}
                size="large">
                Reboot Pi now
	</Button>
        </ButtonGroup>
    );

    const enableButton = (
        <Button
            className={classes.enableButton}
            variant="contained"
            color="primary"
            startIcon={<SpeedIcon />}
            onClick={openEnableDialog}
            size="large">
            Enable overclocking
	</Button>
    );

    const overclockLevels = [
        OverclockLevel.BaseClock,
        OverclockLevel.Level1,
        OverclockLevel.Level2,
        OverclockLevel.Level3
    ];

    const tooltipTitle = (level: OverclockLevel) => {
        return formatLevelValues(level).map((s, index) => {
            return <span key={index}>{s}<br /></span>;
        });
    };

    const overclockButtons = overclockLevels.map((level, index) => (
        <ToggleButton key={index} value={level} className={classes.toggleButton}>
            <Tooltip title={
                <Typography variant="caption">
                    {tooltipTitle(level)}
                </Typography>
            }
                placement="bottom" enterDelay={1000}>
                <Typography className={classes.buttonLabels}>
                    {levelToString(level)}
                </Typography>
            </Tooltip>
        </ToggleButton>
    ));

    const overclockButtonGroup = (
        <ToggleButtonGroup
            value={overclockLevel}
            exclusive={true}
            className={classes.buttonGroup}
            onChange={handleLevelSelect}>

            {overclockButtons}
        </ToggleButtonGroup>
    );


    return (
        <MainCard title="Overclocking" actions={cardActions}>
            <EnableOverclockingDialog
                open={enableDialogOpen}
                onClose={closeEnableDialog} />
            <OverclockingDialog
                open={infoDialogOpen}
                onClose={closeInfoDialog} />
            <Box display="flex" justifyContent="space-between">
                {!enabled ? enableButton : (rebootRequired ? rebootButton : overclockButtonGroup)}
            </Box>
        </MainCard >
    );
}
Example #17
Source File: Graph.component.tsx    From akashlytics with GNU General Public License v3.0 4 votes vote down vote up
Graph: React.FunctionComponent<IGraphProps> = ({}) => {
  const [selectedRange, setSelectedRange] = useState(SelectedRange["7D"]);
  const { snapshot: snapshotUrlParam } = useParams<{ snapshot: string }>();
  const snapshot = urlParamToSnapshot(snapshotUrlParam as SnapshotsUrlParam);
  const { data: snapshotData, status } = useGraphSnapshot(snapshot);
  const mediaQuery = useMediaQueryContext();
  const classes = useStyles();
  const theme = getTheme();
  const intl = useIntl();

  const title = getTitle(snapshot as Snapshots);
  const snapshotMetadata = snapshotData && getSnapshotMetadata(snapshot as Snapshots, snapshotData);
  const rangedData = snapshotData && snapshotData.snapshots.slice(snapshotData.snapshots.length - selectedRange, snapshotData.snapshots.length);
  const minValue = rangedData && snapshotMetadata.unitFn(rangedData.map((x) => x.value).reduce((a, b) => (a < b ? a : b)));
  const maxValue = snapshotData && snapshotMetadata.unitFn(rangedData.map((x) => x.value).reduce((a, b) => (a > b ? a : b)));
  const graphData = snapshotData
    ? [
        {
          id: snapshot,
          color: "rgb(1,0,0)",
          data: rangedData.map((snapshot) => ({
            x: snapshot.date,
            y: round(snapshotMetadata.unitFn(snapshot.value))
          }))
        }
      ]
    : null;
  const graphMetadata = getGraphMetadataPerRange(selectedRange);

  return (
    <div className={clsx("container", classes.root)}>
      <Helmet title={title} />

      <div>
        <Button component={RouterLink} to="/" startIcon={<ArrowBackIcon />}>
          Back
        </Button>
      </div>

      <div className={clsx("row mt-4 mb-2")}>
        <div className="col-xs-12">
          <Typography variant="h1" className={clsx(classes.title)}>
            {title}
          </Typography>
        </div>
      </div>

      {!snapshotData && status === "loading" && (
        <div className={classes.loading}>
          <CircularProgress size={80} />
        </div>
      )}

      {snapshotData && (
        <>
          <Box className={classes.subTitle}>
            <Box className={classes.subTitleValues}>
              <Typography variant="h3" className={classes.titleValue}>
                <FormattedNumber value={snapshotMetadata.unitFn(snapshotData.currentValue)} maximumFractionDigits={2} />
                &nbsp;
                <DiffPercentageChip value={percIncrease(snapshotData.compareValue, snapshotData.currentValue)} size="medium" />
                &nbsp;
                <DiffNumber value={snapshotMetadata.unitFn(snapshotData.currentValue - snapshotData.compareValue)} className={classes.diffNumber} />
              </Typography>
            </Box>

            <ButtonGroup size="small" aria-label="Graph range select" className={classes.graphRangeSelect}>
              <Button variant={selectedRange === SelectedRange["7D"] ? "contained" : "outlined"} onClick={() => setSelectedRange(SelectedRange["7D"])}>
                7D
              </Button>
              <Button variant={selectedRange === SelectedRange["1M"] ? "contained" : "outlined"} onClick={() => setSelectedRange(SelectedRange["1M"])}>
                1M
              </Button>
              <Button variant={selectedRange === SelectedRange["ALL"] ? "contained" : "outlined"} onClick={() => setSelectedRange(SelectedRange["ALL"])}>
                ALL
              </Button>
            </ButtonGroup>
          </Box>

          <div className={classes.graphContainer}>
            <Box className={classes.watermark}>
              <Typography variant="caption">akashlytics.com</Typography>
            </Box>
            <ResponsiveLineCanvas
              theme={theme}
              data={graphData}
              curve="linear"
              margin={{ top: 30, right: 35, bottom: 50, left: 45 }}
              xScale={{ type: "point" }}
              yScale={{
                type: "linear",
                min: minValue * 0.98,
                max: maxValue * 1.02
              }}
              yFormat=" >-1d"
              // @ts-ignore will be fixed in 0.69.1
              axisBottom={{
                tickRotation: mediaQuery.mobileView ? 45 : 0,
                format: (dateStr) => intl.formatDate(dateStr, { day: "numeric", month: "short", timeZone: "utc" }),
                tickValues: getTickValues(rangedData, graphMetadata.xModulo)
              }}
              // @ts-ignore will be fixed in 0.69.1
              axisLeft={{
                format: (val) => nFormatter(val, 2)
              }}
              axisTop={null}
              axisRight={null}
              colors={"#e41e13"}
              pointSize={graphMetadata.size}
              pointBorderColor="#e41e13"
              pointColor={"#ffffff"}
              pointBorderWidth={graphMetadata.border}
              isInteractive={true}
              tooltip={(props) => (
                <div className={classes.graphTooltip}>
                  <Typography variant="caption">
                    <FormattedDate value={new Date(props.point.data.x)} day="numeric" month="long" timeZone="UTC" />
                  </Typography>
                  <Box>{nFormatter(props.point.data.y as number, 2)}</Box>
                </div>
              )}
              enableGridX={false}
              enableCrosshair={true}
            />
          </div>
        </>
      )}
    </div>
  );
}
Example #18
Source File: index.tsx    From vscode-crossnote with GNU Affero General Public License v3.0 4 votes vote down vote up
function OCRWidget(props: WidgetArgs) {
  const classes = useStyles(props);
  const { t } = useTranslation();
  const [canvas, setCanvas] = useState<HTMLCanvasElement>(null);
  // https://github.com/tesseract-ocr/tesseract/wiki/Data-Files#data-files-for-version-400-november-29-2016
  const [link, setLink] = useState<string>("");
  const [imageDataURL, setImageDataURL] = useState<string>("");
  const [ocrDataURL, setOCRDataURL] = useState<string>("");
  const [imageDropAreaElement, setImageDropAreaElement] = useState<
    HTMLInputElement
  >(null);
  const [isProcessing, setIsProcessing] = useState<boolean>(false);
  const [ocrProgresses, setOCRProgresses] = useState<OCRProgress[]>([]);
  const [selectedLanguages, setSelectedLanguages] = useState<string[]>(
    getInitialLanguages()
  );
  const [grayscaleChecked, setGrayscaleChecked] = useState<boolean>(true);

  useEffect(() => {
    if (canvas && imageDataURL) {
      const imageObject = new Image();
      const context = canvas.getContext("2d");
      imageObject.onload = function () {
        canvas.width = imageObject.width;
        canvas.height = imageObject.height;
        context.clearRect(0, 0, canvas.width, canvas.height);
        if (grayscaleChecked) {
          context.fillStyle = "#FFF";
          context.fillRect(0, 0, canvas.width, canvas.height);
          context.globalCompositeOperation = "luminosity";
        }
        context.drawImage(imageObject, 0, 0);
        setOCRDataURL(canvas.toDataURL());
      };
      imageObject.onerror = (error) => {
        throw error;
      };
      imageObject.setAttribute("crossOrigin", "anonymous");
      imageObject.src = imageDataURL;
    }
  }, [canvas, imageDataURL, grayscaleChecked]);

  function clickDropArea(e: any) {
    e.preventDefault();
    e.stopPropagation();
    if (!imageDropAreaElement || isProcessing) return;
    imageDropAreaElement.onchange = function (event) {
      const target = event.target as any;
      const files = target.files || [];
      if (files.length) {
        try {
          const file = files[0] as File;
          const reader = new FileReader();
          reader.readAsDataURL(file);
          reader.onload = () => {
            setImageDataURL(reader.result as string);
          };
          reader.onerror = (error) => {
            throw error;
          };
        } catch (error) {}
      }
    };
    imageDropAreaElement.click();
  }

  function startOCRFromLink() {
    try {
      setImageDataURL(link);
    } catch (error) {}
  }

  function ocr(input: File | string | HTMLCanvasElement) {
    const worker = createWorker({
      logger: (m: OCRProgress) => {
        setOCRProgresses((ocrProgresses) => {
          if (
            ocrProgresses.length &&
            ocrProgresses[ocrProgresses.length - 1].status === m.status
          ) {
            return [...ocrProgresses.slice(0, ocrProgresses.length - 1), m];
          } else {
            return [...ocrProgresses, m];
          }
        });
      },
    });

    (async () => {
      setIsProcessing(true);
      let languagesArr = selectedLanguages;
      if (languagesArr.length === 0) {
        languagesArr = ["eng"];
      }

      await worker.load();
      await worker.loadLanguage(languagesArr.join("+"));
      await worker.initialize(languagesArr.join("+"));
      const {
        data: { text },
      } = await worker.recognize(input);
      props.replaceSelf("\n" + text);
      await worker.terminate();
      setIsProcessing(false);
    })();
  }

  function toggleLanguage(lang: string) {
    setSelectedLanguages((selectedLanguages) => {
      const offset = selectedLanguages.indexOf(lang);
      if (offset >= 0) {
        selectedLanguages.splice(offset, 1);
        selectedLanguages = [...selectedLanguages];
      } else {
        selectedLanguages = [...selectedLanguages, lang];
      }
      return selectedLanguages;
    });
  }

  if (props.isPreview) {
    return <span></span>;
  }

  if (isProcessing) {
    return (
      <Card elevation={2} className={clsx(classes.card)}>
        <Typography variant={"h5"}>{t("general/Processing")}</Typography>
        {/*<Typography variant={"body1"}>{t("general/please-wait")}</Typography>*/}
        <List>
          {ocrProgresses.length > 0 && (
            <ListItem>
              <ListItemText>
                {t(
                  "tesseract/" + ocrProgresses[ocrProgresses.length - 1].status
                )}
              </ListItemText>
              <ListItemSecondaryAction>
                {Math.floor(
                  ocrProgresses[ocrProgresses.length - 1].progress * 100
                ).toString() + "%"}
              </ListItemSecondaryAction>
            </ListItem>
          )}
        </List>
      </Card>
    );
  }

  if (imageDataURL) {
    return (
      <Card elevation={2} className={clsx(classes.card)}>
        <Box className={clsx(classes.section)}>
          <Typography variant={"subtitle1"} style={{ marginBottom: "8px" }}>
            {t("widget/crossnote.ocr/recognize-text-in-languages")}
          </Typography>
          <FormGroup>
            <FormControlLabel
              control={
                <Checkbox
                  checked={selectedLanguages.indexOf("eng") >= 0}
                  onChange={() => toggleLanguage("eng")}
                  value="eng"
                />
              }
              label="English"
            />
            <FormControlLabel
              control={
                <Checkbox
                  checked={selectedLanguages.indexOf("chi_sim") >= 0}
                  onChange={() => toggleLanguage("chi_sim")}
                  value="chi_sim"
                />
              }
              label="简体中文"
            />
            <FormControlLabel
              control={
                <Checkbox
                  checked={selectedLanguages.indexOf("chi_tra") >= 0}
                  onChange={() => toggleLanguage("chi_tra")}
                  value="chi_tra"
                />
              }
              label="繁體中文"
            />
            <FormControlLabel
              control={
                <Checkbox
                  checked={selectedLanguages.indexOf("jpn") >= 0}
                  onChange={() => toggleLanguage("jpn")}
                  value="jpn"
                />
              }
              label="日本語"
            />
          </FormGroup>
        </Box>
        <Box className={clsx(classes.section)}>
          <Typography variant={"subtitle1"} style={{ marginBottom: "8px" }}>
            {t("widget/crossnote.ocr/extra-settings")}
          </Typography>
          <FormControlLabel
            control={
              <Switch
                checked={grayscaleChecked}
                onChange={() => {
                  setGrayscaleChecked(!grayscaleChecked);
                }}
                color={"primary"}
              ></Switch>
            }
            label={t("widget/crossnote.ocr/grayscale")}
          ></FormControlLabel>
        </Box>
        <Box className={clsx(classes.canvasWrapper)}>
          <canvas
            className={clsx(classes.canvas)}
            ref={(element) => setCanvas(element)}
          ></canvas>
        </Box>
        <ButtonGroup>
          <Button
            onClick={() => {
              setImageDataURL("");
              setOCRDataURL("");
            }}
          >
            {t("general/go-back")}
          </Button>
          <Button
            color={"primary"}
            onClick={() => ocr(ocrDataURL)}
            disabled={!ocrDataURL}
          >
            {t("widget/crossnote.ocr/start-ocr")}
          </Button>
        </ButtonGroup>
      </Card>
    );
  }

  return (
    <Card elevation={2} className={clsx(classes.card)}>
      <Typography variant={"h5"}>{t("widget/crossnote.ocr/ocr")}</Typography>
      <Box className={clsx(classes.actionButtons)}>
        <Tooltip title={t("general/Delete")}>
          <IconButton onClick={() => props.removeSelf()}>
            <TrashCan></TrashCan>
          </IconButton>
        </Tooltip>
      </Box>
      <Box className={clsx(classes.section)}>
        <Typography variant={"subtitle1"} style={{ marginBottom: "8px" }}>
          {t("general/Link")}
        </Typography>
        <Input
          margin={"dense"}
          placeholder={t("widget/crossnote.image/image-url-placeholder")}
          value={link}
          onChange={(event) => {
            setLink(event.target.value);
          }}
          onKeyDown={(event) => {
            if (event.which === 13) {
              startOCRFromLink();
            }
          }}
          fullWidth={true}
        ></Input>
      </Box>
      <Typography
        variant={"subtitle1"}
        style={{ marginTop: "16px", textAlign: "center" }}
      >
        {t("widget/crossnote.auth/Or")}
      </Typography>
      <Box className={clsx(classes.section)}>
        <Typography variant={"subtitle1"} style={{ marginBottom: "8px" }}>
          {t("widget/crossnote.ocr/local-image")}
        </Typography>
        <Box
          className={clsx(
            classes.dropArea,
            isProcessing ? classes.disabled : null
          )}
          onClick={clickDropArea}
        >
          <Typography>
            {isProcessing
              ? t("utils/uploading-image")
              : t("widget/crossnote.image/click-here-to-browse-image-file")}
          </Typography>
        </Box>
      </Box>
      <input
        type="file"
        // multiple
        style={{ display: "none" }}
        ref={(element: HTMLInputElement) => {
          setImageDropAreaElement(element);
        }}
      ></input>
    </Card>
  );
}