import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import styled from '@emotion/styled'; import { Map, List } from 'immutable'; import { once } from 'lodash'; import uuid from 'uuid/v4'; import { oneLine } from 'common-tags'; import { lengths, components, buttons, borders, effects, shadows } from 'netlify-cms-ui-default'; import { basename } from 'netlify-cms-lib-util'; const MAX_DISPLAY_LENGTH = 50; const ImageWrapper = styled.div` flex-basis: 155px; width: 155px; height: 100px; margin-right: 20px; margin-bottom: 20px; border: ${borders.textField}; border-radius: ${lengths.borderRadius}; overflow: hidden; ${effects.checkerboard}; ${shadows.inset}; `; const StyledImage = styled.img` width: 100%; height: 100%; object-fit: contain; `; const Image = props => ; const MultiImageWrapper = styled.div` display: flex; flex-wrap: wrap; `; const FileLink = styled.a` margin-bottom: 20px; font-weight: normal; color: inherit; &:hover, &:active, &:focus { text-decoration: underline; } `; const FileLinks = styled.div` margin-bottom: 12px; `; const FileLinkList = styled.ul` list-style-type: none; `; const FileWidgetButton = styled.button` ${buttons.button}; ${components.badge}; `; const FileWidgetButtonRemove = styled.button` ${buttons.button}; ${components.badgeDanger}; margin-top: 12px; `; function isMultiple(value) { return Array.isArray(value) || List.isList(value); } const warnDeprecatedOptions = once(field => console.warn(oneLine` Netlify CMS config: ${field.get('name')} field: property "options" has been deprecated for the ${field.get('widget')} widget and will be removed in the next major release. Rather than \`field.options.media_library\`, apply media library options for this widget under \`field.media_library\`. `), ); export default function withFileControl({ forImage } = {}) { return class FileControl extends React.Component { static propTypes = { field: PropTypes.object.isRequired, getAsset: PropTypes.func.isRequired, mediaPaths: ImmutablePropTypes.map.isRequired, onAddAsset: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, onRemoveInsertedMedia: PropTypes.func.isRequired, onOpenMediaLibrary: PropTypes.func.isRequired, onClearMediaControl: PropTypes.func.isRequired, onRemoveMediaControl: PropTypes.func.isRequired, classNameWrapper: PropTypes.string.isRequired, value: PropTypes.oneOfType([ PropTypes.string, PropTypes.arrayOf(PropTypes.string), ImmutablePropTypes.listOf(PropTypes.string), ]), t: PropTypes.func.isRequired, }; static defaultProps = { value: '', }; constructor(props) { super(props); this.controlID = uuid(); } shouldComponentUpdate(nextProps) { /** * Always update if the value or getAsset changes. */ if (this.props.value !== nextProps.value || this.props.getAsset !== nextProps.getAsset) { return true; } /** * If there is a media path for this control in the state object, and that * path is different than the value in `nextProps`, update. */ const mediaPath = nextProps.mediaPaths.get(this.controlID); if (mediaPath && nextProps.value !== mediaPath) { return true; } return false; } componentDidUpdate() { const { mediaPaths, value, onRemoveInsertedMedia, onChange } = this.props; const mediaPath = mediaPaths.get(this.controlID); if (mediaPath && mediaPath !== value) { onChange(mediaPath); } else if (mediaPath && mediaPath === value) { onRemoveInsertedMedia(this.controlID); } } componentWillUnmount() { this.props.onRemoveMediaControl(this.controlID); } handleChange = e => { const { field, onOpenMediaLibrary, value } = this.props; e.preventDefault(); let mediaLibraryFieldOptions; /** * `options` hash as a general field property is deprecated, only used * when external media libraries were first introduced. Not to be * confused with `options` for the select widget, which serves a different * purpose. */ if (field.hasIn(['options', 'media_library'])) { warnDeprecatedOptions(field); mediaLibraryFieldOptions = field.getIn(['options', 'media_library'], Map()); } else { mediaLibraryFieldOptions = field.get('media_library', Map()); } return onOpenMediaLibrary({ controlID: this.controlID, forImage, privateUpload: field.get('private'), value, allowMultiple: !!mediaLibraryFieldOptions.get('allow_multiple', true), config: mediaLibraryFieldOptions.get('config'), field, }); }; handleRemove = e => { e.preventDefault(); this.props.onClearMediaControl(this.controlID); return this.props.onChange(''); }; getValidateValue = () => { const { value } = this.props; if (value) { return isMultiple(value) ? value.map(v => basename(v)) : basename(value); } return value; }; renderFileLink = value => { const size = MAX_DISPLAY_LENGTH; if (!value || value.length <= size) { return value; } const text = `${value.substring(0, size / 2)}\u2026${value.substring( value.length - size / 2 + 1, value.length, )}`; return ( {text} ); }; renderFileLinks = () => { const { value } = this.props; if (isMultiple(value)) { return ( {value.map(val => (
  • {this.renderFileLink(val)}
  • ))}
    ); } return {this.renderFileLink(value)}; }; renderImages = () => { const { getAsset, value, field } = this.props; if (isMultiple(value)) { return ( {value.map(val => ( ))} ); } const src = getAsset(value, field); return ( ); }; renderSelection = subject => { const { t } = this.props; return (
    {forImage ? this.renderImages() : null}
    {forImage ? null : this.renderFileLinks()} {t(`editor.editorWidgets.${subject}.chooseDifferent`)} {t(`editor.editorWidgets.${subject}.remove`)}
    ); }; renderNoSelection = subject => { const { t } = this.props; return ( {t(`editor.editorWidgets.${subject}.choose`)} ); }; render() { const { value, classNameWrapper } = this.props; const subject = forImage ? 'image' : 'file'; return (
    {value ? this.renderSelection(subject) : this.renderNoSelection(subject)}
    ); } }; }