mirror of
https://github.com/apache/superset.git
synced 2026-05-07 17:04:58 +00:00
285 lines
7.2 KiB
TypeScript
285 lines
7.2 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { FunctionComponent, useState, useEffect, ChangeEvent } from 'react';
|
|
|
|
import { t } from '@apache-superset/core/translation';
|
|
import { styled } from '@apache-superset/core/theme';
|
|
import { useSingleViewResource } from 'src/views/CRUD/hooks';
|
|
import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
|
|
import { Typography } from '@superset-ui/core/components/Typography';
|
|
import { Input, Modal } from '@superset-ui/core/components';
|
|
import withToasts from 'src/components/MessageToasts/withToasts';
|
|
|
|
import { OnlyKeyWithType } from 'src/utils/types';
|
|
import { AnnotationLayerObject } from './types';
|
|
|
|
type AnnotationLayerObjectStringKeys = keyof Pick<
|
|
AnnotationLayerObject,
|
|
OnlyKeyWithType<AnnotationLayerObject, string>
|
|
>;
|
|
|
|
interface AnnotationLayerModalProps {
|
|
addDangerToast: (msg: string) => void;
|
|
addSuccessToast: (msg: string) => void;
|
|
layer?: AnnotationLayerObject | null;
|
|
onLayerAdd?: (layer?: AnnotationLayerObject) => void;
|
|
onHide: () => void;
|
|
show: boolean;
|
|
}
|
|
|
|
const StyledAnnotationLayerTitle = styled.div`
|
|
margin: ${({ theme }) => theme.sizeUnit * 2}px auto
|
|
${({ theme }) => theme.sizeUnit * 4}px auto;
|
|
`;
|
|
|
|
const LayerContainer = styled.div`
|
|
margin-bottom: ${({ theme }) => theme.sizeUnit * 10}px;
|
|
|
|
.control-label {
|
|
margin-bottom: ${({ theme }) => theme.sizeUnit * 2}px;
|
|
}
|
|
|
|
.required {
|
|
margin-left: ${({ theme }) => theme.sizeUnit / 2}px;
|
|
color: ${({ theme }) => theme.colorError};
|
|
}
|
|
|
|
textarea,
|
|
input[type='text'] {
|
|
padding: ${({ theme }) => theme.sizeUnit * 1.5}px
|
|
${({ theme }) => theme.sizeUnit * 2}px;
|
|
border: 1px solid ${({ theme }) => theme.colorBorder};
|
|
border-radius: ${({ theme }) => theme.borderRadius}px;
|
|
width: 50%;
|
|
}
|
|
|
|
input,
|
|
textarea {
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
textarea {
|
|
width: 100%;
|
|
height: 160px;
|
|
resize: none;
|
|
}
|
|
|
|
input::placeholder,
|
|
textarea::placeholder {
|
|
color: ${({ theme }) => theme.colorTextPlaceholder};
|
|
}
|
|
`;
|
|
|
|
const AnnotationLayerModal: FunctionComponent<AnnotationLayerModalProps> = ({
|
|
addDangerToast,
|
|
addSuccessToast,
|
|
onLayerAdd,
|
|
onHide,
|
|
show,
|
|
layer = null,
|
|
}) => {
|
|
const [disableSave, setDisableSave] = useState<boolean>(true);
|
|
const [currentLayer, setCurrentLayer] =
|
|
useState<AnnotationLayerObject | null>();
|
|
const [isHidden, setIsHidden] = useState<boolean>(true);
|
|
const isEditMode = layer !== null;
|
|
|
|
// annotation layer fetch logic
|
|
const {
|
|
state: { loading, resource },
|
|
fetchResource,
|
|
createResource,
|
|
updateResource,
|
|
} = useSingleViewResource<AnnotationLayerObject>(
|
|
'annotation_layer',
|
|
t('annotation_layer'),
|
|
addDangerToast,
|
|
);
|
|
|
|
const resetLayer = () => {
|
|
// Reset layer
|
|
setCurrentLayer({
|
|
name: '',
|
|
descr: '',
|
|
});
|
|
};
|
|
|
|
// Functions
|
|
const hide = () => {
|
|
setIsHidden(true);
|
|
|
|
// Reset layer
|
|
resetLayer();
|
|
|
|
onHide();
|
|
};
|
|
|
|
const onSave = () => {
|
|
if (isEditMode) {
|
|
// Edit
|
|
if (currentLayer?.id) {
|
|
const update_id = currentLayer.id;
|
|
delete currentLayer.id;
|
|
delete currentLayer.created_by;
|
|
updateResource(update_id, currentLayer).then(response => {
|
|
if (!response) {
|
|
return;
|
|
}
|
|
|
|
hide();
|
|
addSuccessToast(t('Annotation template updated'));
|
|
});
|
|
}
|
|
} else if (currentLayer) {
|
|
// Create
|
|
createResource(currentLayer).then(response => {
|
|
if (!response) {
|
|
return;
|
|
}
|
|
|
|
if (onLayerAdd) {
|
|
onLayerAdd(response);
|
|
}
|
|
|
|
hide();
|
|
addSuccessToast(t('Annotation template created'));
|
|
});
|
|
}
|
|
};
|
|
|
|
const onTextChange = (
|
|
event: ChangeEvent<HTMLTextAreaElement> | ChangeEvent<HTMLInputElement>,
|
|
) => {
|
|
const { target } = event;
|
|
const data = {
|
|
...currentLayer,
|
|
name: currentLayer ? currentLayer.name : '',
|
|
descr: currentLayer ? currentLayer.descr : '',
|
|
};
|
|
|
|
data[target.name as AnnotationLayerObjectStringKeys] = target.value;
|
|
setCurrentLayer(data);
|
|
};
|
|
|
|
const validate = () => {
|
|
if (currentLayer?.name?.length) {
|
|
setDisableSave(false);
|
|
} else {
|
|
setDisableSave(true);
|
|
}
|
|
};
|
|
|
|
// Initialize
|
|
useEffect(() => {
|
|
if (
|
|
isEditMode &&
|
|
(!currentLayer?.id ||
|
|
(layer && layer.id !== currentLayer.id) ||
|
|
(isHidden && show))
|
|
) {
|
|
if (show && layer && layer.id !== null && !loading) {
|
|
const id = layer.id || 0;
|
|
|
|
fetchResource(id);
|
|
}
|
|
} else if (
|
|
!isEditMode &&
|
|
(!currentLayer || currentLayer.id || (isHidden && show))
|
|
) {
|
|
// Reset layer
|
|
resetLayer();
|
|
}
|
|
}, [layer, show]);
|
|
|
|
useEffect(() => {
|
|
if (resource) {
|
|
setCurrentLayer(resource);
|
|
}
|
|
}, [resource]);
|
|
|
|
// Validation
|
|
useEffect(() => {
|
|
validate();
|
|
}, [
|
|
currentLayer ? currentLayer.name : '',
|
|
currentLayer ? currentLayer.descr : '',
|
|
]);
|
|
|
|
// Show/hide
|
|
if (isHidden && show) {
|
|
setIsHidden(false);
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
disablePrimaryButton={disableSave}
|
|
onHandledPrimaryAction={onSave}
|
|
onHide={hide}
|
|
primaryButtonName={isEditMode ? t('Save') : t('Add')}
|
|
show={show}
|
|
width="55%"
|
|
name={
|
|
isEditMode
|
|
? t('Edit annotation layer properties')
|
|
: t('Add annotation layer')
|
|
}
|
|
title={
|
|
<ModalTitleWithIcon
|
|
isEditMode={isEditMode}
|
|
title={
|
|
isEditMode
|
|
? t('Edit annotation layer properties')
|
|
: t('Add annotation layer')
|
|
}
|
|
data-test="annotation-layer-modal-title"
|
|
/>
|
|
}
|
|
>
|
|
<StyledAnnotationLayerTitle>
|
|
<Typography.Title level={4}>
|
|
{t('General information')}
|
|
</Typography.Title>
|
|
</StyledAnnotationLayerTitle>
|
|
<LayerContainer>
|
|
<div className="control-label">
|
|
{t('Annotation layer name')}
|
|
<span className="required">*</span>
|
|
</div>
|
|
<Input
|
|
name="name"
|
|
onChange={onTextChange}
|
|
type="text"
|
|
value={currentLayer?.name}
|
|
/>
|
|
</LayerContainer>
|
|
<LayerContainer>
|
|
<div className="control-label">{t('description')}</div>
|
|
<Input.TextArea
|
|
name="descr"
|
|
value={currentLayer?.descr}
|
|
placeholder={t('Description (this can be seen in the list)')}
|
|
onChange={onTextChange}
|
|
/>
|
|
</LayerContainer>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default withToasts(AnnotationLayerModal);
|