@material-ui/lab#Autocomplete TypeScript Examples
The following examples show how to use
@material-ui/lab#Autocomplete.
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: SearchBox.tsx From posso-uscire with GNU General Public License v3.0 | 7 votes |
export default function SearchBox() {
const [language] = useLanguage();
const router = useRouter();
return (
<Box display="flex" justifyContent="center">
<Autocomplete
id="province"
onChange={(_, value) => router.push("/" + (value as Province).urlName)}
options={regions}
getOptionLabel={(option) => option.nome}
style={{ width: "80%", marginTop: 20 }}
renderInput={(params) => (
<TextField
{...params}
label={i18n.CITY[language]}
variant="outlined"
/>
)}
/>
</Box>
);
}
Example #2
Source File: MetricAutocomplete.tsx From abacus with GNU General Public License v2.0 | 6 votes |
/**
* An Autocomplete just for Metrics
*/
export default function MetricAutocomplete<
Multiple extends boolean | undefined = undefined,
DisableClearable extends boolean | undefined = undefined,
FreeSolo extends boolean | undefined = undefined,
>(
props: Omit<AutocompleteProps<Metric, Multiple, DisableClearable, FreeSolo>, 'renderInput'> & {
error?: string | false
},
): ReturnType<typeof Autocomplete> {
const processedOptions = props.options
.filter((a) => !a.name.startsWith('archived_'))
.sort((a, b) => a.name.localeCompare(b.name, 'en'))
return (
<Autocomplete<Metric, Multiple, DisableClearable, FreeSolo>
aria-label='Select a metric'
fullWidth
options={processedOptions}
noOptionsText='No metrics found'
getOptionLabel={(metric: Metric) => metric.name}
getOptionSelected={(metricA: Metric, metricB: Metric) => metricA.metricId === metricB.metricId}
renderOption={(option: Metric) => (
<div>
<Typography variant='body1'>
<strong>{option.name}</strong>
</Typography>
<Typography variant='body1'>
<small>{option.description}</small>
</Typography>
</div>
)}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField
{...params}
placeholder='Select a metric'
error={!!props.error}
helperText={_.isString(props.error) ? props.error : undefined}
required
InputProps={{
...autocompleteInputProps(params, false),
}}
InputLabelProps={{
shrink: true,
}}
/>
)}
{..._.omit(props, ['options', 'error'])}
/>
)
}
Example #3
Source File: NewTreasuryProposalDialog.tsx From homebase-app with MIT License | 6 votes |
AutoCompleteField = styled(Autocomplete)({
"& .MuiInputLabel-root": {
display: "none",
},
"& .MuiAutocomplete-inputRoot": {
padding: 0
},
"& label + .MuiInput-formControl": {
marginTop: "0"
},
'& .MuiAutocomplete-inputRoot[class*="MuiInput-root"] .MuiAutocomplete-input:first-child': {
padding: 0
}
})
Example #4
Source File: NFTTransfer.tsx From homebase-app with MIT License | 6 votes |
AutoCompleteField = styled(Autocomplete)({
"& .MuiInputLabel-root": {
display: "none",
},
"& .MuiAutocomplete-inputRoot": {
padding: 0
},
"& label + .MuiInput-formControl": {
marginTop: "0"
},
'& .MuiAutocomplete-inputRoot[class*="MuiInput-root"] .MuiAutocomplete-input:first-child': {
padding: 0
}
})
Example #5
Source File: ProjectSelector.tsx From backstage with Apache License 2.0 | 6 votes |
ProjectSelector = ({
catalogEntities,
onChange,
disableClearable,
defaultValue,
label,
}: Props) => {
const classes = useStyles();
return (
<div className={classes.container}>
<Autocomplete
className={classes.autocomplete}
fullWidth
disableClearable={disableClearable}
defaultValue={defaultValue}
options={catalogEntities}
getOptionLabel={option => option?.metadata?.name}
renderOption={option => <span>{option?.metadata?.name}</span>}
renderInput={params => <TextField {...params} label={label} />}
onChange={(_, data) => {
onChange(data!);
}}
/>
</div>
);
}
Example #6
Source File: RepoSearchView.tsx From github-deploy-center with MIT License | 6 votes |
RepoSearchBox: FC<RepoSearchBoxProps> = ({ isLoading, options, selectedRepo, setSelectedRepo, }) => ( <Autocomplete loading={isLoading} options={options} id="search-repos" renderInput={(params) => ( <TextField variant="outlined" label="Search" {...params} InputProps={{ ...params.InputProps, endAdornment: isLoading && !selectedRepo ? ( <Box maxWidth={24} maxHeight={24} ml={1} component={CircularProgress}></Box> ) : null, }} /> )} groupBy={(r) => r.owner} getOptionLabel={(r) => r.name} getOptionSelected={(first, second) => first.id === second.id} value={selectedRepo} autoHighlight onChange={(_, value) => setSelectedRepo(value)} /> )
Example #7
Source File: AssigneeAutoComplete.tsx From knboard with MIT License | 6 votes |
AssigneeAutoComplete = ({
controlId,
dataTestId,
members,
assignee,
setAssignee,
}: Props) => {
return (
<Autocomplete
multiple
openOnFocus
filterSelectedOptions
disableClearable
disableCloseOnSelect
id={controlId}
data-testid={dataTestId}
size="small"
options={members}
getOptionLabel={(option) => option.username}
value={assignee}
onChange={(_event, value) => setAssignee(value)}
renderOption={(option) => <AvatarOption option={option} />}
renderInput={(params) => (
<TextField {...params} autoFocus label="Assignees" variant="outlined" />
)}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<AvatarTag
key={option.id}
option={option}
{...getTagProps({ index })}
/>
))
}
/>
);
}
Example #8
Source File: SelectedKindsFilter.tsx From backstage with Apache License 2.0 | 5 votes |
SelectedKindsFilter = ({ value, onChange }: Props) => {
const classes = useStyles();
const alertApi = useApi(alertApiRef);
const catalogApi = useApi(catalogApiRef);
const { error, value: kinds } = useAsync(async () => {
return await catalogApi
.getEntityFacets({ facets: ['kind'] })
.then(response => response.facets.kind?.map(f => f.value).sort() || []);
});
useEffect(() => {
if (error) {
alertApi.post({
message: `Failed to load entity kinds`,
severity: 'error',
});
}
}, [error, alertApi]);
const normalizedKinds = useMemo(
() => (kinds ? kinds.map(k => k.toLocaleLowerCase('en-US')) : kinds),
[kinds],
);
const handleChange = useCallback(
(_: unknown, v: string[]) => {
onChange(
normalizedKinds && normalizedKinds.every(r => v.includes(r))
? undefined
: v,
);
},
[normalizedKinds, onChange],
);
const handleEmpty = useCallback(() => {
onChange(value?.length ? value : undefined);
}, [value, onChange]);
if (!kinds?.length || !normalizedKinds?.length || error) {
return <></>;
}
return (
<Box pb={1} pt={1}>
<Typography variant="button">Kinds</Typography>
<Autocomplete
className={classes.formControl}
multiple
limitTags={4}
disableCloseOnSelect
aria-label="Kinds"
options={normalizedKinds}
value={value ?? normalizedKinds}
getOptionLabel={k => kinds[normalizedKinds.indexOf(k)] ?? k}
onChange={handleChange}
onBlur={handleEmpty}
renderOption={(option, { selected }) => (
<FormControlLabel
control={
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
checkedIcon={<CheckBoxIcon fontSize="small" />}
checked={selected}
/>
}
label={kinds[normalizedKinds.indexOf(option)] ?? option}
/>
)}
size="small"
popupIcon={<ExpandMoreIcon data-testid="selected-kinds-expand" />}
renderInput={params => <TextField {...params} variant="outlined" />}
/>
</Box>
);
}
Example #9
Source File: SearchFilter.Autocomplete.tsx From backstage with Apache License 2.0 | 5 votes |
AutocompleteFilter = (props: SearchAutocompleteFilterProps) => {
const {
className,
defaultValue,
name,
values: givenValues,
valuesDebounceMs,
label,
filterSelectedOptions,
limitTags,
multiple,
} = props;
const [inputValue, setInputValue] = useState<string>('');
useDefaultFilterValue(name, defaultValue);
const asyncValues =
typeof givenValues === 'function' ? givenValues : undefined;
const defaultValues =
typeof givenValues === 'function' ? undefined : givenValues;
const { value: values, loading } = useAsyncFilterValues(
asyncValues,
inputValue,
defaultValues,
valuesDebounceMs,
);
const { filters, setFilters } = useSearch();
const filterValue =
(filters[name] as string | string[] | undefined) || (multiple ? [] : null);
// Set new filter values on input change.
const handleChange = (
_: ChangeEvent<{}>,
newValue: string | string[] | null,
) => {
setFilters(prevState => {
const { [name]: filter, ...others } = prevState;
if (newValue) {
return { ...others, [name]: newValue };
}
return { ...others };
});
};
// Provide the input field.
const renderInput = (params: AutocompleteRenderInputParams) => (
<TextField
{...params}
name="search"
variant="outlined"
label={label}
fullWidth
/>
);
// Render tags as primary-colored chips.
const renderTags = (
tagValue: string[],
getTagProps: AutocompleteGetTagProps,
) =>
tagValue.map((option: string, index: number) => (
<Chip label={option} color="primary" {...getTagProps({ index })} />
));
return (
<Autocomplete
filterSelectedOptions={filterSelectedOptions}
limitTags={limitTags}
multiple={multiple}
className={className}
id={`${multiple ? 'multi-' : ''}select-filter-${name}--select`}
options={values || []}
loading={loading}
value={filterValue}
onChange={handleChange}
onInputChange={(_, newValue) => setInputValue(newValue)}
renderInput={renderInput}
renderTags={renderTags}
/>
);
}
Example #10
Source File: CategoryPicker.tsx From backstage with Apache License 2.0 | 5 votes |
CategoryPicker = () => {
const alertApi = useApi(alertApiRef);
const { error, loading, availableTypes, selectedTypes, setSelectedTypes } =
useEntityTypeFilter();
if (loading) return <Progress />;
if (error) {
alertApi.post({
message: `Failed to load entity types with error: ${error}`,
severity: 'error',
});
return null;
}
if (!availableTypes) return null;
return (
<Box pb={1} pt={1}>
<Typography variant="button">Categories</Typography>
<Autocomplete
multiple
aria-label="Categories"
options={availableTypes}
value={selectedTypes}
onChange={(_: object, value: string[]) => setSelectedTypes(value)}
renderOption={(option, { selected }) => (
<FormControlLabel
control={
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
}
label={capitalize(option)}
/>
)}
size="small"
popupIcon={<ExpandMoreIcon />}
renderInput={params => <TextField {...params} variant="outlined" />}
/>
</Box>
);
}
Example #11
Source File: EntityTagsPicker.tsx From backstage with Apache License 2.0 | 5 votes |
EntityTagsPicker = (
props: FieldExtensionComponentProps<string[], EntityTagsPickerUiOptions>,
) => {
const { formData, onChange, uiSchema } = props;
const catalogApi = useApi(catalogApiRef);
const [inputValue, setInputValue] = useState('');
const [inputError, setInputError] = useState(false);
const tagValidator = makeValidator().isValidTag;
const kinds = uiSchema['ui:options']?.kinds;
const { loading, value: existingTags } = useAsync(async () => {
const tagsRequest: GetEntitiesRequest = { fields: ['metadata.tags'] };
if (kinds) {
tagsRequest.filter = { kind: kinds };
}
const entities = await catalogApi.getEntities(tagsRequest);
return [
...new Set(
entities.items
.flatMap((e: Entity) => e.metadata?.tags)
.filter(Boolean) as string[],
),
].sort();
});
const setTags = (_: React.ChangeEvent<{}>, values: string[] | null) => {
// Reset error state in case all tags were removed
let hasError = false;
let addDuplicate = false;
const currentTags = formData || [];
// If adding a new tag
if (values?.length && currentTags.length < values.length) {
const newTag = (values[values.length - 1] = values[values.length - 1]
.toLocaleLowerCase('en-US')
.trim());
hasError = !tagValidator(newTag);
addDuplicate = currentTags.indexOf(newTag) !== -1;
}
setInputError(hasError);
setInputValue(!hasError ? '' : inputValue);
if (!hasError && !addDuplicate) {
onChange(values || []);
}
};
// Initialize field to always return an array
useEffectOnce(() => onChange(formData || []));
return (
<FormControl margin="normal">
<Autocomplete
multiple
freeSolo
filterSelectedOptions
onChange={setTags}
value={formData || []}
inputValue={inputValue}
loading={loading}
options={existingTags || []}
ChipProps={{ size: 'small' }}
renderInput={params => (
<TextField
{...params}
label="Tags"
onChange={e => setInputValue(e.target.value)}
error={inputError}
helperText="Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"
/>
)}
/>
</FormControl>
);
}
Example #12
Source File: TemplateTypePicker.tsx From backstage with Apache License 2.0 | 5 votes |
TemplateTypePicker = () => {
const alertApi = useApi(alertApiRef);
const { error, loading, availableTypes, selectedTypes, setSelectedTypes } =
useEntityTypeFilter();
if (loading) return <Progress />;
if (!availableTypes) return null;
if (error) {
alertApi.post({
message: `Failed to load entity types`,
severity: 'error',
});
return null;
}
return (
<Box pb={1} pt={1}>
<Typography variant="button">Categories</Typography>
<Autocomplete
multiple
aria-label="Categories"
options={availableTypes}
value={selectedTypes}
onChange={(_: object, value: string[]) => setSelectedTypes(value)}
renderOption={(option, { selected }) => (
<FormControlLabel
control={
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
}
label={capitalize(option)}
/>
)}
size="small"
popupIcon={<ExpandMoreIcon data-testid="categories-picker-expand" />}
renderInput={params => <TextField {...params} variant="outlined" />}
/>
</Box>
);
}
Example #13
Source File: EntityTagPicker.tsx From backstage with Apache License 2.0 | 5 votes |
EntityTagPicker = () => {
const classes = useStyles();
const {
updateFilters,
filters,
queryParameters: { tags: tagsParameter },
} = useEntityList();
const catalogApi = useApi(catalogApiRef);
const { value: availableTags } = useAsync(async () => {
const facet = 'metadata.tags';
const { facets } = await catalogApi.getEntityFacets({
facets: [facet],
filter: filters.kind?.getCatalogFilters(),
});
return facets[facet].map(({ value }) => value);
}, [filters.kind]);
const queryParamTags = useMemo(
() => [tagsParameter].flat().filter(Boolean) as string[],
[tagsParameter],
);
const [selectedTags, setSelectedTags] = useState(
queryParamTags.length ? queryParamTags : filters.tags?.values ?? [],
);
// Set selected tags on query parameter updates; this happens at initial page load and from
// external updates to the page location.
useEffect(() => {
if (queryParamTags.length) {
setSelectedTags(queryParamTags);
}
}, [queryParamTags]);
useEffect(() => {
updateFilters({
tags: selectedTags.length ? new EntityTagFilter(selectedTags) : undefined,
});
}, [selectedTags, updateFilters]);
if (!availableTags?.length) return null;
return (
<Box pb={1} pt={1}>
<Typography variant="button" component="label">
Tags
<Autocomplete
multiple
options={availableTags}
value={selectedTags}
onChange={(_: object, value: string[]) => setSelectedTags(value)}
renderOption={(option, { selected }) => (
<FormControlLabel
control={
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
}
label={option}
/>
)}
size="small"
popupIcon={<ExpandMoreIcon data-testid="tag-picker-expand" />}
renderInput={params => (
<TextField
{...params}
className={classes.input}
variant="outlined"
/>
)}
/>
</Typography>
</Box>
);
}
Example #14
Source File: AutocompleteTextField.tsx From backstage with Apache License 2.0 | 5 votes |
AutocompleteTextField = <TFieldValue extends string>( props: AutocompleteTextFieldProps<TFieldValue>, ) => { const { name, options, required, errors, rules, loading = false, loadingText, helperText, errorHelperText, textFieldProps = {}, } = props; return ( <Controller name={name} rules={rules} render={({ field: { onChange } }) => ( <Autocomplete loading={loading} loadingText={loadingText} options={options || []} autoSelect freeSolo onChange={(_event: React.ChangeEvent<{}>, value: string | null) => onChange(value) } renderInput={params => ( <TextField {...params} helperText={(errors?.[name] && errorHelperText) || helperText} error={Boolean(errors?.[name])} margin="normal" variant="outlined" required={required} InputProps={{ ...params.InputProps, endAdornment: ( <React.Fragment> {loading ? ( <CircularProgress color="inherit" size="1em" /> ) : null} {params.InputProps.endAdornment} </React.Fragment> ), }} {...textFieldProps} /> )} /> )} /> ); }
Example #15
Source File: SelectedRelationsFilter.tsx From backstage with Apache License 2.0 | 5 votes |
SelectedRelationsFilter = ({
relationPairs,
value,
onChange,
}: Props) => {
const classes = useStyles();
const relations = useMemo(() => relationPairs.flat(), [relationPairs]);
const handleChange = useCallback(
(_: unknown, v: string[]) => {
onChange(relations.every(r => v.includes(r)) ? undefined : v);
},
[relations, onChange],
);
const handleEmpty = useCallback(() => {
onChange(value?.length ? value : undefined);
}, [value, onChange]);
return (
<Box pb={1} pt={1}>
<Typography variant="button">Relations</Typography>
<Autocomplete
className={classes.formControl}
multiple
limitTags={4}
disableCloseOnSelect
aria-label="Relations"
options={relations}
value={value ?? relations}
onChange={handleChange}
onBlur={handleEmpty}
renderOption={(option, { selected }) => (
<FormControlLabel
control={
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
checkedIcon={<CheckBoxIcon fontSize="small" />}
checked={selected}
/>
}
label={option}
/>
)}
size="small"
popupIcon={<ExpandMoreIcon data-testid="selected-relations-expand" />}
renderInput={params => <TextField {...params} variant="outlined" />}
/>
</Box>
);
}
Example #16
Source File: TestVariationMergeForm.tsx From frontend with Apache License 2.0 | 5 votes |
TestVariationMergeForm: React.FunctionComponent<IProps> = ({
projectId,
items,
}) => {
const navigate = useNavigate();
const buildDispatch = useBuildDispatch();
const { enqueueSnackbar } = useSnackbar();
const [fromBranch, setFromBranch] = React.useState("");
const [toBranch, setToBranch] = React.useState("");
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
testVariationService
.merge(projectId, fromBranch, toBranch)
.then((build) => {
enqueueSnackbar(`Merge started in build: ${build.id}`, {
variant: "success",
});
navigate({
pathname: buildProjectPageUrl(projectId),
...buildTestRunLocation(build.id),
});
selectBuild(buildDispatch, build.id);
})
.catch((err) =>
enqueueSnackbar(err, {
variant: "error",
})
);
};
return (
<form onSubmit={handleSubmit}>
<Grid container spacing={2} alignItems="flex-end">
<Grid item xs>
<Select
required
fullWidth
displayEmpty
value={fromBranch}
onChange={(event) => setFromBranch(event.target.value as string)}
>
<MenuItem value="">
<em>From branch</em>
</MenuItem>
{items.map((i) => (
<MenuItem key={i} value={i}>
{i}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs>
<Autocomplete
id="toBranch"
options={items.map((i) => ({ title: i }))}
getOptionLabel={(option) => option.title}
freeSolo
fullWidth
renderInput={(params) => (
<TextField {...params} required label="To branch" />
)}
onInputChange={(_, value) => {
setToBranch(value);
}}
/>
</Grid>
<Grid item>
<Button type="submit" color="primary" variant="contained" id={LOCATOR_TEST_VARIATION_SELECT_BRANCH}>
Merge
</Button>
</Grid>
</Grid>
</form>
);
}
Example #17
Source File: DeploymentDialog.tsx From github-deploy-center with MIT License | 4 votes |
DeploymentDialog = () => {
const { deploymentDialog } = useAppState()
const { updateDeployWorkflowDialog, cancelEditDeployment, saveDeployment } =
useActions()
const valid = Boolean(
deploymentDialog &&
deploymentDialog.workflowId &&
deploymentDialog.releaseKey &&
deploymentDialog.ref
)
return (
<Dialog open={!!deploymentDialog} fullWidth onClose={cancelEditDeployment}>
{deploymentDialog ? (
<form
onSubmit={(event) => {
event.preventDefault()
if (valid) {
saveDeployment()
}
}}>
<DialogTitle>Deploy workflow settings</DialogTitle>
<DialogContent
style={{ display: 'flex', gap: '1rem', flexDirection: 'column' }}>
<SelectWorkflow
workflowId={deploymentDialog.workflowId}
onChange={(id) =>
updateDeployWorkflowDialog((state) => (state.workflowId = id))
}
/>
<TextField
label="Release input name"
value={deploymentDialog.releaseKey}
onChange={(e) =>
updateDeployWorkflowDialog(
(settings) => (settings.releaseKey = e.target.value)
)
}
/>
<TextField
label="Environment input name (optional)"
value={deploymentDialog.environmentKey}
onChange={(e) =>
updateDeployWorkflowDialog(
(settings) => (settings.environmentKey = e.target.value)
)
}
/>
<TextField
label="Run workflow from branch"
value={deploymentDialog.ref}
onChange={(e) =>
updateDeployWorkflowDialog(
(settings) => (settings.ref = e.target.value)
)
}
/>
<Autocomplete
style={{ gridColumn: '1 / span 5' }}
multiple
options={[]}
freeSolo
value={Object.entries(deploymentDialog.extraArgs).map(
([key, value]) => `${key}=${value}`
)}
renderInput={(params) => (
<TextField
label="Extra workflow args (press Enter to add)"
placeholder="key=value"
{...params}
/>
)}
onChange={(_, newValue) => {
const pairs = newValue
.filter((x): x is string => typeof x === 'string')
.map((x) => x.split('='))
.filter(([key, value]) => key && value)
const newArgs = fromPairs(pairs)
updateDeployWorkflowDialog(
(settings) => (settings.extraArgs = newArgs)
)
}}
/>
</DialogContent>
<DialogActions style={{ padding: '2rem' }}>
<Button
type="submit"
disabled={!valid}
variant="contained"
color="primary">
Save
</Button>
<Button onClick={cancelEditDeployment}>Cancel</Button>
</DialogActions>
</form>
) : null}
</Dialog>
)
}
Example #18
Source File: EnvironmentDialog.tsx From github-deploy-center with MIT License | 4 votes |
EnvironmentDialog: FC<{
dialogState?: EnvironmentDialogState
updateDialogState: (update: (state: EnvironmentDialogState) => void) => void
title: string
onSave: (settings: EnvironmentSettings) => void
onCancel: () => void
}> = ({ dialogState, onSave, onCancel, title, updateDialogState }) => {
const { data, isLoading, error } = useFetchEnvironments()
const { selectedApplication } = useAppState()
const filteredEnvironments = orderBy(
(data || []).filter((d) => d.name !== 'github-pages'),
[
(e) =>
e.name
.toLowerCase()
.includes(selectedApplication?.name.split(' ')[0].toLowerCase() || '')
? 1
: 2,
(e) => e.name,
]
)
return (
<Dialog open={!!dialogState} fullWidth onClose={onCancel}>
{dialogState ? (
<form
onSubmit={(event) => {
event.preventDefault()
const { environmentName, workflowInputValue } = dialogState
environmentName &&
onSave({
workflowInputValue,
name: environmentName,
})
}}>
<DialogTitle>{title}</DialogTitle>
<DialogContent style={{display: "flex", flexDirection: "column"}}>
{error instanceof Error ? (
<>
<Box mb={2}>
<Alert severity="warning">Could not fetch environments: {error.message}</Alert>
</Box>
<DialogContentText>Enter environment manually:</DialogContentText>
<TextField
label="Environment name"
value={dialogState.environmentName}
onChange={(e) =>
updateDialogState((state) => (state.environmentName = e.target.value))}
/>
<TextField
label="Workflow input value"
value={dialogState.workflowInputValue}
onChange={(e) =>
updateDialogState((state) => (state.workflowInputValue = e.target.value))}
/>
</>
) : (
<>
<DialogContentText>Select environment</DialogContentText>
<Autocomplete
freeSolo
loading={isLoading}
options={filteredEnvironments.map<Option>(identity)}
value={dialogState.environmentName}
openOnFocus
onChange={(_, value) =>
updateDialogState(
(state) =>
(state.environmentName =
typeof value === 'string'
? value
: value?.inputValue ?? value?.name ?? '')
)
}
getOptionLabel={(option) =>
typeof option === 'string' ? option : option.name
}
getOptionSelected={(first, second) =>
first.name === second.name
}
filterOptions={(options, params) => {
const filtered = filter(options, params)
// Suggest the creation of a new value
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
name: `Add "${params.inputValue}"`,
})
}
return filtered
}}
renderInput={(params) => (
<TextField
autoFocus
variant="outlined"
label="Search"
{...params}
inputProps={{
...params.inputProps,
'data-lpignore': true,
}}
InputProps={{
...params.InputProps,
endAdornment:
isLoading && !dialogState.environmentName ? (
<Box
maxWidth={24}
maxHeight={24}
ml={1}
component={CircularProgress}></Box>
) : null,
}}
/>
)}
/>
{selectedApplication?.deploySettings.type === 'workflow' &&
selectedApplication.deploySettings.environmentKey && (
<TextField
style={{ marginTop: '1rem' }}
label="Workflow input value (defaults to environment name)"
fullWidth
variant="outlined"
value={dialogState.workflowInputValue}
onChange={(event) =>
updateDialogState(
(state) =>
(state.workflowInputValue = event.target.value)
)
}
/>
)}
</>
)}
</DialogContent>
<Box p={2} pt={1}>
<DialogActions>
<Button
type="submit"
disabled={!dialogState.environmentName}
variant="contained"
color="primary">
Save
</Button>
<Button onClick={onCancel}>Cancel</Button>
</DialogActions>
</Box>
</form>
) : null}
</Dialog>
)
}
Example #19
Source File: index.tsx From TidGi-Desktop with Mozilla Public License 2.0 | 4 votes |
export default function EditWorkspace(): JSX.Element {
const { t } = useTranslation();
const originalWorkspace = useWorkspaceObservable(workspaceID);
const [workspace, workspaceSetter, onSave] = useForm(originalWorkspace);
const {
backupOnInterval,
disableAudio,
disableNotifications,
gitUrl,
hibernateWhenUnused,
homeUrl,
isSubWiki,
mainWikiToLink,
name,
order,
picturePath,
port,
storageService,
syncOnInterval,
syncOnStartup,
tagName,
transparentBackground,
userName,
wikiFolderLocation,
} = (workspace ?? {}) as unknown as IWorkspace;
const fileSystemPaths = usePromiseValue<ISubWikiPluginContent[]>(
async () => (mainWikiToLink ? await window.service.wiki.getSubWikiPluginContent(mainWikiToLink) : []),
[],
[mainWikiToLink],
) as ISubWikiPluginContent[];
const fallbackUserName = usePromiseValue<string>(async () => (await window.service.auth.get('userName')) as string, '');
const [requestRestartCountDown, RestartSnackbar] = useRestartSnackbar();
const requestSaveAndRestart = useCallback(async () => {
if (!isEqual(workspace, originalWorkspace)) {
await onSave();
requestRestartCountDown();
}
}, [onSave, requestRestartCountDown, workspace, originalWorkspace]);
const actualIP = usePromiseValue<string | undefined>(
async () => (homeUrl ? await window.remote.getLocalHostUrlWithActualIP(homeUrl) : await Promise.resolve(undefined)),
undefined,
[homeUrl],
);
if (workspaceID === undefined) {
return <Root>Error {workspaceID ?? '-'} not exists</Root>;
}
if (workspace === undefined) {
return <Root>{t('Loading')}</Root>;
}
const isCreateSyncedWorkspace = storageService !== SupportedStorageServices.local;
return (
<Root>
<div id="test" data-usage="For spectron automating testing" />
{RestartSnackbar}
<Helmet>
<title>
{t('WorkspaceSelector.EditWorkspace')} {String(order ?? 1)} {name}
</title>
</Helmet>
<FlexGrow>
<TextField
id="outlined-full-width"
label={t('EditWorkspace.Name')}
helperText={t('EditWorkspace.NameDescription')}
placeholder="Optional"
value={name}
onChange={(event) => workspaceSetter({ ...workspace, name: event.target.value })}
/>
<TextField
id="outlined-full-width"
label={t('EditWorkspace.Path')}
helperText={t('EditWorkspace.PathDescription')}
placeholder="Optional"
disabled
value={wikiFolderLocation}
onChange={(event) => workspaceSetter({ ...workspace, wikiFolderLocation: event.target.value })}
/>
<TextField
helperText={t('AddWorkspace.WorkspaceUserNameDetail')}
fullWidth
onChange={(event) => {
workspaceSetter({ ...workspace, userName: event.target.value });
void requestSaveAndRestart();
}}
label={t('AddWorkspace.WorkspaceUserName')}
placeholder={fallbackUserName}
value={userName}
/>
{!isSubWiki && (
<TextField
id="outlined-full-width"
label={t('EditWorkspace.Port')}
helperText={
<span>
{t('EditWorkspace.URL')}{' '}
<Link onClick={async () => actualIP && (await window.service.native.open(actualIP))} style={{ cursor: 'pointer' }}>
{actualIP}
</Link>
</span>
}
placeholder="Optional"
value={port}
onChange={async (event) => {
if (!Number.isNaN(Number.parseInt(event.target.value))) {
workspaceSetter({
...workspace,
port: Number(event.target.value),
homeUrl: await window.remote.getLocalHostUrlWithActualIP(`http://${defaultServerIP}:${event.target.value}/`),
});
void requestSaveAndRestart();
}
}}
/>
)}
{isSubWiki && (
<Autocomplete
freeSolo
options={fileSystemPaths?.map((fileSystemPath) => fileSystemPath.tagName)}
value={tagName}
onInputChange={(_, value) => {
workspaceSetter({ ...workspace, tagName: value });
void requestSaveAndRestart();
}}
renderInput={(parameters) => <TextField {...parameters} label={t('AddWorkspace.TagName')} helperText={t('AddWorkspace.TagNameHelp')} />}
/>
)}
<AvatarFlex>
<AvatarLeft>
<Avatar transparentBackground={transparentBackground}>
<AvatarPicture alt="Icon" src={getValidIconPath(picturePath)} />
</Avatar>
</AvatarLeft>
<AvatarRight>
<Tooltip title={wikiPictureExtensions.join(', ')} placement="top">
<PictureButton
variant="outlined"
size="small"
onClick={async () => {
const filePaths = await window.service.native.pickFile([{ name: 'Images', extensions: wikiPictureExtensions }]);
if (filePaths.length > 0) {
await window.service.workspace.update(workspaceID, { picturePath: filePaths[0] });
}
}}>
{t('EditWorkspace.SelectLocal')}
</PictureButton>
</Tooltip>
<Tooltip title={t('EditWorkspace.NoRevert') ?? ''} placement="bottom">
<PictureButton onClick={() => workspaceSetter({ ...workspace, picturePath: null })} disabled={!picturePath}>
{t('EditWorkspace.ResetDefaultIcon')}
</PictureButton>
</Tooltip>
</AvatarRight>
</AvatarFlex>
<SyncedWikiDescription
isCreateSyncedWorkspace={isCreateSyncedWorkspace}
isCreateSyncedWorkspaceSetter={(isSynced: boolean) => {
workspaceSetter({ ...workspace, storageService: isSynced ? SupportedStorageServices.github : SupportedStorageServices.local });
// requestRestartCountDown();
}}
/>
{isCreateSyncedWorkspace && (
<TokenForm
storageProvider={storageService}
storageProviderSetter={(nextStorageService: SupportedStorageServices) => {
workspaceSetter({ ...workspace, storageService: nextStorageService });
// requestRestartCountDown();
}}
/>
)}
{storageService !== SupportedStorageServices.local && (
<GitRepoUrlForm
storageProvider={storageService}
gitRepoUrl={gitUrl ?? ''}
gitRepoUrlSetter={(nextGitUrl: string) => {
workspaceSetter({ ...workspace, gitUrl: nextGitUrl });
}}
isCreateMainWorkspace={!isSubWiki}
/>
)}
{storageService !== SupportedStorageServices.local && (
<>
<Divider />
<List>
<ListItem disableGutters>
<ListItemText primary={t('EditWorkspace.SyncOnInterval')} secondary={t('EditWorkspace.SyncOnIntervalDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
checked={syncOnInterval}
onChange={(event) => workspaceSetter({ ...workspace, syncOnInterval: event.target.checked })}
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem disableGutters>
<ListItemText primary={t('EditWorkspace.SyncOnStartup')} secondary={t('EditWorkspace.SyncOnStartupDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
checked={syncOnStartup}
onChange={(event) => workspaceSetter({ ...workspace, syncOnStartup: event.target.checked })}
/>
</ListItemSecondaryAction>
</ListItem>
</List>
</>
)}
{storageService === SupportedStorageServices.local && (
<>
<Divider />
<List>
<Divider />
<ListItem disableGutters>
<ListItemText primary={t('EditWorkspace.BackupOnInterval')} secondary={t('EditWorkspace.BackupOnIntervalDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
checked={backupOnInterval}
onChange={(event) => workspaceSetter({ ...workspace, backupOnInterval: event.target.checked })}
/>
</ListItemSecondaryAction>
</ListItem>
</List>
</>
)}
{!isSubWiki && (
<List>
<Divider />
<ListItem disableGutters>
<ListItemText primary={t('EditWorkspace.HibernateTitle')} secondary={t('EditWorkspace.HibernateDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
checked={hibernateWhenUnused}
onChange={(event) => workspaceSetter({ ...workspace, hibernateWhenUnused: event.target.checked })}
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem disableGutters>
<ListItemText primary={t('EditWorkspace.DisableNotificationTitle')} secondary={t('EditWorkspace.DisableNotification')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
checked={disableNotifications}
onChange={(event) => workspaceSetter({ ...workspace, disableNotifications: event.target.checked })}
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem disableGutters>
<ListItemText primary={t('EditWorkspace.DisableAudioTitle')} secondary={t('EditWorkspace.DisableAudio')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
checked={disableAudio}
onChange={(event) => workspaceSetter({ ...workspace, disableAudio: event.target.checked })}
/>
</ListItemSecondaryAction>
</ListItem>
</List>
)}
</FlexGrow>
{!isEqual(workspace, originalWorkspace) && (
<div>
<Button color="primary" variant="contained" disableElevation onClick={requestSaveAndRestart}>
{t('EditWorkspace.Save')}
</Button>
<Button variant="contained" disableElevation onClick={() => void window.remote.closeCurrentWindow()}>
{t('EditWorkspace.Cancel')}
</Button>
</div>
)}
</Root>
);
}
Example #20
Source File: CardSettingsContentPropertySelect.tsx From neodash with Apache License 2.0 | 4 votes |
NeoCardSettingsContentPropertySelect = ({ type, database, settings, onReportSettingUpdate, onQueryUpdate }) => {
const { driver } = useContext<Neo4jContextState>(Neo4jContext);
if (!driver) throw new Error('`driver` not defined. Have you added it into your app as <Neo4jContext.Provider value={{driver}}> ?')
const debouncedRunCypherQuery = useCallback(
debounce(runCypherQuery, RUN_QUERY_DELAY_MS),
[],
);
const manualPropertyNameSpecification = settings['manualPropertyNameSpecification'];
const [labelInputText, setLabelInputText] = React.useState(settings['entityType']);
const [labelRecords, setLabelRecords] = React.useState([]);
const [propertyInputText, setPropertyInputText] = React.useState(settings['propertyType']);
const [propertyRecords, setPropertyRecords] = React.useState([]);
var parameterName = settings['parameterName'];
if (settings["type"] == undefined) {
onReportSettingUpdate("type", "Node Property");
}
if (!parameterName && settings['entityType'] && settings['propertyType']) {
const id = settings['id'] ? settings['id'] : "";
onReportSettingUpdate("parameterName", "neodash_" + (settings['entityType'] + "_" + settings['propertyType'] + (id == "" || id.startsWith("_") ? id : "_" + id)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_"));
}
// Define query callback to allow reports to get extra data on interactions.
const queryCallback = useCallback(
(query, parameters, setRecords) => {
debouncedRunCypherQuery(driver, database, query, parameters, {}, [], 10,
(status) => { status == QueryStatus.NO_DATA ? setRecords([]) : null },
(result => setRecords(result)),
() => { return }, false,
false, false,
[], [], [], [], null);
},
[],
);
function handleParameterTypeUpdate(newValue) {
onReportSettingUpdate('entityType', undefined);
onReportSettingUpdate('propertyType', undefined);
onReportSettingUpdate('id', undefined);
onReportSettingUpdate('parameterName', undefined);
onReportSettingUpdate("type", newValue);
}
function handleNodeLabelSelectionUpdate(newValue) {
setPropertyInputText("");
onReportSettingUpdate('entityType', newValue);
onReportSettingUpdate('propertyType', undefined);
onReportSettingUpdate('parameterName', undefined);
}
function handleFreeTextNameSelectionUpdate(newValue) {
if (newValue) {
const new_parameter_name = ("neodash_" + newValue).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
handleReportQueryUpdate(new_parameter_name, newValue, undefined);
} else {
onReportSettingUpdate('parameterName', undefined);
}
}
function handlePropertyNameSelectionUpdate(newValue) {
onReportSettingUpdate('propertyType', newValue);
if (newValue && settings['entityType']) {
const id = settings['id'] ? settings['id'] : "";
const new_parameter_name = "neodash_" + (settings['entityType'] + "_" + newValue + (id == "" || id.startsWith("_") ? id : "_" + id)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
handleReportQueryUpdate(new_parameter_name, settings['entityType'], newValue);
} else {
onReportSettingUpdate('parameterName', undefined);
}
}
function handleIdSelectionUpdate(value) {
const newValue = value ? value : "";
onReportSettingUpdate('id', "" + newValue);
if (settings['propertyType'] && settings['entityType']) {
const id = value ? "_" + value : "";
const new_parameter_name = "neodash_" + (settings['entityType'] + "_" + settings['propertyType'] + (id == "" || id.startsWith("_") ? id : "_" + id)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
handleReportQueryUpdate(new_parameter_name, settings['entityType'], settings['propertyType']);
}
}
function handleReportQueryUpdate(new_parameter_name, entityType, propertyType) {
// Set query based on whether we are selecting a node or relationship property.
onReportSettingUpdate('parameterName', new_parameter_name);
if (settings['type'] == "Node Property") {
const newQuery = "MATCH (n:`" + entityType + "`) \nWHERE toLower(toString(n.`" + propertyType + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + propertyType + "` as value LIMIT 5";
onQueryUpdate(newQuery);
} else if (settings['type'] == "Relationship Property"){
const newQuery = "MATCH ()-[n:`" + entityType + "`]->() \nWHERE toLower(toString(n.`" + propertyType + "`)) CONTAINS toLower($input) \nRETURN DISTINCT n.`" + propertyType + "` as value LIMIT 5";
onQueryUpdate(newQuery);
} else {
const newQuery = "RETURN true";
onQueryUpdate(newQuery);
}
}
const parameterSelectTypes = ["Node Property", "Relationship Property", "Free Text"]
return <div>
<p style={{ color: "grey", fontSize: 12, paddingLeft: "5px", border: "1px solid lightgrey", marginTop: "0px" }}>
{REPORT_TYPES[type].helperText}
</p>
<TextField select={true} autoFocus id="type" value={settings["type"] ? settings["type"] : "Node Property"}
onChange={(e) => {
handleParameterTypeUpdate(e.target.value);
}}
style={{ width: "25%" }} label="Selection Type"
type="text"
style={{ width: 335, marginLeft: "5px", marginTop: "0px" }}>
{parameterSelectTypes.map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</TextField>
{settings.type == "Free Text" ?
<NeoField
label={"Name"}
key={"freetext"}
value={settings["entityType"] ? settings["entityType"] : ""}
defaultValue={""}
placeholder={"Enter a parameter name here..."}
style={{ width: 335, marginLeft: "5px", marginTop: "0px" }}
onChange={(value) => {
setLabelInputText(value);
handleNodeLabelSelectionUpdate(value);
handleFreeTextNameSelectionUpdate(value);
}}
/>
:
<>
<Autocomplete
id="autocomplete-label-type"
options={manualPropertyNameSpecification ? [settings['entityType']] : labelRecords.map(r => r["_fields"] ? r["_fields"][0] : "(no data)")}
getOptionLabel={(option) => option ? option : ""}
style={{ width: 335, marginLeft: "5px", marginTop: "5px" }}
inputValue={labelInputText}
onInputChange={(event, value) => {
setLabelInputText(value);
if (manualPropertyNameSpecification) {
handleNodeLabelSelectionUpdate(value);
} else {
if (settings["type"] == "Node Property") {
queryCallback("CALL db.labels() YIELD label WITH label as nodeLabel WHERE toLower(nodeLabel) CONTAINS toLower($input) RETURN DISTINCT nodeLabel LIMIT 5", { input: value }, setLabelRecords);
} else {
queryCallback("CALL db.relationshipTypes() YIELD relationshipType WITH relationshipType as relType WHERE toLower(relType) CONTAINS toLower($input) RETURN DISTINCT relType LIMIT 5", { input: value }, setLabelRecords);
}
}
}}
value={settings['entityType'] ? settings['entityType'] : undefined}
onChange={(event, newValue) => handleNodeLabelSelectionUpdate(newValue)}
renderInput={(params) => <TextField {...params} placeholder="Start typing..." InputLabelProps={{ shrink: true }} label={settings["type"] == "Node Property" ? "Node Label" : "Relationship Type"} />}
/>
{/* Draw the property name & id selectors only after a label/type has been selected. */}
{settings['entityType'] ?
<>
<Autocomplete
id="autocomplete-property"
options={manualPropertyNameSpecification ? [settings['propertyType']] : propertyRecords.map(r => r["_fields"] ? r["_fields"][0] : "(no data)")}
getOptionLabel={(option) => option ? option : ""}
style={{ display: "inline-block", width: 185, marginLeft: "5px", marginTop: "5px" }}
inputValue={propertyInputText}
onInputChange={(event, value) => {
setPropertyInputText(value);
if (manualPropertyNameSpecification) {
handlePropertyNameSelectionUpdate(value);
} else {
queryCallback("CALL db.propertyKeys() YIELD propertyKey as propertyName WITH propertyName WHERE toLower(propertyName) CONTAINS toLower($input) RETURN DISTINCT propertyName LIMIT 5", { input: value }, setPropertyRecords);
}
}}
value={settings['propertyType']}
onChange={(event, newValue) => handlePropertyNameSelectionUpdate(newValue)}
renderInput={(params) => <TextField {...params} placeholder="Start typing..." InputLabelProps={{ shrink: true }} label={"Property Name"} />}
/>
<NeoField placeholder='number'
label="Number (optional)" disabled={!settings['propertyType']} value={settings['id']}
style={{ width: "135px", marginTop: "5px", marginLeft: "10px" }}
onChange={(value) => {
handleIdSelectionUpdate(value);
}} />
</> : <></>}
</>}
{parameterName ? <p>Use <b>${parameterName}</b> in a query to use the parameter.</p> : <></>}
</div>;
}
Example #21
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 #22
Source File: CreateTaskDialog.tsx From knboard with MIT License | 4 votes |
CreateTaskDialog = () => {
const theme = useTheme();
const dispatch = useDispatch();
const labelsOptions = useSelector(selectAllLabels);
const members = useSelector(selectAllMembers);
const open = useSelector((state: RootState) => state.task.createDialogOpen);
const columnId = useSelector(
(state: RootState) => state.task.createDialogColumn
);
const createLoading = useSelector(
(state: RootState) => state.task.createLoading
);
const [titleTouched, setTitleTouched] = useState<boolean>(false);
const [title, setTitle] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [assignees, setAssignees] = useState<BoardMember[]>([]);
const [priority, setPriority] = useState<Priority | null>({
value: "M",
label: "Medium",
});
const [labels, setLabels] = useState<Label[]>([]);
const xsDown = useMediaQuery(theme.breakpoints.down("xs"));
const handleEditorChange = ({ text }: any) => {
setDescription(text);
};
const setInitialValues = () => {
if (columnId) {
setTitleTouched(false);
setTitle("");
setDescription("");
setAssignees([]);
setPriority(PRIORITY_2);
setLabels([]);
}
};
useEffect(() => {
setInitialValues();
}, [open]);
const handleClose = () => {
if (window.confirm("Are you sure? Any progress made will be lost.")) {
dispatch(setCreateDialogOpen(false));
}
};
const handleCreate = async () => {
setTitleTouched(true);
if (columnId && priority) {
const newTask = {
title,
description,
column: columnId,
labels: labels.map((l) => l.id),
assignees: assignees.map((a) => a.id),
priority: priority.value,
};
dispatch(createTask(newTask));
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode == Key.Enter && e.metaKey) {
handleCreate();
}
};
return (
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
keepMounted={false}
fullScreen={xsDown}
>
<Content onKeyDown={handleKeyDown}>
<DialogTitle>New issue</DialogTitle>
<TextField
autoFocus
id="create-task-title"
data-testid="create-task-title"
label="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
variant="outlined"
fullWidth
size="small"
onBlur={() => setTitleTouched(true)}
error={titleTouched && !title}
/>
<EditorWrapper>
<MdEditor
plugins={MD_EDITOR_PLUGINS}
config={MD_EDITOR_CONFIG}
value={description}
renderHTML={(text) => mdParser.render(text)}
onChange={handleEditorChange}
placeholder="Describe the issue..."
/>
</EditorWrapper>
<Autocomplete
multiple
filterSelectedOptions
disableClearable
openOnFocus
id="create-assignee-select"
size="small"
options={members}
getOptionLabel={(option) => option.username}
value={assignees}
onChange={(_event, value) => setAssignees(value)}
renderOption={(option) => <AvatarOption option={option} />}
renderInput={(params) => (
<TextField {...params} label="Assignees" variant="outlined" />
)}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<AvatarTag
key={option.id}
option={option}
{...getTagProps({ index })}
/>
))
}
css={css`
width: 100%;
margin-top: 1rem;
`}
/>
<Autocomplete
id="create-priority-select"
size="small"
autoHighlight
options={PRIORITY_OPTIONS}
getOptionLabel={(option) => option.label}
value={priority}
onChange={(_: any, value: Priority | null) => setPriority(value)}
renderOption={(option) => <PriorityOption option={option} />}
renderInput={(params) => (
<TextField {...params} label="Priority" variant="outlined" />
)}
openOnFocus
disableClearable
css={css`
width: 100%;
margin-top: 1rem;
`}
/>
<Autocomplete
multiple
id="create-labels-select"
size="small"
filterSelectedOptions
autoHighlight
openOnFocus
options={labelsOptions}
getOptionLabel={(option) => option.name}
value={labels}
onChange={(_, newLabels) => setLabels(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`
margin-top: 1rem;
width: 100%;
`}
/>
</Content>
<Footer theme={theme}>
<Button
startIcon={
createLoading ? (
<CircularProgress color="inherit" size={16} />
) : (
<FontAwesomeIcon icon={faRocket} />
)
}
variant="contained"
color="primary"
size="small"
onClick={handleCreate}
disabled={createLoading}
data-testid="task-create"
css={css`
${theme.breakpoints.down("xs")} {
flex-grow: 1;
}
`}
>
Create issue ({getMetaKey()}+⏎)
</Button>
<Button
css={css`
margin-left: 1rem;
`}
onClick={handleClose}
>
Cancel (Esc)
</Button>
</Footer>
</Dialog>
);
}
Example #23
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>
);
}
Example #24
Source File: SearchBox.tsx From log4brains with Apache License 2.0 | 4 votes |
export function SearchBox(props: SearchBoxProps) {
const classes = useStyles();
const {
onOpen,
onClose,
open: openProp,
onQueryChange,
query,
results,
loading = false,
...otherProps
} = props;
const [open, setOpenState] = useControlled({
controlled: openProp,
default: false,
name: "SearchBox",
state: "open"
});
const handleOpen = (event: React.ChangeEvent<{}>) => {
if (open) {
return;
}
setOpenState(true);
if (onOpen) {
onOpen(event);
}
};
const router = useRouter();
const handleClose = (
event: React.ChangeEvent<{}>,
reason: AutocompleteCloseReason
) => {
if (!open) {
return;
}
setOpenState(false);
if (onClose) {
onClose(event, reason);
}
};
let noOptionsText: React.ReactNode = "Type to start searching";
if (loading) {
noOptionsText = (
<div style={{ textAlign: "center" }}>
<CircularProgress size={20} />
</div>
);
} else if (query) {
noOptionsText = "No matching documents";
}
return (
<Autocomplete
{...otherProps}
classes={{ paper: classes.acPaper }}
options={results ?? []}
getOptionLabel={(result) => result.title}
renderInput={(params) => (
<SearchBar
{...params}
open={open}
onClear={(event) =>
onQueryChange && onQueryChange(event, "", "clear")
}
className={classes.searchBar}
/>
)}
inputValue={query}
onInputChange={(event, value, reason) => {
// We don't want to replace the inputValue by the selected value
if (reason !== "reset" && onQueryChange) {
onQueryChange(event, value, reason);
}
}}
open={open}
onOpen={handleOpen}
onClose={handleClose}
filterOptions={(r) => r} // We hijack Autocomplete's behavior to display search results as options
renderOption={(result) => (
<>
<SvgIcon fontSize="small">
<AdrIcon />
</SvgIcon>
<Typography className={classes.resultTitle}>
{result.title}
</Typography>
</>
)}
noOptionsText={noOptionsText}
onChange={async (_, result) => {
if (result) {
await router.push(result.href);
}
}}
/>
);
}
Example #25
Source File: TagsMenuPopover.tsx From vscode-crossnote with GNU Affero General Public License v3.0 | 4 votes |
export function TagsMenuPopover(props: Props) {
const classes = useStyles(props);
const { t } = useTranslation();
const [tagName, setTagName] = useState<string>("");
const [options, setOptions] = useState<string[]>([]);
const addTag = useCallback(
(tagName: string) => {
if (!tagName || tagName.trim().length === 0) {
return;
}
props.addTag(tagName);
setTagName("");
},
[props]
);
useEffect(() => {
setTagName("");
}, [props.anchorElement]);
useEffect(() => {
if (!props.anchorElement) {
return;
}
let options: string[] = [];
const helper = (children: TagNode[]) => {
if (!children || !children.length) {
return;
}
for (let i = 0; i < children.length; i++) {
const tag = children[i].path;
options.push(tag);
helper(children[i].children);
}
};
helper(props.notebookTagNode.children);
setOptions(options);
}, [props.notebookTagNode, props]);
return (
<Popover
open={Boolean(props.anchorElement)}
anchorEl={props.anchorElement}
keepMounted
onClose={props.onClose}
>
<List>
<ListItem
className={clsx(classes.menuItemOverride, classes.menuItemTextField)}
>
<Autocomplete
inputValue={tagName}
onInputChange={(event, newInputValue) => {
setTagName(newInputValue);
}}
options={(tagName.trim().length > 0 &&
options.findIndex((x) => x === tagName.trim()) < 0
? [tagName, ...options]
: options
).map((opt) => "+ " + opt)}
style={{ width: 300, maxWidth: "100%" }}
value={""}
onChange={(event: any, newValue: string = "") => {
if (newValue) {
addTag(newValue.replace(/^+/, "").trim());
}
}}
renderInput={(params) => (
<TextField
placeholder={t("general/add-a-tag")}
fullWidth={true}
autoFocus={true}
onKeyUp={(event) => {
if (event.which === 13) {
addTag(tagName);
}
}}
{...params}
></TextField>
)}
noOptionsText={t("general/no-tags")}
openText={t("general/Open")}
closeText={t("general/close")}
loadingText={t("general/loading")}
clearText={t("general/clear-all")}
></Autocomplete>
</ListItem>
{props.tagNames.length > 0 ? (
props.tagNames.map((tagName) => {
return (
<ListItem
key={tagName}
className={clsx(classes.menuItemOverride)}
>
<Box
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
}}
>
<Typography>{tagName}</Typography>
<IconButton onClick={() => props.deleteTag(tagName)}>
<TrashCan></TrashCan>
</IconButton>
</Box>
</ListItem>
);
})
) : (
<ListItem className={clsx(classes.menuItemOverride)}>
<Typography style={{ margin: "8px 0" }}>
{t("general/no-tags")}
</Typography>
</ListItem>
)}
</List>
</Popover>
);
}
Example #26
Source File: index.tsx From frontegg-react with MIT License | 4 votes |
Select: FC<SelectProps> = (props) => {
const styles = useStyles();
const { t } = useT();
const p = mapper(props);
const [open, setOpen] = useState(false);
const {
size,
value,
loading,
onChange,
options,
onOpen,
onBlur,
onClose,
multiple,
fullWidth,
loadingText,
renderOption,
noOptionsText,
getOptionLabel,
open: propOpen,
} = p;
const color: any = p.color;
const [remountCount, setRemountCount] = useState(0);
const refresh = () => setRemountCount(remountCount + 1);
const handleChange = useCallback(
(e, newValue, reson) => {
onChange?.(e, newValue, reson);
},
[onChange]
);
const renderTag = useCallback(
(option, getTagProps, index) => {
const state = { ...getTagProps({ index }) };
return renderOption ? (
<React.Fragment key={index}>{renderOption(option, state)}</React.Fragment>
) : (
<Chip size={size} disabled={true} label={option?.label} {...getTagProps({ index })} />
);
},
[renderOption]
);
return (
<Autocomplete
{...p}
className={classNames({ [styles.inForm]: props.inForm })}
multiple={multiple ?? false}
options={options}
size={size}
value={value}
loading={loading}
{...(multiple && { disableCloseOnSelect: true })}
filterSelectedOptions
open={propOpen ?? open}
noOptionsText={noOptionsText ?? t('common.empty-items')}
loadingText={loadingText ?? `${t('common.loading')}...`}
onOpen={(e) => (onOpen ? onOpen(e) : setOpen(true))}
onClose={(e, reson) => (onClose ? onClose(e, reson) : setOpen(false))}
onChange={(e, newValue, reson) => {
handleChange(e, newValue, reson);
setTimeout(() => refresh());
}}
getOptionSelected={(option: any, value: any) => option.value === value.value}
getOptionLabel={(option: any) => (getOptionLabel ? getOptionLabel(option) : option.label)}
renderTags={(tagValue, getTagProps) => tagValue.map((option, index) => renderTag(option, getTagProps, index))}
renderInput={(params) => (
<TextField
{...params}
name={props.name}
label={props.label}
fullWidth={fullWidth ?? true}
style={{ minWidth: `${fullWidth ? '100%' : '14em'}` }}
variant='outlined'
color={color}
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color='inherit' size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
}
Example #27
Source File: SelectionPicker.tsx From clearflask with Apache License 2.0 | 4 votes |
render() {
const DropdownIcon = this.props.dropdownIcon || ArrowDropDownIcon;
const onInputChange = (e, val: string, reason: AutocompleteInputChangeReason) => {
if (reason === 'reset') return; // Prevent setting text value in textfield
if (this.props.onInputChange) {
this.props.onInputChange(val, reason);
}
if (this.props.inputValue === undefined) {
this.setState({ inputValue: val });
}
};
const renderTags = (value: Label[], getTagProps?: AutocompleteGetTagProps) => this.props.showTags === false ? null : value.map((option, index) => (
<Fade key={option.value} in={true}>
{this.props.bareTags ? (
<div
className={classNames(
this.props.classes.chip,
this.props.classes.chipBare,
)}
style={{
color: option.color,
}}
{...(getTagProps ? getTagProps({ index }) : {})}
>{option.label}</div>
) : (
<Chip
className={this.props.classes.chip}
variant='outlined'
label={option.label}
size='small'
style={{
color: option.color,
}}
{...(getTagProps ? getTagProps({ index }) : {})}
/>
)}
</Fade>
));
return (
<Autocomplete<LabelInternal, boolean, boolean, boolean>
freeSolo={!this.props.disableInput}
autoHighlight
multiple={!!this.props.isMulti}
value={this.props.isMulti ? this.props.value : (this.props.value[0] || null)}
onChange={(e, val, reason) => {
// Convert create label to create-option
var createLabel: LabelInternal | undefined;
if (this.props.isMulti && val instanceof Array) {
createLabel = (val as LabelInternal[]).find(label => label.isCreateOption);
} else if (!this.props.isMulti && val instanceof Object && (val as LabelInternal).isCreateOption) {
createLabel = (val as LabelInternal);
}
if (createLabel) {
reason = 'create-option';
val = createLabel.value;
}
if (reason === 'create-option') {
var createdText: string = '';
if (typeof val === 'string') {
createdText = val;
} else if (Array.isArray(val)) {
const valParsed = val[val.length - 1];
if (typeof valParsed === 'string') {
createdText = valParsed;
}
}
!!createdText && this.props.onValueCreate && this.props.onValueCreate(createdText);
onInputChange(undefined, '', 'clear');
} else if (reason === 'clear' || reason === 'blur') {
this.props.onValueChange([]);
} else if (reason === 'select-option' || reason === 'remove-option') {
this.props.onValueChange(!val ? [] : (this.props.isMulti ? val as Label[] : [val as Label]));
if (!this.props.disableClearOnValueChange) {
onInputChange(undefined, '', 'clear');
}
}
}}
disableCloseOnSelect={this.props.disableCloseOnSelect}
filterSelectedOptions={true}
filterOptions={(options, params) => {
// Sometimes autocomplete decides to pre-filter, so use options from props
var filtered: LabelInternal[] = [...this.props.options];
if (!this.props.disableFilter) {
filtered = filterOptions(options, params);
}
// Suggest the creation of a new value
if (!!this.props.onValueCreate && params.inputValue !== '') {
const createLabel = {
label: this.props.formatCreateLabel
? this.props.formatCreateLabel(params.inputValue)
: `Add "${params.inputValue}"`,
value: params.inputValue,
groupBy: '__EMPTY__',
isCreateOption: true,
};
if (this.props.showCreateAtTop) {
filtered.unshift(createLabel);
} else {
filtered.push(createLabel);
}
}
// Header
if (!!this.props.formatHeader) {
const header = this.props.formatHeader(params.inputValue);
if (header) {
filtered.unshift({
label: header,
value: '__HEADER__',
groupBy: '__HEADER__',
disabled: true,
});
}
}
// Add loading
if (this.props.loading) {
filtered.push({
label: 'Loading…',
value: '__HEADER__',
groupBy: '__HEADER__',
disabled: true,
});
}
if (this.props.noOptionsMessage && filtered.length === 0) {
filtered.push({
label: this.props.noOptionsMessage,
value: '__HEADER__',
groupBy: '__HEADER__',
disabled: true,
});
}
return filtered;
}}
popupIcon={DropdownIcon === null ? null : (
<DropdownIcon
fontSize='small'
color='inherit'
className={this.props.value.length > 0
? this.props.classes.dropdownIconWithTags
: this.props.classes.dropdownIconWithoutTags}
/>
)}
forcePopupIcon={this.props.forceDropdownIcon !== undefined ? this.props.forceDropdownIcon : (!!this.props.dropdownIcon || 'auto')}
options={this.props.options}
getOptionLabel={option => option.filterString || option.value}
getOptionSelected={(option, value) => option.value === value.value}
inputValue={this.props.inputValue !== undefined
? this.props.inputValue
: (this.state.inputValue || '')}
onInputChange={onInputChange}
style={this.props.style}
className={this.props.className}
limitTags={this.props.limitTags}
disabled={this.props.disabled}
getOptionDisabled={option => !!option.disabled}
groupBy={this.props.group ? (label: Label) => label.groupBy || label.value[0] : undefined}
renderGroup={this.props.group ? (params: AutocompleteRenderGroupParams) => (
<div className={classNames(this.props.classes.group, params.group === '__HEADER__' && this.props.classes.header)}>
{params.group && params.group !== '__EMPTY__' && params.group !== '__HEADER__' && (
<Typography key={params.key} variant='overline' className={this.props.classes.menuHeader}>{params.group}</Typography>
)}
{params.children}
</div>
) : undefined}
getLimitTagsText={this.props.limitTags === 0 ? more => null : undefined}
handleHomeEndKeys
openOnFocus
onFocus={this.props.onFocus}
onBlur={e => {
if (this.props.clearOnBlur) {
onInputChange(undefined, '', 'clear');
}
}}
renderOption={(option: Label, { selected }) => (
<Typography
noWrap
style={{
fontWeight: selected ? 'bold' : undefined,
color: option.color,
}}
component='div'
>
{(option.value !== '__HEADER__' && this.props.renderOption)
? this.props.renderOption(option, selected)
: option.label}
</Typography>
)}
renderTags={renderTags}
open={this.props.menuIsOpen}
disableClearable={this.props.disableClearable || this.props.value.length === 0}
onOpen={this.props.menuOnChange ? () => this.props.menuOnChange && this.props.menuOnChange(true) : undefined}
onClose={this.props.menuOnChange ? () => this.props.menuOnChange && this.props.menuOnChange(false) : undefined}
classes={{
...this.props.autocompleteClasses,
root: classNames(this.props.classes.autocomplete, this.props.autocompleteClasses?.root),
focused: classNames(this.props.classes.autocompleteFocused, this.props.autocompleteClasses?.focused),
popupIndicatorOpen: classNames(!!this.props.dropdownIcon && this.props.classes.dropdownIconDontFlip, this.props.autocompleteClasses?.popupIndicator),
endAdornment: classNames(this.props.classes.endAdornment, this.props.autocompleteClasses?.endAdornment),
input: classNames(this.props.classes.input, this.props.autocompleteClasses?.input),
inputRoot: classNames(this.props.classes.inputRoot, this.props.autocompleteClasses?.inputRoot),
popper: classNames(this.props.classes.popper, this.props.autocompleteClasses?.popper),
listbox: classNames(this.props.classes.popperListbox, this.props.autocompleteClasses?.listbox),
tag: classNames(this.props.classes.tag, this.props.autocompleteClasses?.tag),
clearIndicator: classNames(!!this.props.clearIndicatorNeverHide && this.props.classes.clearIndicatorNeverHide, this.props.autocompleteClasses?.clearIndicator),
}}
PopperComponent={getSelectionPopper(this.props.PopperProps)}
renderInput={(params) => {
// Remove limitTags span element since it's just taking up space
const paramsStartAdornment = (params.InputProps.startAdornment as Array<any>);
if (this.props.limitTags === 0
&& paramsStartAdornment
&& paramsStartAdornment[0]['type'] === 'span') {
paramsStartAdornment.shift();
}
const TextFieldCmpt = this.props.TextFieldComponent || TextField;
return (
<TextFieldCmpt
label={this.props.label}
helperText={this.props.errorMsg || this.props.helperText}
placeholder={(!!this.props.bareTags && this.props.value.length > 0)
? undefined
: this.props.placeholder}
error={!!this.props.errorMsg}
{...params}
{...this.props.TextFieldProps}
InputLabelProps={{
...params.InputLabelProps,
...this.props.TextFieldProps?.InputLabelProps,
}}
inputProps={{
...params.inputProps,
...this.props.TextFieldProps?.inputProps,
style: {
minWidth: this.props.inputMinWidth === undefined
? (this.props.disableInput ? 0 : 50)
: this.props.inputMinWidth,
...this.props.TextFieldProps?.inputProps?.style,
maxHeight: this.props.disableInput ? 20 : undefined,
},
}}
InputProps={{
...params.InputProps,
...this.props.TextFieldProps?.InputProps,
readOnly: this.props.disableInput || this.props.TextFieldProps?.InputProps?.readOnly,
startAdornment: (
<>
{this.props.TextFieldProps?.InputProps?.endAdornment || null}
{!!this.props.showTags
&& !this.props.isMulti
&& this.props.value.length > 0
&& renderTags(this.props.value)}
{params.InputProps.startAdornment}
{!!this.props.alwaysWrapChipsInput && !!paramsStartAdornment?.length && (
<div className={this.props.classes.flexWrapBreak} />
)}
</>
),
endAdornment: (
<>
{this.props.TextFieldProps?.InputProps?.endAdornment || null}
{params.InputProps.endAdornment}
</>
),
}}
/>
);
}}
/>
);
}
Example #28
Source File: Organization.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
Organization: React.FC = () => {
const {
apiGet,
apiPut,
apiPost,
user,
setFeedbackMessage
} = useAuthContext();
const { organizationId } = useParams<{ organizationId: string }>();
const [organization, setOrganization] = useState<OrganizationType>();
const [tags, setTags] = useState<AutocompleteType[]>([]);
const [userRoles, setUserRoles] = useState<Role[]>([]);
const [scanTasks, setScanTasks] = useState<ScanTask[]>([]);
const [scans, setScans] = useState<Scan[]>([]);
const [scanSchema, setScanSchema] = useState<ScanSchema>({});
const [newUserValues, setNewUserValues] = useState<{
firstName: string;
lastName: string;
email: string;
organization?: OrganizationType;
role: string;
}>({
firstName: '',
lastName: '',
email: '',
role: ''
});
const classes = useStyles();
const [tagValue, setTagValue] = React.useState<AutocompleteType | null>(null);
const [inputValue, setInputValue] = React.useState('');
const [dialog, setDialog] = React.useState<{
open: boolean;
type?: 'rootDomains' | 'ipBlocks' | 'tags';
label?: string;
}>({ open: false });
const dateAccessor = (date?: string) => {
return !date || new Date(date).getTime() === new Date(0).getTime()
? 'None'
: `${formatDistanceToNow(parseISO(date))} ago`;
};
const userRoleColumns: Column<Role>[] = [
{
Header: 'Name',
accessor: ({ user }) => user.fullName,
width: 200,
disableFilters: true,
id: 'name'
},
{
Header: 'Email',
accessor: ({ user }) => user.email,
width: 150,
minWidth: 150,
id: 'email',
disableFilters: true
},
{
Header: 'Role',
accessor: ({ approved, role, user }) => {
if (approved) {
if (user.invitePending) {
return 'Invite pending';
} else if (role === 'admin') {
return 'Administrator';
} else {
return 'Member';
}
}
return 'Pending approval';
},
width: 50,
minWidth: 50,
id: 'approved',
disableFilters: true
},
{
Header: () => {
return (
<div style={{ justifyContent: 'flex-center' }}>
<Button color="secondary" onClick={() => setDialog({ open: true })}>
<ControlPoint style={{ marginRight: '10px' }}></ControlPoint>
Add member
</Button>
</div>
);
},
id: 'action',
Cell: ({ row }: { row: { index: number } }) => {
const isApproved =
!organization?.userRoles[row.index] ||
organization?.userRoles[row.index].approved;
return (
<>
{isApproved ? (
<Button
onClick={() => {
removeUser(row.index);
}}
color="secondary"
>
<p>Remove</p>
</Button>
) : (
<Button
onClick={() => {
approveUser(row.index);
}}
color="secondary"
>
<p>Approve</p>
</Button>
)}
</>
);
},
disableFilters: true
}
];
const scanColumns: Column<Scan>[] = [
{
Header: 'Name',
accessor: 'name',
width: 150,
id: 'name',
disableFilters: true
},
{
Header: 'Description',
accessor: ({ name }) => scanSchema[name] && scanSchema[name].description,
width: 200,
minWidth: 200,
id: 'description',
disableFilters: true
},
{
Header: 'Mode',
accessor: ({ name }) =>
scanSchema[name] && scanSchema[name].isPassive ? 'Passive' : 'Active',
width: 150,
minWidth: 150,
id: 'mode',
disableFilters: true
},
{
Header: 'Action',
id: 'action',
maxWidth: 100,
Cell: ({ row }: { row: { index: number } }) => {
if (!organization) return;
const enabled = organization.granularScans.find(
(scan) => scan.id === scans[row.index].id
);
return (
<Button
type="button"
onClick={() => {
updateScan(scans[row.index], !enabled);
}}
>
{enabled ? 'Disable' : 'Enable'}
</Button>
);
},
disableFilters: true
}
];
const scanTaskColumns: Column<ScanTask>[] = [
{
Header: 'ID',
accessor: 'id',
disableFilters: true
},
{
Header: 'Status',
accessor: 'status',
disableFilters: true
},
{
Header: 'Type',
accessor: 'type',
disableFilters: true
},
{
Header: 'Name',
accessor: ({ scan }) => scan?.name,
disableFilters: true
},
{
Header: 'Created At',
accessor: ({ createdAt }) => dateAccessor(createdAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Requested At',
accessor: ({ requestedAt }) => dateAccessor(requestedAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Started At',
accessor: ({ startedAt }) => dateAccessor(startedAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Finished At',
accessor: ({ finishedAt }) => dateAccessor(finishedAt),
disableFilters: true,
disableSortBy: true
},
{
Header: 'Output',
accessor: 'output',
disableFilters: true
}
];
const fetchOrganization = useCallback(async () => {
try {
const organization = await apiGet<OrganizationType>(
`/organizations/${organizationId}`
);
organization.scanTasks.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
setOrganization(organization);
setUserRoles(organization.userRoles);
setScanTasks(organization.scanTasks);
const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
setTags(tags);
} catch (e) {
console.error(e);
}
}, [apiGet, setOrganization, organizationId]);
const fetchScans = useCallback(async () => {
try {
const response = await apiGet<{
scans: Scan[];
schema: ScanSchema;
}>('/granularScans/');
let { scans } = response;
const { schema } = response;
if (user?.userType !== 'globalAdmin')
scans = scans.filter(
(scan) =>
scan.name !== 'censysIpv4' && scan.name !== 'censysCertificates'
);
setScans(scans);
setScanSchema(schema);
} catch (e) {
console.error(e);
}
}, [apiGet, user]);
const approveUser = async (user: number) => {
try {
await apiPost(
`/organizations/${organization?.id}/roles/${organization?.userRoles[user].id}/approve`,
{ body: {} }
);
const copy = userRoles.map((role, id) =>
id === user ? { ...role, approved: true } : role
);
setUserRoles(copy);
} catch (e) {
console.error(e);
}
};
const removeUser = async (user: number) => {
try {
await apiPost(
`/organizations/${organization?.id}/roles/${userRoles[user].id}/remove`,
{ body: {} }
);
const copy = userRoles.filter((_, ind) => ind !== user);
setUserRoles(copy);
} catch (e) {
console.error(e);
}
};
const updateOrganization = async (body: any) => {
try {
const org = await apiPut('/organizations/' + organization?.id, {
body: organization
});
setOrganization(org);
setFeedbackMessage({
message: 'Organization successfully updated',
type: 'success'
});
} catch (e) {
setFeedbackMessage({
message:
e.status === 422
? 'Error updating organization'
: e.message ?? e.toString(),
type: 'error'
});
console.error(e);
}
};
const updateScan = async (scan: Scan, enabled: boolean) => {
try {
if (!organization) return;
await apiPost(
`/organizations/${organization?.id}/granularScans/${scan.id}/update`,
{
body: {
enabled
}
}
);
setOrganization({
...organization,
granularScans: enabled
? organization.granularScans.concat([scan])
: organization.granularScans.filter(
(granularScan) => granularScan.id !== scan.id
)
});
} catch (e) {
setFeedbackMessage({
message:
e.status === 422 ? 'Error updating scan' : e.message ?? e.toString(),
type: 'error'
});
console.error(e);
}
};
useEffect(() => {
fetchOrganization();
}, [fetchOrganization]);
const onInviteUserSubmit = async () => {
try {
const body = {
firstName: newUserValues.firstName,
lastName: newUserValues.lastName,
email: newUserValues.email,
organization: organization?.id,
organizationAdmin: newUserValues.role === 'admin'
};
const user: User = await apiPost('/users/', {
body
});
const newRole = user.roles[user.roles.length - 1];
newRole.user = user;
if (userRoles.find((role) => role.user.id === user.id)) {
setUserRoles(
userRoles.map((role) => (role.user.id === user.id ? newRole : role))
);
} else {
setUserRoles(userRoles.concat([newRole]));
}
} catch (e) {
setFeedbackMessage({
message:
e.status === 422 ? 'Error inviting user' : e.message ?? e.toString(),
type: 'error'
});
console.log(e);
}
};
const onInviteUserTextChange: React.ChangeEventHandler<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
> = (e) => onInviteUserChange(e.target.name, e.target.value);
const onInviteUserChange = (name: string, value: any) => {
setNewUserValues((values) => ({
...values,
[name]: value
}));
};
const filter = createFilterOptions<AutocompleteType>();
const ListInput = (props: {
type: 'rootDomains' | 'ipBlocks' | 'tags';
label: string;
}) => {
if (!organization) return null;
const elements: (string | OrganizationTag)[] = organization[props.type];
return (
<div className={classes.headerRow}>
<label>{props.label}</label>
<span>
{elements &&
elements.map((value: string | OrganizationTag, index: number) => (
<Chip
className={classes.chip}
key={index}
label={typeof value === 'string' ? value : value.name}
onDelete={() => {
organization[props.type].splice(index, 1);
setOrganization({ ...organization });
}}
></Chip>
))}
<Chip
label="ADD"
variant="outlined"
color="secondary"
onClick={() => {
setDialog({
open: true,
type: props.type,
label: props.label
});
}}
/>
</span>
</div>
);
};
if (!organization) return null;
const views = [
<Paper className={classes.settingsWrapper} key={0}>
<Dialog
open={dialog.open}
onClose={() => setDialog({ open: false })}
aria-labelledby="form-dialog-title"
maxWidth="xs"
fullWidth
>
<DialogTitle id="form-dialog-title">
Add {dialog.label && dialog.label.slice(0, -1)}
</DialogTitle>
<DialogContent>
{dialog.type === 'tags' ? (
<>
<DialogContentText>
Select an existing tag or add a new one.
</DialogContentText>
<Autocomplete
value={tagValue}
onChange={(event, newValue) => {
if (typeof newValue === 'string') {
setTagValue({
name: newValue
});
} else {
setTagValue(newValue);
}
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
// Suggest the creation of a new value
if (
params.inputValue !== '' &&
!filtered.find(
(tag) =>
tag.name?.toLowerCase() ===
params.inputValue.toLowerCase()
)
) {
filtered.push({
name: params.inputValue,
title: `Add "${params.inputValue}"`
});
}
return filtered;
}}
selectOnFocus
clearOnBlur
handleHomeEndKeys
options={tags}
getOptionLabel={(option) => {
return option.name ?? '';
}}
renderOption={(option) => {
if (option.title) return option.title;
return option.name ?? '';
}}
fullWidth
freeSolo
renderInput={(params) => (
<TextField {...params} variant="outlined" />
)}
/>
</>
) : (
<TextField
autoFocus
margin="dense"
id="name"
label={dialog.label && dialog.label.slice(0, -1)}
type="text"
fullWidth
onChange={(e) => setInputValue(e.target.value)}
/>
)}
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={() => setDialog({ open: false })}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
if (dialog.type && dialog.type !== 'tags') {
if (inputValue) {
organization[dialog.type].push(inputValue);
setOrganization({ ...organization });
}
} else {
if (tagValue) {
if (!organization.tags) organization.tags = [];
organization.tags.push(tagValue as any);
setOrganization({ ...organization });
}
}
setDialog({ open: false });
setInputValue('');
setTagValue(null);
}}
>
Add
</Button>
</DialogActions>
</Dialog>
<TextField
value={organization.name}
disabled
variant="filled"
InputProps={{
className: classes.orgName
}}
></TextField>
<ListInput label="Root Domains" type="rootDomains"></ListInput>
<ListInput label="IP Blocks" type="ipBlocks"></ListInput>
<ListInput label="Tags" type="tags"></ListInput>
<div className={classes.headerRow}>
<label>Passive Mode</label>
<span>
<SwitchInput
checked={organization.isPassive}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setOrganization({
...organization,
isPassive: event.target.checked
});
}}
color="primary"
/>
</span>
</div>
<div className={classes.buttons}>
<Link to={`/organizations`}>
<Button
variant="outlined"
style={{ marginRight: '10px', color: '#565C65' }}
>
Cancel
</Button>
</Link>
<Button
variant="contained"
onClick={updateOrganization}
style={{ background: '#565C65', color: 'white' }}
>
Save
</Button>
</div>
</Paper>,
<React.Fragment key={1}>
<Table<Role> columns={userRoleColumns} data={userRoles} />
<Dialog
open={dialog.open}
onClose={() => setDialog({ open: false })}
aria-labelledby="form-dialog-title"
maxWidth="xs"
fullWidth
>
<DialogTitle id="form-dialog-title">Add Member</DialogTitle>
<DialogContent>
<p style={{ color: '#3D4551' }}>
Organization members can view Organization-specific vulnerabilities,
domains, and notes. Organization administrators can additionally
manage members and update the organization.
</p>
<TextField
margin="dense"
id="firstName"
name="firstName"
label="First Name"
type="text"
fullWidth
value={newUserValues.firstName}
onChange={onInviteUserTextChange}
variant="filled"
InputProps={{
className: classes.textField
}}
/>
<TextField
margin="dense"
id="lastName"
name="lastName"
label="Last Name"
type="text"
fullWidth
value={newUserValues.lastName}
onChange={onInviteUserTextChange}
variant="filled"
InputProps={{
className: classes.textField
}}
/>
<TextField
margin="dense"
id="email"
name="email"
label="Email"
type="text"
fullWidth
value={newUserValues.email}
onChange={onInviteUserTextChange}
variant="filled"
InputProps={{
className: classes.textField
}}
/>
<br></br>
<br></br>
<FormLabel component="legend">Role</FormLabel>
<RadioGroup
aria-label="role"
name="role"
value={newUserValues.role}
onChange={onInviteUserTextChange}
>
<FormControlLabel
value="standard"
control={<Radio color="primary" />}
label="Standard"
/>
<FormControlLabel
value="admin"
control={<Radio color="primary" />}
label="Administrator"
/>
</RadioGroup>
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={() => setDialog({ open: false })}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={async () => {
onInviteUserSubmit();
setDialog({ open: false });
}}
>
Add
</Button>
</DialogActions>
</Dialog>
</React.Fragment>,
<React.Fragment key={2}>
<OrganizationList parent={organization}></OrganizationList>
</React.Fragment>,
<React.Fragment key={3}>
<Table<Scan> columns={scanColumns} data={scans} fetchData={fetchScans} />
<h2>Organization Scan History</h2>
<Table<ScanTask> columns={scanTaskColumns} data={scanTasks} />
</React.Fragment>
];
let navItems = [
{
title: 'Settings',
path: `/organizations/${organizationId}`,
exact: true
},
{
title: 'Members',
path: `/organizations/${organizationId}/members`
}
];
if (!organization.parent) {
navItems = navItems.concat([
// { title: 'Teams', path: `/organizations/${organizationId}/teams` },
{ title: 'Scans', path: `/organizations/${organizationId}/scans` }
]);
}
return (
<div>
<div className={classes.header}>
<h1 className={classes.headerLabel}>
<Link to="/organizations">Organizations</Link>
{organization.parent && (
<>
<ChevronRight></ChevronRight>
<Link to={'/organizations/' + organization.parent.id}>
{organization.parent.name}
</Link>
</>
)}
<ChevronRight
style={{
verticalAlign: 'middle',
lineHeight: '100%',
fontSize: '26px'
}}
></ChevronRight>
<span style={{ color: '#07648D' }}>{organization.name}</span>
</h1>
<Subnav
items={navItems}
styles={{
background: '#F9F9F9'
}}
></Subnav>
</div>
<div className={classes.root}>
<Switch>
<Route
path="/organizations/:organizationId"
exact
render={() => views[0]}
/>
<Route
path="/organizations/:organizationId/members"
render={() => views[1]}
/>
<Route
path="/organizations/:organizationId/teams"
render={() => views[2]}
/>
<Route
path="/organizations/:organizationId/scans"
render={() => views[3]}
/>
</Switch>
</div>
</div>
);
}
Example #29
Source File: Header.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
HeaderNoCtx: React.FC<ContextType> = (props) => {
const { searchTerm, setSearchTerm } = props;
const classes = useStyles();
const history = useHistory();
const location = useLocation();
const {
currentOrganization,
setOrganization,
showAllOrganizations,
setShowAllOrganizations,
user,
logout,
apiGet
} = useAuthContext();
const [navOpen, setNavOpen] = useState(false);
const [organizations, setOrganizations] = useState<
(Organization | OrganizationTag)[]
>([]);
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down('md'));
let userLevel = 0;
if (user && user.isRegistered) {
if (user.userType === 'standard') {
userLevel = STANDARD_USER;
} else {
userLevel = GLOBAL_ADMIN;
}
}
const fetchOrganizations = useCallback(async () => {
try {
const rows = await apiGet<Organization[]>('/organizations/');
let tags: (OrganizationTag | Organization)[] = [];
if (userLevel === GLOBAL_ADMIN) {
tags = await apiGet<OrganizationTag[]>('/organizations/tags');
}
setOrganizations(tags.concat(rows));
} catch (e) {
console.error(e);
}
}, [apiGet, setOrganizations, userLevel]);
React.useEffect(() => {
if (userLevel > 0) {
fetchOrganizations();
}
}, [fetchOrganizations, userLevel]);
const navItems: NavItemType[] = [
{
title: 'Overview',
path: '/',
users: ALL_USERS,
exact: true
},
{
title: 'Inventory',
path: '/inventory',
users: ALL_USERS,
exact: false
},
{ title: 'Feeds', path: '/feeds', users: ALL_USERS, exact: false },
{
title: 'Scans',
path: '/scans',
users: GLOBAL_ADMIN,
exact: true
}
].filter(({ users }) => (users & userLevel) > 0);
const userMenu: NavItemType = {
title: (
<div className={classes.userLink}>
<UserIcon /> My Account <ArrowDropDown />
</div>
),
path: '#',
exact: false,
nested: [
{
title: 'Manage Organizations',
path: '/organizations',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Organizations',
path: '/organizations',
users: STANDARD_USER,
exact: true
},
{
title: 'Manage Users',
path: '/users',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Settings',
path: '/settings',
users: ALL_USERS,
exact: true
},
{
title: 'Logout',
path: '/settings',
users: ALL_USERS,
onClick: logout,
exact: true
}
].filter(({ users }) => (users & userLevel) > 0)
};
const userItemsSmall: NavItemType[] = [
{
title: 'My Account',
path: '#',
users: ALL_USERS,
exact: true
},
{
title: 'Manage Organizations',
path: '/organizations',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Organizations',
path: '/organizations',
users: STANDARD_USER,
exact: true
},
{
title: 'Manage Users',
path: '/users',
users: GLOBAL_ADMIN,
exact: true
},
{
title: 'My Settings',
path: '/settings',
users: ALL_USERS,
exact: true
},
{
title: 'Logout',
path: '/',
users: ALL_USERS,
onClick: logout,
exact: true
}
].filter(({ users }) => (users & userLevel) > 0);
const desktopNavItems: JSX.Element[] = navItems.map((item) => (
<NavItem key={item.title.toString()} {...item} />
));
const navItemsToUse = () => {
if (isSmall) {
return userItemsSmall;
} else {
return navItems;
}
};
return (
<div>
<AppBar position="static" elevation={0}>
<div className={classes.inner}>
<Toolbar>
<Link to="/">
<img
src={logo}
className={classes.logo}
alt="Crossfeed Icon Navigate Home"
/>
</Link>
<div className={classes.lgNav}>{desktopNavItems.slice()}</div>
<div className={classes.spacing} />
{userLevel > 0 && (
<>
<SearchBar
initialValue={searchTerm}
value={searchTerm}
onChange={(value) => {
if (location.pathname !== '/inventory')
history.push('/inventory?q=' + value);
setSearchTerm(value, {
shouldClearFilters: false,
autocompleteResults: false
});
}}
/>
{organizations.length > 1 && (
<>
<div className={classes.spacing} />
<Autocomplete
options={[{ name: 'All Organizations' }].concat(
organizations
)}
autoComplete={false}
className={classes.selectOrg}
classes={{
option: classes.option
}}
value={
showAllOrganizations
? { name: 'All Organizations' }
: currentOrganization ?? undefined
}
filterOptions={(options, state) => {
// If already selected, show all
if (
options.find(
(option) =>
option.name.toLowerCase() ===
state.inputValue.toLowerCase()
)
) {
return options;
}
return options.filter((option) =>
option.name
.toLowerCase()
.includes(state.inputValue.toLowerCase())
);
}}
disableClearable
blurOnSelect
selectOnFocus
getOptionLabel={(option) => option.name}
renderOption={(option) => (
<React.Fragment>{option.name}</React.Fragment>
)}
onChange={(
event: any,
value:
| Organization
| {
name: string;
}
| undefined
) => {
if (value && 'id' in value) {
setOrganization(value);
setShowAllOrganizations(false);
} else {
setShowAllOrganizations(true);
}
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
inputProps={{
...params.inputProps,
id: 'autocomplete-input',
autoComplete: 'new-password' // disable autocomplete and autofill
}}
/>
)}
/>
</>
)}
{isSmall ? null : <NavItem {...userMenu} />}
</>
)}
<IconButton
edge="start"
className={classes.menuButton}
aria-label="toggle mobile menu"
color="inherit"
onClick={() => setNavOpen((open) => !open)}
>
<MenuIcon />
</IconButton>
</Toolbar>
</div>
</AppBar>
<Drawer
anchor="right"
open={navOpen}
onClose={() => setNavOpen(false)}
data-testid="mobilenav"
>
<List className={classes.mobileNav}>
{navItemsToUse().map(({ title, path, nested, onClick }) => (
<React.Fragment key={title.toString()}>
{path && (
<ListItem
button
exact
component={NavLink}
to={path}
activeClassName={classes.activeMobileLink}
onClick={onClick ? onClick : undefined}
>
{title}
</ListItem>
)}
{nested?.map((nested) => (
<ListItem
button
exact
key={nested.title.toString()}
component={NavLink}
to={nested.onClick ? '#' : nested.path}
activeClassName={classes.activeMobileLink}
onClick={nested.onClick ? nested.onClick : undefined}
>
{nested.title}
</ListItem>
))}
</React.Fragment>
))}
</List>
</Drawer>
</div>
);
}