@material-ui/core#Fab TypeScript Examples

The following examples show how to use @material-ui/core#Fab. 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: ScrollTop.tsx    From UsTaxes with GNU Affero General Public License v3.0 6 votes vote down vote up
ScrollTop = (): ReactElement => {
  const classes = useStyles()
  const trigger = useScrollTrigger({
    target: window,
    disableHysteresis: true,
    threshold: 100
  })

  const handleClick = () => {
    window.scrollTo({ top: 0, behavior: 'smooth' })
  }

  return (
    <Zoom in={trigger}>
      <div onClick={handleClick} role="presentation" className={classes.root}>
        <Fab color="default" size="small" aria-label="scroll back to top">
          <KeyboardArrowUpIcon />
        </Fab>
      </div>
    </Zoom>
  )
}
Example #2
Source File: App.tsx    From ts-express-react with MIT License 6 votes vote down vote up
export default function App() {
  const [apiResponse, setApiResponse] = useState("");
  
  const onCallApi = async () => {
    try {
      const response = await fetch('/api', {
        method: "GET",
      });
      const text = await response.text();
      console.log(text);
      setApiResponse(text);
    } catch (error) {
      console.error(error);
      throw error;
    }
  }
  return (
    <div className="App">
      <Grid container spacing={6} justifyContent="center" direction="column">
        <Grid item> 
          {`Client App Name - ${ App_Name } `}
        </Grid>
        <Grid item> 
          <Fab variant="extended" color="primary" onClick={onCallApi}>
            <CloudDownloadRounded className="icon"/>
            Call API
          </Fab>
        </Grid>
        {apiResponse &&
          <Grid item> 
            {`Server Response - ${ apiResponse } `}
          </Grid>
        }
      </Grid>
    </div>
  );
}
Example #3
Source File: CardAddButton.tsx    From neodash with Apache License 2.0 6 votes vote down vote up
NeoAddNewCard = ({ onCreatePressed }) => {
    return (
        <div>
            <Card style={{ background: "#e0e0e0" }}>
                <CardContent style={{ height: '429px' }}>
                    <Typography variant="h2" color="textSecondary" style={{ paddingTop: "155px", textAlign: "center" }}>
                        <Fab size="medium" className={"blue-grey"} aria-label="add"
                            onClick={() => {
                                onCreatePressed();
                            }} >
                            <AddIcon />
                        </Fab>
                    </Typography>
                </CardContent>
            </Card>
        </div>
    );
}
Example #4
Source File: ClearFlaskEmbed.tsx    From clearflask with Apache License 2.0 5 votes vote down vote up
ClearFlaskEmbedHoverFeedback = (props: {
  path?: string;
  Icon: any;
  preload?: boolean;
}) => {
  const { path, Icon, preload } = props;
  const [demoOpen, setDemoOpen] = useState<boolean>();
  const [isHovering, setIsHovering] = useState<boolean>();
  const anchorRef = useRef<any>(null);
  const classes = useStyles();
  return (
    <>
      <div
        ref={anchorRef}
        className={classes.fabContainer}
        onMouseOver={() => setIsHovering(true)}
        onMouseOut={() => setIsHovering(false)}
      >
        <Fab
          className={classes.fab}
          onClick={() => setDemoOpen(!demoOpen)}
          color='primary'
          variant='extended'
        >
          <Icon />
          <CollapseV5 in={isHovering || demoOpen} orientation='horizontal'>
            <span className={classes.noWrap}>&nbsp;What do you think?</span>
          </CollapseV5>
        </Fab>
      </div>
      <ClosablePopper
        anchorType='ref'
        anchor={anchorRef}
        closeButtonPosition='top-left'
        open={!!demoOpen}
        onClose={() => setDemoOpen(false)}
        placement='top'
        arrow
        clickAway
        paperClassName={classes.popper}
        keepMounted
      >
        <iframe
          title='Demo: ClearFlask Feedback'
          src={(demoOpen !== undefined // After it's open, keep it open
            || isHovering !== undefined // After hovered once, keep it preloaded
            || preload) ? `${windowIso.location.protocol}//product.${windowIso.location.host}/${path || ''}` : 'about:blank'}
          width='100%'
          height='100%'
          frameBorder={0}
        />
      </ClosablePopper>
    </>
  );
}
Example #5
Source File: FormComponents.tsx    From TidGi-Desktop with Mozilla Public License 2.0 5 votes vote down vote up
AbsoluteFab = styled(Fab)`
  position: fixed;
  right: 10px;
  bottom: 10px;
  color: rgba(0, 0, 0, 0.2);
  font-size: 10px;
`
Example #6
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 #7
Source File: Header.tsx    From safe-airdrop with MIT License 5 votes vote down vote up
Header = (): JSX.Element => {
  const { messages, showMessages, hideMessages, toggleMessages, removeMessage } = useContext(MessageContext);

  const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
    if (reason === "clickaway") {
      return;
    }

    hideMessages();
  };

  return (
    <HeaderContainer>
      <Fab
        variant="circular"
        size="small"
        className={messages.length === 0 ? "statusDotButtonEmpty" : "statusDotButtonErrors"}
        style={{ textTransform: "none", width: "34px", height: "34px" }}
        onClick={toggleMessages}
      >
        {messages.length === 0 ? (
          <Icon color="white" type="check" size="sm" />
        ) : (
          <Text size="xl" color="white">
            {messages.length}
          </Text>
        )}
      </Fab>
      <FAQModal />
      <Snackbar
        anchorOrigin={{ vertical: "top", horizontal: "right" }}
        open={showMessages}
        onClose={handleClose}
        autoHideDuration={6000}
        style={{ gap: "4px", top: "64px" }}
      >
        <AlertWrapper>
          {messages.length === 0 && (
            <Alert secerity="success" key="successMessage">
              No warnings or errors.
            </Alert>
          )}
          {messages.map((message: Message, index: number) => (
            <Alert severity={message.severity} key={"message" + index} onClose={() => removeMessage(message)}>
              {message.message}
            </Alert>
          ))}
        </AlertWrapper>
      </Snackbar>
    </HeaderContainer>
  );
}
Example #8
Source File: index.tsx    From fishbowl with MIT License 5 votes vote down vote up
function Pending(props: { joinCode: string }) {
  const { t } = useTranslation()
  return (
    <Grid container direction="column" spacing={2}>
      <Grid item>
        <Trans t={t} i18nKey="pending.explanation1">
          {
            "This is embarrassing, we cannot seem to figure out which player you are in game "
          }
          <strong>{{ joinCode: props.joinCode.toLocaleUpperCase() }}</strong>...
        </Trans>
        ?
      </Grid>
      <Grid item>
        {t(
          "pending.explanation2",
          "Ask your host to click the settings button {{ settingsIcon }} in the bottom right corner of their page to send your unique join link so you can get back in it!",
          { settingsIcon: "⚙️" }
        )}
      </Grid>
      <Grid item>
        <Box
          display="flex"
          flexDirection="row"
          justifyContent="flex-end"
          alignItems="center"
        >
          <Box pr={2}>
            {t("pending.settingsButtonExample", "It looks like this")} →{" "}
          </Box>
          <Box>
            <Fab size="small" disabled={true}>
              <SettingsIcon></SettingsIcon>
            </Fab>
          </Box>
        </Box>
      </Grid>
      <Grid item></Grid>
      <Grid item></Grid>
      <Grid item>
        <Trans t={t} i18nKey="pending.differentGame">
          {"If you meant to join a different game, "}
          <Link component={RouterLink} to={routes.root}>
            return to the home page
          </Link>
          .
        </Trans>
      </Grid>
    </Grid>
  )
}
Example #9
Source File: GameLayout.tsx    From fishbowl with MIT License 5 votes vote down vote up
function GameLayout(props: { children: React.ReactNode; joinCode: string }) {
  const currentPlayer = React.useContext(CurrentPlayerContext)
  const location = useLocation()
  const history = useHistory()

  const inSettings = matchPath(location.pathname, {
    path: routes.game.settings,
    exact: true,
  })

  const showFabOnThisRoute = !some(
    [routes.game.pending, routes.game.lobby, routes.game.ended],
    (route) => {
      return matchPath(location.pathname, {
        path: route,
        exact: true,
      })
    }
  )

  return (
    <Box>
      <Box>{props.children}</Box>
      {showFabOnThisRoute && currentPlayer.role === PlayerRole.Host && (
        <Box display="flex" flexDirection="row-reverse" pb={2} pt={6}>
          <Fab
            color="default"
            size="small"
            onClick={() => {
              if (inSettings) {
                history.goBack()
              } else {
                history.push(
                  generatePath(routes.game.settings, {
                    joinCode: props.joinCode.toLocaleUpperCase(),
                  })
                )
              }
            }}
          >
            {inSettings ? <CloseIcon /> : <SettingsIcon />}
          </Fab>
        </Box>
      )}
    </Box>
  )
}
Example #10
Source File: FAQModal.tsx    From safe-airdrop with MIT License 4 votes vote down vote up
FAQModal: () => JSX.Element = () => {
  const [showHelp, setShowHelp] = useState(false);
  return (
    <>
      <Fab variant="extended" size="small" style={{ textTransform: "none" }} onClick={() => setShowHelp(true)}>
        <Icon size="md" type="question" />
        <Text size="xl">Help</Text>
      </Fab>
      {showHelp && (
        <GenericModal
          withoutBodyPadding={false}
          onClose={() => setShowHelp(false)}
          title={<Title size="lg">How to use the CSV Airdrop App</Title>}
          body={
            <div>
              <Title size="md" strong>
                Overview
              </Title>
              <Text size="lg">
                <p>
                  This app can batch multiple transfers of ERC20, ERC721, ERC1155 and native tokens into a single
                  transaction. It's as simple as uploading / copy & pasting a single CSV transfer file and hitting the
                  submit button.
                </p>
                <p>
                  {" "}
                  This saves gas ⛽ and a substantial amount of time ⌚ by requiring less signatures and transactions.
                </p>
              </Text>
              <Divider />
              <Title size="md" strong>
                Preparing a Transfer File
              </Title>
              <Text size="lg">
                Transfer files are expected to be in CSV format with the following required columns:
                <ul>
                  <li>
                    <code>
                      <b>token_type</b>
                    </code>
                    : The type of token that is being transferred. One of <code>erc20,nft</code> or <code>native</code>.
                    NFT Tokens can be either ERC721 or ERC1155.
                  </li>
                  <li>
                    <code>
                      <b>token_address</b>
                    </code>
                    : Ethereum address of ERC20 token to be transferred. This has to be left blank for native (ETH)
                    transfers.
                  </li>
                  <li>
                    <code>
                      <b>receiver</b>
                    </code>
                    : Ethereum address of transfer receiver.
                  </li>
                  <li>
                    <code>
                      <b>amount</b>
                    </code>
                    : the amount of token to be transferred. This can be left blank for erc721 transfers.
                  </li>
                  <li>
                    <code>
                      <b>id</b>
                    </code>
                    : The id of the collectible token (erc721 or erc1155) to transfer. This can be left blank for native
                    and erc20 transfers.
                  </li>
                </ul>
                <p>
                  <b>
                    Important: The CSV file has to use "," as a separator and the header row always has to be provided
                    as the first row and include the described column names.
                  </b>
                </p>
              </Text>
              <div>
                <Link href="./sample.csv" download>
                  Sample Transfer File
                </Link>
              </div>
              <Divider />
              <Title size="md" strong>
                Native Token Transfers
              </Title>
              <Text size="lg">
                Since native tokens do not have a token address, you must leave the <code>token_address</code> column
                blank for native transfers.
              </Text>
            </div>
          }
          footer={
            <Button size="md" color="secondary" onClick={() => setShowHelp(false)}>
              Close
            </Button>
          }
        ></GenericModal>
      )}
    </>
  );
}
Example #11
Source File: index.tsx    From firetable with Apache License 2.0 4 votes vote down vote up
export default function SideDrawer() {
  const classes = useStyles();
  const { tableState, dataGridRef, sideDrawerRef } = useFiretableContext();

  const [cell, setCell] = useState<SelectedCell>(null);
  const [open, setOpen] = useState(false);
  if (sideDrawerRef) sideDrawerRef.current = { cell, setCell, open, setOpen };

  const disabled = !open && (!cell || _isNil(cell.row));
  useEffect(() => {
    if (disabled && setOpen) setOpen(false);
  }, [disabled]);

  const handleNavigate = (direction: "up" | "down") => () => {
    if (!tableState?.rows) return;

    let row = cell!.row;
    if (direction === "up" && row > 0) row -= 1;
    if (direction === "down" && row < tableState.rows.length - 1) row += 1;

    setCell!((cell) => ({ column: cell!.column, row }));

    const idx = tableState?.columns[cell!.column]?.index;
    dataGridRef?.current?.selectCell({ rowIdx: row, idx });
  };

  const [urlDocState, dispatchUrlDoc] = useDoc({});
  useEffect(() => {
    if (urlDocState.doc) setOpen(true);
  }, [urlDocState]);

  useEffect(() => {
    setOpen(false);
    dispatchUrlDoc({ path: "", doc: null });
  }, [window.location.pathname]);

  useEffect(() => {
    const rowRef = queryString.parse(window.location.search).rowRef as string;
    if (rowRef) dispatchUrlDoc({ path: decodeURIComponent(rowRef) });
  }, []);

  useEffect(() => {
    if (cell && tableState?.rows[cell.row]) {
      window.history.pushState(
        "",
        `${tableState?.tablePath}`,
        `${window.location.pathname}?rowRef=${encodeURIComponent(
          tableState?.rows[cell.row].ref.path
        )}`
      );
      // console.log(tableState?.tablePath, tableState?.rows[cell.row].id);
      if (urlDocState.doc) {
        urlDocState.unsubscribe();
        dispatchUrlDoc({ path: "", doc: null });
      }
    }
  }, [cell]);

  return (
    <div className={clsx(open && classes.open, disabled && classes.disabled)}>
      <Drawer
        variant="permanent"
        anchor="right"
        className={classes.drawer}
        classes={{
          paperAnchorDockedRight: clsx(
            classes.paper,
            !disabled && classes.bumpPaper
          ),
          paper: clsx({ [classes.paperClose]: !open }),
        }}
      >
        <ErrorBoundary>
          <div className={classes.drawerContents}>
            {open &&
              (urlDocState.doc || cell) &&
              !_isEmpty(tableState?.columns) && (
                <Form
                  key={urlDocState.path}
                  values={
                    urlDocState.doc ?? tableState?.rows[cell?.row ?? -1] ?? {}
                  }
                />
              )}
          </div>
        </ErrorBoundary>

        {open && (
          <div className={classes.navFabContainer}>
            <Fab
              classes={{
                root: clsx(classes.fab, classes.navFab),
                disabled: classes.disabled,
              }}
              style={{ animationDelay: "0.2s" }}
              color="secondary"
              size="small"
              disabled={disabled || !cell || cell.row <= 0}
              onClick={handleNavigate("up")}
            >
              <ChevronUpIcon />
            </Fab>

            <Fab
              classes={{
                root: clsx(classes.fab, classes.navFab),
                disabled: classes.disabled,
              }}
              style={{ animationDelay: "0.1s" }}
              color="secondary"
              size="small"
              disabled={
                disabled ||
                !tableState ||
                !cell ||
                cell.row >= tableState.rows.length - 1
              }
              onClick={handleNavigate("down")}
            >
              <ChevronDownIcon />
            </Fab>
          </div>
        )}

        <div className={classes.drawerFabContainer}>
          <Fab
            classes={{ root: classes.fab, disabled: classes.disabled }}
            color="secondary"
            disabled={disabled}
            onClick={() => {
              if (setOpen) setOpen((o) => !o);
            }}
          >
            <ChevronIcon className={classes.drawerFabIcon} />
          </Fab>
        </div>
      </Drawer>
    </div>
  );
}
Example #12
Source File: index.tsx    From aqualink-app with MIT License 4 votes vote down vote up
LandingPage = ({ classes }: LandingPageProps) => {
  const [scrollPosition, setScrollPosition] = useState(0);
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down("xs"));
  const isTablet = useMediaQuery(theme.breakpoints.down("md"));
  const firstCard = useRef<HTMLDivElement>(null);

  const seeMore = () => {
    firstCard.current?.scrollIntoView({
      behavior: "smooth",
    });
  };

  useEffect(() => {
    const handleScroll = () => {
      setScrollPosition(window.pageYOffset);
    };

    window.addEventListener("scroll", handleScroll, { passive: true });

    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return (
    <>
      <NavBar routeButtons searchLocation={false} />
      {scrollPosition === 0 && isMobile && (
        <Box
          width="100%"
          display="flex"
          justifyContent="flex-end"
          position="fixed"
          bottom="10px"
          padding="0 10px"
        >
          <Fab onClick={seeMore} size="large">
            <ArrowDownwardIcon />
          </Fab>
        </Box>
      )}
      <div>
        <Box display="flex" alignItems="top" className={classes.landingImage}>
          <Container className={classes.container}>
            <Grid container item xs={9}>
              <Box display="flex">
                <Typography variant="h1" color="textPrimary">
                  Aqua
                </Typography>
                <Typography
                  className={classes.aqualinkSecondPart}
                  color="textPrimary"
                  variant="h1"
                >
                  link
                </Typography>
              </Box>
            </Grid>
            <Grid container item sm={11} md={7}>
              <Box mt="1.5rem" display="flex">
                <Typography variant="h1" color="textPrimary">
                  Monitoring for marine ecosystems
                </Typography>
              </Box>
            </Grid>
            <Grid container item sm={9} md={4}>
              <Box mt="4rem" display="flex">
                <Typography variant="h4" color="textPrimary">
                  A tool for people on the front lines of ocean conservation
                </Typography>
              </Box>
            </Grid>
            <Grid item xs={12}>
              <Box mt="2rem">
                <Grid container spacing={2}>
                  {landingPageButtons.map(
                    ({ label, to, hasWhiteColor, variant }) => (
                      <Grid key={label} item xs={isTablet ? 12 : undefined}>
                        <Button
                          component={Link}
                          to={to}
                          className={classNames(classes.buttons, {
                            [classes.whiteColorButton]: hasWhiteColor,
                          })}
                          variant={variant}
                          color="primary"
                          onClick={() =>
                            trackButtonClick(
                              GaCategory.BUTTON_CLICK,
                              GaAction.LANDING_PAGE_BUTTON_CLICK,
                              label
                            )
                          }
                        >
                          <Typography variant="h5">{label}</Typography>
                        </Button>
                      </Grid>
                    )
                  )}
                </Grid>
              </Box>
            </Grid>
          </Container>
        </Box>
      </div>
      <Container className={classes.cardContainer}>
        {cardTitles.map((item, i) => (
          <Card
            ref={i === 0 ? firstCard : undefined}
            key={item.title}
            title={item.title}
            text={item.text}
            backgroundColor={item.backgroundColor}
            direction={item.direction}
            image={item.image}
            scaleDown={item.scaleDown}
          />
        ))}
      </Container>
      <Footer />
    </>
  );
}
Example #13
Source File: GraphChart.tsx    From neodash with Apache License 2.0 4 votes vote down vote up
NeoGraphChart = (props: ChartProps) => {
    if (props.records == null || props.records.length == 0 || props.records[0].keys == null) {
        return <>No data, re-run the report.</>
    }

    const [open, setOpen] = React.useState(false);
    const [firstRun, setFirstRun] = React.useState(true);
    const [inspectItem, setInspectItem] = React.useState({});

    const handleOpen = () => {
        setOpen(true);
    };

    const handleClose = () => {
        setOpen(false);
    };

    // Retrieve config from advanced settings
    const backgroundColor = props.settings && props.settings.backgroundColor ? props.settings.backgroundColor : "#fafafa";
    const nodeSizeProp = props.settings && props.settings.nodeSizeProp ? props.settings.nodeSizeProp : "size";
    const nodeColorProp = props.settings && props.settings.nodeColorProp ? props.settings.nodeColorProp : "color";
    const defaultNodeSize = props.settings && props.settings.defaultNodeSize ? props.settings.defaultNodeSize : 2;
    const relWidthProp = props.settings && props.settings.relWidthProp ? props.settings.relWidthProp : "width";
    const relColorProp = props.settings && props.settings.relColorProp ? props.settings.relColorProp : "color";
    const defaultRelWidth = props.settings && props.settings.defaultRelWidth ? props.settings.defaultRelWidth : 1;
    const defaultRelColor = props.settings && props.settings.defaultRelColor ? props.settings.defaultRelColor : "#a0a0a0";
    const nodeLabelColor = props.settings && props.settings.nodeLabelColor ? props.settings.nodeLabelColor : "black";
    const nodeLabelFontSize = props.settings && props.settings.nodeLabelFontSize ? props.settings.nodeLabelFontSize : 3.5;
    const relLabelFontSize = props.settings && props.settings.relLabelFontSize ? props.settings.relLabelFontSize : 2.75;
    const styleRules = props.settings && props.settings.styleRules ? props.settings.styleRules : [];
    const relLabelColor = props.settings && props.settings.relLabelColor ? props.settings.relLabelColor : "#a0a0a0";
    const nodeColorScheme = props.settings && props.settings.nodeColorScheme ? props.settings.nodeColorScheme : "neodash";
    const showPropertiesOnHover = props.settings && props.settings.showPropertiesOnHover !== undefined ? props.settings.showPropertiesOnHover : true;
    const showPropertiesOnClick = props.settings && props.settings.showPropertiesOnClick !== undefined ? props.settings.showPropertiesOnClick : true;
    const fixNodeAfterDrag = props.settings && props.settings.fixNodeAfterDrag !== undefined ? props.settings.fixNodeAfterDrag : true;
    const layout = props.settings && props.settings.layout !== undefined ? props.settings.layout : "force-directed";
    const lockable = props.settings && props.settings.lockable !== undefined ? props.settings.lockable : true;
    const drilldownLink = props.settings && props.settings.drilldownLink !== undefined ? props.settings.drilldownLink : "";
    const selfLoopRotationDegrees = 45;
    const rightClickToExpandNodes = false; // TODO - this isn't working properly yet, disable it.
    const defaultNodeColor = "lightgrey"; // Color of nodes without labels
    const linkDirectionalParticles = props.settings && props.settings.relationshipParticles ? 5 : undefined;
    const linkDirectionalParticleSpeed = 0.005; // Speed of particles on relationships.

    const iconStyle = props.settings && props.settings.iconStyle !== undefined ? props.settings.iconStyle : "";
    let iconObject = undefined;
    try {
        iconObject = iconStyle ? JSON.parse(iconStyle) : undefined;
    } catch (error) {
        console.error(error);
    }


    // get dashboard parameters.
    const parameters = props.parameters ? props.parameters : {};

    const [data, setData] = React.useState({ nodes: [], links: [] });

    // Create the dictionary used for storing the memory of dragged node positions.
    if (props.settings.nodePositions == undefined) {
        props.settings.nodePositions = {};
    }
    var nodePositions = props.settings && props.settings.nodePositions;

    // 'frozen' indicates that the graph visualization engine is paused, node positions and stored and only dragging is possible.
    const [frozen, setFrozen] = React.useState(props.settings && props.settings.frozen !== undefined ? props.settings.frozen : false);

    // Currently unused, but dynamic graph exploration could be done with these records.
    const [extraRecords, setExtraRecords] = React.useState([]);

    // When data is refreshed, rebuild the visualization data.
    useEffect(() => {
        buildVisualizationDictionaryFromRecords(props.records);
    }, [])

    const { observe, unobserve, width, height, entry } = useDimensions({
        onResize: ({ observe, unobserve, width, height, entry }) => {
            // Triggered whenever the size of the target is changed...
            unobserve(); // To stop observing the current target element
            observe(); // To re-start observing the current target element
        },
    });


    // Dictionaries to populate based on query results.
    var nodes = {};
    var nodeLabels = {};
    var links = {};
    var linkTypes = {};

    // Gets all graphy objects (nodes/relationships) from the complete set of return values.
    function extractGraphEntitiesFromField(value) {
        if (value == undefined) {
            return
        }
        if (valueIsArray(value)) {
            value.forEach((v, i) => extractGraphEntitiesFromField(v));
        } else if (valueIsNode(value)) {
            value.labels.forEach(l => nodeLabels[l] = true)
            nodes[value.identity.low] = {
                id: value.identity.low,
                labels: value.labels,
                size: value.properties[nodeSizeProp] ? value.properties[nodeSizeProp] : defaultNodeSize,
                properties: value.properties,
                lastLabel: value.labels[value.labels.length - 1]
            };
            if (frozen && nodePositions && nodePositions[value.identity.low]) {
                nodes[value.identity.low]["fx"] = nodePositions[value.identity.low][0];
                nodes[value.identity.low]["fy"] = nodePositions[value.identity.low][1];
            }
        } else if (valueIsRelationship(value)) {
            if (links[value.start.low + "," + value.end.low] == undefined) {
                links[value.start.low + "," + value.end.low] = [];
            }
            const addItem = (arr, item) => arr.find((x) => x.id === item.id) || arr.push(item);
            addItem(links[value.start.low + "," + value.end.low], {
                id: value.identity.low,
                source: value.start.low,
                target: value.end.low,
                type: value.type,
                width: value.properties[relWidthProp] ? value.properties[relWidthProp] : defaultRelWidth,
                color: value.properties[relColorProp] ? value.properties[relColorProp] : defaultRelColor,
                properties: value.properties
            });

        } else if (valueIsPath(value)) {
            value.segments.map((segment, i) => {
                extractGraphEntitiesFromField(segment.start);
                extractGraphEntitiesFromField(segment.relationship);
                extractGraphEntitiesFromField(segment.end);
            });
        }
    }

    // Function to manually compute curvatures for dense node pairs.
    function getCurvature(index, total) {
        if (total <= 6) {
            // Precomputed edge curvatures for nodes with multiple edges in between.
            const curvatures = {
                0: 0,
                1: 0,
                2: [-0.5, 0.5],  // 2 = Math.floor(1/2) + 1
                3: [-0.5, 0, 0.5], // 2 = Math.floor(3/2) + 1
                4: [-0.66666, -0.33333, 0.33333, 0.66666], // 3 = Math.floor(4/2) + 1
                5: [-0.66666, -0.33333, 0, 0.33333, 0.66666], // 3 = Math.floor(5/2) + 1
                6: [-0.75, -0.5, -0.25, 0.25, 0.5, 0.75], // 4 = Math.floor(6/2) + 1
                7: [-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75], // 4 = Math.floor(7/2) + 1
            }
            return curvatures[total][index];
        }
        const arr1 = [...Array(Math.floor(total / 2)).keys()].map(i => {
            return (i + 1) / (Math.floor(total / 2) + 1)
        })
        const arr2 = (total % 2 == 1) ? [0] : [];
        const arr3 = [...Array(Math.floor(total / 2)).keys()].map(i => {
            return (i + 1) / -(Math.floor(total / 2) + 1)
        })
        return arr1.concat(arr2).concat(arr3)[index];
    }

    function buildVisualizationDictionaryFromRecords(records) {
        // Extract graph objects from result set.
        records.forEach((record, rownumber) => {
            record._fields.forEach((field, i) => {
                extractGraphEntitiesFromField(field);
            })
        });
        // Assign proper curvatures to relationships.
        // This is needed for pairs of nodes that have multiple relationships between them, or self-loops.
        const linksList = Object.values(links).map(nodePair => {
            return nodePair.map((link, i) => {
                if (link.source == link.target) {
                    // Self-loop
                    return update(link, { curvature: 0.4 + (i) / 8 });
                } else {
                    // If we also have edges from the target to the source, adjust curvatures accordingly.
                    const mirroredNodePair = links[link.target + "," + link.source];
                    if (!mirroredNodePair) {
                        return update(link, { curvature: getCurvature(i, nodePair.length) });
                    } else {
                        return update(link, {
                            curvature: (link.source > link.target ? 1 : -1) *
                                getCurvature(link.source > link.target ? i : i + mirroredNodePair.length,
                                    nodePair.length + mirroredNodePair.length)
                        });
                    }
                }
            });
        });

        // Assign proper colors to nodes.
        const totalColors = categoricalColorSchemes[nodeColorScheme] ? categoricalColorSchemes[nodeColorScheme].length : 0;
        const nodeLabelsList = Object.keys(nodeLabels);
        const nodesList = Object.values(nodes).map(node => {
            // First try to assign a node a color if it has a property specifying the color.
            var assignedColor = node.properties[nodeColorProp] ? node.properties[nodeColorProp] :
                (totalColors > 0 ? categoricalColorSchemes[nodeColorScheme][nodeLabelsList.indexOf(node.lastLabel) % totalColors] : "grey");
            // Next, evaluate the custom styling rules to see if there's a rule-based override
            assignedColor = evaluateRulesOnNode(node, 'node color', assignedColor, styleRules);
            return update(node, { color: assignedColor ? assignedColor : defaultNodeColor });
        });

        // Set the data dictionary that is read by the visualization.
        setData({
            nodes: nodesList,
            links: linksList.flat()
        });
    }

    // Replaces all global dashboard parameters inside a string with their values.
    function replaceDashboardParameters(str) {
        Object.keys(parameters).forEach(key => {
            str = str.replaceAll("$"+key, parameters[key]);
        });
        return str;
    }


    // Generates tooltips when hovering on nodes/relationships.
    const generateTooltip = (value) => {
        const tooltip = <Card>

            <b style={{ padding: "10px" }}>
                {value.labels ? (value.labels.length > 0 ? value.labels.join(", ") : "Node") : value.type}
            </b>

            {Object.keys(value.properties).length == 0 ?
                <i><br />(No properties)</i> :
                <TableContainer>
                    <Table size="small">
                        <TableBody>
                            {Object.keys(value.properties).sort().map((key) => (
                                <TableRow key={key}>
                                    <TableCell component="th" scope="row" style={{ padding: "3px", paddingLeft: "8px" }}>
                                        {key}
                                    </TableCell>
                                    <TableCell align={"left"} style={{ padding: "3px", paddingLeft: "8px" }}>
                                        {(value.properties[key].toString().length <= 30) ?
                                            value.properties[key].toString() :
                                            value.properties[key].toString().substring(0, 40) + "..."}
                                    </TableCell>
                                </TableRow>
                            ))}
                        </TableBody>
                    </Table>
                </TableContainer>}
        </Card>;
        return ReactDOMServer.renderToString(tooltip);
    }

    const renderNodeLabel = (node) => {
        const selectedProp = props.selection && props.selection[node.lastLabel];
        if (selectedProp == "(id)") {
            return node.id;
        }
        if (selectedProp == "(label)") {
            return node.labels;
        }
        if (selectedProp == "(no label)") {
            return "";
        }
        return node.properties[selectedProp] ? node.properties[selectedProp] : "";
    }

    // TODO - implement this.
    const handleExpand = useCallback(node => {
        if (rightClickToExpandNodes) {
            props.queryCallback && props.queryCallback("MATCH (n)-[e]-(m) WHERE id(n) =" + node.id + " RETURN e,m", {}, setExtraRecords);
        }
    }, []);

    const showPopup = useCallback(item => {
        if (showPropertiesOnClick) {
            setInspectItem(item);
            handleOpen();
        }
    }, []);

    const showPopup2 = useCallback(item => {
        if (showPropertiesOnClick) {
            setInspectItem(item);
            handleOpen();
        }
    }, []);

    // If the set of extra records gets updated (e.g. on relationship expand), rebuild the graph.
    useEffect(() => {
        buildVisualizationDictionaryFromRecords(props.records.concat(extraRecords));
    }, [extraRecords])

    const { useRef } = React;

    // Return the actual graph visualization component with the parsed data and selected customizations.
    const fgRef = useRef();
    return <>
        <div ref={observe} style={{ paddingLeft: "10px", position: "relative", overflow: "hidden", width: "100%", height: "100%" }}>
            <Tooltip title="Fit graph to view." aria-label="">
                <SettingsOverscanIcon onClick={(e) => {
                    fgRef.current.zoomToFit(400)
                }} style={{ fontSize: "1.3rem", opacity: 0.6, bottom: 11, right: 34, position: "absolute", zIndex: 5 }} color="disabled" fontSize="small"></SettingsOverscanIcon>
            </Tooltip>
            {lockable ? (frozen ?
                <Tooltip title="Toggle dynamic graph layout." aria-label="">
                    <LockIcon onClick={(e) => {
                        setFrozen(false);
                        if (props.settings) {
                            props.settings.frozen = false;
                        }
                    }} style={{ fontSize: "1.3rem", opacity: 0.6, bottom: 12, right: 12, position: "absolute", zIndex: 5 }} color="disabled" fontSize="small"></LockIcon>
                </Tooltip>
                :
                <Tooltip title="Toggle fixed graph layout." aria-label="">
                    <LockOpenIcon onClick={(e) => {
                        if (nodePositions == undefined) {
                            nodePositions = {};
                        }
                        setFrozen(true);
                        if (props.settings) {
                            props.settings.frozen = true;
                        }
                    }} style={{ fontSize: "1.3rem", opacity: 0.6, bottom: 12, right: 12, position: "absolute", zIndex: 5 }} color="disabled" fontSize="small"></LockOpenIcon>
                </Tooltip>
            ) : <></>}
            {drilldownLink !== "" ?
            <a href={replaceDashboardParameters(drilldownLink)} target="_blank">
            <Fab style={{ position: "absolute", backgroundColor: "steelblue", right: "15px", zIndex: 50, top: "5px" }} color="primary" size="small" aria-label="search">
                <Tooltip title="Investigate" aria-label="">
                    <SearchIcon />
                </Tooltip>
            </Fab>
            </a> : <></>}

            <ForceGraph2D
                ref={fgRef}
                width={width ? width - 10 : 0}
                height={height ? height - 10 : 0}
                linkCurvature="curvature"
                backgroundColor={backgroundColor}
                linkDirectionalArrowLength={3}
                linkDirectionalArrowRelPos={1}
                dagMode={layouts[layout]}
                linkWidth={link => link.width}
                linkLabel={link => showPropertiesOnHover ? `<div>${generateTooltip(link)}</div>` : ""}
                nodeLabel={node => showPropertiesOnHover ? `<div>${generateTooltip(node)}</div>` : ""}
                nodeVal={node => node.size}
                onNodeClick={showPopup}
                // nodeThreeObject = {nodeTree}
                onLinkClick={showPopup}
                onNodeRightClick={handleExpand}
                linkDirectionalParticles={linkDirectionalParticles}
                linkDirectionalParticleSpeed={d => linkDirectionalParticleSpeed}
                cooldownTicks={100}
                onEngineStop={() => {
                    if (firstRun) {
                        fgRef.current.zoomToFit(400);
                        setFirstRun(false);
                    }
                }}
                onNodeDragEnd={node => {
                    if (fixNodeAfterDrag) {
                        node.fx = node.x;
                        node.fy = node.y;
                    }
                    if (frozen) {
                        if (nodePositions == undefined) {
                            nodePositions = {};
                        }
                        nodePositions["" + node.id] = [node.x, node.y];
                    }
                }}
                nodeCanvasObjectMode={() => "after"}
                nodeCanvasObject={(node, ctx, globalScale) => {
                    if (iconObject && iconObject[node.lastLabel])
                        drawDataURIOnCanvas(node, iconObject[node.lastLabel],ctx, defaultNodeSize);
                    else {
                        const label = (props.selection && props.selection[node.lastLabel]) ? renderNodeLabel(node) : "";
                        const fontSize = nodeLabelFontSize;
                        ctx.font = `${fontSize}px Sans-Serif`;
                        ctx.fillStyle = evaluateRulesOnNode(node, "node label color", nodeLabelColor, styleRules);
                        ctx.textAlign = "center";
                        ctx.fillText(label, node.x, node.y + 1);
                        if (frozen && !node.fx && !node.fy && nodePositions) {
                            node.fx = node.x;
                            node.fy = node.y;
                            nodePositions["" + node.id] = [node.x, node.y];
                        }
                        if (!frozen && node.fx && node.fy && nodePositions && nodePositions[node.id]) {
                            nodePositions[node.id] = undefined;
                            node.fx = undefined;
                            node.fy = undefined;
                        }
                    }
                }}
                linkCanvasObjectMode={() => "after"}
                linkCanvasObject={(link, ctx, globalScale) => {
                    const label = link.properties.name || link.type || link.id;
                    const fontSize = relLabelFontSize;
                    ctx.font = `${fontSize}px Sans-Serif`;
                    ctx.fillStyle = relLabelColor;
                    if (link.target != link.source) {
                        const lenX = (link.target.x - link.source.x);
                        const lenY = (link.target.y - link.source.y);
                        const posX = link.target.x - lenX / 2;
                        const posY = link.target.y - lenY / 2;
                        const length = Math.sqrt(lenX * lenX + lenY * lenY)
                        const angle = Math.atan(lenY / lenX)
                        ctx.save();
                        ctx.translate(posX, posY);
                        ctx.rotate(angle);
                        // Mirrors the curvatures when the label is upside down.
                        const mirror = (link.source.x > link.target.x) ? 1 : -1;
                        ctx.textAlign = "center";
                        if (link.curvature) {
                            ctx.fillText(label, 0, mirror * length * link.curvature * 0.5);
                        } else {
                            ctx.fillText(label, 0, 0);
                        }
                        ctx.restore();
                    } else {
                        ctx.save();
                        ctx.translate(link.source.x, link.source.y);
                        ctx.rotate(Math.PI * selfLoopRotationDegrees / 180);
                        ctx.textAlign = "center";
                        ctx.fillText(label, 0, -18.7 + -37.1 * (link.curvature - 0.5));
                        ctx.restore();
                    }
                }}
                graphData={width ? data : { nodes: [], links: [] }}
            />

            <NeoGraphItemInspectModal open={open} handleClose={handleClose} title={(inspectItem.labels && inspectItem.labels.join(", ")) || inspectItem.type} object={inspectItem.properties}></NeoGraphItemInspectModal>
        </div>
    </>
}
Example #14
Source File: CustomReportStyleModal.tsx    From neodash with Apache License 2.0 4 votes vote down vote up
NeoCustomReportStyleModal = ({ customReportStyleModalOpen, settingName, settingValue, type, fields, setCustomReportStyleModalOpen, onReportSettingUpdate }) => {

    // The rule set defined in this modal is updated whenever the setting value is externally changed.
    const [rules, setRules] = React.useState([]);
    useEffect(() => {
        if (settingValue) {
            setRules(settingValue);
        }
    }, [settingValue])

    const handleClose = () => {
        // If no rules are specified, clear the special report setting that holds the customization rules.
        if (rules.length == 0) {
            onReportSettingUpdate(settingName, undefined);
        } else {
            onReportSettingUpdate(settingName, rules);
        }
        setCustomReportStyleModalOpen(false);
    };


    // Update a single field in one of the rules in the rule array.
    const updateRuleField = (ruleIndex, ruleField, ruleFieldValue) => {
        var newRules = [...rules];  // Deep copy
        newRules[ruleIndex][ruleField] = ruleFieldValue;
        setRules(newRules);
    }

    /**
     * Create the list of suggestions used in the autocomplete box of the rule specification window.
     * This will be dynamic based on the type of report we are customizing.
     */
    const createFieldVariableSuggestions = () => {
        if (!fields) {
            return [];
        }
        if (type == "graph" || type == "map") {
            return fields.map((node, index) => {
                if (!Array.isArray(node)) {
                    return undefined;
                }
                return fields[index].map((property, propertyIndex) => {
                    if (propertyIndex == 0) {
                        return undefined;
                    }
                    return fields[index][0] + "." + property;
                })
            }).flat().filter(e => e !== undefined);
        }
        if (type == "bar" || type == "line" || type == "pie" || type == "table" || type == "value") {
            return fields;
        }
        return [];
    }


    return (
        <div>
            {customReportStyleModalOpen ?
                <Dialog maxWidth={"xl"} open={customReportStyleModalOpen == true}
                    PaperProps={{
                        style: {
                            overflow: 'inherit'
                        },
                    }}
                    style={{ overflow: "inherit", overflowY: "inherit" }}
                    aria-labelledby="form-dialog-title">
                    <DialogTitle id="form-dialog-title">
                        <TuneIcon style={{
                            height: "30px",
                            paddingTop: "4px",
                            marginBottom: "-8px",
                            marginRight: "5px",
                            paddingBottom: "5px"
                        }} />
                        Rule-Based Styling
                        <IconButton onClick={handleClose} style={{ padding: "3px", float: "right" }}>
                            <Badge badgeContent={""} >
                                <CloseIcon />
                            </Badge>
                        </IconButton>
                    </DialogTitle>
                    <div>
                        <DialogContent style={{ overflow: "inherit" }}>
                            <p>You can define rule-based styling for the report here. <br />
                                Style rules are checked in-order and override the default behaviour - if no rules are valid, no style is applied.<br />
                                {(type == "graph" || type == "map") ? <p>For <b>{type}</b> reports, the field name should be specified in the format <code>label.name</code>, for example: <code>Person.age</code>. This is case-sensentive.</p> : <></>}
                                {(type == "line" || type == "value" || type == "bar" || type == "pie" || type == "table") ? <p>For <b>{type}</b> reports, the field name should be the exact name of the returned field. <br />For example, if your query is <code>MATCH (n:Movie) RETURN n.rating as Rating</code>, your field name is <code>Rating</code>.</p> : <></>}
                            </p>
                            <div>

                                <hr></hr>

                                <table>
                                    {rules.map((rule, index) => {
                                        return <>
                                            <tr>

                                       
                                            <td style={{ paddingLeft: "2px", paddingRight: "2px" }}><span style={{ color: "black", width: "50px" }}>{index+1}.</span></td>
                                                <td style={{ paddingLeft: "20px", paddingRight: "20px" }}><span style={{ fontWeight: "bold", color: "black", width: "50px" }}> IF</span></td>
                                                <div style={{ border: "2px dashed grey" }}>
                                                    <td style={{ paddingLeft: "5px", paddingRight: "5px", paddingTop: "5px", paddingBottom: "5px" }}>
                                                        <Autocomplete
                                                            disableClearable={true}
                                                            id="autocomplete-label-type"
                                                            noOptionsText="*Specify an exact field name"
                                                            options={createFieldVariableSuggestions().filter(e => e.toLowerCase().includes(rule['field'].toLowerCase()))}
                                                            value={rule['field'] ? rule['field'] : ""}
                                                            inputValue={rule['field'] ? rule['field'] : ""}
                                                            popupIcon={<></>}
                                                            style={{ display: "inline-block", width: 185, marginLeft: "5px", marginTop: "5px" }}
                                                            onInputChange={(event, value) => {
                                                                updateRuleField(index, 'field', value)
                                                            }}
                                                            onChange={(event, newValue) => {
                                                                updateRuleField(index, 'field', newValue)
                                                            }}
                                                            renderInput={(params) => <TextField {...params} placeholder="Field name..." InputLabelProps={{ shrink: true }} />}
                                                        />
                                                    </td>
                                                    <td style={{ paddingLeft: "5px", paddingRight: "5px" }}>
                                                        <TextField select value={rule['condition']}
                                                            onChange={(e) => updateRuleField(index, 'condition', e.target.value)}>
                                                            {RULE_CONDITIONS.map((option) => (
                                                                <MenuItem key={option.value} value={option.value}>
                                                                    {option.label}
                                                                </MenuItem>
                                                            ))}
                                                        </TextField></td>
                                                    <td style={{ paddingLeft: "5px", paddingRight: "5px" }}><TextField placeholder="Value..." value={rule['value']}
                                                        onChange={(e) => updateRuleField(index, 'value', e.target.value)}></TextField></td>
                                                </div>
                                                <td style={{ paddingLeft: "20px", paddingRight: "20px" }}><span style={{ fontWeight: "bold", color: "black", width: "50px" }}>THEN</span></td>
                                                <div style={{ border: "2px dashed grey", marginBottom: "5px" }}>
                                                    <td style={{ paddingLeft: "5px", paddingRight: "5px", paddingTop: "5px", paddingBottom: "5px" }}>
                                                        <TextField select value={rule['customization']}
                                                            onChange={(e) => updateRuleField(index, 'customization', e.target.value)}>
                                                            {RULE_BASED_REPORT_CUSTOMIZATIONS[type] && RULE_BASED_REPORT_CUSTOMIZATIONS[type].map((option) => (
                                                                <MenuItem key={option.value} value={option.value}>
                                                                    {option.label}
                                                                </MenuItem>
                                                            ))}
                                                        </TextField></td>
                                                    <td style={{ paddingLeft: "5px", paddingRight: "5px", paddingTop: "5px", paddingBottom: "5px" }}>
                                                        <TextField style={{ width: "20px", color: "black" }} disabled={true} value={'='}></TextField>
                                                    </td>
                                                    <td style={{ paddingLeft: "5px", paddingRight: "5px" }}><NeoColorPicker label="" defaultValue="black" key={undefined} style={undefined} value={rule['customizationValue']} onChange={(value) => updateRuleField(index, 'customizationValue', value)} ></NeoColorPicker></td>
                                                </div>
                                                <td>
                                                    <Fab size="small" aria-label="add" style={{ background: "black", color: "white", marginTop: "-6px", marginLeft: "20px" }}
                                                        onClick={() => {
                                                            setRules([...rules.slice(0, index), ...rules.slice(index + 1)])
                                                        }} >
                                                        <CloseIcon />
                                                    </Fab>
                                                </td>
                                                <hr />
                                            </tr></>
                                    })}

                                    <tr >
                                        <td style={{ borderBottom: "1px solid grey", width: "750px" }} colSpan={5}>
                                            <Typography variant="h3" color="primary" style={{ textAlign: "center", marginBottom: "5px" }}>
                                                <Fab size="small" aria-label="add" style={{ background: "white", color: "black" }}
                                                    onClick={() => {
                                                        const newRule = getDefaultRule(RULE_BASED_REPORT_CUSTOMIZATIONS[type][0]['value']);
                                                        setRules(rules.concat(newRule));
                                                    }} >
                                                    <AddIcon />
                                                </Fab>
                                            </Typography>

                                        </td>
                                    </tr>
                                </table>

                            </div>

                            <Button
                                style={{ float: "right", marginTop: "20px", marginBottom: "20px", backgroundColor: "white" }}
                                color="default"
                                variant="contained"
                                size="large"
                                onClick={(e) => {
                                    handleClose();
                                }}>
                                Save</Button>
                        </DialogContent>
                    </div>
                </Dialog> : <></>}
        </div>
    );
}
Example #15
Source File: index.tsx    From react-app-architecture with Apache License 2.0 4 votes vote down vote up
export default function Header(): ReactElement {
  const classes = useStyles();
  const history = useHistory();
  const { isLoggedIn, data: authData } = useStateSelector(({ authState }) => authState);
  const user = authData?.user;

  const isWriter = checkRole(user, Roles.WRITER);
  const isEditor = checkRole(user, Roles.EDITOR);

  const [openAuthDialog, setOpenAuthDialog] = useState(false);
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [popupMoreAnchorEl, setPopupMoreAnchorEl] = useState<HTMLElement | null>(null);
  const isPopupMenuOpen = Boolean(popupMoreAnchorEl);
  const dispatch = useDispatch();

  function handlePopupMenuClose() {
    setPopupMoreAnchorEl(null);
  }

  function handlePopupMenuOpen(event: MouseEvent<HTMLElement>) {
    setPopupMoreAnchorEl(event.currentTarget);
  }

  function toggleDrawer() {
    setDrawerOpen(!drawerOpen);
  }

  const renderProfileView = (onClick: (event: MouseEvent<HTMLButtonElement>) => void) => {
    if (!user) return null;
    return (
      <CardActionArea onClick={onClick}>
        {user.profilePicUrl ? (
          <CardHeader
            title={user.name.split(' ')[0]}
            avatar={
              <Avatar className={classes.avatar} aria-label={user.name} src={user.profilePicUrl} />
            }
          />
        ) : (
          <CardHeader title={user.name.split(' ')[0]} avatar={<FirstLetter text={user.name} />} />
        )}
      </CardActionArea>
    );
  };

  const popupMenuId = 'menu-popup';
  const popupMenu = (
    <Menu
      anchorEl={popupMoreAnchorEl}
      anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
      id={popupMenuId}
      keepMounted
      transformOrigin={{ vertical: 'top', horizontal: 'right' }}
      open={isPopupMenuOpen}
      onClose={handlePopupMenuClose}
      PopoverClasses={{ paper: classes.paper }}
    >
      {isLoggedIn && renderProfileView(handlePopupMenuClose)}
      {isWriter && (
        <MenuItem
          className={classes.menuItem}
          onClick={() => {
            history.push('/write/blog');
            handlePopupMenuClose();
          }}
        >
          <IconButton color="inherit">
            <CreateIcon />
          </IconButton>
          <p>Write Blog</p>
        </MenuItem>
      )}
      {isWriter && (
        <MenuItem
          className={classes.menuItem}
          onClick={() => {
            history.push('/writer/blogs');
            handlePopupMenuClose();
          }}
        >
          <IconButton color="inherit">
            <ListIcon />
          </IconButton>
          <p>My Blogs</p>
        </MenuItem>
      )}
      {isEditor && (
        <MenuItem
          className={classes.menuItem}
          onClick={() => {
            history.push('/editor/blogs');
            handlePopupMenuClose();
          }}
        >
          <IconButton color="inherit">
            <SupervisorAccountIcon />
          </IconButton>
          <p>Blogs Admin</p>
        </MenuItem>
      )}
      {isLoggedIn && (
        <MenuItem
          className={classes.menuItem}
          onClick={() => {
            dispatch(logout());
            handlePopupMenuClose();
          }}
        >
          <IconButton color="inherit">
            <SvgIcon>
              <path d={mdiLogout} />
            </SvgIcon>
          </IconButton>
          <p>Logout</p>
        </MenuItem>
      )}
    </Menu>
  );

  const mobileDrawerMenu = (
    <Drawer anchor="top" open={drawerOpen} onClose={toggleDrawer}>
      {isLoggedIn && renderProfileView(toggleDrawer)}
      <List component="nav">
        {[
          {
            title: 'About Project',
            href: 'https://github.com/afteracademy/react-app-architecture',
            icon: <InfoIcon />,
          },
          {
            title: 'Contact',
            href: 'https://github.com/afteracademy/react-app-architecture/issues',
            icon: <EmailIcon />,
          },
        ].map(({ title, href, icon }, position) => (
          <ListItem
            key={position}
            className={classes.drawerItem}
            button
            href={href}
            target="_blank"
            onClick={toggleDrawer}
            component="a"
          >
            <ListItemIcon className={classes.drawerIcon}>{icon}</ListItemIcon>
            <ListItemText primary={title} />
          </ListItem>
        ))}
        {[{ title: 'Blogs', link: '/blogs', icon: <WebIcon /> }].map(
          ({ title, link, icon }, position) => (
            <ListItem
              key={position}
              className={classes.drawerItem}
              button
              component={Link}
              to={link}
              onClick={toggleDrawer}
            >
              <ListItemIcon className={classes.drawerIcon}>{icon}</ListItemIcon>
              <ListItemText primary={title} />
            </ListItem>
          ),
        )}
        {isWriter && <Divider />}
        {isWriter &&
          [
            { title: 'Write Blog', link: '/write/blog', icon: <CreateIcon /> },
            { title: 'My Blogs', link: '/writer/blogs', icon: <WebIcon /> },
          ].map(({ title, link, icon }, position) => (
            <ListItem
              key={position}
              className={classes.drawerItem}
              button
              component={Link}
              to={link}
              onClick={toggleDrawer}
            >
              <ListItemIcon className={classes.drawerIcon}>{icon}</ListItemIcon>
              <ListItemText primary={title} />
            </ListItem>
          ))}
        <Divider />
        {isEditor && <Divider />}
        {isEditor &&
          [{ title: 'Blog Admin', link: '/editor/blogs', icon: <SupervisorAccountIcon /> }].map(
            ({ title, link, icon }, position) => (
              <ListItem
                key={position}
                className={classes.drawerItem}
                button
                component={Link}
                to={link}
                onClick={toggleDrawer}
              >
                <ListItemIcon className={classes.drawerIcon}>{icon}</ListItemIcon>
                <ListItemText primary={title} />
              </ListItem>
            ),
          )}
        {isLoggedIn && (
          <ListItem
            className={classes.drawerItem}
            onClick={() => {
              dispatch(logout());
              toggleDrawer();
            }}
            button
          >
            <ListItemIcon className={classes.drawerIcon}>
              <SvgIcon>
                <path d={mdiLogout} />
              </SvgIcon>
            </ListItemIcon>
            <ListItemText primary="Logout" />
          </ListItem>
        )}
        {!isLoggedIn && (
          <ListItem
            className={classes.drawerItem}
            onClick={() => {
              setOpenAuthDialog(true);
              toggleDrawer();
            }}
            button
          >
            <ListItemIcon className={classes.drawerIcon}>
              <SvgIcon>
                <path d={mdiLogin} />
              </SvgIcon>
            </ListItemIcon>
            <ListItemText primary="Login" />
          </ListItem>
        )}
      </List>
      <div className={classes.drawerCloseButtonContainer}>
        <IconButton className={classes.drawerCloseButton} onClick={toggleDrawer}>
          <CloseIcon />
        </IconButton>
      </div>
    </Drawer>
  );

  return (
    <div className={classes.root}>
      <AppBar position="fixed" color="secondary" className={classes.appbar}>
        <Toolbar>
          <Avatar
            alt="Logo"
            src={afterAcademyLogo}
            className={classes.logo}
            component={Link}
            to={'/'}
          />
          <Typography variant="h6" className={classes.brandName}>
            AfterAcademy React
          </Typography>
          <div className={classes.sectionDesktop}>
            {[
              {
                title: 'About Project',
                href: 'https://github.com/afteracademy/react-app-architecture',
              },
              {
                title: 'Contact',
                href: 'https://github.com/afteracademy/react-app-architecture/issues',
              },
            ].map(({ title, href }, position) => (
              <Button
                key={position}
                color="inherit"
                className={classes.button}
                href={href}
                target="_blank"
              >
                {title}
              </Button>
            ))}
            {[{ title: 'Blogs', link: '/blogs' }].map(({ title, link }, position) => (
              <Button
                key={position}
                color="inherit"
                className={classes.button}
                component={Link}
                to={link}
              >
                {title}
              </Button>
            ))}
            {user?.profilePicUrl ? (
              <Avatar alt={user.name} src={user.profilePicUrl} className={classes.avatar} />
            ) : (
              user?.name && <FirstLetter text={user.name} />
            )}
            {isLoggedIn ? (
              <IconButton
                aria-label="show more"
                aria-haspopup="true"
                onClick={handlePopupMenuOpen}
                color="primary"
              >
                <MenuIcon />
              </IconButton>
            ) : (
              <Fab
                variant="extended"
                size="medium"
                color="primary"
                aria-label="login"
                className={classes.loginButton}
                onClick={() => setOpenAuthDialog(true)}
              >
                Login
              </Fab>
            )}
          </div>
          <div className={classes.sectionMobile}>
            <IconButton
              aria-label="show more"
              aria-haspopup="true"
              color="inherit"
              onClick={toggleDrawer}
            >
              <MenuIcon />
            </IconButton>
          </div>
        </Toolbar>
      </AppBar>
      {popupMenu}
      {mobileDrawerMenu}
      <AuthDialog open={openAuthDialog} onClose={() => setOpenAuthDialog(false)} />
    </div>
  );
}
Example #16
Source File: ProjectListPage.tsx    From frontend with Apache License 2.0 4 votes vote down vote up
ProjectsListPage = () => {
  const { enqueueSnackbar } = useSnackbar();
  const projectState = useProjectState();
  const projectDispatch = useProjectDispatch();
  const helpDispatch = useHelpDispatch();

  const [createDialogOpen, setCreateDialogOpen] = React.useState(false);
  const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false);
  const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);

  useEffect(() => {
    setHelpSteps(helpDispatch, PROJECT_LIST_PAGE_STEPS);
  });

  const toggleCreateDialogOpen = () => {
    setCreateDialogOpen(!createDialogOpen);
  };

  const toggleUpdateDialogOpen = () => {
    setUpdateDialogOpen(!updateDialogOpen);
  };

  const toggleDeleteDialogOpen = () => {
    setDeleteDialogOpen(!deleteDialogOpen);
  };

  return (
    <Box mt={2}>
      <Grid container spacing={2}>
        <Grid item xs={4}>
          <Box
            height="100%"
            alignItems="center"
            justifyContent="center"
            display="flex"
          >
            <Fab
              color="primary"
              aria-label="add"
              onClick={() => {
                toggleCreateDialogOpen();
                setProjectEditState(projectDispatch);
              }}
            >
              <Add />
            </Fab>
          </Box>

          <BaseModal
            open={createDialogOpen}
            title={"Create Project"}
            submitButtonText={"Create"}
            onCancel={toggleCreateDialogOpen}
            content={<ProjectForm />}
            onSubmit={() =>
              createProject(projectDispatch, projectState.projectEditState)
                .then((project) => {
                  toggleCreateDialogOpen();
                  enqueueSnackbar(`${project.name} created`, {
                    variant: "success",
                  });
                })
                .catch((err) =>
                  enqueueSnackbar(err, {
                    variant: "error",
                  })
                )
            }
          />

          <BaseModal
            open={updateDialogOpen}
            title={"Update Project"}
            submitButtonText={"Update"}
            onCancel={toggleUpdateDialogOpen}
            content={<ProjectForm />}
            onSubmit={() =>
              updateProject(projectDispatch, projectState.projectEditState)
                .then((project) => {
                  toggleUpdateDialogOpen();
                  enqueueSnackbar(`${project.name} updated`, {
                    variant: "success",
                  });
                })
                .catch((err) =>
                  enqueueSnackbar(err, {
                    variant: "error",
                  })
                )
            }
          />

          <BaseModal
            open={deleteDialogOpen}
            title={"Delete Project"}
            submitButtonText={"Delete"}
            onCancel={toggleDeleteDialogOpen}
            content={
              <Typography>{`Are you sure you want to delete: ${projectState.projectEditState.name}?`}</Typography>
            }
            onSubmit={() =>
              deleteProject(projectDispatch, projectState.projectEditState.id)
                .then((project) => {
                  toggleDeleteDialogOpen();
                  enqueueSnackbar(`${project.name} deleted`, {
                    variant: "success",
                  });
                })
                .catch((err) =>
                  enqueueSnackbar(err, {
                    variant: "error",
                  })
                )
            }
          />
        </Grid>
        {projectState.projectList.map((project) => (
          <Grid item xs={4} key={project.id}>
            <Card id={LOCATOR_PROJECT_LIST_PAGE_PROJECT_LIST}>
              <CardContent>
                <Typography>Id: {project.id}</Typography>
                <Typography>Name: {project.name}</Typography>
                <Typography>Main branch: {project.mainBranchName}</Typography>
                <Typography>
                  Created: {formatDateTime(project.createdAt)}
                </Typography>
              </CardContent>
              <CardActions>
                <Button color="primary" href={project.id}>
                  Builds
                </Button>
                <Button
                  color="primary"
                  href={`${routes.VARIATION_LIST_PAGE}/${project.id}`}
                >
                  Variations
                </Button>
                <IconButton
                  onClick={(event: React.MouseEvent<HTMLElement>) => {
                    toggleUpdateDialogOpen();
                    setProjectEditState(projectDispatch, project);
                  }}
                >
                  <Edit />
                </IconButton>
                <IconButton
                  onClick={(event: React.MouseEvent<HTMLElement>) => {
                    toggleDeleteDialogOpen();
                    setProjectEditState(projectDispatch, project);
                  }}
                >
                  <Delete />
                </IconButton>
              </CardActions>
            </Card>
          </Grid>
        ))}
      </Grid>
    </Box>
  );
}
Example #17
Source File: MemberDialog.tsx    From knboard with MIT License 4 votes vote down vote up
MemberDialog = ({ board }: Props) => {
  const theme = useTheme();
  const dispatch = useDispatch();
  const memberId = useSelector((state: RootState) => state.member.dialogMember);
  const members = useSelector(selectMembersEntities);
  const boardOwner = useSelector((state: RootState) =>
    currentBoardOwner(state)
  );
  const xsDown = useMediaQuery(theme.breakpoints.down("xs"));
  const [confirmDelete, setConfirmDelete] = useState(false);
  const member = memberId === null ? null : members[memberId];
  const memberIsOwner = member?.id === board.owner;
  const open = member !== null;

  if (!member) {
    return null;
  }

  const handleClose = () => {
    dispatch(setDialogMember(null));
    setConfirmDelete(false);
  };

  const handleRemoveMember = async () => {
    try {
      const response = await api.post(
        `${API_BOARDS}${board.id}/remove_member/`,
        { username: member.username }
      );
      const removedMember = response.data as BoardMember;
      dispatch(removeBoardMember(removedMember.id));
      dispatch(createSuccessToast(`Removed ${removedMember.username}`));
      handleClose();
    } catch (err) {
      dispatch(createErrorToast(err.toString()));
    }
  };

  return (
    <Dialog
      open={open}
      onClose={handleClose}
      maxWidth="xs"
      fullWidth
      fullScreen={xsDown}
    >
      <Close onClose={handleClose} />
      <DialogTitle id="member-detail">Member</DialogTitle>
      <Container theme={theme}>
        {confirmDelete ? (
          <div>
            <Alert
              severity="error"
              css={css`
                margin-bottom: 2rem;
              `}
            >
              Are you sure you want to remove this member? This member will be
              removed from all cards.
            </Alert>
            <ConfirmAction>
              <Fab
                size="small"
                onClick={() => setConfirmDelete(false)}
                css={css`
                  box-shadow: none;
                  &.MuiFab-sizeSmall {
                    width: 32px;
                    height: 32px;
                  }
                `}
              >
                <FontAwesomeIcon icon={faAngleLeft} color="#555" />
              </Fab>
              <Button
                size="small"
                color="secondary"
                variant="contained"
                onClick={handleRemoveMember}
                css={css`
                  font-size: 0.625rem;
                `}
              >
                Remove member
              </Button>
            </ConfirmAction>
          </div>
        ) : (
          <>
            <Avatar
              css={css`
                height: 6rem;
                width: 6rem;
                font-size: 36px;
                margin-bottom: 1rem;
              `}
              src={member?.avatar?.photo}
              alt={member?.avatar?.name}
            >
              {member.username.charAt(0)}
            </Avatar>
            <Main>
              <PrimaryText>
                {member.first_name} {member.last_name}
              </PrimaryText>
              <SecondaryText>
                username: <b>{member.username}</b>
              </SecondaryText>
              <SecondaryText
                css={css`
                  margin-bottom: 1.5rem;
                `}
              >
                email: <b>{member?.email || "-"}</b>
              </SecondaryText>
              {memberIsOwner && (
                <Alert severity="info">Owner of this board</Alert>
              )}
              {boardOwner && !memberIsOwner && (
                <Button
                  size="small"
                  css={css`
                    color: #333;
                    font-size: 0.625rem;
                  `}
                  variant="outlined"
                  onClick={() => setConfirmDelete(true)}
                >
                  Remove from board
                </Button>
              )}
            </Main>
          </>
        )}
      </Container>
    </Dialog>
  );
}
Example #18
Source File: AdrMenu.tsx    From log4brains with Apache License 2.0 4 votes vote down vote up
export function AdrMenu({ adrs, currentAdrSlug, className, ...props }: Props) {
  const classes = useStyles();

  const [newAdrOpen, setNewAdrOpen] = React.useState(false);
  const mode = React.useContext(Log4brainsModeContext);

  if (adrs === undefined) {
    return null; // Because inside a <Grow>
  }

  let lastDateString = "";

  return (
    <div className={clsx(className, classes.root)} {...props}>
      {mode === Log4brainsMode.preview && (
        <Dialog
          open={newAdrOpen}
          onClose={() => setNewAdrOpen(false)}
          aria-labelledby="newadr-dialog-title"
          aria-describedby="newadr-dialog-description"
        >
          <DialogTitle id="newadr-dialog-title">Create a new ADR</DialogTitle>
          <DialogContent>
            <DialogContentText id="newadr-dialog-description">
              <Typography>
                Run the following command in your terminal:
              </Typography>
              <pre>
                <code className="hljs bash">log4brains adr new</code>
              </pre>
              <Typography>
                This will create a new ADR from your template and will open it
                in your editor.
                <br />
                Just press <code>Ctrl+S</code> to watch your changes here,
                thanks to Hot Reload.
              </Typography>
              <Typography variant="body2" style={{ marginTop: 20 }}>
                Would you have preferred to create a new ADR directly from here?
                <br />
                Leave a ? on{" "}
                <MuiLink
                  href="https://github.com/thomvaill/log4brains/issues/9"
                  target="_blank"
                  rel="noopener"
                >
                  this GitHub issue
                </MuiLink>{" "}
                then!
              </Typography>
            </DialogContentText>
          </DialogContent>
          <DialogActions>
            <Button
              onClick={() => setNewAdrOpen(false)}
              color="primary"
              autoFocus
            >
              OK
            </Button>
          </DialogActions>
        </Dialog>
      )}

      <Timeline className={classes.timeline}>
        {mode === Log4brainsMode.preview && (
          <TimelineItem className={classes.timelineItem}>
            <TimelineOppositeContent
              classes={{ root: classes.timelineOppositeContentRootAdd }}
            />
            <TimelineSeparator>
              <Tooltip title="Create a new ADR">
                <Fab
                  size="small"
                  color="primary"
                  aria-label="create a new ADR"
                  className={classes.newAdrFab}
                  onClick={() => setNewAdrOpen(true)}
                >
                  <AddIcon />
                </Fab>
              </Tooltip>
              <TimelineConnector />
            </TimelineSeparator>
            <TimelineContent />
          </TimelineItem>
        )}

        {adrs.map((adr) => {
          const currentDateString = moment(
            adr.publicationDate || adr.creationDate
          ).format("MMMM|YYYY");
          const dateString =
            currentDateString === lastDateString ? "" : currentDateString;
          lastDateString = currentDateString;
          const [month, year] = dateString.split("|");

          return (
            <TimelineItem
              key={adr.slug}
              className={clsx(classes.timelineItem, {
                [classes.selectedTimelineItem]: currentAdrSlug === adr.slug
              })}
            >
              <TimelineOppositeContent
                classes={{ root: classes.timelineOppositeContentRoot }}
              >
                <Typography variant="body2" className={classes.date}>
                  {month}
                </Typography>
                <Typography variant="body2" className={classes.date}>
                  {year}
                </Typography>
              </TimelineOppositeContent>
              <TimelineSeparator>
                <TimelineDot className={classes.timelineConnector} />
                <TimelineConnector className={classes.timelineConnector} />
              </TimelineSeparator>
              <TimelineContent>
                <div className={classes.timelineContentContainer}>
                  <Link href={`/adr/${adr.slug}`} passHref>
                    <MuiLink
                      className={clsx(classes.adrLink, {
                        [classes[
                          `${adr.status}Link` as keyof typeof classes
                        ]]: true
                      })}
                      variant="body2"
                    >
                      <span className={classes.adrTitle}>
                        {adr.title || "Untitled"}
                      </span>
                      {adr.package ? (
                        <span className={classes.package}>
                          <CropFreeIcon
                            fontSize="inherit"
                            className={classes.icon}
                          />{" "}
                          {adr.package}
                        </span>
                      ) : null}
                    </MuiLink>
                  </Link>
                  <div>
                    <AdrStatusChip
                      status={adr.status}
                      className={classes.adrStatusChip}
                    />
                  </div>
                </div>
              </TimelineContent>
            </TimelineItem>
          );
        })}

        <TimelineItem>
          <TimelineOppositeContent
            classes={{ root: classes.timelineStartOppositeContentRoot }}
          />
          <TimelineSeparator>
            <TimelineConnector />
            <TimelineDot>
              <EmojiFlagsIcon />
            </TimelineDot>
          </TimelineSeparator>
          <TimelineContent />
        </TimelineItem>
      </Timeline>
    </div>
  );
}
Example #19
Source File: Home.tsx    From firetable with Apache License 2.0 4 votes vote down vote up
export default function HomePage() {
  const classes = useStyles();

  const [settingsDialogState, setSettingsDialogState] = useState<{
    mode: null | TableSettingsDialogModes;
    data: null | {
      collection: string;
      description: string;
      roles: string[];
      name: string;
      section: string;
      isCollectionGroup: boolean;
      tableType: string;
    };
  }>({
    mode: null,
    data: null,
  });

  const clearDialog = () =>
    setSettingsDialogState({
      mode: null,
      data: null,
    });

  useEffect(() => {
    const modal = decodeURIComponent(
      queryString.parse(window.location.search).modal as string
    );
    if (modal) {
      switch (modal) {
        case "settings":
          setOpenProjectSettings(true);
          break;
        default:
          break;
      }
    }
  }, [window.location.search]);
  const { sections } = useFiretableContext();
  const { userDoc } = useAppContext();

  const favs = userDoc.state.doc?.favoriteTables
    ? userDoc.state.doc.favoriteTables
    : [];

  const handleCreateTable = () =>
    setSettingsDialogState({
      mode: TableSettingsDialogModes.create,
      data: null,
    });
  const [open, setOpen] = useState(false);
  const [openProjectSettings, setOpenProjectSettings] = useState(false);
  const [openBuilderInstaller, setOpenBuilderInstaller] = useState(false);

  const [settingsDocState, settingsDocDispatch] = useDoc({
    path: "_FIRETABLE_/settings",
  });
  useEffect(() => {
    if (!settingsDocState.loading && !settingsDocState.doc) {
      settingsDocDispatch({
        action: DocActions.update,
        data: { createdAt: new Date() },
      });
    }
  }, [settingsDocState]);
  if (settingsDocState.error?.code === "permission-denied") {
    return (
      <EmptyState
        fullScreen
        message="Access Denied"
        description={
          <>
            <Typography variant="overline">
              You don't current have access to firetable, please contact this
              project's owner
            </Typography>
            <Typography variant="body2">
              If you are the project owner please follow the instructions{" "}
              <a
                href={WIKI_LINKS.securityRules}
                target="_blank"
                rel="noopener noreferrer"
              >
                here
              </a>{" "}
              to setup the project rules.
            </Typography>
          </>
        }
      />
    );
  }

  const TableCard = ({ table }) => {
    const checked = Boolean(_find(favs, table));
    return (
      <Grid key={table.name} item xs={12} sm={6} md={open ? 6 : 4}>
        <StyledCard
          className={classes.card}
          overline={table.section}
          title={table.name}
          headerAction={
            <Checkbox
              onClick={() => {
                userDoc.dispatch({
                  action: DocActions.update,
                  data: {
                    favoriteTables: checked
                      ? favs.filter((t) => t.collection !== table.collection)
                      : [...favs, table],
                  },
                });
              }}
              checked={checked}
              icon={<FavoriteBorder />}
              checkedIcon={<Favorite />}
              name="checkedH"
              className={classes.favButton}
            />
          }
          bodyContent={table.description}
          primaryLink={{
            to: `${
              table.isCollectionGroup ? routes.tableGroup : routes.table
            }/${table.collection.replace(/\//g, "~2F")}`,
            label: "Open",
          }}
          secondaryAction={
            <IconButton
              onClick={() =>
                setSettingsDialogState({
                  mode: TableSettingsDialogModes.update,
                  data: table,
                })
              }
              aria-label="Edit table"
              className={classes.editButton}
            >
              <EditIcon />
            </IconButton>
          }
        />
      </Grid>
    );
  };

  return (
    <HomeNavigation
      open={open}
      setOpen={setOpen}
      handleCreateTable={handleCreateTable}
    >
      <main className={classes.root}>
        <Container>
          {favs.length !== 0 && (
            <section id="favorites" className={classes.section}>
              <Typography
                variant="h6"
                component="h1"
                className={classes.sectionHeader}
              >
                Favorites
              </Typography>
              <Divider className={classes.divider} />
              <Grid
                container
                spacing={4}
                justify="flex-start"
                className={classes.cardGrid}
              >
                {favs.map((table) => (
                  <TableCard key={table.collection} table={table} />
                ))}
              </Grid>
            </section>
          )}

          {sections &&
            Object.keys(sections).map((sectionName) => (
              <section
                key={sectionName}
                id={sectionName}
                className={classes.section}
              >
                <Typography
                  variant="h6"
                  component="h1"
                  className={classes.sectionHeader}
                >
                  {sectionName === "undefined" ? "Other" : sectionName}
                </Typography>

                <Divider className={classes.divider} />

                <Grid
                  container
                  spacing={4}
                  justify="flex-start"
                  className={classes.cardGrid}
                >
                  {sections[sectionName].map((table, i) => (
                    <TableCard key={`${i}-${table.collection}`} table={table} />
                  ))}
                </Grid>
              </section>
            ))}

          <section className={classes.section}>
            <Tooltip title="Create a table">
              <Fab
                className={classes.fab}
                color="secondary"
                aria-label="Create table"
                onClick={handleCreateTable}
              >
                <AddIcon />
              </Fab>
            </Tooltip>
            <Tooltip title="Configure Firetable">
              <Fab
                className={classes.configFab}
                color="secondary"
                aria-label="Create table"
                onClick={() => setOpenProjectSettings(true)}
              >
                <SettingsIcon />
              </Fab>
            </Tooltip>
          </section>
        </Container>
      </main>

      <TableSettingsDialog
        clearDialog={clearDialog}
        mode={settingsDialogState.mode}
        data={settingsDialogState.data}
      />
      {openProjectSettings && (
        <ProjectSettings
          handleClose={() => setOpenProjectSettings(false)}
          handleOpenBuilderInstaller={() => setOpenBuilderInstaller(true)}
        />
      )}
      {openBuilderInstaller && (
        <BuilderInstaller handleClose={() => setOpenBuilderInstaller(false)} />
      )}
    </HomeNavigation>
  );
}
Example #20
Source File: ActionFab.tsx    From firetable with Apache License 2.0 4 votes vote down vote up
export default function ActionFab({
  row,
  column,
  onSubmit,
  value,
  disabled,
  ...props
}: IActionFabProps) {
  const { requestConfirmation } = useConfirmation();
  const { requestParams } = useActionParams();
  const { tableState } = useFiretableContext();
  const { createdAt, updatedAt, id, ref, ...docData } = row;
  const { config } = column as any;

  const action = !value
    ? "run"
    : value.undo
    ? "undo"
    : value.redo
    ? "redo"
    : "";
  const [isRunning, setIsRunning] = useState(false);
  const snack = useContext(SnackContext);

  const callableName: string =
    (column as any).callableName ?? config.callableName ?? "actionScript";
  const handleRun = (actionParams = null) => {
    setIsRunning(true);

    const data = {
      ref: { path: ref.path, id: ref.id, tablePath: window.location.pathname },
      column: { ...column, editor: undefined },
      action,
      schemaDocPath: formatPath(tableState?.tablePath ?? ""),
      actionParams,
    };
    cloudFunction(
      callableName,
      data,
      async (response) => {
        const { message, cellValue, success } = response.data;
        setIsRunning(false);
        snack.open({
          message: JSON.stringify(message),
          variant: success ? "success" : "error",
        });
        if (cellValue && cellValue.status) {
          await ref.update({
            [column.key]: cellValue,
          });
        }
      },
      (error) => {
        console.error("ERROR", callableName, error);
        setIsRunning(false);
        snack.open({ message: JSON.stringify(error), variant: "error" });
      }
    );
  };
  const hasRan = value && value.status;

  const actionState: "run" | "undo" | "redo" = hasRan
    ? value.undo
      ? "undo"
      : "redo"
    : "run";

  const needsParams = Array.isArray(config.params) && config.params.length > 0;
  const needsConfirmation =
    typeof config.confirmation === "string" && config.confirmation !== "";
  return (
    <Fab
      onClick={
        needsParams
          ? () =>
              requestParams({
                column,
                row,
                handleRun,
              })
          : needsConfirmation
          ? () =>
              requestConfirmation({
                title: `${column.name} Confirmation`,
                body: (actionState === "undo" && config.undoConfirmation
                  ? config.undoConfirmation
                  : config.confirmation
                ).replace(/\{\{(.*?)\}\}/g, replacer(row)),
                confirm: "Run",
                handleConfirm: () => handleRun(),
              })
          : () => handleRun()
      }
      disabled={
        isRunning ||
        !!(
          hasRan &&
          (config.redo?.enabled ? false : !value.redo) &&
          (config.undo?.enabled ? false : !value.undo)
        ) ||
        disabled
      }
      {...props}
    >
      {isRunning ? (
        <CircularProgress color="secondary" size={16} thickness={5.6} />
      ) : (
        getStateIcon(actionState)
      )}
    </Fab>
  );
}