@material-ui/core#TextareaAutosize TypeScript Examples
The following examples show how to use
@material-ui/core#TextareaAutosize.
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: DaoSettings.tsx From homebase-app with MIT License | 6 votes |
CustomTextarea = styled(withTheme(TextareaAutosize))((props) => ({
minHeight: 152,
boxSizing: "border-box",
width: "100%",
border: `1px solid ${props.theme.palette.primary.light}`,
marginTop: 14,
fontWeight: 300,
padding: "21px 20px",
fontFamily: "system-ui",
fontSize: 16,
background: props.theme.palette.primary.main,
color: props.theme.palette.text.secondary,
paddingRight: 40,
wordBreak: "break-word",
"&:hover": {
background: "rgba(129, 254, 183, 0.03)",
borderLeft: `2px solid ${props.theme.palette.secondary.light}`,
},
}))
Example #2
Source File: JSONChart.tsx From neodash with Apache License 2.0 | 6 votes |
NeoJSONChart = (props: ChartProps) => {
const records = props.records;
return <div style={{marginTop: "0px"}}>
<TextareaAutosize
style={{ width: "100%", border: "1px solid lightgray" }}
className={"textinput-linenumbers"}
value={JSON.stringify(records, null, 2)}
aria-label=""
placeholder="Query output should be rendered here." />
</div >;
}
Example #3
Source File: CodeViewerComponent.tsx From neodash with Apache License 2.0 | 6 votes |
NeoCodeViewerComponent = ({ value = "", placeholder = "" }) => {
return (
<div style={{ overflowY: "auto", marginLeft: "10px", marginRight: "10px", height: "100%" }}>
<TextareaAutosize
style={{ width: "100%", overflowY: "hidden", scrollbarWidth: "auto", paddingLeft: "10px", background: "none", overflow: "scroll !important", border: "1px solid lightgray" }}
className={"textinput-linenumbers"}
aria-label=""
value={value}
placeholder={placeholder} />
</div>
);
}
Example #4
Source File: TextAreaAutosize.tsx From shadowsocks-electron with GNU General Public License v3.0 | 6 votes |
StyledTextareaAutosize = React.memo((props: TextAreaProps) => {
const useStyles = makeStyles((theme: Theme) => createStyles({
textarea: {
width: '100%',
border: `solid 1px ${theme.palette.type === 'dark' ? grey[700] : 'lightgrey'}`,
outline: 'none',
backgroundColor: theme.palette.background.paper,
color: theme.palette.type === 'dark' ? grey[500] : grey[900],
}
}));
const onInnerChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
if (props.onTextChange) {
return props.onTextChange("acl", event);
}
}
const TextArea = (props: TextAreaProps) => {
const { onTextChange, ...rest } = props;
const classes = useStyles();
return <TextareaAutosize {...rest} className={classes.textarea} onChange={onInnerChange}/>
};
return <TextArea {...props}/>;
})
Example #5
Source File: CommentTextarea.tsx From knboard with MIT License | 6 votes |
CommentTextarea = (props: TextareaAutosizeProps) => {
const theme = useTheme();
return (
<TextareaAutosize
aria-label="comment"
placeholder="Add a comment..."
rowsMin={4}
css={css`
line-height: 1.25rem;
font-size: 0.875rem;
color: ${N800};
width: ${commentBoxWidth}px;
border: 1px solid #bcbcbc;
border-radius: 4px;
padding: 12px 16px 0;
resize: none;
margin-bottom: 8px;
${theme.breakpoints.down("sm")} {
width: ${commentBoxWidthMobile}px !important;
}
&:focus {
outline: none;
border: 1px solid #999;
}
`}
{...props}
/>
);
}
Example #6
Source File: UpgradeOldDashboardModal.tsx From neodash with Apache License 2.0 | 5 votes |
NeoUpgradeOldDashboardModal = ({ open, text, clearOldDashboard, loadDashboard }) => {
return (
<div>
<Dialog maxWidth={"lg"} open={open == true} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">
Old Dashboard Found
</DialogTitle>
<DialogContent>
We've found a dashboard built with an old version of NeoDash.
Would you like to attempt an upgrade, or start from scratch?
<br />
<b>Make sure you back up this dashboard first!</b><br />
<Button onClick={() => {
localStorage.removeItem("neodash-dashboard");
clearOldDashboard();
}}
style={{ marginTop: "20px", marginBottom: "20px", marginRight: "20px" }}
color="default"
variant="contained"
size="large"
endIcon={<DeleteIcon color={"white"} />}>
Delete Old Dashboard
</Button>
<Button onClick={() => {
localStorage.removeItem("neodash-dashboard");
loadDashboard(text);
clearOldDashboard();
}}
style={{ marginTop: "20px", marginRight: "6px", marginBottom: "20px", backgroundColor: "white" }}
color="default"
variant="contained"
size="large">
Upgrade
</Button>
<TextareaAutosize
style={{ minHeight: "200px", width: "100%", border: "1px solid lightgray" }}
className={"textinput-linenumbers"}
onChange={(e) => { }}
value={text ? text : ""}
aria-label=""
placeholder="" />
</DialogContent>
</Dialog>
</div >
);
}
Example #7
Source File: BoardName.tsx From knboard with MIT License | 5 votes |
BoardName = ({ id, name, isOwner, ...props }: Props) => {
const dispatch = useDispatch();
const [pendingName, setPendingName] = useState<string>(name);
const [editing, setEditing] = useState<boolean>(false);
const nameTextAreaRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (!editing && name === pendingName) {
nameTextAreaRef?.current?.blur();
}
}, [pendingName, editing]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode === Key.Enter) {
e.preventDefault();
if (pendingName.length > 0) {
nameTextAreaRef?.current?.blur();
}
}
if (e.keyCode === Key.Escape) {
e.preventDefault();
setPendingName(name);
setEditing(false);
// blur via useEffect
}
};
const handleSave = () => {
if (editing && pendingName.length > 0) {
setEditing(false);
if (pendingName !== name) {
dispatch(patchBoard({ id, fields: { name: pendingName } }));
}
}
};
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setPendingName(e.target.value);
};
const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {
e.target.select();
};
return (
<Container {...props}>
{editing ? (
<div>
<TextareaAutosize
ref={nameTextAreaRef}
value={pendingName}
onChange={handleChange}
onBlur={handleSave}
onKeyDown={handleKeyDown}
data-testid="board-name-textarea"
onFocus={handleFocus}
autoFocus
/>
</div>
) : (
<div
css={css`
${isOwner && `&:hover {cursor: pointer;}`}
`}
onClick={() => setEditing(isOwner)}
>
{pendingName}
</div>
)}
</Container>
);
}
Example #8
Source File: index.tsx From react-app-architecture with Apache License 2.0 | 4 votes |
export default function WritingPad(): ReactElement {
const classes = useStyles();
const dispatch = useDispatch();
const [preventBack, setPreventBack] = useState(false);
const [showPreview, setShowPreview] = useState(false);
const { hydrationBlog, data, isFetchingBlog, isSavingBlog, message } = useStateSelector(
({ writingPadState }) => writingPadState,
);
const [localState, setLocalState] = useState<LocalState>({
isForSubmission: false,
isAllDataSentToServer: false,
isBlogDetailsFormToShow: false,
title: '',
description: '',
imgUrl: '',
blogUrl: '',
tags: [],
isWriting: false,
isTitleError: false,
isDescriptionError: false,
isImgUrlError: false,
isBlogUrlError: false,
isTagsError: false,
});
useEffect(() => {
if (hydrationBlog?._id && !isFetchingBlog) dispatch(fetchBlog(hydrationBlog._id));
if (hydrationBlog)
setLocalState({
...localState,
title: hydrationBlog.title,
description: hydrationBlog.description,
imgUrl: hydrationBlog.imgUrl,
blogUrl: hydrationBlog.blogUrl,
tags: hydrationBlog.tags,
});
return () => {
dispatch(clearPad.action());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleDoneClick = () => {
setLocalState({
...localState,
isBlogDetailsFormToShow: true,
isForSubmission: true,
});
};
const handleSaveClick = () => {
if (!data?._id) {
setLocalState({ ...localState, isBlogDetailsFormToShow: true });
} else {
setLocalState({ ...localState, isAllDataSentToServer: true });
data?._id &&
dispatch(
saveBlog(data._id, {
text: data.draftText,
title: localState.title,
description: localState.description,
tags: localState.tags,
imgUrl: localState.imgUrl,
}),
);
}
setPreventBack(false);
};
const handleCreateClick = () => {
data &&
dispatch(
createBlog({
text: data.draftText,
title: localState.title,
blogUrl: localState.blogUrl,
description: localState.description,
tags: localState.tags,
imgUrl: localState.imgUrl,
}),
);
};
const handleSubmitClick = () => {
setLocalState({ ...localState, isBlogDetailsFormToShow: false });
data?._id && dispatch(submitBlog(data._id));
dispatch(clearEditorPage.action());
};
const handleWithdrawClick = () => {
setLocalState({ ...localState, isBlogDetailsFormToShow: false });
data?._id && dispatch(withdrawBlog(data._id));
dispatch(clearEditorPage.action());
};
const renderMenu = () => {
if (!data) return null;
return (
<SpeedDial
direction="down"
ariaLabel="Blog Editor Menu"
className={classes.speedDial}
hidden={true}
icon={<ArrowDropDownIcon />}
open={true}
>
<SpeedDialAction
FabProps={{
disabled: !(data && data._id && data.isDraft),
}}
key="Done"
icon={<DoneAllIcon />}
tooltipTitle="Done"
onClick={handleDoneClick}
/>
<SpeedDialAction
key="Unsubmit"
FabProps={{
disabled: !(data && data._id && data.isSubmitted),
}}
icon={<CloseIcon />}
tooltipTitle="Remove Submission"
onClick={handleWithdrawClick}
/>
<SpeedDialAction
key="Submit"
FabProps={{
disabled: !(data && data._id && !data.isSubmitted),
}}
icon={<SendIcon />}
tooltipTitle="Submit Blog"
onClick={handleSubmitClick}
/>
<SpeedDialAction
FabProps={{
disabled: !(data?.draftText?.trim().length > 0),
}}
key="Blog Preview"
icon={<VisibilityIcon />}
tooltipTitle="Blog Preview"
onClick={() => setShowPreview(true)}
/>
<SpeedDialAction
FabProps={{
disabled: !(data?.draftText?.trim().length > 0),
}}
key="Save Blog"
icon={<SaveIcon />}
tooltipTitle="Save Blog"
onClick={handleSaveClick}
/>
</SpeedDial>
);
};
return (
<div className={classes.root}>
<Prompt
when={preventBack}
message={() => 'Are you sure you want to go without saving your work.'}
/>
{(isFetchingBlog || isSavingBlog) && <LinearProgress className={classes.progress} />}
<Grid className={classes.content} container justify="center">
<Grid item xs={12} sm={12} md={7}>
<TextareaAutosize
className={classes.pad}
aria-label="blog writing pad"
rowsMin={15}
value={data?.draftText}
onChange={(e) => {
dispatch(editBlog.action({ draftText: e.target.value }));
if (!preventBack) setPreventBack(true);
}}
placeholder="Write something awesome today.."
/>
</Grid>
</Grid>
{data && <Preview blog={data} open={showPreview} onClose={() => setShowPreview(false)} />}
<BlogDetailsForm
blog={data}
localState={localState}
setLocalState={setLocalState}
onSubmit={handleSubmitClick}
onCreate={handleCreateClick}
onSave={handleSaveClick}
/>
{renderMenu()}
{message && (
<Snackbar
message={message.text}
variant={message.type}
onClose={() => dispatch(removeMessage.action())}
/>
)}
</div>
);
}
Example #9
Source File: Dashboard.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
DashboardUI: React.FC<ContextType & { location: any }> = (
props
) => {
const {
current,
setCurrent,
resultsPerPage,
setResultsPerPage,
filters,
addFilter,
removeFilter,
results,
facets,
clearFilters,
sortDirection,
sortField,
setSort,
totalPages,
totalResults,
setSearchTerm,
searchTerm,
noResults
} = props;
const classes = useStyles();
const [selectedDomain, setSelectedDomain] = useState('');
const [resultsScrolled, setResultsScrolled] = useState(false);
const {
apiPost,
apiPut,
setLoading,
showAllOrganizations,
currentOrganization
} = useAuthContext();
const search:
| (SavedSearch & {
editing?: boolean;
})
| undefined = localStorage.getItem('savedSearch')
? JSON.parse(localStorage.getItem('savedSearch')!)
: undefined;
const [showSaveSearch, setShowSaveSearch] = useState<Boolean>(
search && search.editing ? true : false
);
const [savedSearchValues, setSavedSearchValues] = useState<
Partial<SavedSearch> & {
name: string;
vulnerabilityTemplate: Partial<Vulnerability>;
}
>(
search
? search
: {
name: '',
vulnerabilityTemplate: {},
createVulnerabilities: false
}
);
const onTextChange: React.ChangeEventHandler<
HTMLInputElement | HTMLSelectElement
> = (e) => onChange(e.target.name, e.target.value);
const onChange = (name: string, value: any) => {
setSavedSearchValues((values) => ({
...values,
[name]: value
}));
};
const onVulnerabilityTemplateChange = (e: any) => {
(savedSearchValues.vulnerabilityTemplate as any)[e.target.name] =
e.target.value;
setSavedSearchValues(savedSearchValues);
};
const handleResultScroll = (e: React.UIEvent<HTMLElement>) => {
if (e.currentTarget.scrollTop > 0) {
setResultsScrolled(true);
} else {
setResultsScrolled(false);
}
};
useEffect(() => {
if (props.location.search === '') {
// Search on initial load
setSearchTerm('');
}
return () => {
localStorage.removeItem('savedSearch');
};
}, [setSearchTerm, props.location.search]);
useBeforeunload((event) => {
localStorage.removeItem('savedSearch');
});
const fetchDomainsExport = async (): Promise<string> => {
try {
const body: any = {
current,
filters,
resultsPerPage,
searchTerm,
sortDirection,
sortField
};
if (!showAllOrganizations && currentOrganization) {
if ('rootDomains' in currentOrganization)
body.organizationId = currentOrganization.id;
else body.tagId = currentOrganization.id;
}
const { url } = await apiPost('/search/export', {
body
});
return url!;
} catch (e) {
console.error(e);
return '';
}
};
return (
<div className={classes.root}>
<FilterDrawer
addFilter={addFilter}
removeFilter={removeFilter}
filters={filters}
facets={facets}
clearFilters={filters.length > 0 ? () => clearFilters([]) : undefined}
/>
<div className={classes.contentWrapper}>
<Subnav
items={[
{ title: 'Search Results', path: '/inventory', exact: true },
{ title: 'All Domains', path: '/inventory/domains' },
{ title: 'All Vulnerabilities', path: '/inventory/vulnerabilities' }
]}
styles={{
paddingLeft: '0%'
}}
>
<FilterTags filters={filters} removeFilter={removeFilter} />
</Subnav>
<SortBar
sortField={sortField}
sortDirection={sortDirection}
setSort={setSort}
isFixed={resultsScrolled}
saveSearch={
filters.length > 0 || searchTerm
? () => setShowSaveSearch(true)
: undefined
}
existingSavedSearch={search}
/>
{noResults && (
<NoResults
message={"We don't see any results that match your criteria."}
></NoResults>
)}
<div className={classes.content}>
<div className={classes.panel} onScroll={handleResultScroll}>
{results.map((result) => (
<ResultCard
key={result.id.raw}
{...result}
onDomainSelected={(id) => setSelectedDomain(id)}
selected={result.id.raw === selectedDomain}
/>
))}
</div>
<div className={classes.panel}>
{selectedDomain && <DomainDetails domainId={selectedDomain} />}
</div>
</div>
<Paper classes={{ root: classes.pagination }}>
<span>
<strong>
{(totalResults === 0
? 0
: (current - 1) * resultsPerPage + 1
).toLocaleString()}{' '}
-{' '}
{Math.min(
(current - 1) * resultsPerPage + resultsPerPage,
totalResults
).toLocaleString()}
</strong>{' '}
of <strong>{totalResults.toLocaleString()}</strong>
</span>
<Pagination
count={totalPages}
page={current}
onChange={(_, page) => setCurrent(page)}
color="primary"
size="small"
/>
<FormControl
variant="outlined"
className={classes.pageSize}
size="small"
>
<Typography id="results-per-page-label">
Results per page:
</Typography>
<Select
id="teststa"
labelId="results-per-page-label"
value={resultsPerPage}
onChange={(e) => setResultsPerPage(e.target.value as number)}
>
{[15, 45, 90].map((perPage) => (
<MenuItem key={perPage} value={perPage}>
{perPage}
</MenuItem>
))}
</Select>
</FormControl>
<USWDSButton
className={classes.exportButton}
outline
type="button"
onClick={() =>
exportCSV(
{
name: 'domains',
getDataToExport: fetchDomainsExport
},
setLoading
)
}
>
Export Results
</USWDSButton>
</Paper>
</div>
{showSaveSearch && (
<div>
<Overlay />
<ModalContainer>
<Modal
className={classes.saveSearchModal}
actions={
<>
<USWDSButton
outline
type="button"
onClick={() => {
setShowSaveSearch(false);
}}
>
Cancel
</USWDSButton>
<USWDSButton
type="button"
onClick={async () => {
const body = {
body: {
...savedSearchValues,
searchTerm,
filters,
count: totalResults,
searchPath: window.location.search,
sortField,
sortDirection
}
};
if (search) {
await apiPut('/saved-searches/' + search.id, body);
} else {
await apiPost('/saved-searches/', body);
}
setShowSaveSearch(false);
}}
>
Save
</USWDSButton>
</>
}
title={search ? <h2>Update Search</h2> : <h2>Save Search</h2>}
>
<FormGroup>
<Label htmlFor="name">Name Your Search</Label>
<TextInput
required
id="name"
name="name"
type="text"
value={savedSearchValues.name}
onChange={onTextChange}
/>
<p>When a new result is found:</p>
{/* <FormControlLabel
control={
<Checkbox
// checked={gilad}
// onChange={handleChange}
name="email"
/>
}
label="Email me"
/> */}
<FormControlLabel
control={
<Checkbox
checked={savedSearchValues.createVulnerabilities}
onChange={(e) =>
onChange(e.target.name, e.target.checked)
}
id="createVulnerabilities"
name="createVulnerabilities"
/>
}
label="Create a vulnerability"
/>
{savedSearchValues.createVulnerabilities && (
<>
<Label htmlFor="title">Title</Label>
<TextInput
required
id="title"
name="title"
type="text"
value={savedSearchValues.vulnerabilityTemplate.title}
onChange={onVulnerabilityTemplateChange}
/>
<Label htmlFor="description">Description</Label>
<TextareaAutosize
required
id="description"
name="description"
style={{ padding: 10 }}
rowsMin={2}
value={
savedSearchValues.vulnerabilityTemplate.description
}
onChange={onVulnerabilityTemplateChange}
/>
<Label htmlFor="description">Severity</Label>
<Dropdown
id="severity"
name="severity"
onChange={onVulnerabilityTemplateChange}
value={
savedSearchValues.vulnerabilityTemplate
.severity as string
}
style={{ display: 'inline-block', width: '150px' }}
>
<option value="None">None</option>
<option value="Low">Low</option>
<option value="Medium">Medium</option>
<option value="High">High</option>
<option value="Critical">Critical</option>
</Dropdown>
</>
)}
{/* <h3>Collaborators</h3>
<p>
Collaborators can view vulnerabilities, and domains within
this search. Adding a team will make all members
collaborators.
</p>
<button className={classes.addButton} >
<AddCircleOutline></AddCircleOutline> ADD
</button> */}
</FormGroup>
</Modal>
</ModalContainer>
</div>
)}
</div>
);
}
Example #10
Source File: Vulnerability.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
Vulnerability: React.FC = () => {
const { vulnerabilityId } = useParams();
const { apiGet, apiPut } = useAuthContext();
const [vulnerability, setVulnerability] = useState<VulnerabilityType>();
const [comment, setComment] = useState<string>('');
const [showCommentForm, setShowCommentForm] = useState<boolean>(false);
const [menuAnchor, setMenuAnchor] = React.useState<null | HTMLElement>(null);
const classes = useStyles();
const history = useHistory();
const formatDate = (date: string) => {
return format(parseISO(date), 'MM-dd-yyyy');
};
const fetchVulnerability = useCallback(async () => {
try {
const result = await apiGet<VulnerabilityType>(
`/vulnerabilities/${vulnerabilityId}`
);
setVulnerability(result);
} catch (e) {
console.error(e);
}
}, [vulnerabilityId, apiGet]);
const updateVulnerability = async (body: { [key: string]: string }) => {
try {
if (!vulnerability) return;
const res = await apiPut<VulnerabilityType>(
'/vulnerabilities/' + vulnerability.id,
{
body: body
}
);
setVulnerability({
...vulnerability,
state: res.state,
substate: res.substate,
actions: res.actions
});
} catch (e) {
console.error(e);
}
};
useEffect(() => {
fetchVulnerability();
}, [fetchVulnerability]);
if (!vulnerability) return <></>;
const references = vulnerability.references.map((ref) => ref);
if (vulnerability.cve)
references.unshift({
name: 'NIST National Vulnerability Database',
url: `https://nvd.nist.gov/vuln/detail/${vulnerability.cve}`,
source: '',
tags: []
});
const states = [
'unconfirmed',
'exploitable',
'false-positive',
'accepted-risk',
'remediated'
];
interface dnstwist {
'domain-name': string;
fuzzer: string;
'dns-a'?: string;
'dns-aaas'?: string;
'dns-mx'?: string;
'dns-ns'?: string;
'date-first-observed'?: string;
}
return (
<>
{/* <Alert severity="info">
This vulnerability is found on 17 domains you have access to.
</Alert> */}
<div className={classes.root}>
<p>
<Link
to="# "
onClick={() => history.goBack()}
className={classes.backLink}
>
<ChevronLeft
style={{
height: '100%',
verticalAlign: 'middle',
marginTop: '-2px'
}}
></ChevronLeft>
Go back
</Link>
</p>
<div className={classes.contentWrapper}>
<div className={classes.content}>
<div
className={classes.panel}
style={{
flex: '0 0 45%'
}}
>
<Paper classes={{ root: classes.cardRoot }}>
<div className={classes.title}>
<h4>{vulnerability.title}</h4>
<Button
aria-haspopup="true"
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
setMenuAnchor(event.currentTarget)
}
>
<Flag
style={{
fontSize: '14px',
color: '#A9AEB1',
marginRight: '5px'
}}
></Flag>
Mark Item <ArrowDropDown />
</Button>
<Menu
anchorEl={menuAnchor}
keepMounted
open={Boolean(menuAnchor)}
getContentAnchorEl={null}
onClose={() => setMenuAnchor(null)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
{states.map((state) => (
<MenuItem
key={state}
onClick={() => {
updateVulnerability({
substate: state
});
setMenuAnchor(null);
}}
style={{ outline: 'none' }}
>
{stateMap[state]}
</MenuItem>
))}
</Menu>
</div>
<Chip
style={{
marginLeft: '1.5rem'
}}
// icon={<Check></Check>}
label={`${vulnerability.state[0].toUpperCase()}${vulnerability.state.slice(
1
)} (${stateMap[vulnerability.substate]})`}
color={
vulnerability.state === 'open' ? 'secondary' : 'default'
}
/>
<div className={classes.inner}>
<div className={classes.section}>
<h4 className={classes.subtitle}>Description</h4>
{vulnerability.description}
</div>
<div className={classes.section}>
<h4 className={classes.subtitle}>References</h4>
{references &&
references.map((ref, index) => (
<p key={index}>
<a
href={ref.url}
target="_blank"
rel="noopener noreferrer"
>
{ref.name ? ref.name : ref.url}
</a>
{ref.tags.length > 0
? ' - ' + ref.tags.join(',')
: ''}
</p>
))}
</div>
{vulnerability.source === 'hibp' && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Data</h4>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Exposed Emails</TableCell>
<TableCell align="right">Breaches</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(
vulnerability.structuredData['emails']
).map((keyName, keyIndex) => (
<TableRow key={keyName}>
<TableCell component="th" scope="row">
{keyName}
</TableCell>
<TableCell align="right">
{vulnerability.structuredData['emails'][
keyName
].join(', ')}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{vulnerability.source === 'lookingGlass' && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Data</h4>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>First Seen</TableCell>
<TableCell align="right">Last Seen</TableCell>
<TableCell align="right">Vuln Name</TableCell>
<TableCell align="right">Type</TableCell>
</TableRow>
</TableHead>
<TableBody>
{vulnerability.structuredData['lookingGlassData'].map(
(col: any) => (
<TableRow key={col.right_name}>
<TableCell component="th" scope="row">
{formatDistanceToNow(
parseISO(col.firstSeen)
) + ' ago'}
</TableCell>
<TableCell align="right">
{formatDistanceToNow(parseISO(col.lastSeen)) +
' ago'}
</TableCell>
<TableCell align="right">
{col.right_name}
</TableCell>
<TableCell align="right">
{col.vulnOrMal}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</div>
)}
{vulnerability.source === 'dnstwist' && (
<div className={classes.section}>
<h4 className={classes.subtitle}>Data</h4>
<TableContainer>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Domain Name</TableCell>
<TableCell>IP Address / A Record</TableCell>
<TableCell>MX Record</TableCell>
<TableCell>NS Record</TableCell>
<TableCell>Date Observed</TableCell>
<TableCell>Fuzzer</TableCell>
</TableRow>
</TableHead>
<TableBody>
{vulnerability.structuredData['domains'].map(
(dom: dnstwist) => (
<TableRow key={dom['domain-name']}>
<TableCell component="th" scope="row">
{dom['domain-name']}
</TableCell>
<TableCell>{dom['dns-a']}</TableCell>
<TableCell>{dom['dns-mx']}</TableCell>
<TableCell>{dom['dns-ns']}</TableCell>
<TableCell>
{dom['date-first-observed']}
</TableCell>
<TableCell>{dom['fuzzer']}</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</TableContainer>
</div>
)}
</div>
</Paper>
</div>
<div
className={classes.panel}
style={{
flex: '0 0 30%'
}}
>
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Team notes</h2>
<button
onClick={() => {
setShowCommentForm(!showCommentForm);
}}
className={classes.linkSmall}
>
Add new note
</button>
</div>
{showCommentForm && (
<div>
<TextareaAutosize
style={{
width: '100%',
padding: 10,
marginBottom: '20px'
}}
rowsMin={4}
placeholder="Leave a Note"
onChange={(e) => setComment(e.target.value)}
/>
<Button
onClick={() => {
updateVulnerability({
comment
});
setComment('');
setShowCommentForm(false);
}}
style={{
width: 150,
marginBottom: '20px'
}}
variant="contained"
color="secondary"
>
Save
</Button>
</div>
)}
{vulnerability.actions &&
vulnerability.actions
.filter((action) => action.type === 'comment')
.map((action, index) => (
<div className={classes.section} key={index}>
<h4
className={classes.subtitle}
style={{ fontSize: '16px', display: 'inline' }}
>
{action.userName}
</h4>
<span style={{ float: 'right', display: 'inline' }}>
{formatDistanceToNow(parseISO(action.date))} ago
</span>
<ReactMarkdown linkTarget="_blank">
{action.value || ''}
</ReactMarkdown>
</div>
))}
</div>
</Paper>
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Vulnerability History</h2>
</div>
<Timeline
style={{
position: 'relative',
marginLeft: '-90%'
}}
align="left"
>
{vulnerability.actions &&
vulnerability.actions
.filter(
(action) =>
action.type === 'state-change' && action.substate
)
.map((action, index) => (
<TimelineItem key={index}>
<TimelineSeparator>
<TimelineDot />
<TimelineConnector />
</TimelineSeparator>{' '}
<TimelineContent>
State {action.automatic ? 'automatically ' : ''}
changed to {action.state} (
{stateMap[action.substate!].toLowerCase()})
{action.userName ? ' by ' + action.userName : ''}{' '}
<br></br>
<span
style={{
color: '#A9AEB1'
}}
>
{formatDate(action.date)}
</span>
</TimelineContent>
</TimelineItem>
))}
<TimelineItem>
<TimelineSeparator>
<TimelineDot />
</TimelineSeparator>
<TimelineContent>
Vulnerability opened<br></br>
<span
style={{
color: '#A9AEB1'
}}
>
{formatDate(vulnerability.createdAt)}
</span>
</TimelineContent>
</TimelineItem>
</Timeline>
</div>
</Paper>
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Provenance</h2>
<p>
<strong>Root Domain: </strong>
{vulnerability.domain.fromRootDomain}
</p>
<p>
<strong>Subdomain: </strong>
{vulnerability.domain.name} (
{vulnerability.domain.subdomainSource})
</p>
{vulnerability.service && (
<p>
<strong>Service/Port: </strong>
{vulnerability.service.service
? vulnerability.service.service
: vulnerability.service.port}{' '}
({vulnerability.service.serviceSource})
</p>
)}
{vulnerability.cpe && (
<>
<p>
<strong>Product: </strong>
{vulnerability.cpe}
</p>
</>
)}
<p>
<strong>Vulnerability: </strong>
{vulnerability.title} ({vulnerability.source})
</p>
</div>
</div>
</Paper>
{vulnerability.source === 'hibp' && (
<Paper className={classes.cardRoot}>
<div className={classes.inner}>
<div className={classes.section}>
<h2 className={classes.subtitle}>Breaches</h2>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Breach Name</TableCell>
<TableCell align="right">Date Added</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(vulnerability.structuredData['breaches'])
.sort(
(a, b) =>
parseISO(
vulnerability.structuredData['breaches'][b][
'AddedDate'
]
).getTime() -
parseISO(
vulnerability.structuredData['breaches'][a][
'AddedDate'
]
).getTime()
)
.map((keyName, keyIndex) => (
<TableRow key={keyName}>
<TableCell component="th" scope="row">
{keyName}
</TableCell>
<TableCell align="right">
{formatDistanceToNow(
parseISO(
vulnerability.structuredData['breaches'][
keyName
]['AddedDate']
)
) + ' ago'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</Paper>
)}
</div>
</div>
</div>
</div>
</>
);
}
Example #11
Source File: ImportFeedDialog.tsx From bee-dashboard with BSD 3-Clause "New" or "Revised" License | 4 votes |
export function ImportFeedDialog({ onClose }: Props): ReactElement {
const [textareaValue, setTextareaValue] = useState('')
const [name, setName] = useState('')
const fileInputRef = useRef(null)
const { identities, setIdentities } = useContext(Context)
const { enqueueSnackbar } = useSnackbar()
const classes = useStyles()
async function onImport() {
const feed = await importIdentity(name, textareaValue)
if (feed) {
onFeedReady(feed)
} else {
enqueueSnackbar('Feed is not valid', { variant: 'error' })
}
}
function onUploadIdentityFile() {
if (fileInputRef.current) {
const input = fileInputRef.current as HTMLInputElement
input.click()
}
}
function onIdentityFileSelected(event: React.ChangeEvent<HTMLInputElement>) {
const fileReader = new FileReader()
const file = event.target?.files?.[0]
fileReader.onload = async event => {
const string = event.target?.result
if (string) {
const feed = await importIdentity(name, string as string)
if (feed) {
onFeedReady(feed)
} else {
enqueueSnackbar('Feed is not valid', { variant: 'error' })
}
}
}
if (file) {
fileReader.readAsText(file)
}
}
function onFeedReady(identity: Identity) {
persistIdentity(identities, identity)
setIdentities(identities)
enqueueSnackbar('Feed imported successfully', { variant: 'success' })
onClose()
}
return (
<SwarmDialog>
<input onChange={onIdentityFileSelected} ref={fileInputRef} className={classes.displayNone} type="file" />
<Box mb={4}>
<TitleWithClose onClose={onClose}>Import</TitleWithClose>
</Box>
<Box mb={2}>
<SwarmTextInput label="Identity Name" name="name" onChange={event => setName(event.target.value)} />
</Box>
<Box mb={4}>
<TextareaAutosize
className={classes.textarea}
minRows={5}
onChange={event => setTextareaValue(event.target.value)}
/>
</Box>
<ExpandableListItemActions>
<SwarmButton iconType={Upload} onClick={onUploadIdentityFile}>
Upload Json File
</SwarmButton>
<SwarmButton iconType={Check} onClick={onImport}>
Use Pasted Text
</SwarmButton>
</ExpandableListItemActions>
</SwarmDialog>
)
}
Example #12
Source File: LoadModal.tsx From neodash with Apache License 2.0 | 4 votes |
NeoLoadModal = ({ loadDashboard, loadDatabaseListFromNeo4j, loadDashboardFromNeo4j, loadDashboardListFromNeo4j }) => {
const [loadModalOpen, setLoadModalOpen] = React.useState(false);
const [loadFromNeo4jModalOpen, setLoadFromNeo4jModalOpen] = React.useState(false);
const [text, setText] = React.useState("");
const [rows, setRows] = React.useState([]);
const { driver } = useContext<Neo4jContextState>(Neo4jContext);
const [dashboardDatabase, setDashboardDatabase] = React.useState("neo4j");
const [databases, setDatabases] = React.useState(["neo4j"]);
const handleClickOpen = () => {
setLoadModalOpen(true);
};
const handleClose = () => {
setLoadModalOpen(false);
};
const handleCloseAndLoad = () => {
setLoadModalOpen(false);
loadDashboard(text);
setText("");
};
function handleDashboardLoadedFromNeo4j(result) {
setText(result);
setLoadFromNeo4jModalOpen(false);
}
const reader = new FileReader();
reader.onload = async (e) => {
setText(e.target.result);
};
const uploadDashboard = async (e) => {
e.preventDefault();
reader.readAsText(e.target.files[0]);
}
const columns = [
{ field: 'id', hide: true, headerName: 'ID', width: 150 },
{ field: 'date', headerName: 'Date', width: 200 },
{ field: 'title', headerName: 'Title', width: 270 },
{ field: 'author', headerName: 'Author', width: 160 },
{ field: 'version', headerName: 'Version', width: 95 },
{
field: 'load', headerName: 'Select', renderCell: (c) => {
return <Button onClick={(e) => { loadDashboardFromNeo4j(driver, dashboardDatabase, c.id, handleDashboardLoadedFromNeo4j) }} style={{ float: "right", backgroundColor: "white" }} variant="contained" size="medium" endIcon={<PlayArrow />}>Select</Button>
}, width: 120
},
]
return (
<div>
<ListItem button onClick={handleClickOpen}>
<ListItemIcon>
<IconButton style={{ padding: "0px" }} >
<SystemUpdateAltIcon />
</IconButton>
</ListItemIcon>
<ListItemText primary="Load" />
</ListItem>
<Dialog maxWidth={"lg"} open={loadModalOpen == true} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">
<SystemUpdateAltIcon style={{
height: "30px",
paddingTop: "4px",
marginBottom: "-8px",
marginRight: "5px",
paddingBottom: "5px"
}} /> Load Dashboard
<IconButton onClick={handleClose} style={{ padding: "3px", float: "right" }}>
<Badge badgeContent={""} >
<CloseIcon />
</Badge>
</IconButton>
</DialogTitle>
<DialogContent style={{ width: "1000px" }}>
{/* <DialogContentText> Paste your dashboard file here to load it into NeoDash.</DialogContentText> */}
<div>
<Button
component="label"
onClick={(e) => {
loadDashboardListFromNeo4j(driver, dashboardDatabase, (result) => { setRows(result) });
setLoadFromNeo4jModalOpen(true);
loadDatabaseListFromNeo4j(driver, (result) => { setDatabases(result) });
}}
style={{ marginBottom: "10px", backgroundColor: "white" }}
color="default"
variant="contained"
size="medium"
endIcon={<StorageIcon />}>
Select From Neo4j
</Button>
<Button
component="label"
// onClick={(e)=>uploadDashboard(e)}
style={{ marginLeft: "10px", backgroundColor: "white", marginBottom: "10px" }}
color="default"
variant="contained"
size="medium"
endIcon={<PostAddIcon />}>
<input
type="file"
onChange={(e) => uploadDashboard(e)}
hidden
/>
Select From File
</Button>
<Button onClick={(text.length > 0) ? handleCloseAndLoad : null}
style={{ color: text.length > 0 ? "white" : "lightgrey", float: "right", marginLeft: "10px", marginBottom: "10px", backgroundColor: text.length > 0 ? "green" : "white" }}
color="default"
variant="contained"
size="medium"
endIcon={<PlayArrow />}>
Load Dashboard
</Button>
</div>
<TextareaAutosize
style={{ minHeight: "500px", width: "100%", border: "1px solid lightgray" }}
className={"textinput-linenumbers"}
onChange={(e) => setText(e.target.value)}
value={text}
aria-label=""
placeholder="Select a dashboard first, then preview it here..." />
</DialogContent>
{/* <DialogActions> */}
{/* </DialogActions> */}
</Dialog>
<Dialog maxWidth={"lg"} open={loadFromNeo4jModalOpen == true} onClose={(e) => { setLoadFromNeo4jModalOpen(false) }} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">
Select From Neo4j
<IconButton onClick={(e) => { setLoadFromNeo4jModalOpen(false) }} style={{ padding: "3px", float: "right" }}>
<Badge badgeContent={""} >
<CloseIcon />
</Badge>
</IconButton>
</DialogTitle>
<DialogContent style={{ width: "900px" }}>
<DialogContentText>If dashboards are saved in your current database, choose a dashboard below.
</DialogContentText>
<div style={{ height: "380px", borderBottom: "1px solid lightgrey" }}>
<DataGrid
rows={rows}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
disableSelectionOnClick
components={{
ColumnSortedDescendingIcon: () => <></>,
ColumnSortedAscendingIcon: () => <></>,
}}
/></div>
<FormControl style={{ marginTop: "-58px", marginLeft: "10px" }}>
<InputLabel id="demo-simple-select-label">Database</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
style={{ width: "150px" }}
value={dashboardDatabase}
onChange={(e) => {
setRows([]);
setDashboardDatabase(e.target.value);
loadDashboardListFromNeo4j(driver, e.target.value, (result) => { setRows(result); });
}}
>
{databases.map(database => {
return <MenuItem value={database}>{database}</MenuItem>
})}
</Select>
</FormControl>
</DialogContent>
</Dialog>
</div>
);
}
Example #13
Source File: SaveModal.tsx From neodash with Apache License 2.0 | 4 votes |
NeoSaveModal = ({ dashboard, connection, saveDashboardToNeo4j, loadDatabaseListFromNeo4j }) => {
const [saveModalOpen, setSaveModalOpen] = React.useState(false);
const [saveToNeo4jModalOpen, setSaveToNeo4jModalOpen] = React.useState(false);
const [overwriteExistingDashboard, setOverwriteExistingDashboard] = React.useState(false);
const [dashboardDatabase, setDashboardDatabase] = React.useState("neo4j");
const [databases, setDatabases] = React.useState(["neo4j"]);
const { driver } = useContext<Neo4jContextState>(Neo4jContext);
useEffect(() => {
loadDatabaseListFromNeo4j(driver, (result) => { setDatabases(result) });
}, [])
const handleClickOpen = () => {
setSaveModalOpen(true);
};
const handleClose = () => {
setSaveModalOpen(false);
};
const filteredDashboard = filterNestedDict(dashboard, ["fields", "settingsOpen", "advancedSettingsOpen", "collapseTimeout"]);
const dashboardString = JSON.stringify(filteredDashboard, null, 2);
const downloadDashboard = () => {
const element = document.createElement("a");
const file = new Blob([dashboardString], { type: 'text/plain' });
element.href = URL.createObjectURL(file);
element.download = "dashboard.json";
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
}
return (
<div>
<ListItem button onClick={handleClickOpen}>
<ListItemIcon>
<IconButton style={{ padding: "0px" }} >
<SaveIcon />
</IconButton>
</ListItemIcon>
<ListItemText primary="Save" />
</ListItem>
<Dialog maxWidth={"lg"} open={saveModalOpen == true} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">
<SaveIcon style={{
height: "30px",
paddingTop: "4px",
marginBottom: "-8px",
marginRight: "5px",
paddingBottom: "5px"
}} />
Save Dashboard
<IconButton onClick={handleClose} style={{ padding: "3px", float: "right" }}>
<Badge badgeContent={""} >
<CloseIcon />
</Badge>
</IconButton>
</DialogTitle>
<DialogContent style={{ width: "1000px" }}>
<Button
component="label"
onClick={(e) => { setSaveToNeo4jModalOpen(true) }}
style={{ backgroundColor: "white" }}
color="default"
variant="contained"
size="medium"
endIcon={<StorageIcon />}>
Save to Neo4j
</Button>
<Button
component="label"
onClick={downloadDashboard}
style={{ backgroundColor: "white", marginLeft: "10px" }}
color="default"
variant="contained"
size="medium"
endIcon={<GetAppIcon />}>
Save to File
</Button>
<br /><br />
<TextareaAutosize
style={{ minHeight: "500px", width: "100%", border: "1px solid lightgray" }}
className={"textinput-linenumbers"}
value={dashboardString}
aria-label=""
placeholder="Your dashboard JSON should show here" />
</DialogContent>
<DialogActions>
</DialogActions>
</Dialog>
<Dialog maxWidth={"lg"} open={saveToNeo4jModalOpen == true} onClose={(e) => { setSaveToNeo4jModalOpen(false) }} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">
Save to Neo4j
<IconButton onClick={(e) => { setSaveToNeo4jModalOpen(false) }} style={{ padding: "3px", float: "right" }}>
<Badge badgeContent={""} >
<CloseIcon />
</Badge>
</IconButton>
</DialogTitle>
<DialogContent style={{ width: "800px" }}>
<DialogContentText>This will save your current dashboard as a node to your active Neo4j database.
<br />Ensure you have write permissions to the database to use this feature.
</DialogContentText>
<TextareaAutosize
style={{ width: "100%", border: "1px solid lightgray" }}
className={"textinput-linenumbers"}
value={"{\n title: '" + dashboard.title + "',\n" +
" date: '" + new Date().toISOString() + "',\n" +
" user: '" + connection.username + "',\n" +
" content: " + "{...}" + "\n}"}
aria-label=""
placeholder="" />
<FormControl style={{ marginTop: "10px" }}>
<InputLabel id="demo-simple-select-label">Save to Database</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
style={{ width: "150px" }}
value={dashboardDatabase}
onChange={(e) => setDashboardDatabase(e.target.value)}
>
{databases.map(database => {
return <MenuItem value={database}>{database}</MenuItem>
})}
</Select>
</FormControl>
<FormControl style={{ marginTop: "20px", marginLeft: "10px" }}>
<Tooltip title="Overwrite dashboard(s) with the same name." aria-label="">
<FormControlLabel
control={<Checkbox style={{ fontSize: "small", color: "grey" }} checked={overwriteExistingDashboard} onChange={e => setOverwriteExistingDashboard(!overwriteExistingDashboard)} name="overwrite" />}
label="Overwrite"
/>
</Tooltip>
</FormControl>
<Button
component="label"
onClick={e => {
saveDashboardToNeo4j(driver, dashboardDatabase, dashboard, new Date().toISOString(), connection.username, overwriteExistingDashboard);
setSaveToNeo4jModalOpen(false);
setSaveModalOpen(false);
}}
style={{ backgroundColor: "white", marginTop: "20px", float: "right" }}
color="default"
variant="contained"
endIcon={<SaveIcon />}
size="medium">
Save
</Button>
<Button
component="label"
onClick={(e) => { setSaveToNeo4jModalOpen(false) }}
style={{ float: "right", marginTop: "20px", marginRight: "10px", backgroundColor: "white" }}
color="default"
variant="contained"
size="medium">
Cancel
</Button>
</DialogContent>
<DialogActions>
</DialogActions>
</Dialog>
</div>
);
}
Example #14
Source File: ColumnTitle.tsx From knboard with MIT License | 4 votes |
ColumnTitle = ({ id, title, tasksCount, ...props }: Props) => {
const dispatch = useDispatch();
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
null
);
const [pendingTitle, setPendingTitle] = useState<string>(title);
const [editing, setEditing] = useState<boolean>(false);
const titleTextAreaRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (!editing && title === pendingTitle) {
titleTextAreaRef?.current?.blur();
}
}, [pendingTitle, editing]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode === Key.Enter) {
e.preventDefault();
if (pendingTitle.length > 0) {
titleTextAreaRef?.current?.blur();
}
}
if (e.keyCode === Key.Escape) {
e.preventDefault();
setPendingTitle(title);
setEditing(false);
// blur via useEffect
}
};
const handleSave = () => {
if (editing && pendingTitle.length > 0) {
setEditing(false);
if (pendingTitle !== title) {
dispatch(patchColumn({ id, fields: { title: pendingTitle } }));
}
}
};
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setPendingTitle(e.target.value);
};
const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {
e.target.select();
};
const handleOptionsClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleOptionsClose = () => {
setAnchorEl(null);
};
const handleDelete = () => {
if (
window.confirm(
"Are you sure? Deleting the column will also delete related tasks and this cannot be undone."
)
) {
dispatch(deleteColumn(id));
handleOptionsClose();
}
};
const open = Boolean(anchorEl);
const popoverId = open ? `col-${id}options-popover` : undefined;
return (
<Container {...props}>
{editing ? (
<InputTitle>
<TextareaAutosize
ref={titleTextAreaRef}
value={pendingTitle}
onChange={handleChange}
onBlur={handleSave}
onKeyDown={handleKeyDown}
data-testid="column-title-textarea"
onFocus={handleFocus}
autoFocus
/>
</InputTitle>
) : (
<RegularTitle onClick={() => setEditing(true)}>
{pendingTitle}
</RegularTitle>
)}
<Extra>
<InnerExtra>
<Count>{tasksCount}</Count>
<Button
onClick={handleOptionsClick}
data-testid="col-options"
css={css`
margin-left: 0.25rem;
min-width: 0;
padding: 2px 8px;
height: 22px;
`}
>
<FontAwesomeIcon icon={faEllipsisV} />
</Button>
</InnerExtra>
<Popover
id={popoverId}
open={open}
anchorEl={anchorEl}
onClose={handleOptionsClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
>
<OptionsContent>
<Button
startIcon={<FontAwesomeIcon fixedWidth icon={faTrash} />}
onClick={handleDelete}
data-testid="delete-column"
size="small"
css={css`
font-size: 12px;
font-weight: bold;
color: ${ACTION_G};
`}
>
Delete column
</Button>
</OptionsContent>
</Popover>
</Extra>
</Container>
);
}
Example #15
Source File: EditTaskDialog.tsx From knboard with MIT License | 4 votes |
EditTaskDialog = () => {
const theme = useTheme();
const dispatch = useDispatch();
const columns = useSelector(selectAllColumns);
const labels = useSelector(selectAllLabels);
const labelsById = useSelector(selectLabelEntities);
const columnsById = useSelector(selectColumnsEntities);
const tasksByColumn = useSelector((state: RootState) => state.task.byColumn);
const taskId = useSelector((state: RootState) => state.task.editDialogOpen);
const tasksById = useSelector((state: RootState) => state.task.byId);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [editingDescription, setEditingDescription] = useState(false);
const titleTextAreaRef = useRef<HTMLTextAreaElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<MdEditor>(null);
const cancelRef = useRef<HTMLButtonElement>(null);
const xsDown = useMediaQuery(theme.breakpoints.down("xs"));
const open = taskId !== null;
useEffect(() => {
if (taskId && tasksById[taskId]) {
setDescription(tasksById[taskId].description);
setTitle(tasksById[taskId].title);
}
}, [open, taskId]);
const handleSaveTitle = () => {
if (taskId) {
dispatch(patchTask({ id: taskId, fields: { title } }));
}
};
const handleSaveDescription = () => {
if (taskId) {
dispatch(patchTask({ id: taskId, fields: { description } }));
setEditingDescription(false);
}
};
const handleCancelDescription = () => {
if (taskId && tasksById[taskId]) {
setDescription(tasksById[taskId].description);
setEditingDescription(false);
}
};
useEffect(() => {
const handleClickOutside = (event: any) => {
if (
wrapperRef.current &&
!wrapperRef.current.contains(event.target) &&
cancelRef.current &&
!cancelRef.current?.contains(event.target)
) {
handleSaveDescription();
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [wrapperRef, taskId, description]);
useEffect(() => {
if (editingDescription && editorRef && editorRef.current) {
editorRef.current.setSelection({
start: 0,
end: description.length,
});
}
}, [editingDescription]);
const findTaskColumnId = () => {
for (const columnId in tasksByColumn) {
for (const id of tasksByColumn[columnId]) {
if (id === taskId) {
return columnId;
}
}
}
return null;
};
const columnId = findTaskColumnId();
if (!taskId || !tasksById[taskId] || !columnId) {
return null;
}
const task = tasksById[taskId];
const column = columnsById[columnId];
const handleEditorKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode == Key.Enter && e.metaKey) {
handleSaveDescription();
}
if (e.keyCode === Key.Escape) {
// Prevent propagation from reaching the Dialog
e.stopPropagation();
handleCancelDescription();
}
};
const handleTitleKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode === Key.Enter) {
e.preventDefault();
titleTextAreaRef?.current?.blur();
}
if (e.keyCode === Key.Escape) {
// Prevent propagation from reaching the Dialog
e.stopPropagation();
}
};
const handleClose = () => {
dispatch(setEditDialogOpen(null));
setEditingDescription(false);
};
const handleTitleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setTitle(e.target.value);
};
const handleColumnChange = (_: any, value: IColumn | null) => {
if (!column || !value || column.id === value.id) {
return;
}
const current: Id[] = [...tasksByColumn[column.id]];
const next: Id[] = [...tasksByColumn[value.id]];
const currentId = current.indexOf(task.id);
const newPosition = 0;
// remove from original
current.splice(currentId, 1);
// insert into next
next.splice(newPosition, 0, task.id);
const updatedTasksByColumn: TasksByColumn = {
...tasksByColumn,
[column.id]: current,
[value.id]: next,
};
dispatch(updateTasksByColumn(updatedTasksByColumn));
handleClose();
};
const handlePriorityChange = (_: any, priority: Priority | null) => {
if (priority) {
dispatch(patchTask({ id: taskId, fields: { priority: priority.value } }));
}
};
const handleNotImplemented = () => {
dispatch(createInfoToast("Not implemented yet ?"));
};
const handleDelete = () => {
if (window.confirm("Are you sure? Deleting a task cannot be undone.")) {
dispatch(deleteTask(task.id));
handleClose();
}
};
const handleDescriptionClick = () => {
setEditingDescription(true);
};
const handleEditorChange = ({ text }: any) => {
setDescription(text);
};
const handleLabelsChange = (newLabels: Label[]) => {
dispatch(
patchTask({
id: taskId,
fields: { labels: newLabels.map((label) => label.id) },
})
);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
// don't listen for input when inputs are focused
if (
document.activeElement instanceof HTMLInputElement ||
document.activeElement instanceof HTMLTextAreaElement
) {
return;
}
if (e.key === "Backspace" && e.metaKey) {
handleDelete();
}
if (e.key === "Escape" && e.metaKey) {
handleClose();
}
if (e.key === "l" && e.metaKey) {
e.preventDefault();
handleNotImplemented();
}
};
return (
<Dialog
open={open}
onClose={handleClose}
onKeyDown={handleKeyDown}
fullWidth
keepMounted={false}
fullScreen={xsDown}
css={css`
.MuiDialog-paper {
max-width: 920px;
}
`}
>
<Content theme={theme}>
<Close onClose={handleClose} />
<Main>
<Header>id: {task.id}</Header>
<Title>
<FontAwesomeIcon icon={faArrowUp} />
<TextareaAutosize
ref={titleTextAreaRef}
value={title}
onChange={handleTitleChange}
onBlur={handleSaveTitle}
onKeyDown={handleTitleKeyDown}
data-testid="task-title"
/>
</Title>
<DescriptionHeader>
<FontAwesomeIcon icon={faAlignLeft} />
<h3>Description</h3>
</DescriptionHeader>
<Description
key={`${taskId}${editingDescription}`}
data-testid="task-description"
>
<EditorWrapper
onDoubleClick={
editingDescription ? undefined : handleDescriptionClick
}
editing={editingDescription}
ref={wrapperRef}
theme={theme}
onKeyDown={handleEditorKeyDown}
>
<MdEditor
ref={editorRef}
plugins={MD_EDITOR_PLUGINS}
config={
editingDescription ? MD_EDITING_CONFIG : MD_READ_ONLY_CONFIG
}
value={
editingDescription
? description
: description || DESCRIPTION_PLACEHOLDER
}
renderHTML={(text) => mdParser.render(text)}
onChange={handleEditorChange}
placeholder={DESCRIPTION_PLACEHOLDER}
/>
</EditorWrapper>
{editingDescription && (
<DescriptionActions>
<Button
variant="contained"
data-testid="save-description"
onClick={handleSaveDescription}
color="primary"
size="small"
>
Save ({getMetaKey()}+⏎)
</Button>
<Button
variant="outlined"
data-testid="cancel-description"
onClick={handleCancelDescription}
ref={cancelRef}
size="small"
css={css`
margin-left: 0.5rem;
`}
>
Cancel (Esc)
</Button>
</DescriptionActions>
)}
</Description>
<CommentSection taskId={task.id} />
</Main>
<Side theme={theme}>
<TaskAssignees task={task} />
<Autocomplete
id="column-select"
size="small"
options={columns}
getOptionLabel={(option) => option.title}
renderInput={(params) => (
<TextField {...params} label="Column" variant="outlined" />
)}
value={column}
onChange={handleColumnChange}
disableClearable
openOnFocus
data-testid="edit-column"
css={css`
width: 100%;
`}
/>
<Autocomplete
id="priority-select"
size="small"
blurOnSelect
autoHighlight
options={PRIORITY_OPTIONS}
getOptionLabel={(option) => option.label}
value={PRIORITY_MAP[task.priority]}
onChange={handlePriorityChange}
renderInput={(params) => (
<TextField {...params} label="Priority" variant="outlined" />
)}
renderOption={(option) => <PriorityOption option={option} />}
openOnFocus
disableClearable
data-testid="edit-priority"
css={css`
width: 100%;
margin-top: 1rem;
`}
/>
<Autocomplete
multiple
id="labels-select"
data-testid="edit-labels"
size="small"
filterSelectedOptions
autoHighlight
openOnFocus
blurOnSelect
disableClearable
options={labels}
getOptionLabel={(option) => option.name}
value={
tasksById[taskId].labels.map(
(labelId) => labelsById[labelId]
) as Label[]
}
onChange={(_, newLabels) => handleLabelsChange(newLabels)}
renderInput={(params) => (
<TextField {...params} label="Labels" variant="outlined" />
)}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<LabelChip
key={option.id}
label={option}
size="small"
{...getTagProps({ index })}
/>
))
}
renderOption={(option) => <LabelChip label={option} size="small" />}
css={css`
width: 100%;
margin-top: 1rem;
margin-bottom: 2rem;
`}
/>
<ButtonsContainer>
<Button
startIcon={<FontAwesomeIcon fixedWidth icon={faLock} />}
onClick={handleNotImplemented}
size="small"
css={css`
font-size: 12px;
font-weight: bold;
color: ${TASK_G};
`}
>
Lock task ({getMetaKey()}+L)
</Button>
<Button
startIcon={<FontAwesomeIcon fixedWidth icon={faTrash} />}
onClick={handleDelete}
data-testid="delete-task"
size="small"
css={css`
font-size: 12px;
font-weight: bold;
color: ${TASK_G};
margin-bottom: 2rem;
`}
>
Delete task ({getMetaKey()}+⌫)
</Button>
</ButtonsContainer>
<Text>
Updated {formatDistanceToNow(new Date(task.modified))} ago
</Text>
<Text
css={css`
margin-bottom: 1rem;
`}
>
Created {formatDistanceToNow(new Date(task.created))} ago
</Text>
</Side>
</Content>
</Dialog>
);
}