feat(toolbar): new Toolbar to enable reactive state synchronization (#3983)

This commit is contained in:
Alireza authored and GitHub committed 2024-03-27 16:01:32 -04:00
1 parent 79d5c36bda
commit 566b25a544
155 files changed
+6311 -6577

No files matched your search

@@ -0,0 +1,106 @@
import React, { createContext, useContext, useReducer } from 'react';
export const initialState = {};
export const toolboxReducer = (state, action) => {
const { toolbarSectionId } = action.payload;
if (!state[toolbarSectionId]) {
state[toolbarSectionId] = { activeTool: null, toolOptions: {} };
}
switch (action.type) {
case 'SET_ACTIVE_TOOL':
return {
...state,
[toolbarSectionId]: {
...state[toolbarSectionId],
activeTool: action.payload.activeTool,
},
};
case 'UPDATE_TOOL_OPTION':
const { toolName, optionName, value } = action.payload;
return {
...state,
[toolbarSectionId]: {
...state[toolbarSectionId],
toolOptions: {
...state[toolbarSectionId].toolOptions,
[toolName]: state[toolbarSectionId].toolOptions[toolName].map(option =>
option.id === optionName ? { ...option, value } : option
),
},
},
};
case 'INITIALIZE_TOOL_OPTIONS':
// Initialize tool options for each toolbarSectionId
return {
...state,
[action.toolbarSectionId]: {
...state[action.toolbarSectionId],
toolOptions: action.payload,
},
};
default:
return state;
}
};
const ToolboxContext = createContext();
export const ToolboxProvider = ({ children }) => {
const [state, dispatch] = useReducer(toolboxReducer, initialState);
const handleToolSelect = (toolbarSectionId, toolName) => {
dispatch({
type: 'SET_ACTIVE_TOOL',
payload: { toolbarSectionId, activeTool: toolName },
});
};
const handleToolOptionChange = (toolbarSectionId, toolName, optionName, newValue) => {
dispatch({
type: 'UPDATE_TOOL_OPTION',
payload: { toolbarSectionId, toolName, optionName, value: newValue },
});
};
const initializeToolOptions = (toolbarSectionId, toolOptions) => {
dispatch({
type: 'INITIALIZE_TOOL_OPTIONS',
toolbarSectionId,
payload: toolOptions,
});
};
const api = { handleToolSelect, handleToolOptionChange, initializeToolOptions };
const value = { state, api };
return <ToolboxContext.Provider value={value}>{children}</ToolboxContext.Provider>;
};
/**
* Custom hook for accessing toolbox state and actions for a specific toolbar section.
* You can use this hook to access the state and actions for a specific toolbar section (
* defined by the toolbarSectionId) in your custom toolbar components. This hook
* helps to manage the state and actions for the tools and their options in the toolbar.
*/
export const useToolbox = toolbarSectionId => {
const context = useContext(ToolboxContext);
if (context === undefined) {
throw new Error('useToolbox must be used within a ToolboxProvider');
}
const { state, api } = context;
return {
state: state[toolbarSectionId] || { activeTool: null, toolOptions: {} },
api: {
handleToolSelect: toolName => api.handleToolSelect(toolbarSectionId, toolName),
handleToolOptionChange: (toolName, optionName, value) =>
api.handleToolOptionChange(toolbarSectionId, toolName, optionName, value),
initializeToolOptions: toolOptions =>
api.initializeToolOptions(toolbarSectionId, toolOptions),
},
};
};
@@ -0,0 +1,66 @@
export function toolboxReducer(state, action) {
let newState = { ...state };
const { payload } = action;
const { stateSyncService, buttonSectionId } = payload || {};
switch (action.type) {
case 'SET_ACTIVE_TOOL':
newState = {
...state,
activeTool: payload.activeTool,
};
break;
case 'UPDATE_TOOL_OPTION':
newState = {
...state,
toolOptions: {
...state.toolOptions,
[payload.toolName]: state.toolOptions[payload.toolName].map(option =>
option.id === payload.optionName ? { ...option, value: payload.value } : option
),
},
};
break;
case 'INITIALIZE_TOOL_OPTIONS':
// eslint-disable-next-line no-case-declarations
const newToolOptions = Object.keys(payload?.toolOptions || {}).reduce((acc, toolId) => {
const tool = payload.toolOptions[toolId];
if (state.toolOptions[toolId]) {
// Preserve existing options, potentially merging with new ones if necessary
acc[toolId] = state.toolOptions[toolId].map(existingOption => {
const initialOption = tool.find(option => option.id === existingOption.id);
return initialOption
? { ...initialOption, value: existingOption.value }
: existingOption;
});
} else {
acc[toolId] = tool;
}
return acc;
}, {});
newState = {
...state,
toolOptions: newToolOptions,
};
break;
default:
break;
}
// store the state in the stateSyncService
if (payload?.stateSyncService) {
const prevState = stateSyncService.getState()?.['uiStateStore']?.[buttonSectionId];
stateSyncService.store({
uiStateStore: {
...stateSyncService.getState()?.['uiStateStore'],
[buttonSectionId]: {
...prevState,
...newState,
},
},
});
}
return newState;
}