in src/components/custom-fields-panel/custom-fields-panel.tsx [95:301]
export default function CustomFieldsPanel(props: Props) {
const api: Api = getApi();
let currentScrollX: number = 0;
const isComponentMounted = React.useRef<boolean>(false);
const isConnected = useSelector((state: AppState) => state.app.networkState?.isConnected);
const user = useSelector((state: AppState) => state.app.user);
const isReporter = !!user?.profiles?.helpdesk?.isReporter;
const [selectState, setSelectState] = React.useState<SelectState | null>(null);
const [simpleValueState, setSimpleValue] = React.useState<SimpleValueState | null>(null);
const [datePickerState, setDatePickerState] = React.useState<DatePickerState>(dataPickerDefault);
const [isEditingProject, setEditingProject] = React.useState<boolean>(false);
const [isSavingProject, setSavingProject] = React.useState<boolean>(false);
const [editingField, setEditingField] = React.useState<IssueCustomField | null>(null);
const [savingField, setSavingField] = React.useState<IssueCustomField | null>(null);
React.useEffect(() => {
isComponentMounted.current = true;
return () => {
isComponentMounted.current = false;
};
}, []);
React.useEffect(() => {
isComponentMounted.current = true;
return () => {
isComponentMounted.current = false;
};
}, []);
const trackEvent: (message: string) => void = (message: string) => {
if (props.analyticsId) {
usage.trackEvent(props.analyticsId, message);
}
};
const closeEditor = () => {
setEditingField(null);
setEditingProject(false);
setSelectState(null);
setSimpleValue(null);
setDatePickerState(dataPickerDefault);
};
const saveUpdatedField = async (field: IssueCustomField, value: CustomFieldBaseValue) => {
const updateSavingState = (f: IssueCustomField | null) => {
if (isComponentMounted) {
setSavingField(f);
}
};
closeEditor();
updateSavingState(field);
await props.onUpdate(field, value);
updateSavingState(null);
};
const onSelectProject = () => {
trackEvent('Update project: start');
if (isEditingProject) {
return closeEditor();
}
const {hasPermission, helpDeskProjectsOnly} = props;
closeEditor();
setEditingProject(true);
setSelectState({
show: true,
getValue: (it) => {
const p = it as Project;
return `${p.name} (${p.shortName})`;
},
dataSource: async query => {
const projects = await api.getProjects(query);
return projects
.filter(project =>
helpDeskProjectsOnly
? project?.plugins?.helpDeskSettings?.enabled
: !project.plugins?.helpDeskSettings?.enabled
)
.filter(project => !project.archived && !project.template)
.filter(project => hasPermission?.canCreateIssueToProject?.(project));
},
multi: false,
placeholder: i18n('Search for the project'),
selectedItems: [props.issueProject],
onSelect: (project: Project) => {
trackEvent('Update project: updated');
closeEditor();
setSavingProject(true);
return props.onUpdateProject(project).then(() => setSavingProject(false));
},
});
};
const editDateField = (field: IssueCustomField) => {
trackEvent('Edit date field');
const projectCustomField = field.projectCustomField;
const withTime = projectCustomField.field.fieldType.valueType === DATE_AND_TIME_FIELD_VALUE_TYPE;
const date = field.value ? new Date(field.value as number) : null;
return setDatePickerState({
show: true,
placeholder: i18n('Enter time value'),
withTime,
title: projectCustomField.field.name,
date,
emptyValueName: projectCustomField.canBeEmpty ? projectCustomField.emptyFieldText : null,
onSelect: (d: Date | null) => {
if (!d) {
saveUpdatedField(field, null);
} else {
saveUpdatedField(field, d.getTime());
}
},
});
};
const editSimpleValueField = (field: IssueCustomField, type: keyof typeof customFieldPlaceholders) => {
trackEvent('Edit simple value field');
const placeholder: string = customFieldPlaceholders[type] || customFieldPlaceholders.default;
const valueFormatter = customFieldValueFormatters[type] || customFieldValueFormatters.default;
const value: string =
field.value != null
? (field.value as PeriodFieldValue)?.presentation || (field.value as TextFieldValue)?.text || `${field.value}`
: '';
setSimpleValue({
show: true,
placeholder,
value,
onApply: (v: string) => {
saveUpdatedField(field, valueFormatter(v) as CustomFieldValue);
},
});
};
const editCustomField = (field: IssueCustomField) => {
const projectCustomField = field.projectCustomField;
const projectCustomFieldName: string | null | undefined = projectCustomField?.field?.name;
trackEvent(`Edit custom field: ${projectCustomFieldName ? projectCustomFieldName.toLowerCase() : ''}`);
const p = getCustomFieldSelectProps({
field,
issueId: props.issueId,
onChangeSelection: selectedItems => {
setSelectState(prevState => ({...prevState, ...selectState, selectedItems}));
},
onSelect: value => {
saveUpdatedField(field, value);
},
});
setSelectState(p);
};
const onEditField = (field: IssueCustomField) => {
if (field === editingField) {
return closeEditor();
}
const {fieldType} = field.projectCustomField?.field;
if (!fieldType) {
return null;
}
setEditingField(field);
setEditingProject(false);
if (fieldType.valueType === 'date' || fieldType.valueType === DATE_AND_TIME_FIELD_VALUE_TYPE) {
return editDateField(field);
}
if (['period', 'integer', 'string', 'text', 'float'].indexOf(fieldType.valueType) !== -1) {
return editSimpleValueField(field, fieldType.valueType as keyof typeof customFieldPlaceholders);
}
return editCustomField(field);
};
const storeScrollPosition = (event: Record<string, any>) => {
const {nativeEvent} = event;
currentScrollX = nativeEvent.contentOffset.x;
};
const restoreScrollPosition = (scrollNode?: ScrollView | null, ensure: boolean = true) => {
if (!scrollNode || !currentScrollX) {
return;
}
scrollNode.scrollTo({
x: currentScrollX,
y: 0,
animated: false,
});
// Android doesn't get first scrollTo call https://youtrack.jetbrains.com/issue/YTM-402
// iOS doesn't scroll immediately since 0.48 https://github.com/facebook/react-native/issues/15808
if (ensure) {
setTimeout(() => restoreScrollPosition(scrollNode, false));
}
};
const renderSelect = () => {
const Component : React.ElementType = isSplitView() ? SelectModal : Select;
return <Component {...selectState} autoFocus={props.autoFocusSelect} onCancel={() => closeEditor()} />;
};