fix: fixes image export reading non-current scene

This commit is contained in:
Mark Mankarious
2023-10-30 17:25:37 +00:00
parent 68c11e5a1d
commit d54b485565
9 changed files with 125 additions and 45 deletions

View File

@@ -30,6 +30,7 @@ import {
VIEW_DEFAULTS
} from 'src/config';
import { createView } from 'src/stores/reducers';
import { useView } from 'src/hooks/useView';
import { useIconCategories } from './hooks/useIconCategories';
const App = ({
@@ -53,6 +54,7 @@ const App = ({
return state.actions;
});
const { setIconCategoriesState } = useIconCategories();
const { changeView } = useView();
useEffect(() => {
if (initialData?.zoom) {
@@ -100,9 +102,10 @@ const App = ({
prevInitialData.current = fullInitialData;
modelActions.set(fullInitialData);
uiActions.setView(fullInitialData.views[0].id);
changeView(fullInitialData.views[0].id, fullInitialData);
setIsReady(true);
}, [initialData, modelActions, uiActions]);
}, [initialData, modelActions, uiActions, changeView]);
useEffect(() => {
setIconCategoriesState();

View File

@@ -14,7 +14,8 @@ import {
downloadFile as downloadFileUtil,
getTileScrollPosition,
base64ToBlob,
generateGenericFilename
generateGenericFilename,
modelFromModelStore
} from 'src/utils';
import { ModelStore } from 'src/types';
import { useDiagramUtils } from 'src/hooks/useDiagramUtils';
@@ -36,14 +37,7 @@ export const ExportImageDialog = ({ onClose, quality = 4 }: Props) => {
return state.actions;
});
const model = useModelStore((state): Omit<ModelStore, 'actions'> => {
return {
title: state.title,
version: state.version,
items: state.items,
icons: state.icons,
views: state.views,
colors: state.colors
};
return modelFromModelStore(state);
});
const unprojectedBounds = useMemo(() => {

View File

@@ -9,12 +9,11 @@ import {
FolderOpen as FolderOpenIcon,
DeleteOutline as DeleteOutlineIcon
} from '@mui/icons-material';
import { Model } from 'src/types/model';
import { UiElement } from 'src/components/UiElement/UiElement';
import { IconButton } from 'src/components/IconButton/IconButton';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { useModelStore } from 'src/stores/modelStore';
import { exportAsJSON } from 'src/utils';
import { exportAsJSON, modelFromModelStore } from 'src/utils';
import { INITIAL_DATA } from 'src/config';
import { MenuItem } from './MenuItem';
@@ -27,7 +26,7 @@ export const MainMenu = () => {
return state.mainMenuOptions;
});
const model = useModelStore((state) => {
return state;
return modelFromModelStore(state);
});
const modelActions = useModelStore((state) => {
return state.actions;
@@ -76,17 +75,7 @@ export const MainMenu = () => {
}, [uiStateActions, modelActions]);
const onExportAsJSON = useCallback(async () => {
const payload: Model = {
icons: model.icons,
colors: model.colors,
items: model.items,
title: model.title,
version: model.version,
views: model.views,
description: model.description
};
exportAsJSON(payload);
exportAsJSON(model);
uiStateActions.setIsMainMenuOpen(false);
}, [model, uiStateActions]);

34
src/hooks/useView.ts Normal file
View File

@@ -0,0 +1,34 @@
import { useCallback } from 'react';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { useModelStore } from 'src/stores/modelStore';
import { useSceneStore } from 'src/stores/sceneStore';
import { syncScene } from 'src/stores/reducers';
import { Model } from 'src/types';
export const useView = () => {
const modelActions = useModelStore((state) => {
return state.actions;
});
const uiStateActions = useUiStateStore((state) => {
return state.actions;
});
const sceneActions = useSceneStore((state) => {
return state.actions;
});
const changeView = useCallback(
(viewId: string, model: Model) => {
const newState = syncScene(viewId, model);
sceneActions.set(newState.scene);
modelActions.set(newState.model);
uiStateActions.setView(viewId);
},
[uiStateActions, sceneActions, modelActions]
);
return {
changeView
};
};

View File

@@ -23,7 +23,10 @@ export const Connector: ModeActions = {
)
return;
const connector = getItemByIdOrThrow(scene.connectors, uiState.mode.id);
const connector = getItemByIdOrThrow(
scene.currentView.connectors ?? [],
uiState.mode.id
);
const itemAtTile = getItemAtTile({
tile: uiState.mouse.position.tile,

View File

@@ -54,9 +54,8 @@ export const updateConnector = (
viewId: string,
state: State
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
const { connectors } = draft.model.views[view.index];
if (!connectors) return;
@@ -67,8 +66,8 @@ export const updateConnector = (
if (updates.anchors) {
const stateAfterSync = syncConnector(id, viewId, draft);
draft.scene = stateAfterSync.scene;
draft.model = stateAfterSync.model;
draft.scene = stateAfterSync.scene;
}
});
@@ -80,9 +79,8 @@ export const createConnector = (
viewId: string,
state: State
): State => {
const view = getItemByIdOrThrow(state.model.views, viewId);
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
const { connectors } = draft.model.views[view.index];
if (!connectors) {
@@ -90,7 +88,11 @@ export const createConnector = (
} else {
draft.model.views[view.index].connectors?.unshift(newConnector);
}
const stateAfterSync = syncConnector(newConnector.id, viewId, draft);
draft.model = stateAfterSync.model;
draft.scene = stateAfterSync.scene;
});
return syncConnector(newConnector.id, viewId, newState);
return newState;
};

View File

@@ -8,6 +8,29 @@ import {
} from 'src/config';
import { State } from './types';
export const syncTextBox = (
id: string,
viewId: string,
state: State
): State => {
const newState = produce(state, (draft) => {
const view = getItemByIdOrThrow(draft.model.views, viewId);
const textBox = getItemByIdOrThrow(view.value.textBoxes ?? [], id);
const width = getTextWidth(textBox.value.content, {
fontSize: textBox.value.fontSize ?? TEXTBOX_DEFAULTS.fontSize,
fontFamily: DEFAULT_FONT_FAMILY,
fontWeight: TEXTBOX_FONT_WEIGHT
});
const height = 1;
const size = { width, height };
draft.scene.textBoxes[textBox.value.id] = { size };
});
return newState;
};
export const updateTextBox = (
id: string,
updates: Partial<TextBox>,
@@ -26,18 +49,9 @@ export const updateTextBox = (
textBoxes[textBox.index] = newTextBox;
if (updates.content !== undefined || updates.fontSize !== undefined) {
const width = getTextWidth(updates.content ?? textBox.value.content, {
fontSize:
updates.fontSize ??
textBox.value.fontSize ??
TEXTBOX_DEFAULTS.fontSize,
fontFamily: DEFAULT_FONT_FAMILY,
fontWeight: TEXTBOX_FONT_WEIGHT
});
const height = 1;
const size = { width, height };
draft.scene.textBoxes[newTextBox.id] = { size };
const stateAfterSync = syncTextBox(newTextBox.id, viewId, draft);
draft.model = stateAfterSync.model;
draft.scene = stateAfterSync.scene;
}
});

View File

@@ -1,6 +1,35 @@
import { produce } from 'immer';
import { View } from 'src/types';
import { getItemByIdOrThrow } from 'src/utils';
import { State } from './types';
import { syncConnector } from './connector';
import { syncTextBox } from './textBox';
export const syncScene = (viewId: string, model: State['model']) => {
const view = getItemByIdOrThrow(model.views, viewId);
const startingState: State = {
model,
scene: {
connectors: {},
textBoxes: {}
}
};
const stateAfterConnectorsSynced = [
...(view.value.connectors ?? [])
].reduce<State>((acc, connector) => {
return syncConnector(connector.id, viewId, acc);
}, startingState);
const stateAfterTextBoxesSynced = [
...(view.value.textBoxes ?? [])
].reduce<State>((acc, textBox) => {
return syncTextBox(textBox.id, viewId, acc);
}, stateAfterConnectorsSynced);
return stateAfterTextBoxesSynced;
};
export const createView = (
newView: View,

View File

@@ -1,5 +1,5 @@
import { produce } from 'immer';
import { Model } from 'src/types';
import { Model, ModelStore } from 'src/types';
import { validateModel } from 'src/schemas/validation';
import { getItemByIdOrThrow } from './common';
@@ -52,3 +52,15 @@ export const fixModel = (model: Model): Model => {
return acc;
}, model);
};
export const modelFromModelStore = (modelStore: ModelStore): Model => {
return {
version: modelStore.version,
title: modelStore.title,
description: modelStore.description,
colors: modelStore.colors,
icons: modelStore.icons,
items: modelStore.items,
views: modelStore.views
};
};