From ce3047dedb828f590fdf3d5f9c5abd358441e410 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 1 Jul 2024 08:31:27 +0200 Subject: [PATCH] Convert `element-library` to TypeScript (#13503) --- package-lock.json | 3 - packages/element-library/package.json | 6 +- .../{constants.js => constants.ts} | 2 + .../audioSticker/{display.js => display.tsx} | 18 +- .../src/audioSticker/{icon.js => icon.tsx} | 0 .../src/audioSticker/{index.js => index.ts} | 0 .../src/audioSticker/{layer.js => layer.tsx} | 0 .../audioSticker/{output.js => output.tsx} | 11 +- .../src/{elementTypes.js => elementTypes.ts} | 58 ++++- .../src/gif/{constants.js => constants.ts} | 16 ++ .../src/gif/{display.js => display.tsx} | 25 +- .../src/gif/{edit.js => edit.tsx} | 11 +- packages/element-library/src/gif/frame.js | 34 --- .../{media/textContent.js => gif/frame.tsx} | 12 +- .../src/gif/{icon.js => icon.tsx} | 10 +- .../src/gif/{index.js => index.ts} | 0 .../src/gif/{layer.js => layer.ts} | 4 +- .../src/gif/{output.js => output.tsx} | 12 +- .../src/image/{constants.js => constants.ts} | 13 ++ .../src/image/{edit.js => edit.tsx} | 12 +- packages/element-library/src/image/frame.js | 34 --- .../src/{types/index.ts => image/frame.tsx} | 13 +- .../src/image/{icon.js => icon.tsx} | 15 +- .../src/image/{index.js => index.ts} | 0 .../src/image/{layer.js => layer.ts} | 3 +- .../src/image/{output.js => output.tsx} | 34 ++- .../src/{index.js => index.ts} | 0 .../src/media/{constants.js => constants.ts} | 0 .../src/media/{display.js => display.tsx} | 51 +++-- .../src/media/{edit.js => edit.tsx} | 176 +++++++++----- ...itCropMoveable.js => editCropMoveable.tsx} | 52 +++-- ...editPanMoveable.js => editPanMoveable.tsx} | 54 ++--- .../src/media/{frame.js => frame.tsx} | 0 .../{imageDisplay.js => imageDisplay.tsx} | 89 ++++---- .../src/media/{index.js => index.ts} | 0 .../src/media/{output.js => output.tsx} | 28 ++- .../media/{scalePanel.js => scalePanel.tsx} | 51 +++-- .../element-library/src/media/textContent.ts | 26 +++ .../src/media/{util.js => util.ts} | 26 ++- .../media/{videoImage.js => videoImage.tsx} | 9 +- .../product/{constants.js => constants.ts} | 2 +- .../src/product/{display.js => display.tsx} | 25 +- .../src/product/{icon.js => icon.tsx} | 13 +- .../src/product/{index.js => index.ts} | 0 .../src/product/{layer.js => layer.tsx} | 3 +- .../src/product/{output.js => output.tsx} | 12 +- .../src/shape/{constants.js => constants.ts} | 1 - .../src/shape/{display.js => display.tsx} | 31 ++- .../src/shape/{icon.js => icon.tsx} | 36 ++- .../src/shape/{index.js => index.ts} | 0 .../src/shape/{layer.js => layer.ts} | 7 +- .../src/shape/{output.js => output.tsx} | 14 +- .../src/shared/{constants.js => constants.ts} | 0 .../src/shared/{index.js => index.ts} | 0 .../src/shared/{shared.js => shared.ts} | 57 +++-- ...r.js => useCSSVarColorTransformHandler.ts} | 11 +- ...Handler.js => useColorTransformHandler.ts} | 19 +- .../{visibleImage.js => visibleImage.tsx} | 13 +- .../sticker/{constants.js => constants.ts} | 0 .../src/sticker/{display.js => display.tsx} | 11 +- .../src/sticker/{icon.js => icon.tsx} | 11 +- .../src/sticker/{index.js => index.ts} | 0 .../src/sticker/{layer.js => layer.ts} | 4 +- .../src/sticker/{output.js => output.tsx} | 11 +- .../src/text/{constants.js => constants.ts} | 0 .../src/text/{display.js => display.tsx} | 111 +++++---- .../src/text/{edit.js => edit.tsx} | 214 +++++++++++------- .../src/text/{frame.js => frame.tsx} | 67 +++--- .../src/text/{icon.js => icon.tsx} | 0 .../src/text/{index.js => index.ts} | 0 .../src/text/{layer.js => layer.ts} | 3 +- .../src/text/{output.js => output.tsx} | 12 +- .../src/text/outputWithUnits.tsx | 6 +- .../element-library/src/text/test/util.js | 12 +- .../text/{textContent.js => textContent.ts} | 3 +- ...ResizeEvent.js => updateForResizeEvent.ts} | 8 +- packages/element-library/src/text/util.ts | 19 +- packages/element-library/src/types.ts | 30 --- .../src/types/elements/gifElement.ts | 29 --- .../src/types/elements/imageElement.ts | 30 --- .../src/types/elements/index.ts | 23 -- .../src/types/elements/shapeElement.ts | 28 --- .../src/types/elements/stickerElement.ts | 29 --- .../src/types/elements/textElement.ts | 50 ---- .../src/types/elements/videoElement.ts | 42 ---- .../element-library/src/typings/global.d.ts | 78 +++++++ .../productElement.ts => typings/styled.d.ts} | 12 +- packages/element-library/src/typings/svg.d.ts | 23 ++ .../src/utils/{noop.js => noop.ts} | 0 .../src/video/{captions.js => captions.tsx} | 33 ++- .../src/video/{constants.js => constants.ts} | 12 +- .../src/video/{controls.js => controls.tsx} | 28 ++- .../src/video/{display.js => display.tsx} | 25 +- .../src/video/{edit.js => edit.tsx} | 19 +- packages/element-library/src/video/frame.js | 34 --- packages/element-library/src/video/frame.tsx | 26 +++ .../src/video/{icon.js => icon.tsx} | 10 +- .../src/video/{index.js => index.ts} | 0 .../src/video/{layer.js => layer.ts} | 3 +- .../{onDropHandler.js => onDropHandler.ts} | 6 +- .../src/video/{output.js => output.tsx} | 17 +- ...playPauseButton.js => playPauseButton.tsx} | 71 +++--- .../src/video/{trim.js => trim.tsx} | 61 ++--- packages/element-library/tsconfig.json | 11 +- packages/elements/src/constants.ts | 9 +- packages/elements/src/elementType.ts | 2 +- packages/elements/src/index.ts | 1 + .../elements/src/{types => }/propTypes.ts | 6 +- packages/elements/src/types/element.ts | 20 +- .../elements/src/types/elementDefinition.ts | 109 +++++++-- packages/elements/src/types/index.ts | 2 +- packages/elements/src/types/media.ts | 8 +- .../src/utils/getDefinitionForType.ts | 6 +- .../elements/src/utils/getTransformFlip.ts | 2 +- packages/elements/tsconfig.json | 1 + packages/masks/src/borderedMaskedElement.tsx | 2 +- packages/masks/src/display.tsx | 8 +- packages/masks/src/masks.ts | 2 +- packages/masks/src/utils/elementBorder.ts | 8 +- packages/moveable/src/moveable.tsx | 2 +- packages/moveable/src/overlay/index.tsx | 2 +- packages/output/src/test/page.tsx | 1 + packages/output/src/test/story.tsx | 1 + packages/output/src/test/textElement.tsx | 1 + packages/output/src/types.ts | 4 - packages/output/src/typings/global.d.ts | 1 + packages/output/src/utils/backgroundAudio.tsx | 2 +- .../src/utils/test/getAutoAdvanceAfter.ts | 5 +- .../src/utils/test/getLongestMediaElement.ts | 1 + .../output/src/utils/test/getStoryMarkup.ts | 1 + .../src/utils/test/getUsedAmpExtensions.ts | 13 +- packages/stickers/src/types.ts | 2 +- .../src/app/media/utils/useDetectBaseColor.ts | 3 + .../story-editor/src/utils/useCORSProxy.ts | 4 +- packages/transform/package.json | 3 - packages/transform/src/transformProvider.tsx | 51 +++-- packages/transform/src/types.ts | 9 +- tsconfig.json | 1 - 138 files changed, 1407 insertions(+), 1260 deletions(-) rename packages/element-library/src/audioSticker/{constants.js => constants.ts} (97%) rename packages/element-library/src/audioSticker/{display.js => display.tsx} (81%) rename packages/element-library/src/audioSticker/{icon.js => icon.tsx} (100%) rename packages/element-library/src/audioSticker/{index.js => index.ts} (100%) rename packages/element-library/src/audioSticker/{layer.js => layer.tsx} (100%) rename packages/element-library/src/audioSticker/{output.js => output.tsx} (83%) rename packages/element-library/src/{elementTypes.js => elementTypes.ts} (50%) rename packages/element-library/src/gif/{constants.js => constants.ts} (80%) rename packages/element-library/src/gif/{display.js => display.tsx} (83%) rename packages/element-library/src/gif/{edit.js => edit.tsx} (71%) delete mode 100644 packages/element-library/src/gif/frame.js rename packages/element-library/src/{media/textContent.js => gif/frame.tsx} (80%) rename packages/element-library/src/gif/{icon.js => icon.tsx} (86%) rename packages/element-library/src/gif/{index.js => index.ts} (100%) rename packages/element-library/src/gif/{layer.js => layer.ts} (87%) rename packages/element-library/src/gif/{output.js => output.tsx} (83%) rename packages/element-library/src/image/{constants.js => constants.ts} (84%) rename packages/element-library/src/image/{edit.js => edit.tsx} (71%) delete mode 100644 packages/element-library/src/image/frame.js rename packages/element-library/src/{types/index.ts => image/frame.tsx} (75%) rename packages/element-library/src/image/{icon.js => icon.tsx} (73%) rename packages/element-library/src/image/{index.js => index.ts} (100%) rename packages/element-library/src/image/{layer.js => layer.ts} (87%) rename packages/element-library/src/image/{output.js => output.tsx} (73%) rename packages/element-library/src/{index.js => index.ts} (100%) rename packages/element-library/src/media/{constants.js => constants.ts} (100%) rename packages/element-library/src/media/{display.js => display.tsx} (75%) rename packages/element-library/src/media/{edit.js => edit.tsx} (64%) rename packages/element-library/src/media/{editCropMoveable.js => editCropMoveable.tsx} (83%) rename packages/element-library/src/media/{editPanMoveable.js => editPanMoveable.tsx} (82%) rename packages/element-library/src/media/{frame.js => frame.tsx} (100%) rename packages/element-library/src/media/{imageDisplay.js => imageDisplay.tsx} (61%) rename packages/element-library/src/media/{index.js => index.ts} (100%) rename packages/element-library/src/media/{output.js => output.tsx} (80%) rename packages/element-library/src/media/{scalePanel.js => scalePanel.tsx} (66%) create mode 100644 packages/element-library/src/media/textContent.ts rename packages/element-library/src/media/{util.js => util.ts} (83%) rename packages/element-library/src/media/{videoImage.js => videoImage.tsx} (85%) rename packages/element-library/src/product/{constants.js => constants.ts} (96%) rename packages/element-library/src/product/{display.js => display.tsx} (87%) rename packages/element-library/src/product/{icon.js => icon.tsx} (84%) rename packages/element-library/src/product/{index.js => index.ts} (100%) rename packages/element-library/src/product/{layer.js => layer.tsx} (86%) rename packages/element-library/src/product/{output.js => output.tsx} (77%) rename packages/element-library/src/shape/{constants.js => constants.ts} (98%) rename packages/element-library/src/shape/{display.js => display.tsx} (81%) rename packages/element-library/src/shape/{icon.js => icon.tsx} (74%) rename packages/element-library/src/shape/{index.js => index.ts} (100%) rename packages/element-library/src/shape/{layer.js => layer.ts} (77%) rename packages/element-library/src/shape/{output.js => output.tsx} (80%) rename packages/element-library/src/shared/{constants.js => constants.ts} (100%) rename packages/element-library/src/shared/{index.js => index.ts} (100%) rename packages/element-library/src/shared/{shared.js => shared.ts} (66%) rename packages/element-library/src/shared/{useCSSVarColorTransformHandler.js => useCSSVarColorTransformHandler.ts} (82%) rename packages/element-library/src/shared/{useColorTransformHandler.js => useColorTransformHandler.ts} (75%) rename packages/element-library/src/shared/{visibleImage.js => visibleImage.tsx} (76%) rename packages/element-library/src/sticker/{constants.js => constants.ts} (100%) rename packages/element-library/src/sticker/{display.js => display.tsx} (80%) rename packages/element-library/src/sticker/{icon.js => icon.tsx} (82%) rename packages/element-library/src/sticker/{index.js => index.ts} (100%) rename packages/element-library/src/sticker/{layer.js => layer.ts} (87%) rename packages/element-library/src/sticker/{output.js => output.tsx} (81%) rename packages/element-library/src/text/{constants.js => constants.ts} (100%) rename packages/element-library/src/text/{display.js => display.tsx} (75%) rename packages/element-library/src/text/{edit.js => edit.tsx} (68%) rename packages/element-library/src/text/{frame.js => frame.tsx} (71%) rename packages/element-library/src/text/{icon.js => icon.tsx} (100%) rename packages/element-library/src/text/{index.js => index.ts} (100%) rename packages/element-library/src/text/{layer.js => layer.ts} (86%) rename packages/element-library/src/text/{output.js => output.tsx} (85%) rename packages/element-library/src/text/{textContent.js => textContent.ts} (86%) rename packages/element-library/src/text/{updateForResizeEvent.js => updateForResizeEvent.ts} (87%) delete mode 100644 packages/element-library/src/types.ts delete mode 100644 packages/element-library/src/types/elements/gifElement.ts delete mode 100644 packages/element-library/src/types/elements/imageElement.ts delete mode 100644 packages/element-library/src/types/elements/index.ts delete mode 100644 packages/element-library/src/types/elements/shapeElement.ts delete mode 100644 packages/element-library/src/types/elements/stickerElement.ts delete mode 100644 packages/element-library/src/types/elements/textElement.ts delete mode 100644 packages/element-library/src/types/elements/videoElement.ts create mode 100644 packages/element-library/src/typings/global.d.ts rename packages/element-library/src/{types/elements/productElement.ts => typings/styled.d.ts} (76%) create mode 100644 packages/element-library/src/typings/svg.d.ts rename packages/element-library/src/utils/{noop.js => noop.ts} (100%) rename packages/element-library/src/video/{captions.js => captions.tsx} (57%) rename packages/element-library/src/video/{constants.js => constants.ts} (86%) rename packages/element-library/src/video/{controls.js => controls.tsx} (70%) rename packages/element-library/src/video/{display.js => display.tsx} (86%) rename packages/element-library/src/video/{edit.js => edit.tsx} (69%) delete mode 100644 packages/element-library/src/video/frame.js create mode 100644 packages/element-library/src/video/frame.tsx rename packages/element-library/src/video/{icon.js => icon.tsx} (86%) rename packages/element-library/src/video/{index.js => index.ts} (100%) rename packages/element-library/src/video/{layer.js => layer.ts} (87%) rename packages/element-library/src/video/{onDropHandler.js => onDropHandler.ts} (83%) rename packages/element-library/src/video/{output.js => output.tsx} (83%) rename packages/element-library/src/video/{playPauseButton.js => playPauseButton.tsx} (83%) rename packages/element-library/src/video/{trim.js => trim.tsx} (75%) rename packages/elements/src/{types => }/propTypes.ts (98%) diff --git a/package-lock.json b/package-lock.json index e11a1df97313..5fa30cab02d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42320,9 +42320,6 @@ "dependencies": { "@googleforcreators/react": "*" }, - "devDependencies": { - "prop-types": "^15.7.2" - }, "engines": { "node": ">= 12 || >= 14 || >= 16 || >= 18 || >= 20", "npm": ">= 7.3" diff --git a/packages/element-library/package.json b/packages/element-library/package.json index 5d6dc57ed007..bd98c68bda74 100644 --- a/packages/element-library/package.json +++ b/packages/element-library/package.json @@ -26,13 +26,13 @@ }, "customExports": { ".": { - "default": "./src/index.js" + "default": "./src/index.ts" } }, "main": "dist/index.js", "module": "dist-module/index.js", - "types": "dist-types/types.d.ts", - "source": "src/index.js", + "types": "dist-types/index.d.ts", + "source": "src/index.ts", "publishConfig": { "access": "public" }, diff --git a/packages/element-library/src/audioSticker/constants.js b/packages/element-library/src/audioSticker/constants.ts similarity index 97% rename from packages/element-library/src/audioSticker/constants.js rename to packages/element-library/src/audioSticker/constants.ts index 2a704767996e..f398bed81182 100644 --- a/packages/element-library/src/audioSticker/constants.js +++ b/packages/element-library/src/audioSticker/constants.ts @@ -45,6 +45,8 @@ export const resizeRules = { vertical: false, horizontal: false, diagonal: false, + minWidth: 120, + minHeight: 120, }; export const defaultAttributes = { diff --git a/packages/element-library/src/audioSticker/display.js b/packages/element-library/src/audioSticker/display.tsx similarity index 81% rename from packages/element-library/src/audioSticker/display.js rename to packages/element-library/src/audioSticker/display.tsx index 02a499ca24cf..222774389222 100644 --- a/packages/element-library/src/audioSticker/display.js +++ b/packages/element-library/src/audioSticker/display.tsx @@ -17,7 +17,11 @@ * External dependencies */ import styled from 'styled-components'; -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { + Element, + AudioStickerElement, + DisplayProps, +} from '@googleforcreators/elements'; /** * Internal dependencies @@ -29,12 +33,16 @@ import { AUDIO_STICKER_LABELS, } from '../constants'; -const Element = styled.img` +interface ElementProps { + stickerStyle: keyof typeof AUDIO_STICKER_STYLES; +} + +const Element = styled.img` ${elementFillContent} ${({ stickerStyle }) => AUDIO_STICKER_STYLES[stickerStyle]} `; -function AudioStickerDisplay({ element }) { +function AudioStickerDisplay({ element }: DisplayProps) { const { width: elementWidth, height: elementHeight, @@ -53,8 +61,4 @@ function AudioStickerDisplay({ element }) { ); } -AudioStickerDisplay.propTypes = { - element: StoryPropTypes.elements.shape.isRequired, -}; - export default AudioStickerDisplay; diff --git a/packages/element-library/src/audioSticker/icon.js b/packages/element-library/src/audioSticker/icon.tsx similarity index 100% rename from packages/element-library/src/audioSticker/icon.js rename to packages/element-library/src/audioSticker/icon.tsx diff --git a/packages/element-library/src/audioSticker/index.js b/packages/element-library/src/audioSticker/index.ts similarity index 100% rename from packages/element-library/src/audioSticker/index.js rename to packages/element-library/src/audioSticker/index.ts diff --git a/packages/element-library/src/audioSticker/layer.js b/packages/element-library/src/audioSticker/layer.tsx similarity index 100% rename from packages/element-library/src/audioSticker/layer.js rename to packages/element-library/src/audioSticker/layer.tsx diff --git a/packages/element-library/src/audioSticker/output.js b/packages/element-library/src/audioSticker/output.tsx similarity index 83% rename from packages/element-library/src/audioSticker/output.js rename to packages/element-library/src/audioSticker/output.tsx index 7086190bee0a..4c9c8e1b5ffd 100644 --- a/packages/element-library/src/audioSticker/output.js +++ b/packages/element-library/src/audioSticker/output.tsx @@ -17,9 +17,12 @@ /** * External dependencies */ -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { + AudioStickerElement, + OutputProps, +} from '@googleforcreators/elements'; -function AudioStickerOutput({ element }) { +function AudioStickerOutput({ element }: OutputProps) { return (
, + { + type: ElementType.Image, + name: __('Image', 'web-stories'), + ...imageElement, + } as ElementDefinition, + { + type: ElementType.Shape, + name: __('Shape', 'web-stories'), + ...shapeElement, + } as ElementDefinition, + { + type: ElementType.Video, + name: __('Video', 'web-stories'), + ...videoElement, + } as ElementDefinition, + { + type: ElementType.Gif, + name: __('GIF', 'web-stories'), + ...gifElement, + } as ElementDefinition, + { + type: ElementType.Sticker, + name: __('Sticker', 'web-stories'), + ...stickerElement, + } as ElementDefinition, + { + type: ElementType.Product, name: __('Product', 'web-stories'), ...productElement, - }, + } as ElementDefinition, { - type: 'audioSticker', + type: ElementType.AudioSticker, name: __('Audio Sticker', 'web-stories'), ...audioStickerElement, - }, + } as ElementDefinition, ]; export default elementTypes; diff --git a/packages/element-library/src/gif/constants.js b/packages/element-library/src/gif/constants.ts similarity index 80% rename from packages/element-library/src/gif/constants.js rename to packages/element-library/src/gif/constants.ts index 0a4312b1ab23..5b1287d086cd 100644 --- a/packages/element-library/src/gif/constants.js +++ b/packages/element-library/src/gif/constants.ts @@ -17,6 +17,7 @@ * External dependencies */ import { PanelTypes } from '@googleforcreators/design-system'; +import { ResourceType } from '@googleforcreators/media'; /** * Internal dependencies @@ -27,9 +28,11 @@ import { MEDIA_DEFAULT_ATTRIBUTES, MEDIA_PANELS } from '../media'; export { canFlip, isMaskable, + isAspectAlwaysLocked, isMedia, hasEditMode, hasEditModeIfLocked, + hasEditModeMoveable, hasDuplicateMenu, hasDesignMenu, editModeGrayout, @@ -40,6 +43,19 @@ export { resizeRules } from '../media/constants'; export const defaultAttributes = { ...SHARED_DEFAULT_ATTRIBUTES, ...MEDIA_DEFAULT_ATTRIBUTES, + resource: { + type: ResourceType.Gif, + id: 0, + width: 0, + height: 0, + alt: '', + src: '', + mimeType: 'image/gif', + output: { + mimeType: 'video/mp4', + src: '', + }, + }, }; export const panels = [ diff --git a/packages/element-library/src/gif/display.js b/packages/element-library/src/gif/display.tsx similarity index 83% rename from packages/element-library/src/gif/display.js rename to packages/element-library/src/gif/display.tsx index 45fd3085bf39..80b19d8d6b1c 100644 --- a/packages/element-library/src/gif/display.js +++ b/packages/element-library/src/gif/display.tsx @@ -17,10 +17,10 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import { useRef } from '@googleforcreators/react'; import { getMediaSizePositionProps } from '@googleforcreators/media'; -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { GifElement, DisplayProps } from '@googleforcreators/elements'; +import type { RefObject } from 'react'; /** * Internal dependencies @@ -28,14 +28,14 @@ import { StoryPropTypes } from '@googleforcreators/elements'; import MediaDisplay from '../media/display'; import { getBackgroundStyle, Video, VideoImage } from '../media/util'; -function VideoDisplay({ +function GifDisplay({ previewMode, box: { width, height }, element, renderResourcePlaceholder, -}) { +}: DisplayProps) { const { id, poster, resource, isBackground, scale, focalX, focalY } = element; - const ref = useRef(); + const ref = useRef(null); let style = {}; if (isBackground) { const styleProps = getBackgroundStyle(); @@ -55,7 +55,7 @@ function VideoDisplay({ ); return ( - element={element} mediaRef={ref} showPlaceholder @@ -69,7 +69,7 @@ function VideoDisplay({ alt={element.alt || resource.alt} style={style} {...videoProps} - ref={ref} + ref={ref as RefObject} /> ) ) : ( @@ -82,7 +82,7 @@ function VideoDisplay({ autoPlay muted preload="all" - ref={ref} + ref={ref as RefObject} data-testid="videoElement" data-leaf-element="true" > @@ -95,11 +95,4 @@ function VideoDisplay({ ); } -VideoDisplay.propTypes = { - previewMode: PropTypes.bool, - element: StoryPropTypes.elements.video.isRequired, - box: StoryPropTypes.box.isRequired, - renderResourcePlaceholder: PropTypes.func, -}; - -export default VideoDisplay; +export default GifDisplay; diff --git a/packages/element-library/src/gif/edit.js b/packages/element-library/src/gif/edit.tsx similarity index 71% rename from packages/element-library/src/gif/edit.js rename to packages/element-library/src/gif/edit.tsx index 39e7adcd5569..55c713de4518 100644 --- a/packages/element-library/src/gif/edit.js +++ b/packages/element-library/src/gif/edit.tsx @@ -16,20 +16,15 @@ /** * External dependencies */ -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { GifElement, EditProps } from '@googleforcreators/elements'; /** * Internal dependencies */ import MediaEdit from '../media/edit'; -function GifEdit({ element, box, ...rest }) { - return ; +function GifEdit({ element, box, ...rest }: EditProps) { + return element={element} box={box} {...rest} />; } -GifEdit.propTypes = { - element: StoryPropTypes.elements.gif.isRequired, - box: StoryPropTypes.box.isRequired, -}; - export default GifEdit; diff --git a/packages/element-library/src/gif/frame.js b/packages/element-library/src/gif/frame.js deleted file mode 100644 index fd74cde989ef..000000000000 --- a/packages/element-library/src/gif/frame.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * External dependencies - */ -import { StoryPropTypes } from '@googleforcreators/elements'; - -/** - * Internal dependencies - */ -import MediaFrame from '../media/frame'; - -function GifFrame({ element }) { - return ; -} - -GifFrame.propTypes = { - element: StoryPropTypes.elements.gif.isRequired, -}; - -export default GifFrame; diff --git a/packages/element-library/src/media/textContent.js b/packages/element-library/src/gif/frame.tsx similarity index 80% rename from packages/element-library/src/media/textContent.js rename to packages/element-library/src/gif/frame.tsx index ab4487cd4c78..d894dbfa4d6f 100644 --- a/packages/element-library/src/media/textContent.js +++ b/packages/element-library/src/gif/frame.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -function TextContent({ src }) { - return `image: ${src}`; -} +/** + * Internal dependencies + */ +import MediaFrame from '../media/frame'; -export default TextContent; +function GifFrame() { + return ; +} +export default GifFrame; diff --git a/packages/element-library/src/gif/icon.js b/packages/element-library/src/gif/icon.tsx similarity index 86% rename from packages/element-library/src/gif/icon.js rename to packages/element-library/src/gif/icon.tsx index dc7c6e4bdcbd..e0a13322e245 100644 --- a/packages/element-library/src/gif/icon.js +++ b/packages/element-library/src/gif/icon.tsx @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /** * External dependencies */ -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { GifElement, LayerIconProps } from '@googleforcreators/elements'; /** * Internal dependencies @@ -27,12 +28,7 @@ function GifLayerIcon({ element: { resource: { poster, alt }, }, -}) { +}: LayerIconProps) { return ; } - -GifLayerIcon.propTypes = { - element: StoryPropTypes.element.isRequired, -}; - export default GifLayerIcon; diff --git a/packages/element-library/src/gif/index.js b/packages/element-library/src/gif/index.ts similarity index 100% rename from packages/element-library/src/gif/index.js rename to packages/element-library/src/gif/index.ts diff --git a/packages/element-library/src/gif/layer.js b/packages/element-library/src/gif/layer.ts similarity index 87% rename from packages/element-library/src/gif/layer.js rename to packages/element-library/src/gif/layer.ts index e6f265a29bb9..ce7c1cc921a9 100644 --- a/packages/element-library/src/gif/layer.js +++ b/packages/element-library/src/gif/layer.ts @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /** * External dependencies */ import { __ } from '@googleforcreators/i18n'; +import type { GifElement } from '@googleforcreators/elements'; -function getGifLayerText(element) { +function getGifLayerText(element: GifElement) { const { alt } = element?.resource || {}; return alt || __('GIF', 'web-stories'); diff --git a/packages/element-library/src/gif/output.js b/packages/element-library/src/gif/output.tsx similarity index 83% rename from packages/element-library/src/gif/output.js rename to packages/element-library/src/gif/output.tsx index 8471af373b95..4bc7817a06c9 100644 --- a/packages/element-library/src/gif/output.js +++ b/packages/element-library/src/gif/output.tsx @@ -17,16 +17,15 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import { isBlobURL } from '@googleforcreators/media'; -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { GifElement, OutputProps } from '@googleforcreators/elements'; /** * Internal dependencies */ import MediaOutput from '../media/output'; -function GifOutput({ element, box, flags }) { +function GifOutput({ element, box, flags }: OutputProps) { const { resource } = element; const src = @@ -40,6 +39,7 @@ function GifOutput({ element, box, flags }) { ; +function ImageEdit({ element, box, ...rest }: EditProps) { + return element={element} box={box} {...rest} />; } -ImageEdit.propTypes = { - element: StoryPropTypes.elements.image.isRequired, - box: StoryPropTypes.box.isRequired, -}; - export default ImageEdit; diff --git a/packages/element-library/src/image/frame.js b/packages/element-library/src/image/frame.js deleted file mode 100644 index 6694553b0238..000000000000 --- a/packages/element-library/src/image/frame.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * External dependencies - */ -import { StoryPropTypes } from '@googleforcreators/elements'; - -/** - * Internal dependencies - */ -import MediaFrame from '../media/frame'; - -function ImageFrame({ element }) { - return ; -} - -ImageFrame.propTypes = { - element: StoryPropTypes.elements.image.isRequired, -}; - -export default ImageFrame; diff --git a/packages/element-library/src/types/index.ts b/packages/element-library/src/image/frame.tsx similarity index 75% rename from packages/element-library/src/types/index.ts rename to packages/element-library/src/image/frame.tsx index 52c259d2fb28..09e39c88db11 100644 --- a/packages/element-library/src/types/index.ts +++ b/packages/element-library/src/image/frame.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,13 @@ * limitations under the License. */ -export * from './elements'; +/** + * Internal dependencies + */ +import MediaFrame from '../media/frame'; + +function ImageFrame() { + return ; +} + +export default ImageFrame; diff --git a/packages/element-library/src/image/icon.js b/packages/element-library/src/image/icon.tsx similarity index 73% rename from packages/element-library/src/image/icon.js rename to packages/element-library/src/image/icon.tsx index 202882bb4d31..ff922db12a18 100644 --- a/packages/element-library/src/image/icon.js +++ b/packages/element-library/src/image/icon.tsx @@ -18,23 +18,20 @@ * External dependencies */ import { getSmallestUrlForWidth } from '@googleforcreators/media'; -import PropTypes from 'prop-types'; -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { ImageElement, LayerIconProps } from '@googleforcreators/elements'; /** * Internal dependencies */ import VisibleImage from '../shared/visibleImage'; -function ImageLayerIcon({ element: { resource }, getProxiedUrl }) { +function ImageLayerIcon({ + element: { resource }, + getProxiedUrl, +}: LayerIconProps) { const url = getSmallestUrlForWidth(0, resource); - const src = getProxiedUrl(resource, url); + const src = getProxiedUrl(resource, url) || undefined; return ; } -ImageLayerIcon.propTypes = { - element: StoryPropTypes.element.isRequired, - getProxiedUrl: PropTypes.func.isRequired, -}; - export default ImageLayerIcon; diff --git a/packages/element-library/src/image/index.js b/packages/element-library/src/image/index.ts similarity index 100% rename from packages/element-library/src/image/index.js rename to packages/element-library/src/image/index.ts diff --git a/packages/element-library/src/image/layer.js b/packages/element-library/src/image/layer.ts similarity index 87% rename from packages/element-library/src/image/layer.js rename to packages/element-library/src/image/layer.ts index e98ed8219a18..25540c5ffa03 100644 --- a/packages/element-library/src/image/layer.js +++ b/packages/element-library/src/image/layer.ts @@ -17,8 +17,9 @@ * External dependencies */ import { __ } from '@googleforcreators/i18n'; +import type { ImageElement } from '@googleforcreators/elements'; -function getImageLayerText(element) { +function getImageLayerText(element: ImageElement) { const { alt } = element?.resource || {}; return alt || __('Image', 'web-stories'); diff --git a/packages/element-library/src/image/output.js b/packages/element-library/src/image/output.tsx similarity index 73% rename from packages/element-library/src/image/output.js rename to packages/element-library/src/image/output.tsx index 371866312744..11bfe2d469b8 100644 --- a/packages/element-library/src/image/output.js +++ b/packages/element-library/src/image/output.tsx @@ -17,14 +17,14 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import { calculateSrcSet, isBlobURL } from '@googleforcreators/media'; import { PAGE_WIDTH, FULLBLEED_HEIGHT, FULLBLEED_RATIO, } from '@googleforcreators/units'; -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { ImageElement, OutputProps } from '@googleforcreators/elements'; +import type { HTMLProps } from 'react'; /** * Internal dependencies @@ -34,14 +34,16 @@ import MediaOutput from '../media/output'; /** * Returns AMP HTML for saving into post content for displaying in the FE. * - * @param {Object<*>} props Props. - * @return {*} Rendered component. + * @param props Props. + * @return Rendered component. */ -function ImageOutput({ element, box, flags }) { +function ImageOutput({ element, box, flags }: OutputProps) { const { alt, isBackground, resource, width, height, scale } = element; - const props = { - layout: 'fill', + const props: Pick< + HTMLProps, + 'src' | 'alt' | 'srcSet' | 'sizes' + > = { src: flags?.allowBlobs || !isBlobURL(resource.src) ? resource.src : '', alt: alt !== undefined ? alt : resource.alt, }; @@ -59,7 +61,7 @@ function ImageOutput({ element, box, flags }) { aspectRatio <= FULLBLEED_RATIO ? PAGE_WIDTH : aspectRatio * FULLBLEED_HEIGHT; - displayWidth = widthAsBackground * (scale / 100); + displayWidth = widthAsBackground * ((scale || 100) / 100); } // If `srcset` exists but `sizes` doesn't, amp-img will generate a sizes attribute @@ -70,24 +72,16 @@ function ImageOutput({ element, box, flags }) { const desktopWidth = Math.round(imageWidthPercent * 45) + 'vh'; // 1024px is the minimum width for STAMP desktop mode. props.sizes = `(min-width: 1024px) ${desktopWidth}, ${mobileWidth}`; - - // Prevent inline `width` style from being inserted by AMP (due to presence of `sizes` attribute), - // which avoids an undesirable interaction between AMP and the Optimizer's SSR transforms. - // See https://github.com/googleforcreators/web-stories-wp/pull/8099#issuecomment-870987667. - props['disable-inline-width'] = true; } + // Prevent inline `width` style from being inserted by AMP (due to presence of `sizes` attribute), + // which avoids an undesirable interaction between AMP and the Optimizer's SSR transforms. + // See https://github.com/googleforcreators/web-stories-wp/pull/8099#issuecomment-870987667. return ( - + ); } -ImageOutput.propTypes = { - element: StoryPropTypes.elements.image.isRequired, - box: StoryPropTypes.box.isRequired, - flags: PropTypes.object, -}; - export default ImageOutput; diff --git a/packages/element-library/src/index.js b/packages/element-library/src/index.ts similarity index 100% rename from packages/element-library/src/index.js rename to packages/element-library/src/index.ts diff --git a/packages/element-library/src/media/constants.js b/packages/element-library/src/media/constants.ts similarity index 100% rename from packages/element-library/src/media/constants.js rename to packages/element-library/src/media/constants.ts diff --git a/packages/element-library/src/media/display.js b/packages/element-library/src/media/display.tsx similarity index 75% rename from packages/element-library/src/media/display.js rename to packages/element-library/src/media/display.tsx index e44e467d4d50..6845b9937c08 100644 --- a/packages/element-library/src/media/display.js +++ b/packages/element-library/src/media/display.tsx @@ -18,7 +18,6 @@ * External dependencies */ import styled from 'styled-components'; -import PropTypes from 'prop-types'; import { useRef } from '@googleforcreators/react'; import { useUnits } from '@googleforcreators/units'; import { getMediaSizePositionProps } from '@googleforcreators/media'; @@ -27,7 +26,14 @@ import { getResponsiveBorder, shouldDisplayBorder, } from '@googleforcreators/masks'; -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { + SequenceMediaElement, + OverlayableElement, + Element as ElementType, + MediaElement, + DisplayProps, +} from '@googleforcreators/elements'; +import type { ReactNode, RefObject } from 'react'; /** * Internal dependencies @@ -40,7 +46,11 @@ import { } from '../shared'; import { getMediaWithScaleCss } from './util'; -const Element = styled.div.attrs({ className: 'story-media-display-element' })` +const Element = styled.div.attrs({ className: 'story-media-display-element' })< + { + showPlaceholder: boolean; + } & Pick +>` ${elementFillContent} ${({ showPlaceholder }) => showPlaceholder && `background-color: #C4C4C4;`} color: transparent; @@ -53,14 +63,26 @@ const Overlay = styled.div` ${elementWithBackgroundColor} `; -function MediaDisplay({ +interface MediaDisplayProps { + element: T & OverlayableElement; + showPlaceholder?: boolean; + previewMode?: boolean; + mediaRef: RefObject< + T extends SequenceMediaElement + ? HTMLVideoElement | HTMLImageElement + : HTMLImageElement + >; + renderResourcePlaceholder?: DisplayProps['renderResourcePlaceholder']; + children: ReactNode; +} +function MediaDisplay({ element, mediaRef, children, previewMode, showPlaceholder = false, renderResourcePlaceholder, -}) { +}: MediaDisplayProps) { const { id, resource, @@ -79,7 +101,7 @@ function MediaDisplay({ dataToEditorX: state.actions.dataToEditorX, })); - const ref = useRef(); + const ref = useRef(null); useColorTransformHandler({ id, targetRef: ref, @@ -87,7 +109,7 @@ function MediaDisplay({ }); useTransformHandler(id, (transform) => { const target = mediaRef.current; - if (mediaRef.current) { + if (target) { if (transform === null) { target.style.cssText = ''; } else { @@ -103,11 +125,11 @@ function MediaDisplay({ focalY ); target.style.cssText = getMediaWithScaleCss(newImgProps); - if (shouldDisplayBorder(element)) { + if (shouldDisplayBorder(element) && ref.current) { ref.current.style.width = - resize[0] + border.left + border.right + 'px'; + resize[0] + (border?.left || 0) + (border?.right || 0) + 'px'; ref.current.style.height = - resize[1] + border.top + border.bottom + 'px'; + resize[1] + (border?.top || 0) + (border?.bottom || 0) + 'px'; } } } @@ -133,13 +155,4 @@ function MediaDisplay({ ); } -MediaDisplay.propTypes = { - element: StoryPropTypes.elements.media.isRequired, - mediaRef: PropTypes.object, - children: PropTypes.node, - showPlaceholder: PropTypes.bool, - previewMode: PropTypes.bool, - renderResourcePlaceholder: PropTypes.func, -}; - export default MediaDisplay; diff --git a/packages/element-library/src/media/edit.js b/packages/element-library/src/media/edit.tsx similarity index 64% rename from packages/element-library/src/media/edit.js rename to packages/element-library/src/media/edit.tsx index 05abb9841f2d..bf1eaf8f6b73 100644 --- a/packages/element-library/src/media/edit.js +++ b/packages/element-library/src/media/edit.tsx @@ -20,22 +20,28 @@ import styled, { css } from 'styled-components'; import { useCallback, - useState, - useRef, useEffect, + useRef, + useState, useUnmount, } from '@googleforcreators/react'; import { __ } from '@googleforcreators/i18n'; -import PropTypes from 'prop-types'; import { - getMediaSizePositionProps, calculateSrcSet, + getMediaSizePositionProps, } from '@googleforcreators/media'; import { DisplayWithMask as WithMask, shouldDisplayBorder, } from '@googleforcreators/masks'; -import { StoryPropTypes, getTransformFlip } from '@googleforcreators/elements'; +import { + type MediaElement, + type SequenceMediaElement, + type EditProps, + getTransformFlip, + elementIs, +} from '@googleforcreators/elements'; +import type { Dispatch, SetStateAction } from 'react'; /** * Internal dependencies @@ -53,7 +59,14 @@ const Element = styled.div` `; // Opacity of the mask is reduced depending on the opacity assigned to the media. -const fadedMediaCSS = css` +const fadedMediaCSS = css<{ + opacity?: number; + width: number; + height: number; + offsetX: number; + offsetY: number; + $transformFlip: string | null; +}>` position: absolute; opacity: ${({ opacity }) => typeof opacity !== 'undefined' @@ -69,14 +82,21 @@ const FadedImage = styled.img` `; const FadedVideo = styled.video` - ${fadedMediaCSS} max-width: initial; max-height: initial; + ${fadedMediaCSS} `; // Opacity is adjusted so that the double image opacity would equal // the opacity assigned to the image. -const cropMediaCSS = css` +const cropMediaCSS = css<{ + opacity?: number; + width: number; + height: number; + offsetX: number; + offsetY: number; + $transformFlip?: string | null; +}>` ${mediaWithScale} ${elementWithFlip} position: absolute; @@ -93,12 +113,13 @@ const CropImage = styled.img` // Opacity of the mask is reduced depending on the opacity assigned to the video. const CropVideo = styled.video` - ${cropMediaCSS} max-width: initial; max-height: initial; + ${cropMediaCSS} `; -function MediaEdit({ +// eslint-disable-next-line complexity -- TODO: Refactor to reduce complexity. +function MediaEdit({ element, box, setLocalProperties, @@ -107,7 +128,7 @@ function MediaEdit({ zIndexCanvas, scaleMin, scaleMax, -}) { +}: Omit, 'isTrimMode'>) { const { id, resource, @@ -118,27 +139,31 @@ function MediaEdit({ focalY, isBackground, isLocked, - type, borderRadius, } = element; const { x, y, width, height, rotationAngle } = box; - const [fullMedia, setFullMedia] = useState(null); - const [croppedMedia, setCroppedMedia] = useState(null); - const [cropBox, setCropBox] = useState(null); - const elementRef = useRef(); + const [fullMedia, setFullMedia] = useState< + | (T extends SequenceMediaElement ? HTMLVideoElement : HTMLImageElement) + | null + >(null); + const [croppedMedia, setCroppedMedia] = useState< + | (T extends SequenceMediaElement ? HTMLVideoElement : HTMLImageElement) + | null + >(null); + const [cropBox, setCropBox] = useState(null); + const elementRef = useRef(null); const isUpdatedLocally = useRef(false); - const lastLocalProperties = useRef({ scale }); + const lastLocalProperties = useRef>({ scale } as Partial); const updateLocalProperties = useCallback( - (properties) => { - const newProps = { + (properties: Partial | ((p: Partial) => Partial)) => { + lastLocalProperties.current = { ...lastLocalProperties.current, ...(typeof properties === 'function' ? properties(lastLocalProperties.current) : properties), }; - lastLocalProperties.current = newProps; isUpdatedLocally.current = true; setLocalProperties(lastLocalProperties.current); }, @@ -150,42 +175,39 @@ function MediaEdit({ return; } isUpdatedLocally.current = false; - const properties = lastLocalProperties.current; + const properties: Partial = lastLocalProperties.current; updateElementById({ elementId: id, properties }); }, [id, updateElementById]); useUnmount(updateProperties); - const isImage = ['image', 'gif'].includes(type); - const isVideo = 'video' === type; + const isImage = elementIs.media(element) && !elementIs.video(element); + const isVideo = elementIs.video(element); const mediaProps = getMediaSizePositionProps( resource, width, height, scale, - flip?.horizontal ? 100 - focalX : focalX, - flip?.vertical ? 100 - focalY : focalY + flip?.horizontal ? 100 - (focalX || 0) : focalX, + flip?.vertical ? 100 - (focalY || 0) : focalY ); - mediaProps.transformFlip = getTransformFlip(flip); - mediaProps.crossOrigin = 'anonymous'; - const fadedMediaProps = { - ref: setFullMedia, draggable: false, alt: '', - opacity: opacity / 100, + opacity: (opacity || 100) / 100, + $transformFlip: getTransformFlip(flip), ...mediaProps, }; const cropMediaProps = { - ref: setCroppedMedia, draggable: false, src: resource.src, alt: __('Drag to move media element', 'web-stories'), - opacity: opacity / 100, + opacity: (opacity || 100) / 100, tabIndex: 0, + $transformFlip: getTransformFlip(flip), ...mediaProps, }; @@ -201,15 +223,18 @@ function MediaEdit({ }, [croppedMedia]); const srcSet = calculateSrcSet(element.resource); - if (isImage && srcSet) { - cropMediaProps.srcSet = srcSet; - } const handleWheel = useCallback( - (evt) => { - updateLocalProperties(({ scale: oldScale }) => ({ - scale: Math.min(scaleMax, Math.max(scaleMin, oldScale + evt.deltaY)), - })); + (evt: WheelEvent) => { + updateLocalProperties( + ({ scale: oldScale }: Partial) => + ({ + scale: Math.min( + scaleMax, + Math.max(scaleMin, (oldScale || 0) + evt.deltaY) + ), + }) as Partial + ); evt.preventDefault(); evt.stopPropagation(); }, @@ -221,12 +246,20 @@ function MediaEdit({ useEffect(() => { const node = elementRef.current; const opts = { passive: false }; - node.addEventListener('wheel', handleWheel, opts); - return () => node.removeEventListener('wheel', handleWheel, opts); + node?.addEventListener('wheel', handleWheel, opts); + // @ts-expect-error TODO: Fix type. + return () => node?.removeEventListener('wheel', handleWheel, opts); }, [handleWheel]); const borderProps = - shouldDisplayBorder(element) && borderRadius ? element : null; + shouldDisplayBorder(element) && borderRadius + ? { + borderRadius: element.borderRadius, + width: element.width, + height: element.height, + mask: element.mask, + } + : null; return ( @@ -234,24 +267,56 @@ function MediaEdit({ /* eslint-disable-next-line styled-components-a11y/alt-text -- False positive. */ > + } + src={url || undefined} + srcSet={calculateSrcSet(resource) || undefined} /> )} {isVideo && ( //eslint-disable-next-line styled-components-a11y/media-has-caption,jsx-a11y/media-has-caption -- Faded video doesn't need captions. - + > + } + > {url && } )} + + {/* @ts-expect-error TODO: Fix type. */} - {/* eslint-disable-next-line styled-components-a11y/alt-text -- False positive. */} - {isImage && } + {} + {isImage && ( + /*eslint-disable-next-line styled-components-a11y/alt-text -- False positive. */ + + > + } + srcSet={srcSet || undefined} + /> + )} {isVideo && ( /*eslint-disable-next-line styled-components-a11y/media-has-caption,jsx-a11y/media-has-caption -- Tracks might not exist. Also, unwanted in edit mode. */ - - + + > + } + > + )} @@ -293,7 +358,7 @@ function MediaEdit({ /> )} - aria-label={__('Scale media', 'web-stories')} data-testid="edit-panel-slider" setProperties={updateLocalProperties} @@ -310,15 +375,4 @@ function MediaEdit({ ); } -MediaEdit.propTypes = { - element: StoryPropTypes.elements.media.isRequired, - box: StoryPropTypes.box.isRequired, - setLocalProperties: PropTypes.func.isRequired, - getProxiedUrl: PropTypes.func, - updateElementById: PropTypes.func, - zIndexCanvas: PropTypes.object, - scaleMin: PropTypes.number.isRequired, - scaleMax: PropTypes.number.isRequired, -}; - export default MediaEdit; diff --git a/packages/element-library/src/media/editCropMoveable.js b/packages/element-library/src/media/editCropMoveable.tsx similarity index 83% rename from packages/element-library/src/media/editCropMoveable.js rename to packages/element-library/src/media/editCropMoveable.tsx index 191d280dd70a..64bb6363ae18 100644 --- a/packages/element-library/src/media/editCropMoveable.js +++ b/packages/element-library/src/media/editCropMoveable.tsx @@ -17,14 +17,34 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import { useEffect, useRef } from '@googleforcreators/react'; import { useUnits, calcRotatedResizeOffset } from '@googleforcreators/units'; import { getFocalFromOffset } from '@googleforcreators/media'; import { Moveable } from '@googleforcreators/moveable'; -import { StoryPropTypes, getTransformFlip } from '@googleforcreators/elements'; +import { + getTransformFlip, + type Flip, + type MediaElement, +} from '@googleforcreators/elements'; +import type MoveableType from 'react-moveable'; -function EditCropMoveable({ +interface EditCropMoveableProps { + setProperties: (properties: Partial) => void; + cropBox: HTMLElement; + croppedMedia: HTMLElement; + flip?: Flip; + x: number; + y: number; + width: number; + height: number; + rotationAngle: number; + offsetX: number; + offsetY: number; + mediaWidth: number; + mediaHeight: number; +} + +function EditCropMoveable({ setProperties, cropBox, croppedMedia, @@ -38,13 +58,13 @@ function EditCropMoveable({ offsetY, mediaWidth, mediaHeight, -}) { +}: EditCropMoveableProps) { const { editorToDataX, editorToDataY } = useUnits((state) => ({ editorToDataX: state.actions.editorToDataX, editorToDataY: state.actions.editorToDataY, })); - const moveableRef = useRef(); + const moveableRef = useRef(null); const cropRef = useRef([0, 0, 0, 0, 0, 0]); const transformFlip = getTransformFlip(flip); @@ -57,7 +77,7 @@ function EditCropMoveable({ ); }} snappable // todo@: it looks like resizing bounds are not supported. @@ -142,20 +160,4 @@ function EditCropMoveable({ ); } -EditCropMoveable.propTypes = { - setProperties: PropTypes.func.isRequired, - cropBox: PropTypes.object.isRequired, - croppedMedia: PropTypes.object.isRequired, - flip: StoryPropTypes.flip, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - rotationAngle: PropTypes.number.isRequired, - offsetX: PropTypes.number.isRequired, - offsetY: PropTypes.number.isRequired, - mediaWidth: PropTypes.number.isRequired, - mediaHeight: PropTypes.number.isRequired, -}; - export default EditCropMoveable; diff --git a/packages/element-library/src/media/editPanMoveable.js b/packages/element-library/src/media/editPanMoveable.tsx similarity index 82% rename from packages/element-library/src/media/editPanMoveable.js rename to packages/element-library/src/media/editPanMoveable.tsx index 31731816c970..c44be2fbd026 100644 --- a/packages/element-library/src/media/editPanMoveable.js +++ b/packages/element-library/src/media/editPanMoveable.tsx @@ -17,7 +17,6 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import { useEffect, useRef, useCallback } from '@googleforcreators/react'; import { getFocalFromOffset } from '@googleforcreators/media'; import { @@ -25,9 +24,30 @@ import { getKeyboardMovement, } from '@googleforcreators/design-system'; import { Moveable } from '@googleforcreators/moveable'; -import { StoryPropTypes, getTransformFlip } from '@googleforcreators/elements'; +import { + type Flip, + getTransformFlip, + type MediaElement, +} from '@googleforcreators/elements'; +import type MoveableType from 'react-moveable'; + +interface EditPanMoveableProps { + setProperties: (properties: Partial) => void; + fullMedia: HTMLElement; + croppedMedia: HTMLElement; + flip?: Flip; + x: number; + y: number; + width: number; + height: number; + rotationAngle: number; + offsetX: number; + offsetY: number; + mediaWidth: number; + mediaHeight: number; +} -function EditPanMoveable({ +function EditPanMoveable({ setProperties, fullMedia, croppedMedia, @@ -41,8 +61,8 @@ function EditPanMoveable({ offsetY, mediaWidth, mediaHeight, -}) { - const moveableRef = useRef(); +}: EditPanMoveableProps) { + const moveableRef = useRef(null); const translateRef = useRef([0, 0]); const transformFlip = getTransformFlip(flip); @@ -73,7 +93,7 @@ function EditPanMoveable({ setProperties({ focalX: flip?.horizontal ? 100 - panFocalX : panFocalX, focalY: flip?.vertical ? 100 - panFocalY : panFocalY, - }); + } as Partial); update(); }, [ @@ -93,7 +113,7 @@ function EditPanMoveable({ return ( ); update(); }} - // Snappable snappable - snapCenter // todo@: Moveable defines bounds and guidelines as the vertical and // horizontal lines and doesn't work well with `rotationAngle > 0` for // cropping/panning. It's possible to define a larger bounds using @@ -140,20 +158,4 @@ function EditPanMoveable({ ); } -EditPanMoveable.propTypes = { - setProperties: PropTypes.func.isRequired, - fullMedia: PropTypes.object.isRequired, - croppedMedia: PropTypes.object.isRequired, - flip: StoryPropTypes.flip, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - rotationAngle: PropTypes.number.isRequired, - offsetX: PropTypes.number.isRequired, - offsetY: PropTypes.number.isRequired, - mediaWidth: PropTypes.number.isRequired, - mediaHeight: PropTypes.number.isRequired, -}; - export default EditPanMoveable; diff --git a/packages/element-library/src/media/frame.js b/packages/element-library/src/media/frame.tsx similarity index 100% rename from packages/element-library/src/media/frame.js rename to packages/element-library/src/media/frame.tsx diff --git a/packages/element-library/src/media/imageDisplay.js b/packages/element-library/src/media/imageDisplay.tsx similarity index 61% rename from packages/element-library/src/media/imageDisplay.js rename to packages/element-library/src/media/imageDisplay.tsx index 99b61ea9c7e8..df1abb94908f 100644 --- a/packages/element-library/src/media/imageDisplay.js +++ b/packages/element-library/src/media/imageDisplay.tsx @@ -19,28 +19,31 @@ */ import styled from 'styled-components'; import { useEffect, useRef, useState } from '@googleforcreators/react'; -import PropTypes from 'prop-types'; import { - preloadImage, - resourceList, - getMediaSizePositionProps, calculateSrcSet, + getMediaSizePositionProps, getSmallestUrlForWidth, + preloadImage, + type ResourceCacheEntry, + ResourceCacheEntryType, + resourceList, } from '@googleforcreators/media'; -import { StoryPropTypes } from '@googleforcreators/elements'; +import type { ImageElement, DisplayProps } from '@googleforcreators/elements'; +import type { HTMLProps } from 'react'; /** * Internal dependencies */ -import { noop } from '../utils/noop'; import { mediaWithScale } from './util'; import MediaDisplay from './display'; -const Img = styled.img` +const Image = styled.img` position: absolute; ${mediaWithScale} `; +const noop = () => false; + function ImageDisplay({ element, box, @@ -49,22 +52,22 @@ function ImageDisplay({ isCurrentResourceProcessing = noop, isCurrentResourceUploading = noop, renderResourcePlaceholder, -}) { +}: DisplayProps) { const { resource, scale, focalX, focalY } = element; const { id: resourceId, alt } = resource; const { width, height } = box; - const ref = useRef(); + const ref = useRef(null); let initialSrcType = 'smallest'; - let initialSrc = getSmallestUrlForWidth(0, resource); + let initialSrc: string | null = getSmallestUrlForWidth(0, resource); - if (resourceList.get(resourceId)?.type === 'cached') { + if (resourceList.get(resourceId)?.type === ResourceCacheEntryType.Cached) { initialSrcType = 'cached'; - initialSrc = resourceList.get(resourceId).url; + initialSrc = (resourceList.get(resourceId) as ResourceCacheEntry).url; } if ( - resourceList.get(resourceId)?.type === 'fullsize' || + resourceList.get(resourceId)?.type === ResourceCacheEntryType.Fullsize || isCurrentResourceProcessing(resourceId) || isCurrentResourceUploading(resourceId) ) { @@ -75,10 +78,10 @@ function ImageDisplay({ initialSrc = getProxiedUrl(resource, initialSrc); const [srcType, setSrcType] = useState(initialSrcType); - const [src, setSrc] = useState(initialSrc); + const [src, setSrc] = useState(initialSrc); const srcSet = srcType === 'fullsize' ? calculateSrcSet(resource) : ''; - const imgProps = getMediaSizePositionProps( + const imgProps: HTMLProps = getMediaSizePositionProps( resource, width, height, @@ -90,23 +93,32 @@ function ImageDisplay({ imgProps.crossOrigin = 'anonymous'; useEffect(() => { - let timeout; + let timeout: number; let mounted = true; - if (resourceList.get(resourceId)?.type !== 'fullsize' && resource.src) { - timeout = setTimeout(async () => { - const url = getProxiedUrl(resource, resource.src); - try { - const preloadedImg = await preloadImage({ src: url, srcset: srcSet }); - if (mounted) { - resourceList.set(resource.id, { - type: 'fullsize', + if ( + resourceList.get(resourceId)?.type !== ResourceCacheEntryType.Fullsize && + resource.src + ) { + timeout = window.setTimeout(() => { + void (async () => { + const url: string = getProxiedUrl(resource, resource.src) as string; + try { + const preloadedImg = await preloadImage({ + src: url, + srcset: srcSet || undefined, }); - setSrc(preloadedImg.currentSrc); - setSrcType('fullsize'); + if (mounted) { + resourceList.set(resource.id, { + type: ResourceCacheEntryType.Fullsize, + url, + }); + setSrc(preloadedImg.currentSrc); + setSrcType(ResourceCacheEntryType.Fullsize); + } + } catch { + // Ignore } - } catch { - // Ignore - } + })(); }); } else { setSrc(getProxiedUrl(resource, resource.src)); @@ -120,19 +132,20 @@ function ImageDisplay({ const showPlaceholder = srcType !== 'fullsize' || resource.isPlaceholder; return ( - element={element} mediaRef={ref} showPlaceholder={showPlaceholder} previewMode={previewMode} renderResourcePlaceholder={renderResourcePlaceholder} > - {alt}} props Props. - * @return {*} Rendered component. + * @return Rendered component. */ function MediaOutput({ element: { resource, scale, focalX, focalY }, box: { width: vw, height: vh }, children, ...props -}) { +}: MediaOutputProps) { // Width and height are taken from the basis of 100% taking into account the // aspect ratio. const width = vw; @@ -51,7 +61,7 @@ function MediaOutput({ focalY ); - const wrapperStyle = { + const wrapperStyle: CSSProperties = { position: 'absolute', width: `${editorPixels((mediaProps.width / width) * 100)}%`, height: `${editorPixels((mediaProps.height / height) * 100)}%`, @@ -66,10 +76,4 @@ function MediaOutput({ ); } -MediaOutput.propTypes = { - element: StoryPropTypes.elements.media.isRequired, - box: StoryPropTypes.box.isRequired, - children: PropTypes.node.isRequired, -}; - export default MediaOutput; diff --git a/packages/element-library/src/media/scalePanel.js b/packages/element-library/src/media/scalePanel.tsx similarity index 66% rename from packages/element-library/src/media/scalePanel.js rename to packages/element-library/src/media/scalePanel.tsx index 5fd9252bdb55..fbaaa0c72a44 100644 --- a/packages/element-library/src/media/scalePanel.js +++ b/packages/element-library/src/media/scalePanel.tsx @@ -17,23 +17,28 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import styled from 'styled-components'; import { _x } from '@googleforcreators/i18n'; import { Slider } from '@googleforcreators/design-system'; import { InOverlay } from '@googleforcreators/moveable'; +import type { MediaElement } from '@googleforcreators/elements'; const MIN_WIDTH = 165; const HEIGHT = 36; const OFFSET_Y = 8; const HORIZONTAL_PADDING = 8; -const Container = styled.div` +const Container = styled.div<{ + $x: number; + $y: number; + $width: number; + $height: number; +}>` position: absolute; - left: ${({ x, width }) => - `${x + (width - Math.max(width, MIN_WIDTH)) / 2}px`}; - top: ${({ y, height }) => `${y + height + OFFSET_Y}px`}; - width: ${({ width }) => `${Math.max(width, MIN_WIDTH)}px`}; + left: ${({ $x, $width }) => + `${$x + ($width - Math.max($width, MIN_WIDTH)) / 2}px`}; + top: ${({ $y, $height }) => `${$y + $height + OFFSET_Y}px`}; + width: ${({ $width }) => `${Math.max($width, MIN_WIDTH)}px`}; height: ${HEIGHT}px; background: ${({ theme }) => theme.colors.bg.primary}; border-radius: 8px; @@ -41,12 +46,24 @@ const Container = styled.div` margin-top: 8px; `; -const ScaleSlider = styled(Slider)` +const ScaleSlider = styled(Slider)<{ width: number }>` width: ${({ width }) => Math.max(width, MIN_WIDTH) - 2 * HORIZONTAL_PADDING}px; `; -function ScalePanel({ +interface ScalePanelProps { + setProperties: (properties: Partial) => void; + width: number; + height: number; + x: number; + y: number; + scale: number; + zIndexCanvas: Record; + min?: number; + max?: number; +} + +function ScalePanel({ setProperties, width, height, @@ -55,10 +72,10 @@ function ScalePanel({ scale, zIndexCanvas, ...rest -}) { +}: ScalePanelProps) { return ( - + {/* @todo: Should maxScale depend on the maximum resolution? Or should that be left up to the helper errors? Both? In either case there'd be maximum @@ -69,7 +86,9 @@ function ScalePanel({ majorStep={10} minorStep={1} value={scale} - handleChange={(value) => setProperties({ scale: value })} + handleChange={(value: number) => + setProperties({ scale: value } as Partial) + } thumbSize={24} suffix={_x('%', 'Percentage', 'web-stories')} {...rest} @@ -79,14 +98,4 @@ function ScalePanel({ ); } -ScalePanel.propTypes = { - setProperties: PropTypes.func.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - scale: PropTypes.number.isRequired, - zIndexCanvas: PropTypes.object, -}; - export default ScalePanel; diff --git a/packages/element-library/src/media/textContent.ts b/packages/element-library/src/media/textContent.ts new file mode 100644 index 000000000000..ba496ccc8cbc --- /dev/null +++ b/packages/element-library/src/media/textContent.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { type MediaElement } from '@googleforcreators/elements'; + +function TextContent({ resource }: MediaElement) { + return `image: ${resource.src}`; +} + +export default TextContent; diff --git a/packages/element-library/src/media/util.js b/packages/element-library/src/media/util.ts similarity index 83% rename from packages/element-library/src/media/util.js rename to packages/element-library/src/media/util.ts index 76626cd84c21..941d5e8d4988 100644 --- a/packages/element-library/src/media/util.js +++ b/packages/element-library/src/media/util.ts @@ -19,21 +19,41 @@ */ import styled, { css } from 'styled-components'; -export const mediaWithScale = css` +export const mediaWithScale = css<{ + width: number; + height: number; + offsetX: number; + offsetY: number; +}>` width: ${({ width }) => `${width}px`}; height: ${({ height }) => `${height}px`}; left: ${({ offsetX }) => `${-offsetX}px`}; top: ${({ offsetY }) => `${-offsetY}px`}; `; -const videoWithScale = css` +const videoWithScale = css<{ + width: number; + offsetX: number; + offsetY: number; + isBackground?: boolean; +}>` width: ${({ width }) => `${width}px`}; left: ${({ offsetX }) => `${-offsetX}px`}; top: ${({ offsetY }) => `${-offsetY}px`}; max-width: ${({ isBackground }) => (isBackground ? 'initial' : null)}; `; -export function getMediaWithScaleCss({ width, height, offsetX, offsetY }) { +export function getMediaWithScaleCss({ + width, + height, + offsetX, + offsetY, +}: { + width: number; + height: number; + offsetX: number; + offsetY: number; +}) { // @todo: This is a complete duplication of `mediaWithScale` above. But // no other apparent way to execute interpolate `mediaWithScale` dynamically. return `width:${width}px; height:${height}px; left:${-offsetX}px; top:${-offsetY}px;`; diff --git a/packages/element-library/src/media/videoImage.js b/packages/element-library/src/media/videoImage.tsx similarity index 85% rename from packages/element-library/src/media/videoImage.js rename to packages/element-library/src/media/videoImage.tsx index 895b2aa2371c..7fc9a64f74bf 100644 --- a/packages/element-library/src/media/videoImage.js +++ b/packages/element-library/src/media/videoImage.tsx @@ -18,7 +18,6 @@ * External dependencies */ import styled from 'styled-components'; -import PropTypes from 'prop-types'; const Video = styled.video` display: block; @@ -28,13 +27,14 @@ const Video = styled.video` object-fit: cover; `; -function VideoImage({ alt, ...attrs }) { +function VideoImage({ alt, ...attrs }: { src: string; alt: string }) { return (