Co-authored-by: Erez Rokah <erezrokah@users.noreply.github.com>
This commit is contained in:
parent
1e53d35db9
commit
80c577a462
@ -413,6 +413,7 @@ function mediaLibraryOpened(payload: {
|
||||
forImage?: boolean;
|
||||
privateUpload?: boolean;
|
||||
value?: string;
|
||||
replaceIndex?: number;
|
||||
allowMultiple?: boolean;
|
||||
config?: Map<string, unknown>;
|
||||
field?: EntryField;
|
||||
|
@ -45,6 +45,8 @@ const defaultState: {
|
||||
files?: MediaFile[];
|
||||
config: Map<string, unknown>;
|
||||
field?: EntryField;
|
||||
value?: string | string[];
|
||||
replaceIndex?: number;
|
||||
} = {
|
||||
isVisible: false,
|
||||
showMediaButton: true,
|
||||
@ -62,7 +64,8 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
||||
});
|
||||
|
||||
case MEDIA_LIBRARY_OPEN: {
|
||||
const { controlID, forImage, privateUpload, config, field } = action.payload;
|
||||
const { controlID, forImage, privateUpload, config, field, value, replaceIndex } =
|
||||
action.payload;
|
||||
const libConfig = config || Map();
|
||||
const privateUploadChanged = state.get('privateUpload') !== privateUpload;
|
||||
if (privateUploadChanged) {
|
||||
@ -76,6 +79,8 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
||||
controlMedia: Map(),
|
||||
displayURLs: Map(),
|
||||
field,
|
||||
value,
|
||||
replaceIndex,
|
||||
});
|
||||
}
|
||||
return state.withMutations(map => {
|
||||
@ -86,6 +91,8 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
||||
map.set('privateUpload', privateUpload);
|
||||
map.set('config', libConfig);
|
||||
map.set('field', field);
|
||||
map.set('value', value);
|
||||
map.set('replaceIndex', replaceIndex);
|
||||
});
|
||||
}
|
||||
|
||||
@ -95,8 +102,25 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
||||
case MEDIA_INSERT: {
|
||||
const { mediaPath } = action.payload;
|
||||
const controlID = state.get('controlID');
|
||||
const value = state.get('value');
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
return state.withMutations(map => {
|
||||
map.setIn(['controlMedia', controlID], mediaPath);
|
||||
});
|
||||
}
|
||||
|
||||
const replaceIndex = state.get('replaceIndex');
|
||||
const mediaArray = Array.isArray(mediaPath) ? mediaPath : [mediaPath];
|
||||
const valueArray = value as string[];
|
||||
if (typeof replaceIndex == 'number') {
|
||||
valueArray[replaceIndex] = mediaArray[0];
|
||||
} else {
|
||||
valueArray.push(...mediaArray);
|
||||
}
|
||||
|
||||
return state.withMutations(map => {
|
||||
map.setIn(['controlMedia', controlID], mediaPath);
|
||||
map.setIn(['controlMedia', controlID], valueArray);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -169,19 +169,25 @@ const en = {
|
||||
},
|
||||
image: {
|
||||
choose: 'Choose an image',
|
||||
chooseMultiple: 'Choose images',
|
||||
chooseUrl: 'Insert from URL',
|
||||
replaceUrl: 'Replace with URL',
|
||||
promptUrl: 'Enter the URL of the image',
|
||||
chooseDifferent: 'Choose different image',
|
||||
addMore: 'Add more images',
|
||||
remove: 'Remove image',
|
||||
removeAll: 'Remove all images',
|
||||
},
|
||||
file: {
|
||||
choose: 'Choose a file',
|
||||
chooseUrl: 'Insert from URL',
|
||||
chooseMultiple: 'Choose files',
|
||||
replaceUrl: 'Replace with URL',
|
||||
promptUrl: 'Enter the URL of the file',
|
||||
chooseDifferent: 'Choose different file',
|
||||
addMore: 'Add more files',
|
||||
remove: 'Remove file',
|
||||
removeAll: 'Remove all files',
|
||||
},
|
||||
unknownControl: {
|
||||
noControl: "No control for widget '%{widget}'.",
|
||||
|
@ -7,7 +7,15 @@ 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 {
|
||||
lengths,
|
||||
components,
|
||||
buttons,
|
||||
borders,
|
||||
effects,
|
||||
shadows,
|
||||
IconButton,
|
||||
} from 'netlify-cms-ui-default';
|
||||
import { basename } from 'netlify-cms-lib-util';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
import { arrayMoveImmutable as arrayMove } from 'array-move';
|
||||
@ -28,6 +36,15 @@ const ImageWrapper = styled.div`
|
||||
cursor: ${props => (props.sortable ? 'pointer' : 'auto')};
|
||||
`;
|
||||
|
||||
const SortableImageButtonsWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
column-gap: 10px;
|
||||
margin-right: 20px;
|
||||
margin-top: -10px;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const StyledImage = styled.img`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -38,35 +55,55 @@ function Image(props) {
|
||||
return <StyledImage role="presentation" {...props} />;
|
||||
}
|
||||
|
||||
const SortableImage = SortableElement(({ itemValue, getAsset, field }) => {
|
||||
function SortableImageButtons({ onRemove, onReplace }) {
|
||||
return (
|
||||
<ImageWrapper sortable>
|
||||
<Image src={getAsset(itemValue, field) || ''} />
|
||||
</ImageWrapper>
|
||||
<SortableImageButtonsWrapper>
|
||||
<IconButton size="small" type="media" onClick={onReplace}></IconButton>
|
||||
<IconButton size="small" type="close" onClick={onRemove}></IconButton>
|
||||
</SortableImageButtonsWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const SortableMultiImageWrapper = SortableContainer(({ items, getAsset, field }) => {
|
||||
const SortableImage = SortableElement(({ itemValue, getAsset, field, onRemove, onReplace }) => {
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`}
|
||||
>
|
||||
{items.map((itemValue, index) => (
|
||||
<SortableImage
|
||||
key={`item-${itemValue}`}
|
||||
index={index}
|
||||
itemValue={itemValue}
|
||||
getAsset={getAsset}
|
||||
field={field}
|
||||
/>
|
||||
))}
|
||||
<div>
|
||||
<ImageWrapper sortable>
|
||||
<Image src={getAsset(itemValue, field) || ''} />
|
||||
</ImageWrapper>
|
||||
<SortableImageButtons
|
||||
item={itemValue}
|
||||
onRemove={onRemove}
|
||||
onReplace={onReplace}
|
||||
></SortableImageButtons>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const SortableMultiImageWrapper = SortableContainer(
|
||||
({ items, getAsset, field, onRemoveOne, onReplaceOne }) => {
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`}
|
||||
>
|
||||
{items.map((itemValue, index) => (
|
||||
<SortableImage
|
||||
key={`item-${itemValue}`}
|
||||
index={index}
|
||||
itemValue={itemValue}
|
||||
getAsset={getAsset}
|
||||
field={field}
|
||||
onRemove={onRemoveOne(index)}
|
||||
onReplace={onReplaceOne(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const FileLink = styled.a`
|
||||
margin-bottom: 20px;
|
||||
font-weight: normal;
|
||||
@ -102,6 +139,22 @@ function isMultiple(value) {
|
||||
return Array.isArray(value) || List.isList(value);
|
||||
}
|
||||
|
||||
function sizeOfValue(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length;
|
||||
}
|
||||
|
||||
if (List.isList(value)) {
|
||||
return value.size;
|
||||
}
|
||||
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
|
||||
function valueListToArray(value) {
|
||||
return List.isList(value) ? value.toArray() : value;
|
||||
}
|
||||
|
||||
const warnDeprecatedOptions = once(field =>
|
||||
console.warn(oneLine`
|
||||
Netlify CMS config: ${field.get('name')} field: property "options" has been deprecated for the
|
||||
@ -178,26 +231,13 @@ export default function withFileControl({ forImage } = {}) {
|
||||
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());
|
||||
}
|
||||
const mediaLibraryFieldOptions = this.getMediaLibraryFieldOptions();
|
||||
|
||||
return onOpenMediaLibrary({
|
||||
controlID: this.controlID,
|
||||
forImage,
|
||||
privateUpload: field.get('private'),
|
||||
value,
|
||||
value: valueListToArray(value),
|
||||
allowMultiple: !!mediaLibraryFieldOptions.get('allow_multiple', true),
|
||||
config: mediaLibraryFieldOptions.get('config'),
|
||||
field,
|
||||
@ -218,6 +258,47 @@ export default function withFileControl({ forImage } = {}) {
|
||||
return this.props.onChange('');
|
||||
};
|
||||
|
||||
onRemoveOne = index => () => {
|
||||
const { value } = this.props;
|
||||
value.splice(index, 1);
|
||||
return this.props.onChange(sizeOfValue(value) > 0 ? [...value] : null);
|
||||
};
|
||||
|
||||
onReplaceOne = index => () => {
|
||||
const { field, onOpenMediaLibrary, value } = this.props;
|
||||
const mediaLibraryFieldOptions = this.getMediaLibraryFieldOptions();
|
||||
|
||||
return onOpenMediaLibrary({
|
||||
controlID: this.controlID,
|
||||
forImage,
|
||||
privateUpload: field.get('private'),
|
||||
value: valueListToArray(value),
|
||||
replaceIndex: index,
|
||||
allowMultiple: false,
|
||||
config: mediaLibraryFieldOptions.get('config'),
|
||||
field,
|
||||
});
|
||||
};
|
||||
|
||||
getMediaLibraryFieldOptions = () => {
|
||||
const { field } = this.props;
|
||||
|
||||
if (field.hasIn(['options', 'media_library'])) {
|
||||
warnDeprecatedOptions(field);
|
||||
return field.getIn(['options', 'media_library'], Map());
|
||||
}
|
||||
|
||||
return field.get('media_library', Map());
|
||||
};
|
||||
|
||||
allowsMultiple = () => {
|
||||
const mediaLibraryFieldOptions = this.getMediaLibraryFieldOptions();
|
||||
return (
|
||||
mediaLibraryFieldOptions.get('config', false) &&
|
||||
mediaLibraryFieldOptions.get('config').get('multiple', false)
|
||||
);
|
||||
};
|
||||
|
||||
onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
const { value } = this.props;
|
||||
const newValue = arrayMove(value, oldIndex, newIndex);
|
||||
@ -274,6 +355,9 @@ export default function withFileControl({ forImage } = {}) {
|
||||
<SortableMultiImageWrapper
|
||||
items={value}
|
||||
onSortEnd={this.onSortEnd}
|
||||
onRemoveOne={this.onRemoveOne}
|
||||
onReplaceOne={this.onReplaceOne}
|
||||
distance={4}
|
||||
getAsset={getAsset}
|
||||
field={field}
|
||||
axis="xy"
|
||||
@ -292,21 +376,26 @@ export default function withFileControl({ forImage } = {}) {
|
||||
|
||||
renderSelection = subject => {
|
||||
const { t, field } = this.props;
|
||||
const allowsMultiple = this.allowsMultiple();
|
||||
return (
|
||||
<div>
|
||||
{forImage ? this.renderImages() : null}
|
||||
<div>
|
||||
{forImage ? null : this.renderFileLinks()}
|
||||
<FileWidgetButton onClick={this.handleChange}>
|
||||
{t(`editor.editorWidgets.${subject}.chooseDifferent`)}
|
||||
{t(
|
||||
`editor.editorWidgets.${subject}.${
|
||||
this.allowsMultiple() ? 'addMore' : 'chooseDifferent'
|
||||
}`,
|
||||
)}
|
||||
</FileWidgetButton>
|
||||
{field.get('choose_url', true) ? (
|
||||
{field.get('choose_url', true) && !this.allowsMultiple() ? (
|
||||
<FileWidgetButton onClick={this.handleUrl(subject)}>
|
||||
{t(`editor.editorWidgets.${subject}.replaceUrl`)}
|
||||
</FileWidgetButton>
|
||||
) : null}
|
||||
<FileWidgetButtonRemove onClick={this.handleRemove}>
|
||||
{t(`editor.editorWidgets.${subject}.remove`)}
|
||||
{t(`editor.editorWidgets.${subject}.remove${allowsMultiple ? 'All' : ''}`)}
|
||||
</FileWidgetButtonRemove>
|
||||
</div>
|
||||
</div>
|
||||
@ -318,7 +407,7 @@ export default function withFileControl({ forImage } = {}) {
|
||||
return (
|
||||
<>
|
||||
<FileWidgetButton onClick={this.handleChange}>
|
||||
{t(`editor.editorWidgets.${subject}.choose`)}
|
||||
{t(`editor.editorWidgets.${subject}.choose${this.allowsMultiple() ? 'Multiple' : ''}`)}
|
||||
</FileWidgetButton>
|
||||
{field.get('choose_url', true) ? (
|
||||
<FileWidgetButton onClick={this.handleUrl(subject)}>
|
||||
|
Loading…
x
Reference in New Issue
Block a user