Skip to content

Commit d2a9a76

Browse files
author
Morten Barklund
authored
Properly implement default background element (#1798)
* Split `duplicatePage` from `createPage` and support new props * Implemented, documented and tested new default background behavior * Changed story reducer to new background element structure * Fix allowed properties per page and element * Added new migration for default background elements * Updated all references to background element ids * Fixed new background element rules in combineElements and added test of that * Update selection after merge * Fix some missing test coverage * Fix test comment * Updated logic around dragging directly from library to match creating element and then dragging * Fix test comment
1 parent dfd265c commit d2a9a76

45 files changed

Lines changed: 1327 additions & 532 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

assets/src/edit-story/app/story/effects/usePageBackgrounds.js

Lines changed: 0 additions & 60 deletions
This file was deleted.

assets/src/edit-story/app/story/storyProvider.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import useLoadStory from './effects/useLoadStory';
2929
import useSaveStory from './actions/useSaveStory';
3030
import useHistoryEntry from './effects/useHistoryEntry';
3131
import useHistoryReplay from './effects/useHistoryReplay';
32-
import usePageBackgrounds from './effects/usePageBackgrounds';
3332
import useStoryReducer from './useStoryReducer';
3433
import useDeleteStory from './actions/useDeleteStory';
3534
import useAutoSave from './actions/useAutoSave';
@@ -93,13 +92,6 @@ function StoryProvider({ storyId, children }) {
9392
useHistoryEntry({ pages, current, selection, story, capabilities });
9493
useHistoryReplay({ restore });
9594

96-
// Ensure all pages have a background element at all times
97-
usePageBackgrounds({
98-
currentPage,
99-
setBackgroundElement: api.setBackgroundElement,
100-
addElement: api.addElement,
101-
});
102-
10395
// This action allows the user to save the story
10496
// (and it will have side-effects because saving can update url and status,
10597
// thus the need for `updateStory`)

assets/src/edit-story/app/story/useStoryReducer/actions.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ const updateSelectedElements = (dispatch) => ({ properties }) =>
8686
payload: { elementIds: null, properties },
8787
});
8888

89-
const combineElements = (dispatch) => ({ firstId, secondId }) =>
89+
const combineElements = (dispatch) => ({ firstId, firstElement, secondId }) =>
9090
dispatch({
9191
type: types.COMBINE_ELEMENTS,
92-
payload: { firstId, secondId },
92+
payload: { firstId, firstElement, secondId },
9393
});
9494

9595
const setBackgroundElement = (dispatch) => ({ elementId }) =>

assets/src/edit-story/app/story/useStoryReducer/reducers/addPage.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { isInsideRange } from './utils';
2727
*
2828
* Current page will be updated to point to the newly inserted page.
2929
*
30+
* New page must have at least one element, the default background element.
31+
*
3032
* Selection is cleared.
3133
*
3234
* @param {Object} state Current state
@@ -45,10 +47,12 @@ function addPage(state, { page, position }) {
4547

4648
const { id } = page;
4749

48-
// Ensure new page has elements array and background id
50+
// Ensure new page has at least one element
51+
if (!page.elements?.length >= 1) {
52+
return state;
53+
}
54+
4955
const newPage = {
50-
elements: [],
51-
backgroundElementId: null,
5256
backgroundOverlay: OverlayType.NONE,
5357
...page,
5458
};

assets/src/edit-story/app/story/useStoryReducer/reducers/arrangeElement.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,20 @@ function arrangeElement(state, { elementId, position }) {
6262

6363
const page = state.pages[pageIndex];
6464

65-
// Abort if there's less than two elements (nothing to rearrange)
66-
if (page.elements.length < 2) {
65+
// Abort if there's less than three elements (nothing to rearrange as first is bg)
66+
if (page.elements.length < 3) {
6767
return state;
6868
}
6969

7070
const currentPosition = page.elements.findIndex(
7171
({ id }) => id === idToArrange
7272
);
7373

74-
if (currentPosition === -1 || page.backgroundElementId === idToArrange) {
74+
if (currentPosition === -1 || page.elements[0].id === idToArrange) {
7575
return state;
7676
}
7777

78-
const minPosition = page.backgroundElementId ? 1 : 0;
78+
const minPosition = 1;
7979
const maxPosition = page.elements.length - 1;
8080
const newPosition = getAbsolutePosition({
8181
currentPosition,

assets/src/edit-story/app/story/useStoryReducer/reducers/combineElements.js

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,53 +15,124 @@
1515
*/
1616

1717
/**
18-
* Internal dependencies
18+
* External dependencies
1919
*/
20-
import deleteElements from './deleteElements';
21-
import updateElements from './updateElements';
20+
import { v4 as uuidv4 } from 'uuid';
2221

2322
/**
2423
* Combine elements by taking properties from a first item and
25-
* adding them to the given second item
24+
* adding them to the given second item, then remove first item.
25+
*
26+
*
27+
* First item must either to be the id of an existing element with
28+
* a resource (some media element, or be given as the properties of
29+
* an element (with a resource) that doesn't exist but whose properties
30+
* should be merged into the target element.
31+
*
32+
* If second item is background element, copy some extra properties not
33+
* relevant while background, but relevant if unsetting as background.
34+
*
35+
* If the second item is the default background element,
36+
* save a copy of the old element as appropriate and remove flag.
37+
*
38+
* Updates selection to only include the second item after merge.
2639
*
2740
* @param {Object} state Current state
2841
* @param {Object} payload Action payload
2942
* @param {string} payload.firstId Element to take properties from
43+
* @param {string} payload.firstElement Element with properties to merge
3044
* @param {string} payload.secondId Element to add properties to
3145
* @return {Object} New state
3246
*/
33-
function combineElements(state, { firstId, secondId }) {
34-
if (!firstId || !secondId) {
47+
function combineElements(state, { firstId, firstElement, secondId }) {
48+
if ((!firstId && !firstElement) || !secondId) {
3549
return state;
3650
}
3751

38-
const page = state.pages.find(({ id }) => id === state.current);
39-
const element = page.elements.find(({ id }) => id === firstId);
52+
const pageIndex = state.pages.findIndex(({ id }) => id === state.current);
53+
const page = state.pages[pageIndex];
54+
const elementPosition = page.elements.findIndex(({ id }) => id === firstId);
55+
const element =
56+
elementPosition > -1 ? page.elements[elementPosition] : firstElement;
57+
58+
const secondElementPosition = page.elements.findIndex(
59+
({ id }) => id === secondId
60+
);
61+
const secondElement = page.elements[secondElementPosition];
4062

41-
if (!element || !element.resource) {
63+
if (!element || !element.resource || !secondElement) {
4264
return state;
4365
}
4466

67+
const newPageProps = secondElement.isDefaultBackground
68+
? {
69+
defaultBackgroundElement: {
70+
...secondElement,
71+
// But generate a new ID for this temp background element
72+
id: uuidv4(),
73+
},
74+
}
75+
: {};
76+
4577
const {
4678
resource,
4779
scale = 100,
4880
focalX = 50,
4981
focalY = 50,
5082
isFill = false,
83+
width,
84+
height,
85+
x,
86+
y,
5187
} = element;
5288

53-
const updatedState = updateElements(state, {
54-
elementIds: [secondId],
55-
properties: {
56-
resource,
57-
type: resource.type,
58-
scale,
59-
focalX,
60-
focalY,
61-
isFill,
62-
},
63-
});
64-
return deleteElements(updatedState, { elementIds: [firstId] });
89+
const newElement = {
90+
...secondElement,
91+
resource,
92+
type: resource.type,
93+
scale,
94+
focalX,
95+
focalY,
96+
isFill,
97+
// Only remember these for backgrounds, as they're ignored while being background
98+
...(secondElement.isBackground
99+
? {
100+
width,
101+
height,
102+
x,
103+
y,
104+
}
105+
: {}),
106+
// Only unset if it was set
107+
...(secondElement.isDefaultBackground
108+
? { isDefaultBackground: false }
109+
: {}),
110+
};
111+
112+
// Elements are now
113+
const elements = page.elements
114+
// Remove first element if combining from existing id
115+
.filter(({ id }) => id !== firstId)
116+
// Update reference to second element
117+
.map((el) => (el.id === secondId ? newElement : el));
118+
119+
const newPage = {
120+
...page,
121+
elements,
122+
...newPageProps,
123+
};
124+
125+
const newPages = [
126+
...state.pages.slice(0, pageIndex),
127+
newPage,
128+
...state.pages.slice(pageIndex + 1),
129+
];
130+
131+
return {
132+
...state,
133+
pages: newPages,
134+
selection: [secondId],
135+
};
65136
}
66137

67138
export default combineElements;

assets/src/edit-story/app/story/useStoryReducer/reducers/deleteElements.js

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
/**
1818
* Internal dependencies
1919
*/
20-
import { OverlayType } from '../../../../utils/backgroundOverlay';
2120
import { intersect } from './utils';
2221

2322
/**
@@ -30,7 +29,8 @@ import { intersect } from './utils';
3029
*
3130
* If an empty list or a list of only unknown ids is given, state is unchanged.
3231
*
33-
* If any id to delete is the current background element, background element will be unset for the page.
32+
* If any id to delete is the current background element, background element will be unset for the page,
33+
* and default background element will be restored. Default background element cannot be removed.
3434
*
3535
* If any id to delete is in current selection, deleted ids are removed from selection.
3636
* Otherwise selection is unchanged.
@@ -53,41 +53,50 @@ function deleteElements(state, { elementIds }) {
5353

5454
const oldPage = state.pages[pageIndex];
5555
const pageElementIds = oldPage.elements.map(({ id }) => id);
56+
const backgroundElement = oldPage.elements[0];
57+
58+
const isDeletingBackground = idsToDelete.some(
59+
(id) => id === backgroundElement.id
60+
);
61+
const backgroundIsDefault = backgroundElement.isDefaultBackground;
62+
63+
const validDeletionIds =
64+
isDeletingBackground && backgroundIsDefault
65+
? idsToDelete.filter((id) => id !== backgroundElement.id)
66+
: idsToDelete;
5667

5768
// Nothing to delete?
58-
const hasAnythingToDelete = intersect(pageElementIds, idsToDelete).length > 0;
69+
const hasAnythingToDelete =
70+
intersect(pageElementIds, validDeletionIds).length > 0;
5971
if (!hasAnythingToDelete) {
6072
return state;
6173
}
6274

63-
const filteredElements = oldPage.elements.filter(
64-
(element) => !idsToDelete.includes(element.id)
75+
let newElements = oldPage.elements.filter(
76+
(element) => !validDeletionIds.includes(element.id)
6577
);
6678

79+
// Restore default background if non-default bg has been deleted.
80+
if (isDeletingBackground && !backgroundIsDefault) {
81+
newElements = [oldPage.defaultBackgroundElement, ...newElements];
82+
}
83+
6784
const newPage = {
6885
...oldPage,
69-
elements: filteredElements,
86+
elements: newElements,
7087
};
7188

72-
// Clear background if it has been deleted.
73-
if (
74-
Boolean(oldPage.backgroundElementId) &&
75-
idsToDelete.includes(oldPage.backgroundElementId)
76-
) {
77-
newPage.backgroundElementId = null;
78-
newPage.backgroundOverlay = OverlayType.NONE;
79-
}
80-
8189
const newPages = [
8290
...state.pages.slice(0, pageIndex),
8391
newPage,
8492
...state.pages.slice(pageIndex + 1),
8593
];
8694

8795
// This check is to make sure not to modify the selection array if no update is necessary.
88-
const wasAnySelected = intersect(state.selection, idsToDelete).length > 0;
96+
const wasAnySelected =
97+
intersect(state.selection, validDeletionIds).length > 0;
8998
const newSelection = wasAnySelected
90-
? state.selection.filter((id) => !idsToDelete.includes(id))
99+
? state.selection.filter((id) => !validDeletionIds.includes(id))
91100
: state.selection;
92101

93102
return {

assets/src/edit-story/app/story/useStoryReducer/reducers/selectElement.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ function selectElement(state, { elementId }) {
3030
}
3131

3232
const currentPage = state.pages.find(({ id }) => id === state.current);
33-
const isBackgroundElement = currentPage.backgroundElementId === elementId;
33+
const isBackgroundElement = currentPage.elements[0].id === elementId;
3434
const hasExistingSelection = state.selection.length > 0;
3535
// The bg element can't be added to non-empty selection
3636
if (isBackgroundElement && hasExistingSelection) {
3737
return state;
3838
}
3939

4040
// If bg element was already the (only) selection, set selection to new element only
41-
if (state.selection.includes(currentPage.backgroundElementId)) {
41+
if (state.selection.includes(currentPage.elements[0].id)) {
4242
return {
4343
...state,
4444
selection: [elementId],

0 commit comments

Comments
 (0)