Skip to content
Snippets Groups Projects
Commit 25e3f689 authored by Jesse Baker's avatar Jesse Baker Committed by Harumi Jang
Browse files

Issue #3486120 by jessebaker: Tighten up TS definitions around LayoutNodes/RootNodes etc

parent 355530a9
Branches
Tags
1 merge request!397#3486120 Introduce more accurate TS definitions
Pipeline #337208 passed
......@@ -20,23 +20,26 @@ export interface RootNode {
name?: string;
nodeType: 'root';
uuid: 'root';
children: LayoutNode[];
children: Node[];
}
export interface LayoutNode {
export interface Node {
name?: string;
uuid: UUID;
nodeType: 'slot' | 'component' | 'root';
nodeType: 'slot' | 'component';
type?: string;
children: LayoutNode[];
children: Node[];
props?: {} | undefined;
}
export interface LayoutModel {
layout: LayoutNode | RootNode;
export type LayoutNode = RootNode | Node;
export interface RootLayoutModel {
layout: RootNode;
model: ComponentModels;
}
export interface LayoutModelSliceState extends LayoutModel {
export interface LayoutModelSliceState extends RootLayoutModel {
initialized: boolean;
}
......@@ -75,7 +78,7 @@ type DuplicateNodePayload = {
type InsertMultipleNodesPayload = {
to: number[] | undefined;
layoutModel: LayoutModel;
layoutModel: RootLayoutModel;
/**
* Pass an optional UUID that will be assigned to the last, top level node being inserted. Allows you to define the UUID
* so that you can then do something with the newly inserted node using that UUID.
......@@ -118,7 +121,7 @@ export const layoutModelSlice = createSlice({
for (const uuid of removableModelsUuids) {
if (state.model[uuid]) delete state.model[uuid];
}
state.layout = removeNodeByUuid(state.layout, action.payload);
state.layout = removeNodeByUuid(state.layout, action.payload) as RootNode;
}),
duplicateNode: create.reducer(
(state, action: PayloadAction<DuplicateNodePayload>) => {
......@@ -146,7 +149,11 @@ export const layoutModelSlice = createSlice({
return;
}
nodePath[nodePath.length - 1]++;
state.layout = insertNodeAtPath(state.layout, nodePath, updatedNode);
state.layout = insertNodeAtPath(
state.layout,
nodePath,
updatedNode,
) as RootNode;
},
),
moveNode: create.reducer(
......@@ -166,12 +173,6 @@ export const layoutModelSlice = createSlice({
(state, action: PayloadAction<InsertMultipleNodesPayload>) => {
const { layoutModel, to, useUUID } = action.payload;
if (layoutModel.layout.nodeType !== 'root') {
console.error(
'Insert nodes should be passed a root layout node with children as a wrapper for the nodes you want to insert.',
);
}
if (!Array.isArray(to)) {
console.error(
`Cannot insert nodes. Invalid parameters: newNodes: ${layoutModel}, to: ${to}.`,
......@@ -180,7 +181,7 @@ export const layoutModelSlice = createSlice({
}
let updatedModel: ComponentModels = { ...state.model };
let newLayout: LayoutNode = _.cloneDeep(state.layout);
let newLayout: RootNode = _.cloneDeep(state.layout);
const rootNode = layoutModel.layout;
const model = layoutModel.model;
......@@ -285,7 +286,7 @@ export const addNewComponentToLayout =
// @todo Remove this limitation in https://www.drupal.org/project/experience_builder/issues/3467954
const initialData: ComponentModel = { name: payload.component.name };
const children: LayoutNode[] = [];
const children: Node[] = [];
const uuid = 'to_be_replaced';
// Populate the model data with the default values
......@@ -310,7 +311,7 @@ export const addNewComponentToLayout =
});
}
const layoutModel: LayoutModel = {
const layoutModel: RootLayoutModel = {
layout: {
children: [
{
......
import _ from 'lodash';
import type { ComponentModels, LayoutNode } from './layoutModelSlice';
import type {
ComponentModels,
LayoutNode,
Node,
RootNode,
} from './layoutModelSlice';
import { v4 as uuidv4 } from 'uuid';
// recurseNodes,
// findNodeByUuid,
// findNodePathByUuid,
// insertNodeAtPath,
// removeNodeByUuid,
// moveNodeToPath,
type NodeFunction = (
node: LayoutNode,
index: number,
......@@ -50,11 +48,11 @@ export function recurseNodes(
* @param uuid - The UUID of the node to find.
* @returns The found node or null if not found.
*/
export function findNodeByUuid(
node: LayoutNode,
uuid: string,
): LayoutNode | null {
export function findNodeByUuid(node: LayoutNode, uuid: string): Node | null {
if (node.uuid === uuid) {
if (node.nodeType === 'root') {
return null;
}
return node;
}
if (node.children) {
......@@ -110,7 +108,10 @@ export function findNodePathByUuid(
* @param uuid - The UUID of the node to remove.
* @returns A deep clone of the node with the node matching the uuid removed.
*/
export function removeNodeByUuid(node: LayoutNode, uuid: string): LayoutNode {
export function removeNodeByUuid<T extends LayoutNode>(
node: T,
uuid: string,
): T {
const newState = _.cloneDeep(node);
const path = findNodePathByUuid(newState, uuid);
......@@ -134,11 +135,11 @@ export function removeNodeByUuid(node: LayoutNode, uuid: string): LayoutNode {
* @param newNode - The new node to insert.
* @returns A deep clone of the node with the newNode inserted at path.
*/
export function insertNodeAtPath(
layoutNode: LayoutNode,
export function insertNodeAtPath<T extends LayoutNode>(
layoutNode: T,
path: number[],
newNode: LayoutNode,
): LayoutNode {
newNode: Node,
): T {
const newState = _.cloneDeep(layoutNode);
if (path.length === 0) {
......@@ -166,24 +167,24 @@ export function insertNodeAtPath(
newState.children[currentIndex],
restOfPath,
newNode,
);
) as Node;
return newState;
}
/**
* Move a node to a new path.
* @param node - The root node of the layout.
* @param rootNode - The root node of the layout.
* @param uuid - The UUID of the node to move.
* @param path - The path to move the node to.
* @returns A deep clone of the `node` with the node matching the `uuid` moved to the `path`.
* @returns A deep clone of the `rootNode` with the node matching the `uuid` moved to the `path`.
*/
export function moveNodeToPath(
layoutNode: LayoutNode,
rootNode: RootNode,
uuid: string,
path: number[],
): LayoutNode {
const child = findNodeByUuid(layoutNode, uuid);
): RootNode {
const child = findNodeByUuid(rootNode, uuid);
if (!child) {
throw new Error(`Node with UUID ${uuid} not found.`);
}
......@@ -193,7 +194,7 @@ export function moveNodeToPath(
child.uuid = child.uuid + '_remove';
// Insert the clone at toPath
const newState = insertNodeAtPath(layoutNode, path, clone);
const newState = insertNodeAtPath(rootNode, path, clone);
// Remove the original node by finding it by uuid (which is now `${child.uuid}_remove`)
return removeNodeByUuid(newState, child.uuid);
......@@ -236,22 +237,22 @@ export function getNodeDepth(layoutNode: LayoutNode, uuid: string | undefined) {
* @returns An updated model and an updated state.
*/
export function replaceUUIDsAndUpdateModel(
node: LayoutNode,
node: Node,
model: ComponentModels,
newUUID?: string,
): {
updatedNode: LayoutNode;
updatedNode: Node;
updatedModel: ComponentModels;
} {
const oldToNewUUIDMap: Record<string, string> = {};
const updatedModel: ComponentModels = {};
const replaceUUIDs = (
node: LayoutNode,
node: Node,
parentUuid?: string,
newUuid?: string,
): LayoutNode => {
const newNode: LayoutNode = { ...node, uuid: newUuid || uuidv4() };
): Node => {
const newNode: Node = { ...node, uuid: newUuid || uuidv4() };
if (newNode.nodeType === 'slot') {
newNode.uuid = `${parentUuid}-slot-${newNode.name}`;
}
......
// Need to use the React-specific entry point to import createApi
import { createApi } from '@reduxjs/toolkit/query/react';
import type { LayoutNode } from '@/features/layout/layoutModelSlice';
import type { RootLayoutModel } from '@/features/layout/layoutModelSlice';
import { baseQuery } from '@/services/baseQuery';
export interface LayoutResponse {
model: {};
layout: LayoutNode;
}
// Define a service using a base URL and expected endpoints
export const layoutApi = createApi({
reducerPath: 'layoutApi',
baseQuery,
endpoints: (builder) => ({
getLayoutById: builder.query<LayoutResponse, string>({
getLayoutById: builder.query<RootLayoutModel, string>({
query: (nodeId) => `api/layout/{entity_type}/${nodeId}`,
}),
}),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment