import React from "react"; import { Form, Col, Row, InputGroup, Button, FormControl, Badge, } from "react-bootstrap"; import { CollapseButton } from "./CollapseButton"; import { Icon } from "./Icon"; import { ModalComponent } from "./Modal"; import isEqual from "lodash-es/isEqual"; import clone from "lodash-es/clone"; import { FormattedMessage, useIntl } from "react-intl"; import { faCheck, faPencilAlt, faPlus, faTimes, } from "@fortawesome/free-solid-svg-icons"; import { getCountryByISO } from "src/utils/country"; import { CountrySelect } from "./CountrySelect"; export class ScrapeResult { public newValue?: T; public originalValue?: T; public scraped: boolean = false; public useNewValue: boolean = false; public constructor( originalValue?: T | null, newValue?: T | null, useNewValue?: boolean ) { this.originalValue = originalValue ?? undefined; this.newValue = newValue ?? undefined; const hasNewValue = this.newValue !== undefined; const valuesEqual = isEqual(originalValue, newValue); this.useNewValue = useNewValue ?? (hasNewValue && !valuesEqual); this.scraped = hasNewValue && !valuesEqual; } public setOriginalValue(value?: T) { this.originalValue = value; this.newValue = value; } public cloneWithValue(value?: T) { const ret = clone(this); ret.newValue = value; ret.useNewValue = !isEqual(ret.newValue, ret.originalValue); // #2691 - if we're setting the value, assume it should be treated as // scraped ret.scraped = true; return ret; } public getNewValue() { if (this.useNewValue) { return this.newValue; } } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function hasScrapedValues(values: ScrapeResult[]) { return values.some((r) => r.scraped); } export interface IHasName { name: string | undefined; } interface IScrapedFieldProps { result: ScrapeResult; } interface IScrapedRowProps extends IScrapedFieldProps { title: string; renderOriginalField: (result: ScrapeResult) => JSX.Element | undefined; renderNewField: (result: ScrapeResult) => JSX.Element | undefined; onChange: (value: ScrapeResult) => void; newValues?: V[]; onCreateNew?: (index: number) => void; } function renderButtonIcon(selected: boolean) { const className = selected ? "text-success" : "text-muted"; return ( ); } export const ScrapeDialogRow = ( props: IScrapedRowProps ) => { function handleSelectClick(isNew: boolean) { const ret = clone(props.result); ret.useNewValue = isNew; props.onChange(ret); } function hasNewValues() { return props.newValues && props.newValues.length > 0 && props.onCreateNew; } if (!props.result.scraped && !hasNewValues()) { return <>; } function renderNewValues() { if (!hasNewValues()) { return; } const ret = ( <> {props.newValues!.map((t, i) => ( props.onCreateNew!(i)} > {t.name} ))} ); const minCollapseLength = 10; if (props.newValues!.length >= minCollapseLength) { return ( {ret} ); } return ret; } return ( {props.title} {props.renderOriginalField(props.result)} {props.renderNewField(props.result)} {renderNewValues()} ); }; interface IScrapedInputGroupProps { isNew?: boolean; placeholder?: string; locked?: boolean; result: ScrapeResult; onChange?: (value: string) => void; } const ScrapedInputGroup: React.FC = (props) => { return ( { if (props.isNew && props.onChange) { props.onChange(e.target.value); } }} className="bg-secondary text-white border-secondary" /> ); }; interface IScrapedInputGroupRowProps { title: string; placeholder?: string; result: ScrapeResult; locked?: boolean; onChange: (value: ScrapeResult) => void; } export const ScrapedInputGroupRow: React.FC = ( props ) => { return ( ( )} renderNewField={() => ( props.onChange(props.result.cloneWithValue(value)) } /> )} onChange={props.onChange} /> ); }; const ScrapedTextArea: React.FC = (props) => { return ( { if (props.isNew && props.onChange) { props.onChange(e.target.value); } }} className="bg-secondary text-white border-secondary scene-description" /> ); }; export const ScrapedTextAreaRow: React.FC = ( props ) => { return ( ( )} renderNewField={() => ( props.onChange(props.result.cloneWithValue(value)) } /> )} onChange={props.onChange} /> ); }; interface IScrapedImageProps { isNew?: boolean; className?: string; placeholder?: string; result: ScrapeResult; } const ScrapedImage: React.FC = (props) => { const value = props.isNew ? props.result.newValue : props.result.originalValue; if (!value) { return <>; } return ( {props.placeholder} ); }; interface IScrapedImageRowProps { title: string; className?: string; result: ScrapeResult; onChange: (value: ScrapeResult) => void; } export const ScrapedImageRow: React.FC = (props) => { return ( ( )} renderNewField={() => ( )} onChange={props.onChange} /> ); }; interface IScrapeDialogProps { title: string; existingLabel?: string; scrapedLabel?: string; renderScrapeRows: () => JSX.Element; onClose: (apply?: boolean) => void; } export const ScrapeDialog: React.FC = ( props: IScrapeDialogProps ) => { const intl = useIntl(); return ( { props.onClose(true); }, text: intl.formatMessage({ id: "actions.apply" }), }} cancel={{ onClick: () => props.onClose(), text: intl.formatMessage({ id: "actions.cancel" }), variant: "secondary", }} modalProps={{ size: "lg", dialogClassName: "scrape-dialog" }} >
{props.existingLabel ?? ( )} {props.scrapedLabel ?? ( )} {props.renderScrapeRows()}
); }; interface IScrapedCountryRowProps { title: string; result: ScrapeResult; onChange: (value: ScrapeResult) => void; locked?: boolean; locale?: string; } export const ScrapedCountryRow: React.FC = ({ title, result, onChange, locked, locale, }) => ( ( )} renderNewField={() => ( { if (onChange) { onChange(result.cloneWithValue(value)); } }} showFlag={false} isClearable={false} className="flex-grow-1" /> )} onChange={onChange} /> );