@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 |
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 |
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 |
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 |
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}> 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
);
}