Skip to content

Commit 81924fb

Browse files
committed
Show editor placeholders via inline editing decorators
Replace FitViewport's opaque prop with explicit Placeholder and FilePlaceholder components. In the editor, placeholders display crossed diagonal lines to visually distinguish empty slots from black backgrounds. In the published entry, placeholders render a plain black background. This allows consitently applying content element box styles to placeholders. REDMINE-21248
1 parent bff1f67 commit 81924fb

18 files changed

Lines changed: 126 additions & 43 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
3+
import {ExternalLinkList} from 'contentElements/externalLinkList/frontend/ExternalLinkList';
4+
5+
import {renderInContentElement} from 'pageflow-scrolled/testHelpers';
6+
import '@testing-library/jest-dom/extend-expect'
7+
8+
import styles from 'frontend/Placeholder.module.css';
9+
10+
describe('ExternalLinkList placeholder', () => {
11+
it('renders placeholder in editor', () => {
12+
const {container} = renderInContentElement(
13+
<ExternalLinkList configuration={{links: [{id: 1}]}}
14+
sectionProps={{}} />,
15+
{editorState: {isEditable: true}}
16+
);
17+
18+
expect(container.querySelector(`.${styles.placeholder}`)).not.toBeNull();
19+
});
20+
21+
it('does not render placeholder in published entry', () => {
22+
const {container} = renderInContentElement(
23+
<ExternalLinkList configuration={{links: [{id: 1}]}}
24+
sectionProps={{}} />,
25+
{editorState: {isEditable: false}}
26+
);
27+
28+
expect(container.querySelector(`.${styles.placeholder}`)).toBeNull();
29+
});
30+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import {render} from '@testing-library/react';
3+
import '@testing-library/jest-dom/extend-expect';
4+
5+
import {FilePlaceholder} from 'frontend/FilePlaceholderDecorator';
6+
import styles from 'frontend/Placeholder.module.css';
7+
8+
describe('FilePlaceholder', () => {
9+
it('renders placeholder when no file is given', () => {
10+
const {container} = render(
11+
<FilePlaceholder />
12+
);
13+
14+
expect(container.querySelector(`.${styles.placeholder}`)).not.toBeNull();
15+
});
16+
17+
it('renders placeholder when file is not ready', () => {
18+
const {container} = render(
19+
<FilePlaceholder file={{isReady: false}} />
20+
);
21+
22+
expect(container.querySelector(`.${styles.placeholder}`)).not.toBeNull();
23+
});
24+
25+
it('does not render placeholder when file is ready', () => {
26+
const {container} = render(
27+
<FilePlaceholder file={{isReady: true}} />
28+
);
29+
30+
expect(container.querySelector(`.${styles.placeholder}`)).toBeNull();
31+
});
32+
});

entry_types/scrolled/package/spec/frontend/FitViewport-spec.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,6 @@ describe('FitViewport', () => {
6161
expect(getOuter(container)).toHaveStyle('--fit-viewport-scale: 0.8');
6262
});
6363

64-
it('is not opaque by default', () => {
65-
const {container} = render(
66-
<FitViewport aspectRatio={0.5}>
67-
<FitViewport.Content />
68-
</FitViewport>
69-
);
70-
71-
expect(getOuter(container)).not.toHaveClass(styles.opaque);
72-
});
73-
74-
it('can be made opaque', () => {
75-
const {container} = render(
76-
<FitViewport aspectRatio={0.5} opaque>
77-
<FitViewport.Content />
78-
</FitViewport>
79-
);
80-
81-
expect(getOuter(container)).toHaveClass(styles.opaque);
82-
});
83-
8464
it('support covering full height', () => {
8565
const {container} = render(
8666
<FitViewport aspectRatio={0.5} fill>

entry_types/scrolled/package/src/contentElements/externalLinkList/frontend/ExternalLink.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ export function ExternalLink({id, configuration, contentElementId, ...props}) {
165165
aspectRatio={props.thumbnailAspectRatio}
166166
cropPosition={props.thumbnailCropPosition}
167167
fit={props.thumbnailFit}
168-
load={props.loadImages}>
168+
load={props.loadImages}
169+
showPlaceholder={isEditable}>
169170
<InlineFileRights configuration={configuration}
170171
context="insideElement"
171172
position={props.textPosition === 'overlay' ? 'top': 'bottom'}

entry_types/scrolled/package/src/contentElements/externalLinkList/frontend/Thumbnail.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React from 'react';
22
import classNames from 'classnames';
33

4-
import {Image} from 'pageflow-scrolled/frontend';
4+
import {Image, FilePlaceholder} from 'pageflow-scrolled/frontend';
55

66
import styles from './Thumbnail.module.css';
77

8-
export function Thumbnail({imageFile, aspectRatio, cropPosition, fit, load, children}) {
8+
export function Thumbnail({imageFile, aspectRatio, cropPosition, fit, load, showPlaceholder, children}) {
99
imageFile = {
1010
...imageFile,
1111
cropPosition
@@ -17,6 +17,7 @@ export function Thumbnail({imageFile, aspectRatio, cropPosition, fit, load, chil
1717
<div className={classNames(styles.thumbnail,
1818
{[styles.cover]: fit === 'cover'})}
1919
style={{paddingTop: aspectRatioPadding}}>
20+
{showPlaceholder && <FilePlaceholder file={imageFile} />}
2021
<Image imageFile={imageFile}
2122
load={load}
2223
preferSvg={true}

entry_types/scrolled/package/src/contentElements/hotspots/Hotspots.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ContentElementBox,
66
Image,
77
ContentElementFigure,
8+
FilePlaceholder,
89
FitViewport,
910
FullscreenViewer,
1011
ToggleFullscreenCornerButton,
@@ -97,7 +98,7 @@ export function HotspotsImage({
9798
const {shouldLoad} = useContentElementLifecycle();
9899
const {isEditable, isSelected} = useContentElementEditorState();
99100

100-
const aspectRatio = imageFile ? `${imageFile.width} / ${imageFile.height}` : '3 / 4';
101+
const aspectRatio = imageFile ? `${imageFile.width} / ${imageFile.height}` : '4 / 3';
101102

102103
const [containerRect, containerRef] = useContentRect({
103104
enabled: shouldLoad
@@ -236,8 +237,7 @@ export function HotspotsImage({
236237
activateArea={activateArea}>
237238
<FitViewport file={imageFile}
238239
fallbackAspectRatio={0.75}
239-
fill={configuration.position === 'backdrop'}
240-
opaque={!imageFile}>
240+
fill={configuration.position === 'backdrop'}>
241241
<Composite activeIndex={activeIndex + 1}
242242
loop={false}
243243
onNavigate={index => activateArea(index - 1)}>
@@ -248,6 +248,7 @@ export function HotspotsImage({
248248
<FitViewport.Content>
249249
<div className={styles.stack}
250250
ref={containerRef}>
251+
<FilePlaceholder file={imageFile} />
251252
{renderLetterboxBackground()}
252253
<div className={styles.wrapper}
253254
ref={panZoomRefs.wrapper}

entry_types/scrolled/package/src/contentElements/iframeEmbed/IframeEmbed.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ContentElementBox,
66
ContentElementFigure,
77
FitViewport,
8+
Placeholder,
89
ThirdPartyOptIn,
910
ThirdPartyOptOutInfo,
1011
useContentElementEditorState,
@@ -55,10 +56,10 @@ export function IframeEmbed({configuration}) {
5556
return (
5657
<div className={styles.wrapper}
5758
style={{pointerEvents: isEditable && !isSelected ? 'none' : undefined}}>
58-
<FitViewport aspectRatio={configuration.autoResize ? null : aspectRatios[aspectRatio || 'wide']}
59-
opaque={utils.isBlank(configuration.source)}>
59+
<FitViewport aspectRatio={configuration.autoResize ? null : aspectRatios[aspectRatio || 'wide']}>
6060
<ContentElementBox>
6161
<ContentElementFigure configuration={configuration}>
62+
{utils.isBlank(configuration.source) && <Placeholder />}
6263
{renderSpanningWrapper(
6364
<ThirdPartyOptIn>
6465
{shouldLoad &&

entry_types/scrolled/package/src/contentElements/imageGallery/ImageGallery.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
useFileWithInlineRights,
88
ContentElementBox,
99
Figure,
10+
FilePlaceholder,
1011
FitViewport,
1112
FullscreenViewer,
1213
Image,
@@ -251,15 +252,15 @@ function ItemImageWithCaption({item, imageFile, configuration, current, onClick,
251252
return (
252253
<FitViewport file={imageFile}
253254
fallbackAspectRatio={0.75}
254-
scale={0.8}
255-
opaque={!imageFile}>
255+
scale={0.8}>
256256
<ContentElementBox>
257257
<Figure caption={caption}
258258
variant={configuration.captionVariant}
259259
onCaptionChange={handleCaptionChange}
260260
addCaptionButtonVisible={current && !item.placeholder}
261261
addCaptionButtonPosition="inside">
262262
<FitViewport.Content>
263+
<FilePlaceholder file={imageFile} />
263264
<div onClick={onClick}>
264265
<Image imageFile={imageFile} load={shouldLoad} />
265266
</div>

entry_types/scrolled/package/src/contentElements/inlineImage/InlineImage.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ContentElementBox,
55
Image,
66
ContentElementFigure,
7+
FilePlaceholder,
78
FitViewport,
89
contentElementWidths,
910
useContentElementLifecycle,
@@ -91,8 +92,7 @@ function ImageWithCaption({
9192
return (
9293
<FitViewport file={imageFile}
9394
aspectRatio={aspectRatio}
94-
fallbackAspectRatio={0.75}
95-
opaque={!imageFile}>
95+
fallbackAspectRatio={0.75}>
9696
<ContentElementBox borderRadius={isCircleCrop ? 'none' : rounded}>
9797
<ContentElementFigure configuration={configuration}>
9898
<FitViewport.Content>
@@ -101,6 +101,7 @@ function ImageWithCaption({
101101
<ExpandableImage enabled={supportFullscreen && shouldLoad}
102102
imageFile={imageFile}
103103
contentElementId={contentElementId}>
104+
<FilePlaceholder file={imageFile} />
104105
<Image imageFile={imageFile}
105106
load={shouldLoad}
106107
structuredData={true}

entry_types/scrolled/package/src/contentElements/inlineVideo/InlineVideo.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
VideoPlayer,
66
ContentElementBox,
77
ContentElementFigure,
8+
FilePlaceholder,
89
MediaInteractionTracking,
910
VideoPlayerControls,
1011
InlineFileRights,
@@ -124,11 +125,11 @@ function OrientationUnawareInlineVideo({
124125
<MediaInteractionTracking playerState={playerState} playerActions={playerActions}>
125126
<FitViewport file={videoFile}
126127
fallbackAspectRatio={fallbackAspectRatio}
127-
fill={configuration.position === 'backdrop'}
128-
opaque={!videoFile}>
128+
fill={configuration.position === 'backdrop'}>
129129
<ContentElementBox>
130130
<ContentElementFigure configuration={configuration}>
131131
<FitViewport.Content>
132+
<FilePlaceholder file={videoFile} />
132133
<MutedIndicator visible={media.muted &&
133134
playerState.shouldPlay &&
134135
!configuration.keepMuted} />

0 commit comments

Comments
 (0)