import * as React from 'react'; import { BubbleType, Actions, DocSpec, MenuItemSpec, Xml } from './types'; import { askLongString } from './Util'; interface Props { actions: Actions; attribute: string; docSpec: DocSpec; element: string; id: string[]; left: number; mode: 'laic' | 'nerd'; show: boolean; top: number; type: BubbleType; value: string; xml: Xml; } const formatCaption = (caption: string) => { let key = 1; return caption.split(/((?:<\/?[^>]+\/?>)|(?:@[^ =]+(?:="[^"]*")?))/).map((piece) => { const elementMatches = piece.match(/<(\/?)([^>/]+)(\/?)>/); const attrMatches = piece.match(/@(?:([^ ="]+))?=?(?:"([^"]*)")?/); if (elementMatches) { return ( <{ elementMatches[1] } { elementMatches[2] } { elementMatches[3] }> ); } else if (attrMatches) { let name; if (attrMatches[1]) { name = { attrMatches[1] }; } let value; if (typeof attrMatches[2] !== 'undefined') { let equals; if (name) { equals = =; } value = ( { equals } " { attrMatches[2] && { attrMatches[2] } } " ); } return ( { name } { value } ); } return piece; }); }; export default class Bubble extends React.Component { public constructor(props: Props) { super(props); this.showMenuItem = this.showMenuItem.bind(this); } public render(): React.ReactNode { const { id, show, } = this.props; if (!show) { return null; } if (id.length > 2 && id[id.length - 2] === '$') { return this.getAttributeBubble(); } else if (id.length > 2 && id[id.length - 2] === '$$') { return this.getElementBubble(); } else if (id.length > 1 && id[id.length - 1] === '_') { return this.getTextBubble(); } return null; } private getTextBubble(): React.ReactNode { const { actions, docSpec, element, id, left, mode, top, value, xml } = this.props; const asker = docSpec.elements?.[element]?.asker || askLongString; return ( { asker({ actions, defaultValue: value, id, xml, }) } ); } private getElementBubble(): React.ReactNode { const { actions, docSpec, element, id, left, mode, top, xml } = this.props; const menu = docSpec.elements?.[element]?.menu; if (menu) { const menuItems = menu.filter(this.showMenuItem).map((menuItemSpec, index) => { let icon; if (menuItemSpec.icon) { icon = ( ); } return ( { actions.setXml(await menuItemSpec.action(xml, id)); actions.showBubble({ show: false }); } } > { icon } { formatCaption(menuItemSpec.caption) } ); }); return ( {menuItems} ); } return null; } private getAttributeBubble(): React.ReactNode { const { type } = this.props; if (type === BubbleType.ASKER) { return this.getAttributeAskerBubble(); } return this.getAttributeMenuBubble(); } private getAttributeMenuBubble(): React.ReactNode { const { actions, attribute, docSpec, element, id, left, mode, top, xml } = this.props; const menu = docSpec.elements?.[element]?.attributes?.[attribute]?.menu; if (menu) { const menuItems = menu.filter(this.showMenuItem).map((menuItemSpec, index) => ( { actions.setXml(await menuItemSpec.action(xml, id)); actions.showBubble({ show: false }); } } > { menuItemSpec.caption } )); return ( {menuItems} ); } return null; } private getAttributeAskerBubble(): React.ReactNode { const { actions, attribute, docSpec, element, id, left, mode, top, value, xml } = this.props; const attributeSpec = docSpec.elements?.[element]?.attributes?.[attribute]; if (attributeSpec && attributeSpec.asker) { const asker = attributeSpec.asker; return ( { asker({ actions, defaultValue: value, id, xml, }) } ); } return null; } private showMenuItem(menuItemSpec: MenuItemSpec): boolean { const { id, xml } = this.props; if (menuItemSpec.hideIf) { return !menuItemSpec.hideIf(xml, id); } return true; } }