diff --git a/includes/KSES.php b/includes/KSES.php index 7d902f7f4045..4c1b3821ed42 100644 --- a/includes/KSES.php +++ b/includes/KSES.php @@ -736,6 +736,11 @@ public function filter_kses_allowed_html( $allowed_tags ) { 'clip-rule' => true, 'fill' => true, ], + 'amp-story-audio-sticker' => [ + 'size' => true, + 'sticker' => true, + 'sticker-style' => true, + ], ]; $allowed_tags = $this->array_merge_recursive_distinct( $allowed_tags, $story_components ); diff --git a/packages/design-system/src/icons/audio_stickers.svg b/packages/design-system/src/icons/audio_stickers.svg new file mode 100644 index 000000000000..bd69726dc48a --- /dev/null +++ b/packages/design-system/src/icons/audio_stickers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/design-system/src/icons/index.ts b/packages/design-system/src/icons/index.ts index b9846ad81db0..301b688bcb4e 100644 --- a/packages/design-system/src/icons/index.ts +++ b/packages/design-system/src/icons/index.ts @@ -17,6 +17,7 @@ /** * Internal dependencies */ + export { default as AlignBottom } from './align_bottom.svg'; export { default as AlignCenter } from './align_center.svg'; export { default as AlignLeft } from './align_left.svg'; @@ -39,8 +40,9 @@ export { default as ArrowOutline } from './arrow_outline.svg'; export { default as ArrowRight } from './arrow_right.svg'; export { default as ArrowRightCurved } from './arrow_right_curved.svg'; export { default as ArrowRightLarge } from './arrow_right_large.svg'; -export { default as ArrowUp } from './arrow_up.svg'; export { default as ArrowsLeftRight } from './arrows_leftright.svg'; +export { default as ArrowUp } from './arrow_up.svg'; +export { default as AudioSticker } from './audio_stickers.svg'; export { default as BackgroundBlur } from './background_blur.svg'; export { default as BackgroundBlurOff } from './background_blur_off.svg'; export { default as Border } from './border.svg'; @@ -83,6 +85,7 @@ export { default as FloppyDisk } from './floppy_disk.svg'; export { default as GearWithGauge } from './gear_with_gauge.svg'; export { default as Gif } from './gif.svg'; export { default as Group } from './group.svg'; +export { default as History } from './history.svg'; export { default as Keyboard } from './keyboard.svg'; export { default as Launch } from './launch.svg'; export { default as LetterAHeight } from './letter_a_height.svg'; @@ -94,9 +97,9 @@ export { default as LetterMOutline } from './letter_m_outline.svg'; export { default as LetterSStrikethrough } from './letter_s_strikethrough.svg'; export { default as LetterT } from './letter_t.svg'; export { default as LetterTArrow } from './letter_t_arrow.svg'; +export { default as LetterTLargeLetterTSmall } from './letter_t_large_letter_t_small.svg'; export { default as LetterTPlus } from './letter_t_plus.svg'; export { default as LetterTUppercase } from './letter_t_uppercase.svg'; -export { default as LetterTLargeLetterTSmall } from './letter_t_large_letter_t_small.svg'; export { default as LetterUUnderline } from './letter_u_underline.svg'; export { default as Link } from './link.svg'; export { default as LockClosed } from './lock_closed.svg'; @@ -123,12 +126,13 @@ export { default as PictureSwap } from './picture_swap.svg'; export { default as Pipette } from './pipette.svg'; export { default as Play } from './play.svg'; export { default as PlayFilled } from './play_filled.svg'; -export { default as PlusFilledSmall } from './plus_filled_small.svg'; export { default as PlayOutline } from './play_outline.svg'; export { default as Plus } from './plus.svg'; export { default as PlusFilled } from './plus_filled.svg'; +export { default as PlusFilledSmall } from './plus_filled_small.svg'; export { default as PlusOutline } from './plus_outline.svg'; export { default as QuestionMarkOutline } from './question_mark_outline.svg'; +export { default as RemoveMask } from './remove_mask.svg'; export { default as Rotate } from './rotate.svg'; export { default as Scissors } from './scissors.svg'; export { default as Settings } from './settings.svg'; @@ -148,5 +152,3 @@ export { default as Union } from './union.svg'; export { default as Video } from './video.svg'; export { default as Visibility } from './visibility.svg'; export { default as VisibilityOff } from './visibility_off.svg'; -export { default as RemoveMask } from './remove_mask.svg'; -export { default as History } from './history.svg'; diff --git a/packages/design-system/src/utils/panelTypes.ts b/packages/design-system/src/utils/panelTypes.ts index 1cb98a21eef9..5fab8003a1f5 100644 --- a/packages/design-system/src/utils/panelTypes.ts +++ b/packages/design-system/src/utils/panelTypes.ts @@ -34,6 +34,7 @@ enum PanelTypes { ImageAccessibility = 'imageAccessibility', VideoAcessibility = 'videoAccessibility', Product = 'product', + AudioSticker = 'audioSticker', } export default PanelTypes; diff --git a/packages/element-library/src/audioSticker/constants.js b/packages/element-library/src/audioSticker/constants.js new file mode 100644 index 000000000000..2a704767996e --- /dev/null +++ b/packages/element-library/src/audioSticker/constants.js @@ -0,0 +1,58 @@ +/* + * Copyright 2023 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 { PanelTypes } from '@googleforcreators/design-system'; + +/** + * Internal dependencies + */ +import { SHARED_DEFAULT_ATTRIBUTES } from '../shared'; + +export const hasEditMode = false; +export const hasEditModeIfLocked = false; +export const hasEditModeMoveable = false; +export const editModeGrayout = false; + +export const hasDesignMenu = true; + +export const hasDuplicateMenu = false; + +export const isMedia = false; + +export const canFlip = true; + +export const isMaskable = false; + +export const isAspectAlwaysLocked = true; + +export const resizeRules = { + vertical: false, + horizontal: false, + diagonal: false, +}; + +export const defaultAttributes = { + ...SHARED_DEFAULT_ATTRIBUTES, + size: 'small', + sticker: 'headphone-cat', + style: 'none', + lockDimensions: true, +}; + +export const panels = [PanelTypes.ElementAlignment, PanelTypes.AudioSticker]; diff --git a/packages/element-library/src/audioSticker/display.js b/packages/element-library/src/audioSticker/display.js new file mode 100644 index 000000000000..02a499ca24cf --- /dev/null +++ b/packages/element-library/src/audioSticker/display.js @@ -0,0 +1,60 @@ +/* + * Copyright 2023 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 styled from 'styled-components'; +import { StoryPropTypes } from '@googleforcreators/elements'; + +/** + * Internal dependencies + */ +import { elementFillContent } from '../shared'; +import { + AUDIO_STICKERS, + AUDIO_STICKER_STYLES, + AUDIO_STICKER_LABELS, +} from '../constants'; + +const Element = styled.img` + ${elementFillContent} + ${({ stickerStyle }) => AUDIO_STICKER_STYLES[stickerStyle]} +`; + +function AudioStickerDisplay({ element }) { + const { + width: elementWidth, + height: elementHeight, + sticker, + style, + } = element; + + return ( + + ); +} + +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.js new file mode 100644 index 000000000000..c938b30d2c6e --- /dev/null +++ b/packages/element-library/src/audioSticker/icon.js @@ -0,0 +1,37 @@ +/* + * 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 styled from 'styled-components'; +import { Icons } from '@googleforcreators/design-system'; + +const IconContainer = styled.div` + height: 21px; + width: 21px; + overflow: hidden; +`; + +function AudioStickerIcon() { + return ( + + + + ); +} + +export default AudioStickerIcon; diff --git a/packages/element-library/src/audioSticker/images/audio-cloud/audio-cloud-posttap.png b/packages/element-library/src/audioSticker/images/audio-cloud/audio-cloud-posttap.png new file mode 100644 index 000000000000..51511459f650 Binary files /dev/null and b/packages/element-library/src/audioSticker/images/audio-cloud/audio-cloud-posttap.png differ diff --git a/packages/element-library/src/audioSticker/images/audio-cloud/audio-cloud-pretap.png b/packages/element-library/src/audioSticker/images/audio-cloud/audio-cloud-pretap.png new file mode 100644 index 000000000000..c748cfb6fcc9 Binary files /dev/null and b/packages/element-library/src/audioSticker/images/audio-cloud/audio-cloud-pretap.png differ diff --git a/packages/element-library/src/audioSticker/images/headphone-cat/headphone-cat-posttap.gif b/packages/element-library/src/audioSticker/images/headphone-cat/headphone-cat-posttap.gif new file mode 100644 index 000000000000..7ee380cc8409 Binary files /dev/null and b/packages/element-library/src/audioSticker/images/headphone-cat/headphone-cat-posttap.gif differ diff --git a/packages/element-library/src/audioSticker/images/headphone-cat/headphone-cat-pretap.png b/packages/element-library/src/audioSticker/images/headphone-cat/headphone-cat-pretap.png new file mode 100644 index 000000000000..a818ec39b66b Binary files /dev/null and b/packages/element-library/src/audioSticker/images/headphone-cat/headphone-cat-pretap.png differ diff --git a/packages/element-library/src/audioSticker/images/loud-speaker/loud-speaker-posttap.png b/packages/element-library/src/audioSticker/images/loud-speaker/loud-speaker-posttap.png new file mode 100644 index 000000000000..98428118de84 Binary files /dev/null and b/packages/element-library/src/audioSticker/images/loud-speaker/loud-speaker-posttap.png differ diff --git a/packages/element-library/src/audioSticker/images/loud-speaker/loud-speaker-pretap.png b/packages/element-library/src/audioSticker/images/loud-speaker/loud-speaker-pretap.png new file mode 100644 index 000000000000..fd598a7eb32c Binary files /dev/null and b/packages/element-library/src/audioSticker/images/loud-speaker/loud-speaker-pretap.png differ diff --git a/packages/element-library/src/audioSticker/images/tape-player/tape-player-posttap.gif b/packages/element-library/src/audioSticker/images/tape-player/tape-player-posttap.gif new file mode 100644 index 000000000000..a25cc2dc65f8 Binary files /dev/null and b/packages/element-library/src/audioSticker/images/tape-player/tape-player-posttap.gif differ diff --git a/packages/element-library/src/audioSticker/images/tape-player/tape-player-pretap.png b/packages/element-library/src/audioSticker/images/tape-player/tape-player-pretap.png new file mode 100644 index 000000000000..1157457fcd4e Binary files /dev/null and b/packages/element-library/src/audioSticker/images/tape-player/tape-player-pretap.png differ diff --git a/packages/element-library/src/audioSticker/index.js b/packages/element-library/src/audioSticker/index.js new file mode 100644 index 000000000000..9f614dd784b5 --- /dev/null +++ b/packages/element-library/src/audioSticker/index.js @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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. + */ +export { default as Display } from './display'; +export { default as Output } from './output'; +export { default as LayerIcon } from './icon'; +export { default as getLayerText } from './layer'; + +export * from './constants'; diff --git a/packages/element-library/src/audioSticker/layer.js b/packages/element-library/src/audioSticker/layer.js new file mode 100644 index 000000000000..43768d113c67 --- /dev/null +++ b/packages/element-library/src/audioSticker/layer.js @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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 { __ } from '@googleforcreators/i18n'; + +function getAudioStickerLayerText() { + return __('Audio Sticker', 'web-stories'); +} + +export default getAudioStickerLayerText; diff --git a/packages/element-library/src/audioSticker/output.js b/packages/element-library/src/audioSticker/output.js new file mode 100644 index 000000000000..7086190bee0a --- /dev/null +++ b/packages/element-library/src/audioSticker/output.js @@ -0,0 +1,43 @@ +/* + * Copyright 2023 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'; + +function AudioStickerOutput({ element }) { + return ( +
+ +
+ ); +} + +AudioStickerOutput.propTypes = { + element: StoryPropTypes.elements.audioSticker.isRequired, +}; + +export default AudioStickerOutput; diff --git a/packages/element-library/src/audioSticker/test/output.js b/packages/element-library/src/audioSticker/test/output.js new file mode 100644 index 000000000000..df4b78cf948f --- /dev/null +++ b/packages/element-library/src/audioSticker/test/output.js @@ -0,0 +1,42 @@ +/* + * 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. + */ + +/** + * Internal dependencies + */ +import AudioStickerOutput from '../output'; + +describe('AudioSticker output', () => { + it('should produce valid AMP output', async () => { + const props = { + element: { + type: 'audioSticker', + id: '123', + x: 50, + y: 100, + height: 1920, + width: 1080, + rotationAngle: 0, + size: 'small', + sticker: 'headphone-cat', + style: 'none', + }, + box: { width: 1080, height: 1920, x: 50, y: 100, rotationAngle: 0 }, + }; + + await expect().toBeValidAMPStoryElement(); + }); +}); diff --git a/packages/element-library/src/constants.ts b/packages/element-library/src/constants.ts index e538748d2e0f..c7afb4ebb54b 100644 --- a/packages/element-library/src/constants.ts +++ b/packages/element-library/src/constants.ts @@ -13,6 +13,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * External dependencies + */ +import { __ } from '@googleforcreators/i18n'; + +/** + * Internal dependencies + */ +import headphoneCat from './audioSticker/images/headphone-cat/headphone-cat-pretap.png'; +import tapePlayer from './audioSticker/images/tape-player/tape-player-pretap.png'; +import loudSpeaker from './audioSticker/images/loud-speaker/loud-speaker-posttap.png'; +import audioCloud from './audioSticker/images/audio-cloud/audio-cloud-posttap.png'; + +// TODO: Move to audioSticker folder once TypeScript conversion is complete. +// See https://github.com/GoogleForCreators/web-stories-wp/pull/13503. +export const AUDIO_STICKERS: Record = { + 'headphone-cat': headphoneCat, + 'tape-player': tapePlayer, + 'loud-speaker': loudSpeaker, + 'audio-cloud': audioCloud, +} as const; + +export const AUDIO_STICKER_STYLES = { + none: '', + outline: 'border: 4px solid white; border-radius: 20px', + dropshadow: 'filter: drop-shadow(2px 2px 10px white)', +}; + +export const AUDIO_STICKER_LABELS = { + 'headphone-cat': { + label: __('Headphone Cat', 'web-stories'), + }, + 'tape-player': { + label: __('Tape Player', 'web-stories'), + }, + 'loud-speaker': { + label: __('Loud Speaker', 'web-stories'), + }, + 'audio-cloud': { + label: __('Audio Cloud', 'web-stories'), + }, +}; + export const DEFAULT_ATTRIBUTES_FOR_MEDIA = { scale: 100, focalX: 50, diff --git a/packages/element-library/src/elementTypes.js b/packages/element-library/src/elementTypes.js index f2c08bb18024..1554ad03bc7f 100644 --- a/packages/element-library/src/elementTypes.js +++ b/packages/element-library/src/elementTypes.js @@ -29,6 +29,7 @@ import * as videoElement from './video'; import * as gifElement from './gif'; import * as stickerElement from './sticker'; import * as productElement from './product'; +import * as audioStickerElement from './audioSticker'; const elementTypes = [ { type: 'text', name: __('Text', 'web-stories'), ...textElement }, @@ -42,6 +43,11 @@ const elementTypes = [ name: __('Product', 'web-stories'), ...productElement, }, + { + type: 'audioSticker', + name: __('Audio Sticker', 'web-stories'), + ...audioStickerElement, + }, ]; export default elementTypes; diff --git a/packages/element-library/src/product/output.js b/packages/element-library/src/product/output.js index 6ddf8b261506..8fb57502a645 100644 --- a/packages/element-library/src/product/output.js +++ b/packages/element-library/src/product/output.js @@ -36,7 +36,7 @@ function ProductOutput({ element }) { } ProductOutput.propTypes = { - element: StoryPropTypes.elements.shape.isRequired, + element: StoryPropTypes.elements.product.isRequired, }; export default ProductOutput; diff --git a/packages/element-library/src/typings/images.d.ts b/packages/element-library/src/typings/images.d.ts new file mode 100644 index 000000000000..a806794ce5bd --- /dev/null +++ b/packages/element-library/src/typings/images.d.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2024 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. + */ + +declare module '*.png' { + const value: string; + export = value; +} diff --git a/packages/element-library/tsconfig.json b/packages/element-library/tsconfig.json index 8d8998d7ea2f..37b6ba5ba1d5 100644 --- a/packages/element-library/tsconfig.json +++ b/packages/element-library/tsconfig.json @@ -19,9 +19,11 @@ { "path": "../units" } ], "include": [ + "src/audioSticker/images", "src/constants.ts", "src/types.ts", "src/types", + "src/typings", "src/utils/textMeasurements.tsx", "src/text/*.ts", "src/text/*.tsx" diff --git a/packages/elements/src/constants.ts b/packages/elements/src/constants.ts index a98350572eeb..61f3ccb92921 100644 --- a/packages/elements/src/constants.ts +++ b/packages/elements/src/constants.ts @@ -27,6 +27,7 @@ export const ELEMENT_TYPES = { GIF: 'gif', STICKER: 'sticker', PRODUCT: 'product', + AUDIO_STICKER: 'audioSticker', } as const; export const MEDIA_ELEMENT_TYPES = [ diff --git a/packages/elements/src/types/element.ts b/packages/elements/src/types/element.ts index 0956ca0728a7..4082eb904a71 100644 --- a/packages/elements/src/types/element.ts +++ b/packages/elements/src/types/element.ts @@ -187,6 +187,13 @@ export interface TextElement extends Element { textAlign: TextAlign; } +export interface AudioStickerElement extends Element { + type: ElementType.AudioSticker; + sticker: string; + size: string; + style: string; +} + export interface ShapeElement extends Element { type: ElementType.Shape; } diff --git a/packages/elements/src/types/elementType.ts b/packages/elements/src/types/elementType.ts index 93585dc80a2c..67c31d4ef0c9 100644 --- a/packages/elements/src/types/elementType.ts +++ b/packages/elements/src/types/elementType.ts @@ -22,4 +22,5 @@ export enum ElementType { Sticker = 'sticker', Shape = 'shape', Product = 'product', + AudioSticker = 'audioSticker', } diff --git a/packages/elements/src/types/propTypes.ts b/packages/elements/src/types/propTypes.ts index ce13a87817d1..92c415adf6fc 100644 --- a/packages/elements/src/types/propTypes.ts +++ b/packages/elements/src/types/propTypes.ts @@ -193,6 +193,14 @@ const background = PropTypes.shape({ inner: element, }); +const product = PropTypes.shape({ + ...StoryElementPropTypes, +}); + +const audioSticker = PropTypes.shape({ + ...StoryElementPropTypes, +}); + const StoryPropTypes = { mask, link, @@ -202,7 +210,18 @@ const StoryPropTypes = { element, layer, textContent, - elements: { image, video, gif, media, text, shape, sticker, background }, + elements: { + image, + video, + gif, + media, + text, + shape, + sticker, + background, + product, + audioSticker, + }, }; export { StoryPropTypes }; diff --git a/packages/jest-amp/src/utils.js b/packages/jest-amp/src/utils.js index 2ebb021df983..12b42a7fcf87 100644 --- a/packages/jest-amp/src/utils.js +++ b/packages/jest-amp/src/utils.js @@ -24,6 +24,8 @@ import AmpOptimizer, { TRANSFORMATIONS_AMP_FIRST, } from '@ampproject/toolbox-optimizer'; import amphtmlValidator from 'amphtml-validator'; +// eslint-disable-next-line import/no-extraneous-dependencies -- Required by @ampproject/toolbox-optimizer anyway. +import runtimeVersion from '@ampproject/toolbox-runtime-version'; const fallback = tmpdir() + '/validator_wasm.js'; const validatorJs = @@ -123,6 +125,7 @@ async function getAMPValidationErrors(string, optimize = true) { }); const params = { canonical: 'https://example.com', + ampRuntimeVersion: await runtimeVersion.currentVersion(), }; completeString = await ampOptimizer.transformHtml(completeString, params); } diff --git a/packages/output/src/page.tsx b/packages/output/src/page.tsx index ebd862364dc1..8b098b7b634c 100644 --- a/packages/output/src/page.tsx +++ b/packages/output/src/page.tsx @@ -220,7 +220,6 @@ function OutputPage({ )} - {/* needs to be the last child element */} {hasPageAttachment && } {hasProducts && ( diff --git a/packages/output/src/utils/getUsedAmpExtensions.ts b/packages/output/src/utils/getUsedAmpExtensions.ts index f24a2dcc3fde..90f6d3cd7840 100644 --- a/packages/output/src/utils/getUsedAmpExtensions.ts +++ b/packages/output/src/utils/getUsedAmpExtensions.ts @@ -63,6 +63,11 @@ const getUsedAmpExtensions = (pages: Page[]) => { src: 'https://cdn.ampproject.org/v0/amp-story-shopping-0.1.js', }; + const ampStoryAudioSticker: AmpExtension = { + name: 'amp-story-audio-sticker', + src: 'https://cdn.ampproject.org/v0/amp-story-audio-sticker-0.1.js', + }; + for (const { elements, backgroundAudio } of pages) { if (backgroundAudio?.resource?.src && backgroundAudio?.tracks?.length) { extensions.push(ampVideo); @@ -83,6 +88,9 @@ const getUsedAmpExtensions = (pages: Page[]) => { case ElementType.Product: extensions.push(ampStoryShopping); break; + case ElementType.AudioSticker: + extensions.push(ampStoryAudioSticker); + break; default: break; } diff --git a/packages/output/src/utils/styles.tsx b/packages/output/src/utils/styles.tsx index 40534c52c986..1cb7c1b409f8 100644 --- a/packages/output/src/utils/styles.tsx +++ b/packages/output/src/utils/styles.tsx @@ -135,7 +135,7 @@ function CustomStyles() { amp-story-grid-layer.align-bottom { align-content: end; padding: 0; - /* + /* AMP CTA Layer will exactly occupy 74px regardless of any device. To space out captions 74px from the BOTTOM (AMP CTA Layer), 74px from the TOP should also be spaced out and thus: 2 * 74px @@ -152,6 +152,14 @@ function CustomStyles() { margin-bottom: 16px; text-align: center; } + + amp-story-audio-sticker { + height: 100%; + } + + .audio-sticker { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; + } `, }} /> diff --git a/packages/story-editor/src/app/quickActions/constants.js b/packages/story-editor/src/app/quickActions/constants.js index 3a8f63fe7d75..26586e190669 100644 --- a/packages/story-editor/src/app/quickActions/constants.js +++ b/packages/story-editor/src/app/quickActions/constants.js @@ -76,6 +76,10 @@ export const ACTIONS = { trackingEventName: 'trim_video', text: __('Trim video', 'web-stories'), }, + INSERT_AUDIO_STICKER: { + trackingEventName: 'insert_audio_sticker', + text: __('Insert audio sticker', 'web-stories'), + }, }; export const RESET_PROPERTIES = { diff --git a/packages/story-editor/src/app/quickActions/useQuickActions.js b/packages/story-editor/src/app/quickActions/useQuickActions.js index 0f9aeb50a09a..061e7d8311e9 100644 --- a/packages/story-editor/src/app/quickActions/useQuickActions.js +++ b/packages/story-editor/src/app/quickActions/useQuickActions.js @@ -31,13 +31,14 @@ import { useStory } from '../story'; import useInsertElement from '../../components/canvas/useInsertElement'; import { DEFAULT_PRESET } from '../../components/library/panes/text/textPresets'; import { useMediaRecording } from '../../components/mediaRecording'; +import { AUDIO_STICKER_DEFAULT_PRESET } from '../../constants'; import { getResetProperties } from './utils'; import { ACTIONS } from './constants'; import useTextActions from './useTextActions'; import useMediaActions from './useMediaActions'; import useForegroundActions from './useForegroundActions'; -const { Bucket, LetterTPlus, Media } = Icons; +const { Bucket, LetterTPlus, Media, AudioSticker } = Icons; /** * Determines the quick actions to display in the quick @@ -150,8 +151,21 @@ const useQuickActions = () => { [handleMouseDown] ); + const hasAudioAnywhere = useStory( + ({ state }) => + state.story?.backgroundAudio || + state.pages?.some((page) => { + return ( + page.backgroundAudio || + page.elements + .filter((element) => element.type === ElementType.Video) + .some((element) => !element.resource.isMuted) + ); + }) + ); + const noElementSelectedActions = useMemo(() => { - return [ + const actions = [ { Icon: Bucket, label: ACTIONS.CHANGE_BACKGROUND_COLOR.text, @@ -195,6 +209,19 @@ const useQuickActions = () => { ...actionMenuProps, }, ]; + + if (hasAudioAnywhere) { + actions.push({ + Icon: AudioSticker, + label: ACTIONS.INSERT_AUDIO_STICKER.text, + onClick: () => { + insertElement('audioSticker', AUDIO_STICKER_DEFAULT_PRESET); + }, + ...actionMenuProps, + }); + } + + return actions; }, [ actionMenuProps, backgroundElement, @@ -202,6 +229,7 @@ const useQuickActions = () => { handleFocusPageBackground, setHighlights, insertElement, + hasAudioAnywhere, ]); const foregroundCommonActions = useForegroundActions({ diff --git a/packages/story-editor/src/components/canvas/karma/quickActions.karma.js b/packages/story-editor/src/components/canvas/karma/quickActions.karma.js index c007ec9f5c4f..cde7c6e85081 100644 --- a/packages/story-editor/src/components/canvas/karma/quickActions.karma.js +++ b/packages/story-editor/src/components/canvas/karma/quickActions.karma.js @@ -128,10 +128,12 @@ describe('Quick Actions integration', () => { await fixture.events.click( fixture.editor.canvas.quickActionMenu.insertTextButton ); + await fixture.events.sleep(100); expect(fixture.editor.canvas.framesLayer.frames.length).toBe(2); expect( fixture.editor.sidebar.designPanel.selectionSection ).not.toBeNull(); + expect(document.activeElement).toEqual( fixture.editor.canvas.framesLayer.frames[1].node ); diff --git a/packages/story-editor/src/components/canvas/rightClickMenu.js b/packages/story-editor/src/components/canvas/rightClickMenu.js index d58dc7807621..e630ef590df8 100644 --- a/packages/story-editor/src/components/canvas/rightClickMenu.js +++ b/packages/story-editor/src/components/canvas/rightClickMenu.js @@ -108,6 +108,9 @@ const RightClickMenu = () => { return StickerMenu; case ELEMENT_TYPES.PRODUCT: return ProductMenu; + case ELEMENT_TYPES.AUDIO_STICKER: + // NOTE: Using same options for this case as Products. + return ProductMenu; default: return PageMenu; } diff --git a/packages/story-editor/src/components/floatingMenu/menus/audioSticker.js b/packages/story-editor/src/components/floatingMenu/menus/audioSticker.js new file mode 100644 index 000000000000..4ccde5367427 --- /dev/null +++ b/packages/story-editor/src/components/floatingMenu/menus/audioSticker.js @@ -0,0 +1,58 @@ +/* + * Copyright 2021 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 { memo } from '@googleforcreators/react'; + +/** + * Internal dependencies + */ +import { + LayerOpacity, + FlipHorizontal, + FlipVertical, + More, + Separator, + Dismiss, + Settings, +} from '../elements'; + +const FloatingAudioStickerMenu = memo(function FloatingAudioStickerMenu() { + return ( + <> + + + + + + + + + + + + + + + + + + ); +}); + +export default FloatingAudioStickerMenu; diff --git a/packages/story-editor/src/components/floatingMenu/menus/selector.js b/packages/story-editor/src/components/floatingMenu/menus/selector.js index d8fbcfc326f4..b79413610c3c 100644 --- a/packages/story-editor/src/components/floatingMenu/menus/selector.js +++ b/packages/story-editor/src/components/floatingMenu/menus/selector.js @@ -31,6 +31,7 @@ import Sticker from './sticker'; import Text from './text'; import Video from './video'; import Product from './product'; +import AudioSticker from './audioSticker'; const FloatingMenuSelector = memo(function FloatingMenuSelector({ selectedElementType, @@ -52,6 +53,8 @@ const FloatingMenuSelector = memo(function FloatingMenuSelector({ return