import * as React from 'react'; import { EventObserver } from '../viewUtils/events'; import { HtmlSpinner } from '../viewUtils/spinner'; import { Element } from '../diagram/elements'; import { DiagramView } from '../diagram/view'; import { Vector } from '../diagram/geometry'; import { EditorController } from './editorController'; import { ElementChange } from './authoringState'; import { ElementValidation, LinkValidation } from './validation'; const CLASS_NAME = `ontodia-authoring-state`; export interface ElementDecoratorProps { model: Element; view: DiagramView; editor: EditorController; position: Vector; } interface State { state?: ElementChange; validation?: ElementValidation; isTemporary?: boolean; } export class ElementDecorator extends React.Component { private readonly listener = new EventObserver(); constructor(props: ElementDecoratorProps) { super(props); const {model, editor} = props; this.state = { state: editor.authoringState.elements.get(model.iri), validation: editor.validationState.elements.get(model.iri), isTemporary: editor.temporaryState.elements.has(model.iri), }; } componentDidMount() { const {model, editor} = this.props; this.listener.listen(model.events, 'changeSize', () => this.forceUpdate() ); this.listener.listen(editor.events, 'changeAuthoringState', e => { const state = editor.authoringState.elements.get(model.iri); if (state === e.previous.elements.get(model.iri)) { return; } this.setState({state}); }); this.listener.listen(editor.events, 'changeValidationState', e => { const validation = editor.validationState.elements.get(model.iri); if (validation === e.previous.elements.get(model.iri)) { return; } this.setState({validation}); }); this.listener.listen(editor.events, 'changeTemporaryState', e => { const isTemporary = editor.temporaryState.elements.has(model.iri); if (isTemporary === e.previous.elements.has(model.iri)) { return; } this.setState({isTemporary}); }); this.listener.listen(model.events, 'changeData', e => { if (e.previous.id !== model.iri) { this.setState({ isTemporary: editor.temporaryState.elements.has(model.iri), validation: editor.validationState.elements.get(model.iri), state: editor.authoringState.elements.get(model.iri), }); } }); } componentWillUnmount() { this.listener.stopListening(); } shouldComponentUpdate(nextProps: ElementDecoratorProps, nextState: State) { return ( this.state.state !== nextState.state || this.state.validation !== nextState.validation || this.state.isTemporary !== nextState.isTemporary || this.props.position !== nextProps.position ); } private renderElementOutlines() { const {model} = this.props; const {state, isTemporary} = this.state; const {width, height} = model.size; if (isTemporary) { return [ , ]; } if (state && state.deleted) { const right = width; const bottom = height; return ( ); } return null; } private renderErrorIcon(title: string, validation: LinkValidation | ElementValidation) { return
{validation.loading ? :
} {(!validation.loading && validation.errors.length > 0) ? validation.errors.length : undefined}
; } private renderElementErrors() { const {view} = this.props; const {validation} = this.state; if (!validation) { return null; } const title = validation.errors.map(error => { if (error.propertyType) { const {id, label} = view.model.createProperty(error.propertyType); const source = view.formatLabel(label, id); return `${source}: ${error.message}`; } else { return error.message; } }).join('\n'); return this.renderErrorIcon(title, validation); } private renderElementState() { const {model, editor} = this.props; const {state} = this.state; if (state) { const onCancel = () => editor.discardChange(state); let renderedState: React.ReactElement; let statusText: string; let title: string; if (state.deleted) { statusText = 'Delete'; title = 'Revert deletion of the element'; } else if (!state.before) { statusText = 'New'; title = 'Revert creation of the element'; } else { statusText = 'Change'; title = 'Revert all changes in properties of the element'; } if (statusText && title) { renderedState = ( {statusText} [cancel] ); } const renderedErrors = this.renderElementErrors(); if (renderedState || renderedErrors) { return (
{renderedState} {renderedErrors}
); } } return null; } render() { const {position, size} = this.props.model; const transform = `translate(${position.x}px,${position.y}px)`; const outlines = this.renderElementOutlines(); const state = this.renderElementState(); if (!outlines && !state) { return null; } return (
{outlines ? ( {this.renderElementOutlines()} ) : null} {state}
); } }