From a5766680ac7b95887636118f14b865735e7cee56 Mon Sep 17 00:00:00 2001 From: Tim Arney Date: Fri, 4 Nov 2022 07:51:02 -0400 Subject: [PATCH 01/36] update font storage --- packages/migration/src/migrate.ts | 3 + .../v0046_removeElementFontProperties.ts | 85 +++++++++++++++++++ packages/output/src/story.js | 3 + .../src/app/story/effects/useLoadStory.js | 3 + .../src/app/story/useStoryReducer/actions.js | 9 ++ .../src/app/story/useStoryReducer/reducer.js | 4 + .../story/useStoryReducer/reducers/index.js | 1 + .../reducers/updateStoryFonts.js | 39 +++++++++ .../test/addElementsAcrossPages.js | 3 +- .../src/app/story/useStoryReducer/types.js | 1 + .../story/useStoryReducer/useStoryReducer.js | 2 +- .../app/story/utils/getStoryPropsToSave.js | 9 +- .../src/components/canvas/useInsertElement.js | 25 ++++-- .../src/components/storyFontPicker/index.js | 10 ++- .../src/utils/populateElementFontData .js | 25 ++++++ packages/wp-story-editor/src/api/story.js | 2 + 16 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 packages/migration/src/migrations/v0046_removeElementFontProperties.ts create mode 100644 packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js create mode 100644 packages/story-editor/src/utils/populateElementFontData .js diff --git a/packages/migration/src/migrate.ts b/packages/migration/src/migrate.ts index 3dd3cf810c89..d42e5a5a400d 100644 --- a/packages/migration/src/migrate.ts +++ b/packages/migration/src/migrate.ts @@ -68,6 +68,7 @@ import removeTrackName from './migrations/v0042_removeTrackName'; import removeTagNames from './migrations/v0043_removeTagNames'; import unusedProperties from './migrations/v0044_unusedProperties'; import globalPageAdvancement from './migrations/v0045_globalPageAdvancement'; +import removeElementFontProperties from './migrations/v0046_removeElementFontProperties'; type MigrationFn = (storyData: T) => S; @@ -119,6 +120,7 @@ const MIGRATIONS: Record[]> = { // eslint-disable- 43: [removeTagNames], 44: [unusedProperties], 45: [globalPageAdvancement], + 46: [removeElementFontProperties], }; export const DATA_VERSION = Math.max.apply( @@ -127,6 +129,7 @@ export const DATA_VERSION = Math.max.apply( ); export function migrate(storyData: Story, version: number): Story { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Remove when MigrationFn is typed correctly. let result = storyData; for (let v = version; v < DATA_VERSION; v++) { const migrations = MIGRATIONS[v + 1]; diff --git a/packages/migration/src/migrations/v0046_removeElementFontProperties.ts b/packages/migration/src/migrations/v0046_removeElementFontProperties.ts new file mode 100644 index 000000000000..097af957c38c --- /dev/null +++ b/packages/migration/src/migrations/v0046_removeElementFontProperties.ts @@ -0,0 +1,85 @@ +/* + * Copyright 2022 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 type { + GifElementV45, + ImageElementV45, + PageV45, + ProductElementV45, + ShapeElementV45, + StoryV45, + TextElementV45, + VideoElementV45, + UnionElementV45, +} from './v0045_globalPageAdvancement'; + +export interface TextElementV46 extends Omit { + font: { + family: string; + }; +} +export type ProductElementV46 = ProductElementV45; +export type ShapeElementV46 = ShapeElementV45; +export type ImageElementV46 = ImageElementV45; +export type VideoElementV46 = VideoElementV45; +export type GifElementV46 = GifElementV45; + +export type UnionElementV46 = + | ShapeElementV46 + | ImageElementV46 + | VideoElementV46 + | GifElementV46 + | TextElementV46 + | ProductElementV46; + +export interface StoryV46 extends Omit { + pages: PageV46[]; +} +export interface PageV46 extends Omit { + elements: UnionElementV46[]; +} + +function removeElementFontProperties({ pages, ...rest }: StoryV45): StoryV46 { + return { + pages: pages.map(reducePage), + ...rest, + }; +} + +function reducePage({ elements, ...rest }: PageV45): PageV46 { + return { + elements: elements.map(updateElement), + ...rest, + }; +} + +function updateElement(element: UnionElementV45): UnionElementV46 { + if ('font' in element) { + const { font, ...rest } = element; + return { + font: { + family: font.family, + }, + ...rest, + }; + } + return element; +} + +export default removeElementFontProperties; diff --git a/packages/output/src/story.js b/packages/output/src/story.js index 0b440e1d5914..261e0921fd99 100644 --- a/packages/output/src/story.js +++ b/packages/output/src/story.js @@ -38,6 +38,7 @@ function OutputStory({ featuredMedia, link, title, + fonts, autoAdvance, defaultPageDuration, backgroundAudio, @@ -87,6 +88,7 @@ function OutputStory({ > {pages.map((page) => ( 0 ? storyData.pages : [createPage()]; + populateElementFontData(pages, storyData?.fonts); // Initialize color/style presets, if missing. // Otherwise ensure the saved presets are unique. @@ -99,6 +101,7 @@ function loadStory(storyId, post, restore, clearHistory, globalConfig) { const story = { storyId: storyId, title, + fonts: storyData?.fonts || {}, status, author, date, diff --git a/packages/story-editor/src/app/story/useStoryReducer/actions.js b/packages/story-editor/src/app/story/useStoryReducer/actions.js index 5603812456f1..f55a3cdfebc0 100644 --- a/packages/story-editor/src/app/story/useStoryReducer/actions.js +++ b/packages/story-editor/src/app/story/useStoryReducer/actions.js @@ -221,6 +221,14 @@ const updateStory = ({ properties }) => dispatch({ type: types.UPDATE_STORY, payload: { properties } }); +const updateStoryFonts = + (dispatch) => + ({ properties }) => + dispatch({ + type: types.UPDATE_STORY_FONTS, + payload: properties, + }); + const updateAnimationState = (dispatch) => ({ animationState }) => @@ -343,6 +351,7 @@ export const exposedActions = { updateAnimationState, addAnimations, updateStory, + updateStoryFonts, toggleLayer, updateElementsByFontFamily, addGroup, diff --git a/packages/story-editor/src/app/story/useStoryReducer/reducer.js b/packages/story-editor/src/app/story/useStoryReducer/reducer.js index a3e3ac2c222e..636d3e655120 100644 --- a/packages/story-editor/src/app/story/useStoryReducer/reducer.js +++ b/packages/story-editor/src/app/story/useStoryReducer/reducer.js @@ -107,6 +107,10 @@ function reducer(state, { type, payload }) { return reducers.updateStory(state, payload); } + case types.UPDATE_STORY_FONTS: { + return reducers.updateStoryFonts(state, payload); + } + case types.UPDATE_ANIMATION_STATE: { return reducers.updateAnimationState(state, payload); } diff --git a/packages/story-editor/src/app/story/useStoryReducer/reducers/index.js b/packages/story-editor/src/app/story/useStoryReducer/reducers/index.js index 59cb521a240f..fc0565a477b1 100644 --- a/packages/story-editor/src/app/story/useStoryReducer/reducers/index.js +++ b/packages/story-editor/src/app/story/useStoryReducer/reducers/index.js @@ -53,6 +53,7 @@ export { default as restore } from './restore'; // Manipulate story-global properties. export { default as updateStory } from './updateStory'; +export { default as updateStoryFonts } from './updateStoryFonts'; // Layer groups. export { default as addGroup } from './addGroup'; diff --git a/packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js b/packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js new file mode 100644 index 000000000000..9176c6cffe63 --- /dev/null +++ b/packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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 { produce } from 'immer'; + +/** + * Updates fonts at the Story level. + * + * + * @param {Object} draft Current state + * @param {Object} payload Action payload + * @param {Array.} payload.font Font object. + */ +export const updateStoryFonts = (draft, { font }) => { + if (!font || !font.family) { + return; + } + + const fonts = [...draft.story.fonts]; + fonts[font.family] = font; + draft.story.fonts = fonts; +}; + +export default produce(updateStoryFonts); diff --git a/packages/story-editor/src/app/story/useStoryReducer/test/addElementsAcrossPages.js b/packages/story-editor/src/app/story/useStoryReducer/test/addElementsAcrossPages.js index 47b963b2ae62..9c88ce7492ad 100644 --- a/packages/story-editor/src/app/story/useStoryReducer/test/addElementsAcrossPages.js +++ b/packages/story-editor/src/app/story/useStoryReducer/test/addElementsAcrossPages.js @@ -36,6 +36,7 @@ describe('addElementsAcrossPages', () => { ], current: '111', selection: ['000'], + story: { fonts: {} }, }); const result = addElementsAcrossPages({ @@ -98,7 +99,7 @@ describe('addElementsAcrossPages', () => { animationState: STORY_ANIMATION_STATE.RESET, capabilities: {}, copiedElementState: {}, - story: {}, + story: { fonts: {} }, }); }); }); diff --git a/packages/story-editor/src/app/story/useStoryReducer/types.js b/packages/story-editor/src/app/story/useStoryReducer/types.js index ed3eee90af27..29b9769b55eb 100644 --- a/packages/story-editor/src/app/story/useStoryReducer/types.js +++ b/packages/story-editor/src/app/story/useStoryReducer/types.js @@ -46,6 +46,7 @@ export const TOGGLE_LAYER = 'TOGGLE_LAYER'; // Manipulate story-global state. export const UPDATE_STORY = 'UPDATE_STORY'; +export const UPDATE_STORY_FONTS = 'UPDATE_STORY_FONTS'; // Manipulate animation state. export const UPDATE_ANIMATION_STATE = 'UPDATE_ANIMATION_STATE'; diff --git a/packages/story-editor/src/app/story/useStoryReducer/useStoryReducer.js b/packages/story-editor/src/app/story/useStoryReducer/useStoryReducer.js index 834e8452a046..7f9cb623b90c 100644 --- a/packages/story-editor/src/app/story/useStoryReducer/useStoryReducer.js +++ b/packages/story-editor/src/app/story/useStoryReducer/useStoryReducer.js @@ -31,7 +31,7 @@ const INITIAL_STATE = { capabilities: {}, current: null, selection: [], - story: {}, + story: { fonts: {} }, animationState: STORY_ANIMATION_STATE.RESET, copiedElementState: {}, }; diff --git a/packages/story-editor/src/app/story/utils/getStoryPropsToSave.js b/packages/story-editor/src/app/story/utils/getStoryPropsToSave.js index 617949883d4a..3dea75d8c0a6 100644 --- a/packages/story-editor/src/app/story/utils/getStoryPropsToSave.js +++ b/packages/story-editor/src/app/story/utils/getStoryPropsToSave.js @@ -22,11 +22,13 @@ import { getStoryMarkup } from '@googleforcreators/output'; * Internal dependencies */ import objectPick from '../../../utils/objectPick'; +import { getInUseFontsForPages } from '../../../utils/getInUseFonts'; import getAllProducts from './getAllProducts'; function getStoryPropsToSave({ story, pages, metadata, flags }) { - const { terms, ...propsFromStory } = objectPick(story, [ + const { terms, fonts, ...propsFromStory } = objectPick(story, [ 'title', + 'fonts', 'status', 'author', 'date', @@ -43,12 +45,17 @@ function getStoryPropsToSave({ story, pages, metadata, flags }) { 'backgroundAudio', 'terms', ]); + + // cleanup font state -- only save fonts that are "in-use" + const inUseFontFamilies = getInUseFontsForPages(pages); + const inUseFonts = objectPick(fonts, inUseFontFamilies); const products = getAllProducts(pages); const content = getStoryMarkup(story, pages, metadata, flags); return { content, pages, ...propsFromStory, + fonts: inUseFonts, ...terms, products, }; diff --git a/packages/story-editor/src/components/canvas/useInsertElement.js b/packages/story-editor/src/components/canvas/useInsertElement.js index 354b913bc0b2..447baf8eea1a 100644 --- a/packages/story-editor/src/components/canvas/useInsertElement.js +++ b/packages/story-editor/src/components/canvas/useInsertElement.js @@ -26,6 +26,7 @@ import { createNewElement } from '@googleforcreators/elements'; import { useStory } from '../../app/story'; import { useLayout } from '../../app/layout'; import { ZOOM_SETTING } from '../../constants'; +import { useFont } from '../../app/font'; import useFocusCanvas from './useFocusCanvas'; import getElementProperties from './utils/getElementProperties'; @@ -39,13 +40,17 @@ function createElementForCanvas(type, props) { } function useInsertElement() { - const { addElement, combineElements, backgroundElementId } = useStory( - ({ state, actions }) => ({ + const { addElement, combineElements, backgroundElementId, updateStoryFonts } = + useStory(({ state, actions }) => ({ addElement: actions.addElement, + updateStoryFonts: actions.updateStoryFonts, combineElements: actions.combineElements, backgroundElementId: state.currentPage?.elements?.[0]?.id, - }) - ); + })); + + const { + actions: { getFontByName, getFontsBySearch }, + } = useFont(); const { setZoomSetting } = useLayout(({ actions: { setZoomSetting } }) => ({ setZoomSetting, @@ -59,12 +64,19 @@ function useInsertElement() { * @param {boolean} insertAsBackground Whether to insert the element as a background element. */ const insertElement = useCallback( - (type, props, insertAsBackground = false) => { + async (type, props, insertAsBackground = false) => { setZoomSetting(ZOOM_SETTING.FIT); const element = createElementForCanvas(type, props); const { id, resource, pageId } = element; addElement({ element, pageId }); + if (type === 'text') { + // need to ensure font is available here + await getFontsBySearch(element.font); + const font = getFontByName(element.font?.family); + updateStoryFonts({ properties: { font: font } }); + } + if (insertAsBackground) { combineElements({ firstElement: element, @@ -94,6 +106,9 @@ function useInsertElement() { combineElements, focusCanvas, setZoomSetting, + getFontByName, + getFontsBySearch, + updateStoryFonts, ] ); diff --git a/packages/story-editor/src/components/storyFontPicker/index.js b/packages/story-editor/src/components/storyFontPicker/index.js index 04eff5e6a84d..798786911af4 100644 --- a/packages/story-editor/src/components/storyFontPicker/index.js +++ b/packages/story-editor/src/components/storyFontPicker/index.js @@ -34,12 +34,12 @@ import FontPicker from '../fontPicker'; import updateProperties from '../style/updateProperties'; const StoryFontPicker = forwardRef(function StoryFontPicker({ ...rest }, ref) { - const { updateSelectedElements, selectedElements } = useStory( - ({ state, actions }) => ({ + const { updateSelectedElements, selectedElements, updateStoryFonts } = + useStory(({ state, actions }) => ({ updateSelectedElements: actions.updateSelectedElements, + updateStoryFonts: actions.updateStoryFonts, selectedElements: state.selectedElements, - }) - ); + })); const pushUpdate = useCallback( (update, commitValues) => { @@ -80,6 +80,7 @@ const StoryFontPicker = forwardRef(function StoryFontPicker({ ...rest }, ref) { const onChange = useCallback( async (newFont) => { const { id, name, value, ...newFontFormatted } = newFont; + updateStoryFonts({ properties: { font: newFont } }); trackEvent('font_family_changed', { name }); await maybeEnqueueFontStyle( @@ -106,6 +107,7 @@ const StoryFontPicker = forwardRef(function StoryFontPicker({ ...rest }, ref) { pushUpdate, selectedElements, handleResetFontWeight, + updateStoryFonts, ] ); diff --git a/packages/story-editor/src/utils/populateElementFontData .js b/packages/story-editor/src/utils/populateElementFontData .js new file mode 100644 index 000000000000..c1bd7d6fe2da --- /dev/null +++ b/packages/story-editor/src/utils/populateElementFontData .js @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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 function populateElementFontData(pages, fonts) { + return pages.map(({ elements = [] }) => { + elements.forEach((element) => { + if (element.type === 'text' && Boolean(element.font?.family)) { + element.font = fonts[element.font?.family]; + } + }); + return elements; + }); +} diff --git a/packages/wp-story-editor/src/api/story.js b/packages/wp-story-editor/src/api/story.js index c36350804b17..bc3dfe4d5279 100644 --- a/packages/wp-story-editor/src/api/story.js +++ b/packages/wp-story-editor/src/api/story.js @@ -44,6 +44,7 @@ export function getStoryById(config, storyId) { const getStorySaveData = ( { pages, + fonts, featuredMedia, globalStoryStyles, publisherLogo, @@ -62,6 +63,7 @@ const getStorySaveData = ( story_data: { version: DATA_VERSION, pages, + fonts, autoAdvance, defaultPageDuration, currentStoryStyles, From 11fbcfec6727788d0bbb22a21bb3f4908687c599 Mon Sep 17 00:00:00 2001 From: Tim Arney Date: Sat, 5 Nov 2022 08:10:28 -0400 Subject: [PATCH 02/36] updateStoryFonts --- .../app/story/useStoryReducer/reducers/updateStoryFonts.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js b/packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js index 9176c6cffe63..532c4b48c2c6 100644 --- a/packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js +++ b/packages/story-editor/src/app/story/useStoryReducer/reducers/updateStoryFonts.js @@ -30,10 +30,7 @@ export const updateStoryFonts = (draft, { font }) => { if (!font || !font.family) { return; } - - const fonts = [...draft.story.fonts]; - fonts[font.family] = font; - draft.story.fonts = fonts; + draft.story.fonts[font.family] = font; }; export default produce(updateStoryFonts); From 32c114aa11754869799346c39e0229a44aed87ea Mon Sep 17 00:00:00 2001 From: Tim Arney Date: Sat, 5 Nov 2022 09:09:29 -0400 Subject: [PATCH 03/36] clean font props when saving --- packages/output/src/story.js | 2 +- packages/output/src/utils/fontDeclarations.js | 5 ++-- .../story/utils/cleanElementFontProperties.js | 25 +++++++++++++++++++ .../app/story/utils/getStoryPropsToSave.js | 5 ++++ 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 packages/story-editor/src/app/story/utils/cleanElementFontProperties.js diff --git a/packages/output/src/story.js b/packages/output/src/story.js index 261e0921fd99..c8da196374db 100644 --- a/packages/output/src/story.js +++ b/packages/output/src/story.js @@ -65,7 +65,7 @@ function OutputStory({ {ampExtensions.map(({ name, src }) => (