feat: implements lastUpdated field on views

This commit is contained in:
Mark Mankarious
2023-11-03 18:41:03 +00:00
parent e7c79f0b9b
commit 3cff06dc5e
16 changed files with 409 additions and 177 deletions

View File

@@ -31,7 +31,9 @@ export const DEFAULT_COLOR: Colors[0] = {
export const DEFAULT_FONT_FAMILY = 'Roboto, Arial, sans-serif';
export const VIEW_DEFAULTS: Required<Omit<View, 'id' | 'description'>> = {
export const VIEW_DEFAULTS: Required<
Omit<View, 'id' | 'description' | 'lastUpdated'>
> = {
name: 'Untitled view',
items: [],
connectors: [],
@@ -88,6 +90,10 @@ export const INITIAL_UI_STATE = {
offset: CoordsUtils.zero()
}
};
export const INITIAL_SCENE_STATE = {
connectors: {},
textBoxes: {}
};
export const MAIN_MENU_OPTIONS: MainMenuOptions = [
'ACTION.OPEN',
'EXPORT.JSON',

View File

@@ -1,13 +1,13 @@
import { useCallback, useState, useRef } from 'react';
import { InitialData, IconCollectionState } from 'src/types';
import { INITIAL_DATA, VIEW_DEFAULTS } from 'src/config';
import { INITIAL_DATA, INITIAL_SCENE_STATE } from 'src/config';
import {
generateId,
getFitToViewParams,
CoordsUtils,
categoriseIcons
categoriseIcons,
generateId
} from 'src/utils';
import { createView } from 'src/stores/reducers';
import * as reducers from 'src/stores/reducers';
import { useModelStore } from 'src/stores/modelStore';
import { useView } from 'src/hooks/useView';
import { useUiStateStore } from 'src/stores/uiStateStore';
@@ -43,13 +43,14 @@ export const useInitialDataManager = () => {
const initialData = _initialData;
if (initialData.views.length === 0) {
const updates = createView(
{
...VIEW_DEFAULTS,
id: generateId()
},
initialData
);
const updates = reducers.view({
action: 'CREATE_VIEW',
payload: {},
ctx: {
state: { model: initialData, scene: INITIAL_SCENE_STATE },
viewId: generateId()
}
});
Object.assign(initialData, updates);
}

View File

@@ -119,11 +119,11 @@ export const useScene = () => {
const createViewItem = useCallback(
(newViewItem: ViewItem) => {
const newState = reducers.createViewItem(
newViewItem,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'CREATE_VIEWITEM',
payload: newViewItem,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -131,12 +131,11 @@ export const useScene = () => {
const updateViewItem = useCallback(
(id: string, updates: Partial<ViewItem>) => {
const newState = reducers.updateViewItem(
id,
updates,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'UPDATE_VIEWITEM',
payload: { id, ...updates },
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -144,7 +143,11 @@ export const useScene = () => {
const deleteViewItem = useCallback(
(id: string) => {
const newState = reducers.deleteViewItem(id, currentViewId, getState());
const newState = reducers.view({
action: 'DELETE_VIEWITEM',
payload: id,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -152,11 +155,11 @@ export const useScene = () => {
const createConnector = useCallback(
(newConnector: Connector) => {
const newState = reducers.createConnector(
newConnector,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'CREATE_CONNECTOR',
payload: newConnector,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -164,12 +167,11 @@ export const useScene = () => {
const updateConnector = useCallback(
(id: string, updates: Partial<Connector>) => {
const newState = reducers.updateConnector(
id,
updates,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'UPDATE_CONNECTOR',
payload: { id, ...updates },
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -177,7 +179,11 @@ export const useScene = () => {
const deleteConnector = useCallback(
(id: string) => {
const newState = reducers.deleteConnector(id, currentViewId, getState());
const newState = reducers.view({
action: 'DELETE_CONNECTOR',
payload: id,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -185,11 +191,11 @@ export const useScene = () => {
const createTextBox = useCallback(
(newTextBox: TextBox) => {
const newState = reducers.createTextBox(
newTextBox,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'CREATE_TEXTBOX',
payload: newTextBox,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -197,12 +203,11 @@ export const useScene = () => {
const updateTextBox = useCallback(
(id: string, updates: Partial<TextBox>) => {
const newState = reducers.updateTextBox(
id,
updates,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'UPDATE_TEXTBOX',
payload: { id, ...updates },
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -210,7 +215,11 @@ export const useScene = () => {
const deleteTextBox = useCallback(
(id: string) => {
const newState = reducers.deleteTextBox(id, currentViewId, getState());
const newState = reducers.view({
action: 'DELETE_TEXTBOX',
payload: id,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -218,12 +227,11 @@ export const useScene = () => {
const createRectangle = useCallback(
(newRectangle: Rectangle) => {
const newState = reducers.createRectangle(
newRectangle,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'CREATE_RECTANGLE',
payload: newRectangle,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -231,12 +239,11 @@ export const useScene = () => {
const updateRectangle = useCallback(
(id: string, updates: Partial<Rectangle>) => {
const newState = reducers.updateRectangle(
id,
updates,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'UPDATE_RECTANGLE',
payload: { id, ...updates },
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -244,7 +251,11 @@ export const useScene = () => {
const deleteRectangle = useCallback(
(id: string) => {
const newState = reducers.deleteRectangle(id, currentViewId, getState());
const newState = reducers.view({
action: 'DELETE_RECTANGLE',
payload: id,
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]
@@ -252,12 +263,11 @@ export const useScene = () => {
const changeLayerOrder = useCallback(
(action: LayerOrderingAction, item: ItemReference) => {
const newState = reducers.changeLayerOrder(
action,
item,
currentViewId,
getState()
);
const newState = reducers.view({
action: 'CHANGE_LAYER_ORDER',
payload: { action, item },
ctx: { viewId: currentViewId, state: getState() }
});
setState(newState);
},
[getState, setState, currentViewId]

View File

@@ -1,8 +1,9 @@
import { useCallback } from 'react';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { useSceneStore } from 'src/stores/sceneStore';
import { syncScene } from 'src/stores/reducers';
import * as reducers from 'src/stores/reducers';
import { Model } from 'src/types';
import { INITIAL_SCENE_STATE } from 'src/config';
export const useView = () => {
const uiStateActions = useUiStateStore((state) => {
@@ -15,7 +16,12 @@ export const useView = () => {
const changeView = useCallback(
(viewId: string, model: Model) => {
const newState = syncScene(viewId, model);
const newState = reducers.view({
action: 'SYNC_SCENE',
payload: undefined,
ctx: { viewId, state: { model, scene: INITIAL_SCENE_STATE } }
});
sceneActions.set(newState.scene);
uiStateActions.setView(viewId);
},

View File

@@ -12,6 +12,7 @@ export const viewItemSchema = z.object({
export const viewSchema = z.object({
id,
lastUpdated: z.string().datetime().optional(),
name: constrainedStrings.name,
description: constrainedStrings.description.optional(),
items: z.array(viewItemSchema),

View File

@@ -1,21 +1,8 @@
// This file will be exported as it's own bundle (separate to the main bundle). This is because the main
// bundle requires `window` to be present and so can't be imported into a Node environment.
export const version = PACKAGE_VERSION;
export * as reducers from 'src/stores/reducers';
export { INITIAL_DATA } from 'src/config';
export { modelSchema } from 'src/schemas/model';
export const version = PACKAGE_VERSION;
export type {
IsoflowProps,
InitialData,
Model,
View,
Views,
ModelItems,
ViewItem,
Icon,
ModelItem,
Rectangle,
Colors,
Icons,
Connector
} from 'src/types';
export type { IsoflowProps, InitialData } from 'src/types';
export type * from 'src/types/model';

View File

@@ -1,7 +1,7 @@
import { produce } from 'immer';
import { model as modelFixture } from 'src/fixtures/model';
import { ItemReference } from 'src/types';
import { changeLayerOrder } from '../layerOrdering';
import * as reducers from 'src/stores/reducers';
const getModel = () => {
return produce(modelFixture, (draft) => {
@@ -37,9 +37,17 @@ describe('Layer ordering reducers works correctly', () => {
type: 'RECTANGLE',
id: 'rect3'
};
const result = changeLayerOrder('BRING_FORWARD', item, 'view1', {
model,
scene
const result = reducers.view({
action: 'CHANGE_LAYER_ORDER',
payload: {
action: 'BRING_FORWARD',
item
},
ctx: {
viewId: 'view1',
state: { model, scene }
}
});
expect(result.model.views[0].rectangles?.[1].id).toBe('rect3');
@@ -51,9 +59,17 @@ describe('Layer ordering reducers works correctly', () => {
type: 'RECTANGLE',
id: 'rect3'
};
const result = changeLayerOrder('BRING_TO_FRONT', item, 'view1', {
model,
scene
const result = reducers.view({
action: 'CHANGE_LAYER_ORDER',
payload: {
action: 'BRING_TO_FRONT',
item
},
ctx: {
viewId: 'view1',
state: { model, scene }
}
});
expect(result.model.views[0].rectangles?.[0].id).toBe('rect3');
@@ -65,9 +81,17 @@ describe('Layer ordering reducers works correctly', () => {
type: 'RECTANGLE',
id: 'rect1'
};
const result = changeLayerOrder('SEND_BACKWARD', item, 'view1', {
model,
scene
const result = reducers.view({
action: 'CHANGE_LAYER_ORDER',
payload: {
action: 'SEND_BACKWARD',
item
},
ctx: {
viewId: 'view1',
state: { model, scene }
}
});
expect(result.model.views[0].rectangles?.[1].id).toBe('rect1');
@@ -79,9 +103,17 @@ describe('Layer ordering reducers works correctly', () => {
type: 'RECTANGLE',
id: 'rect1'
};
const result = changeLayerOrder('SEND_TO_BACK', item, 'view1', {
model,
scene
const result = reducers.view({
action: 'CHANGE_LAYER_ORDER',
payload: {
action: 'SEND_TO_BACK',
item
},
ctx: {
viewId: 'view1',
state: { model, scene }
}
});
expect(result.model.views[0].rectangles?.[2].id).toBe('rect1');

View File

@@ -2,12 +2,11 @@ import { Connector } from 'src/types';
import { produce } from 'immer';
import { getItemByIdOrThrow, getConnectorPath, getAllAnchors } from 'src/utils';
import { validateConnector } from 'src/schemas/validation';
import { State } from './types';
import { State, ViewReducerContext } from './types';
export const deleteConnector = (
id: string,
viewId: string,
state: State
{ viewId, state }: ViewReducerContext
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
const connector = getItemByIdOrThrow(view.value.connectors ?? [], id);
@@ -20,7 +19,10 @@ export const deleteConnector = (
return newState;
};
export const syncConnector = (id: string, viewId: string, state: State) => {
export const syncConnector = (
id: string,
{ viewId, state }: ViewReducerContext
) => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
const connector = getItemByIdOrThrow(view.value.connectors ?? [], id);
@@ -32,7 +34,8 @@ export const syncConnector = (id: string, viewId: string, state: State) => {
});
if (issues.length > 0) {
const stateAfterDelete = deleteConnector(id, viewId, draft);
const stateAfterDelete = deleteConnector(id, { viewId, state: draft });
draft.scene = stateAfterDelete.scene;
draft.model = stateAfterDelete.model;
} else {
@@ -49,10 +52,8 @@ export const syncConnector = (id: string, viewId: string, state: State) => {
};
export const updateConnector = (
id: string,
updates: Partial<Connector>,
viewId: string,
state: State
{ id, ...updates }: { id: string } & Partial<Connector>,
{ state, viewId }: ViewReducerContext
): State => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
@@ -65,7 +66,11 @@ export const updateConnector = (
connectors[connector.index] = newConnector;
if (updates.anchors) {
const stateAfterSync = syncConnector(id, viewId, draft);
const stateAfterSync = syncConnector(newConnector.id, {
viewId,
state: draft
});
draft.model = stateAfterSync.model;
draft.scene = stateAfterSync.scene;
}
@@ -76,8 +81,7 @@ export const updateConnector = (
export const createConnector = (
newConnector: Connector,
viewId: string,
state: State
{ state, viewId }: ViewReducerContext
): State => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
@@ -89,7 +93,11 @@ export const createConnector = (
draft.model.views[view.index].connectors?.unshift(newConnector);
}
const stateAfterSync = syncConnector(newConnector.id, viewId, draft);
const stateAfterSync = syncConnector(newConnector.id, {
viewId,
state: draft
});
draft.model = stateAfterSync.model;
draft.scene = stateAfterSync.scene;
});

View File

@@ -1,7 +1,3 @@
export * from './connector';
export { view } from './view';
export * from './modelItem';
export * from './viewItem';
export * from './rectangle';
export * from './textBox';
export * from './view';
export * from './layerOrdering';
export { syncConnector } from './connector';

View File

@@ -1,13 +1,11 @@
import { produce } from 'immer';
import { ItemReference, LayerOrderingAction, View } from 'src/types';
import { getItemByIdOrThrow } from 'src/utils';
import { State } from './types';
import { State, ViewReducerContext } from './types';
export const changeLayerOrder = (
action: LayerOrderingAction,
item: ItemReference,
viewId: string,
state: State
{ action, item }: { action: LayerOrderingAction; item: ItemReference },
{ viewId, state }: ViewReducerContext
): State => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);

View File

@@ -1,13 +1,11 @@
import { produce } from 'immer';
import { Rectangle } from 'src/types';
import { getItemByIdOrThrow } from 'src/utils';
import { State } from './types';
import { State, ViewReducerContext } from './types';
export const updateRectangle = (
id: string,
updates: Partial<Rectangle>,
viewId: string,
state: State
{ id, ...updates }: { id: string } & Partial<Rectangle>,
{ viewId, state }: ViewReducerContext
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
@@ -26,8 +24,7 @@ export const updateRectangle = (
export const createRectangle = (
newRectangle: Rectangle,
viewId: string,
state: State
{ viewId, state }: ViewReducerContext
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
@@ -41,13 +38,15 @@ export const createRectangle = (
}
});
return updateRectangle(newRectangle.id, newRectangle, viewId, newState);
return updateRectangle(newRectangle, {
viewId,
state: newState
});
};
export const deleteRectangle = (
id: string,
viewId: string,
state: State
{ viewId, state }: ViewReducerContext
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
const rectangle = getItemByIdOrThrow(view.value.rectangles ?? [], id);

View File

@@ -1,13 +1,11 @@
import { produce } from 'immer';
import { TextBox } from 'src/types';
import { getItemByIdOrThrow, getTextBoxDimensions } from 'src/utils';
import { State } from './types';
import { State, ViewReducerContext } from './types';
export const syncTextBox = (
id: string,
viewId: string,
state: State
{ viewId, state }: ViewReducerContext
): State => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
@@ -22,10 +20,8 @@ export const syncTextBox = (
};
export const updateTextBox = (
id: string,
updates: Partial<TextBox>,
viewId: string,
state: State
{ id, ...updates }: { id: string } & Partial<TextBox>,
{ viewId, state }: ViewReducerContext
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
@@ -39,7 +35,11 @@ export const updateTextBox = (
textBoxes[textBox.index] = newTextBox;
if (updates.content !== undefined || updates.fontSize !== undefined) {
const stateAfterSync = syncTextBox(newTextBox.id, viewId, draft);
const stateAfterSync = syncTextBox(newTextBox.id, {
viewId,
state: draft
});
draft.model = stateAfterSync.model;
draft.scene = stateAfterSync.scene;
}
@@ -50,8 +50,7 @@ export const updateTextBox = (
export const createTextBox = (
newTextBox: TextBox,
viewId: string,
state: State
{ viewId, state }: ViewReducerContext
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
@@ -65,13 +64,12 @@ export const createTextBox = (
}
});
return updateTextBox(newTextBox.id, newTextBox, viewId, newState);
return updateTextBox(newTextBox, { viewId, state: newState });
};
export const deleteTextBox = (
id: string,
viewId: string,
state: State
{ viewId, state }: ViewReducerContext
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
const textBox = getItemByIdOrThrow(view.value.textBoxes ?? [], id);

View File

@@ -1,6 +1,89 @@
import { Model, Scene } from 'src/types';
import type * as viewReducers from './view';
import type * as viewItemReducers from './viewItem';
import type * as connectorReducers from './connector';
import type * as textBoxReducers from './textBox';
import type * as rectangleReducers from './rectangle';
import type * as layerOrderingReducers from './layerOrdering';
export interface State {
model: Model;
scene: Scene;
}
export interface ViewReducerContext {
viewId: string;
state: State;
}
type ViewReducerAction =
| {
action: 'SYNC_SCENE';
payload: undefined;
}
| {
action: 'CREATE_VIEW';
payload: Parameters<typeof viewReducers.createView>[0];
}
| {
action: 'UPDATE_VIEW';
payload: Parameters<typeof viewReducers.updateView>[0];
}
| {
action: 'CREATE_VIEWITEM';
payload: Parameters<typeof viewItemReducers.createViewItem>[0];
}
| {
action: 'UPDATE_VIEWITEM';
payload: Parameters<typeof viewItemReducers.updateViewItem>[0];
}
| {
action: 'DELETE_VIEWITEM';
payload: Parameters<typeof viewItemReducers.deleteViewItem>[0];
}
| {
action: 'CREATE_CONNECTOR';
payload: Parameters<typeof connectorReducers.createConnector>[0];
}
| {
action: 'UPDATE_CONNECTOR';
payload: Parameters<typeof connectorReducers.updateConnector>[0];
}
| {
action: 'DELETE_CONNECTOR';
payload: Parameters<typeof connectorReducers.deleteConnector>[0];
}
| {
action: 'SYNC_CONNECTOR';
payload: Parameters<typeof connectorReducers.syncConnector>[0];
}
| {
action: 'CREATE_TEXTBOX';
payload: Parameters<typeof textBoxReducers.createTextBox>[0];
}
| {
action: 'UPDATE_TEXTBOX';
payload: Parameters<typeof textBoxReducers.updateTextBox>[0];
}
| {
action: 'DELETE_TEXTBOX';
payload: Parameters<typeof textBoxReducers.deleteTextBox>[0];
}
| {
action: 'CREATE_RECTANGLE';
payload: Parameters<typeof rectangleReducers.createRectangle>[0];
}
| {
action: 'UPDATE_RECTANGLE';
payload: Parameters<typeof rectangleReducers.updateRectangle>[0];
}
| {
action: 'DELETE_RECTANGLE';
payload: Parameters<typeof rectangleReducers.deleteRectangle>[0];
}
| {
action: 'CHANGE_LAYER_ORDER';
payload: Parameters<typeof layerOrderingReducers.changeLayerOrder>[0];
};
export type ViewReducerParams = ViewReducerAction & { ctx: ViewReducerContext };

View File

@@ -1,41 +1,141 @@
import { produce } from 'immer';
import { View } from 'src/types';
import { getItemByIdOrThrow } from 'src/utils';
import { State } from './types';
import { VIEW_DEFAULTS, INITIAL_SCENE_STATE } from 'src/config';
import type { ViewReducerContext, State, ViewReducerParams } from './types';
import { syncConnector } from './connector';
import { syncTextBox } from './textBox';
import * as viewItemReducers from './viewItem';
import * as connectorReducers from './connector';
import * as textBoxReducers from './textBox';
import * as rectangleReducers from './rectangle';
import * as layerOrderingReducers from './layerOrdering';
export const syncScene = (viewId: string, model: State['model']) => {
const view = getItemByIdOrThrow(model.views, viewId);
export const updateViewTimestamp = (ctx: ViewReducerContext): State => {
const now = new Date().toISOString();
const newState = produce(ctx.state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, ctx.viewId);
view.value.lastUpdated = now;
});
return newState;
};
export const syncScene = ({ viewId, state }: ViewReducerContext): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
const startingState: State = {
model,
scene: {
connectors: {},
textBoxes: {}
}
model: state.model,
scene: INITIAL_SCENE_STATE
};
const stateAfterConnectorsSynced = [
...(view.value.connectors ?? [])
].reduce<State>((acc, connector) => {
return syncConnector(connector.id, viewId, acc);
return syncConnector(connector.id, { viewId, state: acc });
}, startingState);
const stateAfterTextBoxesSynced = [
...(view.value.textBoxes ?? [])
].reduce<State>((acc, textBox) => {
return syncTextBox(textBox.id, viewId, acc);
return syncTextBox(textBox.id, { viewId, state: acc });
}, stateAfterConnectorsSynced);
return stateAfterTextBoxesSynced;
};
export const createView = (
newView: View,
model: State['model']
): State['model'] => {
return produce(model, (draft) => {
draft.views.push(newView);
export const updateView = (
updates: Partial<Pick<View, 'name'>>,
ctx: ViewReducerContext
): State => {
const newState = produce(ctx.state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, ctx.viewId);
view.value = { ...view.value, ...updates };
});
return newState;
};
export const createView = (
newView: Partial<View>,
ctx: ViewReducerContext
): State => {
const newState = produce(ctx.state, (draft) => {
draft.model.views.push({
...VIEW_DEFAULTS,
id: ctx.viewId,
...newView
});
});
return newState;
};
export const view = ({ action, payload, ctx }: ViewReducerParams) => {
let newState: State;
switch (action) {
case 'SYNC_SCENE':
newState = syncScene(ctx);
break;
case 'CREATE_VIEW':
newState = createView(payload, ctx);
break;
case 'UPDATE_VIEW':
newState = updateView(payload, ctx);
break;
case 'CREATE_VIEWITEM':
newState = viewItemReducers.createViewItem(payload, ctx);
break;
case 'UPDATE_VIEWITEM':
newState = viewItemReducers.updateViewItem(payload, ctx);
break;
case 'DELETE_VIEWITEM':
newState = viewItemReducers.deleteViewItem(payload, ctx);
break;
case 'CREATE_CONNECTOR':
newState = connectorReducers.createConnector(payload, ctx);
break;
case 'UPDATE_CONNECTOR':
newState = connectorReducers.updateConnector(payload, ctx);
break;
case 'SYNC_CONNECTOR':
newState = connectorReducers.syncConnector(payload, ctx);
break;
case 'DELETE_CONNECTOR':
newState = connectorReducers.deleteConnector(payload, ctx);
break;
case 'CREATE_TEXTBOX':
newState = textBoxReducers.createTextBox(payload, ctx);
break;
case 'UPDATE_TEXTBOX':
newState = textBoxReducers.updateTextBox(payload, ctx);
break;
case 'DELETE_TEXTBOX':
newState = textBoxReducers.deleteTextBox(payload, ctx);
break;
case 'CREATE_RECTANGLE':
newState = rectangleReducers.createRectangle(payload, ctx);
break;
case 'UPDATE_RECTANGLE':
newState = rectangleReducers.updateRectangle(payload, ctx);
break;
case 'DELETE_RECTANGLE':
newState = rectangleReducers.deleteRectangle(payload, ctx);
break;
case 'CHANGE_LAYER_ORDER':
newState = layerOrderingReducers.changeLayerOrder(payload, ctx);
break;
default:
throw new Error('Invalid action.');
}
const withUpdatedTimeStamp = updateViewTimestamp({
state: newState,
viewId: ctx.viewId
});
return withUpdatedTimeStamp;
};

View File

@@ -2,14 +2,12 @@ import { produce } from 'immer';
import { ViewItem } from 'src/types';
import { getItemByIdOrThrow, getConnectorsByViewItem } from 'src/utils';
import { validateView } from 'src/schemas/validation';
import { State } from './types';
import { updateConnector, syncConnector } from './connector';
import { State, ViewReducerContext } from './types';
import * as reducers from './view';
export const updateViewItem = (
id: string,
updates: Partial<ViewItem>,
viewId: string,
state: State
{ id, ...updates }: { id: string } & Partial<ViewItem>,
{ viewId, state }: ViewReducerContext
): State => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
@@ -28,7 +26,11 @@ export const updateViewItem = (
);
const updatedConnectors = connectorsToUpdate.reduce((acc, connector) => {
return updateConnector(connector.id, connector, viewId, acc);
return reducers.view({
action: 'UPDATE_CONNECTOR',
payload: connector,
ctx: { viewId, state: acc }
});
}, draft);
draft.model.views[view.index].connectors =
@@ -50,9 +52,9 @@ export const updateViewItem = (
export const createViewItem = (
newViewItem: ViewItem,
viewId: string,
state: State
ctx: ViewReducerContext
): State => {
const { state, viewId } = ctx;
const view = getItemByIdOrThrow(state.model.views, viewId);
const newState = produce(state, (draft) => {
@@ -60,13 +62,12 @@ export const createViewItem = (
items.unshift(newViewItem);
});
return updateViewItem(newViewItem.id, newViewItem, viewId, newState);
return updateViewItem(newViewItem, { viewId, state: newState });
};
export const deleteViewItem = (
id: string,
viewId: string,
state: State
{ state, viewId }: ViewReducerContext
): State => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
@@ -80,7 +81,11 @@ export const deleteViewItem = (
);
const updatedConnectors = connectorsToUpdate.reduce((acc, connector) => {
return syncConnector(connector.id, viewId, acc);
return reducers.view({
action: 'SYNC_CONNECTOR',
payload: connector.id,
ctx: { viewId, state: acc }
});
}, draft);
draft.model.views[view.index].connectors =

View File

@@ -3,6 +3,8 @@ import {
iconSchema,
modelSchema,
modelItemSchema,
modelItemsSchema,
viewsSchema,
viewSchema,
viewItemSchema,
connectorSchema,
@@ -17,12 +19,12 @@ import { StoreApi } from 'zustand';
export { connectorStyleOptions } from 'src/schemas';
export type Model = z.infer<typeof modelSchema>;
export type ModelItems = Model['items'];
export type Views = Model['views'];
export type ModelItems = z.infer<typeof modelItemsSchema>;
export type Icon = z.infer<typeof iconSchema>;
export type Icons = z.infer<typeof iconsSchema>;
export type Colors = z.infer<typeof colorsSchema>;
export type ModelItem = z.infer<typeof modelItemSchema>;
export type Views = z.infer<typeof viewsSchema>;
export type View = z.infer<typeof viewSchema>;
export type ViewItem = z.infer<typeof viewItemSchema>;
export type ConnectorStyle = keyof typeof connectorStyleOptions;