import { get } from "lodash"; import { ComponentTypes } from "@sc/plugins/webcomponents/v2/types"; import { AddTypes } from "./components/EditorObject/EditorObject"; import { EditorObjectState } from "./components/EditorObject/types"; import { EditorActions, CURSOR_ID } from "./types"; // import { recursivelyRemoveItem } from "./actions"; const defaultCursorContent = { type: ComponentTypes.CURSOR, id: CURSOR_ID, parent: false, style: false, }; interface IContent { past: any[]; present: any; future: any[]; } export const contentReducer = (content: IContent, { type, payload }) => { switch (type) { /** * Changes the state of the provided item and returns a modified content array * @params */ case EditorActions.CHANGE_STATE: { const { id, state } = payload; if (!id) return content; // 1. Find the location in the array where the item is const key = content.present.findIndex((itm) => itm.id === id); if (key === -1) return content; // 2. Remove the state field for all items in the content array with that state const contentWithoutStateField = content.present.map((itm) => ({ ...itm, state: itm.state === state ? EditorObjectState.NORMAL : itm.state, })); // 3. Reconstruct the content array, but modifify the state of the specific object const updatedContent = [ ...contentWithoutStateField.slice(0, key), { ...contentWithoutStateField[key], state }, ...contentWithoutStateField.slice(key + 1), ]; if ( state === EditorObjectState.HOVER && content.present[key].state === EditorObjectState.ACTIVE ) return content; if ( state === EditorObjectState.NORMAL && content.present[key].state === EditorObjectState.ACTIVE ) return content; // 4. Remove any cursor still showing const contentWithoutCursors = updatedContent.filter( (itm) => itm.id !== CURSOR_ID ); // 5. Return the updated content return { past: content.past, present: contentWithoutCursors, future: content.future, }; } case EditorActions.CLEAR_ALL_STATES: { // console.log("CLEAR_ALL_STATES"); return { past: content.past, present: content.present.map((itm) => ({ ...itm, state: EditorObjectState.NORMAL, })), future: content.future, }; } /** * Moves an item in the content array to another location in the content array * @param id1 The id of the content object (the source) that you want to move * @param id2 The id of the content object (the destination) that you want to move id1 under (or next to) * @param content The array of all the content in the page */ case EditorActions.MOVE_THIS_BY_THAT: { const { id1, id2 } = payload; // Cancel the move if the ids are the same if (id1 === id2) return content; // Find where in the content array those items are const keyOfId1 = content.present.findIndex((itm) => itm.id === id1); const keyOfId2 = content.present.findIndex((itm) => itm.id === id2); const id1content = content.present[keyOfId1]; // Cancel if any of the id's do not exist if (keyOfId1 === -1 || keyOfId2 === -2) return content; // Remove the first id const contentAfterId1IsRemoved = [ ...content.present.slice(0, keyOfId1), ...content.present.slice(keyOfId1 + 1), ]; // Add the first id after the second id const parent = keyOfId2 > -2 ? content.present[keyOfId2].parent : false; // const parent = false; const contentAfterId2IsAdded = [ ...contentAfterId1IsRemoved.slice(0, keyOfId2), { ...id1content, parent }, ...contentAfterId1IsRemoved.slice(keyOfId2), ]; // Remove any cursors and return the updated content return { past: [...content.past, content.present], present: contentAfterId2IsAdded.filter((itm) => itm.id !== CURSOR_ID), future: [], }; } /** * Adds a cursor above or below the item matching the provided id * TODO: add left and right positing too */ case EditorActions.ADD_HOVER_CURSOR: { const { id, addType } = payload; if (!id) return content; // 0. If there's already a cursor in that position, ignore const k = content.present.findIndex((itm) => itm.id === id); if (k > -1 && get(content.present, `${k + 1}.id`, false) === CURSOR_ID) return content; // 1. Remove all existing curosrs const contentWithoutCursors = content.present.filter( (itm) => itm.id !== CURSOR_ID ); // 2. Find the location in the content array where the item is const key = contentWithoutCursors.findIndex((itm) => itm.id === id); if (key === -1) return content.present; const i = addType === AddTypes.AFTER ? 1 : 0; // 3. Add the cursor in that position const theCursorsParentId = addType === AddTypes.INSIDE ? id : contentWithoutCursors[key].parent; const cursorContent = { ...defaultCursorContent, parent: theCursorsParentId, }; // 4. Change the state of the parent container to be in a hover state let contentWithParentHover = contentWithoutCursors; if (theCursorsParentId) { const cursorParentKey = contentWithoutCursors.findIndex( (itm) => itm.id === theCursorsParentId ); const parentContent = contentWithoutCursors[cursorParentKey]; // a. remove the state field for all items in the content array with that state const noHoverStateContent = contentWithoutCursors.map((itm) => { const { state, ...stateRemoved } = itm; return stateRemoved; }); // b. reconstruct the content array, but modify the state of the specific object contentWithParentHover = [ ...noHoverStateContent.slice(0, cursorParentKey), { ...parentContent, state: EditorObjectState.HOVER }, ...noHoverStateContent.slice(cursorParentKey + 1), ]; } // 5. Return the updated content // return contentWithoutCursors; const returnContent = [ ...contentWithParentHover.slice(0, key + i), cursorContent, ...contentWithParentHover.slice(key + i), ]; return { past: content.past, present: returnContent, future: content.future, }; } case EditorActions.REMOVE_ITEM: { const { id } = payload; if (!id) return content; // return content.filter((itm) => itm.id !== id); let removedContent = []; const recursivelyRemoveItem = (rrContent, id) => { removedContent = rrContent.filter((itm) => itm.id !== id); // everything without the item removedContent .filter((itm) => itm.parent === id) .forEach((itm) => { removedContent = recursivelyRemoveItem(removedContent, itm.id); }); return removedContent; }; const key = content.present.findIndex((itm) => itm.id === id); if (content.present[key].parent) { return { past: [...content.past, content.present], present: recursivelyRemoveItem(content.present, id), future: [], }; } return content; } case EditorActions.DUPLICATE_ITEM: { const { id } = payload; // Get the key of the id const key = content.present.findIndex((itm) => itm.id === id); const generateId = () => Math.random() .toString(36) .slice(2); // Create a duplicate version of the item, but only modify the id const newId = generateId(); const originalItem = { ...content.present[key], state: EditorObjectState.NORMAL, id: newId, }; let duplicateItemCollection = [originalItem]; const recursivelyDuplicate = (id, rdContent, parent) => { // 1. Get the chldren of the provided object const children = rdContent.filter((itm) => itm.parent === id); // 2. Loop through each child children.forEach((child) => { // 3. Add a modified object to the class level array const myNewId = generateId(); duplicateItemCollection = [ ...duplicateItemCollection, { ...child, state: EditorObjectState.NORMAL, id: myNewId, parent }, ]; // 4. Call recursively duplicate on child object recursivelyDuplicate(child.id, rdContent, myNewId); }); // 5. Return the class level array so that it can be added to the state return duplicateItemCollection; }; const duplicateItem = recursivelyDuplicate(id, content.present, newId); const returnContent = [ ...content.present.slice(0, key + 1), ...duplicateItem, ...content.present.slice(key + 1), ]; return { past: [...content.past, content.present], present: returnContent, future: [], }; } case EditorActions.UPDATE_CONTENT: { return { past: [...content.past, content.present], present: payload, future: [], }; } case EditorActions.UNDO: { const { past, present, future } = content; const previous = past[past.length - 1]; const newPast = past.slice(0, past.length - 1); if (past.length) return { past: newPast, present: previous, future: [present, ...future], }; else return content; } case EditorActions.REDO: { const { past, present, future } = content; const next = future[0]; const newFuture = future.slice(1); if (future.length) return { past: [...past, present], present: next, future: newFuture, }; } default: return content; } };