import React, { useCallback } from "react"; import classNames from "classnames"; import { FieldArray } from "formik"; import { Droppable } from "react-beautiful-dnd"; import { DraggableCore, DraggableData, DraggableEvent } from "react-draggable"; import { HotKeys } from "react-hotkeys"; import { TextField, Icon } from "@amplication/design-system"; import { Button, EnumButtonStyle } from "../Components/Button"; import * as models from "../models"; import { DATA_TYPE_TO_LABEL_AND_ICON } from "../Entity/constants"; import { CLASS_NAME, COMMON_FIELDS, EntityPositionData, FieldIdentifier, keyMap, } from "./EntitiesDiagram"; import { EntitiesDiagramField } from "./EntitiesDiagramField"; import { EntitiesDiagramStaticField } from "./EntitiesDiagramStaticField"; type Props = { entity: models.AppCreateWithEntitiesEntityInput; entityIndex: number; editedFieldIdentifier: FieldIdentifier | null; editedEntity: number | null; positionData: EntityPositionData; zoomLevel: number; onDrag?: (entityIndex: number, data: DraggableData) => void; onEditField: (fieldIdentifier: FieldIdentifier | null) => void; onEditEntity: (entityIndex: number | null) => void; onDeleteEntity: (entityIndex: number) => void; onAddEntity: (entityIndex: number) => void; }; export const EntitiesDiagramEntity = React.memo( ({ entity, entityIndex, editedFieldIdentifier, editedEntity, positionData, zoomLevel, onDrag, onEditField, onEditEntity, onDeleteEntity, onAddEntity, }: Props) => { const handleAddEntity = useCallback(() => { onAddEntity && onAddEntity(entityIndex); }, [entityIndex, onAddEntity]); const handleDeleteEntity = useCallback(() => { onDeleteEntity && onDeleteEntity(entityIndex); }, [entityIndex, onDeleteEntity]); const handleDrag = useCallback( (e: DraggableEvent, data: DraggableData) => { onDrag && onDrag(entityIndex, data); }, [onDrag, entityIndex] ); const handleEditEntity = useCallback(() => { onEditEntity && onEditEntity(entityIndex); }, [onEditEntity, entityIndex]); const selected = entityIndex === editedEntity; const handlers = { CLOSE_MODAL: () => { onEditEntity(null); }, }; return ( <DraggableCore handle=".handle" onDrag={handleDrag}> <div id={`entity${entityIndex}`} className={`${CLASS_NAME}__entities__entity`} style={{ top: positionData?.top, left: positionData?.left }} > <div> <HotKeys keyMap={keyMap} handlers={handlers}> <div className={`${CLASS_NAME}__entities__entity__name-wrapper `}> {selected ? ( <TextField name={`entities.${entityIndex}.name`} autoFocus autoComplete="off" label="" placeholder="Entity Name" required /> ) : ( <> <div className={classNames( `${CLASS_NAME}__entities__entity__name`, "handle" )} > <Icon icon="database" /> <span>{entity.name}</span> <span className="spacer" /> </div> <span className="spacer" /> {entityIndex > 0 && ( <Button className={`${CLASS_NAME}__entities__entity__delete`} buttonStyle={EnumButtonStyle.Clear} type="button" onClick={handleDeleteEntity} icon="trash_2" /> )} <Button className={`${CLASS_NAME}__entities__entity__edit`} buttonStyle={EnumButtonStyle.Clear} type="button" onClick={handleEditEntity} icon="edit_2" /> </> )} <Button className={`${CLASS_NAME}__entities__entity__add`} buttonStyle={EnumButtonStyle.Primary} onClick={handleAddEntity} type="button" icon="plus" /> </div> </HotKeys> <FieldArray name={`entities.${entityIndex}.fields`} render={(fieldsArrayHelpers) => ( <div className={`${CLASS_NAME}__fields`}> {COMMON_FIELDS.map((field, fieldIndex) => ( <EntitiesDiagramStaticField key={`static_${entityIndex}_${fieldIndex}`} field={field} /> ))} <Droppable droppableId={`droppable_${entityIndex}`} //we use re-parenting to allow frag when the canvas is scaled (using transform: scale()) //https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/reparenting.md renderClone={(provided, snapshot, rubric) => ( <div {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} > <div style={{ transform: `scale(${zoomLevel})` }} className={classNames( `${CLASS_NAME}__fields__field`, `${CLASS_NAME}__fields__field--dragged` )} > <Icon size="xsmall" icon={ DATA_TYPE_TO_LABEL_AND_ICON[ entity.fields[rubric.source.index].dataType || models.EnumDataType.SingleLineText ].icon } /> <span>{entity.fields[rubric.source.index].name}</span> <span className="spacer" /> </div> </div> )} > {(provided, snapshot) => ( <div {...provided.droppableProps} ref={provided.innerRef} className={classNames(`${CLASS_NAME}__droppable`, { [`${CLASS_NAME}__droppable--over`]: snapshot.isDraggingOver, })} > {entity.fields.map((field, fieldIndex) => ( <EntitiesDiagramField key={`${entityIndex}_${fieldIndex}`} field={field} entityIndex={entityIndex} fieldIndex={fieldIndex} onEdit={onEditField} editedFieldIdentifier={editedFieldIdentifier} /> ))} {provided.placeholder} </div> )} </Droppable> </div> )} /> </div> </div> </DraggableCore> ); } );